由于 Cortex-M 栈未满足 8 字节对齐导致的问题与原因分析
# 由于 Cortex-M 栈未满足 8 字节对齐导致的问题与原因分析 @[toc] 嵌入式工程中,栈对齐通常不会被单独拿出来讨论。很多 Cortex-M 工程长期使用 4 字节对齐的内存访问,也能正常启动、跑任务、处理中断,因此“栈必须 8 字节对齐”看起来不像一个硬性问题。真正容易暴露问题的场景,往往出现在变参函数、64 位数据、浮点参数、启动阶段 C 代码、异常入口,以及手写汇编与编译器生成代码混用的边界上。 这篇文章从规定开始,解释为什么 Cortex-M 工程需要关心栈的 8 字节对齐;再说明不满足时可能出现什么现象;最后给出不同工具链脚本的查看方法和修改方法。 一、规定:公开接口处 SP 必须 8 字节对齐AAPCS32 是 Arm 32 位架构的过程调用标准。它规定函数之间如何传参、如何返回、哪些寄存器由调用者保存、哪些寄存器由被调用者保存,以及栈指针需要满足什么约束。 在 AAPCS32 中,栈有两层约束: 位置 要求 含义 任意时刻 SP mod 4 = 0 栈指针至少按字对齐 函数公开调用边界 SP mod 8 = 0 栈指针必须按双字对...
CANopen PDO 运行流程
CANopen PDO 运行流程 @[toc] PDO 不是“带 index/sub-index 的小 SDO”,也不是“谁发给谁的一问一答报文”。它更像一条预先约定好格式的实时数据通道:发送方把对象字典里的几个变量按固定顺序塞进一个 CAN 数据帧,接收方再按自己的 PDO 映射表把这些字节拆回本地对象字典变量。 理解 PDO 时要先把两个问题分开: 协议规定 PDO 应该是什么样。 包括它为什么只有过程数据、为什么要靠对象字典配置、为什么要区分 RPDO/TPDO、为什么 transmission type 会影响触发时机。 CANopenNode 源码怎样实现这些规定。 包括 CO_CANopenInitPDO() 如何初始化每个 PDO,CO_PDO_receive() 为什么只缓存,CO_RPDO_process() 为什么等到主循环才写 OD,CO_TPDO_process() 又为什么要同时看 SYNC、event timer、inhibit timer 和 sendRequest。 本文按这个顺序讲。前半部分先把协议模型讲清楚,后半部分...
CANopen TIME/SYNC 运行流程
CANopen TIME/SYNC 运行流程 @[toc] 1. 先把 TIME 和 SYNC 分开TIME 和 SYNC 都属于 CANopen 的特殊功能通信对象,但它们解决的问题不同: 对象 核心问题 默认 CAN-ID 数据长度 典型用途 SYNC “什么时候进入一个同步周期?” 0x080 0 或 1 同步 TPDO/RPDO、周期采样、周期控制 TIME “网络当前时间是多少?” 0x100 6 网络时间校准、时间戳、日志时间基准 一句话区分: 1SYNC 是节拍;TIME 是时钟。 SYNC 不携带实际过程数据,它只广播一个同步事件。同步 PDO 在这个事件前后按配置动作。TIME 也不携带过程数据,它只携带“当天毫秒数 + 自 1984-01-01 起的天数”。 2. 协议侧:SYNC 规定了什么2.1 SYNC 的角色SYNC 使用 producer/consumer 模型: 1234567flowchart LR P["SYNC producer<br/>周期发送同步对...
CANopen 搞懂 SDO client/server 运行流程
CANopen 搞懂 SDO client/server 运行流程 @[toc] 先给结论SDO(Service Data Object)是 CANopen 里用于读写对象字典的标准服务。它不是实时过程数据通道,也不是 Heartbeat 那类状态上报通道。它更像“标准化的配置/诊断访问入口”: SDO 永远由 client 发起。client 访问另一个节点的对象字典;拥有对象字典的一方是 server。 download / upload 是站在 server 角度命名。 SDO download:client 把数据写入 server 的对象字典。 SDO upload:client 从 server 的对象字典读取数据。 普通 CANopen 从机必须有 SDO server;SDO client 通常是可选的。一个普通 STM32 从机被主站配置,只需要 CO_SDOserver;只有当前节点要主动读写别的节点时,才需要 CO_SDOclient。 CANopenNode 的 SDO 实现是非阻塞状态机。CAN 接...
CANopene Heartbeat运行流程
CANopene Heartbeat运行流程 @[toc] 123456789101112131415flowchart LR subgraph PNode["远端 Heartbeat Producer<br/>Node-ID = n"] P_OD["OD 0x1017<br/>Producer heartbeat time<br/>配置发送周期"] P["Heartbeat producer<br/>周期 = producer time"] P_OD --> P end P -->|周期广播| Frame["CAN 数据帧<br/>COB-ID = 0x700 + n<br/>DLC = 1<br/>Byte0 = NMT state"] Frame -->|CAN 接收过滤| Cons["Heartbeat consu...
CANopen NMT:搞懂 NMT 运行流程
CANopen NMT:搞懂 NMT 运行流程 @[toc] 先给结论NMT 是 CANopen 的网络管理状态机控制协议,Heartbeat 是配套的节点在线与状态上报机制。在 CANopenNode 里,CO_NMT_Heartbeat.c/h 不是单纯“收一帧命令就切状态”的代码,而是把以下功能合在一个对象 CO_NMT_t 里: NMT slave / NMT consumer:接收 CAN-ID 0x000 的 NMT 命令,目标 Node-ID 为 0 或本节点时才处理。 Heartbeat producer:用 CAN-ID 0x700 + nodeId 周期发送本节点 NMT 状态;初始化阶段还会发 boot-up message。 NMT 状态机执行点:真正状态切换不在 CAN 接收回调里完成,而是在 CO_NMT_process() 周期调用中完成。 错误条件联动:可根据 CAN bus off、Heartbeat consumer 错误、Error register mask,把 Operational 自动降到 Pre-operation...
CANopen Emergency: 搞懂 EM 运行流程
CANopen Emergency: 搞懂 EM 运行流程 @[toc] 先给结论Emergency(下文简称 EMCY 或 EM)不是普通日志,也不是 PDO。它是 CANopen 用于故障事件上报的标准机制。CAN in Automation 对 EMCY 的公开说明包括:由设备内部错误触发、映射到单个 CAN Classic 帧、内容包含 0x1001 error register、16 位 emergency error code 和最多 5 字节制造商信息,默认 CAN-ID 为 0x80 + node-ID,且同一 error event 只发送一次。[^cia-emcy] 在 CANopenNode 的 301/CO_Emergency.c/h 中,运行链路可以压缩成: 1234567应用/协议栈检测到错误变化 -> CO_errorReport() / CO_errorReset() -> CO_error() 修改 errorStatusBits[] 并写入 FIFO -> CO_EM_process() 周期运行 ...
CiA 303_3 CANopenNode 如何计算 RUN ERROR 指示灯
按 LED 源码学习 CiA 303-3:CANopenNode 如何计算 RUN / ERROR 指示灯@[toc] 结论303/CO_LEDs.c/h 的核心不是“驱动 LED”,而是实现 CiA 303-3 CANopen indicators:把 CANopen 设备的通信相关状态,转换成标准的 绿色 RUN LED 与 红色 ERROR LED 指示模式。 本模块的输入来自 CANopen 状态与错误上下文,例如 NMT 状态、LSS 配置状态、CAN bus-off、CAN warning、Heartbeat consumer 错误、SYNC 超时、RPDO event timer 超时、其他错误、固件下载状态等;输出是 LEDred / LEDgreen bitfield 中的 CO_LED_CANopen 位。 本文只讨论 CANopenNode 经典 CANopen 的: 303/CO_LEDs.h 303/CO_LEDs.c NMT、LSS、Heartbeat consumer、SYNC、PDO、CAN bus-off 只作为 LE...
CANopenNode LSS运行流程
从源码看 CANopenNode LSS:搞懂主从机运行流程 @[toc] 先给结论LSS(Layer Setting Services)不是用来传过程数据的,也不是普通 SDO 配置。它解决的是设备还没有有效 Node-ID,或者需要统一切换 CAN 位率时,主站如何在 CAN 总线上找到并配置从站的问题。 在 CANopenNode 的 305 目录中,LSS 的运行核心可以压缩成三句话: 主站只通过 0x7E5 发命令,从站只通过 0x7E4 回响应。 这是 LSS 固定使用的两条 CAN 帧通道。 从站通过 0x1018 身份对象形成 128 bit LSS Address。 主站可以已知地址精准选择,也可以对未配置节点做 Fastscan。 Node-ID 和 bit rate 先进入 pending 值。 Node-ID 通常要经过保存和通信复位后才真正成为 CANopen 栈运行用的 ID;bit rate 的激活更危险,必须保证全网节点同步切换。 1. LSS 解决的工程问题在普通 CANopen 网络中,很多通信对象都依赖 Node-ID。例如心跳常见为...
CANopenNode `CO_new()` 与 `CO_config
CANopenNode CO_new() 与 CO_config.h 宏机制详解@[toc] CO_new() 不是一个单纯的 malloc() 包装函数。它是 CANopenNode 把“协议栈功能选择”落到“对象实例、内存占用、CAN 接收过滤槽位、CAN 发送缓冲槽位”的集中入口。 换句话说,CO_config.h 里的宏回答的是:这个协议能力是否参与编译、是否保留对应代码路径、是否启用某些回调或动态配置特性。CO_new() 回答的是:在当前对象字典或 CO_config_t 配置下,实际要创建几个对象、每个对象占多少内存、每类 CAN 报文在 CANrx[] 和 CANtx[] 中从哪个下标开始。 CO_new() 的核心任务不是协议通信,而是对象装配从代码结构看,CO_new() 可以分为四层:参数检查、对象分配、CAN 报文槽位统计、最终对象返回。 第一层是配置合法性检查。只有在 CO_MULTIPLE_OD 被定义时,函数才会把 config 视为有效配置来源,并检查 CNT_NMT、CNT_HB_CONS、CNT_EM、CNT_SDO_SRV、CNT_SD...







