
详细拆解,DeviceNet转UART嵌入式板卡Demo程序的修改要点。先给出整体架构,再逐模块说明demo中的典型修改位置和示例代码。
一、板卡硬件架构(典型)
[DeviceNet现场总线]──>[CAN收发器(如MCP2551/TJA1050)]──>[MCU的CAN控制器]
│
▼
[协议转换层]
│
▼
[MCU的UART]──>[UART设备]
典型MCU选型:
-STM32F103/F407/F105:自带CAN控制器+多个UART,资源够用
-STM32F030+MCP2515(外挂CAN控制器):成本更低的方案
-UART端通常配置:115200/8N1(也支持9600、921600等可调)
二、demo程序典型结构
原厂SDK/demo一般包含以下目录:
Project/
├──Driver/
│├──bsp_can.cCAN驱动(DeviceNet物理层)
│├──bsp_uart.cUART驱动
│└──bsp_timer.c心跳/连接超时定时器
├──Middleware/
│├──devicenet.cDeviceNet协议栈(最关键的修改层)
│└──uart_protocol.c自定义UART上层协议
├──App/
│└──main.c主循环/状态机
└──Config/
└──devicenet_cfg.h节点MAC、波特率、I/O映射
三、demo程序的修改细节(逐项说明)
1.CAN过滤器配置(DeviceNet报文标识符)
DeviceNet基于CAN2.0B,使用29位标识符,由MACID+服务代码组成。demo默认过滤器常常配错,必须改。
修改文件:bsp_can.c中的CAN_FilterInit()
c
//=====原demo通用配置(不合适DeviceNet)=====
CAN_FilterInitTypeDeffilter;
filter.CAN_FilterIdHigh=0x0000;
filter.CAN_FilterIdLow=0x0000;
filter.CAN_FilterMaskIdHigh=0xFFFF;
filter.CAN_FilterMaskIdLow=0xFFFF;//全通,接收所有报文
//=====改为DeviceNet专用过滤=====
//DeviceNet连接报文通常走CANID10xxxxxx+xxx(Group1/3)
//ExplicitMessaging:COB-ID=0x600+MACID或0x400+MACID
//这里以Group2Server(0类报文)为例
filter.CAN_FilterNumber=0;
filter.CAN_FilterMode=CAN_FilterMode_IdMask;
filter.CAN_FilterScale=CAN_FilterScale_32bit;
filter.CAN_FilterIdHigh=(CAN_ID_EXPLICIT<<3)>>16;//高16位
filter.CAN_FilterIdLow=(CAN_ID_EXPLICIT<<3)&0xFFFF;
filter.CAN_FilterMaskIdHigh=0x1FFF;//关注ID,忽略帧类型/RTR
filter.CAN_FilterMaskIdLow=0xFFE0;
filter.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;
filter.CAN_FilterActivation=ENABLE;
HAL_CAN_ConfigFilter(&hcan,&filter);
关键点:
必须区分Group1(优先报文:0x18xxxxx)和Group2(显式/轮询报文:0x10xxxxx~0x17xxxxx)
-demo默认只接Group2,丢了Group1会导致连接请求/断开报文漏收→节点永远连不上主站
2.DeviceNet节点身份/重复MAC检查
demo通常硬编码NODE_MAC_ID=63,实际产线每个节点MAC不同,且必须支持DuplicateMACIDCheck(重复MAC校验)。
修改文件:devicenet_cfg.h
c
defineNODE_MAC_ID5//硬件拨码开关读入,见下文
defineBAUDRATE_KBPS125//DeviceNet标准:125/250/500
defineSUPPORT_SEGMENTED_XFER1//是否支持分段报文
defineHEARTBEAT_INTERVAL_MS1000//心跳周期
demo中通常这样实现的拨码读取→替换掉硬编码:
c
//1)GPIO拨码开关读取(8位MACID→0~63)
uint8_tBoard_ReadMacId(void)
{
uint8_tmac=0;
for(inti=0;i<6;i++){//6位拨码开关
if(HAL_GPIO_ReadPin(GPIOA,(1<<i))==GPIO_PIN_RESET){
mac|=(1<<i);
}
}
returnmac;
}
//2)上电时执行DuplicateMACCheck
voidDevicenet_DupMacCheck(void)
{
CanTxMsgtx;
tx.StdId=0;//DeviceNet重复MAC检查用特定功能码
tx.IDE=CAN_ID_EXT;
tx.RTR=CAN_RTR_DATA;
tx.DLC=1;
tx.Data[0]=0x07;//DuplicateMACIDCheck报文的服务代码
//发送后监听总线~1.5s,若收到其他节点的相同报文则报错
HAL_CAN_Transmit(&hcan,&tx,100);
//...监听流程省略
}
3.报文分发/协议转换核心(最关键)
这是demo改造的核心部分。原demo一般只是把CAN报文原样echo到UART,没有协议转换逻辑,需要彻底重写。
修改文件:Middleware/uart_protocol.c
c
//===UART帧格式(自定义,与上位机约定)===
//帧头(2)+长度(1)+命令字(1)+MAC_ID(1)+数据(N)+CRC16(2)
//0xAA0x55|LEN|CMD|MAC|DATA...|CRC_LOCRC_HI
defineFRAME_HEAD_HI0xAA
defineFRAME_HEAD_LO0x55
defineMAX_DATA_LEN64
typedefstruct{
uint8_tcmd;//0x01=读I/O,0x02=写I/O,0x03=事件上报
uint8_tmac_id;
uint8_tlen;
uint8_tdata[MAX_DATA_LEN];
}UartFrame_t;
//===CAN显式报文→UART帧的转换===
voidDevicenet_ExplicitMsg_to_Uart(CanRxMsgrx)
{
if(rx->DLC<2)return;
uint8_tservice_code=rx->Data[0];//Servicecode
uint8_tclass_id=rx->Data[1];
//========关键:DeviceNet类映射=========
//Class0x01IdentityObject
//Class0x02MessageRouter
//Class0x03DeviceNetObject
//Class0x05ConnectionObject
//Class0x08DiscreteInputPoint(数字量输入)
//Class0x09DiscreteOutputPoint(数字量输出)
//...
UartFrame_tuart;
uart.cmd=(service_code&0x80)?0x82:0x81;//响应vs请求
uart.mac_id=NODE_MAC_ID;
uart.len=rx->DLC;
memcpy(uart.data,rx->Data,rx->DLC);
Uart_SendFrame(&uart);
}
修改文件:Middleware/devicenet.c(显式报文处理)
c
//1)把demo中的占位Get_Attribute替换为真实的设备I/O读取
uint8_tDevicenet_GetAttribute(uint16_tclass_id,uint16_tinstance,
uint8_tattr_id,uint8_tout,uint8_tout_len)
{
switch(class_id){
case0x01://Identity
if(instance==1&&attr_id==1){out_len=sprintf((char)out,"%d",VENDOR_ID);return0;}
if(instance==1&&attr_id==4){out_len=1;out[0]=NODE_MAC_ID;return0;}
break;
case0x08://DiscreteInput
if(attr_id==3){
//真实读取MCUGPIO
uint8_tval=0;
val|=(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)<<0);
val|=(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)<<1);
val|=(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)<<2);
out_len=1;
out[0]=val;
return0;
}
break;
case0x09://DiscreteOutput(可选,有些板卡没有)
break;
}
return0xFF;//AttributeNotSupported
}
//2)demo默认只支持Get,实际生产需要Set
uint8_tDevicenet_SetAttribute(uint16_tclass_id,uint16_tinstance,
uint8_tattr_id,uint8_tin,uint8_tin_len)
{
if(class_id==0x09&&attr_id==3){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_3,(in[0]&0x01)?GPIO_PIN_SET:GPIO_PIN_RESET);
return0;//Success
}
return0xFF;
}
4.UART接收环形缓冲区+超时判定
demo一般用单字节中断,实战必须改成环形FIFO。
修改文件:bsp_uart.c
c
defineUART_RX_BUF_SIZE256
typedefstruct{
uint8_tbuf[UART_RX_BUF_SIZE];
uint16_thead;
uint16_ttail;
}RingBuf_t;
staticRingBuf_tg_uart_rx={0};
voidUART_RxCallback(uint8_tbyte)
{
uint16_tnext=(g_uart_rx.head+1)%UART_RX_BUF_SIZE;
if(next!=g_uart_rx.tail){
g_uart_rx.buf[g_uart_rx.head]=byte;
g_uart_rx.head=next;
}
//重启3.5字符超时定时器(用于Modbus-style帧定界)
__HAL_TIM_SET_COUNTER(&htim3,0);
HAL_TIM_Base_Start_IT(&htim3);
}
//在TIM3中断回调里检查是否到3.5字符时间
voidTIM3_IRQHandler(void)
{
if(__HAL_TIM_GET_FLAG(&htim3,TIM_FLAG_UPDATE)){
__HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
HAL_TIM_Base_Stop_IT(&htim3);
Frame_Decode_Task();//解析上面累积的字节
}
}
5.主循环/状态机改造
修改文件:App/main.c
c
intmain(void)
{
HAL_Init();
SystemClock_Config();
//1)GPIO/UART/CAN/TIM初始化
MX_GPIO_Init();
MX_USART1_UART_Init();//UART1给上位机用
MX_CAN_Init();//CAN1给DeviceNet
MX_TIM3_Init();//帧间隔定时器
//2)节点参数读入+重复MAC检查
Node_MacId=Board_ReadMacId();
Devicenet_DupMacCheck();
//3)注册回调
HAL_CAN_RegisterCallback(&hcan,HAL_CAN_RX_MSG_CB_ID,CAN_RxHandler);
HAL_CAN_RegisterCallback(&hcan,HAL_CAN_TX_CPLT_CB_ID,CAN_TxCpltHandler);
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);
//4)启动CAN
HAL_CAN_Start(&hcan);
while(1){
//a)处理UART协议解析
Uart_Process();//解析来自上位机的命令→触发DeviceNet报文
//b)处理DeviceNet协议
Devicenet_Poll();//连接状态机/心跳/I/O轮询
//c)LED状态指示
LED_Run_Indicate(Node_State);//绿色:运行红色:故障闪烁:未连接
}
}
6.EDS文件(给主站配置用,常被遗漏)
demo通常只给.bin/.hex,没生成EDS,主站(如RockwellRSLogix)无法识别。必须新增:
ini
[File]
FileName="MyDevNode.eds";
FileVersion=1.0;
[Device]
VendorID=0x1234;
ProductType=0x00;//CommunicationAdapter
ProductCode=0x0005;
MajorRev=1;
MinorRev=0;
[Connection]
BaudRate=125;//125/250/500
DefaultMACID=5;
[Port]
PortName="UART(RS-232/RS-485)";
[Assembly]
InputAssembly=0x01;//Input数据
OutputAssembly=0x02;//Output数据
添加路径:Config/Generated/MyDevNode.eds→导入主站扫描工具。
四、调试/测试清单
五、最容易踩的几个坑
1.CANID高低位写反:StdId/ExtId在STM32HAL里要左移3位,因为bit0~2是RTR/IDE控制位,demo经常写错。
2.滤波器配置成16位但标识符是29位:会出现"忽然连不上/偶尔丢连接"。
3.波特率与终端电阻不匹配:1Mbps和125k的终端电阻值不同(都是120Ω),demo默认焊了就行,实际产线现场若距离过长需要加终端电阻。
4.UART的DMA与CAN中断优先级冲突:调高CANRX中断优先级,否则高负载时丢帧。
5.EDS文件Class/Instance没对齐代码:主站连上后读不到对象,排查这种问题一定要先对比EDS和代码常量表。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。