Учебный курс. Организация обмена по USART `у с использованием кольцевого буфера

07/01/2010 - 21:39

Вступление

   Реализация обмена данными по USART`у, которую мы рассматривали в предыдущей статье имеет некоторые недостатки. Функция отправки символа перед тем как инициировать передачу выполняет опрос флага UDRE регистра UCSRA в цикле while. А это значит, что микроконтроллер тратит свое драгоценное время на пустую работу. Прием данных осуществляется в однобайтовый буфер, при этом предыдущие данные затираются, если микроконтроллер не успевает их считывать.  
   Для простых приложений такого подхода вполне достаточно, но для сложных гораздо эффективнее организовать передачу (прием) данных используя кольцевой (циклический) буфер. Чтобы ответить на вопрос почему, давайте сначала разберемся, что из себя представляет этот буфер.

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

   Простая реализация кольцевого буфера включает в себя массив и три дополнительные переменные – указатель головы буфера, указатель хвоста буфера и счетчик символов, содержащихся в массиве.

#define SIZE_BUF 8

//кольцевой (циклический) буфер
unsigned char cycleBuf[SIZE_BUF];
unsigned char tail = 0;      //"указатель" хвоста буфера 
unsigned char head = 0;   //"указатель" головы буфера
unsigned char count = 0;  //счетчик символов

   Данные добавляются в “хвост” буфера, при этом "указатель" хвоста и счетчик символов увеличиваются на единицу. Переменная tail всегда указывает на следующую свободную ячейку буфера и может изменяться в диапазоне от 0 до SIZE_BUF – 1.
   Считываются (извлекаются) данные из “головы”, при этом индекс головы увеличивается на единицу, а счетчик символов уменьшается. Переменная head указывает на текущий считываемый символ и тоже изменяется в диапазоне от 0 до SIZE_BUF – 1.
   Буфер называется кольцевым, потому что данные в него могут добавляться с любой позиции до тех пор пока в нем есть место. Получается, что данные записываются как бы по кольцу. Посмотрите на рисунки, думаю станет понятно.
кольцевой (циклический) буфер пуст
Когда в кольцевом буфере нет данных, указатель головы равен указателю хвоста, а счетчик символов равен нулю.
в кольцевом (циклическом) буфере данные
Когда в буфер помещены данные, tail указывает на следующую пустую ячейку буфера, head на первый символ, а счетчик показывает сколько ячеек в буфере занято. При считывании данных из буфера изменяются только указатели и счетчик, содержимое массива не меняется. Данные помещаемые в буфер записываются "поверх" старых.
в кольцевом (циклическом) буфере данные
Указатели хвоста и головы могут принимать любые значения от 0 до SIZE_BUF - 1.

Для работы с буфером требуется как минимум три функции – очистить буфер, положить символ, взять символ.
   
//"очищает" буфер
void FlushBuf(void)
{
  tail = 0;
  head = 0;
  count = 0;
}

   Как вы видите, массив cycleBuf[] не очищается. Функция обнуляет "указатели" хвоста, головы и счетчик символов, но это равносильно тому, что в буфере нет данных.

//положить символ в буфер
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 `у? Очень просто. Допустим нам нужно послать по USART `у строку. Первый символ строки мы записываем в регистр UDR, а остальные кидаем в буфер. По завершению передачи модуль USART вызовет прерывание (естественно оно должно быть разрешено) и в прерывании возьмет из буфера следующий символ. В следующем прерывании еще, потом еще и так пока не опустошит весь буфер. Данные помещаются в буфер намного быстрее, чем осуществляется их передача и микроконтроллер не тратит свое драгоценное время на опрос флага готовности модуля USART.  
   А что делать с приемом? То же самое. В прерывании модуля USART (по завершению приема) записываем данные в другой кольцевой буфер, в основной программе обрабатываем. Конечно и здесь возможны потери данных, если буфер переполнится, но в этом случае мы можем просто увеличить размер приемного буфера.

Обмен по 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)
{
  txBufTail = 0;
  txCount = 0;
  txBufHead = 0;
}
   В этой функции есть один нюанс! Переменная txCount специально обнуляется раньше txBufHead. Если  поменять их местами, могут возникнуть проблемы. Например, происходит передача данных, переменная txBufHead указывает на 4 элемент массива и в основном цикле программы мы запускаем эту функцию. Она обнуляет две переменные txBufTail и txBufHead и происходит прерывание по завершению передачи. Поскольку txCount еще не равна нулю, микроконтроллер будет извлекать данные из буфера. Но не из 4 ячейки массива, а из 0!
   По-хорошему это лечиться запрещением прерываний на время выполнения функции, например, с помощью ключевого слова __monitor

Функция загрузки символа в буфер

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;
    }
  }
}

   Если модуль USART не занят и передающий буфер пустой, символ записывается в регистр UDR. Это инициирует передачу данных и вызовет прерывание, когда модуль освободится.  В данной реализации символ будет помещаться в буфер, только если в нем есть свободное место.

Функция загрузки строки в буфер

void USART_SendStr(unsigned char * data)
{
  unsigned char sym;
  while(*data){
    sym = *data++;
    USART_PutChar(sym);
  }
}

  С подобной функцией мы уже имели дело, когда разбирались с lcd дисплеем. Функции передается указатель на строку и она посимвольно помещается в буфер с помощью функции USART_PutChar(sym). Цикл while выполняется пока не будет считан символ конца строки.

Обработчик прерывания по завершению передачи

#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   

# alexandershahbazov 2010-01-09 22:02
Возьмем на заметку !

Я в основном на прием делал так :

unsigned char pars;
while(rx_counter)
{
pars=getchar();putchar(pars);
'Здесь код порой достаточно приличный
};

rx_counter , getchar , putchar
образованы через CodeWizardAVR ( CodeVisionAVR )
Reply | Reply with quote | Quote
# Pashgan 2010-01-10 15:27
Любой способ хорош, если он работает.. кольцевой буфер можно еще сделать на структурах (struct)
Reply | Reply with quote | Quote
# Guest 2010-01-18 14:22
подскажите полному, чайнику(, как мне через UART заставить светить светодиоды?
То есть подаю один сигнал-горит красный, подаю другой сигнал-зеленый. ЗЫ Железо собрано, светики зажигать уже умею, спасибо!
Reply | Reply with quote | Quote
# gringo 2010-01-18 16:46
считывать данные из UDR в какую-нибудь переменную, затем сравнивать ее с константами... например так
if (data == 'g'){
OnLedGreen();OffLedRed();
}
if (data == 'r'){
OnLedRed();OffLedGreen();
}
или с помощью оператора switch
Reply | Reply with quote | Quote
# Guest 2010-01-18 19:37
спасибо
Reply | Reply with quote | Quote
# Guest 2010-02-04 11:19
Есть простор для оптимизации:
1)Можно обойтись без счетчика. Достаточно сравнивать индексы головы и хвоста. Неравенство означает наличие данных в буфере. Вам когда-нибудь нужно было знать, сколько конкретно данных в буфере? Мне нет. Достаточно просто знать - есть данные или нет.
2) Если поставить условие, что размер буфера должен быть степенью двойки, то txBufTail++;
if (txBufTail == SIZE_BUF) txBufTail = 0;
превращается в более эффективное
txBufTail = (txBufTail + 1) & (SIZE_BUF - 1));
В этом случае легко считать и количество данных в буфере (если понадобится): txCount = (txBufTail - txBufHead) & (SIZE_BUF - 1);

А пренебрегать volatile ни в коем случае не стоит.
Reply | Reply with quote | Quote
# DigRo 2013-03-28 09:39
Quoting Guest:

2) Если поставить условие, что размер буфера должен быть степенью двойки, то txBufTail++;
if (txBufTail == SIZE_BUF) txBufTail = 0;
превращается в более эффективное
txBufTail = (txBufTail + 1) & (SIZE_BUF - 1));

