Продолжаем учиться писать код для микроконтроллера ESP8266.
На данном занятии мы попробуем принять данные по шине UART.
Мы уже по данной шине передавали данные в уроке 7, а теперь нам предстоит их принять.
Казалось, бы какая разница и что тут сложного. Но не так-то всё просто. Чтобы нам не висеть постоянно где-то в бесконечном цикле и не ждать, а не пришли ли нам данные, мы будем использовать обработку события по их приходу в наш буфер. Вот в этом и будет некоторая сложность. Также пришедшие данные мы попробуем отобразить как-то на символьном дисплее, с которым мы работали на прошлом занятии.
Схема также будет у нас такая же как и в прошлом уроке
Проект с исходным кодом также был сделан из проекта прошлого урока с именем I2C_LCD2004 и назван был, соответственно, UART_RX.
Откроем наш проект в Eclipse и первым делом добавим библиотеку для работы с интерфейсом UART в файле main.c
1 2 |
#include "driver/i2c_master.h" #include "driver/uart.h" |
В функции user_init() сконфигурируем наш UART
1 2 3 |
char str01[6]; // Configure the UART uart_init(BIT_RATE_115200, BIT_RATE_115200); |
Строчный массив нам не потребуется.
char str01[6];
После небольшой задержки перейдём на новую строку в терминальной программе
1 2 3 |
i2c_master_gpio_init(); ets_delay_us(100000); os_printf("\r\n"); |
Вывод строк на дисплей, 2-секундную паузу и очистку нижней строки также удалим
LCD_String(«String 1»);
LCD_SetPos(3,1);
LCD_String(«String 2»);
LCD_SetPos(6,2);
LCD_String(«String 3»);
LCD_SetPos(9,3);
LCD_String(«String 4»);
ets_delay_us(1000000);
system_soft_wdt_feed();
ets_delay_us(1000000);
system_soft_wdt_feed();
LCD_SetPos(9,3);
LCD_String(» «);
Из тела бесконечного цикла также всё удалим.
Добавим пока пустотелую функцию для обработки прерываний от UART
1 2 3 4 5 6 |
#include "lcd.h" //------------------------------------------------------ static void uart0_rx_intr_handler(void *para) { } //------------------------------------------------------ |
Объявим глобальный буфер для данных, которые будут приходить по UART
1 2 3 4 |
#include "lcd.h" //------------------------------------------------------ char rx_buf[100] = {}; //------------------------------------------------------ |
Также объявим прототипы функций для того, чтобы были видны макросы для прерываний
1 2 3 4 5 |
#include "lcd.h" //------------------------------------------------------ void ets_isr_mask(unsigned intr); void ets_isr_unmask(unsigned intr); //------------------------------------------------------ |
В функции user_init организуем обработку прерываний от UART, аналогично как мы делали это для GPIO
1 2 3 4 5 6 |
I2C_MASTER_SDA_LOW_SCL_LOW(); ETS_UART_INTR_DISABLE(); ETS_UART_INTR_ATTACH(uart0_rx_intr_handler, rx_buf); PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD); ETS_UART_INTR_ENABLE(); |
В функции-обработчике прерываний отреагируем на различные события, исследуя флаги
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
static void uart0_rx_intr_handler(void *para) { if (UART_FRM_ERR_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_FRM_ERR_INT_ST)) { WRITE_PERI_REG(UART_INT_CLR(UART0), UART_FRM_ERR_INT_CLR); } if (UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_RXFIFO_FULL_INT_ST)) { uart_rx_intr_disable(UART0); } else if (UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_RXFIFO_TOUT_INT_ST)) { uart_rx_intr_disable(UART0); } else if (UART_TXFIFO_EMPTY_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_TXFIFO_EMPTY_INT_ST)) { CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA); WRITE_PERI_REG(UART_INT_CLR(UART0), UART_TXFIFO_EMPTY_INT_CLR); } else if (UART_RXFIFO_OVF_INT_ST == (READ_PERI_REG(UART_INT_ST(UART0)) & UART_RXFIFO_OVF_INT_ST)) { WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_OVF_INT_CLR); } |
Также добавим глобальную переменную для хранения длины принятых данных и пользовательский флаг
1 2 3 |
char rx_buf[100] = {}; uint8 fifo_len; uint8_t fl; |
В функции-обработчике uart0_rx_intr_handler измерим длину данных. Если она окажется большей нуля, то установим наш флаг
1 2 3 4 |
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_OVF_INT_CLR); } fifo_len = (READ_PERI_REG(UART_STATUS(UART0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT; if(fifo_len>0) fl=1; |
В функции user_init в бесконечном цикле добавим ещё один цикл, в котором мы будем время от времени сбрасывать сторожевой таймер, а если вдруг наш пользовательский флаг окажется установленными, то мы из этого цикла выйдем и сбросим этот флаг
1 2 3 4 5 6 7 8 9 10 11 12 13 |
while(1) { while(!fl) { i++; if(i>500) { i=0; system_soft_wdt_feed(); } ets_delay_us(1000); } fl = 0; |
Отправим в ответ в терминальную программу длину принятого пакета и освободим место для принятых символов на дисплее
1 2 3 4 5 |
fl = 0; os_printf("fifo_len: %d\r\n",fifo_len); LCD_SetPos(0,3); LCD_String(" "); LCD_SetPos(0,3); |
Так как отправлять на дисплей мы данные будем посимвольно, а не целой строкой, то в файле lcd.c добавим для этого функцию после функции отправки байта в дисплей sendbyte
1 2 3 4 5 6 |
//------------------------------------------------ void LCD_Char(char c) { sendbyte((uint8_t)c, 1); } //------------------------------------------------ |
Добавим прототип для данной функции в заголовочном файле и вернёмся в функцию user_init файла main.c, в которой добавим ещё 2 локальные переменные
1 2 3 |
uint16_t i=0; uint8 d_tmp = 0; uint8 idx=0; |
В бесконечном цикле пробежимся по буферу и, не выводя перевод строки и возврат каретки, выведем наши принятые символы на дисплей
1 2 3 4 5 6 7 |
LCD_SetPos(0,3); for(idx=0;idx<fifo_len;idx++) { d_tmp = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; if((d_tmp != 0x0D) && (d_tmp != 0x0A)) LCD_Char(d_tmp); } |
Затем очистим необходимые флаги UART и включим прерывания
1 2 3 4 |
LCD_Char(d_tmp); } WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR|UART_RXFIFO_TOUT_INT_CLR); uart_rx_intr_enable(UART0); |
Соберём код, прошьём контроллер и запустим терминальную программу, в которой кроме настройки порта и скорости передачи включим также режим ввода данных Line Mode
Соединимся с портом и попробуем что-нибудь отправить в дисплей
В ответ мы сразу в приёмной части получим количество байтов
Также мы видим, что данные все принялись в ESP8266, судя по надписи на дисплее
Итак, на данном уроке мы научились принимать данные по шине UART, при этом используя механизм прерываний.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Многофункциональный переходник JTAG UART FIFO SPI I2C можно приобрести здесь CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
Исходный код не загружается, ссылка выдает ошибку! За пример СПАСИБО!
Спасибо! Поправил, теперь загружается.
Зачем буфер объявляли, если мы его не используем и выводим посимвольно из регистра? Спасибо.
Спасибо за урок! Функция обработки прерываний немного похожа на китайскую грамоту. Работает и хорошо.
Видимо мы таким образом выделяем память под данные регистра.
Заметил, что используется (PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD)/ Конфигурация пина GPIO1) вместо (PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD)/ Конфигурация пина GPIO3 (func U0RXD = 0), которой нет в описании eagl_soc.h, но по даташиту должна быть. Поэтому просто подставил FUNC_U0TXD, т.к. тоже =0.
Вообще в хедерах много пропусков, как например с объявлением прототипов(ets_isr_mask), а если посмотреть SDK 3.0.5 , то все на месте.