SVG圖展示了系統的分層架構:
- RTAI實時層
:包含RT_TASK、信號量和定時器
- EtherCAT Master層
:主站、域、從站配置和PDO映射
- EtherCAT網絡層
:與實際硬件設備(EL3162模擬輸入、EL2004數字輸出)通信
關鍵特點:
- 實時性
:使用RTAI確保2000Hz的精確定時
- 同步保護
:通過信號量保護EtherCAT操作
- 狀態監控
:定期檢查主站、域和從站狀態
- 雙向數據流
:接收模擬輸入數據,發送數字輸出控制信號
這個系統典型用于工業自動化場景,需要高精度的實時控制和可靠的現場總線通信
這個序列圖詳細描繪了從模塊加載到卸載的整個生命周期中,Linux 內核、RTAI 子系統、驅動邏輯以及 EtherCAT 主站之間的動態交互。它特別突出了 RTAI 任務的創建、周期性執行以及與信號量的交互。
時序圖展示了EtherCAT RTAI程序的完整執行流程:
- 模塊初始化階段
:請求主站、創建域、配置從站、注冊PDO等
- 循環執行階段
:以2000Hz頻率進行實時數據交換
- 模塊卸載階段
:清理資源
這段使用了?RTAI (Real-Time Application Interface)?的 IgH EtherCAT Master 示例代碼。這是一個在內核空間運行的硬實時應用,通過 RTAI 提供的任務調度和定時器來實現高精度的周期性控制。
// Linux // 區域注釋:Linux 內核頭文件#include?<linux/module.h>?// 包含 Linux 內核模塊編程所需的核心頭文件#include?<linux/err.h>?// 包含內核錯誤處理相關的頭文件// RTAI // 區域注釋:RTAI 頭文件#include?<rtai_sched.h>?// 包含 RTAI 調度器相關的頭文件,如任務創建、周期性設置#include?<rtai_sem.h>?// 包含 RTAI 信號量相關的頭文件,用于同步// EtherCAT // 區域注釋:EtherCAT 頭文件#include?"../../include/ecrt.h"?// 包含 IgH EtherCAT 主站的實時接口頭文件/****************************************************************************/?// 分隔注釋// Module parameters // 區域注釋:模塊參數#define?FREQUENCY 2000?// task frequency in Hz // 宏定義:任務頻率為 2000 Hz#define?INHIBIT_TIME 20?// 宏定義:抑制時間為 20 (單位可能是微秒,用于回調函數)#define?TIMERTICKS (1000000000 / FREQUENCY)?// 宏定義:每個周期的納秒數// Optional features (comment to disable) // 區域注釋:可選特性 (注釋掉以禁用)#define?CONFIGURE_PDOS?// 宏定義:啟用 PDO 配置代碼塊#define?PFX?"ec_rtai_sample: "?// 宏定義:內核日志消息的前綴,方便調試/****************************************************************************/?// 分隔注釋// EtherCAT // 區域注釋:EtherCAT 相關全局變量static?ec_master_t?*master =?NULL;?// 聲明一個靜態的 EtherCAT 主站對象指針static?ec_master_state_t?master_state = {};?// 聲明一個靜態的主站狀態結構體變量static?ec_domain_t?*domain1 =?NULL;?// 聲明一個靜態的 EtherCAT 過程數據域指針static?ec_domain_state_t?domain1_state = {};?// 聲明一個靜態的域狀態結構體變量static?ec_slave_config_t?*sc_ana_in =?NULL;?// 聲明一個靜態的從站配置對象指針,用于模擬量輸入從站static?ec_slave_config_state_t?sc_ana_in_state = {};?// 聲明一個靜態的從站配置狀態結構體變量// RTAI // 區域注釋:RTAI 相關全局變量static?RT_TASK task;?// 聲明一個 RTAI 任務結構體變量static?SEM master_sem;?// 聲明一個 RTAI 信號量結構體變量static?cycles_t?t_last_cycle =?0, t_critical;?// 聲明 RTAI 的時間戳變量 (CPU周期數),用于高精度時間測量/****************************************************************************/?// 分隔注釋// process data // 區域注釋:過程數據static?uint8_t?*domain1_pd;?// process data memory // 聲明一個指向過程數據內存區域的指針#define?AnaInSlavePos ?0, 3?// 宏定義:模擬量輸入從站的位置#define?DigOutSlavePos 0, 2?// 宏定義:數字量輸出從站的位置#define?Beckhoff_EL2004 0x00000002, 0x07D43052?// 宏定義:倍福 EL2004 的廠商/產品ID#define?Beckhoff_EL3162 0x00000002, 0x0C5A3052?// 宏定義:倍福 EL3162 的廠商/產品IDstatic?unsigned?int?off_ana_in;?// offsets for PDO entries // 靜態無符號整型,存儲模擬量輸入值的偏移量static?unsigned?int?off_dig_out;?// 靜態無符號整型,存儲數字量輸出的偏移量const?static?ec_pdo_entry_reg_t?domain1_regs[] = {?// 定義一個靜態常量數組,用于注冊需要映射到域的PDO條目? ? {AnaInSlavePos, ?Beckhoff_EL3162,?0x3101,?2, &off_ana_in},?// 注冊 EL3162 的值 PDO? ? {DigOutSlavePos, Beckhoff_EL2004,?0x3001,?1, &off_dig_out},?// 注冊 EL2004 的輸出 PDO? ? {}?// 數組結束標志};static?unsigned?int?counter =?0;?// 靜態無符號整型,用作通用計數器static?unsigned?int?blink =?0;?// 靜態無符號整型,用作閃爍標志/****************************************************************************/?// 分隔注釋#ifdef?CONFIGURE_PDOS?// 如果定義了 CONFIGURE_PDOS 宏// 以下是為從站進行 SII (從站信息接口) 覆蓋配置所需的數據結構static?ec_pdo_entry_info_t?el3162_channel1[] = {?// 定義 EL3162 通道1的 PDO 條目信息? ? {0x3101,?1, ?8},?// status (狀態,8位)? ? {0x3101,?2,?16} ?// value (值,16位)};static?ec_pdo_entry_info_t?el3162_channel2[] = {?// 定義 EL3162 通道2的 PDO 條目信息? ? {0x3102,?1, ?8},?// status (狀態,8位)? ? {0x3102,?2,?16} ?// value (值,16位)};static?ec_pdo_info_t?el3162_pdos[] = {?// 定義 EL3162 的 PDO 信息 (將條目分組)? ? {0x1A00,?2, el3162_channel1},?// TxPDO 0x1A00? ? {0x1A01,?2, el3162_channel2} ?// TxPDO 0x1A01};static?ec_sync_info_t?el3162_syncs[] = {?// 定義 EL3162 的同步管理器配置? ? {2, EC_DIR_OUTPUT},?// SM2 是一個空的輸出 SM? ? {3, EC_DIR_INPUT,?2, el3162_pdos},?// SM3 是輸入 SM,關聯2個 PDO? ? {0xff}?// 結束標志};static?ec_pdo_entry_info_t?el2004_channels[] = {?// 定義 EL2004 的 PDO 條目信息? ? {0x3001,?1,?1},?// Value 1 (值1,1位)? ? {0x3001,?2,?1},?// Value 2 (值2,1位)? ? {0x3001,?3,?1},?// Value 3 (值3,1位)? ? {0x3001,?4,?1} ?// Value 4 (值4,1位)};static?ec_pdo_info_t?el2004_pdos[] = {?// 定義 EL2004 的 PDO 信息? ? {0x1600,?1, &el2004_channels[0]},?// RxPDO 0x1600? ? {0x1601,?1, &el2004_channels[1]},?// RxPDO 0x1601? ? {0x1602,?1, &el2004_channels[2]},?// RxPDO 0x1602? ? {0x1603,?1, &el2004_channels[3]} ?// RxPDO 0x1603};static?ec_sync_info_t?el2004_syncs[] = {?// 定義 EL2004 的同步管理器配置? ? {0, EC_DIR_OUTPUT,?4, el2004_pdos},?// SM0 是輸出 SM,關聯4個 PDO? ? {1, EC_DIR_INPUT},?// SM1 是一個空的輸入 SM? ? {0xff}?// 結束標志};#endif?// 結束 #ifdef/****************************************************************************/?// 分隔注釋void?check_domain1_state(void)?// 定義一個函數,用于檢查并打印域的狀態變化{? ??ec_domain_state_t?ds;?// 聲明一個局部的域狀態結構體變量? ??rt_sem_wait(&master_sem);?// 等待(獲取)RTAI信號量? ??ecrt_domain_state(domain1, &ds);?// 調用 ecrt API,獲取 domain1 的當前狀態? ??rt_sem_signal(&master_sem);?// 發送(釋放)RTAI信號量? ??if?(ds.working_counter != domain1_state.working_counter)?// 比較當前工作計數器(WC)與上次記錄的WC? ? ? ??printk(KERN_INFO PFX?"Domain1: WC %u.\n", ds.working_counter);?// 如果不一致,打印新的WC值? ??if?(ds.wc_state != domain1_state.wc_state)?// 比較當前工作計數器狀態與上次記錄的狀態? ? ? ??printk(KERN_INFO PFX?"Domain1: State %u.\n", ds.wc_state);?// 如果不一致,打印新的WC狀態? ? domain1_state = ds;?// 將當前狀態賦值給全局變量,用于下次比較}/****************************************************************************/?// 分隔注釋void?check_master_state(void)?// 定義一個函數,用于檢查并打印主站的狀態變化{? ??ec_master_state_t?ms;?// 聲明一個局部的主站狀態結構體變量? ??rt_sem_wait(&master_sem);?// 等待 RTAI 信號量? ??ecrt_master_state(master, &ms);?// 調用 ecrt API,獲取主站的當前狀態? ??rt_sem_signal(&master_sem);?// 釋放 RTAI 信號量? ??if?(ms.slaves_responding != master_state.slaves_responding)?// 比較當前響應的從站數量與上次記錄的數量? ? ? ??printk(KERN_INFO PFX?"%u slave(s).\n", ms.slaves_responding);?// 如果不一致,打印新的從站數量? ??if?(ms.al_states != master_state.al_states)?// 比較當前應用層(AL)狀態與上次記錄的狀態? ? ? ??printk(KERN_INFO PFX?"AL states: 0x%02X.\n", ms.al_states);?// 如果不一致,以十六進制格式打印新的AL狀態? ??if?(ms.link_up != master_state.link_up)?// 比較當前鏈路連接狀態與上次記錄的狀態? ? ? ??printk(KERN_INFO PFX?"Link is %s.\n", ms.link_up ??"up"?:?"down");?// 如果不一致,打印鏈路是 "up" 還是 "down"? ? master_state = ms;?// 將當前狀態賦值給全局變量,用于下次比較}/****************************************************************************/?// 分隔注釋void?check_slave_config_states(void)?// 定義一個函數,用于檢查并打印特定從站的配置狀態變化{? ??ec_slave_config_state_t?s;?// 聲明一個局部的從站配置狀態結構體變量? ??rt_sem_wait(&master_sem);?// 等待 RTAI 信號量? ??ecrt_slave_config_state(sc_ana_in, &s);?// 調用 ecrt API,獲取模擬量輸入從站的當前配置狀態? ??rt_sem_signal(&master_sem);?// 釋放 RTAI 信號量? ??if?(s.al_state != sc_ana_in_state.al_state)?// 比較當前應用層狀態與上次記錄的狀態? ? ? ??printk(KERN_INFO PFX?"AnaIn: State 0x%02X.\n", s.al_state);?// 如果不一致,打印新的AL狀態? ??if?(s.online != sc_ana_in_state.online)?// 比較當前在線狀態與上次記錄的狀態? ? ? ??printk(KERN_INFO PFX?"AnaIn: %s.\n", s.online ??"online"?:?"offline");?// 如果不一致,打印在線/離線狀態? ??if?(s.operational != sc_ana_in_state.operational)?// 比較當前操作狀態與上次記錄的狀態? ? ? ??printk(KERN_INFO PFX?"AnaIn: %soperational.\n",?// 如果不一致,打印是否進入操作狀態? ? ? ? ? ? ? ? s.operational ??""?:?"Not ");?// 根據 operational 的值打印 "operational" 或 "Not operational"? ? sc_ana_in_state = s;?// 將當前狀態賦值給全局變量,用于下次比較}/****************************************************************************/?// 分隔注釋void?run(long?data)?// RTAI 實時任務的主體函數{? ??while?(1) {?// 進入一個無限循環? ? ? ? t_last_cycle =?get_cycles();?// 使用 RTAI 函數獲取當前 CPU 周期數,記錄周期開始時間? ? ? ??// receive process data // 注釋:接收過程數據? ? ? ??rt_sem_wait(&master_sem);?// 獲取信號量? ? ? ??ecrt_master_receive(master);?// 從網絡接口接收數據幀? ? ? ??ecrt_domain_process(domain1);?// 處理域的數據? ? ? ??rt_sem_signal(&master_sem);?// 釋放信號量? ? ? ??// check process data state (optional) // 注釋:檢查過程數據狀態(可選)? ? ? ??check_domain1_state();?// 調用函數檢查并打印域的狀態變化? ? ? ??if?(counter) {?// 如果計數器不為0? ? ? ? ? ? counter--;?// 計數器減1? ? ? ? }?else?{?// do this at 1 Hz // 如果計數器為0 (即每秒執行一次)? ? ? ? ? ? counter = FREQUENCY;?// 重置計數器為頻率值 (2000)? ? ? ? ? ??// calculate new process data // 注釋:計算新的過程數據? ? ? ? ? ? blink = !blink;?// 對 blink 變量取反,實現閃爍邏輯? ? ? ? ? ??// check for master state (optional) // 注釋:檢查主站狀態(可選)? ? ? ? ? ??check_master_state();?// 調用函數檢查并打印主站的狀態變化? ? ? ? ? ??// check for islave configuration state(s) (optional) // 注釋:檢查從站配置狀態(可選)? ? ? ? ? ??check_slave_config_states();?// 調用函數檢查并打印特定從站的狀態變化? ? ? ? }? ? ? ??// write process data // 注釋:寫入過程數據? ? ? ??EC_WRITE_U8(domain1_pd + off_dig_out, blink ??0x06?:?0x09);?// 將數據寫入過程數據區,控制數字量輸出 (輸出 0b0110 或 0b1001)? ? ? ??rt_sem_wait(&master_sem);?// 獲取信號量? ? ? ??ecrt_domain_queue(domain1);?// 將要發送的域數據放入發送隊列? ? ? ??ecrt_master_send(master);?// 將數據幀發送到 EtherCAT 總線? ? ? ??rt_sem_signal(&master_sem);?// 釋放信號量? ? ? ??rt_task_wait_period();?// RTAI 函數:使當前任務睡眠,直到下一個周期性調度點? ? }}/****************************************************************************/?// 分隔注釋void?send_callback(void?*cb_data)?// 定義一個發送回調函數 (用于外部事件驅動模式){? ??ec_master_t?*m = (ec_master_t?*) cb_data;?// 將 void* 指針轉換為 master 指針? ??// too close to the next real time cycle: deny access... // 注釋:離下一個實時周期太近:拒絕訪問...? ??if?(get_cycles() - t_last_cycle <= t_critical) {?// 如果當前時間與上個周期開始時間的差值小于臨界值? ? ? ??rt_sem_wait(&master_sem);?// 獲取信號量? ? ? ??ecrt_master_send_ext(m);?// 調用擴展的發送函數? ? ? ??rt_sem_signal(&master_sem);?// 釋放信號量? ? }}/****************************************************************************/?// 分隔注釋void?receive_callback(void?*cb_data)?// 定義一個接收回調函數 (用于外部事件驅動模式){? ??ec_master_t?*m = (ec_master_t?*) cb_data;?// 將 void* 指針轉換為 master 指針? ??// too close to the next real time cycle: deny access... // 注釋:離下一個實時周期太近:拒絕訪問...? ??if?(get_cycles() - t_last_cycle <= t_critical) {?// 如果當前時間與上個周期開始時間的差值小于臨界值? ? ? ??rt_sem_wait(&master_sem);?// 獲取信號量? ? ? ??ecrt_master_receive(m);?// 調用接收函數? ? ? ??rt_sem_signal(&master_sem);?// 釋放信號量? ? }}/****************************************************************************/?// 分隔注釋int?__init?init_mod(void)?// 內核模塊的初始化函數{? ??int?ret =?-1;?// 聲明并初始化返回值? ? RTIME tick_period, requested_ticks, now;?// 聲明 RTAI 的時間變量#ifdef?CONFIGURE_PDOS?// 如果定義了 CONFIGURE_PDOS 宏? ??ec_slave_config_t?*sc;?// 聲明一個從站配置對象指針#endif?// 結束 #ifdef? ??printk(KERN_INFO PFX?"Starting...\n");?// 在內核日志中打印啟動信息? ??rt_sem_init(&master_sem,?1);?// 初始化 RTAI 信號量,初始值為1? ??// 計算一個臨界時間值,用于回調函數中判斷是否離下一個周期太近? ? t_critical = cpu_khz *?1000?/ FREQUENCY - cpu_khz * INHIBIT_TIME /?1000;? ? master =?ecrt_request_master(0);?// 請求索引為0的 EtherCAT 主站實例? ??if?(!master) {?// 檢查主站請求是否成功? ? ? ? ret = -EBUSY;?// 設置返回值為設備忙? ? ? ??printk(KERN_ERR PFX?"Requesting master 0 failed!\n");?// 打印錯誤信息? ? ? ??goto?out_return;?// 跳轉到返回處? ? }? ??ecrt_master_callbacks(master, send_callback, receive_callback, master);?// 注冊發送和接收的回調函數? ??printk(KERN_INFO PFX?"Registering domain...\n");?// 打印信息? ??if?(!(domain1 =?ecrt_master_create_domain(master))) {?// 在主站上創建一個過程數據域? ? ? ??printk(KERN_ERR PFX?"Domain creation failed!\n");?// 如果失敗,打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }? ??if?(!(sc_ana_in =?ecrt_master_slave_config(?// 為模擬量輸入從站創建配置? ? ? ? ? ? ? ? ? ? master, AnaInSlavePos, Beckhoff_EL3162))) {? ? ? ??printk(KERN_ERR PFX?"Failed to get slave configuration.\n");?// 打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }#ifdef?CONFIGURE_PDOS?// 如果定義了 CONFIGURE_PDOS 宏? ??printk(KERN_INFO PFX?"Configuring PDOs...\n");?// 打印信息? ??if?(ecrt_slave_config_pdos(sc_ana_in, EC_END, el3162_syncs)) {?// 為 EL3162 配置 PDO? ? ? ??printk(KERN_ERR PFX?"Failed to configure PDOs.\n");?// 打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }? ??if?(!(sc =?ecrt_master_slave_config(master, DigOutSlavePos,?// 為數字量輸出從站創建配置? ? ? ? ? ? ? ? ? ? Beckhoff_EL2004))) {? ? ? ??printk(KERN_ERR PFX?"Failed to get slave configuration.\n");?// 打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }? ??if?(ecrt_slave_config_pdos(sc, EC_END, el2004_syncs)) {?// 為 EL2004 配置 PDO? ? ? ??printk(KERN_ERR PFX?"Failed to configure PDOs.\n");?// 打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }#endif?// 結束 #ifdef? ??printk(KERN_INFO PFX?"Registering PDO entries...\n");?// 打印信息? ??if?(ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) {?// 將定義的 PDO 注冊列表注冊到域? ? ? ??printk(KERN_ERR PFX?"PDO entry registration failed!\n");?// 如果注冊失敗,打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }? ??printk(KERN_INFO PFX?"Activating master...\n");?// 打印信息? ??if?(ecrt_master_activate(master)) {?// 激活主站,開始總線通信? ? ? ??printk(KERN_ERR PFX?"Failed to activate master!\n");?// 如果激活失敗,打印錯誤? ? ? ??goto?out_release_master;?// 跳轉到釋放主站處? ? }? ??// Get internal process data for domain // 注釋:獲取域的內部過程數據? ? domain1_pd =?ecrt_domain_data(domain1);?// 獲取域的過程數據內存區指針? ??printk(KERN_INFO PFX?"Starting cyclic sample thread...\n");?// 打印信息? ? requested_ticks =?nano2count(TIMERTICKS);?// 將周期納秒數轉換為 RTAI 的時鐘節拍數? ? tick_period =?start_rt_timer(requested_ticks);?// 啟動 RTAI 的實時定時器,并獲取實際的節拍周期? ??printk(KERN_INFO PFX?"RT timer started with %i/%i ticks.\n",?// 打印定時器信息? ? ? ? ? ?(int) tick_period, (int) requested_ticks);? ??// 初始化 RTAI 任務? ??if?(rt_task_init(&task, run,?0,?2000,?0,?1,?NULL)) {?// 參數:任務結構體, 函數, 參數, 棧大小, 優先級, 使用FPU, 信號? ? ? ??printk(KERN_ERR PFX?"Failed to init RTAI task!\n");?// 如果失敗,打印錯誤? ? ? ??goto?out_stop_timer;?// 跳轉到停止定時器處? ? }? ? now =?rt_get_time();?// 獲取當前 RTAI 時間? ??// 將任務設置為周期性任務? ??if?(rt_task_make_periodic(&task, now + tick_period, tick_period)) {?// 參數:任務, 首次啟動時間, 周期? ? ? ??printk(KERN_ERR PFX?"Failed to run RTAI task!\n");?// 如果失敗,打印錯誤? ? ? ??goto?out_stop_task;?// 跳轉到刪除任務處? ? }? ??printk(KERN_INFO PFX?"Initialized.\n");?// 打印初始化成功信息? ??return?0;?// 返回成功?out_stop_task:?// 清理標簽:刪除任務? ??rt_task_delete(&task);?// 刪除 RTAI 任務?out_stop_timer:?// 清理標簽:停止定時器? ??stop_rt_timer();?// 停止 RTAI 實時定時器?out_release_master:?// 清理標簽:釋放主站? ??printk(KERN_ERR PFX?"Releasing master...\n");?// 打印信息? ??ecrt_release_master(master);?// 釋放主站資源?out_return:?// 清理標簽:返回? ??rt_sem_delete(&master_sem);?// 刪除 RTAI 信號量? ??printk(KERN_ERR PFX?"Failed to load. Aborting.\n");?// 打印加載失敗信息? ??return?ret;?// 返回錯誤碼}/****************************************************************************/?// 分隔注釋void?__exit?cleanup_mod(void)?// 內核模塊的退出函數{? ??printk(KERN_INFO PFX?"Stopping...\n");?// 在內核日志中打印停止信息? ??rt_task_delete(&task);?// 刪除 RTAI 任務? ??stop_rt_timer();?// 停止 RTAI 實時定時器? ??ecrt_release_master(master);?// 釋放主站資源? ??rt_sem_delete(&master_sem);?// 刪除 RTAI 信號量? ??printk(KERN_INFO PFX?"Unloading.\n");?// 打印卸載完成信息}/****************************************************************************/?// 分隔注釋MODULE_LICENSE("GPL");?// 宏:聲明模塊的許可證為 GPLMODULE_AUTHOR("Florian Pose <fp@igh.de>");?// 宏:聲明模塊的作者MODULE_DESCRIPTION("EtherCAT RTAI sample module");?// 宏:聲明模塊的描述module_init(init_mod);?// 宏:將 init_mod 函數注冊為模塊的初始化函數module_exit(cleanup_mod);?// 宏:將 cleanup_mod 函數注冊為模塊的退出函數/****************************************************************************/
這是一個在?Linux 內核空間運行的、基于?RTAI (Real-Time Application Interface)?的硬實時 EtherCAT 主站示例模塊。它展示了如何利用 RTAI 提供的實時任務和調度機制來構建一個高精度、確定性的工業控制應用。
核心架構與功能:
內核模塊形態:與?
tty.c
?示例類似,這是一個通過?insmod
?加載、rmmod
?卸載的完整內核模塊,其生命周期由?module_init
?和?module_exit
?函數管理。硬實時環境 (RTAI):此示例的核心是 RTAI。它不依賴 Linux 自身的調度器或定時器,而是使用 RTAI 提供的、運行在 Linux 內核之下的實時微內核服務。
- RTAI 任務
:通過?
rt_task_init
?創建一個名為?task
?的 RTAI 實時任務,并指定其執行函數為?run
。 - RTAI 調度與定時
:通過?
start_rt_timer
?啟動 RTAI 的高精度實時定時器,并通過?rt_task_make_periodic
?將?task
?設置為由該定時器驅動的周期性任務。周期的精確性由 RTAI 內核保證,抖動(jitter)非常小。 - 周期性執行
:
run
?函數中的?rt_task_wait_period()
?會精確地阻塞任務,直到下一個由 RTAI 定時器確定的調度點到來。
并發保護 - RTAI 信號量:
為了保護對共享 EtherCAT 主站資源的訪問,代碼使用了 RTAI 提供的信號量 (
rt_sem_init
,?rt_sem_wait
,?rt_sem_signal
)。在?
run
?函數和兩個回調函數中,所有對?ecrt_*
?函數的調用都被信號量加鎖和解鎖,確保了在 RTAI 的多任務環境下的數據一致性和原子性。
周期性任務 (
run
):這是 RTAI 實時任務的主體,在一個無限循環中運行。
每個周期開始時,它執行標準的 EtherCAT I/O 流程:接收、處理、應用邏輯、發送。
- 應用邏輯
:包含一個簡單的 1Hz 閃爍邏輯,控制一個數字量輸出模塊。
- 狀態監控
:以較低頻率檢查并打印主站、域和從站的狀態。
- 高精度時間戳
:使用?
get_cycles()
?獲取 CPU 周期數,用于高精度的時間測量,這在?send/receive_callback
?中用于防止與實時周期沖突。
可選的回調機制:
代碼中注冊了?
send_callback
?和?receive_callback
,這通常用于外部事件驅動的模式(例如,由網卡中斷直接觸發數據收發)。在這些回調函數中,通過比較當前 CPU 周期數與上一個實時周期開始的時間戳,來判斷是否離下一個實時周期太近。如果太近,就不執行操作,這是為了避免外部事件干擾到由 RTAI 定時器驅動的主實時循環的確定性。
生命周期管理:
- 加載 (
insmod
): 執行?
init_mod
。完成 EtherCAT 配置、初始化 RTAI 信號量、啟動 RTAI 實時定時器,并創建和啟動 RTAI 周期性任務。 - 運行
: RTAI 調度器周期性地喚醒?
run
?任務,執行硬實時控制循環。 - 卸載 (
rmmod
): 執行?
cleanup_mod
。安全地刪除 RTAI 任務,停止 RTAI 定時器,釋放 EtherCAT 主站資源,并刪除 RTAI 信號量。
總結:此代碼是一個經典的硬實時內核空間 EtherCAT 控制器范例。它完全繞開了標準 Linux 的調度和定時機制,將所有實時關鍵操作都交由 RTAI 微內核處理,從而獲得了微秒級的、確定性的性能。這是在需要極高實時性保證的工業自動化和機器人控制等領域中常見的架構。
- 加載 (