91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

【睿擎派】CANOpen總線之IO模塊讀寫(DS401協(xié)議)

RT-Thread官方賬號 ? 2025-12-24 18:01 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

睿擎派以瑞芯微 RK3506 為主控芯片,底層搭載 RT-Thread 操作系統(tǒng),基于專為工業(yè)場景打造的睿擎工業(yè)平臺進行開發(fā)。該平臺是全棧自主可控的軟硬件一體化解決方案,整合了數(shù)據(jù)采集、通信、控制、工業(yè)協(xié)議、AI、顯示六大核心功能,精準適配工業(yè)應用需求。


官方僅提供了基于 CANOpen 協(xié)議(即 DS402 設備規(guī)范)操作伺服電機的示例代碼,暫無 IO 模塊相關的操作參考文檔與實踐案例。經(jīng)過數(shù)日的深入鉆研與反復調(diào)試,最終成功實現(xiàn)雷賽 EM32DX-C4 模塊的 IO 信號采集與輸出控制功能。


下面將簡要分享這段時間積累的 CANOpen 相關技術要點,以及代碼編寫與調(diào)試的具體實踐過程。


一、CANOpen背景知識介紹

CAN 總線于 1986 年 2 月正式發(fā)布,CANOpen 協(xié)議則在 1994 年 11 月推出。作為基于 CAN 總線的工業(yè)級通信協(xié)議,CANOpen 遵循 EN 50325-4 標準,是工業(yè)自動化領域主流的現(xiàn)場總線解決方案之一。


其核心優(yōu)勢體現(xiàn)在標準化設計上 —— 通過統(tǒng)一的對象字典保障設備間互操作性,支持 PDO(過程數(shù)據(jù)對象)、SDO(服務數(shù)據(jù)對象)等靈活通信機制,兼顧實時性與數(shù)據(jù)完整性。協(xié)議內(nèi)置 DS401(IO 模塊)、DS402(運動控制)等專用行規(guī),可適配伺服電機、IO 模塊、傳感器等各類工業(yè)設備。


我們自行生產(chǎn)的網(wǎng)關產(chǎn)品很早就集成了 CAN 總線功能,但僅用于與自有 IO 模塊的實時通信,較少對接第三方模塊。目前國內(nèi)主流智能模塊仍以 RS485 通信為主,我雖早已知曉 CANOpen 協(xié)議,但實際應用機會不多。隨著計劃基于睿賽德睿擎工業(yè)平臺開發(fā)新一代網(wǎng)關產(chǎn)品,各類現(xiàn)場工業(yè)總線均需深入研究。


CANOpen 協(xié)議相對復雜,核心原因是它要求設備遵循嚴格的狀態(tài)機模式,主要包含四大核心狀態(tài):

(1) 初始化狀態(tài)(Initialization)

設備剛上電,正在進行硬件自檢和協(xié)議棧初始化

不參與網(wǎng)絡通信,不接收任何命令 (除基本復位)

完成后自動進入 Pre-operational 狀態(tài),并發(fā)送一個啟動心跳信號

(2) 預操作狀態(tài)(Pre-operationa)

設備已初始化完成,等待配置

可接收 SDO (服務數(shù)據(jù)對象),進行參數(shù)配置和診斷

PDO (過程數(shù)據(jù)對象) 通信被禁用,無法進行正常數(shù)據(jù)交換

是唯一可修改對象字典的狀態(tài),適合設備參數(shù)配置

(3) 操作狀態(tài)(Operational)

設備正常工作狀態(tài),所有功能激活

PDO 和 SDO 通信全部啟用,可收發(fā)過程數(shù)據(jù)

設備自主執(zhí)行任務,響應網(wǎng)絡請求

由 NMT 主機發(fā)送 “Start Node” 命令觸發(fā)進入

(4) 停止狀態(tài)(Stopped)

設備的安全狀態(tài),功能受到限制。

PDO 通信被完全禁用,僅允許 NMT 命令和心跳

設備保持配置,但不執(zhí)行控制任務

由 NMT 主機發(fā)送 “Stop Node” 命令觸發(fā),常用于安全暫停

75fa02f6-e0af-11f0-8ce9-92fbcf53809c.png


注:對伺服運動設備(DS402),還額外定義了電源相關狀態(tài),比如電源禁用區(qū),電源啟用區(qū),故障區(qū)等相關定義。


CANOpen 協(xié)議的四大核心狀態(tài)中,嵌套了三種核心通信模型,具體如下:

(1) 主 / 從站模型:與 Modbus 協(xié)議邏輯類似,核心差異是支持多主機架構,最多可接入 127 個從站,主要用于網(wǎng)絡診斷和設備狀態(tài)管理。

(2) 客戶端 / 服務器模型:借鑒 TCP/IP 協(xié)議的交互模式,專門適配對象字典(OD)的讀寫操作。從設備作為服務器提供參數(shù)訪問服務,主設備作為客戶端發(fā)起讀寫請求。

(3) 生產(chǎn)者 / 消費者模型:與 MQTT 協(xié)議的通信邏輯一致,從設備擔任數(shù)據(jù)生產(chǎn)者,主設備擔任數(shù)據(jù)消費者。生產(chǎn)者可按主設備的明確請求(拉模型),或主動無請求推送(推模型),傳輸預設的目標數(shù)據(jù)。


了解了以上知識后,還需要了解CANOpen協(xié)議的如下相關概念

(1) 對象字典

對象字典的功能類似 Modbus 協(xié)議中的寄存器,是定義 CANOpen 節(jié)點所有行為、參數(shù)及通信規(guī)則的核心。與 Modbus 寄存器不同,它通過 “16 位索引 + 8 位子索引” 的雙標識方式,唯一確定每個條目。

對象字典分為公共對象字典和私有對象字典。其中 DS301 協(xié)議作為 CANOpen 的基礎通用協(xié)議,明確了所有 CANOpen 設備必須遵循的公共對象字典規(guī)范。

760b3ca6-e0af-11f0-8ce9-92fbcf53809c.png


針對我們對接的EM32DX-C4查看設備手冊,DS301對應的數(shù)據(jù)字典的通用參數(shù)如下:

76133578-e0af-11f0-8ce9-92fbcf53809c.png


設備參數(shù)如下:

7624d846-e0af-11f0-8ce9-92fbcf53809c.png


DS401屬于子協(xié)議,專門定義數(shù)字量 / 模擬量 IO 的采集、控制、診斷等特有功能,索引范圍集中在 0x6000-0x77FF,核心條目按 “數(shù)字量 IO”“模擬量 IO”“診斷” 分類


76321a74-e0af-11f0-8ce9-92fbcf53809c.png


查EM32DX-C4設備手冊 DS401協(xié)議對應的對象字典參數(shù)如下:

7644997e-e0af-11f0-8ce9-92fbcf53809c.png


(2) COB-ID

COB-ID其實就是11位CAN ID,它分兩部分組成,高4位為功能碼,低7位為從設備地址碼,所以最多支持127個從設備。

764cfb32-e0af-11f0-8ce9-92fbcf53809c.png


功能碼和具體的通信服務相關(如下圖所示):

765bc0b8-e0af-11f0-8ce9-92fbcf53809c.jpg


(3) 網(wǎng)絡管理(NMT)

NMT服務用于通過NMT命令(如:啟動、停止、復位)來控制CANopen設備的狀態(tài)(如:預運行、運行、停止)。

為了改變狀態(tài),NMT主機發(fā)送一個帶有 CAN ID 的2字節(jié)消息(即功能代碼和節(jié)點ID )。所有從站節(jié)點都處理這個報文。節(jié)點ID 0表示廣播命令。


766aa2b8-e0af-11f0-8ce9-92fbcf53809c.png


功能代碼如下:

766aa2b8-e0af-11f0-8ce9-92fbcf53809c.png


(4) 服務數(shù)據(jù)對象(SDO)

SDO(Server Data Object,服務數(shù)據(jù)對象)的核心功能是訪問或修改 CANopen 設備對象字典內(nèi)的參數(shù)值。例如,當應用主站需調(diào)整 CANopen 設備的特定配置參數(shù)時,可借助 SDO 服務完成參數(shù)的讀寫操作,實現(xiàn)設備配置的靈活變更。

(5) 過程數(shù)據(jù)對象(PDO)

PDO(Process Data Object,過程數(shù)據(jù)對象) 是專為設備間實時、高速數(shù)據(jù)傳輸設計的核心通信服務,是工業(yè)場景中過程數(shù)據(jù)交互的關鍵通道。

PDO數(shù)據(jù)上傳有四種方式觸發(fā):

定時發(fā)送,同步傳輸(同步信號觸發(fā)),遠程請求和事件觸發(fā)。

(6) 心跳信號(Heartbeat)

CANopen 的心跳服務具有雙重核心目的:一是向網(wǎng)絡發(fā)送 “設備在線” 的活動消息,二是確認 NMT 命令的執(zhí)行狀態(tài)。NMT 從設備會按預設周期(例如 200 毫秒)發(fā)送心跳消息,消息 CAN ID 遵循固定規(guī)則(如節(jié)點 2 的 CAN ID 為 0x702),其第一個數(shù)據(jù)字節(jié)攜帶節(jié)點當前的 NMT 狀態(tài)碼(如下圖)。若心跳消息的接收方(如NMT主站)在設定時限內(nèi)未收到該消息,將觸發(fā)預設的離線響應機制。

76897936-e0af-11f0-8ce9-92fbcf53809c.png

(7) 同步(SYNC)

CANopen 的 SYNC 報文核心作用是同步多個從設備的輸入采集與輸出響應,通常由應用主站發(fā)起。主站向 CANopen 網(wǎng)絡發(fā)送 SYNC 消息(COB-ID 為 0x080),支持帶或不帶 SYNC 計數(shù)器兩種傳輸形式。多個從節(jié)點可預先配置為響應 SYNC 信號,要么同步捕獲輸入數(shù)據(jù)并傳輸,要么與其他參與同步的節(jié)點協(xié)同設置輸出,確保動作一致性。

SYNC 計數(shù)器的存在可靈活劃分同步組,實現(xiàn)多組設備的獨立同步操作,適配不同場景下的協(xié)同需求。

(8) 緊急情況(EMCY)

CANopen 的緊急服務(EMCY)專為設備發(fā)生致命錯誤(如傳感器故障)設計,用于向網(wǎng)絡其他節(jié)點及時上報故障狀態(tài)。受影響的節(jié)點會以高優(yōu)先級、單次觸發(fā)式向網(wǎng)絡發(fā)送 EMCY 消息(例如節(jié)點 2 的消息 COB-ID 為 0x082)。消息的數(shù)據(jù)字節(jié)攜帶具體錯誤碼及相關輔助信息,通過查詢設備手冊或協(xié)議規(guī)范,可獲取對應的故障詳情。

(9) 時間戳(Timestamp)

該消息由主站發(fā)起,對應的 CAN ID 為 0x100。使用 6 字節(jié) (48 位)表示。前 4 個字節(jié) (32 位): 表示從午夜開始的毫秒數(shù)(范圍: 0-4294967295 毫秒,約 1193 小時)。后 2 個字節(jié) (16 位): 表示自 1984 年 1 月 1 日 0 時起的天數(shù)(范圍: 0-65535 天,約 179.4 年) 。


二、CANOpen DS401協(xié)議實現(xiàn)


官方示例(06_bus_canopen_master_motor)在免費開源的CanFestival(LGPLv2 許可證)的基礎上實現(xiàn)的。該開源代碼實現(xiàn)了CANOpen協(xié)議如下功能:

(1)NMT(網(wǎng)絡管理):節(jié)點狀態(tài)控制(初始化、預操作、操作、停止)和心跳監(jiān)測

(2)PDO(過程數(shù)據(jù)對象):高速實時數(shù)據(jù)傳輸,支持循環(huán)和事件觸發(fā)模式,優(yōu)化工業(yè)控制場景響應速度

(3)SDO(服務數(shù)據(jù)對象):對象字典參數(shù)訪問,支持快速下載和分段下載,用于設備配置和參數(shù)調(diào)整

(4)SYNC(同步對象):網(wǎng)絡時鐘同步和周期性數(shù)據(jù)傳輸協(xié)調(diào)

(5)EMCY(緊急對象):錯誤報告和故障通知機制


769924f8-e0af-11f0-8ce9-92fbcf53809c.jpg


我是在官方示例06_bus_canopen_master_motor的基礎上進行大幅度修改而完成。除了canopen_callback.*相關內(nèi)容沒有多少變化外,其他文件改動比較大。

講解代碼之前,先簡單說一下硬件接線。查EM32DX-C4手冊,CANOpen接口采用了以太網(wǎng)的接口,管腳定義如下:

76a10880-e0af-11f0-8ce9-92fbcf53809c.jpg


根據(jù)這個定義,自己做了一個CAN網(wǎng)絡連接線,主要用到1,2兩根線,對應的網(wǎng)線是1-白橙和2-橙色。白橙也就是CAN_P接入睿擎派的CAN_H接口,橙色接入睿擎派的CAN_L接口。


76b377f4-e0af-11f0-8ce9-92fbcf53809c.jpg


由于我對 CANOpen 協(xié)議的了解不夠深入,且是初次接觸塞雷 EM32DX-C4 硬件模塊,初期的調(diào)試工作遇到了不少阻礙。好在手頭恰好有 PCAN-USB 模塊,將其接入 CAN 總線后,我通過 PCAN-View 工具實時監(jiān)聽 CAN 幀數(shù)據(jù),這一操作直接顯著提升了開發(fā)與調(diào)試的效率(如下圖)。


76c43a94-e0af-11f0-8ce9-92fbcf53809c.png


master402_od.c改名為master401_od.c

主要是DS301和DS401對象字典定義的地方。對原有的數(shù)據(jù)字典進行了大幅度的刪減。

原有的對象字典定義:

const indextable master402_objdict[] ={ { (subindex*)master402_Index1000,sizeof(master402_Index1000)/sizeof(master402_Index1000[0]), 0x1000}, { (subindex*)master402_Index1001,sizeof(master402_Index1001)/sizeof(master402_Index1001[0]), 0x1001}, { (subindex*)master402_Index1005,sizeof(master402_Index1005)/sizeof(master402_Index1005[0]), 0x1005}, { (subindex*)master402_Index1006,sizeof(master402_Index1006)/sizeof(master402_Index1006[0]), 0x1006}, { (subindex*)master402_Index1014,sizeof(master402_Index1014)/sizeof(master402_Index1014[0]), 0x1014}, { (subindex*)master402_Index1016,sizeof(master402_Index1016)/sizeof(master402_Index1016[0]), 0x1016}, { (subindex*)master402_Index1017,sizeof(master402_Index1017)/sizeof(master402_Index1017[0]), 0x1017}, { (subindex*)master402_Index1018,sizeof(master402_Index1018)/sizeof(master402_Index1018[0]), 0x1018}, { (subindex*)master402_Index1200,sizeof(master402_Index1200)/sizeof(master402_Index1200[0]), 0x1200}, { (subindex*)master402_Index1280,sizeof(master402_Index1280)/sizeof(master402_Index1280[0]), 0x1280}, { (subindex*)master402_Index1281,sizeof(master402_Index1281)/sizeof(master402_Index1281[0]), 0x1281}, { (subindex*)master402_Index1400,sizeof(master402_Index1400)/sizeof(master402_Index1400[0]), 0x1400}, { (subindex*)master402_Index1401,sizeof(master402_Index1401)/sizeof(master402_Index1401[0]), 0x1401}, { (subindex*)master402_Index1402,sizeof(master402_Index1402)/sizeof(master402_Index1402[0]), 0x1402}, { (subindex*)master402_Index1403,sizeof(master402_Index1403)/sizeof(master402_Index1403[0]), 0x1403}, { (subindex*)master402_Index1600,sizeof(master402_Index1600)/sizeof(master402_Index1600[0]), 0x1600}, { (subindex*)master402_Index1601,sizeof(master402_Index1601)/sizeof(master402_Index1601[0]), 0x1601}, { (subindex*)master402_Index1602,sizeof(master402_Index1602)/sizeof(master402_Index1602[0]), 0x1602}, { (subindex*)master402_Index1603,sizeof(master402_Index1603)/sizeof(master402_Index1603[0]), 0x1603}, { (subindex*)master402_Index1800,sizeof(master402_Index1800)/sizeof(master402_Index1800[0]), 0x1800}, { (subindex*)master402_Index1801,sizeof(master402_Index1801)/sizeof(master402_Index1801[0]), 0x1801}, { (subindex*)master402_Index1802,sizeof(master402_Index1802)/sizeof(master402_Index1802[0]), 0x1802}, { (subindex*)master402_Index1803,sizeof(master402_Index1803)/sizeof(master402_Index1803[0]), 0x1803}, { (subindex*)master402_Index1A00,sizeof(master402_Index1A00)/sizeof(master402_Index1A00[0]), 0x1A00}, { (subindex*)master402_Index1A01,sizeof(master402_Index1A01)/sizeof(master402_Index1A01[0]), 0x1A01}, { (subindex*)master402_Index1A02,sizeof(master402_Index1A02)/sizeof(master402_Index1A02[0]), 0x1A02}, { (subindex*)master402_Index1A03,sizeof(master402_Index1A03)/sizeof(master402_Index1A03[0]), 0x1A03}, { (subindex*)master402_Index2001,sizeof(master402_Index2001)/sizeof(master402_Index2001[0]), 0x2001}, { (subindex*)master402_Index2002,sizeof(master402_Index2002)/sizeof(master402_Index2002[0]), 0x2002}, { (subindex*)master402_Index2003,sizeof(master402_Index2003)/sizeof(master402_Index2003[0]), 0x2003}, { (subindex*)master402_Index2004,sizeof(master402_Index2004)/sizeof(master402_Index2004[0]), 0x2004}, { (subindex*)master402_Index2005,sizeof(master402_Index2005)/sizeof(master402_Index2005[0]), 0x2005}, { (subindex*)master402_Index2006,sizeof(master402_Index2006)/sizeof(master402_Index2006[0]), 0x2006}, { (subindex*)master402_Index2007,sizeof(master402_Index2007)/sizeof(master402_Index2007[0]), 0x2007}, { (subindex*)master402_Index2124,sizeof(master402_Index2124)/sizeof(master402_Index2124[0]), 0x2124}, { (subindex*)master402_Index2F00,sizeof(master402_Index2F00)/sizeof(master402_Index2F00[0]), 0x2F00}, { (subindex*)master402_Index2F01,sizeof(master402_Index2F01)/sizeof(master402_Index2F01[0]), 0x2F01}, { (subindex*)master402_Index6040,sizeof(master402_Index6040)/sizeof(master402_Index6040[0]), 0x6040}, { (subindex*)master402_Index6041,sizeof(master402_Index6041)/sizeof(master402_Index6041[0]), 0x6041}, { (subindex*)master402_Index6060,sizeof(master402_Index6060)/sizeof(master402_Index6060[0]), 0x6060}, { (subindex*)master402_Index6064,sizeof(master402_Index6064)/sizeof(master402_Index6064[0]), 0x6064}, { (subindex*)master402_Index606C,sizeof(master402_Index606C)/sizeof(master402_Index606C[0]), 0x606C}, { (subindex*)master402_Index607A,sizeof(master402_Index607A)/sizeof(master402_Index607A[0]), 0x607A}, { (subindex*)master402_Index607C,sizeof(master402_Index607C)/sizeof(master402_Index607C[0]), 0x607C}, { (subindex*)master402_Index6081,sizeof(master402_Index6081)/sizeof(master402_Index6081[0]), 0x6081}, { (subindex*)master402_Index6098,sizeof(master402_Index6098)/sizeof(master402_Index6098[0]), 0x6098}, { (subindex*)master402_Index6099,sizeof(master402_Index6099)/sizeof(master402_Index6099[0]), 0x6099}, { (subindex*)master402_Index60C1,sizeof(master402_Index60C1)/sizeof(master402_Index60C1[0]), 0x60C1}, { (subindex*)master402_Index60C2,sizeof(master402_Index60C2)/sizeof(master402_Index60C2[0]), 0x60C2}, { (subindex*)master402_Index60FF,sizeof(master402_Index60FF)/sizeof(master402_Index60FF[0]), 0x60FF},};

刪減后的對象字典定義:

const indextable master401_objdict[] ={ { (subindex*)master401_Index1000,sizeof(master401_Index1000)/sizeof(master401_Index1000[0]), 0x1000}, { (subindex*)master401_Index1001,sizeof(master401_Index1001)/sizeof(master401_Index1001[0]), 0x1001}, { (subindex*)master401_Index1005,sizeof(master401_Index1005)/sizeof(master401_Index1005[0]), 0x1005}, { (subindex*)master401_Index1006,sizeof(master401_Index1006)/sizeof(master401_Index1006[0]), 0x1006}, { (subindex*)master401_Index1014,sizeof(master401_Index1014)/sizeof(master401_Index1014[0]), 0x1014}, { (subindex*)master401_Index1016,sizeof(master401_Index1016)/sizeof(master401_Index1016[0]), 0x1016}, { (subindex*)master401_Index1017,sizeof(master401_Index1017)/sizeof(master401_Index1017[0]), 0x1017}, { (subindex*)master401_Index1018,sizeof(master401_Index1018)/sizeof(master401_Index1018[0]), 0x1018}, { (subindex*)master401_Index1200,sizeof(master401_Index1200)/sizeof(master401_Index1200[0]), 0x1200}, { (subindex*)master401_Index1280,sizeof(master401_Index1280)/sizeof(master401_Index1280[0]), 0x1280}, { (subindex*)master401_Index1400,sizeof(master401_Index1400)/sizeof(master401_Index1400[0]), 0x1400}, { (subindex*)master401_Index1600,sizeof(master401_Index1600)/sizeof(master401_Index1600[0]), 0x1600}, { (subindex*)master401_Index1800,sizeof(master401_Index1800)/sizeof(master401_Index1800[0]), 0x1800}, { (subindex*)master401_Index1A00,sizeof(master401_Index1A00)/sizeof(master401_Index1A00[0]), 0x1A00}, { (subindex*)master401_Index2000,sizeof(master401_Index2000)/sizeof(master401_Index2000[0]), 0x2000}, { (subindex*)master401_Index2001,sizeof(master401_Index2001)/sizeof(master401_Index2001[0]), 0x2001},};

相比原有代碼,增加了DO和DI相關的對象字典的定義:

/* -------------------------- 0x2000 本地DO輸出緩存 -------------------------- */// 子索引0:最高子索引編號(=1,因為有2個子索引:0和1)// 子索引1:實際DO數(shù)據(jù)存儲(uint16,RW)UNS8 master401_highestSubIndex_obj2000 =1; /* 最高子索引編號 = 子索引數(shù)量-1 */uint16_tmaster401_obj2000_do_val =0x0000; /* DO數(shù)據(jù)存儲變量(關聯(lián)g_em32dx_do)*/subindex master401_Index2000[] ={ // 子索引0:聲明最高子索引編號(RO,不可寫) { RO, uint8,sizeof(UNS8), (void*)&master401_highestSubIndex_obj2000,NULL}, // 子索引1:實際DO數(shù)據(jù)(RW,uint16) { RW, uint16,sizeof(uint16_t), (void*)&master401_obj2000_do_val,NULL}};/* -------------------------- 0x2001 本地DI輸入緩存 -------------------------- */// 子索引0:最高子索引編號(=1)// 子索引1:實際DI數(shù)據(jù)存儲(uint16,RO)UNS8 master401_highestSubIndex_obj2001 =1; /* 最高子索引編號 = 子索引數(shù)量-1 */uint16_tmaster401_obj2001_di_val =0x0000; /* DI數(shù)據(jù)存儲變量(關聯(lián)g_em32dx_di)*/subindex master401_Index2001[] ={ // 子索引0:聲明最高子索引編號(RO,不可寫) { RO, uint8,sizeof(UNS8), (void*)&master401_highestSubIndex_obj2001,NULL}, // 子索引1:實際DI數(shù)據(jù)(RO,uint16,協(xié)議棧自動更新) { RO, uint16,sizeof(uint16_t), (void*)&master401_obj2001_di_val,NULL}};

需要特別注意的是,master401_od.c中定義的對象字典僅適用于主設備 —— 這是我初期的核心困惑點,曾誤以為主設備無需額外定義對象字典。且主設備對象字典中0x1400、0x1800 索引的含義,與從設備對應索引的描述恰好相反:具體來說,主設備的 TPDO1(發(fā)送過程數(shù)據(jù)對象 1)對應從設備的 RPDO1(接收過程數(shù)據(jù)對象 1),而主設備的 RPDO1 則對應從設備的 TPDO1。


文件調(diào)整方面:已移除motor_control.c與motor_control.h文件,并將原文件中的相關 IO 操作整合至master401_canopen.c中;同時將原master402_canopen.c文件重命名為master401_canopen.c,且對文件內(nèi)大部分核心代碼進行了適配性修改。


從設備 IO 模塊的對象字典配置,均在該文件中完成實現(xiàn),具體代碼如下:

/************************** 核心修改:IO模塊PDO映射配置 **************************/// 說明:// - 從站(EM32DX-C4)接收DO輸出:RPDO1(0x1400)映射DO0-DO15(2字節(jié))// - 從站(EM32DX-C4)發(fā)送DI輸入:TPDO1(0x1800)映射DI0-DI15(2字節(jié))// - 復用原PDO通道,刪除伺服相關映射/* TPDO1配置(從站→主站:DI輸入)*/staticUNS8IO_DIS_SLAVE_TPDO1(uint8_tnodeId){ rt_kprintf("config...0!\n"); UNS32 TPDO_COBId =PDO_DISANBLE(0x00000180, nodeId); // COB-ID: 0x182(IO_NODEID=2) returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1800,1,4, uint32, &TPDO_COBId, config_node_param_cb,0);}staticUNS8IO_Write_SLAVE_TPDO1_Type(uint8_tnodeId){ rt_kprintf("config...1!\n"); UNS8 trans_type = PDO_TRANSMISSION_TYPE; // 同步傳輸 returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1800,2,1, uint8, &trans_type, config_node_param_cb,0);}staticUNS8IO_Clear_SLAVE_TPDO1_Cnt(uint8_tnodeId){ rt_kprintf("config...2!\n"); UNS8 pdo_map_cnt =0; // 清除原有映射 returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1A00,0,1, uint8, &pdo_map_cnt, config_node_param_cb,0);}staticUNS8IO_Write_SLAVE_TPDO1_Map(uint8_tnodeId){ rt_kprintf("config...3!\n"); // TPDO1映射:DI0-DI15(模塊DI對應索引0x6100,子索引0x01,2字節(jié)) UNS32 pdo_map_val =0x61000110; // 索引0x6100 + 子索引0x01 + 16位長度(0x10) returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1A00,1,4, uint32, &pdo_map_val, config_node_param_cb,0);}staticUNS8IO_Write_SLAVE_TPDO1_Cnt(uint8_tnodeId){ rt_kprintf("config...4!\n"); UNS8 pdo_map_cnt =1; // 1個映射項(2字節(jié)) returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1A00,0,1, uint8, &pdo_map_cnt, config_node_param_cb,0);}staticUNS8IO_EN_SLAVE_TPDO1(uint8_tnodeId){ rt_kprintf("config...5!\n"); UNS32 TPDO_COBId =PDO_ENANBLE(0x00000180, nodeId); returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1800,1,4, uint32, &TPDO_COBId, config_node_param_cb,0);}//-----------------------------------------------------------///* RPDO1配置(主站→從站:DO輸出)*/staticUNS8IO_DIS_SLAVE_RPDO1(uint8_tnodeId){ rt_kprintf("config...6!\n"); UNS32 RPDO_COBId =PDO_DISANBLE(0x00000200, nodeId); // COB-ID: 0x202(IO_NODEID=2) returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1400,1,4, uint32, &RPDO_COBId, config_node_param_cb,0);}staticUNS8IO_Write_SLAVE_RPDO1_Type(uint8_tnodeId){ rt_kprintf("config...7!\n"); UNS8 trans_type = PDO_TRANSMISSION_TYPE; // 同步傳輸 returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1400,2,1, uint8, &trans_type, config_node_param_cb,0);}staticUNS8IO_Clear_SLAVE_RPDO1_Cnt(uint8_tnodeId){ rt_kprintf("config...8!\n"); UNS8 pdo_map_cnt =0; // 清除原有映射 returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1600,0,1, uint8, &pdo_map_cnt, config_node_param_cb,0);}staticUNS8IO_Write_SLAVE_RPDO1_Map(uint8_tnodeId){ rt_kprintf("config...9!\n"); // RPDO1映射:DO0-DO15(模塊DO對應索引0x6300,子索引0x01,2字節(jié)) UNS32 pdo_map_val =0x63000110; // 索引0x6300 + 子索引0x01 + 16位長度(0x10) returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1600,1,4, uint32, &pdo_map_val, config_node_param_cb,0);}staticUNS8IO_Write_SLAVE_RPDO1_Cnt(uint8_tnodeId){ rt_kprintf("config...10!\n"); UNS8 pdo_map_cnt =1; // 1個映射項(2字節(jié)) returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1600,0,1, uint8, &pdo_map_cnt, config_node_param_cb,0);}staticUNS8IO_EN_SLAVE_RPDO1(uint8_tnodeId){ rt_kprintf("config...11!\n"); UNS32 RPDO_COBId =PDO_ENANBLE(0x00000200, nodeId); returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1400,1,4, uint32, &RPDO_COBId, config_node_param_cb,0);}//-----------------------------------------------------------///* 心跳配置(IO模塊生產(chǎn)者心跳)*/staticUNS8IO_Write_SLAVE_Heartbeat(uint8_tnodeId){ rt_kprintf("config...12!\n"); UNS16 producer_heartbeat_time = PRODUCER_HEARTBEAT_TIME; returnwriteNetworkDictCallBack(OD_Data, nodeId,0x1017,0,2, uint16, &producer_heartbeat_time, config_node_param_cb,0);}/* 配置完成回調(diào) */staticUNS8IO_Config_Done(uint8_tnodeId){ rt_kprintf("config...13!\n"); node_config_state *conf = &slave_conf; rt_sem_release(&(conf->finish_sem)); return0;}// IO模塊配置函數(shù)指針數(shù)組(按順序執(zhí)行)staticUNS8(*IOCFG_Operation[])(uint8_tnodeId)= { // TPDO1(DI輸入)配置(6步) IO_DIS_SLAVE_TPDO1, // 步驟0:禁用TPDO1 IO_Write_SLAVE_TPDO1_Type,// 步驟1:寫TPDO1傳輸類型 IO_Clear_SLAVE_TPDO1_Cnt, // 步驟2:清除TPDO1映射數(shù) IO_Write_SLAVE_TPDO1_Map, // 步驟3:寫TPDO1映射 IO_Write_SLAVE_TPDO1_Cnt, // 步驟4:設置TPDO1映射數(shù) IO_EN_SLAVE_TPDO1, // 步驟5:啟用TPDO1 // RPDO1(DO輸出)配置(6步) IO_DIS_SLAVE_RPDO1, // 步驟6:禁用RPDO1 IO_Write_SLAVE_RPDO1_Type,// 步驟7:寫RPDO1傳輸類型 IO_Clear_SLAVE_RPDO1_Cnt, // 步驟8:清除RPDO1映射數(shù) IO_Write_SLAVE_RPDO1_Map, // 步驟9:寫RPDO1映射 IO_Write_SLAVE_RPDO1_Cnt, // 步驟10:設置RPDO1映射數(shù) IO_EN_SLAVE_RPDO1, // 步驟11:啟用RPDO1 // 心跳配置(1步) IO_Write_SLAVE_Heartbeat, // 步驟12:寫從站心跳 // 配置完成(1步) IO_Config_Done, // 步驟13:釋放信號量};

