STM32 - кольцевой буфер USART'а




Библиотека для создания кольца всевластия кольцевого буфера USART'а по типу ардуиновского (код подсмотрен в библиотеке ардуино).

кольцевой буфер
Конечно же никаких колец нет, мы просто выделяем какой-то массив под приёмный буфер. Предположим что мы выделили под это дело массив из шести байт. Самому мне рисовать иллюстрацию было лень, поэтому нашёл в сети нечто подходящее...

back — это указатель записи (индекс ячейки, в которую нужно записывать. Его ещё называют head — голова).

front — указатель чтения (индекс ячейки, из которой нужно забрать байт. Его ещё называют tail — хвост).



При старте программы оба указателя стоят в самом начале буфера и они равны, это говорит о том, что приёмный буфер пуст.

Потом прилетают три байта (a, b, c) и после каждого последующего байта указатель записи перемещается на следующую ячейку. Из-за разницы между указателем записи и указателем чтения мы знаем что в буфере лежат три новых байта. Далее мы говорим программе, что хотим прочитать из этих трёх байт первые два (a, b), а «с» оставляем в буфере (мы можем и все три забрать, но так анимация нарисована ), при этом указатель чтения так же смещается и останавливается на не прочитанной ячейки №2.

Потом прилетают ещё четыре байта (d, e, f, g). Запись начинается с той ячейки где стоит указатель записи, то есть с ячейки №3. При этом последний байт (g) записывается в нулевую ячейку, то есть буфер провернулся через максимальное значение и вернулся на начало — закольцевался. Ну и далее мы читаем данные с того места где стоит указатель чтения, то есть с ячейки №2. Дальше анимация обрывается, но думаю понятно, что если бы мы продолжили вычитывать данные, то указатель чтения так же провернулся бы через максимальное значение и продолжил читать с нулевой ячейки.

Если бы мы не вычитывали данные, то указатель записи перепрыгнув на начало, увидел бы что ячейка занята и не стал бы её перезаписывать до тех пор пока она не будет прочитана, то есть буфер как бы «остановился».

В итоге, нам совершенно не нужно знать где стоит указатель записи и указатель чтения, обо всём заботится программа, гоняя эти самые указатели по кругу.


Не знаю на сколько доступно мне удалось объяснить работу кольцевого буфера, поэтому добавлю ещё одну анимацию…


Здесь всё то же самое — по кусочкам записывается и вычитывается фраза «HELLOWIKPEDIA».

Кстати говоря, это кольцевой буфер клавиатуры обычного компьютера. Когда буфер переполнен и процессор ещё не успел его освободить, а пользователь продолжает неистово жать кнопки, тогда некоторые компьютеры будут сообщать об этом пищанием. Это так, инфа для любознательных. Описан в английской версии википедии.


Библиотека работает по описанному выше принципу: создаётся приёмный буфер (массив), в который записываются поступающие данные (это происходит непосредственно в обработчике прерывания), а в бесконечном цикле мы постоянно проверяем поступили ли новые данные в приёмный буфер, и если да, тогда считываем их в другой массив.


В бесконечном цикле можно проверять появилось ли хоть что-то новое…

if(uart_available())
...


А можно указать что ждём не менее определённого количества байт…

if(uart_available() > 4)
...

В этом случае ждём когда в приёмном буфере появятся не меньше пяти новых байт, и только после этого забираем их.

Если данные не забирать из приёмного буфера, а они будут продолжать поступать, тогда при заполнении приём «остановиться», то есть прекратится запись новых данных (они будут теряться) до тех пор пока вы не начнёте вычитывать старые чтоб освободить место.

То есть необходимо своевременно забирать поступающие данные из приёмного буфера.

Можно сделать буфер, который не будет «останавливаться», но в таком случае он будет просто перетирать предыдущие данные если их опять же вовремя не вычитать. Это проблема/особенность любых кольцевых буферов — нужно успевать забирать данные.



