1. 环境准备与硬件连接在开始NUC980与ESP32的SPI-WiFi联调之前我们需要先准备好开发环境和硬件连接。我用的NUC980开发板是新唐官方的NUC980DK61YESP32模块是常见的ESP32-WROOM-32。硬件连接上ESP32作为SPI从设备通过SPI总线与NUC980相连。具体连接方式如下SPI_CLK连接NUC980的SPI1_CLKPB2SPI_MOSI连接NUC980的SPI1_MOSIPB1SPI_MISO连接NUC980的SPI1_MISOPB0SPI_CS连接NUC980的SPI1_SS0PB3ESP32的中断引脚连接NUC980的EINT0PA0ESP32的Ready引脚连接NUC980的EINT1PA1这里有个小细节需要注意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_SUPPORTy CONFIG_ESP_HOSTED_SDIO_SUPPORTn这个配置关闭了SDIO支持启用了SPI模式。修改完成后执行编译命令make -j4 all编译完成后在build目录下会生成esp_hosted_spi.bin固件文件。使用esptool.py工具将固件烧录到ESP32esptool.py -p /dev/ttyUSB0 -b 460800 write_flash 0x1000 esp_hosted_spi.bin烧录完成后ESP32就准备好了作为SPI WiFi模块工作。这里有个小技巧如果遇到烧录失败可以尝试降低波特率或者检查USB转串口线的稳定性。3. NUC980驱动编译与移植接下来是NUC980端的驱动编译工作。进入esp-hosted-ng/host目录首先需要修改MakefileCROSS_COMPILEarm-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_SPIyCONFIG_SPI_NUC980yCONFIG_CFG80211yCONFIG_WIRELESSyCONFIG_WEXT_COREy可以通过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{ ssidyour_wifi_ssid pskyour_wifi_password }然后启动wpa_supplicantwpa_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足够大多数嵌入式应用场景使用。
NUC980与ESP32的SPI-WiFi联调实战:从驱动编译到网络连通
发布时间:2026/6/25 12:56:52
1. 环境准备与硬件连接在开始NUC980与ESP32的SPI-WiFi联调之前我们需要先准备好开发环境和硬件连接。我用的NUC980开发板是新唐官方的NUC980DK61YESP32模块是常见的ESP32-WROOM-32。硬件连接上ESP32作为SPI从设备通过SPI总线与NUC980相连。具体连接方式如下SPI_CLK连接NUC980的SPI1_CLKPB2SPI_MOSI连接NUC980的SPI1_MOSIPB1SPI_MISO连接NUC980的SPI1_MISOPB0SPI_CS连接NUC980的SPI1_SS0PB3ESP32的中断引脚连接NUC980的EINT0PA0ESP32的Ready引脚连接NUC980的EINT1PA1这里有个小细节需要注意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_SUPPORTy CONFIG_ESP_HOSTED_SDIO_SUPPORTn这个配置关闭了SDIO支持启用了SPI模式。修改完成后执行编译命令make -j4 all编译完成后在build目录下会生成esp_hosted_spi.bin固件文件。使用esptool.py工具将固件烧录到ESP32esptool.py -p /dev/ttyUSB0 -b 460800 write_flash 0x1000 esp_hosted_spi.bin烧录完成后ESP32就准备好了作为SPI WiFi模块工作。这里有个小技巧如果遇到烧录失败可以尝试降低波特率或者检查USB转串口线的稳定性。3. NUC980驱动编译与移植接下来是NUC980端的驱动编译工作。进入esp-hosted-ng/host目录首先需要修改MakefileCROSS_COMPILEarm-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_SPIyCONFIG_SPI_NUC980yCONFIG_CFG80211yCONFIG_WIRELESSyCONFIG_WEXT_COREy可以通过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{ ssidyour_wifi_ssid pskyour_wifi_password }然后启动wpa_supplicantwpa_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足够大多数嵌入式应用场景使用。