# can_comm
neozng1@hnu.edu.cn
> TODO: > > 1. 对`CANCommGet()`进行修改,使得其可以返回数据是否更新的相关信息。 ## 重要提醒 如果传输过程中出现多次丢包或长度校验不通过,尤其是传输长度较大的时候,请开启CAN的Auto Retransmission,并尝试修改CANComm实例的发送和接受ID(以提高在总线仲裁中的优先级)。 ## 总览和封装说明 CAN comm是用于CAN多机通信的模块。你不需要关心实现的协议,**只需要传入你想发送/接收的结构体的长度**(通过`sizeof()`获取)即可。单帧发送长度目前限制为60字节,如果需要加长可以在`can_comm.h`中修改`CAN_COMM_MAX_BUFFSIZE`的定义值。注意CAN的负载以及使用此模块的发送频率,否则可能造成堵塞。 ## 代码结构 .h中放置的是数据定义和外部接口,以及协议的定义和宏,.c中包含一些私有函数。 ## 外部接口 ```c CANCommInstance *CANCommInit(CANComm_Init_Config_s* comm_config); void CANCommSend(CANCommInstance *instance, uint8_t *data); void *CANCommGet(CANCommInstance *instance); uint8_t CANCommIsOnline(CANCommInstance *instance); ``` 第一个函数将会初始化一个CANComm实例,返回其指针。使用CANComm进行通信的应用应该保存返回的指针。初始化需要传入一个初始化结构体。请在应用初始化的时候调用该函数。推荐的结构体配置方式如下: ```c /* 初始化结构体的定义 */ typedef struct { can_instance_config_s can_config; uint8_t send_data_len; //发送数据长度 uint8_t recv_data_len; //接收数据长度,两者请用sizeof获取 } CANComm_Init_Config_s; CANComm_Init_Config_s cconfig = { .can_config = { .can_handle=&hcan1, .tx_id=0x02, .rx_id=0x03}, .send_data_len = sizeof(your_data_structure), .recv_data_len = sizeof(recv_data_structure) }; ``` 第二个函数将会通过CAN发送一帧数据。这里的“帧”不是CAN的一帧报文,而是你使用的数据。在CANComm初始化的时候,使用sizeof获得你要发送的数据的长度。调用此函数时,将你的数据结构或数据cast成`u8`类型的指针传入即可。==**特别注意,你的结构体需要使用预编译指令`#pragma pack(1)`以及`#pragma pack()`包裹**==,从而进行字节压缩而不进行字节对齐,如: ```c #pragma pack(1) typedef struct { uint8_t aa; float bb; double cc; uint16_t dd; } some_struct; #pragma pack() ``` 只有这样,这个结构体才不会进行padding扩充字节实现字节对齐。你可以尝试一下,如果不使用pack处理,上面的结构体将会占据18个字节以上的空间;开启pack后,会降低至15。更多关于字节对齐的内容,自行查询。 > 后期可能更新字节对齐和内存访问相关的教程。 `CANCommGet()`是获取来自CANComm接收到的数据的接口,返回值为一个void类型指针,你需要通过**强制类型转换**将其变为你设定的接收类型指针,然后就可以访问其数据了。 ## 私有函数和变量 ```c static CANCommInstance *can_comm_instance[MX_CAN_COMM_COUNT] = {NULL}; static uint8_t idx; static void CANCommResetRx(CANCommInstance *ins); static void CANCommRxCallback(can_instance *_instance); ``` 第一个指针数组保存所有的can comm实例,从而在callback中区分不同实例。idx是初始化使用的索引,用于确定每个can comm实例指针存放的位置。 `CANCommResetRx()`在`CANCommRxCallback()`中多次被调用,用于清空接收缓冲区。 `CANCommRxCallback()`是CAN comm初始化can实例时的回调函数,用于can接收中断,进行协议解析。 ## 使用范例 例如,这里要发送的数据是一个float,接收的数据是如下的`struct`,**==注意要使用pack==**: ```c #pragma pack(1) struct test { float aa; float bb; float cc; uint16_t dd; }; #pragma pack() ``` 初始化时设置如下: ```c CANComm_Init_Config_s cconfig = { .can_config = { .can_handle=&hcan1, .tx_id=0x02, .rx_id=0x03}, .send_data_len = sizeof(float), .recv_data_len = sizeof(struct test) }; CANCommInstance* ins = CANCommInit(&cconfig); ``` 通过`CANCommGet()`并使用强制类型转换获得接收到的数据指针: ```c struct test* data_ptr=(struct test*)CANCommGet(ins) ``` 发送通过`CANCommSend()`,建议使用强制类型转换: ```c float tx=114.514; CANCommSend(ins, (uint8_t*)&tx); ``` ## 接收解析流程 CAN comm的通信协议如下: | 帧头 | 数据长度 | 数据 | crc8校验 | 帧尾 | | ----------- | -------- | ---------------- | -------- | ----------- | | 's'(0x73) | 0~255 | 最大60(可修改) | 校验码 | 'e'(0x65) | | 1-byte | 1-byte | n-byte | 1-byte | 1-byte | 接收的流程见代码注释。 流程图如下:![未命名文件](../../.assets/CANcomm.png)