记——关于一种简单方便的ECU的CAN报文组合方案

11 篇文章 1 订阅
7 篇文章 13 订阅

0x00原因

因为现有历史原因,导致现在很多的CAN报文参数大概有四种不同的选择项目:Intel、Motorola、normal、reserve。前两方为对于Byte的定义,后两者为bit的定义。具体的可以查看我之前关于CAN的介绍。

这里可以发现,在针对不同车厂的设备下,对与各个设备的CAN帧适配是一种很繁琐的事情。所以这里使用分层的思想,进行分级。使读者可以更加方便的进行上层的定义。

笔者当时使用的是FreeRTOS 9,而在设备在某一些时刻会出现异常的通讯问题,且问题呈现概率式的分布,笔者查看了很长时间才找到问题点。所以在这里作为记录,聊以备忘并且希望可以给一些遇到差不多的问题的同志一些思路

阅读本文,您可能需要掌握的知识:

技能名称技能熟练度
CAN总线熟悉
数据类型熟悉

0x10定义

0x11格式定义

当前四种定义分为两类,Intel、Motorlora。也就是常说的大端模式,小端模式。

image20210928205639099.png

而整车厂商的设置下,有些bit位并不会是连续的

image20210926213647099.png

在连续的bit位下,大小端对于数据的影响很小,而bit反转则会着重影响到当前的数据格式。
image20210926213347852.png

所以,这里就有了四个比较笼统的定义。

定义释义
Intel-Normal小端默认排列
Intel-Inverted小端翻转排列
Motorola-Normal大端默认排列
Motorola-Inverted大端反转排列

再上图可以看到,四种不同的排列方式虽然在用同样的参数,但是实际的组成效果千奇百怪。很容易在某些紧急的情况下出现意想不到的错误。

所以这里定义了四个枚举进行标志。

typedef enum
{
    CAN_BYPE_TYPE_MIN = 0,
    INTEL_NORMAL,
    INTEL_INVERTED,
    MOTOROLA_NORMAL,
    MOTOROLA_INVERTED,
    CAN_BYPE_TYPE_MAX
}E_CAN_BYPE_TYPE;

0x12具体应用

一般的CAN帧参数定义一般为两种:起始位、参数长度。而数据的具体性质在这里不会关注,所以无需定义。

起始位代表着一个具体的参数的定义位,参数长度代表当前的参数占总体CAN帧的区域。这里定义的长度还需要注意的就是当前的参数起始位+参数长度不能超过当前CAN帧的最后一位。这个需要进行仔细的判断并且进行输出。否则很容易出现溢出。

这里因为CAN还分为CAN-Normal、CAN-FD两种,所以还需要具体的定义进行动态的适配。但是这里的适配因为一个设备很多时候只需要支持其中一种,所以可以直接选择宏定义等方式进行编译时动态修改以适配两种模式。如果真的有必须要同时支持,改为变量的方式也不是什么难事。

typedef enum
{
    CAN_POROL_TYPE_MIN = 0,
    CAN_POTOL_NORMAL,
    CAN_POTOL_FD,
    CAN_POROL_TYPE_MAX
}E_CAN_POROL_TYPE;

0x20实现

0x21结构定义

具体的实现主要需要一个具体的组成接口

extern unsigned char * transfer(E_CAN_BYPE_TYPE byte_type,INT8U* OutpBuf,void* data);

这里可以看到,当前的基本需要的数据都会被抽象成几个接口入参。其中1个byte的指针是当前的输入的数组参数。也是最后组包完成的数组。这里倒是还有一个比较柔和的方式进行对于void系列不怎么喜欢的人的方案。

可以定义一个对于组合的共用体,进行数据的架构:

typedef union 
{
    unsigned long int INT64U;
    double Float64;
    float Float32[2];
    unsigned int INT32U[2];
    unsigned char* string[2];
    unsigned short Short[4];
    unsigned char Char[8];
}U_TYPE_FACTORY

随后如果定义了一个0x123的CAN帧,就可以进行如下的设计

typedef struct
{
    type_a:11;
    type_b:11;
    type_c:11;
    type_d:11;
    type_e:11;
    type_f:7;
    
}S_ID_VCU_1;

typedef union 
{
    S_ID_VCU_1 s1;
    U_TYPE_FACTORY fact;
}U_CAN_VCU_1;