Поясните пожалуйста: допустим txBufTail = 2 (0010), а
SIZE_BUF = 10 (1010)
Получается, что следующее значение
txBufTail = (0011)&(1001)=(0001)
13 Что то я не понимаю...
Reply | Reply with quote | Quote
# DigRo 2013-03-28 09:42
Quoting Guest:

должен быть степенью двойки

Извините, заработался.
Reply | Reply with quote | Quote
# Pashgan 2013-03-28 18:24
Бывает :-)
Reply | Reply with quote | Quote
# Guest 2010-02-04 11:36
И еще - зачем использовать прерывание TXC, да еще и с проверкой флага UDR в USART_PutChar() ? Неаккуратненько...
Вот:
#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
Reply | Reply with quote | Quote
# Guest 2010-02-04 11:39
блин... сдвиги обрезало. заменил сдвиг влево на shl:
__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();
}
Reply | Reply with quote | Quote
# Канопус 2016-06-20 07:12
Пример хорош всем кроме одного - проверка количества элемнтов в проверке наличия места сделана не корректно. Это грубая ошибка
Reply | Reply with quote | Quote
# Pashgan 2010-02-04 13:13
Оптимизация делает код менее очевидным для начинающего, поэтому иногда ей приходится пренебрегать.
1)Можно обойтись, но со счетчиком код будет короче. Особенно если кольцевой буфер на структурах.
2)Согласен, эффективнее. Возьму на заметку. ;-)
Reply | Reply with quote | Quote
# Guest 2010-02-12 21:43
Как я понимаю, если отправился последний нужный нам символ, возникнет прерывание, но помещение в регистр символа не будет, а выполнится только проверка. Видимо это будет небольшим недостатком, так как получается что-то вроде "пустого" прерывания...
Reply | Reply with quote | Quote
# Pashgan 2010-02-13 10:28
да, последнее прерывание будет "пустое"
Reply | Reply with quote | Quote
# Guest 2010-02-15 13:15
Так по хорошему надо проверку делать на отсылку данных - все или не все
Reply | Reply with quote | Quote
# Silverio 2010-02-16 18:56
Если не тяжело растолкуйте принцип вычисления адреса в массиве:
TxBuffer[ Tmp & (TX_BUFF_SIZE - 1) ]
Reply | Reply with quote | Quote
# gringo 2010-02-17 06:23
Дело не в вычислении адреса, а в подходе к реализации счетчика.

TxBuffer[ Tmp & (TX_BUFF_SIZE - 1) ] = Byte;
++Tmp;

Переменная Tmp инкрементируетс я и на нее накладывается битовая маска. Маска обрезает лишние разряды. Таким образом проверку if производить не нужно.

