Кольцевой буфер


• О проекте
• Обратная связь
• Полезные ссылки
• Полезные программы
• Друзья сайта


Последние комментарии

Васил: Загрузчик для AVR микроконтроллеров
Ошибка #ifdef ST...

Алексей: Загрузчик для AVR микроконтроллеров
Да, есть такая фи...




           

Библиотека для AVR





AXLIB Генератор





Помощь сайту


   
				

Кольцевой буфер



	
Как не странно но микроконтроллер может не только выводить данные в порт и читать пришедшие в него но и пользоваться разными шинами данных для тех же целей, а именно прием и передача данных. К таким шинам можно отнести UART, SPI, I2C. Объеденяет эти шины в основном один регистр данных, не важно как он у кого называется, важно что он есть и в него можно положить данные для передачи и также забрать при приеме. Затрагивать работу шин данных в этой статье я не буду, а вот про один из хитроумных вариантов получения данных я расскажу. И так, кольцевой буфер. Что в нем кольцевого? А вот что, представьте себе что МК подключен к шине RS-485 и по ней получает инструкции для управления чего-либо. Для этих целей МК использует UART. Но вот вопрос, не сидеть же в бесконечном цикле для ожидания очередной команды. Не проще это все переложить на аппаратную часть МК. Ну например включить прерывание по приходу байта данных. Все конечно хорошо если должен прийти один байт, а если на линии реализован MODBUS? Что тогда? Как принять пакет данных и не пропустить ничего? Вот тут нам и поможет кольцевой буфер. Так все же почему кольцевой? Да потому что данные пишутся в него по кольцу, новые затирают старые. А как в этом хаосе разобраться я сейчас расскажу. Для начала нам нужно выделить место для нашего буфера. Для этого мы просто инициализируем однобайтовый массив. (Ведь UART пересылает по одному байту, не считая 9-и битной экзотики). А сколько ячеек памяти нужно выделить? Вот тут есть два пути. Первый. В зависимости от передаваемого пакета выделяем места чуть больше максимума приема но с одной оговоркой, размер должен быть степенью двойки. 2, 4, 8, 16, 32, 64, 128, 256 для примера. Зачем так, я потом расскажу. Второй. Опять же в зависимости от длинны пакета но уже любое количество от 1 до 256. Далее чтоб разбираться где начало данных, а где конец, нам понадобятся еще две однобайтовые переменные. Эти переменные будут указывать на индекс ячейки массива начала данных и на индекс ячейки конца данных. Отсюда видно что если индексы равны, то данных нет, а если не равны, то данные присутствуют и их можно читать. То есть начальный индекс как бы догоняет конечный индекс и так по кругу. Теперь наверное стало понятно почему кольцевой и почему нас не интересует в какой конкретно ячейках лежат данные. Ну слова словами, а давайте ка все это в коде. #define BUFFER_SIZE 16 // Это мы задали длину буфера #define BUFFER_MASK (BUFFER_SIZE-1) // Это мы задали маску обнуления unsigned char buffer[BUFFER_SIZE]; // Это мы создали наш массив буфер unsigned char IndexStart; // Это мы создали переменную индекс начала данных unsigned char IndexEnd; // Это мы создали переменную индекс конца данных Теперь после того как мы подготовились нам потребуется создать несколько функций. Первая функция будет класть один байт в буфер. unsigned char InBuffer(unsigned char bite) { IndexEnd++; // Сдвигаем конец на шаг вперед для записи данных if(IndexEnd == IndexStart) return 1; // Если буфер переполнен, конец догнал начало, то все бросаем и возвращаем 1. Тем самым предотвращаем запись данных на начало еще не прочитанных данных. IndexEnd &= BUFFER_MASK; // Накладываем маску для обнуления индекса если он перевалил за 16. buffer[IndexEnd] = bite; // Если все хорошо то записываем байт в буфер. return 0; // Возвращаем ноль сигнализируя что все хорошо прошло. } Немного расскажу про маску обнуления. Выше я писал про два разных варианта задачи длинны массива. Вот как раз при условии длинны массива заданным степенью двойки мы накладываем маску. Объясняю на пальцах. Мы задали длину массива равную 16. Маску задали 16-1 что стало равно 15. Теперь давайте взглянем на число 15 в бинаре 0b00001111. Теперь представим что индекс конца буфера стал равен 15, а данные еще можно записывать. Индекс гордо увеличивается на единицу и становится равным 16, а это уже за пределами длинны нашего массива. Что делать? А давайте посмотрим на число 16 в бинаре 0b00010000. Ни на что не наводит? Вот наша масочка 0b00010000 & 0b00001111 = 0b00000000. Теперь понятно почему длину лучше выбирать кратно степени двойки. А если например длина равна 256, то тогда вообще ничего делать не надо, само обнулится. Но, а что делать при длине например 135. А ничего. пишем if(IndexEnd == 136) IndexEnd = 0; Все. Следующая функция будет вычитывать сразу строку свежеполученных данных. В качестве аргументов функция получает адрес первого элемента локального массива. Это массив в котором потом будут наши данные. И длину получаемых данных. void OutBufferStr(unsigned char*str, unsigned char lenght) { char i; for(i=0; i<lenght; i++) // Забираем данные из буфера нужное количество { IndexStart++; // Для движения по буферу увеличиваем начальный индекс IndexStart &= BUFFER_MASK; // Также обнуляемся маской если надо *str = buffer[IndexStart]; // Кладем байт в локальный массив по текущему адресу указателя ячейки массива. str++; // Увеличиваем адрес ячейки локального массива } } В двух словах что там происходит. Сначала внутри функции main() мы создаем локальный массив для получения данных из буфера. Далее в виде аргумента передаем адрес первой ячейки локального массива. Далее в массив по указателям записываются данные и выйдя из функции ми имеем локальный массив с данными. Такая заморочка нужна из-за того что функция не может вернуть массив. Следующая функция возвращает количество непрочитанных данных в буфере. unsigned char IndexNumber(void) { if (IndexEnd >= IndexStart) // Если конец больше чем начало { return (IndexEnd - IndexStart); // то возвращаем разницу } else { return ((BUFFER_SIZE - IndexStart) + IndexEnd); // А вот если начало обогнало конец, это когда начало перескочило через ноль, а конец еще не успел. То выполняем эту процедурку и возвращаем ее значение. } } Таким образом вызвав эту функцию мы получим либо ноль(буфер пуст), либо некое значение от 1 до 16(сколько байт пришло в буфер). А вот чтоб постоянно не вычислять количество данных пришли они или нет, мы напишем еще одну небольшую функцию которая вернет есть данные или нет. unsigned char GetData(void) { if(IndexEnd != IndexStart) return 1; return 0; } Здесь все просто, если конец не равен началу, значит данные пришли иначе ноль. Вот и все. Резюме: Для записи пришедших данных например по UART разрешаем прерывание по приходу данных и в обработчике прерывания вызываем функцию unsigned char InBuffer(unsigned char bite), а в качестве аргумента передаем UDR. Для того чтобы узнать пришли ли данные вызываем функцию unsigned char GetData(void). Вернет ноль, ничего нет, а если единицу то читаем. Для чтения данных создаем локальный массив далее вызываем функцию void OutBufferStr(unsigned char*str, unsigned char lenght), а в качестве аргументов передаем созданный локальный массив и длину данных которую получили из unsigned char IndexNumber(void). После этого в локальном массиве будут лежать данные который можно спокойно обрабатывать, а в это же время буфер будет наполнятся очередными данными. Вроде изложил идею самым доступным языком, если будут вопросы то я готов на них ответить.




Вверх


Рейтинг@Mail.ru Яндекс.Метрика