打開SAE J1939
SAE J1939是一種以特定方式塑造CAN-BUS消息的協議,該方式適合拖拉機,機械,卡車等工業車輛。
SAE J1939是一個非常容易使用的協議,但是由於協議文檔的成本,缺乏有關SAE J1939的信息,因此如何根據SAE J1939協議標準來構建CAN-BUS消息。因此,我正在編寫可免費在任何嵌入式系統(例如STM32,Arduino,AVR,PIC等)的嵌入式系統上免費使用的SAE J1939協議。
要學習在這個項目上,您首先需要了解SAE J1939。我已經用C語言編寫了這個項目,因為C是行業標準。我選擇的C語言方言是ANSI C (C89) ,我不使用此庫中的動態內存分配。因此,它將與MISRA C標準配合使用。
借助此圖書館,您可以與閥門,發動機,執行器,機械,硬件以及適合重型工業移動應用程序的所有其他物品進行通信。我已經建立了該項目的基本結構,希望其他用戶將其C代碼的拉動請求發送到SAE J1939標準,因為SAE J1939是一個巨大的標準。
尋找用於嵌入式系統的C Canopen庫? https://github.com/danielmartensson/easy-canopen
尋找在USB上使用開放式SAE J1939的C ++ GUI框架? https://github.com/danielmartensson/goobysoft
尋找帶有SAE J1939的C STM32項目? https://github.com/danielmartensson/stm32-plc
入門
您需要知道的第一件事是在Documentation文件夾中閱讀我自己的PDF文檔。了解項目的結構,否則您將無法理解SAE J1939。在對項目有基本的了解之後,您就可以在其基礎上進行構建。保持簡單,並遵循SAE J1939標準!
了解項目的結構後,然後在Hardware -> Hardware.h文件中選擇“處理器”選擇。在這裡,您可以選擇例如STM32 , Arduino , PIC , AVR等。或者如果您想先在PC上運行它,請選擇PROCESSOR_CHOICE 0並運行一些示例。這是內部CAN反饋的調試模式。
如何使用項目
- 步驟1:下載此存儲庫
- 步驟2:轉到
Hardware -> Hardware.h - 步驟3:將
Src文件夾複製到IDE內部的項目文件夾。將Src重命名為例如Open SAE J1939 。那是個好名字。 - 步驟4:使用
Examples -> Open SAE J1939 -> Main.txt示例作為SAE J1939項目的初始起始代碼。
/*
* Main.c
*
* Created on: 16 juli 2021
* Author: Daniel Mårtensson
*/
#include <stdio.h>
/* Include Open SAE J1939 */
#include "Open_SAE_J1939/Open_SAE_J1939.h"
/* Include ISO 11783 */
#include "ISO_11783/ISO_11783-7_Application_Layer/Application_Layer.h"
void Callback_Function_Send ( uint32_t ID , uint8_t DLC , uint8_t data []) {
/* Apply your transmit layer here, e.g:
* uint32_t TxMailbox;
* static CAN_HandleTypeDef can_handler;
* This function transmit ID, DLC and data[] as the CAN-message.
* HardWareLayerCAN_TX(&can_handler, ID, DLC, data, &TxMailbox);
*
* You can use TCP/IP, USB, CAN etc. as hardware layers for SAE J1939
*/
}
void Callback_Function_Read ( uint32_t * ID , uint8_t data [], bool * is_new_data ) {
/* Apply your receive layer here, e.g:
* CAN_RxHeaderTypeDef rxHeader = {0};
* static CAN_HandleTypeDef can_handler;
* This function read CAN RX and give the data to ID and data[] as the CAN-message.
* if (HardWareLayerCAN_RX(can_handler, &rxHeader, ID, data) == STATUS_OK){
* *is_new_data = true;
* }
*
* You can use TCP/IP, USB, CAN etc. as hardware layers for SAE J1939
*/
}
/* This function reads the CAN traffic */
void Callback_Function_Traffic ( uint32_t ID , uint8_t DLC , uint8_t data [], bool is_TX ) {
/* Print if it is TX or RX */
printf ( "%st" , is_TX ? "TX" : "RX" );
/* Print ID as hex */
printf ( "%08Xt" , ID );
/* Print the data */
uint8_t i ;
for ( i = 0U ; i < DLC ; i ++ ) {
printf ( "%Xt" , data [ i ]);
}
/* Print the non-data */
for ( i = DLC ; i < 8U ; i ++ ) {
printf ( "%Xt" , 0U );
}
/* New line */
printf ( "n" );
}
/* Apply your delay here */
void Callback_Function_Delay ( uint8_t delay ){
/* Place your hardware delay here e.g HAL_Delay(delay); for STM32 */
}
int main () {
/* Create our J1939 structure */
J1939 j1939 = { 0 };
/*
* Callbacks can be used if you want to pass a specific CAN-function into the hardware layer.
* All you need to do is to enable INTERNAL_CALLLBACK inside hardware.h
* If you don't want to have the traffic callback, just set the argument as NULL.
* If you don't want any callback at all, you can write your own hardware layer by selecting a specific processor choice at hardware.h
*/
CAN_Set_Callback_Functions ( Callback_Function_Send , Callback_Function_Read , Callback_Function_Traffic , Callback_Function_Delay );
/* Load your ECU information */
Open_SAE_J1939_Startup_ECU ( & j1939 );
/* SAE J1939 process */
bool run = true;
while ( run ) {
/* Read incoming messages */
Open_SAE_J1939_Listen_For_Messages ( & j1939 );
/* Your application code here */
}
/* Save your ECU information */
Open_SAE_J1939_Closedown_ECU ( & j1939 );
return 0 ;
}請參閱Examples -> SAE J1939如何更改ECU的地址,名稱或標識。
項目的結構
一個工作示例,如何完成開放SAE J1939的結構
代碼中此流程圖開放的SAE J1939庫如何工作。此示例演示瞭如何發送請求並獲得答案。
- 步驟1:
ECU X將向ECU Y發送PGN 。將PGN解釋為功能代碼。 | ENUM_J1939_STATUS_CODES SAE_J1939_SEND_REQUEST_ECU_INDICALIGY ( J1939 * J1939 , UINT8_T DA ){ |
| 返回SAE_J1939_SEND_REQUEST ( J1939 , DA , PGN_ECU_INDICALIGIAD ); |
| } |
- 步驟2:
ECU Y將從ECU X讀取該PGN消息。 | bool is_new_message = can_read_message ( & id , data ); |
| 如果( is_new_message ){ |
| / *保存最新 */ |
| J1939- > ID = ID ; |
| memcpy ( J1939- >數據,數據, 8 ); |
| j1939- > id_and_data_is_updated = true; |
| |
| uint8_t id0 = id >> 24 ; |
| uint8_t id1 = id >> 16 ; |
| uint8_t da = id >> 8 ; /*目標地址是此ECU。如果da = 0xff =向所有ECU廣播。有時DA也可以是ID號 */ |
| uint8_t sa = id ; / *我們從 */收到消息的ECU的源地址 |
| |
| / *閱讀其他ECU的請求 */ |
| if ( id0 == 0x18 && id1 == 0xea && ( da == j1939- > inovy_this_ecu 。 |
| SAE_J1939_READ_REQUEST ( J1939 , SA , DATA ); |
- 步驟3:
PGN功能代碼將通過ECU Y解釋。 | void sae_j1939_read_request ( j1939 * j1939 , uint8_t sa , uint8_t data []){ |
| uint32_t pgn = (數據[ 2 ] << 16 )| (數據[ 1 ] << 8 )|數據[ 0 ]; |
| if ( pgn == pgn_acknowledgement ){ |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); |
| } else if ( pgn == pgn_address_claimed ){ |
| sae_j1939_response_request_address_claimed ( j1939 ); |
| } else if ( pgn == pgn_commanded_address ){ |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); |
| } else if ( pgn == pgn_address_delete ){ |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); / *不是SAE J1939標準 */ |
| } else if ( pgn == pgn_dm1 ){ |
| sae_j1939_response_request_dm1 ( j1939 , sa ); |
| } else if ( pgn == pgn_dm2 ){ |
| sae_j1939_response_request_dm2 ( j1939 , sa ); |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); |
| } else if ( pgn == pgn_dm3 ){ |
| sae_j1939_response_request_dm3 ( j1939 , sa ); |
| } else if ( pgn == pgn_request ){ |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); |
| } else if ( pgn == pgn_tp_cm ){ |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); |
| } else if ( pgn == pgn_tp_dt ){ |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_acknowledgement_pgn_supported , group_function_value_normal , pgn ); |
| } else if ( pgn > = pgn_auxiliary_valve_estimated_flow_0 && pgn <= pgn_auxiliary_valve_estimated_flow_15 ){ |
| ISO_11783_RESPONSE_REQUST_AUXILIARY_VALVE_ESTIMATED_FLOW ( J1939 , PGN & 0XF ); / * pgn&0xf = valve_number */ |
| } else if ( pgn == pgn_general_purpose_valve_estimated_flow ){ |
| iso_11783_Response_request_general_purpose_valve_estimated_flow ( j1939 , sa ); |
| } else if ( pgn > = pgn_auxiliary_valve_measured_position_0 && pgn <= pgn_auxiliary_valve_measured_position_15 ){ |
| iso_11783_Response_request_auxiliary_valve_measured_position ( j1939 , pgn & 0xf ); / * pgn&0xf = valve_number */ |
| } else if ( pgn == pgn_software_ientification ){ |
| sae_j1939_response_request_software_istification ( j1939 , sa ); |
| } else if ( pgn == pgn_ecu_istientification ){ |
| sae_j1939_response_request_ecu_istification ( j1939 , sa ); |
- 步驟4:現在通過
ECU Y將PGN功能代碼解釋為ECU Identification 。然後, ECU Y將向所有ECUs廣播ECU Identification 。- 步驟4.1.1:對於1個軟件包消息,
ECU Y將廣播ECU Identification 。 | ENUM_J1939_STATUS_CODES SAE_J1939_RESPONSE_REQUST_REQUEST_ECU_INDICALIGY ( J1939 * J1939 , UINT8_T DA ){ |
| / *找到數組字段的長度 */ |
| uint8_t length_of_each_field = j1939- > inoverle_this_ecu 。 this_Identifications 。 ECU_識別。 length_of_each_field ; |
| 如果( length_of_each_field < 2 ){ |
| / *如果每個字段具有長度1,則可以發送ECU標識,因為這是正常消息 */ |
| uint32_t id = ( 0x18fdc5 << 8 )| J1939- > Inloge_this_ecu 。 this_ecu_address ; |
| uint8_t數據[ 8 ]; |
| 數據[ 0 ] = J1939- > Information_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_part_number [ 0 ]; |
| 數據[ 1 ] = J1939- > Information_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_serial_number [ 0 ]; |
| 數據[ 2 ] = J1939- > Information_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_location [ 0 ]; |
| 數據[ 3 ] = J1939- > Information_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_type [ 0 ]; |
| 數據[ 4 ] = 0xff ; /* 預訂的 */ |
| 數據[ 5 ] = 0xff ; /* 預訂的 */ |
| 數據[ 6 ] = 0xff ; /* 預訂的 */ |
| 數據[ 7 ] = 0xff ; /* 預訂的 */ |
| 返回CAN_SEND_MESSAGE ( ID ,數據); |
| }別的{ |
- 步驟4.1.2:
ECU X閱讀ECU Y的響應,因為ECU Identification是廣播的。 | } else if ( id0 == 0x18 && id1 == 0xfd && da == 0xc5 ){ |
| sae_j1939_read_response_request_ecu_istification ( j1939 , sa , data ); |
- 步驟4.2.1:對於多軟件包消息,控製字節可以是BAM或RTS。僅當您發送到所有
ECUs例如地址0xFF = 255 ,才使用BAM。但是,如果控件字節是RTS,例如地址不是0xFF ,那麼ECU Y將發送RTS並通過ECU X收聽CTS響應。 RTS是一個問題:“讓我知道何時可以傳輸消息?” CTS是響應Now you can transmit the message to me 。 | J1939- > this_ecu_tp_cm 。 total_message_size = 0 ; |
| uint8_t i ; |
| for ( i = 0 ; i < length_of_each_field ; i ++ ){ |
| J1939- > this_ecu_tp_dt 。 data [ i ] = J1939- > Information_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_part_number [ i ]; |
| J1939- > this_ecu_tp_dt 。 data [ length_of_each_field + i ] = j1939- > Information_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_serial_number [ i ]; |
| J1939- > this_ecu_tp_dt 。 data [ length_of_each_field * 2 + i ] = j1939- > inoverle_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_location [ i ]; |
| J1939- > this_ecu_tp_dt 。 data [ length_of_each_field * 3 + i ] = j1939- > inoverle_this_ecu 。 this_Identifications 。 ECU_識別。 ecu_type [ i ]; |
| J1939- > this_ecu_tp_cm 。 total_message_size += 4 ; |
| } |
| |
| / *發送TP CM */ |
| J1939- > this_ecu_tp_cm 。 number_of_packages = j1939- > this_ecu_tp_cm 。 total_message_size % 8 > 0 ? J1939- > this_ecu_tp_cm 。 total_message_size / 8 + 1 : j1939- > this_ecu_tp_cm 。 total_message_size / 8 ; / *四捨五入 */ |
| J1939- > this_ecu_tp_cm 。 pgn_of_the_packeted_message = pgn_ecu_istinefication ; |
| J1939- > this_ecu_tp_cm 。 control_byte = da == 0xff ? control_byte_tp_cm_bam : control_byte_tp_cm_rts ; / *如果廣播,請使用BAM控件字節 */ |
| ENUM_J1939_STATUS_CODES status = SAE_J1939_SEND_SEND_TRANSPORT_PROTOCOL_CONNECTION_MANAGEME ( J1939 , DA ); |
- 步驟4.2.2:如果
ECU Y發送RTS,則ECU X將讀取RTS並使用CTS返回到ECU Y | if ( j1939- > from_other_ecu_tp_cm。control_byte == control_byte_tp_cm_rts ) { |
| J1939- > this_ecu_tp_cm = J1939- > from_other_ecu_tp_cm ; / *複製 - 我們需要擁有相同的數據 */ |
| J1939- > this_ecu_tp_cm 。 control_byte = control_byte_tp_cm_cts ; / *我們只需要將控件字節從RTS更改為CTS */ |
| sae_j1939_send_transport_protocol_connection_management ( J1939 , SA ); |
| } |
- 步驟4.2.3:一旦
ECU Y收到了CTS,然後將數據傳輸到ECU Identification ,回到ECU X | if ( j1939- > from_other_ecu_tp_cm。control_byte == control_byte_tp_cm_cts ){ |
| SAE_J1939_SEND_TRANSPORT_PROTOCOL_DATA_TRANSFER ( J1939 , SA ); |
| } |
- 步驟4.2.3:一旦
ECU Y在包裝後發送包... | ENUM_J1939_STATUS_CODES SAE_J1939_SEND_TRANSPORT_PROTOCOL_DATA_TRANSFER ( J1939 * J1939 , UINT8_T DA ){ |
| uint32_t id = ( 0x1ceb << 16 )| ( da << 8 )| J1939- > Inloge_this_ecu 。 this_ecu_address ; |
| uint8_t i , j ,軟件包[ 8 ]; |
| uint16_t bytes_sent = 0 ; |
| enum_j1939_status_codes status = status_send_ok ; |
| for ( i = 1 ; i <= j1939- > this_ecu_tp_cm。number_of_packages ; i ++ ){ |
| 軟件包[ 0 ] = i ; / *包裝數 */ |
| for ( j = 0 ; j < 7 ; j ++ ){ |
| if ( bytes_sent <j1939-> this_ecu_tp_cm。total_message_size ) { |
| 軟件包[ J + 1 ] = J1939- > this_ecu_tp_dt 。數據[ bytes_sent ++ ]; / *我們收集的數據 */ |
| }別的{ |
| 軟件包[ J + 1 ] = 0xff ; /* 預訂的 */ |
| } |
| } |
| status = can_send_message ( id , package ); |
| can_delay ( 100 ); / *重要的可能會根據標準延遲 */ |
| if ( status != status_send_ok ){ |
| 返回狀態; |
| } |
| } |
| 返回狀態; |
| } |
- 步驟4.2.4:然後
ECU X通過了解PNG功能代碼來收回每個軟件包並構建消息。 | void sae_j1939_read_transport_protocol_data_transfer ( j1939 * j1939 , uint8_t sa , uint8_t data []){ |
| / *保存序列數據 */ |
| j1939- > from_other_ecu_tp_dt 。 sequence_number = data [ 0 ]; |
| j1939- > from_other_ecu_tp_dt 。 from_ecu_address = sa ; |
| uint8_t i , j , index = data [ 0 ] -1 ; |
| for ( i = 1 ; i < 8 ; i ++ ){ |
| j1939- > from_other_ecu_tp_dt 。數據[索引* 7 + i -1 ] =數據[ i ] ; / *對於每個軟件包,我們發送7個字節的數據,其中第一個字節數據[0]是序列編號 */ |
| } |
| / *檢查我們是否已經完成了消息 - 返回=未完成 */ |
| if ( j1939- > from_other_ecu_tp_cm。 |
| 返回; |
| } |
| |
| / *我們的消息已經完成 - 構建它並將其稱為完整_data [total_message_size] */ |
| uint32_t pgn = j1939- > from_other_ecu_tp_cm 。 pgn_of_the_packeted_message ; |
| uint16_t total_message_size = j1939- > from_other_ecu_tp_cm 。 total_message_size ; |
| uint8_t完整_data [ max_tp_dt ]; |
| uint16_t inserted_bytes = 0 ; |
| for ( i = 0 ; i <j1939-> from_other_ecu_tp_dt。sequence_number ; i ++ ) { |
| for ( j = 0 ; j < 7 ; j ++ ){ |
| if ( inserted_bytes < total_message_size ){ |
| 完整_data [ inserted_bytes ++ ] = j1939- > from_other_ecu_tp_dt 。數據[ I * 7 + J ]; |
| } |
| } |
| } |
| |
| / *發送消息的末尾ACK回扣 */ |
| if ( j1939- > from_other_ecu_tp_cm。control_byte == control_byte_tp_cm_rts ) { |
| sae_j1939_send_acknowledgement ( j1939 , sa , control_byte_tp_cm_endofmsgack , group_function_value_normal , pgn ); |
| } |
然後最終實現消息。 | 案例pgn_ecu_識別: |
| sae_j1939_read_response_request_ecu_istification ( j1939 , sa , pounttre_data ); |
| 休息; |
SAE J1939功能
- SAE J1939:21運輸層
- 致謝
- 要求
- 使用BAM,CTS,RTS和EOM的運輸協議連接管理
- 傳輸協議數據傳輸
- SAE J1939:71應用程序層
- 請求組件標識
- 請求ECU標識
- 請求軟件標識
- 請求專有a
- 請求專有b
- SAE J1939:73診斷層
- DM1
- DM2
- DM3
- DM14
- DM15
- DM16
- SAE J1939:81網絡管理層
額外的功能
- ISO 11783農業和林業機械
- ISO 11783-7實施消息應用程序層
- 輔助閥命令
- 輔助閥估計流量
- 輔助閥測量的位置
- 通用閥命令
- 通用閥估計流量
問答
- 問:該庫可以與
C++一起使用嗎? - 問:我想在這個圖書館上建立,該怎麼辦?
- 答:首先,您需要了解
ANSI C (C89)和位操作。然後,您需要了解SAE J1939:21 Transport Layer結構。不要忘記使用新功能更新PDF。
- 問:我可以在我的arduino上使用它嗎?
- 答:是的,此
C代碼是100%純C代碼,僅使用C標準庫,而且該代碼也不考慮您使用的硬件。
- 問:我需要安裝庫以便使用庫嗎?
- 答:不,只需將
.c和.h文件複製到您的項目並編譯即可。我已將其與QT框架一起使用。
- 問:這個項目現在已經很舊了,而且更新還沒有太多,是否值得使用它?
- 答:是的,僅當我或其他包含SAE J1939的更多功能時,此庫才會更新。我在
ANSI C (C89)中寫這本書的原因是因為它是行業標準,您將始終能夠編譯該庫並將其用於所有系統。
- 問:您對圖書館的計劃是什麼?
- 問:我沒有CAN-BUS,但是我是否可以將此庫與UART,USB,WiFi等一起使用嗎?
- 問:即使我沒有Can-Bus,我也可以在此庫中發送數據嗎?
- 答:是的。有一種叫做DM14發送請求,DM15狀態響應和DM16二進制傳輸的東西。如果您想以工業方式傳輸數據,請使用它。
- 問:我可以同時將多個包裝消息從多個ECU發送到一個ECU嗎?
- 答:不。如果您開始將多包從多個ECU發送到另一個ECU,那麼ECU將無法理解該消息。如果目標地址相同,則僅在當時發送多包消息。
- 問:我不想使用開放式SAE J1939的
ANSI C (C89) 。我可以使用開放式SAE J1939的最新C標準嗎? - 問:是否可以將此庫編譯到Windows MS-DOS或Windows 95機器上?
- 問:我可以調整程序所佔據的內存嗎?
- 答:是的,您可以根據您的用例調整定義的
MAX_PROPRIETARY_A , MAX_PROPRIETARY_B和MAX_PROPRIETARY_B_PGNS在文件Structs.h中,根據您的用例。提供了“理智”默認值,但是如果不需要專有PGN支持,則可以將它們設置為最小值,或者如果需要使用更多的PGN,則可以將其設置為最小值。
問題和答案
- I:我無法編譯此庫。我正在使用
Keil Microvision 。- 答:
Keil Microvision無法處理二進制數字,例如0b010101 。嘗試使用STM32CubeIDE ,因為Open SAE J1939是在STM32CubeIDE中製造的
- I:您能為我們提供一些硬件示例,例如
STM32嗎?- 答:是的!有一個STM32示例如何與Can-Bus連接,包括中斷聽眾的消息。轉到
Examples -> Hardware文件夾中的“查找CAN_STM32.txt 。此外,還有一個QT C++的USB示例。