TxBuffer[Tmp] = Byte;
++Tmp;
if (Tmp == TX_BUFF_SIZE) Tmp = 0;
Reply | Reply with quote | Quote
# Pyku_He_oTTyda 2010-05-16 18:58
USART_GetChar();
для меня непонятно работает.
Считываем массив, а возвращаем символ... Растолкуйте мне, как она работает.
Reply | Reply with quote | Quote
# Pashgan 2010-05-16 20:51
Если приемный буфер не пустой, считываем из него символ, используя в качестве индекса "указатель головы". Счетчик символов при этом уменьшаем на 1. А "указатель головы" увеличиваем на 1.
Reply | Reply with quote | Quote
# Pyku_He_oTTyda 2010-05-17 03:02
Да, действительно так. Я заработался и перепутал if и while.
Поэтому м посчитал, что выход из функции происходит только когда прочтется весь буфер и соответственно вернется только последнее значение.
Reply | Reply with quote | Quote
# foxit 2010-07-07 21:00
А проект для протеуса есть
Reply | Reply with quote | Quote
# Pashgan 2010-08-05 00:22
Проект для Proteus`a такой же как и здесь - http://chipenable.ru/index.php/programming-c/43-avr-usart-uart.html
Reply | Reply with quote | Quote
# Guest 2010-08-03 17:43
PutChar - в широких массах считается передать аcки символ, например

char ch = 'a';
PutChar(ch);

Для передачи строки строят функцию
SendSrting(char *s) в качестве аргумента которой передают указатель на строку или буфер. В теле этой функции последовательно вызывается примитивная передать символ, столько раз, сколько нужно. Было бы неплохо в коде 1)или поправить названия функций 2)или переписать их содержание, т.к. в функции передать символ мы фактически передаем строку (буфер), что вносит изрядную сумятицу в неокрепшие еще умы. Некорректно это методологически .
Reply | Reply with quote | Quote
# Pashgan 2010-08-05 00:28
В функции передать символ мы передаем только символ. В моем неокрепшем уме чего то никакой сумятицы нет.
Reply | Reply with quote | Quote
# Guest 2010-08-21 13:23
Решил для себя сделать либу, и возник вопросец:
А имеем ли мы право в примере от SAB в функции putchar делать так:
while((uint8_t) (Tmp - TxTail) >= (uint8_t) TX_BUFF_SIZE);
формально у нас может случиться перенос, и Head станет меньше Tail в нормальном состоянии, и программа повиснет. Похоже, что без счетчика байт в буфере не обойтись :( Поправьте меня если что не так.
Reply | Reply with quote | Quote
# Ant 2010-10-25 12:01
подскажите пожалуйста, открыл ваш проект, при компиляции пишет, что имена битов не определены. Как исправить?
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
Reply | Reply with quote | Quote
# Ant 2010-10-25 12:41
это происходит в режиме Release, в Debug все нормально.
Reply | Reply with quote | Quote
# Pashgan 2010-10-31 20:46
Project - Options - General Options - вкладка System - внизу поставить галочку Enable bit definition .. и эти ошибки исчезнут
Reply | Reply with quote | Quote
# Ant 2010-11-01 09:40
спасибо)
Reply | Reply with quote | Quote
# AJ26 2010-11-19 17:58
Доброго времени суток! возникла такая задача: построить клавиатуру по распределенной схеме. 1ый МК (atmega16) совершает опрос клавиатуры (сканирование) и передает код нажатой кнопки по uart. планируется передача 1-3 байт за сеанс передачи. (один байт описывает состояние ctrl, alt, shift, второй содержит номер нажатой клавиши, третий - номер второй нажатой клавиши, если нажато 2 одновременно ) Постоянно пересылать состояние всех клавиш счел нецелесообразны м. Второй МК (pic18f2550) принимает данные по уарт и передает в компьютер по usb, определяясь как стандартное hid устройство. Реализация usb клавиатуры подошла от фреймворка microchip, с этим нет вопросов - работает стабильно. На стороне и принимающего, и передающего МК будет создан циклический буфер.

ПОСТАНОВКА ВОПРОСА: как лучше реализовать обмен по UART - с прерываниями или без ( чтобы не было конфликта прерываний usb и uart ) Больше всего интересует со стороны принимающего мк. Как это вижу я. в цикле ждем завершения очередного приема, проверяя флаг. По завершении приема записываем полученное значение в регистр usb. usb сам с нужной ему частотой инициирует прерывание и передачу данных в ПК. Какие рекомендации и советы можете дать?
Reply | Reply with quote | Quote
# vovka11 2010-11-29 19:29
помогите в проекте под iar компилятор выдал ошибку
Error[Pe020]: identifier "SIZE_BUF" is undefined
немогу понять почему, вроде проект скопировал правильно проверял
возможно настроил неправельно.
Reply | Reply with quote | Quote
# Anton_ 2011-02-08 08:35
У меня ворзникает ситуация, когда txCount = SIZE_BUF_UART и контроллер не входит в прерывания передачи символа. Как побороть данную ситуацию?
Reply | Reply with quote | Quote
# Pashgan 2011-02-08 20:20
Увеличить SIZE_BUF_UART.
Увеличить скорость обмена.
Reply | Reply with quote | Quote
# R_ura 2011-03-13 20:36
Не совсем понятно назначение функций
Quote:
//возвращает колличество символов находящихся в приемном буфере
unsigned char USART_GetRxCount(void)
{ return rxCount; }
Quote:
//"очищает" приемный буфер
void USART_FlushRxBuf(void)
{ unsigned char saveSreg = SREG;
cli();
rxBufTail = 0;
rxBufHead = 0;
rxCount = 0;
SREG = saveSreg; }
Предполагаю, что первой функцией в основном цикле получаем текущее значение
rxCount и если оно равно 0, то второй функцией очищаем буфер?? Зачем его очищать, если новые данные пишутся поверх старых?
Поясните, пож..
Reply | Reply with quote | Quote
# R_ura 2011-03-13 21:03
Пардон, не внимательно прочел про очистку буфера. Очищаются не данные в буфере, а указатели и счетчик.
Тем не менее - в вашем примере для WINAvr не увидел использования вышеуказанных функций. Где они используются?
Reply | Reply with quote | Quote
# Pashgan 2011-03-15 19:50
Да, в проектах они не используются. Где используется? Например, когда нужно очистить буфер по команде.
Reply | Reply with quote | Quote
# ciscobsci 2011-03-29 17:26
Вопрос, а как правильно прочитать при приеме RX не 1 символ, а строку из USART ? Желательно пример в коде..
Reply | Reply with quote | Quote
# v3launch 2013-06-04 12:51
таже проблема, пример WINAVR не читает всю посылку, а только 1 символ. Не понимаю, как прочитать остальные символы из usartRxBuf[].
Reply | Reply with quote | Quote
# Pashgan 2013-06-04 18:24
Эта функция не реализована. Напиши функцию последовательно го чтения буфера до первого нулевого символа.
Reply | Reply with quote | Quote
# Dmi3 2011-04-29 11:02
Размышления в конце статьи не верны.
Код содержит не меньше одной ошибки.
Нет гарантии атомарности изменения переменых.
Например: ++Count может быть откомпилировано так:
  • прочесть Count в регистр. LDS r16,Count
    увеличить регистр. INC r16
    записать регистр в Count. STS Count,r16

Если между чтением и записью (между LDS и STS) произойдёт прерывание то любые изменения Count в этом прерывании будут потеряны.
Reply | Reply with quote | Quote
# Menudo 2011-08-23 16:13
Quote:
//возвращает колличество символов находящихся в приемном буфере unsigned char USART_GetRxCoun t(void) { return rxCount; }
Эта функция нужна когда нужно прочитать из массива больше 1 символа за раз. Нужно читать пока функция не стенет выдавать 0.
Reply | Reply with quote | Quote
# Юра 2011-10-09 19:09
Разобрался вроде бы в общих чертах с Юартом. Но возникло несколько вопросов.
1. Если передаваемые команды состоят не из одного байта, а из строки - "imp_X", "imp_Y" и т.д. то как их идентифицироват ь в приемном буфере, если он содержит уже десяток байт?
2. Если произойдет искажение передаваемой команды, можно отправить на передающую сторону об этом сообщение. Но ведь там передающий буфер уже вполне может быть заполнен другими данными, к тому же уже успеют измениться и значения головы, хвоста и счетчика передающего буфера. Т.е. практически непрошедшую команду повторить уже не выйдет?
Reply | Reply with quote | Quote
# дмитриус 2011-10-29 07:27
что-то мне кажется что лучче сдвигать адреса процедур в очереди, чем хранить кол. процедур, хвост и конец. Просто у вас нигде не описано что после записи/чтения символа в последнюю ячейку массива, следующий надо писать/читать в/из первой(нулевой) ячейки. т.е проблема при переходе из конца в начало массива никак не описано.
Reply | Reply with quote | Quote
# дмитриус 2011-10-29 07:28
ну в вашем случае процедуры - это символы. суть та же
Reply | Reply with quote | Quote
# дмитриус 2011-10-29 07:31
txCount++; //инкрементируе м счетчик символов
txBufTail++;- а из-за такой бодяги вы просто вылазите за пределы массива. компилятору и МК конечто же все равно))) МК просто инкрементирует счетчик адресов
Reply | Reply with quote | Quote
# Pashgan 2011-11-23 22:36
Никто никуда не вылезает. Прогоните код в симуляторе и убедитесь в этом. Или покажите мне ситуацию в которой это происходит. Конкретную ситуацию.
Reply | Reply with quote | Quote
# -devil- 2011-10-29 07:39
ну вы б внимательно хотя бы читали, чтоль. там все учтено!!!
зачем по вашему там
if (txBufTail == SIZE_BUF) txBufTail = 0;
?
Reply | Reply with quote | Quote
# ~cosinus~ 2011-11-01 14:10
Quoting -devil-:
ну вы б внимательно хотя бы читали, чтоль. там все учтено!!!
зачем по вашему там
if (txBufTail == SIZE_BUF) txBufTail = 0;
?


Я бы не согласился с Вами.
Пусть адрес начала буфера (Start_BUF) в памяти равен 25h. Размер буфера 0Ah-байт
Тогда, "хвост" и "голова" выставляются на начало буфера, а именно становятся равны 25h.
Вот тут как раз и получится что будет выход за границы буфера. И "хвост"\"голова " никогда не перейдёт с конца массива на его начало.
Следовательно надо "хвост"\"голову " сравнивать с (SIZE_BUF + Start_BUF+1h).
Другими словами проверить выход за границы.
Reply | Reply with quote | Quote
# Чаба 2011-10-29 07:43
и таки -devil- прав
Reply | Reply with quote | Quote
# ~cosinus~ 2011-11-01 14:13
Так что дмитриус верно подметил Quote:
. Просто у вас нигде не описано что после записи/чтения символа в последнюю ячейку массива, следующий надо писать/читать в/из первой(нулевой) ячейки. т.е проблема при переходе из конца в начало массива никак не описано.
Reply | Reply with quote | Quote
# Дмитрий А. 2011-11-03 09:14
Посмотрите на эту функцию Code: void USART_PutChar(unsigned char sym)

По первому условию txCount == 0 символ запихивается в UDR, но где txCount становится равным единице? То есть в тело второго условия этой функции программа никогда не попадает и буфер всё время остаётся пустым...
Reply | Reply with quote | Quote
# Pashgan 2011-11-23 21:43
Зачем увеличивать счетчик, если первый символ кладется в буфер USART модуля,а не в массив? Счетчик txCount считает символы находящиеся в кольцевом буфере!
В тело второго условия мы попадем, когда сбросится флаг UDRE. А сбрасывается он при записи в регистр данных (UDR).
Reply | Reply with quote | Quote
# дмитриус 2011-11-15 07:03
но все равно статья хорошая. главное - идея, а остальное умному несложно допилить)))
Reply | Reply with quote | Quote
# MacMaffy 2011-11-16 05:14
Привет, вопрос:
Я не много запутался, прием данных идёт по прерывания?
Почему такой вопрос, собираю робота на РУ по уарт+автономная работа, я хотел бы чтобы по прерыванию приходил сигнал по уарт и сражу же обрабатывался, на роботе стоят ещё пару датчиков, когда сигнала по уарт нет, они работают
Reply | Reply with quote | Quote
# Oxidizer 2012-08-21 23:04
Отличная статья! Большое спасибо! Очень помагло.
Reply | Reply with quote | Quote
# Neitron 2012-08-22 17:29
Подскажите, а что произойдет если переполнится приемный кольцевой буфер?
Reply | Reply with quote | Quote
# _Gringo_ 2012-08-23 20:55
Когда буфер заполнится, туда нельзя будет добавить данные. Соответственно функция USART_PutChar() будет работать в холостую. Можно добавить в нее возврат ошибки в этом случае или ожидание готовности - какой нибудь цикл while. Но это чревато зависоном.
Reply | Reply with quote | Quote
# ir0407 2012-11-23 03:29
1)Функция отправки строки не универсальна.
Вот как отработает функция отправки строки если ей на входе подсунуть указатель на строку во флеше или же на строку лежащую в раме, которую сформировали динамически.
2)Какое может быть решение если строка не влазит в буфер, а увеличивать буфер некуда из-за ограничений по размеру рамы в некоторых камешках. :-) Замену камешка не предлагать... :-*
Reply | Reply with quote | Quote
# SeNiMal 2012-12-06 09:28
Подскажите, чем аргументируется использование именно кольцевого буфера, а не обычного? Короче код? Или еще почему-то?
Reply | Reply with quote | Quote
# Pashgan 2012-12-09 19:23
Просто такая структура данных позволяет асинхронно буферизировать и обрабатывать потоковые данные. Пишем в одно время, а обрабатываем в другое. Обработка и наполнение буфера может происходить при любом его наполнении, при наличии свободного места. Линейный буфер, как правило используют для потоковых данных фиксированной длинны.
Reply | Reply with quote | Quote
# SeNiMal 2012-12-06 11:45
Еще вопрос:
Если буфер полон, то указатели головы и хвоста тоже совпадут. Как определить, полон он или пуст? Если допустить, что при совпадении головы и хвоста - переполнение, то одна ячейка кольцевого буфера не будет использоваться. Как разрешить это противоречие?
Reply | Reply with quote | Quote
# Pashgan 2012-12-09 19:25
Добавить в функции PutChar и GetChar возврат какого-нибудь значения.
Reply | Reply with quote | Quote
# AFZ 2013-02-27 07:56
У меня траблы с отправкой строки в основной программе, выдает вот такую ошибку: function argument #1 of type 'unsigned char [10]' is incompatible with required parameter of type 'unsigned char'. Программу скачал с этой страницы, перепроверил, мб я где накосячил, но увы, всё точно так же, как и в статье. Упорно не хочет компилировать проект в CodeVisionAVR 2.03.4, ругается на отправляемую строку USART_SendStr(" test - OK"); Как это можно исправить?
Reply | Reply with quote | Quote
# JoJo 2013-02-27 09:00
Сделай тему на форуме и выложи там код. Так непонятно в чем может быть ошибка.
Reply | Reply with quote | Quote
# AFZ 2013-02-27 10:33
Зачем создавать тему?
Дело в том, что код скачан отсюда для Code Vision AVR. Всё тоже самое, только инициализацию контроллера вынес в отдельный файл, но это никак не связано с ошибкой. Вообще в версии 2.03.4 не хочет работать с указателями, мб кто-то сталкивался с подобным?
Reply | Reply with quote | Quote
# AFZ 2013-02-27 10:46
Функция загрузки строки в буфер

void USART_SendStr(u nsigned char * data)//ошибка тут, на тип указателя ругается...
{
unsigned char sym;
while(*data){
sym = *data++;
USART_PutChar(s ym);
}
}
Reply | Reply with quote | Quote
# Pashgan 2013-02-27 10:49
Я пользуюсь CodeVision 2.04. Только что скачал код с этой страницы и запустил компиляцию. Проект скомпилировался .
Reply | Reply with quote | Quote
# zoomerland 2013-05-11 12:02
Уважаемый автор! По какой то причине USART_SendStr не работает как надо.
Если просто вызвать USART_SendStr(' test'); то вообще ничего не происходит, а если
char с='AT+CFUN?\r';
USART_SendStr(&с);

то посылает только последний символ строки.
Reply | Reply with quote | Quote
# Pashgan 2013-06-04 18:22
Установлена не та скорость обмена.
Выложи здесь свой проект, я посмотрю.
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);
Reply | Reply with quote | Quote
# Extremist 2013-06-05 16:36
zoomerland
чувак, char - это один символ, а не строка. а строку надо заключать вообщето в двойные кавычки. по идеее нормальный компилятор должен выкинуть ошибку или варнинг хотя бы на этапе предварительной компиляции
Reply | Reply with quote | Quote
# kleef 2013-07-21 06:39
Мне вот что непонятно, в обработчике прерывания по завершению передачи в случае, если у нас передаётся больше одного символа каким образом мы вновь вызываем PutChar? В функции SendStr PutChar вызывается явно если у нас это первый символ, но затем в прерывании идет присваивание UDR второго третьего и т д символов, но каким образом простое присваивание значения UDR вызывает вновь функцию PutChar для осуществления передачи?
Reply | Reply with quote | Quote
# NaDmitrAn 2013-10-21 16:11
Вот такая ситуация, мне надо отправить в порт массив рассчитанных данных, все бы ничего, но вот с окончанием загрузки в буфер проблема, у меня нет данные стоят в ячейках 6F,70, а кольцевой буфер начинается с ячейки 71, тоесть нет нулевого значения, которое программа воспримет как конец строки, и если размер буфера будет хотя-бы 4, то после загрузки второго символа в буфер, программа начнет в следующую ячейку буфера закладывать уже не данные которые закончились, а начало буфера. С текстом-то все просто берешь, ставиш пробел в конце и все работает, как в данных прилепить ячейку с "0" в конце.
Reply | Reply with quote | Quote
# Zlodey 2015-02-19 13:16
Обнаружил ошибку в статье.
Вот здесь

Code://положить символ в буфер
void PutChar(unsigned char sym)
{
if (count < SIZE_BUF){ //если в буфере еще есть место
cycleBuf[tail] = sym; //помещаем в него символ
count++; //инкрементируем счетчик символов
tail++; //и индекс хвоста буфера
if (txBufTail == SIZE_BUF) txBufTail = 0;
}
}


используется одна и та же переменная, названная "tail" и "txBufTail" в разных местах кода
Reply | Reply with quote | Quote
# lenivec 2015-09-17 10:06
Только у меня работает кудряво при отправке буфера длиной более 27 байт? При отправке откуда-то берутся магические нули в конце посылки.
Reply | Reply with quote | Quote
# lenivec 2015-09-18 11:01
Разобрался, проблема в атомарности, volatile вылечил
Reply | Reply with quote | Quote
# Zlodey 2017-01-09 13:22
Заметил грубую ошибку в коде. Если по какой-то причине не забрать байты из буфера, и буфер переполнится, то мы навсегда зависнем в обработчике прерывания USART_RXNE.

Code:
//прерывание по завершению приема
if (rxCount < SIZE_BUF)
{
//если в буфере еще есть место
usartRxBuf[rxBufTail] = UDR;
//считать символ из UDR в буфер
rxBufTail++;
//увеличить индекс хвоста приемного буфера
if (rxBufTail == SIZE_BUF) rxBufTail = 0;
rxCount++;
//увеличить счетчик принятых символов
}
else
{
// если в буфере больше нет места
// мы вынуждены прочитать UDR, иначе повиснем здесь
u8 free = UDR;
}
Reply | Reply with quote | Quote
# Zlodey 2017-01-09 14:24
Или лучше сделать вот так

Code:
//прерывание по завершению приема
u8 RxData = UDR;
//если в буфере еще есть место
if (rxCount < SIZE_BUF)
{
//считать символ из UDR в буфер
usartRxBuf[rxBufTail] = RxData;
//увеличить индекс хвоста приемного буфера
rxBufTail++;
if (rxBufTail == SIZE_BUF) rxBufTail = 0;
//увеличить счетчик принятых символов
rxCount++;
}
Reply | Reply with quote | Quote
# max-1 2017-01-14 21:56
помогите!!! не могу отправить строку по usart`у из камня, вместо строки отправляется только первый символ. РАЗЖУЙТЕ ДЛЯ НАЧИНАЮЩЕГО.
Reply | Reply with quote | Quote

Add comment