Программа

В Кубе активируем какой-нибудь USART и включаем у него прерывания. В проект добавляем файлы из библиотекиusart_ring.h и usart_ring.c.

В файле usart_ring.h нужно указать номер вашего USART'а, и прописать размер приёмного буфера:

#define MYUART huart1 // задефайнить USART
#define UART_RX_BUFFER_SIZE 128 // указать размер приёмного буфера

Размер можно оставить как есть. Если принимаете пакеты большей длины, тогда желательно сделать буфер больше пакета, на тот случай если будете не успевать вычитывать.


Далее идём в файл stm32f1xx_it.c, инклюдим там хедер…

/* USER CODE BEGIN Includes */
#include "usart_ring.h"
/* USER CODE END Includes */


Объявляем переменные…

/* USER CODE BEGIN PV */
extern volatile uint16_t rx_buffer_head;
extern volatile uint16_t rx_buffer_tail;
extern unsigned char rx_buffer[UART_RX_BUFFER_SIZE];
/* USER CODE END PV */


И добавляем код в обработчик прерывания…

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	if((MYUART.Instance->SR & USART_SR_RXNE) != RESET)
	{
		uint8_t rbyte = (uint8_t)(MYUART.Instance->DR & (uint8_t)0x00FF); // читает байт из регистра
		uint16_t i = (uint16_t)(rx_buffer_head + 1) % UART_RX_BUFFER_SIZE;

		if(i != rx_buffer_tail)
		{
			rx_buffer[rx_buffer_head] = rbyte;
			rx_buffer_head = i;
		}
	}

	return;
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

Здесь происходит запись полученного байта в приёмный буфер, и перемещение указателя записи. HAL в этом деле не участвует, ибо нужно чтоб всё было максимально быстро, поэтому return.

У камней серии F3 и выше, немного другие названия регистров…

SR ⇨ ISR

USART_SR_RXNE ⇨ USART_ISR_RXNE

DR ⇨ RDR

Компилятор вам подскажет что менять.



Теперь переходим в файл main.c и тоже инклюдим хедер…

/* USER CODE BEGIN Includes */
#include "usart_ring.h"
/* USER CODE END Includes */


Перед бесконечным циклом включить прерывания USART'а…

/* USER CODE BEGIN 2 */
__HAL_UART_ENABLE_IT(&MYUART, UART_IT_RXNE); // включить прерывания usart'a

#define SIZE_BF 32
/* USER CODE END 2 */


Ну и в самом цикле принимаем данные…

while (1)
{
	  if(uart_available()) // есть ли что-то в приёмном буфере, тогда читаем
	  {
		  char str[SIZE_BF] = {0,};
		  uint8_t i = 0;

		  while(uart_available())
		  {
			  str[i++] = uart_read(); // читаем байт

			  if(i == SIZE_BF - 1)
			  {
				  str[i] = '\0';
				  break;
			  }

			  //HAL_Delay(1);
		  }

                  str[i] = '\0';

		  HAL_UART_Transmit(&MYUART, (uint8_t*)str, strlen(str), 100); // отправляем обратно что получили
	  }
}

Внутри условия, организация чтения зависит от ваших задач.

Вся основная работа (проверка наличия новых данных, манипуляции с указателями чтения и записи) происходит в файле usart_ring.c.


Для очистки/обнуления приёмного буфера есть функция…

clear_uart_buff();

После выполнения этой функции указатель записи устанавливается на начало приёмного буфера.


Если нужно использовать несколько USART'ов, тогда продублируйте библиотеку с другими названиями функций и переменных.



Всем спасибо


Телеграм-чат istarik

Телеграм-чат STM32


  • 0
  • 4378
Поддержать автора




Telegram-чат istarik

Задать вопрос по статье
Telegram-канал istarik

Известит Вас о новых публикациях






Комментарии (0)

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.