资讯详情

资讯详情

ESP32 SPI驱动W5500实现有线以太网:时序详解与寄存器读写实战

做嵌入式这么久我一直觉得 SPI 是个“看着简单、翻车率奇高”的协议。尤其是第一次用 ESP32 驱动 W5500 做有线以太网接线查了八遍、代码能编译、上电后死活不通这种坑我至少踩过三四回。后来把 SPI 时序、W5500 寄存器映射、收发缓冲区指针逻辑彻底搞清楚之后再回头看这套方案其实就一个字稳。ESP32 做主控W5500 把 TCP/IP 协议栈整个吃掉MCU 只需要通过 SPI 读写几个寄存器和缓冲区就能跑通以太网通信特别适合需要稳定有线连接、又不想被协议栈拖垮的物联网设备。这篇文章我就把 ESP32 SPI 和 W5500 的配合从头到尾拆开例程逐行解释最后给你一份可以直接抄的作业。1. 为什么是 ESP32 W5500 这个组合1.1 W5500 到底帮你干了什么W5500 是 WIZnet 家的硬协议栈芯片把 TCP、UDP、ICMP、ARP、MAC、PHY 全部集成到一个封装里还带了 8 个独立的 socket 和 32KB 收发缓存。它的工作方式很粗暴有效主控 MCU 不用跑 lwIP、不用管三次握手、不用处理分片重传只需要通过 SPI 接口往寄存器里写目标 IP、端口号然后往发送缓冲区里丢要发出去的数据芯片自己就把以太网帧组装好、发出去收到数据时芯片会把负载写进接收缓冲区再拉低 INT 脚通知你。用生活里的话说这就好比你想寄快递不用自己造纸箱、不用学货车路线、不用跟快递员讨价还价只需要把东西放到驿站窗口填好单子剩下的全由驿站搞定。W5500 就是那个驿站而 SPI 就是你手里递东西的那扇窗口。这个设计最大的好处是确定性。硬协议栈不吃 CPU 资源不依赖操作系统的调度中断延迟再高也不会导致 TCP 重传超时。对于做工业设备、数据采集器、智能网关的人来说这套组合远比在 MCU 上裸跑软协议栈省心。1.2 有 Wi-Fi 的 ESP32 为什么还要接网线很多人第一反应是ESP32 自带 Wi-Fi 和蓝牙干嘛还要外挂一颗以太网芯片我自己也做过几个无线产品但遇到下面这些场景时Wi-Fi 是真的撑不住。工业现场环境里金属机柜、电机变频器、水泥墙体都会对 Wi-Fi 信号造成干扰设备装在配电柜里信号衰减到 ping 不通是常事。此时一根网线拉过去什么问题都解决了。再有就是需要固定 IP、远程登录调试的设备Wi-Fi 的 DHCP 租约、漫游切换、信道抖动都会让远程会话断掉而有线网络只要物理链路不断IP 基本不会变。还有一类是数据量大的场景比如持续上传传感器波形、视频流Wi-Fi 的延迟抖动和丢包重传会让上层应用非常难受有线以太网哪怕只有 10Mbps 也会稳定得多。另外ESP32 部分型号本身没有以太网 MAC就算有也需要外接 PHY 芯片比如 LAN8720还得自己把 MAC 层的活全干了。而 W5500 连 PHY 带 MAC 带协议栈全包ESP32 只需要一棵树一样站在旁边发号施令开发成本和调试难度都低一大截。1.3 为什么例程要自己写而不是直接抄库WIZnet 官方有一套 ioLibrary_Driver网上也能找到不少封装好的 Arduino 库但说实话对新手很不友好。官方库为了兼容 W5100、W5200、W5500做了大量条件编译和抽象层开场一个函数套一个函数你根本不知道哪一层在干什么出了问题也没法定位。我写这套例程的思路很简单只针对 W5500只针对 ESP-IDF把 SPI 初始化和几个最核心的寄存器读写函数写透不搞花活。原理通了后面不管换 W5100 还是换别的 MCU你都能快速迁移。2. 动手之前先把 SPI 这层窗户纸捅破2.1 ESP32 上的几条 SPI 总线都能用吗经典 ESP32 芯片内部有三条 SPI 控制器SPI0、SPI1、SPI2、SPI3 一共四个控制器其中 SPI0 和 SPI1 被 Flash 和 PSRAM 占死了用户程序千万不要碰。能正常给外设用的就是 SPI2 和 SPI3。在 ESP-IDF 里它们对应的 host 枚举是 SPI2_HOST 和 SPI3_HOST在 Arduino 环境里你常听到的 HSPI 就是 SPI2VSPI 就是 SPI3。很多开发板引出的 VSPI 默认引脚是SCKGPIO18、MISOGPIO19、MOSIGPIO23、CSGPIO5。这个组合在 Arduino 里直接能用但到了 ESP-IDF 里其实可以通过 GPIO 矩阵映射到任意 GPIO只要避开输入专用引脚就行。我下面例程里虽然沿用了这几个默认脚但代码里用的是宏定义你想换脚直接改宏。2.2 四种模式与 W5500 怎么选SPI 有四个模式由 CPOL时钟极性和 CPHA时钟相位组合决定。CPOL 决定空闲时 SCK 是高还是低CPHA 决定数据在时钟的哪个边沿被采样。模式CPOLCPHA特点Mode 000空闲低电平上升沿采样Mode 101空闲低电平下降沿采样Mode 210空闲高电平下降沿采样Mode 311空闲高电平上升沿采样W5500 数据手册明确写了支持 Mode 0 和 Mode 3。我例程里用 Mode 0原因很简单大部分 MCU 的 SPI 外设默认就是 Mode 0不容易出错。如果你换到 Mode 3 也没问题关键是要和主机保持一致。判断模式对不对最直观的办法是看逻辑分析仪或示波器。SCK 空闲是低电平的基本就是 Mode 0 或 Mode 1然后看数据位是不是在 SCK 上升沿时被采样如果数据在上升沿时保持稳定那就是 Mode 0。2.3 W5500 的 SPI 帧格式W5500 的 SPI 协议不是简单发一个寄存器地址就完事它每帧分三段16 位地址先发高 8 位再发低 8 位8 位控制字节其中 bit4 决定是块访问还是可变地址访问bit2~bit0 决定是读还是写数据阶段读操作时主机发 dummy 字节接收数据写操作时主机发实际数据。控制字节最低三位0x00 表示读0x04 表示写这个必须记牢。bit4 置 1 走块模式表示后续数据都访问同一个地址适合反复读写某个寄存器的状态bit4 清零走可变数据模式地址自动递增适合连续读一批寄存器。打个比方地址就是门牌号控制字节就是“我要进屋搬东西还是放东西”的指令数据阶段才是真正搬运的内容。搞懂这个帧结构后面所有代码都是围绕它转的。3. 硬件接线3.1 引脚对照表直接抄下面这套接线我实测过很多次用的是最常见的 W5500 模块带 RJ45 网口那种。ESP32 开发板的 3.3V 必须够电流W5500 模块工作时电流比想象中要大如果供电不足会出现能初始化但网线插上后 ping 不通的诡异问题。ESP32 引脚W5500 模块说明3V3VCC / 3.3V供电电流最好 300mA 以上GNDGND共地GPIO18SCLKSPI 时钟GPIO23MOSI主机输出接 W5500 的 MOSIGPIO19MISO主机输入接 W5500 的 MISOGPIO5SCS片选低电平有效GPIO4RST硬件复位低电平复位GPIO15INT中断输出低电平表示有事件注意W5500 的供电范围是 3.0V 到 3.6V逻辑电平也是 3.3V千万别图省事直接接 5V。如果用了 5V 供电的开发板必须先做电平转换再接 W5500否则芯片很容易烧。3.2 为什么 CS 要用手动 GPIO 而不是自动片选ESP32 的 SPI 外设其实自带硬件 CS把 spics_io_num 配成某个引脚就能自动拉。但我强烈建议你把它设成 -1然后自己找一根 GPIO 手动控制。原因有两个一是手动控制时你完全清楚片选什么时候拉低、什么时候拉高调试协议时心里有数二是 W5500 的一帧分成“发地址头”和“读写数据”两段中间不能让 CS 跳变手动控制能把这两段稳稳包在一个低电平区间里。如果你非要用硬件 CS也可以但得小心配置 CS 的延时参数否则卡在临界时序上时很难查。新手阶段手动 CS 是最稳妥的。3.3 电源和复位电路的小讲究W5500 模块上一般都有电平转换和晶振但很多廉价模块并没有在 VCC 引脚附近放足够的去耦电容。我的习惯是在模块的 VCC 和 GND 之间焊一个 10uF 和 0.1uF 的电容能有效减少 SPI 高速翻转时的电源纹波。复位脚 RST 不要直接接死到高电平最好由 ESP32 的 GPIO 控制。上电后软件里先把 RST 拉低 100ms再拉高再等 150ms 让芯片完成内部初始化比在代码里反复软复位要可靠得多。INT 脚也是一样别悬空接上拉电阻到 3.3V然后接到 ESP32 的一个带中断功能的 GPIO 上。4. 例程逐行拆解SPI 初始化与寄存器读写4.1 SPI 外设初始化下面代码基于 ESP-IDFArduino 用户可以把 API 对应替换成SPI.begin()但底层思路完全一致。#include driver/spi_master.h #include driver/gpio.h #define PIN_MOSI 23 #define PIN_MISO 19 #define PIN_SCLK 18 #define PIN_CS 5 #define PIN_RST 4 #define PIN_INT 15 spi_device_handle_t spi; void spi_init(void) { spi_bus_config_t buscfg { .mosi_io_num PIN_MOSI, .miso_io_num PIN_MISO, .sclk_io_num PIN_SCLK, .quadwp_io_num -1, .quadhd_io_num -1, .max_transfer_sz 4096, }; spi_bus_initialize(SPI2_HOST, buscfg, SPI_DMA_CH_AUTO); spi_device_interface_config_t devcfg { .clock_speed_hz 4 * 1000 * 1000, .mode 0, .spics_io_num -1, .queue_size 4, }; spi_bus_add_device(SPI2_HOST, devcfg, spi); }.clock_speed_hz 4MHz这个值我调过很多回短杜邦线条件下稳定。W5500 最高能跑到 80MHz 左右但实际频率要看你接线质量。如果你用的是飞线、面包板频率超过 10MHz 很容易出现读回数据错位。后面调稳了再慢慢提频。.spics_io_num -1表示不用外设自动 CSCS 脚由我们手动拉。.queue_size 4是 SPI 事务队列深度给 4 就够了因为我们大多数时候是同步传输不会同时塞很多事务。4.2 两个最底层的读写原语这俩函数是整个驱动的基石W5500 所有寄存器操作、缓冲区读写最终都落到这两个函数上。static void w5500_read_burst(uint16_t addr, uint8_t *buf, uint16_t len) { uint8_t header[3] { addr 8, // 地址高 8 位 addr 0xFF, // 地址低 8 位 0x00 // 控制字节0x00 表示读 }; spi_transaction_t t { .length (3 len) * 8, .tx_buffer header, .rx_buffer buf, // 注意 rx 会包含头部 3 字节要跳过 }; gpio_set_level(PIN_CS, 0); spi_device_transmit(spi, t); gpio_set_level(PIN_CS, 1); }这里有个细节要提醒rx_buffer的前 3 个字节是读操作时 SPI 主从双方在同步发送头部时收到的垃圾数据真正有用的数据从第 3 个字节之后才开始。所以你调用这个函数时传入的buf前面最好留 3 个字节的偏移或者读完后再 memcpy 一次否则数据会对不上位置。我写例程时通常会在调用层再封装一层读寄存器时分配一个临时数组把读到的数据挪出来。下面这个函数就是读单个寄存器的标准写法static uint8_t w5505_read_reg(uint16_t addr) { uint8_t buf[4]; uint8_t header[3] { 0 }; spi_transaction_t t { .length 4 * 8, .tx_buffer header, .rx_buffer buf, }; // 这里直接用读 burst 逻辑 gpio_set_level(PIN_CS, 0); uint8_t tx[4] { addr 8, addr 0xFF, 0x00, 0x00 }; t.tx_buffer tx; t.length 4 * 8; t.rx_buffer buf; spi_device_transmit(spi, t); gpio_set_level(PIN_CS, 1); return buf[3]; }读操作里主机发第 4 个字节 0x00 是因为 SPI 是全双工从机要输出数据主机必须同时给时钟发一个 dummy 字节就是为了提供时钟。这个 dummy 是 SPI 协议的老规矩别想着省掉。写操作更简单数据阶段直接发要写的值static void w5500_write_reg(uint16_t addr, uint8_t val) { uint8_t tx[4] { addr 8, addr 0xFF, 0x04, val }; spi_transaction_t t { .length 4 * 8, .tx_buffer tx, }; gpio_set_level(PIN_CS, 0); spi_device_transmit(spi, t); gpio_set_level(PIN_CS, 1); }控制字节 0x04 就是写操作。这里你会发现写寄存器比读寄存器干净不需要管接收缓冲。4.3 批量读写与环形缓冲区操作W5500 的 socket 缓冲区是环形结构读写指针到末尾后要绕回开头。下面这段代码是写入发送缓冲区的核心注意环形分两段写。#define S0_TX_BASE 0x0000 #define S0_RX_BASE 0x4000 #define TX_BUF_SIZE 8192 #define RX_BUF_SIZE 8192 static void w5500_write_txbuf(uint16_t offset, const uint8_t *data, uint16_t len) { if (offset len TX_BUF_SIZE) { uint16_t first TX_BUF_SIZE - offset; w5500_write_burst(S0_TX_BASE offset, data, first); w5500_write_burst(S0_TX_BASE, data first, len - first); } else { w5500_write_burst(S0_TX_BASE offset, data, len); } }这里假设 Socket 0 独占了 8KB TX 空间所以数据写在物理地址 0x0000 到 0x1FFF。如果你配置了多个 socketS0_TX_BASE要根据数据手册的缓冲区分配表重新计算不是固定 0。读接收缓冲区同理只是基地址换成了 0x4000 那一带。5. 让它跑起来配置网络参数与 TCP Server5.1 软件复位上电后第一件事不是急着配 IP而是先把芯片复位到干净状态。硬件复位已经做了的话这一步其实就是软件保险。void w5500_soft_reset(void) { w5500_write_reg(0x0000, 0x80); // MR 寄存器 bit7 置 1触发软复位 while (w5500_read_reg(0x0000) 0x80) { vTaskDelay(pdMS_TO_TICKS(1)); } }0x0000是模式寄存器 MR 的地址。往 bit7 写 1 会让芯片执行内部复位复位完成后该位自动清零。这个等待循环很重要有些代码复位完立刻去写寄存器数据经常会丢。复位之后最好再读一下 MR 和版本寄存器确认 SPI 通路是通的。如果读出来全是 0xFF 或者全是 0x00先别急着往下走八成是接线或者 SPI 模式的问题。5.2 配置 MAC、IP、网关和掩码W5500 的寄存器都是大端序MAC 按顺序写 6 字节IP 按顺序写 4 字节。uint8_t mac[6] { 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01 }; uint8_t ip[4] { 192, 168, 1, 200 }; uint8_t gw[4] { 192, 168, 1, 1 }; uint8_t mask[4] { 255, 255, 255, 0 }; void w5500_net_config(void) { w5500_write_burst(0x0001, gw, 4); // GAR 网关地址 w5500_write_burst(0x0005, mask, 4); // SUBR 子网掩码 w5500_write_burst(0x0009, mac, 6); // SHAR 源 MAC w5500_write_burst(0x000F, ip, 4); // SIPR 源 IP }注意MAC 地址不要随便编尤其在同一局域网里有多个设备时MAC 冲突会导致 ARP 混乱设备时通时断。生产环境下 MAC 一般会写进 EEPROM 或从模块上读取。5.3 打开 TCP Server 的完整流程打开一个 TCP Server 听着玄乎其实就五步设置协议类型、设置源端口、发 OPEN 命令、发 LISTEN 命令、等待客户端连接。#define S0_MR (0x0400 0) #define S0_CR (0x0400 1) #define S0_SR (0x0400 3) #define S0_PORTR (0x0400 4) #define SOCK_CLOSED 0x00 #define SOCK_INIT 0x13 #define SOCK_LISTEN 0x14 #define SOCK_ESTABLISHED 0x17 void w5500_tcp_server_start(uint16_t port) { w5500_write_reg(S0_MR, 0x01); // Socket0 模式TCP w5500_write_reg(S0_PORTR, port 8); w5500_write_reg(S0_PORTR 1, port 0xFF); // 端口是大端 w5500_write_reg(S0_CR, 0x01); // OPEN 命令 while (w5500_read_reg(S0_SR) ! SOCK_INIT) { vTaskDelay(pdMS_TO_TICKS(1)); } w5500_write_reg(S0_CR, 0x02); // LISTEN 命令 while (w5500_read_reg(S0_SR) ! SOCK_LISTEN) { vTaskDelay(pdMS_TO_TICKS(1)); } }S0_MR写 0x01 表示 TCP 模式S0_PORTR是 16 位端口先高后低。OPEN 之后芯片进入 INIT 状态再发 LISTEN 才会真正监听端口。这段代码有两种跑法一种是阻塞等待适合初始化阶段另一种是开一个任务轮询状态适合正式产品。实际项目里别在主循环里死等万一网线没插芯片永远不会进入 ESTABLISHED整个程序就卡死了。我一般会给等待加超时超过 5 秒就报错重来。6. 收发数据绕不开的缓冲区指针6.1 接收数据的长征W5500 收到 TCP 数据后INT 脚会拉低Sn_IR 寄存器里 RECV 位会置 1。这只是一个提醒真正的数据在接收缓冲区里排着需要用一套指针机制去取。读取数据分四步读 Sn_RX_RSR 得到已经收到但还没被取走的字节数读 Sn_RX_RD 得到当前读指针从接收缓冲区读数据更新 Sn_RX_RD 指针然后发 RECV 命令通知芯片。uint16_t w5500_recv(uint8_t sn, uint8_t *buf, uint16_t maxlen) { uint16_t rsvr w5500_read_reg16(S0_RX_RSR); if (rsvr 0) return 0; uint16_t rd w5500_read_reg16(S0_RX_RD); uint16_t len (rsvr maxlen) ? maxlen : rsvr; // 处理环形回绕 if (rd len RX_BUF_SIZE) { uint16_t first RX_BUF_SIZE - rd; w5500_read_burst(S0_RX_BASE rd, buf, first); w5500_read_burst(S0_RX_BASE, buf first, len - first); } else { w5500_read_burst(S0_RX_BASE rd, buf, len); } rd (rd len) % RX_BUF_SIZE; w5500_write_reg16(S0_RX_RD, rd); w5500_write_reg(S0_CR, 0x40); // RECV 命令 return len; }这里最容易被忽略的是最后两步。如果你只读数据不更新 Sn_RX_RD 也不发 RECV 命令芯片会以为数据一直没被取走下一次收包时缓冲区永远报告满的。这个坑我至少见过五六个人踩过现象就是设备收第一包正常之后再也收不到新数据。6.2 发送数据同样是指针游戏发送前要先看 Sn_TX_FSR这个寄存器表示发送缓冲区还剩多少空间。数据写入后更新 Sn_TX_WR然后发 SEND 命令。uint16_t w5500_send(uint8_t sn, const uint8_t *buf, uint16_t len) { while (w5500_read_reg16(S0_TX_FSR) len) { vTaskDelay(pdMS_TO_TICKS(1)); // 实际项目里这里要加超时否则缓冲区满时会死循环 } uint16_t wr w5500_read_reg16(S0_TX_WR); if (wr len TX_BUF_SIZE) { uint16_t first TX_BUF_SIZE - wr; w5500_write_burst(S0_TX_BASE wr, buf, first); w5500_write_burst(S0_TX_BASE, buf first, len - first); } else { w5500_write_burst(S0_TX_BASE wr, buf, len); } wr (wr len) % TX_BUF_SIZE; w5500_write_reg16(S0_TX_WR, wr); w5500_write_reg(S0_CR, 0x20); // SEND 命令 return len; }发送有个特性SEND 命令发出后芯片会异步地把缓冲区里的数据组帧发出去。发送完成时 Sn_IR 的 SEND_OK 位会置 1。如果只是发个几十字节可以不做判断但如果发大数据包、或者追求可靠性还是等 SEND_OK 再继续下一包否则可能把还没发完的数据覆盖掉。6.3 用中断替代轮询上面代码里我故意用轮询演示逻辑清楚。正式产品建议把 INT 引脚接成下降沿中断然后在中断服务函数里只置一个标志位static volatile bool w5500_irq_flag false; void IRAM_ATTR w5500_isr(void *arg) { w5500_irq_flag true; } // 主循环里 if (w5500_irq_flag) { w5500_irq_flag false; uint8_t irq w5500_read_reg(S0_IR); if (irq 0x01) { // CONNECT w5500_write_reg(S0_IR, 0x01); // 写 1 清零 } if (irq 0x02) { // RECV w5500_recv(0, buf, sizeof(buf)); w5500_write_reg(S0_IR, 0x02); } }注意中断服务函数里不能做 SPI 传输SPI 的spi_device_transmit里面会取互斥锁、可能休眠放在中断上下文会直接崩。中断里只置标志真正处理放到主循环或其他任务里这个习惯能帮你躲掉一大半嵌入式 RTOS 的坑。7. 调试中踩过的坑与排查技巧7.1 读回全是 0xFF这个现象十有八九是接线或 SPI 配置问题。我排查顺序是先量 CS 和 SCK 引脚在通信时有没有电平跳变没有就查 GPIO 是否被复用再看 MOSI 和 MISO 有没有接反这种小模块的丝印不一定靠谱建议对照原理图确认最后用逻辑分析仪抓一下模式是不是 Mode 0很多模块对时序非常敏感。还有一种隐蔽情况模块的 MISO 引脚需要上拉。有些 W5500 模块上 MISO 是推挽输出不需要外接上拉但如果是开漏输出就必须加 4.7k 上拉否则读出来全是 0xFF。7.2 能初始化但 ping 不通SPI 寄存器读写正常芯片也进入了 LISTEN 状态但电脑 ping 不通优先检查网络参数。网关、子网掩码、IP 有没有配错MAC 地址有没有和局域网里其他设备冲突。然后检查网口状态灯RJ45 座子上一般有绿灯和黄灯绿灯亮说明物理链路通黄灯闪说明有数据在跑。如果链路灯都不亮那就是网络变压器、RJ45 焊接或网线问题和代码无关。7.3 数据错位、偶发丢字节SPI 是全双工同步协议数据错位往往是时钟沿不对或频率太高。先用 1MHz 的低频跑一遍确认频率不是瓶颈后再往上提。杜邦线超过 10cm 时信号完整性会明显下降MISO 上的数据眼可能已经闭合了。我的经验是 4MHz 以下随便飞线8MHz 就要短粗线16MHz 以上建议做等长线或直接打板。还有一种情况是 DMA 与手动 CS 冲突。开了 DMA 后spi_device_transmit可能会用异步方式CS 手动控制的时序要保证整帧完成后才拉高否则从机只收到半个帧后面全乱。7.4 缓冲区指针忘更新这个前面提过接收端最容易漏Sn_RX_RD写回和 RECV 命令。发送端容易漏Sn_TX_WR更新。现象都一样通信通一次后就卡死。排查时打印这两个指针看看如果指针不往前走问题一定出在更新逻辑上。7.5 常见问题速查表现象可能原因解决方法读寄存器全 0xFF接线错误 / MISO 未上拉检查 MOSI MISO加 4.7k 上拉读寄存器全 0x00CS 没拉低 / SPI 频率过低异常查 CS 时序锁相环初始化能初始化但 ping 不通IP/MAC 配置错误对照局域网网段重新配置收第一包后卡死未更新 Sn_RX_RD 或未发 RECV补上指针回写和 RECV 命令数据偶发错位频率太高 / 杜邦线太长降到 4MHz缩短接线网口灯不亮网线 / 网络变压器焊接问题换网线检查 RJ45 焊接7.6 后续扩展思路这套驱动跑通后往上叠东西就轻松了。想做一个 HTTP 服务端在 TCP Server 的基础上解析 HTTP 请求行就行想上报数据就把 MQTT 协议封装在 TCP 里往服务器推如果你在折腾 ESP32 OTA 升级也可以用 W5500 做有线网络下载固件有线传输比 Wi-Fi 稳定得多固件包大一点也不用担心断流。我个人后续比较建议先跑通 TCP Client让 ESP32 主动连服务器这样调试起来更直观。因为 Server 模式还要处理客户端断开重连、TIME_WAIT 等等Client 模式逻辑更短新手成就感来得更快。8. 关于这套方案的几点个人体会最后多说两句。我实际用下来W5500 加 ESP32 这套组合最适合的场景就是“24 小时在线、数据不能断”的固定设备像环境采集网关、Modbus TCP 转换器、工业控制器都很合适。它的缺点也很明显芯片价格比纯 Wi-Fi 方案贵而且必须物理铺网线安装灵活度不如无线。如果你第一次调这个组合不要把频率一开始就拉满先把 4MHz 跑通然后慢慢提。调试时多用逻辑分析仪别凭肉眼猜时序。另外给中断、复位、CS 这几个引脚加个串口打印日志函数出问题的时候能省很多时间。做这类底层驱动最有价值的不是把代码抄对而是知道每一行代码在等什么、在防什么。你把这套例程吃透之后再去看其他 SPI 设备比如 OLED、Flash、传感器会发现套路都差不多先搞定读写原语再看芯片手册找寄存器定义剩下的就是堆逻辑而已。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →