Продолжаем учиться писать код для микроконтроллера 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
Многофункциональный переходник CJMCU FT232H USB к JTAG UART FIFO SPI I2C можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий