engineering/modules/can_comm/can_comm.md

141 lines
4.9 KiB
Markdown
Raw Normal View History

2024-04-25 23:12:31 +08:00
# can_comm
<p align='right'>neozng1@hnu.edu.cn</p>
> 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)