Продолжаем учиться писать код для микроконтроллера ESP8266.
На данном занятии мы продолжим работу с интерфейсом UART и попробуем принять данные по данной шине.
Такую работу мы уже проделывали в уроке 11, только операционную систему FreeRTOS мы не использовали, а с её использованием на приём данных по шине UART налагаются некоторые свои требования. Нам нежелательно уже будет использовать глобальные переменные, буферы, а использовать мы будем очередь, с которой мы уже знакомы, правда отправка данных в очередь из обработчиков прерываний незначительно отличается.
Надеюсь, что во всём этом мы сейчас разберёмся.
Схему мы возьмём из урока 20, так как мы принятые данные из UART будем отображать на дисплее
И проект за основу мы также возьмём из этого же урока с именем I2C_LCD2004_RTOS и присвоим ему новое имя UART_RX_RTOS.
Откроем наш проект в Eclipse и в файле main.c немного переделаем объявление глобального типа структуры qData. Мы переименуем первое поле номера задачи в номер строки, а второе поле сделаем не переменной для счётчика, а указателем на символьный массив, в котором будет находиться строка, отображаемая впоследствии на дисплее. После этого функция приёма данных их очереди и вывода их на дисплей станет более универсальной
1 2 3 4 5 |
typedef struct { unsigned char y_pos; char *str; } qData; |
Объявим ещё одну очередь, посредством которой данные будут передаваться в задачу обработки принятых символов из обработчиков прерываний
1 2 |
xQueueHandle xQueue; xQueueHandle xQueueUart; |
Для данной очереди никакая структура будет не нужна, так как передавать через данную очередь мы будем обычные байты.
Добавим функцию, которая будет служить обработчиком прерываний от UART, в теле которой для начала объявим переменные для длины буфера, счётчика элементов буфера, символьный массив для временного буфера, переменную состояния задачи, в которая будет принимать байты из очереди, и переменную для хранения символа
1 2 3 4 5 6 7 8 9 10 11 |
pData dt1, dt2, dt3, dt4; //------------------------------------------------------ LOCAL void uart0_rx_intr_handler(void *para) { uint8 fifo_len = 0; uint8 buf_idx = 0; uint8 fifo_tmp[128] = {0}; portBASE_TYPE xHigherPriorityTaskWoken; uint8 RcvChar; } //------------------------------------------------------ |
Прочитаем регистр с флагами прерываний
1 2 3 |
uint8 RcvChar; uint32 uart_intr_status = READ_PERI_REG(UART_INT_ST(UART0)); |
Добавим цикл, который будет работать до тех пор, пока хотя бы один флаг прерываний будет установлен
1 2 3 4 |
uint32 uart_intr_status = READ_PERI_REG(UART_INT_ST(UART0)); while (uart_intr_status != 0x0) { } |
В теле данного цикла сбросим флаг прочих ошибок, если будет установлен
1 2 3 4 5 6 |
while (uart_intr_status != 0x0) { if (UART_FRM_ERR_INT_ST == (uart_intr_status & UART_FRM_ERR_INT_ST)) { WRITE_PERI_REG(UART_INT_CLR(UART0), UART_FRM_ERR_INT_CLR); } |
Если буфер приёма полон, то сначала узнаем его длину и установим указатель на ноль
1 2 3 4 5 6 7 |
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_FRM_ERR_INT_CLR); } else if (UART_RXFIFO_FULL_INT_ST == (uart_intr_status & UART_RXFIFO_FULL_INT_ST)) { fifo_len = (READ_PERI_REG(UART_STATUS(UART0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT; buf_idx = 0; } |
В теле той же ветки условия добавим цикл, в котором будем, двигаясь по элементам буфера, считывать их значения и отправлять в очередь, отдавая затем управление задаче-приёмнику из данной очереди (если у неё приоритет выше, чем у задачи, вызвавшей прерывание, по крайней мере так написано в документации к FreeRTOS)
1 2 3 4 5 6 7 8 |
buf_idx = 0; while (buf_idx < fifo_len) { RcvChar = (char) READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; buf_idx++; xQueueSendFromISR(xQueueUart, (void *)&RcvChar, &xHigherPriorityTaskWoken); portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } |
Выйдя из тела цикла (но не из ветки условия), сбросим флаг
1 2 3 |
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR); |
Выйдя из ветки условия, проделаем то же самое, если у нас будет установлен флаг окончания тайм-аута приёма, только с той разницей, что мы в конце тела ветки условия сбросим уже соответствующий флаг
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR); } else if (UART_RXFIFO_TOUT_INT_ST == (uart_intr_status & UART_RXFIFO_TOUT_INT_ST)) { fifo_len = (READ_PERI_REG(UART_STATUS(UART0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT; buf_idx = 0; while (buf_idx < fifo_len) { RcvChar = (char) READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; buf_idx++; xQueueSendFromISR(xQueueUart, (void *)&RcvChar, &xHigherPriorityTaskWoken); portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); } WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR); } |
В следующей ветке условия обработаем событие опустошение буфера передачи путём сброса соответствующего флага, а также сбросим аналогичный бит в регистре разрешения прерываний
1 2 3 4 5 6 7 |
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR); } else if (UART_TXFIFO_EMPTY_INT_ST == (uart_intr_status & UART_TXFIFO_EMPTY_INT_ST)) { WRITE_PERI_REG(UART_INT_CLR(UART0), UART_TXFIFO_EMPTY_INT_CLR); CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA); } |
Выйдя из тела данной ветки условия, не забудем прочитать заново регистр в переменную, иначе мы будем крутиться в нашем цикле вечно
1 2 3 |
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA); } uart_intr_status = READ_PERI_REG(UART_INT_ST(UART0)); |
Ниже функции vRecvTask добавим функцию, которая будет принимать байты из очереди, в которую мы их послали в обработчике, обрабатывать их, склеивая в строку и отправлять указатель на данную строку с помощью другой очереди в задачу, которая после приёма из очереди будет отображать наши строки на дисплее. В данной задаче мы для начала объявим переменную типа структуры для очереди на отправку, переменную для хранения текущей позиции в строке, символьный массив, проинициализируем поля переменной структуры и объявим ещё переменную для хранения символа
1 2 3 4 5 6 7 8 9 10 11 |
//------------------------------------------------------ void ICACHE_FLASH_ATTR uart_task(void *pvParameters) { qData xReceivedData; uint8 x_pos = 0; char str1[30]; xReceivedData.y_pos = 1; xReceivedData.str = str1; char c; } //------------------------------------------------------ |
Добавим бесконечный цикл, в котором попытаемся прочитать байт из очереди в переменную символа
1 2 3 4 5 6 7 |
char c; for (;;) { if (xQueueReceive(xQueueUart, (void *)&c, (portTickType)portMAX_DELAY)) { } } |
Здесь наша задача будет в блокированном состоянии, до тех пор, пока в очереди не появится элемент либо до тех пор, пока не истечёт тайм-аут, а он вряд ли истечёт, так как максимальный. Если байт в очереди появился и он считан, то добавим ещё одно условие, в котором узнаем, что это не символ возврата каретки или перевода строки. Если это так, то запишем символ в массив и увеличим указатель на 1, а если это всё же символ возврата каретки или перевода строки, то забьём пробелами оставшиеся символы до 19-го (у нас такая величина строки в дисплее), закончим строку нулём и попытаемся передать указатель на нашу строку, а также позицию в функцию вывода строк на дисплей, ну и, конечно же, обнулим счётчик элементов массива
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (xQueueReceive(xQueueUart, (void *)&c, (portTickType)portMAX_DELAY)) { if((c != 0x0D) && (c != 0x0A)) { str1[x_pos] = c; x_pos++; } else if(c == 0x0D) { memset(str1+x_pos,' ',20 - x_pos); str1[20] = 0; xQueueSendToBack(xQueue, &xReceivedData, 0); x_pos=0; } } |
Теперь нам надо немного поработать с функцией задачи, выводящей на дисплей строки — vRecvTask. В теле данной функции сначала удалим объявление символьного массива, так как у нас будет указатель на такой массив, и нам дополнительный локальный будет не нужен
char str01[20];
Удалим вот это условие, мы не будем проверять очередь, так как это ёмкая операция, а нам тормоза не нужны
if (uxQueueMessagesWaiting(xQueue) != 0)
{
os_printf(«Queue should have been empty!\n»);
}
Удалим также содержимое тела следующего условия
snprintf(str01, sizeof(str01), «%7lu», xReceivedData.cnt);
LCD_SetPos(7,xReceivedData.num_task — 1);
LCD_String(str01);
Вместо этого мы просто установим позицию, взяв её из переменной структуры и выведем строку на дисплей, воспользовавшись указателем из поля той же переменной типа структуры
1 2 |
LCD_SetPos(0,xReceivedData.y_pos); LCD_String(xReceivedData.str); |
Противный случай также удалим
else
{
os_printf(«Could not receive from the queue.\n»);
}
Задачу task1 мы оставим, чтобы убедиться, что приём данных у нас работает даже при условии работы других задач, только код её тела нам надо будет также несколько переработать в свете требований задачи, которая будет выводить данные на дисплей. Сначала мы в данном теле удалим вот эту инициализацию
xReceivedData.cnt = 0;
Вместо этого мы объявим и проинициализируем обычную переменную
uint32 cnt = 0;
Удалим следующую строку
xReceivedData.num_task = pdt->num_task;
Вместо неё объявим символьный массив и проинициализируем поля переменной структуры
1 2 3 4 |
uint32 cnt = 0; char str[30] = {}; xReceivedData.y_pos = pdt->num_task-1; xReceivedData.str = str; |
В бесконечном цикле мы вначале сформируем нашу строку
1 2 3 |
while(1) { snprintf(str, sizeof(str), "Task%d: %7lu", pdt->num_task, cnt); |
А вместо вот этих строк
xReceivedData.cnt++;
if(xReceivedData.cnt>=10000000) xReceivedData.cnt=0;
мы добавим подобные, но использующие обычную переменную, а не поле
cnt++;
if(cnt>=10000000) cnt=0;
Переходим в тело функции user_init, в котором сначала дождёмся опустошения буферов передачи обоих модулей UART
1 2 3 |
I2C_MASTER_SDA_LOW_SCL_LOW(); UART_WaitTxFifoEmpty(UART0); UART_WaitTxFifoEmpty(UART1); |
Сконфигурируем модуль UART0, для чего сначала объявим переменную типа соответствующей структуры, присвоим её полям нужные значения и применим данные настройки при помощи вызова соответствующей функции SDK
1 2 3 4 5 6 7 8 9 10 |
UART_WaitTxFifoEmpty(UART1); UART_ConfigTypeDef uart_config; uart_config.baud_rate = BIT_RATE_115200; uart_config.data_bits = UART_WordLength_8b; uart_config.parity = USART_Parity_None; uart_config.stop_bits = USART_StopBits_1; uart_config.flow_ctrl = USART_HardwareFlowControl_None; uart_config.UART_RxFlowThresh = 120; uart_config.UART_InverseMask = UART_None_Inverse; UART_ParamConfig(UART0, &uart_config); |
У нас не будет 4 задачи, оставим только одну, поэтому вывод соответствующих строк на дисплей удалим
LCD_SetPos(0,1);
LCD_String(«Task2:»);
LCD_SetPos(0,2);
LCD_String(«Task3:»);
LCD_SetPos(0,3);
LCD_String(«Task4:»);
Удалим также и инициализацию соответствующих полей
dt2.del = 400; dt2.num_task = 2;
dt3.del = 500; dt3.num_task = 3;
dt4.del = 600; dt4.num_task = 4;
Удалим создание задач
xTaskCreate(task1, «task2», 256, (void *) &dt2, 1, NULL);
xTaskCreate(task1, «task3», 256, (void *) &dt3, 1, NULL);
xTaskCreate(task1, «task4», 256, (void *) &dt4, 1, NULL);
Разрешим нужные прерывания от UART и зарегистрируем их обработчик
1 2 3 4 5 6 7 8 9 10 11 |
UART_ParamConfig(UART0, &uart_config); UART_IntrConfTypeDef uart_intr; ETS_UART_INTR_DISABLE(); uart_intr.UART_IntrEnMask = UART_RXFIFO_TOUT_INT_ENA | UART_FRM_ERR_INT_ENA | UART_RXFIFO_FULL_INT_ENA | UART_TXFIFO_EMPTY_INT_ENA; uart_intr.UART_RX_FifoFullIntrThresh = 10; uart_intr.UART_RX_TimeOutIntrThresh = 2; uart_intr.UART_TX_FifoEmptyIntrThresh = 20; UART_IntrConfig(UART0, &uart_intr); UART_SetPrintPort(UART0); UART_intr_handler_register(uart0_rx_intr_handler, NULL); ETS_UART_INTR_ENABLE(); |
Создадим очередь и задачу
1 2 3 |
xTaskCreate(task1, "task1", 256, (void *) &dt1, 1, NULL); xQueueUart = xQueueCreate(32, sizeof(char)); xTaskCreate(uart_task, (uint8 const *)"uTask", 1024, NULL, tskIDLE_PRIORITY + 2, NULL); |
Соберём код, прошьём контроллер. Наша задача со счётчиком работает
Запустим терминальную программу, не забыв настроить там передачу данных из неё
Попытаемся что-нибудь передать из терминальной программы в наш модуль, стараясь не превысить максимальный размер строки в 20 символов
Данные из PC успешно пришли в модуль и отображаются на дисплее
Передадим теперь строку покороче, чтобы проверить стирание старого текста
Всё успешно принимается
При этом задача task1 также работает без сбоев.
Итак, на данном уроке мы научились принимать данные по шине UART, при этом используя механизм прерываний и операционную систему FreeRTOS.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Дисплей LCD 20×4 можно приобрести здесь Дисплей LCD 20×4
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий