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.cmongoose.h 到我们的工程中,现在我们手上有网络驱动、网络协议栈和HTTP库了,Mongoose还提供了很多示例,其中之一是设备仪表盘示例。这个示例实现了很多事情,像登录、通过WebSocket实时传输数据、嵌入式文件系统、MQTT通信等等,我们就使用这个例子,再拷贝2个文件:

我们需要告诉 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.cnet.cpacked_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获取更多细节。

Device dashboard

完整工程源码可以 step-7-webserver 文件夹找到。

公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值