首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >DeviceNet如何转UART?嵌入式Demo六处改造详解

DeviceNet如何转UART?嵌入式Demo六处改造详解

原创
作者头像
稳联技术老杨
发布于 2026-09-18 17:26:22
发布于 2026-09-18 17:26:22
1020
举报

详细拆解,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 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档