Уже много лет пользуется большой популярностью микроконтроллер ESP8266 фирмы Espressif System, главная особенность которого встроенный интерфейс Wi-Fi. Так же на рынке присутствуют множество модулей на базе этого микроконтроллера, имеющих на борту различную периферию. Основной средой разработки является платформа Arduino.

Для микроконтроллера производителем разработано программное обеспечение модема сети Wi-Fi управляемого АТ-командами. К сожалению, использование ESP8266 как модема не снискало должной популярности. В данной работе предлагается программное обеспечение взаимодействия микроконтроллера с модемом через АТ-команды для реализации асинхронного многоканального ТСР-сервера. (Скачать исходник)

Работа с модемом через АТ-команды переставляют собой обмен текстовыми строками завершающимися символами возврат каретки и перевод строки (0x0D, 0x0A, они же CR, LF, они же /r, /n). Например, для команды проверки связи с модемом:

Запрос

Ответ модема

Комментарий

АТ/r/n

 

АТ-команда

 

/r/nАТ/r/n

Эхо команды

 

/r/nOK/r/n

Ответ

Стоит отметить, что некоторые ответные сообщения модема могут начинаться с символов возврата каретки и перевода строки, то есть модем выдает строки нулевой длины, а некоторые сообщения не содержат этих префиксных символов. Таким образом, на АТ-команду модем отвечает строкой (или несколькими строками) ненулевой длины, завершающейся символами возврат каретки и перевода строки. Установить закономерность появления строк нулевой длины не представляется возможным.

Отдельно стоят асинхронные сообщения модема. Эти сообщения модем выдает не как ответ на АТ-команды, а как реакцию на какие-то внешние события. Например, с помощью сообщение «+IPD …» - модем выдает принятые из сети данные, строками «0,CONNECT/r/n», «0,CLOSE/r/n» модем сообщает о подключении/отключении клиента к серверу TCP. Асинхронные сообщения не могут вклиниться в строку сообщений модема, однако могут перебить процесс получения ответов на АТ-команды. Например:

АТ/r/n

 

АТ-команда

 

/r/nАТ/r/n

Эхо команды

 

+IPD,0,1,192.168.0.101,49176:1

Асинхронное сообщение

 

/r/nOK/r/n

Ответ

Формат и наличие асинхронных сообщений зависят от режима работы модема: точка доступа или оконечная станция, режим множественного или единичного соединения, режим клиента или сервера и т. п. К сожалению, разработчики программного обеспечения не достаточно хорошо продумали форматы асинхронных сообщений, что несколько затрудняет их идентификацию.

После глубоко анализа системы АТ-команд модема ESP8266, разработан программный модуль для микроконтроллеров реализующий многоканальный ТСР-сервер. (Скачать исходник). Программное обеспечение предназначено для использования в шаблоне «Суперцикл» и состоит из функций инициализации ServerTCPInit() и главного метода ServerTCPMain() вызываемого в суперцикле (главном цикле).

Кратко опишем работу модуля. В суперцикле вызывается главный метод. В главном методе проходит конфигурирование модема в режиме многоканального ТСР-сервера, после успешной конфигурации программа переходит в режим считывания и передачи данных модему. Считанные данные помещаются в приемный буфер типа «первый вошел, первый вышел» (FIFO), интерфейсными функциями модуля можно считать принятые данные. Отправляемые данные, с помощью интерфейсных функций, записываются в передающий буфер, модуль по наличию данных в буфере автоматически начинается их передачу модему. При выполнении на процессоре Cortex-M0 с тактовой частоте 32 МГц, максимальное время выполнения кода главного метода не превышает 200мкс.

Модуль использует следующие функции:

  • Стандартная библиотека языка Си для работы со строками <string.h>,
    Функции memset(), strncpy(), strncmp(), strlen().

  • Функции для работы с таймером.
    void StartTimer(Timer_t* t, unsigned long pt); //запустить таймер t, на время pt
    unsigned char Timer(Timer_t* t); //Узнать состояние таймера t, если не 0, то таймер сработал

    Функции работают с системным миллисекундным таймером и позволяют гибко отмерять интервалы времени. Код функций работы с таймером можно изучить в примере проекта в файлах wait.c, wait.h.

  • Функции для работы с последовательным портом.
    void PutModem(unsigned char a); //передача байта в поток вывода
    void PutStrModemF(const char* s); //передача константной строки в поток вывода из ПЗУ
    void PutStrModemM(char* s); //передача неконстантной строки в поток вывода из ОЗУ
    unsigned short InkeyModem(void); //Прием байта из потока ввода, возвращает uint16, нет данных 0, есть 0x0100|data
    void ClearModem(void); //очистка приемного буфера потока ввода
    void GetTxModem(void); //узнать количество свободного места в буфере передачи
    Для удобства портировании модуля, функции определены через макросы.
    Работа с последовательным портом и на прием, и на передачу, осуществляется через буфер FIFO. Соответственно функции ввода/вывода являются неблокирующими. Функции PutStrModemF(), PutStrModemM() по сути, являются одинаковыми, но на некоторых платформах, например AVR, требуют различной реализации, поэтому они выделены в две различные функции.
    Функция GetTxModem() возвращает количество свободного места в буфере передачи и необходима для контроля переполнения буфера передачи последовательного порта. Если контроль переполнения не требуется, то в макросе определения функции необходимо поставить ненулевое значение.

Существуют два основных метода инициализации модуля: на этапе компиляции - через константы инициализации или в процессе выполнения программы - через функции установки параметров.

Основные константы инициализации:

#define ServerTCPStationAP (1) //Использовать режим станции !0 /точки доступа 0
#define ServerTCPDHCP (0) //Использовать DHCP
#define ServerTCPLocalPort "80" //локальный порт
#define ServerTCPIpLocal "192.168.0.105" //Локальный адрес
#define ServerTCPGateway "192.168.0.1" //Шлюз
#define ServerTCPNetmask "255.255.255.0" //Маска подсети
#define ServerTCPSSID "_INT" //Наименование точки доступа для подключения
#define ServerTCPParolAP "111111111" //пароль точки доступа для подключения
#define ServerTCPSSIDNew "IBAH" //наименование точки доступа для создания
#define ServerTCPParolAPNew "0123456789" //пароль точки доступа для
#define ServerTCPChanelAP (13) //Канал создаваемой точки доступа
#define ServerTCPNumStation (1) //Максимальное количество станций подключаемых к создаваемой точке доступа
#define ServerTCPSoket (2) //Максимальное количество подключений (сокетов) к ТСР-серверу 1-5
#define ServerTCPTimout (5) //Таймаут соединения ТСР-сервера, 1–7200 секунд, 0 без таймаута
#define ServerTCPTimoutSend (2999) //Таймаут отправки пакета, мс
#define ServerTCPBufferRX (512)  //размер приемного буфера сокета, допустимые значения 16,32,64,128,256,512,1024
#define ServerTCPBufferTX (2048) //размер буфера передачи сокета, допустимые значения 16,32,64,128,256,512,1024,2048-max

Стоит отметить что каждому сокету выделяется память на этапе компиляции, например при использовании двух сокетов, 512 байт приемного буфера сокета, 2048 буфера передачи, требуемый объем оперативной памяти (2048+512)*2=5120 байт. Чем больше размер буфера передачи (константа ServerTCPBufferTX), тем меньше «накладных расходов» при передаче данных большого объема, однако программное обеспечение модема ограничивает максимальный размер блока передаваемых данных 2048 байт. Размер буфера приема (ServerTCPBufferRX) должен быть больше максимального размера блока принимаемых данных. Константа ServerTCPTimoutSend таймаут отправки пакета, это время ожидания доставки блока данных клиенту, если за это время не блок данных не будет выставлен флаг ошибки.

Для настройки настроек модуля в процессе выполнения программы можно использовать функции установки, которые необходимо вызвать сразу после вызова функции инициализации ServerTCPInit(). В случае недопустимого вызова, функций установки возвращаемое значение ненулевое.

unsigned char SetStationAPServerTCP(unsigned char a); //Установить режим станции !0/ точки доступа 0
unsigned char SetDHCPServerTCP(unsigned char a); //Включить/выключить сервер DHCP !0/0
unsigned char SetLocalPortServerTCP(const char* s); //Установить локальный порт
unsigned char SetLocalIPServerTCP(const char* s); //Установить локальный адрес
unsigned char SetGetwayServerTCP(const char* s); //Установить шлюз
unsigned char SetNetmaskServerTCP(const char* s); //Установить маску подсети
unsigned char SetNameAPServerTCP(const char* s); //Установить наименование точки доступа
unsigned char SetParolAPServerTCP(const char* s); //Установить пароль точки доступа
unsigned char SetChanelAPServerTCP(unsigned char a); //Установить канал точки доступа
unsigned char SetNumStationServerTCP(unsigned char a); //Установить количество станций подключаемых к точке доступа

Функции имеют различный смысл в зависимости от конфигураций модема, например в режиме точки функция SetDHCPServerTCP() управляет DHCP-сервером, а в режиме оконечной станции DHCP-клиентом. Функции SetNameAPServerTCP(), SetParolAPServerTCP() устанавливают либо наименование/пароль создаваемой точки доступа, либо наименование/пароль точки доступа к которой осуществляется подключение.

Обмен данными с через ТСР-сервер осуществляется посредством буферов типа FIFO через интерфейсные функции. Размер буферов указывается в константах настройки модуля. Каждая интерфейсная функция принимает в качестве аргумента номер сокета (NumSoket), к которому она обращается. Константой

#define UseCheckSoket (0)//Использовать проверку номера сокета

можно отключить проверку номера сокета.
Функции являются неблокирующими.
Функции записи в буфер передачи сокета имеют две реализации, одна записывает данные в буфер и инициирует передачу, другая только записывает данные в буфер передачи.

Функции чтения из буфера приема сокета:

unsigned short Inkey16ServerTCP(unsigned char NumSoket); //Прочитать из приемного буфера, нет данных возвращает 0, есть данные 0x0100|data
unsigned char  InkeyServerTCP(unsigned char NumSoket); //Прочитать из приемного буфера, нет данных возвращает 0

Функция Inkey16ServerTCP() предназначена для чтения бинарных данных, Функция InkeyServerTCP() для чтения текстовых данных.

Функции передачи данных:

void PutServerTCP(unsigned char a, unsigned char NumSoket); //Передать байт, сразу инициируется передача
void PutDelayServerTCP(unsigned char a, unsigned char NumSoket); //Положить в буфер передачи, передача не инициируется
void StartPutServerTCP(unsigned char NumSoket); //Инициировать передачу из буфера
unsigned char GetStartPutServerTCP(unsigned char NumSoket); //Узнать состояние передачи

Функции работы с буферами приема-передачи:

void ClearRxServerTCP(unsigned char NumSoket); //очистка буфера приема сокета
void ClearTxServerTCP(unsigned char NumSoket); //очистка буфера передачи сокета
unsigned short GetTxFreeServerTCP(unsigned char NumSoket); //Узнать количество свободного места в буфере передачи
unsigned short GetTxBusyServerTCP(unsigned char NumSoket); //Узнать количество байт в буфере передачи

Функции форматного вывода данных:

void PutStrServerTCP(const char* a, unsigned char NumSoket); //Передать строку
void PutStrDelayServerTCP(const char* a, unsigned char NumSoket); //Записать строку в буфер передачи

Вывод в десятичном виде без знака и значащих нулей:

void PutDec16uServerTCP(unsigned short a, unsigned char NumSoket);
void PutDec32uServerTCP(unsigned long a, unsigned char NumSoket);
void PutDec16uDelayServerTCP(unsigned short a, unsigned char NumSoket);
void PutDec32uDelayServerTCP(unsigned long a, unsigned char NumSoket);

Вывод в шестнадцатеричном виде

void PutHex8ServerTCP(unsigned char a, unsigned char NumSoket);
void PutHex16ServerTCP(unsigned short a, unsigned char NumSoket);
void PutHex32ServerTCP(unsigned long a, unsigned char NumSoket);
void PutHex8DelayServerTCP(unsigned char a, unsigned char NumSoket);
void PutHex16DelayServerTCP(unsigned short a, unsigned char NumSoket);
void PutHex32DelayServerTCP(unsigned long a, unsigned char NumSoket);

Для отображения состояний модуля определены глобальные переменные, доступные только для чтения из других участков программы.

extern const volatile unsigned char InitServerTCP; //Инициализация модема
extern const volatile unsigned char ReadyServerTCP; //Готов к подключению клиента
extern const volatile unsigned char ConnectServerTCP; //Подключен клиент
extern const volatile unsigned char ErrorServerTCP; //Ошибка инициализации модема

С помощью этих переменных можно определить состояние модуля и ошибки инициализации модема.

Индикация приема/передачи данных через ТСР-сервер

extern const volatile unsigned char IndikRxServerTCP; //Индикатор Прием
extern const volatile unsigned char IndikTxServerTCP; //Индикатор Передача

Данные переменные устанавливаются в ненулевое состояние в начале процесса передачи и при приеме данных. Время индикации, время нахождения в ненулевом состоянии, определяется константами:

#define TimeRxIndik (100) //Время индикации Прием, мс
#define TimeTxIndik (100) //Время индикации Передача, мс

Считать параметры настройки модема можно с помощью следующих глобальных переменных доступных только для чтения:

extern const volatile unsigned char StationAPServerTCP; //Режим станции !0 / режим точки доступа 0
extern const volatile unsigned char OnDHCPServerTCP; //Включен DHCP
extern const volatile unsigned char ChanelAPServerTCP; //Канал точки доступа extern const volatile unsigned char NumStationServerTCP; //Количество станций подключаемых к точке доступа
extern const char IpLocalServerTCP[ServerTCPRazmerBufferIP]; //Локальный адрес
extern const char IpGatewayServerTCP[ServerTCPRazmerBufferIP]; //Шлюз
extern const char IpNetmaskServerTCP[ServerTCPRazmerBufferIP]; //Маска подсети
extern const char SSIDServerTCP[ServerTCPRazmerBufferAP]; //наименование Точки доступа
extern const char ParolAPServerTCP[ServerTCPRazmerBufferAP]; //пароль Точки доступа

Эти переменные имеют различное поведение при работе модема в режиме станции, режиме точки доступа, при включенном или выключенном клиенте/сервере DHCP.

Следующая группа переменных позволяет считать информацию о подключениях к ТСР-серверу.

extern const char IpRemovServerTCP[ServerTCPSoket][ServerTCPRazmerBufferIP]; //Удаленный адрес
extern const char PortRemovServerTCP[ServerTCPSoket][ServerTCPRazmerBufferPort]; //Удаленный порт

Следует отметить, что данные переменные являются массивом строк, количество строк в массиве определяется константой

#define ServerTCPSoket (2) //Максимальное количество подключений ТСР-сервера 1-5 

Последняя группа переменных позволяет получить информацию о работе точки доступа и сервера DHCP.

extern const char MACLocalServerTCP[ServerTCPRazmerBufferMAC]; //локальный MAC адрес. Обновляется при подключении в режиме станции к DHCP-серверу
extern const char ConMACAdressServerTCP[ServerTCPRazmerBufferMAC]; //Последний подключившийся MAC к точке доступа. Обновляется в режиме Точки доступа
extern const char DisMACAdressServerTCP[ServerTCPRazmerBufferMAC]; //Последний отключившийся MAC от точки доступа. Обновляется в режиме Точки доступа
extern const char DHCPIPAdressServerTCP[ServerTCPRazmerBufferIP]; //Последний выданный DHCP-сервером IP-адрес. Обновляется в режиме Точки доступа и DHCP-сервера
extern const char DHCPMACAdressServerTCP[ServerTCPRazmerBufferMAC];//Последний MAC которому DHCP-сервер выдавал IP-адрес. Обновляется в режиме Точки доступа и DHCP-сервера

Эти переменные, также имеют различное поведение при работе модема в режиме станции, режиме точки доступа, при включенном или выключенном клиенте/сервере DHCP, что отражено в комментариях.

Пример проекта собран в среде разработки IAR7, для микроконтроллера STM32L072. Протестирован с версией ПО модема 3.0.5 (от 08.2021).

Пример получен урезанием всего лишнего из работающего устройства. Данный проект предназначен для изучения приемов работы с ТСР-сервером. В примере реализован простейший асинхронный Web-сервер, выдающий страничку с основными параметрами устройства. Сервер поддерживает два подключения. Настройки и состояние ТСР-сервера доступны по интерфейсу Modbus

Из периферии микроконтроллера используются:

  • Системный таймер (файлы wait.h, wait.c), на котором реализованы функции измерения интервалов времени.

  • LPUART (файлы uart0.h, uart0DMATx.c), непосредственно в проекте не используется, однако на это порт можно выводить отладочную информацию о работе модуля. Вывод отладочной инфор мации определяется константами:
    #define OTLADKA (0) //включить режим отладки при инициализации модема
    #define OTLADKA_IPD (0) //включить режим отладки при приеме данных
    #define OTLADKA_IPD_ERR (0) //включить режим выдачи сообщений об ошибках при приеме
    #define OTLADKA_IPD_DATA (0) //включить режим отображения принимаемых данных
    #define OTLADKA_TX (0) //включить режим отладки при передаче данных
    #define OTLADKA_TX_DATA (0) //включить режим отображения передаваемых данных

  • USART1 (файлы uart1.h, uart1DMATx.c) используется для обмена по протоколу Modbus RTU со скадой.

  • USART2 (файлы uart2.h, uart2InteruptTx.c) используется для обмена данными с модемом ESP8266.

Для сохранения настроек модема используется микросхема FRAM памяти FM25l256. Интерфейс памяти в файлах FM25L256.h, FM25L256.c. Описание энергонезависимых данных и функции автоматического сохранения/восстановления данных в файлах SaveRestore.h, SaveRestore.c.

В файле main.h определен битовый регистр состояния устройства. Из регистра удалены флаги, не имеющие отношения к работе ТСР и Web серверов.

Кратко опишем работу программы. После старта микроконтроллера происходит инициализация всех периферийных модулей. А так же инициализация ТСР-сервера и Web-сервера. В главном цикле последовательно вызываются функции  ТСР-сервера, Web-сервера, интерфейса Modbus RTU и интерфейса автоматического сохранения энергонезависимых данных.

Состояние, настройки, а также служебные переменные модема выводятся и загружаются по интерфейсу Modbus RTU. Описание и адреса тегов для чтения можно посмотреть в файле функции Prg2ModBusOutReg(), а тегов для записи в функции ModBus2PrgOutReg() (файл ModBus2Prg.c). Стоит отметить, что заполнение регистров Modbus происходит различными значениями при разных настройках модема, режим точки доступа или режим оконечной станции, включенном выключенном клиенте или сервере DHCP. Например, в режиме оконечной станции при включенном DHCP-клиенте в регистры Modbus записывается значение строки параметров ТСР-сервера, а при выключенном DHCP-клиенте значение настроек из энергонезависимой памяти. Это позволяет через интерфейс Modbus гибко конфигурировать модем, используя минимальное количество регистров.

С помощью интерфейсных функций ТСР-сервера данные HTTP запроса считываются Web-сервером. Сервер формирует страничку в соответствии с запросом браузера. Описание файловой системы Web-сайта можно посмотреть в файле WebSite.h.

В завершении несколько фотографий.

Электрический шкаф с платой контроллера, на которой установлен модуль ESP-01S
Электрический шкаф с платой контроллера, на которой установлен модуль ESP-01S
Страница скады с настройками модема ESP8266
Страница скады с настройками модема ESP8266

В заключении хочется отметить, что модем на базе ESP8266, хоть и не отличается высоким быстродействием и не предназначен для передачи больших объемов данных, но является вполне адекватным устройством при своей стоимости. Тестирование в режиме приема-передачи в сети Wi-Fi совместно с Modbus-клиентом показало, что пропадает один блок данных на 40-50 тысяч запросов с интервалом 100мс. Возможно, это не является ошибкой, просто был превышен таймаут отправки блока данных, если таймаут установить большим значением количество ошибок существенно уменьшится.

Скачать исходные файлы ТСР-сервера

Скачать пример проекта

Комментарии (9)


  1. wigneddoom
    21.12.2024 17:13

    Я видел такое, что вам, людям, и не снилось. Атакующие корабли, пылающие над Орионом; Лучи Си, разрезающие мрак у ворот Тангейзера. Все эти мгновения затеряются во времени, как... слёзы в дожде... Пришло время умирать.

    Восемь лет назад, чтобы не "зашквариться" об Ардуино, я собирал под линуксом компилятор GGC с патчами для Tensilica Xtensa. Оптимизировал нашу прошивку, чтобы она полностью из RAM выполнялась, а не с флешки. Воевал с WiFi стеком, который цельнотянут с BSD. Эх, были времена...


  1. tigreavecdesailes
    21.12.2024 17:13

    Как поднять Wi-Fi на ESP8266 и не зашквариться об Arduino

    Использовать замечательный родной SDK на бвзе FreeRTOS

    https://github.com/espressif/ESP8266_RTOS_SDK


    1. IBAH_II Автор
      21.12.2024 17:13

      Если АТ-команду существуют, должен же ими кто-то пользоваться


      1. Moog_Prodigy
        21.12.2024 17:13

        Обычно ими пользуются те, кто работает с такими железками как SIM900 и иже с ними. Ну или с железными диалап-модемами.


        1. VirtualVoid
          21.12.2024 17:13

          Или если так уже устроена и спроектирована система в которую внедряется "модем". В новых заработках не вижу особого смысла в сей абстракции, если только оно не должно отвечать каким-то строгим требованиям.


  1. randomsimplenumber
    21.12.2024 17:13

    не зашквариться об Arduino

    Как говорится, вечер в хату, господа программисты. Кофе-брейк в радость, печенька в сладость.


    1. IBAH_II Автор
      21.12.2024 17:13

      Тогда "не оскоромиться". Аминь.

      Зашквар. По моему вполне современное слово, без всякой уголовно-блатной окраски. Не только ли некоторые знают его происхождение.


  1. Equity184
    21.12.2024 17:13

    а что не так с arduino ?


  1. 4chemist
    21.12.2024 17:13

    Лапша из if не читаема. Воспользовались бы switch-case вариантом.

    Велосипедов полон код. Особенно доставило ваше "!0".

    С комментариями переборщили, следует писать код так чтобы он был понятен с минимумом комментариев.

    "Магических" констант в коде не должно быть, ваша переменная SostWiFi постоянно сравнивается с этими "магическими" константами.