打开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_istientification ); |
| } |
- 步骤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示例。