Вступление
Всем доброго времени суток! В заголовке данной статьи как раз написано то, о чем я хотел бы вам рассказать, только добавим немного конкретики об использованном стеке:
Камень STM32F103C8
CubeMX 6.2.1
IAR 8.30.1
FreeRTOS 10.0.1, CMSIS v2
Все периферийные драйвера LL
Для написания и отладки прошивки был собран небольшой стенд (картинка ниже). На стенде используется платка Bluepill, программатор ST-Link v2, два USB-UART переходника на CP2102 и несколько выводных отладочных светодиодов (куда без них?).
Основная часть
Микроконтроллер был настроен следующим образом (смотреть картинку ниже), выбрана максимальная частота работы.
Описание буду проводить на примере USART2. Для начала сконфигурируем его как на картинках ниже. Выбираем стандартные параметры, добавляем 6 и 7 DMA каналы, разрешаем глобальное прерывание от USART2.
Перейдем к настройке freeRTOS. Выбираем версию интерфейса CMSIS_v2. Теперь добавим очереди RX/TX, потоки для их обработки и одно событие. Для моей задачи нужно было принимать и отправлять данные в виде ASCII строк, с максимальной длинной в 128 байтов (с учетом '\0'
). Событие нам потребуется для определения состояния DMA в потоке передачи данных.
С настройкой в CubeMX закончили, перейдем к коду.
В main.h создаем макросы:
#define UART2_TX_LENGTH 128
#define UART2_RX_LENGTH 128
/*
Выставляется, когда 7 канал DMA завершит работу.
То есть когда все отправляемые данные будут переданы USART периферии.
Это будет сигналом о том, что можно запускать передачи следующих данных.
*/
#define UART2_event_tx_dma_complete 0x00000001U
В main.c:
/*
Обработчик будет вызван в случае обнаружения IDLE состояния RX линии USART.
Это будет означать, что завершилась транзакция приема данных и бизнес логика
приложения может их забрать.
*/
void USART2_IdleCallback()
{
extern char UART2_rx[UART2_RX_LENGTH];
extern osMessageQueueId_t rxDataUART2Handle;
uint32_t length = UART2_RX_LENGTH - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_6);
if (length < UART2_RECEIVE_LENGTH) {
UART2_rx[length] = 0; //Добавляем '\0' в конец строки
osMessageQueuePut(rxDataUART2Handle, UART2_rx, 0, 0);
} else {
//Overflow rx data
}
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_6);
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_6, UART2_RX_LENGTH);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_6);
}
В usart.c добавляем следующий код:
/* USER CODE BEGIN 0 */
char UART2_tx[UART2_TX_LENGTH];
char UART2_rx[UART2_RX_LENGTH];
/* USER CODE END 0 */
/* USER CODE BEGIN USART2_Init 2 */
//Настройка 6 канала DMA (прием данных)
LL_USART_EnableIT_IDLE(USART2);
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_6, LL_USART_DMA_GetRegAddr(USART2), (uint32_t)&UART2_rx, LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_6)););
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_6, UART2_RX_LENGTH);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_6);
LL_USART_EnableDMAReq_RX(USART2);
//Настройка 7 канала DMA (передача данных)
LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_7);
LL_USART_EnableDMAReq_TX(USART2);
/* USER CODE END USART2_Init 2 */
В файле stm32f1xx_it.c находим прерывание 6 и 7 каналов DMA и вставляем следующий код:
/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */
/* USER CODE BEGIN DMA1_Channel6_IRQn 0 */
if(LL_DMA_IsActiveFlag_TC6(DMA1))
{
LL_DMA_ClearFlag_TC6(DMA1);
}
/* USER CODE END DMA1_Channel6_IRQn 0 */
/* USER CODE BEGIN DMA1_Channel7_IRQn 0 */
extern osEventFlagsId_t UART2_EventsHandle;
if(LL_DMA_IsActiveFlag_TC7(DMA1))
{
LL_DMA_ClearFlag_TC7(DMA1);
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_7);
//Выставляем флаг завершения передачи данных DMA периферии USART
osEventFlagsSet(UART2_EventsHandle, UART2_event_tx_dma_complete);
}
/* USER CODE END DMA1_Channel7_IRQn 0 */
В файле freeRTOS.c добавляем код наших задач:
void StartParseUART2DataTask(void *argument)
{
/* USER CODE BEGIN StartParseUART2DataTask */
char data[UART2_RX_LENGTH];
/* Infinite loop */
for(;;)
{
//Ожидаем принятых данных. Пока ждем - данная задача в состоянии BLOCKED
osMessageQueueGet(rxDataUART2Handle, data, NULL, osWaitForever);
/*
Работа с data
*/
}
void StartSendUART2Task(void *argument)
{
/* USER CODE BEGIN StartSendUART2Task */
char data[UART2_TX_LENGTH];
/* Infinite loop */
for(;;)
{
//Ждем сообщения из очереди. Пока ждем - данный поток BLOCKED
osMessageQueueGet(txDataUART2Handle, data, NULL, osWaitForever);
//Ждем выставления флага о завершении передачи DMA. Пока ждем - данный поток BLOCKED
osEventFlagsWait(UART2_EventsHandle, UART2_event_tx_dma_complete, osFlagsWaitAny, osWaitForever);
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_7, (uint32_t)&str, LL_USART_DMA_GetRegAddr(USART2), LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_7) );
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_7, strlen(data));
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_7);
}
/* USER CODE END StartSendUART2Task */
}
/* USER CODE BEGIN RTOS_EVENTS */
//Выставляем флаг завершения передачи DMA при инициализации, иначе не передадим первое сообщение пе
osEventFlagsSet(UART2_EventsHandle, UART2_event_tx_dma_complete);
/* USER CODE END RTOS_EVENTS */
Теперь можно собирать проект и заливать прошивку в наш камень.
Логика работы
Прием данных
При запуске устройства мы сразу же инициализируем прием данных по DMA. Выставляем количество байт, которые нужно принять равное максимально возможному количеству. Разрешаем IDLE прерывании линии RX USARTx. Таким образом, данные которые поступают в устройство, с помощью DMA перемещаются в указанный нами буфер. Когда поступление данных в устроство закончилось, на линии RX USARTx выставляется и удерживается высокий уровень. Если высокий уровень удерживается в течении времени приема одного байта, то периферии USARTx вызывает прерывание IDLE Line detection. В этот момент мы переходим в обработчик прерывания USART2_IdleCallback
, в котором мы заносим данные из буфера в очередь. Также у нас есть поток обработки принятых данных. В нем мы просто ожидаем появления елемента в очереди. Когда он появляется - поток выходит из состояния BLOCKED и мы можем обрабатывать данные.
Передача данных
Для передачи данных по USARTx через DMA: выставляем флаг разрешения передачи через DMA и разрешаем прерывание по завершению передачи. При инициализции freeRTOS выставляем события завершения передачи по DMA для передачи первого сообщения. В потоке, который обслуживает передачу по USARTx, мы ждем появления данных в очереди, затем ждем флаг, и после этого запускаем передачу. Когда передача по DMA будет завершена - произойдет вызов обработчика события DMA TC, в котором мы очистим флаг TC и выставим флаг события для отправки следующих данных.
Завершение
На этом пожалуй все, надеюсь эта информация была вам полезна!
Комментарии (4)
Gryphon88
03.12.2021 22:14Извините, но статья похожа на сокращенный вариант вот этого туториала, там и примеры под разные платы. Мне кажется, что rtos тут немного лишняя, тяжёлая уж больно. Очереди я бы заменил на кольцевые буферы, например, реализация в LUFA позволяет одновременную запись и чтение, а обрабатывал принятое в бесконечном цикле через КА или фланговый автомат
Кстати, не подскажете, как можно реализовать на half без all или прямого обращения к регистрам? Не пойму, как разрешать прерывание по idle
Dominikanez
Очень бодро.
А с какой периферией это тестировалось? Гоняли ли именно этот код, из поста, в реальных условиях, долгосрочно?
vladyslavNovytskyi Автор
Тестировалось с помощью "программ-заглушек", запущенных на ПК. Гонялся именно код из поста. Тестирование проводил на граничных значения и оставлял на ночь тестироваться.