NUC980与ESP32的SPI-WiFi联调实战:从驱动编译到网络连通
1. 环境准备与硬件连接
在开始NUC980与ESP32的SPI-WiFi联调之前,我们需要先准备好开发环境和硬件连接。我用的NUC980开发板是新唐官方的NUC980DK61Y,ESP32模块是常见的ESP32-WROOM-32。硬件连接上,ESP32作为SPI从设备,通过SPI总线与NUC980相连。具体连接方式如下:
- SPI_CLK:连接NUC980的SPI1_CLK(PB2)
- SPI_MOSI:连接NUC980的SPI1_MOSI(PB1)
- SPI_MISO:连接NUC980的SPI1_MISO(PB0)
- SPI_CS:连接NUC980的SPI1_SS0(PB3)
- ESP32的中断引脚:连接NUC980的EINT0(PA0)
- ESP32的Ready引脚:连接NUC980的EINT1(PA1)
这里有个小细节需要注意,NUC980的SPI控制器有多个片选信号(SS0-SS3),我使用的是SS0。如果你的硬件设计使用了其他片选信号,后续的驱动配置也需要相应调整。
开发环境方面,需要准备:
- NUC980的交叉编译工具链(arm-nuvoton-linux-uclibcgnueabi-)
- Linux内核源码(我用的是4.4.115版本)
- ESP32的工具链(xtensa-esp32-elf-)
- ESP-hosted代码包(我用的esp-hosted-ng版本)
2. ESP32固件编译与烧录
ESP32作为SPI WiFi模块,需要先烧录专门的固件。Espressif官方提供了esp-hosted项目,里面包含了ESP32作为网络适配器的固件代码。
首先从GitHub克隆esp-hosted仓库:
git clone https://github.com/espressif/esp-hosted.git cd esp-hosted进入esp-hosted-ng/esp/esp_driver/network_adapter目录,这里需要修改sdkconfig文件。由于我的开发环境make menuconfig无法正常显示,我直接编辑了sdkconfig文件:
CONFIG_ESP_HOSTED_SPI_SUPPORT=y CONFIG_ESP_HOSTED_SDIO_SUPPORT=n这个配置关闭了SDIO支持,启用了SPI模式。修改完成后,执行编译命令:
make -j4 all编译完成后,在build目录下会生成esp_hosted_spi.bin固件文件。使用esptool.py工具将固件烧录到ESP32:
esptool.py -p /dev/ttyUSB0 -b 460800 write_flash 0x1000 esp_hosted_spi.bin烧录完成后,ESP32就准备好了作为SPI WiFi模块工作。这里有个小技巧,如果遇到烧录失败,可以尝试降低波特率或者检查USB转串口线的稳定性。
3. NUC980驱动编译与移植
接下来是NUC980端的驱动编译工作。进入esp-hosted-ng/host目录,首先需要修改Makefile:
CROSS_COMPILE=arm-nuvoton-linux-uclibcgnueabi- KERNEL_DIR=/path/to/nuc980-linux-4.4.115由于NUC980使用的是Linux 4.4内核,与最新驱动可能存在兼容性问题。我在编译过程中遇到了几个问题:
- 内核API变化:4.4内核的net_device_ops结构体与新版不同,需要调整注册方式
- 函数参数变化:skb_alloc和free函数参数列表有差异
- 头文件路径变化:部分内核头文件位置发生了变化
针对这些问题,我做了如下修改:
// 修改net_device_ops初始化方式 static const struct net_device_ops esp_netdev_ops = { .ndo_open = esp_open, .ndo_stop = esp_close, .ndo_start_xmit = esp_hard_start_xmit, // 4.4内核不需要.ndo_features }; // 调整skb分配方式 skb = dev_alloc_skb(len); if (!skb) { return -ENOMEM; }内核配置方面,需要确保以下选项已启用:
- CONFIG_SPI=y
- CONFIG_SPI_NUC980=y
- CONFIG_CFG80211=y
- CONFIG_WIRELESS=y
- CONFIG_WEXT_CORE=y
可以通过make menuconfig命令检查这些配置,或者直接修改内核的.config文件。
4. SPI总线与中断配置
驱动编译通过后,接下来是关键的SPI总线和中断配置。这部分最容易出问题,我在这里踩了不少坑。
首先修改esp_spi.c文件中的总线配置:
struct esp_board_config esp_board = { .bus_num = 1, // SPI1总线 .chip_select = 0, // 使用SS0片选 };如果你的硬件使用了不同的SPI总线或片选信号,需要相应调整这些参数。NUC980的SPI控制器编号从0开始,SPI1对应编号1。
中断配置更为关键,需要修改esp_spi.h文件:
#define ESP_SPI_INT_PIN 0 // EINT0 #define ESP_SPI_READY_PIN 1 // EINT1这里定义的中断引脚号需要与硬件连接一致。我遇到了一个典型问题:中断只触发一次就不再响应。经过排查发现是中断标志位没有清除导致的。
解决方法是在中断处理函数中添加清除标志位的代码:
static irqreturn_t esp_interrupt_handler(int irq, void *dev_id) { // 清除中断标志位 writel(readl(REG_IRQ_ENABLE) | (1 << ESP_SPI_INT_PIN), REG_IRQ_ENABLE); writel(readl(REG_IRQ_ENABLE) | (1 << ESP_SPI_READY_PIN), REG_IRQ_ENABLE); // 正常的中断处理逻辑 ... }REG_IRQ_ENABLE是NUC980的中断使能寄存器地址,具体值需要参考NUC980的技术手册。这个细节在官方文档中不太容易找到,我也是通过反复试验才解决的。
5. 驱动加载与网络配置
当所有修改完成后,就可以加载驱动并测试网络连接了。将编译好的esp_spi.ko驱动文件拷贝到NUC980的文件系统中,然后加载驱动:
insmod esp_spi.ko如果一切正常,dmesg应该能看到类似下面的输出:
[ 123.456789] esp_spi: loading out-of-tree module taints kernel. [ 123.567890] ESP SPI peripheral initialized [ 123.678901] ESP Hosted SPI driver registered接下来配置网络连接。首先确保wpa_supplicant工具已经包含在文件系统中。编辑/etc/wpa_supplicant.conf文件:
network={ ssid="your_wifi_ssid" psk="your_wifi_password" }然后启动wpa_supplicant:
wpa_supplicant -B -i espsta0 -c /etc/wpa_supplicant.conf最后获取IP地址:
udhcpc -i espsta0测试网络连接:
ping www.baidu.com -I espsta0如果ping通,说明整个SPI-WiFi链路已经正常工作。我在实际测试中,发现有时候需要复位ESP32模块才能建立稳定连接,可以在驱动加载后通过GPIO控制ESP32的复位引脚来实现自动复位。
6. 常见问题排查
在实际项目中,可能会遇到各种问题。以下是我总结的几个常见问题及解决方法:
驱动加载失败:
- 检查内核版本是否匹配
- 确认SPI总线配置正确
- 查看dmesg输出中的错误信息
中断不工作:
- 确认中断引脚配置正确
- 检查中断标志位是否清除
- 测试GPIO是否能正常检测电平变化
网络连接不稳定:
- 检查SPI时钟速率是否过高(建议初始使用10MHz测试)
- 确认电源供应稳定
- 检查天线连接是否良好
传输速度慢:
- 尝试提高SPI时钟速率
- 检查DMA配置是否启用
- 优化驱动中的缓冲区管理
一个实用的调试技巧是在驱动中添加详细的打印信息,特别是在关键函数和中断处理中。这样当出现问题时,可以通过日志快速定位问题所在。
7. 性能优化建议
当基本功能实现后,可以考虑进行一些性能优化:
SPI时钟优化: NUC980的SPI控制器最高支持50MHz时钟,但实际稳定运行频率取决于PCB布局和线长。建议从10MHz开始逐步提高,测试稳定性。
DMA传输: 启用SPI DMA可以显著提高吞吐量。需要在驱动中实现DMA缓冲区管理,并正确配置DMA通道。
中断合并: 对于高频小数据包传输,可以考虑实现中断合并机制,减少中断处理开销。
电源管理: 如果设备需要低功耗运行,可以优化ESP32的电源状态切换,在不使用时进入低功耗模式。
在我的测试中,经过优化后,SPI-WiFi的TCP吞吐量可以达到5-6Mbps,足够大多数嵌入式应用场景使用。
