CAN报文解析SIGNAL的C语言实现

在开发汽车控制器的过程中,不可避免要使用CAN通信,而CAN通信又需要使用CAN矩阵和DBC来约束各个控制器之间的数据传输格式和协议。

DBC里面的signal有Intel和Motorola两种格式,下面用一个图来展示两种格式的排列区别

CAN报文解析SIGNAL的C语言实现_第1张图片

Intel格式

信号的高位在高字节的高位,信号的低位在低字节的低位,如图的INTEL1,箭头由低位指向高位。

Motorola格式

信号的高位在低字节的高位,信号的低位在高字节的高位

具体的理解要根据CAN矩阵的MAP图来理解,下面来分析下根据8字节原始报文解析对应signal的过程。

在写代码之前,我们还需要了解关于signal的一些基本知识

起始位:start_bit,这个范围一般是0-64,表示siganl在64bit里面最低有效位,有时候的起始位也表示在起始字节里面的最低有效位

起始字节:start_byte,signal的最低有效位所在的字节

长度:信号占用的长度,嵌入式系统里面一般是1-32

精度和偏移:由于CAN报文里面是无法发送浮点数和负数,所以在原始CAN报文的基础上增加一个精度的概念,发送方按照精度和偏移将浮点数或者负数进行计算后填充到CAN报文,接收方再按照精度和偏移计算后取出真实数据

第一种方法:这是公司里面同事不知道从哪搞来的代码,我的项目中直接沿用了

以Intel格式为例,8个字节数组里面共有64个bit,Intel格式的signal占用的bit永远是连续的,而且start_bit就是signal的最低位,那么理所当然我们可以想到,我们只需要把8个字节数组转成64bit,然后根据signal的起始位和信号长度从64bit里面拿对应的值就可以了。代码实现如下:

uint32_t get_data_from_char_array_intel(uint8_t *raw,
										uint8_t raw_size,
										uint8_t start_byte,
										uint8_t start_bit,
										uint8_t bit_length)
{
	unsigned char data_bit[64] = {0};
	int j, k=0;
	
	for(j=0;j

这种方式简单粗暴,直接转成64长度的数组,然后按起始字节和长度从数组里面取数据出来即可。

这个代码在TBOX上面跑的时候,没有任何问题,所以想也没想直接移植到我的控制器里面,知道有一天,测试反馈我说,我们的控制器总是比别人慢半拍,因为控制器里面有很多CAN报文需要解析,signal也比较多,于是分析解析的代码,仔细看才知道,这个代码没执行一次,避免不了8*8的一个for循环,如果一个CAN报文里面有64个signal,那这个运算量就非常庞大,会导致整个程序运行速度变慢。

第二种方法:

于是开始优化代码,优化方向是先把8字节数组转64字节数组这个计算先去掉,既然是按bit从8字节数组里面取数据,那么就根据起始字节和长度,从起始字节里面的最低位开始取数据,然后bit自增,增加到7以后byte就自增,bit归0继续取数据,直到结束。

以上面图中的INTEL2信号为例,start_byte = 5; start_bit = 5; length = 16; 取值步骤如下:

取byte 5 的 bit 5 作为第 0 位

取byte 5 的 bit 6 作为第 1 位

取byte 5 的 bit 7 作为第 2 位

(byte++; bit=0;)

取byte 6 的 bit 0 作为第 3 位

...

取byte 7 的 bit 4 作为第 15位

根据以上取值步骤,只需要根据信号长度,从指定字节的指定位取值,然后向左偏移后跟结果进行或运算即可。

代码如下:

uint32_t get_data_from_char_array_intel(uint8_t *raw,
										uint8_t raw_size,
										uint8_t start_byte,
										uint8_t start_bit,
										uint8_t bit_length)
{
	(void)raw_size;
	uint32_t i = 0;

	uint8_t byte_index = start_byte;	/* start from 0 */
	uint8_t bit_index = start_bit;	/* range 0-7 */
	uint32_t ret_val = 0;

	for(i = 0; i < bit_length; i++)
	{
		ret_val |= ((raw[byte_index] >> bit_index)&0X01) << i;
		if (bit_index++ >= 7)
		{
			bit_index = 0;
			byte_index++;
		}
	}
	
	return ret_val;
}

上面代码中,根据信号的长度,依次从指定字节获取对应的bit,然后进行左移后跟ret_val进行或运算。

Motorola格式的解析也是一样的,只是取值的顺序有变化,字节内的bit增长方式也有变化,按上述方式取值的话,先取起始字节的起始位作为返回值的最高位,然后bit减一,bit到0后byte++,bit从7开始取值,直到取值完成。

以上面图中的MOT2信号为例,start_byte = 1; start_bit = 1; length = 16; 取值步骤如下:

取byte 1 的 bit 1 作为第 15 位

取byte 1 的 bit 0 作为第 14 位

(byte++; bit=7;)

取byte 2 的 bit 7 作为第 13 位

...

取byte 3 的 bit 2 作为第 0 位

实现代码如下:

uint32_t get_data_from_char_array_motorola(	uint8_t *raw,
                                            uint8_t raw_size,
                                            uint8_t start_byte,
                                            uint8_t start_bit,
                                            uint8_t bit_length)
{
    (void)raw_size;
    uint32_t i = 0;

    uint8_t byte_index = start_byte;	/* start from 0 */
    uint8_t bit_index = start_bit;		/* range 0-7 */
    uint32_t ret_val = 0;


    for(i = 0; i < bit_length; i++)
    {
        ret_val |= ((raw[byte_index] >> bit_index)&0X01) << (bit_length - i - 1);
        if (bit_index == 0)
        {
            bit_index = 7;
            byte_index++;
        }
        else
        {
            bit_index--;
        }
    }

    return ret_val;
}

两种方式的区别就是第一次取值是返回值的最低位还是最高位,byte内bit的增长方式是正增长还是负增长。

经过以上优化以后,程序的实时性就满足要求了。

第三种方法:

第二种方式有一个缺点就是每次调用这个解析函数的时候,必不可少的是这个for循环次数必定跟信号的长度相等,如果信号都是32位的,那么这个for循环还是要调用32次。于是继续优化代码,思路如下,直接来看下上面图中INTEL2信号的解析方式:

uint16_t value = 0;
  
value |= buf[5] >> 5;
value |= (uint32_t)buf[6] << 3;
value |= (uint32_t)(buf[7]&0X1F) << 11;

采用这种方法,一个4字节的signal,最多执行5次或运算就行了,执行效率大大提高,具体代码就不贴了,根据这个思路就可以写出对应的代码。

你可能感兴趣的:(汽车电子,CAN矩阵,c语言,汽车)