尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32F105的CAN/485/以太网多协议网关设计详解

基于STM32F105的CAN/485/以太网多协议网关设计详解 简介一款完整的STM32F105工业通信控制板设计资料涵盖硬件原理图、PCB版图与软件源码面向嵌入式硬件工程师、单片机开发者以及需要快速落地通信控制类产品的研发人员。硬件部分由核心板与底板组成均采用双层板设计核心板尺寸35×21mm底板93×61mm双面布局布线集成W5500以太网、RS232、RS485与CAN总线接口主控为STM32F105R配套常用接口芯片。原理图与PCB使用Altium Designer绘制文件完整且可直接修改复用软件源码已在制板项目中实际验证可作为产品设计的参考基线。资源压缩包共226个文件约3.44MB以C语言源文件、汇编启动文件、原理图文档和PCB文档为主辅以PDF说明与表格资料结构便于按硬件与软件分块查阅。目前已有597人学习下载从方案选型到布局布线再到固件编写均有完整呈现对希望借鉴成熟多接口控制板方案、缩短开发周期的工程师具有较高的参考价值。1. STM32F105 控制板为什么有人放着 F103 不用偏偏挑互联型STM32F105-W5500-CAN-232-485 这块板子目标不是跑点灯而是把多路工业总线汇总到以太网上。F105 自带双 CAN、多个 USART 和一个以太网 MAC但 MAC 没有 PHY工程上普遍外挂 W5500用 SPI 接口就把协议栈消化掉。CAN 采现场设备485 接电表变频器232 接老式仪表W5500 把数据转成 TCP 给上位机一块板子完成总线桥接。我拿到这类项目时第一件事不是翻原理图而是确认接口之间是否隔离CAN 和 485 建议光电隔离232 做电荷泵方案成本高通常只加防护。下面从引脚分配、PCB 布线到源码骨架把完整路径理一遍。2. 接口资源分配先让 STM32F105 把引脚和时钟说通2.1 五路外设怎么挂SPI / CAN / USART 的资源映射STM32F105 的外设资源不能闭眼分配最典型的坑是 CAN1 和 USB 抢 PA11/PA12。默认情况下 CAN1_RX/TX 在 PA11/PA12USB D/D- 也在这两个脚如果控制板既要 CAN 又要 USB 口必须把 CAN1 重映射到 PB8/PB9否则 USB 枚举和 CAN 收发只能活一个。我一般会在原理图阶段就决定是否留 USB避免后期软件改不通。W5500 固定挂 SPI1把高速外设放在主 SPI 上CAN2 使用 PB12/PB13逻辑清晰PCB 上也容易把数字电路和模拟接口分开。下面这张表是这类网关板最常用的引脚分配可以作为原理图引脚的起点外设接口STM32F105 引脚说明W5500SPI1SCKPA5 / MISOPA6 / MOSIPA7 / SCSPA4主 SPISCS 用 GPIO 控制W5500INTPB0低电平有效下降沿触发W5500RSTPB1低电平复位需软件延时CAN1CANPB8 / PB9重映射CAN1_RXPB8, CAN1_TXPB9CAN2CANPB12 / PB13默认映射不与 USB 冲突RS232USART2PA2 / PA3TXPA2, RXPA3RS485USART3PB10 / PB11方向控制用 PC12GPIO 初始化里有一点容易忽略W5500 的 SCS 引脚要设置成推挽输出不能配置为 SPI 硬件片选否则片选会被硬件自动拉低SPI 访问全乱。引脚规划完成后要立刻在代码里做一次外设重映射验证最省事的方法是直接在 CAN 初始化前调用一次引脚重映射函数// 将 CAN1 从 PA11/PA12 重映射到 PB8/PB9 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE); GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);参数说明这个调用必须在 GPIO 配置之前完成使能 AFIO 时钟后重映射才生效。如果源码里漏了这一步CAN1 一直不进中断而 USB 偶尔枚举失败现象非常像硬件虚焊。原理图评审时我会把重映射必需的网络名直接标在 CAN1 引脚旁防止 Layout 工程师按默认 PA11/PA12 布局。2.2 W5500 的 SPI 驱动片选、中断和复位时序W5500 作为 SPI 从机SCS 低有效一次完整访问流程是拉低 SCS发送 16 位地址和 8 位控制字节然后连续读写数据最后拉高 SCS。W5500 支持 SPI Mode 0 和 Mode 3我习惯用 Mode 0即 CPOL0、CPHA1Edge时钟空闲为低数据在第一个边沿采样。SPI 预分频先取/872MHz 主频下是 9MHz原型跑稳后再尝试/4高速跑不上时也知道是走线还是时序问题。void W5500_SPI_Init(void) { SPI_InitTypeDef spi; GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; // PA5/PA6/PA7 gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_4; // PA4 SCS手工拉片选 gpio.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOA, gpio); GPIO_SetBits(GPIOA, GPIO_Pin_4); // 空闲释放 SPI_I2S_DeInit(SPI1); spi.SPI_Direction SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode SPI_Mode_Master; spi.SPI_DataSize SPI_DataSize_8b; spi.SPI_CPOL SPI_CPOL_Low; spi.SPI_CPHA SPI_CPHA_1Edge; spi.SPI_NSS SPI_NSS_Soft; spi.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; spi.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spi); SPI_Cmd(SPI1, ENABLE); }参数说明PA4 配置成普通输出后代码里不能再调用 SPI 硬件 NSS 相关函数片选时序完全由软件决定。W5500 的 RST 脚由 PB1 控制上电后先拉低 1ms再拉高之后延时 100ms 才能访问寄存器。这个延时不要压缩W5500 内部电源稳定前读回的值全是 0xFF很多“W5500 not found”是复位时序导致的不是焊接问题。2.3 CAN 收发器的采样点BS1/BS2/SJW 和波特率表STM32F105 的 bxCAN 位时间由同步段、时间段 BS1、时间段 BS2 组成SJW 是重同步跳跃宽度。采样点位置由(1BS1)/(1BS1BS2)决定。现场最常见的错误是先定波特率BS1/BS2 随便填采样点掉到 50% 以下总线一旦有长短线混接就大量 CRC 错误。我一般按总位数 12 或 16、采样点 75%~87.5% 反推参数再对照示波器波形微调。以 APB1 时钟 36MHz 为例常用 500kbps 的配置是实际预分频 6BS18tqBS23tq一个位总共 12tq采样点正好 75%。注意标准外设库的CAN_Prescaler写入寄存器后实际分频是写入值加 1例子中代码写 5实际是 6 分频这个细节在换 HAL 库时最容易踩坑。CAN_InitTypeDef canInit; canInit.CAN_TTCM DISABLE; // 时间触发通信不开启 canInit.CAN_ABOM ENABLE; // 总线 Off 后自动恢复 canInit.CAN_AWUM ENABLE; // 自动唤醒 canInit.CAN_NART DISABLE; // 总线仲裁失败后自动重发 canInit.CAN_RFLM DISABLE; // FIFO 溢出不锁定 canInit.CAN_TXFP DISABLE; // 按报文 ID 优先级发送 canInit.CAN_SJW CAN_SJW_1tq; // 1tq 同步跳跃宽度 canInit.CAN_BS1 CAN_BS1_8tq; // 时间段1 canInit.CAN_BS2 CAN_BS2_3tq; // 时间段2 canInit.CAN_Prescaler 5; // 实际 6 分频36MHz/(6*12)500k CAN_Init(CAN1, canInit);参数说明CAN_ABOM必须打开总线短路或节点掉电后控制器进入 Bus-Off 状态不打开自动恢复这个节点只能重新上电才能恢复通信。SJW 在 1~2tq 之间选长线或光耦隔离时选 2tq增加容错同时不会明显降低采样精度。下面这张表可以直接抄不同波特率的 BRP 按 36MHz APB1 计算波特率APB1实际预分频BS1BS2SJW采样点1Mbps36MHz383175%500kbps36MHz683175%250kbps36MHz1283175%125kbps36MHz2483175%CAN2 和 CAN1 可以分别设置不同预分频但下项目时尽量让两个控制器使用相同配置否则网关转换时会因为位时序不一致多出几个微秒的转发延迟。2.4 232/485 电平转换电路MAX3232 与 SP3485 的选型RS232 和 RS485 的差别不仅是电平还有驱动方式和节点数。RS232 是全双工点对点RS485 是半双工多点总线控制板上 232 口给本地调试和协议分析485 口给远程设备组网。电平转换芯片我常用 MAX3232 和 SP3485前者自带电荷泵外部只需 0.1uF 电容后者需要 A/B 线的上下拉偏置和终端匹配电阻。项目MAX3232SP3485工作电压3.0~3.6V3.3V电平标准RS232 ±5V ~ ±15VRS485 差分最大速率1Mbps10Mbps节点数点对点最多 32 个节点外部元件5 个 0.1uF 电荷泵电容A/B 各 10k 偏置120 欧终端原理图上有三个细节容易出错。MAX3232 的电荷泵电容必须用 0.1uF换成 1uF 后输出摆幅可能不足接部分 USB 转串口时识别不到电平跳变。SP3485 的 A 线上拉 10kB 线下拉 10k保证总线空闲时逻辑为 1否则第一个字节容易丢。120 欧终端电阻不直接焊死用排针跳线选择只有总线最远端节点才短接其他节点保留开路。3. PCB 走线要求与板层设置哪根线该粗、哪根线该短3.1 板层设置与铜厚双层板就够四层板更省心W5500 模块本身对布线要求不高但以太网变压器、CAN 和 485 的接口防护占地方板子面积在 10cm x 10cm 以内时我一般直接上四层板。表层走 W5500 差分对、SPI 和接口保护件第二层完整地平面第三层电源底层走低速信号和测试点。四层板成本只比双层多几十元但差分布线阻抗控制容易W5500 的 SPI 时钟也不会因为绕线而过冲。在 Cadence 或 Altium Designer 里设置层叠时外层铜厚 1oz内层 0.5oz介电常数按 FR4 常规值。四层层叠的具体参数可以参照下表层用途铜厚关键走线L1信号 差分1ozW5500 差分对、SPI、TVS 防护走线L2地平面0.5oz严禁大面积开槽L33.3V/5V 电源0.5oz电源分割主线宽 1.2mm 以上L4低速信号1oz232/485、跳线、调试口整板最小间距按 6mil 设置电源网络间距放宽到 15mil。L2 地平面是所有高速信号的返回路径W5500 变压器下方和网口连接器下方要挖空不能让地平面把网口共模噪声直接耦合进主控地。3.2 W5500 以太网差分走线100欧差分布局W5500 内置 MACPHY和网络变压器之间需要两组差分线TXN/TXP 和 RXN/RXP。差分阻抗控制在 100 欧 ±10%走线宽度 5~6mil间距 7~8mil具体值要按板厂阻抗计算表来。等长误差控制在 5mil 内差分对内部不要换层也不要打过孔过孔会引入阻抗突变和相位偏移。W5500 差分线对周围 3 倍线宽范围内不要走其他信号。网口变压器隔离带下方的内层铜皮要挖掉否则雷击或 ESD 测试时二次侧高压会沿着覆铜耦合到主控侧。变压器中心抽头的对地电容要靠近芯片放置这个 0.01uF 电容的位置比容量更重要放远了等效串联电感变大EMI 测试很难过。3.3 CAN 和 485 的布线终端电阻与 TVS 管靠近端子CAN 的 CANH/CANL 是差分信号但不需要按 100 欧控制直接用 15~20mil 普通走线即可。关键是防护器件的位置TVS 管 SM712 紧贴接插件然后是共模电感和终端电阻。没有压敏或保险丝时至少保证 TVS 到端子的走线小于 3mm否则雷击拉弧可能先打到芯片端。485 的 A/B 走线类似偏置电阻、TVS、共模电感都放在接口侧。共模电感下方不要走其他信号线它正好做数字地和总线地的物理隔离边界。如果板子用了隔离方案DC-DC 隔离电源模块放在隔离带中央二次侧地独立铺铜不要和数字地大面积重叠。原理图上看不出问题但 PCB 阶段不这么做485 在电机启动时就会丢帧。3.4 电源、晶振与去耦电容用脚本核算走线宽度电源走线宽度不能拍脑袋。整板 3.3V 电流由 W5500、主控和收发器构成W5500 在 100M 全双工下电流接近 180mA加上 CAN 收发器和逻辑芯片整板约 500mA。我习惯用 Python 按 IPC-2221 经验公式估算最小线宽再把结果乘以 1.3 留余量然后填进 PCB 设计工具的线宽约束里。import math def min_width(current_ma, copper_um35, rise_c10): # IPC-2221 外层走线经验公式copper_um 为铜厚 area (current_ma / 1000.0) / (0.024 * (rise_c ** 0.44)) width_mm area / (copper_um / 1000.0) return width_mm * 1.3 # 预留 30% 余量 for name, ma in [(3.3V, 500), (W5500, 180), (CAN_VCC, 200)]: w min_width(ma) print(f{name}: {w:.2f} mm ({w*39.37:.1f} mil))参数说明copper_um35 对应 1oz 铜厚温度上升按 10 摄氏度计算。计算结果中 3.3V 主干线大约需要 1.2mmW5500 电源脚 0.8mm普通信号 0.2mm 以上。晶振周围不要走数字信号STM32F105 的 8MHz 晶振负载电容要靠近 OSC_IN/OSC_OUT 引脚走线越短越好两个负载电容的地脚分别过孔到地平面不要共用一小段引线。4. 软件源码的骨架从 W5500 初始化到 CAN 收发4.1 工程目录与通信任务划分拿到软件源码第一眼看的是 W5500 驱动、CAN 驱动和平台层是否分开。如果驱动和应用代码耦合在一起换一块控制板基本要重写。我一般保持bsp/w5500、bsp/can、bsp/uart、app/gateway四个目录主循环用分时调度不跑 RTOS 也能让每个外设都有响应。调度表如下周期任务说明1ms看门狗、按键扫描不阻塞10msCAN 接收处理中断只写 FIFO主循环解析100msW5500 socket 轮询检查连接、读取接收长度1s状态上报通过 TCP 或 485 发心跳W5500 的 SPI 读取一次数据包会占用几百微秒这期间 CAN 中断会抢占。如果 CAN 中断优先级设得比 SPI 处理低总线负载高时可能丢帧。我习惯把 CAN 接收中断优先级设为最高抢占优先级网络协议栈代码里不用关中断因为 W5500 本身是轮询接收寄存器不以中断为核心。4.2 W5500 初始化与 TCP Server 收发代码W5500 上电初始化顺序不能乱硬件复位、软复位、配置 MAC/IP最后打开 Socket。硬件复位后要延时 100ms软复位后要等芯片内部就绪否则寄存器写入不生效。这里给出最小可运行的 TCP Server 启动代码端口 502 方便直接接 Modbus 网关void w5500_tcp_server_start(void) { uint8_t mac[6] {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56}; uint8_t ip[4] {192, 168, 1, 200}; w5500_hard_reset(); // RST 拉低 1ms - 拉高再延时 100ms w5500_sw_reset(); // 写入 MR 寄存器 0x80 w5500_set_mac(mac); w5500_set_ip(ip, gw, mask); socket(0, Sn_MR_TCP, 502, 0x0); // 端口 502TCP Server listen(0); } void w5500_poll(void) { if (getSn_SR(0) SOCK_ESTABLISHED) { uint16_t len getSn_RX_RSR(0); if (len 0) { uint8_t buf[512]; uint16_t n recv(0, buf, len); can_send_buf(buf, n); // 透传到 CAN 总线 } } }参数说明Sn_MR_TCP必须使用库里的宏不要直接写数字。getSn_RX_RSR返回的是接收数据长度recv之后要立即处理数据TCP 端口缓冲一旦写满芯片会丢弃新报文。如果用 RTOS所有 socket 操作要放在同一个任务里避免多任务同时读写 W5500 寄存器。4.3 CAN 收发与过滤器配置STM32F105 的 bxCAN 有 28 个滤波器网络号与具体通道无关每个 CAN 外设可用的滤波器数量相同。做网关时我一般先开掩码过滤只收和本节点相关的报文再用中断服务函数把数据转给 W5500。下面的配置只匹配一个标准帧 IDvoid can_filter_node(uint16_t node_id) { CAN_FilterInitTypeDef f; f.CAN_FilterNumber 0; f.CAN_FilterMode CAN_FilterMode_IdMask; // 掩码模式 f.CAN_FilterScale CAN_FilterScale_32bit; // 标准帧 ID 是高 11 位在 32 位过滤器里左移 5 位对齐 f.CAN_FilterIdHigh (uint16_t)((node_id 5) 16); f.CAN_FilterIdLow (uint16_t)((node_id 5) | CAN_ID_STD); f.CAN_FilterMaskIdHigh 0x7FF 5; // 掩码置1表示必须对应 f.CAN_FilterMaskIdLow 0x0000; f.CAN_FilterFIFOAssignment 0; f.CAN_FilterActivation ENABLE; CAN_FilterInit(f); } void CAN_RX_IRQHandler(void) { CanRxMsg rx; if (CAN_GetITStatus(CAN1, CAN_IT_FMP0)) { CAN_Receive(CAN1, CAN_FIFO0, rx); w5500_send_tcp((uint8_t *)rx, 13); // 打包 IDDLC数据 } }参数说明node_id 5是把标准帧 11 位 ID 移到寄存器对应位域掩码0x7FF 5表示 ID 的 11 位全部参与匹配。如果需要接受所有 CAN 报文把掩码改成 0x0000 即可但注意 FilterIdLow 里的CAN_ID_STD不能也被掩码掉否则扩展帧也可能混进来。4.4 232/485 串口透传与 Modbus 模式切换232 和 485 代码本身难度不大难点是同一个物理口既要在 Modbus RTU 模式下跑又要能透传。常见做法是启动后看总线上第一个字节如果首字节是设备地址且 CRC 校验通过就进入 Modbus 模式否则按透明传输把数据直接转发到 W5500 或 CAN。485 是半双工发送前要把 DE 拉高发送结束立刻拉低方向切换慢了会吞掉第一个字节快了会把最后一位截断。void RS485_SendBytes(uint8_t *buf, uint16_t len) { GPIO_SetBits(GPIOB, GPIO_Pin_12); // DE1 进入发送 for (uint16_t i 0; i len; i) { while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) RESET); USART_SendData(USART3, buf[i]); } while (USART_GetFlagStatus(USART3, USART_FLAG_TC) RESET); GPIO_ResetBits(GPIOB, GPIO_Pin_12); // TC1 最后一位已移出 }逻辑说明TC标志代表移位寄存器空此时才能安全切回接收如果只看 TXE最后一字节还卡在移位寄存器里切换后会被总线上的回环干扰。RS232 直连不需要方向控制MAX3232 的 TIN/ROUT 直接接 USART2省去一个 GPIO。Modbus RTU 要求帧与帧之间间隔 3.5 个字符时间串口中断服务函数里只做字节接收应用层用定时器记录最后字节时间超时后再认为一帧结束。5. 软硬件联调四个让控制板一次点亮的验证技巧5.1 上电先读 W5500 版本寄存器焊接完第一块板不要急着 ping 通。先写一段最小代码读 W5500 的版本寄存器 0x003F正常值为 0x04。读回 0xFF 或 0x00说明 SPI 时序、复位电路或焊接有问题。用逻辑分析仪同时抓 SCS、SCK、MOSI、MISO重点确认 SCS 在读写期间全程拉低MISO 上有对应响应位。这一步比反复 ping 强得多因为 TCP/IP 协议栈即使通了也不能说明 SPI 时序余量是否充足。5.2 用示波器量 CAN 差分波形确认采样点CAN 总线空闲时是隐性电平CANH 和 CANL 都在 2.5V 附近差分 0V发送显性位时CANH 抬到 3.5VCANL 拉到 1.5V差分约 2V。用示波器双探头分别接 CANH/CANL数学通道做 CH1-CH2观察完整报文。如果波形边沿有振铃或台阶优先检查终端电阻位置如果采样点附近毛刺大按 2.3 节的参数表调整 BS1/BS2。常见现象对照如下波形现象可能原因处理手段没有显性差分收发器方向或 DE 没拉对检查 DE/TE 引脚下拉电阻采样点处毛刺BS1/BS2 比例不当调整采样点到 75% 以上边沿缓慢线缆电容太大或速率过高降低波特率或加重试器5.3 用脚本跑一轮 232/485 回环压力测试单板调试完成后做自动化回环测试最划算。RS232 用跳线短路 TX/RXRS485 接 USB 转 485 适配器让板子把收到的串口数据原样返回。PC 端用 Python 随机生成 256 字节包连续发 1000 包校验一致性import serial, random ser serial.Serial(/dev/ttyUSB0, baudrate115200, timeout0.3) random.seed(2024) for i in range(1000): payload bytes(random.getrandbits(8) for _ in range(256)) ser.write(payload) reply ser.read(len(payload)) if reply ! payload: print(ffail at {i}) break else: print(pass)参数说明timeout 要大于 256 字节在 115200bps 下的传输时间约 22ms这里留 0.3s 防止调度抖动。波特率必须和板子一致关闭流控。脚本在某一帧失败先查 DMA 缓冲是否被覆盖再看 485 方向切换是否忽略了 TC 标志。跑完 1000 帧说明缓存和 DMA 没有丢包可以进入长时间稳定性测试。本文还有配套的精品资源点击获取
返回列表