關於由多個不同的C文件構成的工程,我采用以下方法
以為400Hz數字電源程序為例
假設工程由以下文件組成
DC_Comm.c 主要完成串口通訊部分
DC_Config.c 主要完成時鍾,外設 中斷初始化
DC_Control.c 主要完成電源數字化SPWM控制,以及串口接收中斷的處理
DC_Memory.c 主要完成FM33256 的SPI時序的軟件實現。故障記錄與操作記錄的寫入與讀取操作。
DC_Timing.h 主要完成與CPLD配合的一些時序。
響應的在include 中我還用到了一些頭文件
DC_Comm.h 主要用來對DC_Comm.c中用到的數據類型進行聲明,以及函數進行聲明。這些函數都在DC_Comm.c中定義
DC_Control.h 主要用來對DC_Control.c中用到的數據類型進行聲明,以及函數進行聲明。這些函數在DC_Control.c 中定義
DC_Types.h 中宏定義了 一些Q格式常量 ,以及一些函數的聲明。
總之:假設在DC_Comm.c中 定義了函數SCIRXProcess ()
則在DC_Comm.h中聲明了 extern void SCIRXProcess ()
那么我在main.c 文件中調用 SCIRXProcess()的時候, 直接在main.c的前方將DC_Comm.h 包含進來就ok .
總結 就是 一個工程假設有A,B,C,main.c 4個文件組成, 假設在main.c 中定義了一些變量p,q,m 若A文件要使用p , 則需要在A文件的開頭 用extern 關鍵字進行聲明。
拋磚引玉:開始進入基於ican協議的CAN開發,該平台單片機采用STC89C52
該工程由兩個文件組成SJA.C 和ican.c
SJA1000.h 中 定義了寄存器的硬件地址
基本地址 #define SJA_BaseAdr 0X7F00 由外部電路的硬件地址決定 單片機的那一個引腳連接在SJA1000的CS引腳上
內部控制寄存器 #define REG_CONTROL SJA_BaseAdr+0x00
命令寄存器 #define REG_COMMAND SJA_BaseAdr+0x01
狀態此存器 #define REG_STATUS SJA_BaseAdr+0x02
…….
發送緩沖區寄存器
#define REG_TXBuffer1 SJA_BaseAdr+0x10 //發送緩沖區1
#define REG_TXBuffer2 SJA_BaseAdr+0x11 //
#define REG_TXBuffer3 SJA_BaseAdr+0x12 //
#define REG_TXBuffer4 SJA_BaseAdr+0x13 //
#define REG_TXBuffer5 SJA_BaseAdr+0x14 //
#define REG_TXBuffer6 SJA_BaseAdr+0x15 //
#define REG_TXBuffer7 SJA_BaseAdr+0x16 //
#define REG_TXBuffer8 SJA_BaseAdr+0x17 //
#define REG_TXBuffer9 SJA_BaseAdr+0x18 //
#define REG_TXBuffer10 SJA_BaseAdr+0x19 //
#define REG_TXBuffer11 SJA_BaseAdr+0x1A //
#define REG_TXBuffer12 SJA_BaseAdr+0x1B //
#define REG_TXBuffer13 SJA_BaseAdr+0x1C //發送緩沖區13
接收緩沖區寄存器
#define REG_RXBuffer1 SJA_BaseAdr+0x10 //接收緩沖區1
#define REG_RXBuffer2 SJA_BaseAdr+0x11 //
#define REG_RXBuffer3 SJA_BaseAdr+0x12 //
#define REG_RXBuffer4 SJA_BaseAdr+0x13 //
#define REG_RXBuffer5 SJA_BaseAdr+0x14 //
#define REG_RXBuffer6 SJA_BaseAdr+0x15 //
#define REG_RXBuffer7 SJA_BaseAdr+0x16 //
#define REG_RXBuffer8 SJA_BaseAdr+0x17 //
#define REG_RXBuffer9 SJA_BaseAdr+0x18 //
#define REG_RXBuffer10 SJA_BaseAdr+0x19 //
#define REG_RXBuffer11 SJA_BaseAdr+0x1A //
#define REG_RXBuffer12 SJA_BaseAdr+0x1B //
#define REG_RXBuffer13 SJA_BaseAdr+0x1C //接收緩沖區13
SJA1000.h中聲明了若干函數 包括:
CAN總線發送數據的流程:
發送數據還有一種寫法:
if ((ReadSJAReg(REG_CAN_SR) & (TBS_BIT|TCS_BIT)) != (TBS_BIT|TCS_BIT))
{ status = 0;}
查看SJA1000資料 有以下要點:
-
SJA1000 的peilican模式的發送是單次發送
(2)與發送有關的狀態寄存器的各位定義
位 |
符號 |
名稱 |
值 |
功能 |
SR.5 |
Ts |
發送狀態 注3 |
1 |
發送 sja1000 在傳送信息 |
0 |
空閑 沒有要發送的信息 |
|||
SR.3 |
Tcs |
發送完畢狀態 注4 |
1 |
完畢 最近一次發送請求被成功處理 |
0 |
未完畢 當前發送請求未處理完畢 |
|||
SR.2 |
Tbs |
發送緩沖區狀態 注5 |
1 |
釋放:CPU可以向發送緩存器寫數據 |
0 |
鎖定:CPU不能訪問發送緩沖器,有信息正在等待 發送或者正在發送 |
注3:如果接收狀態位和發送狀態位 都是0 ,則CAN總線是空閑的。
注4:無論何時發送請求位被置為1,發送完畢位(Tcs)都會被置為0,發送完畢位會一直保持到消息被成功發送。
注5:如果CPU在發送緩沖器狀態為是0時(鎖定)試圖寫發送緩沖器,則寫入的字節被拒絕接收且會在無任何提示的情況下丟失。
與485通訊比較,485發送出去的數據 若接收方沒有安裝,主機依然顯示發送成功,相比較CAN,CAN發送數據給另一個節點,則CAN節點在應答場會給主機CAN節點一個信號,表示主節點的CAN發送成功。
關於ican.c 中的應用
首先 我用結構體定義 iCANMSG 數據類型
在SJA.C 中定義了 icanmsg 數據類型的變量
iCANMSG message1 ;
iCANMSG * pcan ;
iCANMSG msg_readonly_s;
此外:對於ican協議我專門定義了指針 pcan 並用宏定義去進行處理,這樣很方便的與29位ID號所對應的標識符號對應上。
然后在ican.c中因為 用到了這些變量 全部在前面加上 extern
在main.c 里 我用
至於為什么要在main () 文件 的開頭 定義 msg_readonly_s
iCANMSG msg_readonly_s; //保存副本
是因為 如果出現如下情況 相當於是一個臨時變量,
關於使用位閾型結構體的總結:
ICAN協議:
遵循原則 第 條我認為不一定對,因為在ican下 我infoID定義8位
但是ican下 我的低3位 是沒有被定義的 undef
Ican 協議的格式定義如下:
幀結構信息
位 |
BIT7 |
BIT6 |
BIT5 |
BIT4 |
BIT3 |
BIT2 |
BIT1 |
BIT0 |
說明 |
FF |
RTR |
X |
X |
DLC.3 |
DLC.2 |
DLC.1 |
DLC.0 |
幀標識符信息
幀 標 識 符 |
ID28 |
ID27 |
ID26 |
ID25 |
ID24 |
ID23 |
ID22 |
ID21 |
|
00 |
SRCMACID(資源節點編號) |
||||||||
ID20 |
ID19 |
ID18 |
ID17 |
ID16 |
ID15 |
ID14 |
ID13 |
||
00 |
DestMACID(目標節點編號) |
||||||||
ID12 |
ID11 |
ID10 |
ID9 |
ID8 |
ID7 |
ID6 |
ID5 |
||
ACK |
FUNCID(功能碼) |
SourceID(資源節點編號) |
|||||||
ID4 |
ID3 |
ID2 |
ID1 |
ID0 |
X |
X |
X |
||
SourceID(資源節點編號) |
未使用(忽略) |
我定義的方法如下:
但是 我在應用j1939協議的時候
J1939協議 所定義的幀信息ID結構如下 (29位擴展)
ID28 |
ID27 |
ID26 |
ID25 |
ID24 |
ID23 |
ID22 |
ID21 |
優先級 |
保留位 |
數據頁 |
PDU格式 |
||||
ID20 |
ID19 |
ID18 |
ID17 |
ID16 |
ID15 |
ID14 |
ID13 |
PDU格式 |
特定PDU |
||||||
ID12 |
ID11 |
ID10 |
ID9 |
ID8 |
ID7 |
ID6 |
ID5 |
特定PDU |
源地址 |
||||||
ID4 |
ID3 |
ID2 |
ID1 |
ID0 |
X |
X |
X |
源地址 |
我的定義方法如下: 從低位到高位定義
假設 節點2 和節點3正在通訊,某一時刻節點1要設置節點3 。給節點3發數據,
節點1不用等到節點2和節點3不通訊了。節點1直接發送數據,節點1 的CAN硬件會自動控制給節點1發數據。不用人為控制,由CAN控制器的硬件來完成。
基於51單片機的CAN通訊試驗:
方法: 51單片機程序中不斷的往上位機(CANtest)發送數據 ,然后在某一任意時刻, 我用周立功的(CANtest)發送建立連接命令,看單片機是否可以正常響應。 並且記錄示波器的波形圖。
在CANTEST 上 點擊 發送消息幀 如下圖 第2個較短的時間間隔內的幀
該消息的ID號是 0x0023e4fe 數據場是 00 ee 0a
51單片機接收到消息以后,往上傳送消息 該消息的數據場 00 01 02 03 04
如下圖所示: 左側第一個較短的幀 就是 51單片機上傳的響應幀。數據場為00 01 02 03 04
響應場 的數據 (該數據我先不解析)
為了驗證我用kavaser 捕捉以下時間間隔 。
看兩個時間間隔
第一 就是上位機 發送建立連接命令 到收到51單片機 返回的響應幀的時間間隔
第二 就是51 單片機 返回響應幀 到 51單片機繼續往上位機傳送計數值的時間間隔
第三 測試 看一下 默認情況下 上位機不發送連接命令,51單片機上傳數據的時間間隔
第一個時間 我用示波器測試是: 約為200ms
第二個時間 我用示波器測試是: 約為1.5ms
第三個時間 我用示波器測試是: 約為12.4ms
我用kavaser 在 20190423 的 9點32 和 9點33 分左右的時候分別用cantest 發送建立連接命令 接收的時間間隔是 6041-4021=2021
2021*百分之一毫秒 約等於 200ms 與示波器測試一致
接下來 我用kavaser 的logging 功能測試
時刻 9點32 的數據
9點33時刻的數據
接下來 我的想法是 你新找一個51單片機 ,然后替換 周立功上位機的功能,進行連接命令的發送
試驗平台大家如下:
試驗平台照片
實際上 CAN 網絡是不分主機和從機的,不像485網絡。這里我設計的主機的功能就是:
按下:靠4個數碼管一側的按鍵, 按一下 數碼管的顯示增加1 然后並發送一幀
發送的消息幀 為 ID號 0x0023e4fe 數據場是00 ee 20 (16進制的20代表十進制32
計數 32次,認為握手時間是32秒,超過32秒可以認為連接斷開)
做這個事情的目的是:消息幀的發送我在用嵌入式編程的時候,用can_send_anylength()函數就可以搞定。這種情況使用於網絡中一直有數據通訊存在的情況。
試驗現象:
在時間10點24
我用主機(51單片機)的按鍵 發送消息幀 ID號0x0023e4fe 數據 00 ee 2 0
從機51單片機 在1秒以后 反饋給我響應幀 ID號0X3E034EE 數據是 00 01 02 03 04
在這個1秒的時間間隔內,CAN數據線上 還有一幀消息在傳遞 如下圖所示:
若在32秒內,主機再次發送連接命令,。從機將給主機反饋 已經在連接中的提示消息
該消息 的ID號是 0X3E02FFE 00 03
在時間:10:30:10:7323 我又用主機發送了 建立連接的消息
ID號是 0x023e4fe 00 ee 20
此時在10:30:10:8433 時刻 從機就給主機回復了消息幀 在這個時間間隔內,無其他幀在傳遞。 如下圖:
重要:與上面的那個中間有一幀的情況的截圖進行對比:可以知道:從機在接收到主機的連接,命令后,會判斷CAN線上是否空閑,如果當前有數據發送或接收 從機就等該數據發送完畢以后,在發送響應幀, 如果CAN線上空閑,則從機便可以直接發送給主機器響應幀。發送程序的時候 從機程序僅僅檢測 是不是上一幀數據是不是發送完成,並不檢測總線上空閑,這一塊是CAN控制器硬件自動完成的,我暫且先這么認為。
在時間 10時30分 13秒 在32秒的計時時間內, 我再次發送建立連接命令,此時 從機
便會給我回復響應的消息幀 幀ID號 0x 3e02ffe 00 03 如下圖所示:
Word 源文件在百度網盤