Учебный курс. Организация обмена по USART `у с использованием кольцевого буфера
Кольцевой буфер
#define SIZE_BUF 8
Для работы с буфером требуется как минимум три функции – очистить буфер, положить символ, взять символ.
//"очищает" буфер
void FlushBuf(void)
{
tail = 0;
head = 0;
count = 0;
}
//положить символ в буфер
void PutChar(unsigned char sym)
{
if (count < SIZE_BUF){ //если в буфере еще есть место
cycleBuf[tail] = sym; //помещаем в него символ
count++; //инкрементируем счетчик символов
tail++; //и индекс хвоста буфера
if (txBufTail == SIZE_BUF) txBufTail = 0;
}
}
//взять символ из буфера
unsigned char GetChar(void)
{
unsigned char sym = 0;
if (count > 0){ //если буфер не пустой
sym = cycleBuf[head]; //считываем символ из буфера
count--; //уменьшаем счетчик символов
head++; //инкрементируем индекс головы буфера
if (head == SIZE_BUF) head = 0;
}
return sym;
}
Как можно использовать кольцевой буфер
Обмен по USART/UART с использованием кольцевого буфера
За основу я взял предыдущий проект и существенно его переработал.
Добавил в функцию инициализации USART `а разрешение прерывания по завершению передачи. За это отвечает бит TXCIE регистра UCSRB.
Инициализация usart `a
void USART_Init(void){
UBRRH = 0;
UBRRL = 51; //скорость обмена 9600 бод
//разр. прерыв при приеме и передачи, разр приема, разр передачи.
UCSRB = (1<<RXCIE)|(1<<TXCIE)|(1<<RXEN)|(1<<TXEN);
//размер слова 8 разрядов
UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
}
Передающий кольцевой буфер
unsigned char usartTxBuf[SIZE_BUF];
unsigned char txBufTail = 0;
unsigned char txBufHead = 0;
unsigned char txCount = 0;
Возможно не лишним будет добавить перед объявлением массива usartTxBuf[] и переменной txCount ключевое слово volatile, поскольку эти переменные используются и в основной программе и в прерываниях. Но проект работал без нареканий и я оставил так.
Функция очистки передающего буфера
void USART_FlushTxBuf(void){
Функция загрузки символа в буфер
void USART_PutChar(unsigned char sym)
{
if(((UCSRA & (1<<UDRE)) != 0) && (txCount == 0)) UDR = sym;
else {
if (txCount < SIZE_BUF){ //если в буфере еще есть место
usartTxBuf[txBufTail] = sym; //помещаем в него символ
txCount++; //инкрементируем счетчик символов
txBufTail++; //и индекс хвоста буфера
if (txBufTail == SIZE_BUF) txBufTail = 0;
}
}
}
Функция загрузки строки в буфер
void USART_SendStr(unsigned char * data)
{
unsigned char sym;
while(*data){
sym = *data++;
USART_PutChar(sym);
}
}
Обработчик прерывания по завершению передачи
#pragma vector=USART_TXC_vect
__interrupt void usart_txc_my(void)
{
if (txCount > 0){ //если буфер не пустой
UDR = usartTxBuf[txBufHead]; //записываем в UDR символ из буфера
txCount--; //уменьшаем счетчик символов
txBufHead++; //инкрементируем индекс головы буфера
if (txBufHead == SIZE_BUF) txBufHead = 0;
}
}
Пока буфер содержит данные - обработчик прерывания будет считывать их из буфера и передавать по USART `у.
“Передающая часть” аналогична, только в прерывании данные принимаются, а для их чтения используется функция unsigned char USART_GetChar().
Полную версию программного модуля можно посмотреть по ссылкам ниже
usart.h
usart.c
Основную программу не привожу, она очень простая. Скачивайте нужный проект, смотрите, разбирайтесь, моделируйте в Proteus.
Размышления
Я долго думал, нужно ли в этом проект запрещать прерывания при работе с буферами в основной программе или нет и пришел к выводу, что не нужно. Единственная функция с которой могут возникнуть проблемы - функция очистки приемного буфера. Там уж никак не обойтись без запрещения прерываний. В остальных случаях порядок работы с переменными подобран таким образом, что прерывания не вызовут ошибок. Поправьте если ошибаюсь.
Файлы
Comments
Я в основном на прием делал так :
unsigned char pars;
while(rx_counter)
{
pars=getchar();putchar(pars);
'Здесь код порой достаточно приличный
};
rx_counter , getchar , putchar
образованы через CodeWizardAVR ( CodeVisionAVR )
То есть подаю один сигнал-горит красный, подаю другой сигнал-зеленый. ЗЫ Железо собрано, светики зажигать уже умею, спасибо!
if (data == 'g'){
OnLedGreen();OffLedRed();
}
if (data == 'r'){
OnLedRed();OffLedGreen();
}
или с помощью оператора switch
1)Можно обойтись без счетчика. Достаточно сравнивать индексы головы и хвоста. Неравенство означает наличие данных в буфере. Вам когда-нибудь нужно было знать, сколько конкретно данных в буфере? Мне нет. Достаточно просто знать - есть данные или нет.
2) Если поставить условие, что размер буфера должен быть степенью двойки, то txBufTail++;
if (txBufTail == SIZE_BUF) txBufTail = 0;
превращается в более эффективное
txBufTail = (txBufTail + 1) & (SIZE_BUF - 1));
В этом случае легко считать и количество данных в буфере (если понадобится): txCount = (txBufTail - txBufHead) & (SIZE_BUF - 1);
А пренебрегать volatile ни в коем случае не стоит.
Поясните пожалуйста: допустим txBufTail = 2 (0010), а
SIZE_BUF = 10 (1010)
Получается, что следующее значение
txBufTail = (0011)&(1001)=(0001)
13 Что то я не понимаю...
Извините, заработался.
Вот:
#define TX_BUFF_SIZE 8 // must be power of 2
static volatile char TxBuffer[TX_BUFF_SIZE];
static volatile uint8_t TxHead, TxTail;
#pragma vector = USART_UDRE_vect
__interrupt void Tx232Handler(vo id)
{
uint8_t Tmp = TxTail; // use local variable instead of volatile
UDR = TxBuffer[Tmp & (TX_BUFF_SIZE - 1)];
++Tmp;
TxTail = Tmp;
if(Tmp == TxHead) // all transmitted
{
UCSRB &= ~(1
__interrupt void Tx232Handler(vo id)
{
uint8_t Tmp = TxTail; // use local variable instead of volatile
UDR = TxBuffer[Tmp & (TX_BUFF_SIZE - 1)];
++Tmp;
TxTail = Tmp;
if(Tmp == TxHead) // all transmitted
{
UCSRB &= ~(1 shl UDRIE); // disable this int
}
}
void putchar_hard (char Byte)
{
uint8_t Tmp = TxHead;
while((uint8_t) (Tmp - TxTail) >= (uint8_t) TX_BUFF_SIZE)
; // buffer full, wait until symbol transmitted in interrupt
TxBuffer[ Tmp & (TX_BUFF_SIZE - 1) ] = Byte;
++Tmp;
__disable_inter rupt();
TxHead = Tmp;
UCSRB |= (1 shl UDRIE);
__enable_interrupt();
}
1)Можно обойтись, но со счетчиком код будет короче. Особенно если кольцевой буфер на структурах.
2)Согласен, эффективнее. Возьму на заметку. ;-)
TxBuffer[ Tmp & (TX_BUFF_SIZE - 1) ]
TxBuffer[ Tmp & (TX_BUFF_SIZE - 1) ] = Byte;
++Tmp;
Переменная Tmp инкрементируетс я и на нее накладывается битовая маска. Маска обрезает лишние разряды. Таким образом проверку if производить не нужно.
TxBuffer[Tmp] = Byte;
++Tmp;
if (Tmp == TX_BUFF_SIZE) Tmp = 0;
для меня непонятно работает.
Считываем массив, а возвращаем символ... Растолкуйте мне, как она работает.
Поэтому м посчитал, что выход из функции происходит только когда прочтется весь буфер и соответственно вернется только последнее значение.
char ch = 'a';
PutChar(ch);
Для передачи строки строят функцию
SendSrting(char *s) в качестве аргумента которой передают указатель на строку или буфер. В теле этой функции последовательно вызывается примитивная передать символ, столько раз, сколько нужно. Было бы неплохо в коде 1)или поправить названия функций 2)или переписать их содержание, т.к. в функции передать символ мы фактически передаем строку (буфер), что вносит изрядную сумятицу в неокрепшие еще умы. Некорректно это методологически .
А имеем ли мы право в примере от SAB в функции putchar делать так:
while((uint8_t) (Tmp - TxTail) >= (uint8_t) TX_BUFF_SIZE);
формально у нас может случиться перенос, и Head станет меньше Tail в нормальном состоянии, и программа повиснет. Похоже, что без счетчика байт в буфере не обойтись :( Поправьте меня если что не так.
Error[Pe020]: identifier "RXCIE" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 34
Error[Pe020]: identifier "TXCIE" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 34
Error[Pe020]: identifier "RXEN" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 34
Error[Pe020]: identifier "TXEN" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 34
Error[Pe020]: identifier "URSEL" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 35
Error[Pe020]: identifier "UCSZ1" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 35
Error[Pe020]: identifier "UCSZ0" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 35
Error[Pe020]: identifier "UDRE" is undefined D:\IAR_opit\USA RT2-IAR\USART2\ usart.c 58
ПОСТАНОВКА ВОПРОСА: как лучше реализовать обмен по UART - с прерываниями или без ( чтобы не было конфликта прерываний usb и uart ) Больше всего интересует со стороны принимающего мк. Как это вижу я. в цикле ждем завершения очередного приема, проверяя флаг. По завершении приема записываем полученное значение в регистр usb. usb сам с нужной ему частотой инициирует прерывание и передачу данных в ПК. Какие рекомендации и советы можете дать?
Error[Pe020]: identifier "SIZE_BUF" is undefined
немогу понять почему, вроде проект скопировал правильно проверял
возможно настроил неправельно.
Увеличить скорость обмена.
Quote:Quote: Предполагаю, что первой функцией в основном цикле получаем текущее значение
rxCount и если оно равно 0, то второй функцией очищаем буфер?? Зачем его очищать, если новые данные пишутся поверх старых?
Поясните, пож..
Тем не менее - в вашем примере для WINAvr не увидел использования вышеуказанных функций. Где они используются?
Код содержит не меньше одной ошибки.
Нет гарантии атомарности изменения переменых.
Например: ++Count может быть откомпилировано так:
увеличить регистр. INC r16
записать регистр в Count. STS Count,r16
Если между чтением и записью (между LDS и STS) произойдёт прерывание то любые изменения Count в этом прерывании будут потеряны.
1. Если передаваемые команды состоят не из одного байта, а из строки - "imp_X", "imp_Y" и т.д. то как их идентифицироват ь в приемном буфере, если он содержит уже десяток байт?
2. Если произойдет искажение передаваемой команды, можно отправить на передающую сторону об этом сообщение. Но ведь там передающий буфер уже вполне может быть заполнен другими данными, к тому же уже успеют измениться и значения головы, хвоста и счетчика передающего буфера. Т.е. практически непрошедшую команду повторить уже не выйдет?
txBufTail++;- а из-за такой бодяги вы просто вылазите за пределы массива. компилятору и МК конечто же все равно))) МК просто инкрементирует счетчик адресов
зачем по вашему там
if (txBufTail == SIZE_BUF) txBufTail = 0;
?
Я бы не согласился с Вами.
Пусть адрес начала буфера (Start_BUF) в памяти равен 25h. Размер буфера 0Ah-байт
Тогда, "хвост" и "голова" выставляются на начало буфера, а именно становятся равны 25h.
Вот тут как раз и получится что будет выход за границы буфера. И "хвост"\"голова " никогда не перейдёт с конца массива на его начало.
Следовательно надо "хвост"\"голову " сравнивать с (SIZE_BUF + Start_BUF+1h).
Другими словами проверить выход за границы.
void USART_PutChar(unsigned char sym)
По первому условию txCount == 0 символ запихивается в UDR, но где txCount становится равным единице? То есть в тело второго условия этой функции программа никогда не попадает и буфер всё время остаётся пустым...
В тело второго условия мы попадем, когда сбросится флаг UDRE. А сбрасывается он при записи в регистр данных (UDR).
Я не много запутался, прием данных идёт по прерывания?
Почему такой вопрос, собираю робота на РУ по уарт+автономная работа, я хотел бы чтобы по прерыванию приходил сигнал по уарт и сражу же обрабатывался, на роботе стоят ещё пару датчиков, когда сигнала по уарт нет, они работают
Вот как отработает функция отправки строки если ей на входе подсунуть указатель на строку во флеше или же на строку лежащую в раме, которую сформировали динамически.
2)Какое может быть решение если строка не влазит в буфер, а увеличивать буфер некуда из-за ограничений по размеру рамы в некоторых камешках. :-) Замену камешка не предлагать... :-*
Если буфер полон, то указатели головы и хвоста тоже совпадут. Как определить, полон он или пуст? Если допустить, что при совпадении головы и хвоста - переполнение, то одна ячейка кольцевого буфера не будет использоваться. Как разрешить это противоречие?
Дело в том, что код скачан отсюда для Code Vision AVR. Всё тоже самое, только инициализацию контроллера вынес в отдельный файл, но это никак не связано с ошибкой. Вообще в версии 2.03.4 не хочет работать с указателями, мб кто-то сталкивался с подобным?
void USART_SendStr(u nsigned char * data)//ошибка тут, на тип указателя ругается...
{
unsigned char sym;
while(*data){
sym = *data++;
USART_PutChar(s ym);
}
}
Если просто вызвать USART_SendStr(' test'); то вообще ничего не происходит, а если
char с='AT+CFUN?\r';
USART_SendStr(&с);
то посылает только последний символ строки.
Выложи здесь свой проект, я посмотрю.
http://chipenable.ru/index.php/forum/materialy-sajta-chipenable/3245-vetka-dlya-vremennogo-khraneniya-proektov.html
У меня вот этот код работает.
Code:
char c[] = "AT+CFUN?\r";
USART_SendStr(c);
чувак, char - это один символ, а не строка. а строку надо заключать вообщето в двойные кавычки. по идеее нормальный компилятор должен выкинуть ошибку или варнинг хотя бы на этапе предварительной компиляции
Вот здесь
Code:
//положить символ в буфер
void PutChar(unsigned char sym)
{
if (count < SIZE_BUF){ //если в буфере еще есть место
cycleBuf[tail] = sym; //помещаем в него символ
count++; //инкрементируем счетчик символов
tail++; //и индекс хвоста буфера
if (txBufTail == SIZE_BUF) txBufTail = 0;
}
}
используется одна и та же переменная, названная "tail" и "txBufTail" в разных местах кода
Code:
//прерывание по завершению приема
if (rxCount < SIZE_BUF)
{
//если в буфере еще есть место
usartRxBuf[rxBufTail] = UDR;
//считать символ из UDR в буфер
rxBufTail++;
//увеличить индекс хвоста приемного буфера
if (rxBufTail == SIZE_BUF) rxBufTail = 0;
rxCount++;
//увеличить счетчик принятых символов
}
else
{
// если в буфере больше нет места
// мы вынуждены прочитать UDR, иначе повиснем здесь
u8 free = UDR;
}
Code:
//прерывание по завершению приема
u8 RxData = UDR;
//если в буфере еще есть место
if (rxCount < SIZE_BUF)
{
//считать символ из UDR в буфер
usartRxBuf[rxBufTail] = RxData;
//увеличить индекс хвоста приемного буфера
rxBufTail++;
if (rxBufTail == SIZE_BUF) rxBufTail = 0;
//увеличить счетчик принятых символов
rxCount++;
}
RSS feed for comments to this post