Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2739
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Разработка IoT-приложений

728

Функция начинает проверку, равно ли состояние сокета SOCK_ESTABLISHED: это означает, что удаленный узел установил соединение с нашим устройством. И, напротив, если сокет не находится в режиме прослушивания (строка 66), возможно, удаленный узел закрыл соединение: поэтому нам нужно снова сконфигурировать сокет в режиме прослушивания, вызвав функцию RetargetInit(). Если удаленный узел установил соединение, мы можем начать отправку буфера ptr через соединение TCP/IP.

Функция _read() практически не отличается от функции _write(). Полный исходный код см. в примерах книги. Чтобы использовать данный модуль, нам просто нужно определить макрос RETARGET_TCP на уровне проекта и, в конечном итоге, удалить макрос

OS_USE_SEMIHOSTING.

Чтобы установить соединение с устройством, пользователи Linux и MacOS могут использовать команду telnet, а пользователи Windows могут использовать программуэмулятор терминала, например putty.

26.2.4. Настройка HTTP-сервера

Модуль Internet/httpServer обеспечивает полную реализацию HTTP-сервера, построенного на уровне Ethernet. Данный модуль позволяет вам настроить HTTP-сервер за несколько шагов, особенно если вам нужно просто обработать статический контент (то есть простые веб-страницы, на которых не нужно динамически обрабатывать данные).

Функция

void httpServer_init(uint8_t * tx_buf, uint8_t * rx_buf, uint8_t cnt, uint8_t * socklist);

используется для конфигурации модуля HTTP. Она принимает два указателя, tx_buf и rx_buf, на два буфера памяти, используемых для хранения данных, которыми обменивается HTTP-сервер. В данных массивах должно быть достаточно места для хранения заголовков HTTP. Фактически, при доступе к веб-странице браузеру необходимо обменяться с веб-сервером несколькими «базовыми» сообщениями, определенными протоколом HTTP. Эти сообщения занимают несколько сотен байт, и по этой причине минимально допустимый размер буферов tx_buf и rx_buf равен 1024 Байт21. Параметр cnt сообщает модулю HTTP, сколько сокетов W5500 он может использовать, а параметр socklist используется для передачи точного списка доступных сокетов.

Например, следующий фрагмент кода инициализирует HTTP-сервер, передавая два буфера, каждый размером 1024 Байт, и массив, содержащий список сокетов, используемых для обработки HTTP-запросов:

#define DATA_BUF_SIZE 1024 #define MAX_HTTPSOCK 5

uint8_t RX_BUF[DATA_BUF_SIZE], TX_BUF[DATA_BUF_SIZE];

uint8_t socknumlist[] = {0, 1, 2, 3, 4};

...

httpServer_init(TX_BUF, RX_BUF, MAX_HTTPSOCK, socknumlist);

...

21 Можно уменьшить размер двух буферов, поскольку библиотека HTTP может разделить весь поток HTTP на более мелкие части, но это увеличит время передачи.

Разработка IoT-приложений

729

После того, как HTTP-сервер настроен с точки зрения сети, нам нужно сообщить ему о содержимом, которое ему нужно обслуживать (HTML-страницы, изображения и т. д.). Это можно сделать двумя способами: один подходит для действительно небольших и ограниченных приложений, а другой – для более сложных и структурированных вебприложений.

Воспользовавшись функцией:

void reg_httpServer_webContent(uint8_t * content_name, uint8_t * content);

мы можем связать с заданным ресурсом (например, с файлом index.html) массив байт для отправки через сокет в браузер. Параметр content_name соответствует имени ресурса, а параметр content – массиву, содержащему байты, образующие ресурс.

Почему HTTP-серверу для выполнения своей работы требуется нечто большее, чем просто свободный сокет? Это фундаментальная концепция, которую следует учитывать при разработке встроенных веб-приложений, поэтому мы остановимся на ней несколько слов.

Современные веб-приложения достаточно сложны. Обычно сайт состоит из нескольких ресурсов:

HTML-страницы, содержащие фактическое содержимое веб-приложения;

изображения, украшающие содержимое HTML и, в некоторых случаях, составляющие часть содержимого веб-страницы;

Файлы CSS и Javascript, которые конфигурируют отображение страницы и ее функциональные возможности.

Когда веб-браузер обращается к веб-сайту, он начинает загрузку главной HTMLстраницы (которая соответствует файлу index.html, если не указан иной). Эта страница анализируется почти сразу (некоторые браузеры могут начать синтаксический анализ страниц, если они получают самые первые байты), и, если она содержит ссылки на другие веб-ресурсы, браузер начинает загружать их практически параллельно. Этот одновременный доступ к ресурсам веб-сайта подразумевает, что несколько сокетов открываются браузером по отношению к HTTP-серверу (протокол HTTP не имеет состояния и определяет, что для каждого веб-ресурса должен быть выполнен отдельный запрос к серверу). Для настоящего веб-сервера, такого как Apache или NGIX, предназначенного для работы на мощной машине, это не составляет проблемы. Такие серверные приложения предназначены для обработки даже тысяч одновременных подключений. Более того, используемое мощное оборудование позволяет обслуживать контент даже за несколько миллисекунд, в зависимости от скорости соединения. Сокеты открываются и закрываются менее чем за секунду.

Для веб-сервера, работающего на полностью встроенной платформе, доступ к одновременным ресурсам – вещь, которую необходимо тщательно охарактеризовать. Каждый сокет потребляет несколько аппаратных ресурсов, и для устройства WIZnet максимальное количество сокетов ограничено. Это означает, что мы не можем организовать приложение по своему усмотрению, и некоторые современные фреймворки (например, Bootstrap, Angular JS и т. д.) часто необходимо переорганизовывать при использовании на встроенном устройстве (иногда нам приходится вообще избегать их).

Разработка IoT-приложений

730

Например, чтобы отправить простую HTML-страницу, мы можем написать следующий код:

const char webpage[] = "<html> <head>

<title>Simple Web Page</title> </head>

<body>

<h1>Hello World!</h1> </body>";

reg_httpServer_webContent((uint8_t*)"index.html", webpage);

...

У этого подхода есть несколько подводных камней. Прежде всего, веб-страница встроена в код прошивки. Это означает, что содержимое HTML добавляется к самой прошивке, увеличивая весь бинарный образ.

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

Второй подход заключается в написании модуля HTTP таким образом, чтобы он находил и извлекал статический контент из устройства памяти. Например, мы можем изменить его код так, чтобы он загружал веб-ресурсы из Flash-памяти. Именно это мы и сделаем в следующем примере, где будет использоваться библиотека FatFs для извлечения веб-контента, хранящегося на внешней SD-карте.

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

void httpServer_run(uint8_t seqnum);

Данная функция принимает индекс, соответствующий идентификатору сокета, хранящемуся в параметре socklist, переданном в функцию httpServer_init(). Для каждого зарегистрированного сокета эта функция проверяет состояние соответствующего сокета и выполняет конечный автомат HTTP в соответствии с текущим состоянием сокета. Например, если переданный сокет открыт и находится в режиме прослушивания, функция httpServer_init() проверяет, установил ли удаленный узел соединение. Весь протокол HTTP и конечный автомат обрабатываются модулем HTTP, при этом нет необходимости знать детали реализации, если нам не нужно делать с ним что-то более сложное.

Наконец, модуль HTTP использует некоторые внутренние задержки, основанные на общей единице временного интервала (тике). Функция:

httpServer_time_handler();

должна вызываться из таймера, сконфигурированного на истечение каждые 1 мс (ISR таймера SysTick – подходящее место для вызова этой функции).

Разработка IoT-приложений

731

26.2.4.1. Веб-осциллограф

Из-за ограниченных аппаратных ресурсов большинства микроконтроллеров STM32, оснащающих шестнадцать платам Nucleo, данный пример был протестирован только на микроконтроллерах STM32F401RE, STM32F411RE и STM32F446RE.

А сейчас мы рассмотрим более полный пример, который показывает, как использовать ИС W5500 и модуль ioLibrary_Driver22 для создания сложных и структурированных приложений. В этом примере мы будем использовать несколько периферийных устройств STM32 для создания чего-то вроде веб-осциллографа, показанного на рисунке 7. Подключив источник сигнала к одному из входов периферийного устройства АЦП, мы можем увидеть сигнал соответствующей формы с простым доступом к веб-консоли, используя обычный браузер. Видео, показывающее, как работает осциллограф, можно посмотреть здесь23.

Рисунок 7: Интерфейс веб-осциллографа

В примере используется несколько популярных и современных веб-фреймворков для структурирования пользовательского интерфейса: Bootstrap24, jQuery25 и D3js26. Описание этих фреймворков выходит за рамки книги, и предполагается, что читатель имеет достаточные знания о наиболее распространенных и современных методах веб-разра- ботки.

Приложение состоит из двух основных частей: «основная» часть, отвечающая за ана- лого-цифровое преобразование с выбранного входа АЦП (по умолчанию IN0), и часть,

22Библиотека ioLibrary_Driver примера из данной главы не является официальной библиотекой, предоставляемой WIZnet. Автор книги внес несколько изменений в модуль HTTP для повышения его надежности, производительности и гибкости. Например, данная модифицированная версия может обслуживать контент, хранящийся на SD-карте, с помощью модуля FatFs или на ПК разработчика с использованием полуохостинга ARM.

23https://youtu.be/fjtLQJDJ_04

24http://getbootstrap.com/

25https://jquery.com/

26https://d3js.org/

Разработка IoT-приложений

732

которая обслуживает HTTP-запросы с помощью модуля ioLibrary_Driver. Предполагается, что все веб-ресурсы размещены на SD-карте, доступ к которой осуществляется с помощью модуля FatFs и SPI-совместимого драйвера, разработанного автором книги. Однако для упрощения процесса разработки приложение также может обслуживать контент с ПК разработчика, используя вызовы полуохостинга ARM и обычные функции стандартной библиотеки Си.

Следующий фрагмент кода относится к функции main(). Чтобы получить сигнал, который изменяется во времени (например, синусоидальный 50 Гц сигнал), нам необходимо выполнять АЦП преобразование через равные промежутки времени. Поэтому мы используем таймер TIM227 для управления периферийным устройством ADC1, которое сконфигурировано для работы с DMA в циклическом режиме: так таймер будет запускать преобразование непрерывно. Таким образом, АЦП запускается в режиме DMA (строка 145), а преобразованные значения сохраняются в массиве _adcConv. Семафор adcSem, созданный в строке 140, будет использоваться для управления доступом к массиву _adcConv, который содержит преобразованные значения АЦП. Его роль будет лучше объяснена позже.

Имя файла: src/ch25/main-ex2.c

135int main(void) {

136HAL_Init();

137Nucleo_BSP_Init();

139osSemaphoreDef(adcSem);

140adcSemID = osSemaphoreCreate(osSemaphore(adcSem), 1);

142MX_ADC1_Init();

143MX_TIM2_Init();

144HAL_TIM_Base_Start(&htim2);

145HAL_ADC_Start_DMA(&hadc1, (uint32_t*)_adcConv, 200);

147

MX_SPI1_Init();

148

 

149#if defined(_USE_SDCARD_) && !defined(OS_USE_SEMIHOSTING)

150SD_SPI_Configure(SD_CS_GPIO_Port, SD_CS_Pin, &hspi1);

151MX_FATFS_Init();

152

153if(f_mount(&diskHandle, "0:", 1) != FR_OK) {

154#ifdef DEBUG

155asm("BKPT #0");

156#else

157while(1) {

158HAL_Delay(500);

159HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

160}

161#endif //#ifdef DEBUG

162}

163

164 #ifdef OS_USE_TRACE_ITM

27 Представленный здесь исходный код относится к плате Nucleo-F401RE.

Разработка IoT-приложений

733

165/* Вывод содержимого SD через порт ITM */

166TCHAR buff[256];

167strcpy(buff, (char*)L"/");

168scan_files(buff);

169#endif //#ifdef OS_USE_TRACE_ITM

170

171 #endif //#if defined(_USE_SDCARD_) && !defined(OS_USE_SEMIHOSTING)

172

173osThreadDef(w5500, SetupW5500Thread, osPriorityNormal, 0, 512);

174osThreadCreate(osThread(w5500), NULL);

175

176osKernelStart();

177/* Сюда никогда не придем, но на всякий случай... */

178while(1);

179}

Если установлен глобальный макрос _USE_SDCARD_, и мы не используем полухостинг ARM (поэтому глобальный макрос OS_USE_SEMIHOSTING не установлен), это означает, что веб-ресурсы (файлы HTML, изображения и файлы сценариев CSS/JS) хранятся на карте MicroSD, вставленной в слот W5500 Shield. Поэтому инициализируется библиотека FatFs (строки [150:151]) и монтируется первый раздел (строка 153). Более того, если мы используем отладчик, способный считывать стимулы ITM, мы выводим на SWV-консоли содержимое SD-карты, вызывая процедуру scan_files() (которая была показана в Главе

25).

Наконец, запускается поток SetupW5500Thread(), который отвечает за конфигурацию ИС W5500 и обработку входящих HTTP-запросов. Его код достаточно прост, и он показан ниже.

Имя файла: src/ch25/main-ex2.c

181void SetupW5500Thread(void const *argument) {

182UNUSED(argument);

183

184/* Конфигурация модуля W5500 */

185IO_LIBRARY_Init();

186

187/* Конфигурация сервера HTTP */

188httpServer_init(TX_BUF, RX_BUF, MAX_HTTPSOCK, socknumlist);

189reg_httpServer_cbfunc(NVIC_SystemReset, NULL);

190

191/* Запуск обработки сокетов */

192while(1) {

193for(uint8_t i = 0; i < MAX_HTTPSOCK; i++)

194httpServer_run(i);

195/* Вводим задержку в 1 мс просто за тем, чтобы другие потоки

196* с таким же или более низким приоритетом могли выполняться */

197osDelay(1);

198}

199}

Разработка IoT-приложений

734

Поток начинает конфигурацию как ИС W5500, так и библиотеки ioLibrary_Driver, вызывая функцию IO_LIBRARY_Init() (показано ниже). Затем конфигурируется HTTP-сервер (строка 188), и бесконечный цикл вызывает функцию httpServer_run() для каждого сокета, выделенного для HTTP-сервера.

Имя файла: src/ch25/main-ex2.c

79void IO_LIBRARY_Init(void) {

80uint8_t runApplication = 0, dhcpRetry = 0, phyLink = 0, bufSize[] = {2, 2, 2, 2, 2};

81wiz_NetInfo netInfo;

82

83reg_wizchip_cs_cbfunc(cs_sel, cs_desel);

84reg_wizchip_spi_cbfunc(spi_rb, spi_wb);

85reg_wizchip_spiburst_cbfunc(spi_rb_burst, spi_wb_burst);

86reg_wizchip_cris_cbfunc(vPortEnterCritical, vPortExitCritical);

88wizchip_init(bufSize, bufSize);

90

ReadNetCfgFromFile(&netInfo);

91

 

92/* Ожидание подключения кабеля ETH */

93do {

94ctlwizchip(CW_GET_PHYLINK, (void*) &phyLink);

95osDelay(10);

96} while(phyLink == PHY_LINK_OFF);

97

98if(netInfo.dhcp == NETINFO_DHCP) { /* Режим DHCP */

99DHCP_init(DHCP_SOCK, RX_BUF);

100

101while(!runApplication) {

102switch(DHCP_run()) {

103case DHCP_IP_LEASED:

104case DHCP_IP_ASSIGN:

105case DHCP_IP_CHANGED:

106getIPfromDHCP(netInfo.ip);

107getGWfromDHCP(netInfo.gw);

108getSNfromDHCP(netInfo.sn);

109getDNSfromDHCP(netInfo.dns);

110runApplication = 1;

111break;

112case DHCP_FAILED:

113dhcpRetry++;

114if(dhcpRetry > MAX_DHCP_RETRY)

115{

116netInfo.dhcp = NETINFO_STATIC;

117DHCP_stop(); // при перезапуске повторный вызов DHCP_init()

118#ifdef _MAIN_DEBUG_

119printf(">> DHCP %d Failed\r\n", my_dhcp_retry);

120Net_Conf();

121Display_Net_Conf(); // вывод статического netinfo на последовательный порт

122#endif

Разработка IoT-приложений

735

123dhcpRetry = 0;

124asm("BKPT #0");

125}

126break;

127default:

128break;

129}

130}

131}

132wizchip_setnetinfo(&netInfo);

133}

Функция IO_LIBRARY_Init() отвечает за правильную конфигурацию микросхемы W5500. Она начинается с конфигурация функций, используемых для обмена данными по шине SPI (строки [83:88], как показано в первом примере данной главы). Затем, в строке 90, функция ReadNetCfgFromFile() используется для получения конфигурации сети из файла, хранящегося на SD-карте. Этот файл называется net.cfg и должен иметь следующую структуру:

1NODHCP

20:11:22:33:44:55

3192.168.1.165

4255.255.255.0

5192.168.1.1

68.8.8.8

Первая строка может принимать значения (NODHCP and DHCP) и указывает, конфигурироваться ли сетевому IP статически или динамически. Вторая строка соответствует MACадресу, а следующие четыре строки соответствуют IP-адресу устройства, маске подсети, сетевому шлюзу и первичному DNS. При чтении содержимого этого файла автоматически конфигурируется сетевой интерфейс. Пользователь может изменять параметры сети через специальную веб-страницу, как показано на рисунке 8.

Рисунок 8: Веб-страница, используемая для настройки параметров сети

Разработка IoT-приложений

736

Как только сетевые настройки извлечены из файла конфигурации, функция IO_LIBRARY_Init() входит в бесконечный цикл, ожидая подключения кабеля LAN к порту RJ45 (строки [93:96]). Когда это происходит, функция запускает процедуру обнаружения DHCP, если сетевой интерфейс сконфигурирован в режиме DHCP. Наконец, в строке 132 сетевой интерфейс настраивается согласно параметрам, хранящимся в файле net.cfg, или параметрам, полученным DHCP-сервером в той же сети.

Остальная часть приложения в основном состоит из HTTP-сервера. Когда сокет устанавливает соединение с удаленным узлом, процедура httpServer_run() вызывает функцию http_process_handler(), которая отвечает за обработку входящих HTTP-запросов.

Эта функция начинает анализ HTTP-метода запроса (GET, POST, PUT и т. д.). Здесь нас интересует способ обработки метода GET.

Имя файла: Middlewares/ioLibrary_Driver/Internet/httpServer/httpServer.c

531case METHOD_GET :

532get_http_uri_name(p_http_request->URI, uri_buf);

533uri_name = uri_buf;

534

535// Если URI – «/», ответить index.html.

536if (!strcmp((char *)uri_name, "/")) strcpy((char *)uri_name, INITIAL_WEBPAGE);

537if (!strcmp((char *)uri_name, "m")) strcpy((char *)uri_name, M_INITIAL_WEBPAGE);

358if (!strcmp((char *)uri_name, "mobile")) strcpy((char *)uri_name, MOBILE_INITIAL_WEBPAGE

359// Проверка запрошенных типов файлов (включая HTML, TEXT, GIF, JPEG и др.)

540 find_http_uri_type(&p_http_request->TYPE, uri_name); 541

542#ifdef _HTTPSERVER_DEBUG_

543printf("\r\n> HTTPSocket[%d] : HTTP Method GET\r\n", s);

544printf("> HTTPSocket[%d] : Request Type = %d\r\n", s, p_http_request->TYPE);

545printf("> HTTPSocket[%d] : Request URI = %s\r\n", s, uri_name);

546#endif

547

548if(p_http_request->TYPE == PTYPE_CGI)

549{

550content_found = http_get_cgi_handler(uri_name, pHTTP_TX, &file_len);

551if(content_found && (file_len <= (DATA_BUF_SIZE-(strlen(RES_CGIHEAD_OK)+8))))

552{

553send_http_response_cgi(s, http_response, pHTTP_TX, (uint16_t)file_len);

554}

555else

556{

557send_http_response_header(s, PTYPE_CGI, 0, STATUS_NOT_FOUND);

558}

559}

560else

561{

562// Нахождение зарегистрированного пользователем индекса для веб-контента

563if(find_userReg_webContent(uri_buf, &content_num, &file_len))

564{

565content_found = 1; // Веб-контент, найденный во Flash-памяти кода

566content_addr = (uint32_t)content_num;

Разработка IoT-приложений

737

567HTTPSock_Status[get_seqnum].storage_type = CODEFLASH;

568}

569// Запрос не CGI, запрошен веб-контент на «SD-карте» или в «данных Flash-памяти»

570#if defined(_USE_SDCARD_) && !defined(OS_USE_SEMIHOSTING)

571#ifdef _HTTPSERVER_DEBUG_

572printf("\r\n> HTTPSocket[%d] : Searching the requested content\r\n", s);

573#endif

574if((fr = f_open(&HTTPSock_Status[get_seqnum].fs, (const char *)uri_name, FA_READ))==0)

575{

576content_found = 1; // файл открыт успешно

577

578file_len = f_size(&HTTPSock_Status[get_seqnum].fs);

579HTTPSock_Status[get_seqnum].file_len = file_len;

580strcpy(HTTPSock_Status[get_seqnum].file_name, uri_name);

581HTTPSock_Status[get_seqnum].storage_type = SDCARD;

582}

583#elif defined(OS_USE_SEMIHOSTING)

584// Не запрос CGI, веб-контент, полученный через ARM Semihosting

585char *base_path = OS_BASE_FS_PATH;

586char *path;

587

588 path = malloc(sizeof(char)*strlen(base_path)+strlen(uri_name));

589strcpy(path, base_path);

590strcpy(path+strlen(base_path), uri_name);

592HTTPSock_Status[get_seqnum].fs = fopen((const char *)path,"r");

593if(HTTPSock_Status[get_seqnum].fs != NULL) {

594content_found = 1; // файл открыт успешно

596fseek(HTTPSock_Status[get_seqnum].fs, 0L, SEEK_END);

597file_len = ftell(HTTPSock_Status[get_seqnum].fs);

598HTTPSock_Status[get_seqnum].file_len = file_len;

599fseek(HTTPSock_Status[get_seqnum].fs, 0L, SEEK_SET);

600strcpy(HTTPSock_Status[get_seqnum].file_name, uri_name);

601HTTPSock_Status[get_seqnum].storage_type = SDCARD;

602}

603}

Функция get_http_uri_name() в строке 532 извлекает URL-адрес, запрошенный клиентским приложением. Если этот URL-адрес равен только «/», это означает, что браузер запрашивает URL-адрес по умолчанию, который соответствует файлу index.html. Вызов функции find_http_uri_type() в строке 541 определяет Content-Type, связанный с запрошенным URL. Content-Type является производным от расширения файла. Например, Content-Type файла, заканчивающегося на .gif, устанавливается на PTYPE_GIF.

Если Content-Type – CGI28 (строка 549), то вызов функции http_get_cgi_handler() определяет создание динамического содержимого. Позже мы разберем, как устроена данная

28 Общий интерфейс шлюза (Common Gateway Interface, CGI) – это стандартизованный протокол, используемый для взаимодействия «серверных приложений», который динамически обрабатывает запросы, поступающие от клиентов. Исторически CGI были введены для динамической генерации веб-контента. В

Разработка IoT-приложений

738

функция. Для всех других зарегистрированных типов содержимого Content-Type (см.

полный список в реализации find_http_uri_type()) функция http_process_handler()

начинает поиск запрошенного ресурса (строка 561). Прежде всего, функция проверяет, было ли зарегистрировано содержимое с помощью функции reg_httpServer_webContent(). В этом случае содержимое автоматически извлекается из Flash-памяти и отправляется в браузер. Если содержимое не хранится во Flash-памяти микроконтроллера и установлен макрос _USE_SDCARD_, тогда функция проверяет, сохранено ли запрошенное содержимое на карте MicroSD (строки [575:584]). Для доступа к запрошенному файлу используется API-интерфейс FatFs. Если вместо этого включен полуохостинг ARM, то для получения файла с ПК разработчика используются стандартные процедуры Си (строки

[586:605]).

Функция http_get_cgi_handler() отвечает за создание динамического содержимого вебприложения (например, данных, отобранных периферийным устройством ADC). Функция написана так, что она запрашивает доступ к динамическим страницам /adc.cgi и /network.cgi. Давайте начнем со второго.

Имя файла: Middlewares/ioLibrary_Driver/Internet/httpServer/httpUtil.c

22extern ADC_HandleTypeDef hadc1;

23extern uint16_t adcConv[100], _adcConv[200];

24extern TIM_HandleTypeDef htim2;

25extern osSemaphoreId adcSemID;

27uint8_t http_get_cgi_handler(uint8_t * uri_name, uint8_t * buf, uint32_t * file_len)

28{

29uint8_t ret = HTTP_FAILED;

30uint16_t len = 0;

32if(strcmp((const char*)uri_name, "adc.cgi") == 0) {

33char *pbuf = (char*)buf;

35/* Вычисление текущей частоты TIM2 */

36uint32_t freq = HAL_RCC_GetPCLK2Freq() / (((htim2.Init.Prescaler + 1) *

37

(htim2.Init.Period + 1)));

38

pbuf += sprintf(pbuf, "{\"f\":%lu,\"d\":[", freq);

39

 

40/* Ожидание завершения HAL_ADC_ConvCpltCallback()

41или HAL_ADC_HalfConvCpltCallback() */

42osSemaphoreWait(adcSemID, osWaitForever);

43for(uint8_t i = 0; i < 100; i++)

44pbuf += sprintf(pbuf, "%.2f,", adcConv[i]*0.805);

45osSemaphoreRelease(adcSemID);

46

47sprintf(--pbuf, "]}");

48*file_len = strlen((char*)buf);

50return HTTP_OK;

настоящее время эта форма серверной обработки в веб-приложениях заменена множеством веб-фрейм- ворков, построенных с использованием динамических и более мощных скриптовых языков, таких как

PHP, Python и Ruby.

259 void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { 260 UNUSED(hadc);
261
262 if(osSemaphoreWait(adcSemID, 0) == osOK) {
263 memcpy(adcConv, _adcConv, sizeof(uint16_t)*100);
29 http://192.168.1.165/network.cgi

Разработка IoT-приложений

739

51

52} else if(strcmp((const char*)uri_name, "network.cgi") == 0) {

53wiz_NetInfo ni;

54wizchip_getnetinfo(&ni);

55sprintf((char*)buf, "{\"ip\":\"%d.%d.%d.%d\","

56

"\"nm\":\"%d.%d.%d.%d\","

57

"\"gw\":\"%d.%d.%d.%d\","

58

"\"dns\":\"%d.%d.%d.%d\","

59

"\"dhcp\":\"%d\"}", ni.ip[0], ni.ip[1], ni.ip[2], ni.ip[3],

60

ni.sn[0], ni.sn[1], ni.sn[2], ni.sn[3],

61

ni.gw[0], ni.gw[1], ni.gw[2], ni.gw[3],

62

ni.dns[0], ni.dns[1], ni.dns[2], ni.dns[3],

63

ni.dhcp);

64*file_len = strlen((char*)buf);

65return HTTP_OK;

66}

67

68 if(ret) *file_len = len;

Страница network.cgi выполняет простую функцию: она возвращает текущие сетевые настройки на страницу /net-work.html, которая, в свою очередь, выполняет вызов AJAX на страницу /network.cgi. Просто чтобы быть уверенным, что все читатели понимают этот вопрос, предположим, что ваша Nucleo доступна по IP-адресу 192.168.1.165, тогда переход по URL-адресу http://192.168.1.165/network.cgi29 в вашем веб-браузере даст вам следующий результат:

{"ip":"192.168.1.165","nm":"255.255.255.0","gw":"192.168.1.1","dns":"8.8.8.8","dhcp":"1"}

Он соответствует настройкам сети, возвращаемым в формате JSON. Когда браузер обращается к динамической странице /adc.cgi, приложение возвращает текущую частоту TIM2 и отобранные данные АЦП в формате JSON. Строки [32:52] отвечают за данную операцию. Функция http_get_cgi_handler() начинает вычислять частоту таймера в строке 36. Эта информация будет использоваться веб-приложением для построения данных на графике. Строки [42:45] представляют «сложную часть» функции.

Преобразование АЦП выполняется в циклическом режиме DMA. Преобразование происходит само по себе, и эта операция выполняется таймером TIM2. Если таймер работает очень быстро, доступ к массиву _adcConv[] может привести к условиям гонки: его содержимое может быть изменено, в то время как http_get_cgi_handler() преобразует его в строку в строке 44. Приложение организовано следующим образом: половина содержимого массива _adcConv[] копируется в массив adcConv[] при вызове процедур

HAL_ADC_ConvHalfCpltCallback() и HAL_ADC_ConvCpltCallback(), как показано ниже.

Имя файла: src/ch25/main-ex2.c

Разработка IoT-приложений

740

264osSemaphoreRelease(adcSemID);

265}

266}

267

268void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {

269UNUSED(hadc);

270

 

271

if(osSemaphoreWait(adcSemID, 0) == osOK) {

272

memcpy(adcConv, _adcConv+100, sizeof(uint16_t)*100);

273

osSemaphoreRelease(adcSemID);

274

}

275

}

 

 

 

Когда вызывается функция HAL_ADC_ConvHalfCpltCallback(), в массиве _adcConv[] хра-

 

нится сто значений: поэтому мы копируем первую половину в массив adcConv[], размер

 

которого равен 100. Когда вызывается другой обратный вызов, мы копируем вторую по-

 

ловину. Перед тем, как две процедуры обратного вызова скопируют содержимое мас-

 

сива _adcConv[], они пытаются получить семафор adcSem. Если он доступен, они выпол-

 

няют копирование, в противном случае это означает, что http_get_cgi_handler() уже по-

 

лучил его и выполняется преобразование массива adcConv[]. Это решение предотвра-

 

щает создание условий гонки, несмотря на то что оно не самое быстрое.

 

Функция http_get_cgi_handler() обрабатывает все GET-запросы к сценариям CGI. Анало-

 

гичным образом функция http_post_cgi_handler() обрабатывает все POST-запросы к

 

сценариям CGI.

 

Имя файла: Middlewares/ioLibrary_Driver/Internet/httpServer/httpUtil.c

 

 

72

uint8_t http_post_cgi_handler(uint8_t * uri_name, st_http_request * p_http_request, \

73

uint8_t *buf, uint32_t * file_len)

74{

75uint8_t ret = HTTP_OK;

76uint16_t len = 0;

77uint8_t *param = p_http_request->URI;

79if(strcmp((const char *)uri_name, "sf.cgi") == 0) {

80param = get_http_param_value((char*)p_http_request->URI, "f");

81if(param != p_http_request->URI) {

82/* Пользователь хочет изменить частоту дискретизации АЦП. Останавливаем преобразование */

83HAL_ADC_Stop_DMA(&hadc1);

84HAL_TIM_Base_Stop(&htim2);

86/* Получение текущей частоты TIM2 */

87uint32_t cfreq = HAL_RCC_GetPCLK2Freq() / (((htim2.Init.Prescaler + 1) *

88

(htim2.Init.Period + 1))), nfreq = 0;

89

 

90if(*param == '1')

91nfreq = cfreq * 2;

92else

93nfreq = cfreq / 2;

95htim2.Init.Prescaler = 0;

Разработка IoT-приложений

741

96htim2.Init.Period = 1;

97/* Повторяем цикл, пока не достигнем желаемой частоты. На частотах ниже 30 Гц

98этот алгоритм в значительной степени неэффективен */

99while(1) {

100cfreq = HAL_RCC_GetPCLK2Freq() / (((htim2.Init.Prescaler + 1) *

101

(htim2.Init.Period + 1)));

102if (nfreq < cfreq) {

103if(++htim2.Init.Period == 0) {

104

htim2.Init.Prescaler++;

105

htim2.Init.Period++;

106}

107} else {

108

break;

109}

110}

111HAL_TIM_Base_Init(&htim2);

112HAL_TIM_Base_Start(&htim2);

113HAL_ADC_Start_DMA(&hadc1, (uint32_t*)_adcConv, 200);

115sprintf((char*)buf, "OK");

116len = strlen((char*)buf);

117

}

118

 

119}

120else if(strcmp((const char *)uri_name, "network.cgi") == 0) {

121wiz_NetInfo netInfo;

122wizchip_getnetinfo(&netInfo);

124param = get_http_param_value((char*)p_http_request->URI, "dhcp");

125if(param != 0) {

126netInfo.dhcp = NETINFO_DHCP;

127} else {

128netInfo.dhcp = NETINFO_STATIC;

130param = get_http_param_value((char*)p_http_request->URI, "ip");

131if(param != 0)

132inet_addr_((u_char*)param, netInfo.ip);

133else

134return HTTP_FAILED;

136param = get_http_param_value((char*)p_http_request->URI, "sn");

137if(param != 0)

138inet_addr_((u_char*)param, netInfo.sn);

139else

140return HTTP_FAILED;

142param = get_http_param_value((char*)p_http_request->URI, "gw");

143if(param != 0)

144inet_addr_((u_char*)param, netInfo.gw);

145 else

Разработка IoT-приложений

742

146 return HTTP_FAILED;

147

148param = get_http_param_value((char*)p_http_request->URI, "dns");

149if(param != 0)

150inet_addr_((u_char*)param, netInfo.dns);

151else

152return HTTP_FAILED;

153}

154if(!WriteNetCfgInFile(&netInfo))

155sprintf((char*)buf, "FAILED");

156else

157sprintf((char*)buf, "OK");

158

159/* Изменение параметров сети */

160wizchip_setnetinfo(&netInfo);

161len = strlen((char*)buf);

162}

163

164 if(ret) *file_len = len;

Эта функция предназначена для обслуживания двух динамических страниц: /sf.cgi и /network.cgi. Вторая обрабатывает HTML-форму, когда пользователь изменяет сетевые настройки (посмотрите файл network.html). А страница /sf.cgi обрабатывает изменение частоты TIM2. Когда пользователь нажимает на значки «увеличить/уменьшить масштаб», браузер выполняет запрос к странице /sf.cgi, передавая значение 1 для увеличения частоты и 0 для ее уменьшения.

Остальная часть нашего примера приложения посвящена HTML, CSS и JavaScript. Файлы index.html и network.html содержат весь необходимый код для построения графика при помощи библиотеки D3js, а также для отображения и изменения сетевых настроек.

Чтобы использовать данный пример, вы можете просто скопировать содержимое подкаталога src/ch25/webpages на SD-карту. В качестве альтернативы вы можете использовать полухостинг ARM после того, как был установлен макрос OS_BASE_FS_PATH с полным путем к src/ch25/webpages в файловой системе вашего ПК. Например, если вы работаете

в Windows, для OS_BASE_FS_PATH можно указать путь C:/STM32Toolchain/projects/nucleof401RE/src/ch25/webpages.