原先DS301一些邏輯我們進行了保留。

并且新增了一些 IO操作接口函數(shù),代碼如下:

/************************** 新增IO操作API(上層調(diào)用) **************************//***@brief設置EM32DX-C4的DO輸出*@paramdo_val: 16位DO值(bit0=DO0, bit15=DO15,1=導通,0=斷開)*@retvalRT_EOK: 成功,-RT_ERROR: 失敗*/rt_err_tem32dx_set_do(uint16_t do_val){ if(*can_node[1].nmt_state != Operational) { rt_kprintf("EM32DX-C4 not in Operational state!\n"); return-RT_ERROR; } // 更新全局緩存 g_em32dx_do = do_val; // 通過RPDO1發(fā)送DO值 UNS32size=2; UNS32errorCode=writeLocalDict(OD_Data,0x2000,1, &do_val, &size,0); if(errorCode != OD_SUCCESSFUL) { rt_kprintf("Write DO failed! Error code: 0x%08X\n", errorCode); return-RT_ERROR; } returnRT_EOK;}/***@brief讀取EM32DX-C4的DI輸入*@paramdi_val: 輸出參數(shù),存儲16位DI值(bit0=DI0, bit15=DI15,1=導通,0=斷開)*@retvalRT_EOK: 成功,-RT_ERROR: 失敗*/rt_err_tem32dx_get_di(){ if(*can_node[1].nmt_state != Operational) { rt_kprintf("EM32DX-C4 not ready!\n"); return-RT_ERROR; } // 從本地字典讀取TPDO1接收的DI值 uint16_tdi_val=0; UNS32size=2; UNS8 data_type; UNS32errorCode=readLocalDict(OD_Data,0x2001,1, &di_val, &size, &data_type,0); if(errorCode != OD_SUCCESSFUL) { rt_kprintf("Read DI failed! Error code: 0x%08X\n", errorCode); return-RT_ERROR; } rt_kprintf("Read DI: 0x%04X\n", di_val); // 更新全局緩存 g_em32dx_di = di_val; returnRT_EOK;}MSH_CMD_EXPORT(em32dx_get_di, Get EM32DX-C4 DI input);/***@brief單獨控制某一路DO*@paramchannel: DO通道(0-15)*@paramstate: 0=斷開,1=導通*@retvalRT_EOK: 成功,-RT_ERROR: 失敗*/rt_err_tem32dx_set_do_channel(uint8_t argc,char**argv){ if(argc =16) { rt_kprintf("DO channel out of range (0-15)!\n"); return-RT_ERROR; } if(state) { g_em32dx_do |= (1<< channel);? ? }?else?{? ? ? ? g_em32dx_do &= ~(1?<< channel);? ? }? ? return?em32dx_set_do(g_em32dx_do);}MSH_CMD_EXPORT(em32dx_set_do_channel, Set single DO?channel?(channel?0-15, state?0/1));


代碼編譯完成后,我們將其部署至睿擎派,具體操作步驟如下:

(1)執(zhí)行 canopen_start 指令,完成 CANOpen 服務的初始化與啟動;

(2)執(zhí)行 em32dx_get_di 指令,獲取 16 路開關量的當前狀態(tài);

(3)執(zhí)行 em32dx_set_do_channel 1 1 指令,配置 16 路 DO 通道的輸出狀態(tài)。

其中第一個參數(shù)為通道索引(取值范圍:0–15),第二個參數(shù)為輸出狀態(tài)(0 = 關閉,1 = 打開)。


76d27686-e0af-11f0-8ce9-92fbcf53809c.png76da88bc-e0af-11f0-8ce9-92fbcf53809c.png


上述指令執(zhí)行完成后,我們可以觀察到對應的 DO 通道狀態(tài)指示燈,會同步呈現(xiàn)出預期的狀態(tài)變化(與指令配置的輸出狀態(tài)一致)。


76e917e2-e0af-11f0-8ce9-92fbcf53809c.png



源代碼下載鏈接:

鏈接:https://pan.baidu.com/s/1aZDxzb3NNhn3WRBA4OeN4w?pwd=w8au

提取碼: w8au


附錄:

(1)CANOpen DS301、DS302、DS401、DS402等全套協(xié)議下載:

https://link.gitcode.com/i/614ed2a5064e1990bff8ffcde2328ada?uuid_tt_dd=10_19283516180-1733805088376-790686&isLogin=1&from_id=142936482


(2)DS301協(xié)議中文版

https://files.cnblogs.com/files/winshton/301_v04020005_cn_v02_ro.pdf

https://winshton.gitbooks.io/canopen-ds301-cn/content/


————————————————


版權聲明:本文為RT-Thread論壇用戶「yefanqiu」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議,轉載請附上原文出處鏈接及本聲明。


原文鏈接:

https://club.rt-thread.org/ask/article/bb8a52de0882d43b.html


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • CAN
    CAN
    +關注

    關注

    59

    文章

    3065

    瀏覽量

    472714
  • 總線
    +關注

    關注

    10

    文章

    3039

    瀏覽量

    91650
  • RT-Thread
    +關注

    關注

    32

    文章

    1611

    瀏覽量

    44806
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    基于輕松玩轉CANopen電機控制

    在工業(yè)自動化領域,穩(wěn)定高效的設備間通信是核心。CANopen協(xié)議憑借其可靠性,成為眾多工業(yè)設備(如伺服電機)的首選通信標準。今天,我們就來展示如何利用RT-Thread工業(yè)開發(fā)平臺
    的頭像 發(fā)表于 07-19 09:04 ?5903次閱讀
    基于<b class='flag-5'>睿</b><b class='flag-5'>擎</b><b class='flag-5'>派</b>輕松玩轉<b class='flag-5'>CANopen</b>電機控制

    基于輕松玩轉Modbus工業(yè)通信

    。今天,我們就帶來一篇實戰(zhàn)教程,教大家如何基于/工業(yè)平臺,分別作為ModbusRTU主機和ModbusTCP主機,讀取Modbus
    的頭像 發(fā)表于 07-23 17:05 ?3796次閱讀
    基于<b class='flag-5'>睿</b><b class='flag-5'>擎</b><b class='flag-5'>派</b>輕松玩轉Modbus工業(yè)通信

    】EtherCAT總線IO模塊讀寫

    在上一篇文章《【CANOpen總線IO
    的頭像 發(fā)表于 12-31 19:07 ?5417次閱讀
    【<b class='flag-5'>睿</b><b class='flag-5'>擎</b><b class='flag-5'>派</b>】EtherCAT<b class='flag-5'>總線</b><b class='flag-5'>之</b><b class='flag-5'>IO</b><b class='flag-5'>模塊</b><b class='flag-5'>讀寫</b>

    Canopen協(xié)議讀后感想

    ,那么不僅要實現(xiàn)CANopen的CiA DS301協(xié)議,而且還需要實現(xiàn)符合開發(fā)模塊需求的子協(xié)議規(guī)范,例如CiA
    發(fā)表于 09-21 12:58

    XGate-COP10 CANopen協(xié)議轉換模塊硬件設計指

    XGate-COP10是一款CANopen從站協(xié)議轉換模塊,其內(nèi)部已經(jīng)集成了CANopen從站協(xié)議棧代碼,不需要進行二次開發(fā)。
    發(fā)表于 03-06 14:54 ?19次下載

    CANopen協(xié)議講座(1)CAN總線簡介

    CANopen協(xié)議講座(1)CAN總線簡介 CANopen協(xié)議以其成熟的結構、抗干擾能力強等
    發(fā)表于 03-26 17:27 ?81次下載

    CANopen協(xié)議講座(2)CANopen協(xié)議簡介

    CANopen協(xié)議講座(2)CANopen協(xié)議簡介 CANopen
    發(fā)表于 03-26 17:29 ?73次下載

    CANopen協(xié)議講座(5)CANopen從站模塊(XGa

    CANopen協(xié)議講座(5)CANopen從站模塊(XGate-COP10) CANopen
    發(fā)表于 03-26 17:38 ?82次下載

    CANopen協(xié)議講座(6)CANopen從站模塊(Tin

    CANopen協(xié)議講座(6)CANopen從站模塊(TinyARM) 隨著國內(nèi)電力事業(yè)前所未有的發(fā)展,電力資源的需求也迅速增長。為保證電
    發(fā)表于 03-26 17:40 ?106次下載

    CAN總線CANopen方案推薦

    廣成科技GCAN-30X系列模塊是一系列定制型的CANopen轉換模塊,該模塊是在CAN總線網(wǎng)關轉換器設備的基礎上在CAN
    的頭像 發(fā)表于 03-02 11:06 ?9248次閱讀
    CAN<b class='flag-5'>總線</b>轉<b class='flag-5'>CANopen</b>方案推薦

    IO耦合器如何通過CANopen總線進行通訊

    can io模塊一般指的是集成有CAN總線接口,可以通過CANopen總線進行通訊的IO耦合器。
    的頭像 發(fā)表于 04-22 17:23 ?3453次閱讀

    CANopen IO耦合器功能的實現(xiàn)

    canopen分布式io是集成有CANBUS接口的IO耦合器,它可以通過CANopen總線與支持CANo
    的頭像 發(fā)表于 10-26 16:50 ?1834次閱讀

    虹科推出多款CANopen IO模塊及hipecs系列模塊通訊測試

    /Positive Switching,與總線和系統(tǒng)供電隔離,所有輸出端具有短路保護的特性,支持DS301和DS401,CAN端支持常用波特率,高達1Mbit/s,歡迎聯(lián)系虹科選購。
    的頭像 發(fā)表于 11-25 10:53 ?1817次閱讀

    淺談CANopen協(xié)議虹科CANopen IO方案

    01CANopen概述CANopen是一種架構在CAN串行總線系統(tǒng)上的高層通訊協(xié)議,也是一種針對于行業(yè)的標準化的協(xié)議。該
    的頭像 發(fā)表于 11-19 16:11 ?1941次閱讀
    淺談<b class='flag-5'>CANopen</b><b class='flag-5'>協(xié)議</b><b class='flag-5'>之</b>虹科<b class='flag-5'>CANopen</b> <b class='flag-5'>IO</b>方案

    基于輕松玩轉EtherCAT主站,實現(xiàn)電機精確控制

    總線技術。本次實戰(zhàn)教程將基于平臺,演示實現(xiàn):EtherCAT主站開發(fā)CSP模式伺服電機精準控制遠程IO流水燈聯(lián)動開啟工業(yè)實時通信開發(fā)新
    的頭像 發(fā)表于 07-30 17:03 ?5165次閱讀
    基于<b class='flag-5'>睿</b><b class='flag-5'>擎</b><b class='flag-5'>派</b>輕松玩轉EtherCAT主站,實現(xiàn)電機精確控制