Продолжаем учиться писать код для микроконтроллера ESP8266.
И сегодня мы попробуем передать какие-нибудь данные с нашего модуля по шине UART на ПК.
С данным интерфейсом мы уже прекрасно знакомы из курсов занятий по программированию других МК, знаем протокол передачи, поэтому нам будет несложно, вооружившись знанием функционала SDK, настроить данный модуль UART и передать с него данные. Причём в технической документации на ESP8266 назван он именно UART, а не USART, как мы привыкли, из чего мы можем понять, что данная периферия та же, что и у других контроллеров, только здесь нет синхронизации.
Также в технической документации мы видим, что у контроллера два модуля UART, один из которых поддерживает ещё и аппаратный контроль, так как у него есть контакты RTS и CTS, а другой только приём и передачу. Максимальная скорость передачи данных заявлена производителем аж целых 4,5 Mbps.
Мы воспользуемся в нашем занятии самым первым полноправным модулем UART0, хотя аппаратный контроль мы использовать не будем.
Тем более выбор для занятия именно UART0 ещё обусловлен и тем, что к данному модулю уже подключена микросхема-переходник на нашей плате NodeMCU, конвертирующая поток данных между интерфейсами USB и UART. Если у кого обычный модуль ESP-01, то нужно будет подключать к ножкам GPIO1 и GPIO3 соответствующий переходник.
Также через этот же интерфейс UART0 мы считываем и записываем прошивку в наш модуль. В этом ничего страшного нет и никаких коллизий на этот счёт у нас не будет, так как в момент прошивания ПО в нашем МК не запущено, а в момент работы ПО мы ничего не прошиваем.
Также к нашей плате к ножкам RX и TX, выведенных ещё и наружу, мы подключим логический анализатор для того, чтобы посмотреть, как у нас будет происходить передача данных по UART
Думаю, что с функционалом SDK мы лучше будем знакомиться по мере настройки и использования нашего UART, так будет интереснее.
Проект мы сделаем из проекта прошлого урока BUTTON01 и назовём его UART_TX.
В качестве вступления давайте немного ещё улучшим наш файл сценария сборки Makefile.
Мы нашу основную цель all разобьём на несколько и добавим зависимости, благодаря которым у нас не будут формироваться новые файлы, если те, от которых они зависят, не были изменены.
Поэтому давайте добавим цель формирования объектного файла, он у нас пока единственный. А зависеть он будет от также единственного заголовочного файла и единственного файла с исходным кодом
1 2 3 4 5 |
ESPTOOL := esptool src/main.o: src/main.c inc/user_config.h @echo "CC src/main.o" @$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json $(CC_FLAGS) src/main.c -o src/main.o |
Теперь, если эти два файла не изменятся, у нас не будет формироваться объектный файл
Далее добавим следующую цель — формирование архивного файла, включающего в себя все объектные файлы
1 2 3 4 5 |
@$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json $(CC_FLAGS) src/main.c -o src/main.o build/app_app.a: src/main.o @echo "AR build/app_app.a" @$(AR) cru build/app_app.a src/main.o |
Данная цель будет зависеть от результата предыдущей.
Добавим ещё одну цель — формирование исполняемого файла формата ELF
1 2 3 4 5 6 |
@$(AR) cru build/app_app.a src/main.o build/app.out: build/app_app.a @echo "LD build/app.out" @$(LD) -L$(SDK)/lib -T$(SDK)/ld/eagle.app.v6.ld $(LD_FLAGS) build/app_app.a \ -Wl,--end-group -o build/app.out |
Остальное оставим в цели all, только для начала добавим теперь для неё зависимость от самой нашей последней цели
all: build/app.out
Также удалим те строки из команд данной цели, которые теперь выполняются в предыдущих целях
@echo «CC src/main.o»
@$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json $(CC_FLAGS) src/main.c -o src/main.o
@echo «AR build/app_app.a»
@$(AR) cru build/app_app.a src/main.o
@echo «LD build/app.out»
@$(LD) -L$(SDK)/lib -T$(SDK)/ld/eagle.app.v6.ld $(LD_FLAGS) build/app_app.a \
-Wl,—end-group -o build/app.out
Теперь у нас будет всё нормально и если файлы зависимостей не модифицировались, то цели, зависящие от них, выполняться не будут.
Теперь в файле main.c для начала удалим макрос для кнопки, так как в данном проекте мы её использовать не будем
#define BUTTON 4
Также удалим вот эти строки настройки ножки кнопки
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4);
PIN_PULLUP_EN(PERIPHS_IO_MUX_GPIO4_U);
И вот эту
gpio_output_set(0, 0, 0, (1 << BUTTON));
В бесконечном цикле удалим условие, оставив его тело
if(!((gpio_input_get()>>BUTTON) & BIT0))
{
gpio_output_set(0, (1 << LED), 0, 0);
}
Мы практически вернули код нашей мигалки из 5 урока, только я не стал делать проект для данного урока из проекта того урока, потому что мы ещё меняли процесс сборки.
Теперь по настройке UART.
Для начала подключим заголовочный файл библиотеки с прототипами функций для работы с UART
1 2 |
#include "gpio.h" #include "driver/uart.h" |
Только ввиду того, что каталог с такими библиотеками у нас не подключен, проект у нас не соберётся. Поэтому для начала добавим её в настройках проекта в соответствующем месте
Также данный путь добавим в Makefile в виде переменной
1 2 |
SDK_INC := $(SDK)/include SDK_DRIVER_INC := $(SDK)/driver_lib/include |
И добавим данную переменную в соответствующую команду
@$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json -I$(SDK_DRIVER_INC) $(CC_FLAGS) src/main.c -o src/main.o
Теперь проект соберётся.
Только если мы начнём функции, прототипы которых лежат в нашем файле вызывать, то у нас ничего не получится и мы получим ошибку.
Почему так будет происходить и как с этим бороться, мы чуть позже узнаем.
А пока добавим переменную для счётчика в app_main()
1 2 3 |
void ICACHE_FLASH_ATTR user_init() { uint16_t i=0; |
А вот теперь вызовем функцию инициализации из подключенной библиотеки, в двух параметрах которой передадим желаемую скорость работы обоих наших модулей UART
1 2 3 |
uint16_t i=0; // Configure the UART uart_init(BIT_RATE_115200, BIT_RATE_115200); |
И мы получим вот такую ошибку при линковке
Дело в том, что функции реализации находятся в определённой статической библиотеке, которую мы можем подключить и спокойно вызывать данные функции.
А второй путь, по которому пошли многие, — это написать свои функции с такими именами и добавить их, чтобы более гибко настраивать нашу периферию. Также есть готовый файл с исходным кодом, можно подключить его к проекту и менять там всё по своему усмотрению.
Так как нам никаких особенных настроек пока не требуется, то подключим в Makefile соответствующую статическую библиотеку
@$(LD) -L$(SDK)/lib -T$(SDK)/ld/eagle.app.v6.ld $(LD_FLAGS) build/app_app.a $(SDK)/lib/libdriver.a \
Вот теперь всё соберётся.
С отправкой строк вообще нет проблем, есть специальная функция os_printf из библиотеки osapi, которую мы подключили на прошлом уроке. Данная функция умеет по формату передавать строки в UART0.
В бесконечном цикле в обоих задержках прибавим интервал
ets_delay_us(1000000);
Давайте что-нибудь передадим, например номер итерации нашего бесконечного цикла
1 2 |
ets_delay_us(1000000); os_printf("String %04d\r\n", i); |
И чтобы наш итератор наращивался, добавим его инкрементирование и ограничив максимальное число, при котором будет итератор сбрасываться
1 2 3 4 |
while(1) { i++; if(i>9999) i=1; |
Настроим терминальную программу на соответствующий порт, пока не соединяемся, соберём код, прошьём контроллер, соединимся с портом в терминальной программе и увидим, что все наши строки благополучно приходят в окно терминала
Отлично! Всё передаётся.
Ну, раз уж так у нас быстро всё получилось с передачей данных, давайте передадим ещё что-нибудь полезное.
В технической документации по SDK существует ряд таких информационных полезных функций в разделе 3.3. System APIs.
Вызовем некоторые из них
1 2 3 4 5 6 7 8 9 10 11 |
os_printf("String %04d\r\n", i); os_printf("SDK version: %s\n", system_get_sdk_version()); os_printf("Version info of boot: %d\n", system_get_boot_version()); os_printf("Userbin address: 0x%x\n", system_get_userbin_addr()); os_printf("Time = %ld\r\n", system_get_time()); os_printf("RTC time = %ld\r\n", system_get_rtc_time()); os_printf("Chip id = 0x%x\r\n", system_get_chip_id()); os_printf("CPU freq = %d MHz\r\n", system_get_cpu_freq()); os_printf("Flash size map = %d\r\n", system_get_flash_size_map()); os_printf("Free heap size = %d\r\n", system_get_free_heap_size()); system_print_meminfo(); |
Данные функции имеют говорящие имена, которые подсказывают нам, какую информацию возвращает каждая функция, также описание есть и в документации. Например мы выведем в терминал версию SDK, системное время, время с момента запуска МК, информацию о свободном месте в памяти и т.д.
Давайте соберём наш код, прошьём контроллер и попытаемся всё это увидеть в терминальной программой. Перед процедурой заливки прошивки в контроллер не забываем освободить виртуальный порт, отключив соединение в терминальной программе
Также мы помним, что у нас подключен логический анализатор, давайте посмотрим, что происходит на ножке TX нашего UART0 при передаче данных
Мы видим, что всё передаётся бесперебойно и отлично распознаётся программой логического анализа.
Итак, на данном уроке мы научились пользоваться модулем UART контроллера ESP8266 и передавать с помощью него информацию подключенному узлу.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Многофункциональный переходник JTAG UART FIFO SPI I2C можно приобрести здесь CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий