本文目录
系列目录
- STM32裸机编程指南-1,存储和寄存器相关知识
- STM32裸机编程指南-2,更易读的外设寄存器编程
- STM32裸机编程指南-3,启动代码和向量表
- STM32裸机编程指南-4,Makefile构建自动化
- STM32裸机编程指南-5,闪烁LED
- STM32裸机编程指南-6,用SysTick中断实现闪烁
- STM32裸机编程指南-7,添加串口调试输出
- STM32裸机编程指南-8,重定向
printf()
到串口 - STM32裸机编程指南-9,用Segger Ozone进行调试
- STM32裸机编程指南-10,供应商CMSIS头文件
- STM32裸机编程指南-11,配置时钟
- STM32裸机编程指南-12,带设备仪表盘的网络服务器
带设备仪表盘的网络服务器
Nucleo-F429ZI 带有板载以太网。以太网硬件需要两个组件:PHY(向铜缆、光缆等介质发送和接收电信号)和 MAC(驱动 PHY 控制器)。
在我们的Nucleo开发板上,MAC控制器是MCU内置的,PHY是外部的(具体来说,是Microchip的LAN8720a)。
MAC和PHY可以用多个接口通信,我们将使用RMII。为此,一些引脚必须配置为使用其替代功能 (AF)。要实现 Web 服务器,我们需要 3 个软件组件:
- 网络驱动程序,用于向 MAC 控制器发送/接收以太网帧
- 一个网络堆栈,用于解析帧并理解 TCP/IP
- 理解HTTP的网络库
我们将使用猫鼬网络库,它在单个文件中实现所有这些。这是一个双重许可的库(GPLv2/商业),旨在使网络嵌入式开发快速简便。
先拷贝 mongoose.c 和 mongoose.h 到我们的工程中,现在我们手上有网络驱动、网络协议栈和HTTP库了,Mongoose还提供了很多示例,其中之一是设备仪表盘示例。这个示例实现了很多事情,像登录、通过WebSocket实时传输数据、嵌入式文件系统、MQTT通信等等,我们就使用这个例子,再拷贝2个文件:
- net.c,实现了仪表盘功能
- packed_fs.c,包含了HTML/CSS/JS GUI文件
我们需要告诉 Mongoose 开启哪些功能,可以通过设置预处理常数等编译器标记实现,也可以在 mongoose_custom.h
文件中设置。我们用第二种方法,创建 mongoose_custom.h
文件并写入以下内容:
#pragma once
#define MG_ARCH MG_ARCH_NEWLIB
#define MG_ENABLE_MIP 1
#define MG_ENABLE_PACKED_FS 1
#define MG_IO_SIZE 512
#define MG_ENABLE_CUSTOM_MILLIS 1
现在向 main.c
添加一些网络代码,#include "mongoose.c"
初始化以太网RMII引脚,并在RCC中使能以太网:
uint16_t pins[] = {PIN('A', 1), PIN('A', 2), PIN('A', 7),
PIN('B', 13), PIN('C', 1), PIN('C', 4),
PIN('C', 5), PIN('G', 11), PIN('G', 13)};
for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) {
gpio_init(pins[i], GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_INSANE,
GPIO_PULL_NONE, 11);
}
nvic_enable_irq(61); // Setup Ethernet IRQ handler
RCC->APB2ENR |= BIT(14); // Enable SYSCFG
SYSCFG->PMC |= BIT(23); // Use RMII. Goes first!
RCC->AHB1ENR |= BIT(25) | BIT(26) | BIT(27); // Enable Ethernet clocks
RCC->AHB1RSTR |= BIT(25); // ETHMAC force reset
RCC->AHB1RSTR &= ~BIT(25); // ETHMAC release reset
Mongoose的驱动程序使用以太网中断,因此我们需要更新 startup.c
并将 ETH_IRQHandler
添加到向量表中。让我们以不需要任何修改就能添加中断处理函数的方式重新组织 startup.c
中的向量表定义,方法是使用“弱符号”概念。
函数可以标记为“弱”,它的工作方式与普通函数类似。当源代码定义具有相同名称的函数时,差异就来了。通常,两个同名的函数会构建失败。但是,如果一个函数被标记为弱函数,则可以构建成功并且链接器会选择非弱函数。这提供了设置样板中的函数为“默认函数”的能力,然后可以在代码中的其他位置简单地创建一个同名函数来覆盖它。
我们接下来用这种方法填充向量表,创建一个 DefaultIRQHandler()
并标记为weak,然后给每一个中断处理函数声明一个处理函数名并使它成为 DefaultIRQHandler()
的别名:
void __attribute__((weak)) DefaultIRQHandler(void) {
for (;;) (void) 0;
}
#define WEAK_ALIAS __attribute__((weak, alias("DefaultIRQHandler")))
WEAK_ALIAS void NMI_Handler(void);
WEAK_ALIAS void HardFault_Handler(void);
WEAK_ALIAS void MemManage_Handler(void);
...
__attribute__((section(".vectors"))) void (*tab[16 + 91])(void) = {
0, _reset, NMI_Handler, HardFault_Handler, MemManage_Handler,
...
现在,我们可以在代码中定义任何中断处理函数,它会替代默认的那个。这就是我们的例子中所发生的:Mongoose的STM32驱动中定义了一个 ETH_IRQHandler()
,它会替代默认的中断处理函数。
下一步是初始化Mongoose库:创建时间管理器、配置网络驱动、启动监听HTTP连接:
struct mg_mgr mgr; // Initialise Mongoose event manager
mg_mgr_init(&mgr); // and attach it to the MIP interface
mg_log_set(MG_LL_DEBUG); // Set log level
struct mip_driver_stm32 driver_data = {.mdc_cr = 4}; // See driver_stm32.h
struct mip_if mif = {
.mac = {2, 0, 1, 2, 3, 5},
.use_dhcp = true,
.driver = &mip_driver_stm32,
.driver_data = &driver_data,
};
mip_init(&mgr, &mif);
extern void device_dashboard_fn(struct mg_connection *, int, void *, void *);
mg_http_listen(&mgr, "http://0.0.0.0", device_dashboard_fn, &mgr);
MG_INFO(("Init done, starting main loop"));
剩下的就是把 mg_mgr_poll()
调用加到主循环。
现在把 mongoose.c
、net.c
和 packed_fs.c
文件加到Makefile,重新构建,烧写到板子上。连接一个串口控制台到调试输出,可以观察到板子通过DHCP获取了IP地址:
847 3 mongoose.c:6784:arp_cache_add ARP cache: added 0xc0a80001 @ 90:5c:44:55:19:8b
84e 2 mongoose.c:6817:onstatechange READY, IP: 192.168.0.24
854 2 mongoose.c:6818:onstatechange GW: 192.168.0.1
859 2 mongoose.c:6819:onstatechange Lease: 86363 sec
LED: 1, tick: 2262
LED: 0, tick: 2512
打开一个浏览器,输入上面的IP地址,就可以看到一个仪表盘。在full description获取更多细节。
完整工程源码可以 step-7-webserver 文件夹找到。
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。