ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ADμC812实现DeviceNet从站:时序优化与SJA1000驱动设计

ADμC812实现DeviceNet从站:时序优化与SJA1000驱动设计 简介基于ADμC812单片机的DeviceNet协议从站程序是一份面向工业现场总线与嵌入式开发学习者的完整工程源码适合需要实现CAN/DeviceNet从站通信的软硬件工程师参考。资源共43个文件压缩包仅283KB以C源文件.c/.h、Keil工程文件.uv2/.opt/.plg、编译中间文件.obj/.lst/.m51及HEX烧录文件为主同时保留启动文件、读取说明和DeviceNet定义头文件便于直接查阅、重新编译与烧录验证。程序在ADμC812单片机的CAN控制器上实现了DeviceNet从站协议源码中包含CAN波特率与滤波器配置、接收/发送缓冲区管理、SDO服务器与PDO映射、中断服务程序以及错误帧处理等关键模块同时涵盖对象字典与网络管理逻辑能够清晰展现从站从初始化、参数配置到与主站状态交互的完整流程。配套Readme说明、Keil工程备份与HEX文件便于阅读和实验验证。目前已有484人学习下载可作为入门DeviceNet从站开发或课程设计的参考资料。1. ADμC812 跑 DeviceNet 从站难的不是协议是时序一个 ADμC812 单片机要同时做 12 位 ADC 采集、继电器控制和 DeviceNet 从站上报实际项目里最常见的故障是“平时能通讯一加采样就丢站”。标题里这个基于 ADμC812 的 DeviceNet 从站程序难的不是对象模型和协议状态翻译而是这枚 8051 核心的 RAM 只有 256 字节片内 Flash 只有 8KB却要挂在最大 500kbps 的 CAN 总线上每收到一帧 SJA1000 报文都要在几百微秒内读走否则后帧覆盖前帧。这个方案的典型使用场景是老产线改造用 ADμC812 把模拟量输入变成 PLC 可以直接轮询的 I/O 报文适合的读者是维护现场设备、又不想把整套控制系统换掉的工程师也适合想从零写 DeviceNet 从站、但手里只有一个老款 C51 开发板的入门者。2. DeviceNet 从站的协议骨架与 ADμC812 外设映射2.1 从站要实现的三个最小对象标识、连接和 I/ODeviceNet 从站程序不需要把协议栈里所有对象类都做出来主站扫描器上电后会先通过未连接报文找到从站接着读取标识对象确认设备身份再打开显式报文连接和 I/O 连接。所以最小可用的从站至少要提供三个对象类标识对象、连接对象和 I/O 对象否则连接建立不起来。实践中我会把对象表压缩成一张很小的静态结构每个对象只保留能响应的属性和允许的服务。下表是能通过主站扫描器离线验证的最小集合。对象类 ID实例最少实现内容0x01 标识对象1厂商 ID、设备类型、序列号、设备状态0x05 连接对象1传输类型、累计超时计数、目标连接 ID、源连接 ID0x04 I/O 对象1I/O 报文长度、输入输出方向、实例状态这几项在程序里只占几十个字节。用结构体维护即可不建议在 ADμC812 这种资源紧的单片机上引入完整 OD 管理表否则 Flash 和 RAM 都会告急。显式报文的属性读写直接针对对象类跟属性号做散转函数。2.2 用 MOVX 把 SJA1000 挂到外部总线别用 GPIO 模拟时序ADμC812 内部没有 CAN 控制器外挂 SJA1000 是这个标题下最常见的硬件方案。SJA1000 是并行接口 CAN 控制器接线方式通常是把 AD0 到 AD7 接到 ADμC812 的 P0 口ALE 接到单片机的 ALE片选由 P2 高地址译码产生。这样 CPU 的MOVX到外部 RAM 地址就等价于读写 SJA1000 寄存器比用 GPIO 模拟 SPI 挂 MCP2515 快很多也不容易受中断抖动影响。C51 工程里我一般会这样定义寄存器访问宏// C51 下 SJA1000 寄存器访问宏 #define SJA_CE_BASE 0x8000u // P2.7 片选对应的外部地址页 #define SJA_REG(reg) (*(volatile unsigned char xdata *)(SJA_CE_BASE | ((unsigned int)(reg) 1))) #define SJA_READ(reg) SJA_REG(reg) #define SJA_WRITE(reg, val) (SJA_REG(reg) (val))这里reg左移一位是为了让地址总线的 A0 与 SJA1000 的地址/数据复用线对齐。SJA1000 在 ALE 下降沿锁存地址而 8051 系列MOVX写外部地址时P0 先输出低 8 位地址正好满足 SJA1000 的锁存方向。SJA_CE_BASE根据实际片选连接调整电路上用 P2.7 则高地址是 0x8000用 P2.6 就改成 0x4000这个值在项目里不能错错一引脚整片控制器都访问不到。2.2.1 初始化 SJA1000 的复位序列上电后 SJA1000 处于 BasicCAN 模式必须先进入复位模式再写时钟分频器切到 PeliCAN 模式。下面的初始化顺序是固定的不能颠倒。void can_ctrl_init(void) { SJA_WRITE(REG_MOD, 0x09); // 复位模式 睡眠模式 SJA_WRITE(REG_CDR, 0x48); // 打开 PeliCAN关闭 CLKOUT SJA_WRITE(REG_ACR0, 0x00); // 验收代码先清 0 SJA_WRITE(REG_AMR0, 0x00); // 验收掩码先清 0表示全比较 SJA_WRITE(REG_MOD, 0x08); // 返回正常模式使能验收滤波器 }REG_CDR写入 0x48bit7 是 PeliCAN 模式使能bit6 关闭 CLKOUT 时钟输出避免片外高频信号干扰 ADμC812 的 ADC 和晶振。REG_MOD的 0x08 使能验收滤波器0x01 是复位位初始化完成后必须清掉否则后续所有寄存器配置都会被回收。2.3 片内 ADC 和 CAN 接收中断的优先级划分ADμC812 的卖点就是片内 12 位 ADC但它和 CAN 中断共用同一个 8051 中断体系。如果让 ADC 中断优先级高于外部中断 0那每次采样都会打断 SJA1000 的接收处理一旦接收缓冲区没有及时释放总线上的下一帧就会把当前帧覆盖。常见做法是把 ADC 转换结果读取放到主循环CAN 接收中断只做缓存。void adc_start_sample(void) { ADCCON1 | 0x80; // 启动一次 12 位转换 } void main_loop(void) { while (1) { if (ADCCON1 0x20) { // 转换结束标志 adc_value (ADCDATAH 4) | ADCDATAL; } dn_poll(); // 取 CAN 接收缓冲里的协议帧 } }ADCCON1的 bit7 是启动转换位bit5 是转换结束标志。主循环读取ADCDATAH和ADCDATAL后不需要额外清标志真正启动下一次采样时也会自动复位。这样设计之后CAN 接收中断只工作几十个周期ADC 的采样周期则由主循环时间决定后面第 4.3 节再展开讲参数。3. 从站程序的状态机从复位到进入 Operational3.1 上电后的重复 MAC ID 检查流程DeviceNet 从站不能上来就直接发 I/O 报文必须先确认自己的 MAC ID 在总线上不冲突。协议要求设备上电后发送一条重复 MAC ID 检查报文如果总线上的其他设备已经占用这个 MAC ID就会收到冲突响应此时从站进入错误状态不能参与通讯。这个流程在程序里可以压缩成一个短函数static uint8_t dn_mac_id; static uint8_t dn_state; void dn_start(uint8_t mac_id) { dn_mac_id mac_id; dn_state DN_STATE_FAULT; // 先进入错误检查通过才翻转 dn_send_dup_mac_id_check(); // 发送重复 MAC ID 检查报文 timer_delay_ms(150); // 等待可能存在的重复设备回应 if (!dn_dup_mac_id_conflict()) { dn_state DN_STATE_PREOPERATIONAL; } }前面把dn_state先置成DN_STATE_FAULT有个好处如果主站 PLC 在异常时序下提前扫到从站从站会因为状态不对而拒绝连接请求不会把自己的 MAC ID 重复暴露给总线。150ms 是一个保守值足够让同网段所有节点作出响应也不会让上电时间太长。dn_send_dup_mac_id_check()内部封装了 SJA1000 的发送缓冲操作发送完成后必须等待发送完成状态位这个等待可以放到发送函数里也可以放到这个函数后面的轮询中。3.2 SJA1000 验收滤波器设置DeviceNet 使用 CAN 扩展帧的 11 位标识符来区分报文类型从站只需要接收与自己 MAC ID 相关的组 2 报文。SJA1000 的验收滤波器可以放在硬件层面先过滤掉无关总线负载但我一般会先在调试阶段设成全通等协议解析正常后再锁过滤器。// 只接收本机 MAC ID 相关的组2报文并放行主站广播 void dn_set_accept_filter(uint8_t mac_id) { // 先用统一掩码放行所有 11 位标识符 SJA_WRITE(REG_ACR0, 0x00); SJA_WRITE(REG_AMR0, 0x00); // 实际工程把 mac_id 填入组2报文对应的位段 }REG_ACR0是验收代码置 1 的位要求接收帧的对应位必须与之相同REG_AMR0是验收掩码置 1 的位表示忽略、置 0 的位表示必须匹配。全 0 掩码意味着只接收与验收代码完全相同的标识符全 1 掩码则会放行所有报文。调试期建议先写全 1再用协议层去判断是不是发给本机的帧避免滤波器计算错误导致整机“聋掉”。3.3 接收中断只做“读走释放”两个动作CAN 中断服务程序里尽量只做两件事把接收缓冲区的一帧数据读进全局变量然后命令 SJA1000 释放该缓冲区。协议解析和响应构造全部推到主循环。void can_rx_isr(void) interrupt 0 { uint8_t irq_reg; irq_reg SJA_READ(REG_IRQ); if ((irq_reg 0x01) 0x01) { // bit0 是接收缓冲区状态位 dn_rx_frame SJA_READ(REG_RX_BUF); SJA_WRITE(REG_CMD, 0x04); // 发送“释放接收缓冲器”命令 dn_rx_flag 1; } }这里把dn_rx_frame声明成全局的接收帧结构体SJA1000 的接收缓冲区有独立的地址区读REG_RX_BUF时可以连续读出 13 字节。读完必须立即写REG_CMD的 bit2也就是命令字 0x04否则 SJA1000 会认为接收缓冲还没读完后续报文进不来。把解析放在主循环的dn_poll()中中断里就只需要做一次内存搬运这样即使 ADC 中断短暂打断外部中断也不至于把 CAN 接收缓存冲垮。4. 必调参数与三个坑波特率、超时、ADC 抖动4.1 DeviceNet 波特率与采样点参数表DeviceNet 标准波特率只有三种125kbps、250kbps、500kbps。ADμC812 没有内置 CAN波特率完全由外部 SJA1000 的 BTR0/BTR1 决定。下面这张参数表是我在 16MHz 外部晶振下爱用的一组采样点配置位时间固定分成 16 个时间量子。DeviceNet 波特率位时间TSEG1TSEG2采样点位置125kbps16 tq13 tq2 tq87.5%250kbps16 tq13 tq2 tq87.5%500kbps16 tq13 tq2 tq87.5%这个表里没有直接写 BTR0/BTR1 寄存器值因为 BTR0 的低 6 位是波特率预分频器它由外部晶振频率和位时间共同决定。同样 16MHz 晶振下要跑到 125kbps 和 500kbps 时预分频值不同但 TSEG1、TSEG2 和采样点保持一致就比较省心。手算容易错我一般会先用现成的 CAN 波特率计算工具算一次生成 BTR0/BTR1再对照数据手册检查一次采样点。DeviceNet 主站侧对采样点不强制要求完全一致但请尽量让从站采样点落在 75% 到 87.5% 之间否则长线上过冲噪声会把位边沿误判成数据。4.2 重复 MAC ID 超时和 UCMM 超时的计时要挂在定时器 T2DeviceNet 从站的超时有两类一类是重复 MAC ID 检查时的短暂等待另一类是显式报文连接建立后的响应超时。用while加空循环延时会让整个单片机在延时期间失去 CAN 响应能力因此工程上必须把时间基准放到定时器里。void timer2_isr(void) interrupt 5 { TF2 0; dn_tick_ms; }配合 T2CON 设置成自动重载模式每 1ms 进一次中断。协议栈里所有超时判断都读dn_tick_ms这个全局变量例如重复 MAC ID 检查等待 150ms就记录start_tick dn_tick_ms然后在主循环里判断差值。这样即使 CAN 中断和 ADC 都在跑状态机的时间推进也不会停顿。4.3 12 位 ADC 采样周期、校准与抖动ADμC812 的 ADC 是 12 位逐次逼近型单次转换时间大约几十微秒。如果把它放在中断里读关中断时间会被拉长到几十微秒刚好落在 CAN 帧位间隔内丢帧概率很高。所以采样要放在主循环并用定时器触发启动读结果时还需做滤波。#define ADC_FILTER_SIZE 8 void adc_filter(uint16_t newval) { static uint32_t sum; static uint8_t count; sum newval; if (count ADC_FILTER_SIZE) { adc_result sum / ADC_FILTER_SIZE; sum 0; count 0; } else { count; } }这里的滑动平均本质上是 8 次累加后取平均只用一个 32 位变量和 8 位计数变量。adc_result在每次满 8 次后更新一次输出数据就不会因为单个采样点异常而出现毛刺跳变。如果现场还有 50Hz 工频干扰可以把采样周期故意设成 20ms 的整数倍再做整周期平均效果比单纯加大滤波长度更直接。5. 用总线抓包核对从站上线顺序再谈栈与驱动分离5.1 SJA1000 环回模式先验证 CAN 控制器本身没有外部总线设备时可以先把 SJA1000 设成自测模式让它自己发自己收验证驱动层读写是否正确。自测模式不需要在总线上接任何节点也不会干扰同一网段的其他设备。SJA_WRITE(REG_MOD, 0x09); // 进入复位模式 SJA_WRITE(REG_MOD, 0x0C); // 自测模式 正常退出复位REG_MOD写入 0x0C 后SJA1000 会把发送缓冲区的帧自己收进接收缓冲区。如果中断服务程序能正常置dn_rx_flag就说明寄存器映射宏、中断向量和释放接收缓冲命令都是对的可以继续接总线调试。5.2 抓 CAN 波形时重点看三个报文顺序用逻辑分析仪接 CAN_H 和 CAN_L采样率放到至少 4 倍于总线波特率抓从站上电到主站扫描建立连接的全过程。125kbps 连接上应该能看到三类帧重复 MAC ID 检查帧、显式报文连接请求/响应帧、以及周期性的 I/O 轮询帧。如果抓到的总是错误帧先看 BTR0/BTR1 和实际晶振是否匹配如果只有重复 MAC ID 检查后没有后续报文说明从站没有正确响应主站的 UCMM 请求优先查对象表和连接对象里的属性值是否填对。5.3 移植到 STM32 时只换驱动层现在很多新项目不再用 ADμC812但保留同样的 DeviceNet 从站协议代码只把 SJA1000 相关驱动替换成 STM32F103 的 bxCAN 驱动。做法是把can_ctrl_init()、can_tx()、can_rx_isr()这三个接口抽象成dn_can_send()和dn_can_read()上层状态机完全不动。这样既保住了老单片机调试好的协议逻辑也让后续换主控时不需要重新做一遍 DeviceNet 报文时序验证。本文还有配套的精品资源点击获取
返回列表