CAN SDK
CAN协议简介
CAN(Controller Area Network)是一种多主机、高效、实时的串行通信协议,具有较高的容错能力、实时性、低成本和高效性,因此非常适用于需要稳定和实时数据传输的嵌入式系统。
特点
- 多主机通信:CAN协议支持多主机架构,多个节点(设备)可以同时连接在同一总线上。
- 实时性:采用优先级机制,确保高优先级消息先被传输,满足实时要求。
- 高效性:基于消息的通信,支持广播式通信,可以减少总线带宽的占用。
- 容错性:具备自动错误检测、错误处理和错误恢复机制。
- 灵活性:通过不同的总线传输速率(从10kbps到1Mbps)和多种帧格式满足不同应用需求。
工作原理
CAN协议采用CSMA/CD(Carrier Sense Multiple Access with Collision Detection)机制来控制总线访问。每个节点监听总线,当总线空闲时,节点可以发送数据。为了避免总线冲突,CAN使用位级优先级机制,通过消息ID来确定消息的优先级,ID越小的消息优先发送。
数据帧格式
CAN协议的数据帧有两种主要类型:标准帧和扩展帧。
- 标准帧:包含11位标识符(ID)。
- 扩展帧:包含29位标识符(ID)。
数据帧(Data Frame)格式
- 起始位:一个固定的位,用来指示数据帧的开始。
- 标识符:用于区分不同的消息。标准CAN帧使用11位,扩展CAN帧使用29位标识符。
- 控制字段:用于指示数据长度(通常为0到8字节)。
- 数据字段:实际传输的数据内容,最大长度为8字节。
- CRC校验:用于检测数据传输是否发生错误。
- 应答位:接收节点确认是否成功接收到数据。
- 结束位:表示数据帧的结束。
优缺点
优点:
- 高效:提供可靠、快速的通信。
- 容错:具备强大的错误检测与恢复机制。
- 实时性强:优先级机制确保实时性。
- 拓展性好:节点可动态添加,不影响现有系统。
缺点:
- 带宽有限:尽管CAN非常高效,但其带宽(最大1 Mbps)仍有限,不能满足所有高带宽需求。
- 网络规模有限:随着节点数量增加,网络可能会出现延迟和带宽瓶颈。
- 不支持复杂的数据类型:CAN协议以字节为单位传输数据,复杂数据类型需要额外的编码和解码工作。
硬件接口
接口说明
设备共提供2路CAN端口供您使用,其接口定义为
端口序号 | 端口丝印 | 系统文件名 |
---|---|---|
CAN-1 | CAN-H1/CAN-L1 | awlink0 |
CAN-2 | CAN-H2/CAN-L2 | awlink1 |
接线方式
H 和 L 分别连接到目标设备的对应端子
API说明
请您确保已经将SDK引入到项目中,SDK的相关引入可参考SDK安装章节。
SDK中封装了基础的CAN接口操作,开发人员可以通过相关API快速的控制端口的打开、关闭,进行接口消息的读写。
使用CAN接口相关操作函数,需要引用 can.h
头文件。
can_init
功能
打开指定的485串口
函数原型
/**
* @brief CAN接口初始化
*
* @param[in] ch CAN接口序号,1~2
* @param[in] bps CAN接口波特率
*
* @return
* - 1: 成功
* - -1: 初始化失败
* - -2: 授权校验失败
*
*/
int can_init(int ch, int bps);
参数
参数名 | 类型 | 说明 |
---|---|---|
ch | int | CAN接口序号,1~2 |
bps | int | 端口波特率,如500000 |
返回值
- 成功:1
- 失败:
- 初始化失败:-1
- 授权校验失败:-2
can_write
功能
CAN接口发送数据
函数原型
/**
* @brief CAN接口发送数据
*
* @param ch[in] CAN接口序号,1~2
* @param id[in] 发送的帧ID
* @param buf[in] 发送的帧数据
* @param len[in] 发送的帧数据长度
*
* @return
* - >0: 写入数据长度
* - -1: 失败
*/
int can_write(int ch, u32 id, u8 *buf, int len);
参数
参数名 | 类型 | 说明 |
---|---|---|
ch | int | CAN接口序号,1~2 |
id | u32 | 发送的帧ID |
buf | u8 | 发送的帧数据 |
len | int | 发送的帧数据长度 |
返回值
- 成功:>0,发送的数据长度
- 失败:-1
can_read
功能
CAN接口读取数据
函数原型
/**
* @brief CAN接口读取数据
*
* @param ch[in] CAN接口序号,1~2
* @param pid[out] 读取到的帧ID
* @param buf[out] 读取到的帧数据
*
* @return
* - >0: 读取数据长度
* - -1: 读取数据失败
*//**
* @brief CAN接口读取数据
*
* @param ch[in] CAN接口序号,1~2
* @param pid[out] 读取到的帧ID
* @param buf[out] 读取到的帧数据
*
* @return
* - >0: 读取数据长度
* - -1: 读取数据失败
*/
int can_read(int ch, u32 *pid, u8 *buf);
参数
参数名 | 类型 | 说明 |
---|---|---|
ch | int | CAN接口序号,1~2 |
pid | u32 | 读取到的帧ID |
buf | u8 | 读取到的帧数据 |
can_close
功能
CAN接口释放
函数原型
/**
* @brief CAN释放
*
* @param ch[in] CAN接口序号,1~2
*
* @return
* - 1: 成功
* - -1: 失败
*/
int can_close(int ch);
参数
参数名 | 类型 | 说明 |
---|---|---|
ch | int | CAN接口序号,1~2 |
示列代码
以下以控制第1路CAN端口为例,进行相关API操作的说明
#include "can.h"
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
/**
* 模拟CAN消息循环发送线程
*/
void *can_send_thread(void *arg)
{
// 要发送的CAN接口
int ch = *((int *)arg);
// 要发送的CAN帧ID
u32 can_id = 0x123;
// 要发送的CAN帧数据
u8 data[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
// 要发送的CAN数据长度
int len = 8;
// 一直循环发送数据
while (1)
{
// 调用SDK相关函数发送CAN消息
int write_type = can_write(ch, can_id, data, len);
// 发送消息失败
if (write_type == -1)
{
printf("Error in sending CAN message.");
}
// 发送消息成功
else
{
printf("CAN message sent successfully.\n");
}
// 休眠1秒
usleep(1000 * 1000);
}
return NULL;
}
int main()
{
// 定义CAN接口序号
int can_channel = 1;
// 初始化CAN接口
int rc = can_init(can_channel, 500000);
// 初始化失败
if (rc < 0)
{
printf("Failed to initialize CAN!\n");
return -1;
}
printf("Waiting for CAN message...\n");
pthread_t send_thread;
// 创建线程来周期性发送 CAN 消息
if (pthread_create(&send_thread, NULL, can_send_thread, (void *)&can_channel) != 0)
{
perror("Failed to create send thread");
return -1;
}
// 定义CAN帧ID变量,用于存储读取到的帧ID
u32 pid;
// 定义CAN帧数据,用于存储读取到的帧数据
u8 buf[8];
// 主线程来周期性读取 CAN 消息
while (1)
{
// 读取CAN消息,
int read_byte = can_read(can_channel, &pid, buf);
// 读取消息失败
if (read_byte == -1)
{
printf("Error in reading CAN message.\n");
break;
}
// 读取成功,打印CAN帧ID和接收到的数据
printf("Received CAN message: CAN ID = 0x%X, Data = ", pid);
for (int i = 0; i < read_byte; i++)
{
printf("%02X ", buf[i]);
}
printf("\n");
// 休眠200ms后继续尝试读取
usleep(200 * 1000);
}
// 释放端口
can_close(can_channel);
return 0;
}