UART, RingBuffer и FreeRTOS. Развлекаемся с приёмом и передачей.
Как применять показанный ранее кольцевой буфер для приёма и передачи информации по UART (и не только) в тасках RTOS.
Постановка задачи следующая: нужно принять по uart
некоторое количество пакетов, линия может быть зашумлена и в паузах могут появлятся фантомные байты (которые передатчик не передавал), отделить пакеты от мусора и выполнить какие-то действия, скорость соединения не большая, допустим 115200
.
Статья будет являться небольшой демонтрацией для работы с FreeRTOS
и тем кольцевым буфером, о котором я однажды писал. А uart
здесь каким боком? А таким, что данная заметка будет еще и небольшим туториалом для одного хорошего человека.
Погнали...
Сразу говорю - большенство кода будет дано только для понимания принципа работы, а не гарантом полной функциональности, куски взял из одного готового проекта. Примеры для микроконтроллера компании Миландр
с использованием их SPL
(очень похожа на SPL
STLM32F10x
), но это видно только в нескольких строках кода, всё остальное универсально.
Пакет информации будет иметь следующую структуру:
- заголовок - 2 байта;
- команда - 3 байта;
- данный - 1 байт;
- контрольная сумма - 2 байта.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
HEADH | HEADL | CMD2 | CMD1 | CMD0 | DATA | CRCH | CRCL |
И так, приступим.
Для начала нам нужно проинициализировать всю необходимую переферию контроллера и подключить все необходимые библиотеки.
функция инициализации модуля обмена информацией (модуля, потомучто это отдельный файл в коде, выполняющий только эти функции, так проще читать и отлаживать код, да и удобно разбираться в нём).
#include "ring_buffer.h"
static TaskHandle_t COMM_SentThread_h = NULL;
static TaskHandle_t COMM_Parse_h = NULL;
uint8_t rx_buff[ SIZE_RX_BUFFER ] = { 0 };
uint8_t tx_buff[ SIZE_TX_BUFFER ] = { 0 };
RING_buffer_t ring_rx;
RING_buffer_t ring_tx;
void COMM_ParseThread(void *argument);
void COMM_SentThread(void *argument);
/*
... тут хренова туча другого кода
*/
void COMM_Init( void )
{
/*
Тут мы инициализируем ноги контроллера, uart и разрешаем прерывания на приём.
*/
// инициализация кольцевых буферов на приём и передачу.
RING_Init(&ring_rx, rx_buff, SIZE_RX_BUFFER );
RING_Init(&ring_tx, tx_buff, SIZE_TX_BUFFER );
// создаём задачи для FreeRTOS.
xTaskCreate(COMM_ParseThread, "Parse", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &COMM_Parse_h );
xTaskCreate(COMM_SentThread, "Sent", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &COMM_SentThread_h);
}
Вызов функции выполняется следующим образом. Повторюсь - код является не истиной в последней инстанции, а только лишь ознакомительным материалом.
int main( void )
{
/* Здесь инициализация других устройств и модулей */
COMM_Init();
/* Здесь инициализация других устройств и модулей */
// Запускаем диспетчер задач и понеслась.
vTaskStartScheduler();
for(;;){}; // не обязательно
}
Теперь пора познакомиться с нашими обработчиками. За приём информации отвечает прерывание по приёму каждого байта. Сразу оговорюсь, можно сделать оптимальнее, но для первого знакомства или небольшой нагрузки линии связи этого достаточно.
В прерывании происходить очистка флага срабатывания прерывания и загрузка принятого байта в кольцевой буфер.
void UART1_IRQHandler(void)
{
UART_ClearITPendingBit(MDR_UART1, UART_IT_RX);
RING_Put(&ring_tx, UART_ReceiveData(MDR_UART1));
}
Передачей занимается задача RTOS
, которая отслеживает заполненость кольцевого буфера и при наличии в нем данных отправляет их на линию. Сам буфер заполняется отдельно, это сделано для того, что бы не тормозить функцию которая его заполняет ожиданиями при отправке байтов.
void COMM_SentThread(void *argument)
{
for ( ;; )
{
while(RING_GetCount(&ring_tx))
{
UART_SendData(MDR_UART1, RING_Pop( &ring_tx ));
while (UART_GetFlagStatus(MDR_UART1, UART_FLAG_TXFE) == RESET);
}
vTaskDelay(TIME_MSEC * 100);
}
#pragma push
#pragma diag_suppress 111
vTaskDelete( NULL );
#pragma pop
}
Кстати, если заметили TIME_MSEC
- это количество тактов FreeRTOS
в 1 милитекунде. Я себе всегда назначаю несколько макросов, которые помогают писать более понятный и не зависящий от настроек FreeRTOS
код.
#define TIME_MSEC portTICK_PERIOD_MS ///< тактов FreeRTOS в 1 милитекунде
#define TIME_SEC configTICK_RATE_HZ ///< тактов FreeRTOS в 1 секунде
#define TIME_MINUT (TIME_SEC * 60) ///< тактов FreeRTOS в 1 минуте
А теперь самое интересное - разбор пакета.
void COMM_ParseThread(void *argument)
{
char cmd[3] = {0};
uint8_t datax;
for ( ;; )
{
// тут будем парсить ответ
while(RING_GetCount(&ring_rx) >= SIZE_RX_COMMAND)
{
if((RING_ShowSymbol(&ring_rx, 0) == MARKER_H) // Проверка заголовка
&& (RING_ShowSymbol(&ring_rx, 1) == MARKER_L) // Проверка заголовка
&& RING_CRC16ccitt(&ring_rx, SIZE_RX_COMMAND - 4, 2)) // Проверка контрольной суммы пакета
{
//убираем маркер из кольцевого буфера
RING_Pop(&ring_rx);
RING_Pop(&ring_rx);
for(uint8_t j = 0; j < 3; j++) cmd[j] = RING_Pop( &ring_rx );
datax = RING_Pop( &ring_rx );
// удаляем CRC из буфера
RING_Pop( &ring_rx );
RING_Pop( &ring_rx );
if (COMM_Compare( cmd, "CC1", 3 )) Run_CMD1(datax); // Выполнение действий команды 1
else if(COMM_Compare( cmd, "CC2", 3 )) Run_CMD2(datax); // Выполнение действий команды 2
else if(COMM_Compare( cmd, "CC3", 3 )) Run_CMD3(datax); // Выполнение действий команды 3
else if(COMM_Compare( cmd, "CC4", 3 )) Run_CMD4(datax); // Выполнение действий команды 4
}
else
{
// удаляем первый символ из рингбуфера, и парсим заново
RING_Pop( &ring_rx );
}
}
vTaskDelay(TIME_MSEC * 100);
}
#pragma push
#pragma diag_suppress 111
vTaskDelete( NULL );
#pragma pop
}
А теперь разберёмся в алгоритме работы данной задачи.
- Задача ждёт пока в буфере не наберётся достатояное количество байт - больше или равное размеру пакета (
SIZE_RX_COMMAND
). - Затем проверяет заголовок и контрольную сумму в пакете, если всё совпадает, то переходит на следующий шаг, если нет, то удаляет первый байт из буфера и начинает парсинг заново.
- Если распознавание пакета прошло успешно, то удаляем маркеры (заголовок) из буфера и считываем оттуда команду и данные, а затем удаляем байты контрольной суммы. Переходим к распознаванию команд и их выполненению.
Собственно всё. На этот раз файлов для загрузки не будет, извините.
Хорошего кодинга и до новых встреч.