如果还有其他的帧也可以尝试同样的处理方式。(当然,如果使用了C11的代码规范,则可以直接声明匿名类进行传输)这里就可以得到这个很好的传输的数据结构。

随后就得到了如下的结构

extern unsigned char * transfer(E_CAN_BYPE_TYPE byte_type,U_TYPE_FACTORY* OutpBuf,U_TYPE_FACTORY* data);

这个结构就可以使用了。

0x22大小端组合

有了这些就可以搞定一个简单的组码了:

//#include <stdio.h>
#include <string.h>
#include <stdbool.h>

typedef union
{
    //这里默认是小端模式的芯片,数据总线为32bit
    unsigned long long int INT64U;		//一个正好可以表示长度的位置
    double Float64;						//这里都是方便转换的情况,但是还需要考虑芯片的结构
    float Float32[2];					//这里都是方便转换的情况,但是还需要考虑芯片的结构
    unsigned int INT32U[2];				//这里都是方便转换的情况,但是还需要考虑芯片的结构
    unsigned char* string[2];			//这个是指针接口,可以传输一些字符
    unsigned short Short[4];			//这里都是方便转换的情况,但是还需要考虑芯片的结构
    unsigned char Char[8];				//这个可以作为直接传输的方案
}U_TYPE_FACTORY;

typedef enum
{
    CAN_BYPE_TYPE_MIN = 0,
    INTEL_NORMAL,			//小端模式下默认情况	
    INTEL_INVERTED,			//小端模式下反转bit
    MOTOROLA_NORMAL,		//大端模式下默认情况
    MOTOROLA_INVERTED,		//大端模式下反转bit
    CAN_BYPE_TYPE_MAX
}E_CAN_BYPE_TYPE;


void normal(U_TYPE_FACTORY* input, U_TYPE_FACTORY value, unsigned char startbit, unsigned char len)
{
    unsigned char pos = 0;			//结构位置
    bool bits_map[8][8] = {0};		//待处理展开
    bool value_map[8][8] = {0};		//修改值展开
    //这里使用了很浪费的方式,但是笔者觉得这样展示从逻辑上展现是很清楚的,所以选择这样写,如果读者有更好的办法可以采用自己的方式,但是一定要保证自己可以了解这个机制。
    //首先展开当前的数据
    for ( int i = 7; i > -1; i--)
    {
        for ( int j = 0; j < 8; j++)
        {
            bits_map[i][j] = (input->Char[i] >> j) & 0x01;
        }
    }
    //把当前的加入的数据展开
    //这里可以通过调节当前的i的增长方式进行大小端的选择,j再这里可以bit反转,也可以再最后处理一下这个东西
    unsigned int post = 0;
    for (int i = startbit/8; i > -1; i--)
    {
        if(len == 0)break;
        for (int j = 0; j < 8; j++)
        {
            len--;
            if (len <= 0)break;
            value_map[i][j] = (value.INT64U >> (j+(8* post))) & 0x01;
        }
        post++;
    } 
    //然后把数据接进去
    for (int i = 7; i > -1; i--)
    {
        for (int j = 0; j < 8; j++)
        {
            bits_map[i][j] |= value_map[i][j];
        }
    }

    //最后拼起来再拿过来
    for (unsigned int i = 0; i < sizeof(bits_map); i++)
    {
        input->Char[i/8] |= bits_map[i/8][i % 8] << (i % 8);
    }
    while(1);
}

void transfer(E_CAN_BYPE_TYPE byte_type, U_TYPE_FACTORY* input, U_TYPE_FACTORY value,unsigned char startbit,unsigned char len)
{

        normal(input,value,startbit,len);

}



int main()
{
    U_TYPE_FACTORY byte_type = {0xFF};
    U_TYPE_FACTORY value;
    value.INT64U = 0xa5a5a5a5a5a5a5a5;
    warning:这里制作为示范思想,实际的实现并不能实现完整的结构,这里也并没有实现intel的完整代码。后续笔者闲的时候可能会重写完成,但是现在笔者还在准备成人考试,暂时先挖个坑回头再填
    transfer(INTEL_NORMAL, &byte_type,value,37,15);
}

0x30 节后语

这个方式需要注意的是,现在的两者分的不是很开,和在一起也是不错的使用方案,分开也是可以的,调用时可能需要考虑的更多

本文首发自 记——关于一种简单方便的ECU的CAN报文组合方案,更多文章可进入我的博客详查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GreenDreamer

如果帮到了你,还望请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值