AT32F421实战---SPI驱动CH395Q构建简易物联网网关
1. 为什么选择AT32F421+CH395Q组合?
在资源受限的物联网网关开发中,芯片选型往往让人纠结。我实测过W5500、ENC28J60等多款以太网方案后,最终选择了AT32F421+CH395Q这个组合,原因很实在——既要协议栈完整,又要开发省心。
CH395Q这颗芯片最吸引我的地方在于内置完整TCP/IP协议栈。做过网络开发的朋友都知道,在MCU上移植lwIP或uIP协议栈有多痛苦:要调ARP、要处理ICMP、还要应付各种异常状态。而CH395Q直接把协议栈做在硬件里,相当于把网络协议处理外包给了专用芯片,主控MCU只需要通过SPI发命令就行。
AT32F421作为主控的优势也很明显:
- 72MHz主频足够处理常规网关业务
- 32KB Flash和16KB RAM在同类产品中算大容量
- 价格只有STM32F103的60%左右
- 原生支持硬件SPI,实测驱动CH395Q时钟能跑到18MHz
硬件连接示意图:
AT32F421 CH395Q PB13(SCK) ----- SCK PB14(MISO) ----- MISO PB15(MOSI) ----- MOSI PB12(NSS) ----- SCS PA11(RST) ----- RST PA12(INT) ----- INT2. 硬件设计避坑指南
2.1 电源设计要点
CH395Q对电源质量极为敏感,我的血泪教训是:
- 必须使用LDO稳压:尝试过DC-DC方案,通信时会出现随机丢包
- 加大滤波电容:VCC3V3引脚至少并联100μF钽电容+0.1μF陶瓷电容
- 独立供电:当与射频模块(如LoRa)共用电源时,CH395Q容易死机
2.2 PCB布局建议
- SPI走线长度控制在5cm以内
- 在SCK信号线串联33Ω电阻抑制振铃
- INT中断信号线要远离高频信号线
- 保留测试点:SCK、MOSI、MISO、INT这四个信号一定要引出测试点
2.3 散热处理
CH395Q工作时芯片表面温度可达60℃,建议:
- 预留散热焊盘
- 避免在芯片正下方走大电流线路
- 环境温度超过40℃时要降低SPI时钟频率
3. 驱动开发实战
3.1 SPI初始化关键代码
void CH395Q_SPI_Init(void) { gpio_init_type gpio_init; spi_init_type spi_init; // 使能时钟 crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_SPI2_PERIPH_CLOCK, TRUE); // 配置SPI引脚 gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE13, GPIO_MUX_0); // SCK gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE14, GPIO_MUX_0); // MISO gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE15, GPIO_MUX_0); // MOSI // SPI参数配置 spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX; spi_init_struct.master_slave_mode = SPI_MODE_MASTER; spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8; // 9MHz时钟 spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB; spi_init_struct.frame_bit_num = SPI_FRAME_8BIT; spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH; spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE; spi_init(SPI2, &spi_init_struct); // 使能SPI spi_enable(SPI2, TRUE); }3.2 芯片检测与初始化流程
- 硬件复位:拉低RST引脚至少500μs
- 检查芯片存在:发送0x06命令+0xA5,应返回0x5A
- 获取版本号:版本≥0x44才支持完整功能
- 初始化协议栈:耗时约200ms,必须等待完成
典型问题排查:
- 如果检测不到芯片,先检查SPI信号是否正常
- 初始化超时可能是电源不稳导致
- 版本号读取异常需检查SPI相位配置
4. 网络功能实现
4.1 UDP通信配置
void UDP_Socket_Init(uint8_t socket_num) { // 设置协议类型 CH395Q_setSocketProtType(socket_num, PROTO_TYPE_UDP); // 设置目标IP(广播地址) uint8_t dest_ip[4] = {255,255,255,255}; CH395Q_setSocketDesIP(socket_num, dest_ip); // 设置本地端口 CH395Q_setSocketSrcPort(socket_num, 8888); // 打开Socket CH395Q_OpenSocket(socket_num); }4.2 数据收发处理
发送数据:
void UDP_Send(uint8_t socket, uint8_t *data, uint16_t len) { // 检查发送缓冲区状态 uint8_t status = CH395Q_getSocketInt(socket); if(status & SINT_STAT_SENBUF_FREE) { CH395Q_SendData(socket, data, len); } }接收数据:
void UDP_Recv_Handler(uint8_t socket) { uint16_t len = CH395Q_getDataLength(socket); if(len > 0) { uint8_t buf[512]; CH395Q_RevData(socket, buf, len); // 处理接收到的数据... CH395Q_ClearRecvBuf(socket); // 必须清空缓冲区 } }4.3 DHCP使用技巧
void Enable_DHCP(void) { // 必须先初始化协议栈 while(CH395Q_InitNet() != CMD_ERR_SUCCESS) { delay_ms(10); } // 启用DHCP CH395Q_setDHCP(1); // 检查获取状态 uint8_t dhcp_status = 0; do { delay_ms(1000); dhcp_status = CH395Q_getDHCPStatus(); } while(dhcp_status != 0); }5. 实战优化建议
5.1 提高通信可靠性
- 增加重传机制:UDP包要添加序列号
- 心跳包设计:每30秒发送一次心跳
- 双缓冲处理:避免数据覆盖
5.2 降低功耗方案
- 空闲时关闭不用的Socket
- 降低SPI时钟频率到1MHz
- 启用PHY自动协商功能
5.3 调试技巧
- 用LED指示网络状态:
- 常亮:物理连接正常
- 慢闪:DHCP获取中
- 快闪:数据传输中
- 保留串口打印调试信息
- 使用网络调试助手验证基础通信
这个方案已经在智能家居网关项目中稳定运行超过6个月,日均处理UDP包约3万次。最让我惊喜的是CH395Q的DHCP客户端非常稳定,从没出现过IP丢失的情况。当然,如果预算充足,建议选择CH395Q的工业级版本,温度范围更宽,可靠性也更好。
