跳转至

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)格式
  1. 起始位:一个固定的位,用来指示数据帧的开始。
  2. 标识符:用于区分不同的消息。标准CAN帧使用11位,扩展CAN帧使用29位标识符。
  3. 控制字段:用于指示数据长度(通常为0到8字节)。
  4. 数据字段:实际传输的数据内容,最大长度为8字节。
  5. CRC校验:用于检测数据传输是否发生错误。
  6. 应答位:接收节点确认是否成功接收到数据。
  7. 结束位:表示数据帧的结束。

优缺点

优点:

  • 高效:提供可靠、快速的通信。
  • 容错:具备强大的错误检测与恢复机制。
  • 实时性强:优先级机制确保实时性。
  • 拓展性好:节点可动态添加,不影响现有系统。

缺点:

  • 带宽有限:尽管CAN非常高效,但其带宽(最大1 Mbps)仍有限,不能满足所有高带宽需求。
  • 网络规模有限:随着节点数量增加,网络可能会出现延迟和带宽瓶颈。
  • 不支持复杂的数据类型:CAN协议以字节为单位传输数据,复杂数据类型需要额外的编码和解码工作。

硬件接口

接口说明

设备共提供2路CAN端口供您使用,其接口定义为

端口序号 端口丝印 系统文件名
CAN-1 CAN-H1/CAN-L1 awlink0
CAN-2 CAN-H2/CAN-L2 awlink1

接线方式

HL 分别连接到目标设备的对应端子

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;
}