I2C通信添加帧头、帧尾、校验和或CRC实例介绍

 目录

1 数据包结构

2 通信流程

3 实例代码(伪代码)

4 C语言实现的发送端例子:

5 接收端实例:

6 FAQ:是不是有了crc校验就不需要帧头和帧尾了

CRC校验的作用

帧头和帧尾的作用

结合使用的原因

综合运用


在I2C通信中添加帧头、帧尾、校验和或CRC可以提高数据传输的可靠性和完整性,尤其是在噪声较大的环境中或需要确保数据准确无误的应用场景中。下面是一个使用这些技术的I2C通信实例:

假设我们正在设计一个I2C通信协议,用于在微控制器和一个外部传感器模块之间传输数据。我们将使用`0xA5`作为帧头,`0x5A`作为帧尾,并且在每个数据包中加入一个8位的CRC校验码。以下是数据包的结构和通信流程:

1 数据包结构

数据包的结构如下:

1. **帧头**:`0xA5`,用于标识数据包的开始。
2. **数据长度**:1字节,表示后续数据的有效载荷长度。
3. **数据载荷**:N字节,其中N是数据长度字段指定的值。
4. **CRC校验码**:1字节,基于前面所有字节的CRC-8校验码。
5. **帧尾**:`0x5A`,用于标识数据包的结束。

2 通信流程

1. **微控制器发送数据包**:
   - 微控制器首先发送起始条件。
   - 发送设备地址和写方向位。
   - 接下来发送`0xA5`作为帧头。
   - 发送数据长度字段。
   - 发送数据载荷。
   - 计算并发送CRC校验码。
   - 发送`0x5A`作为帧尾。
   - 发送停止条件。

2. **外部传感器接收数据包**:
   - 外部传感器检测到起始条件和地址,如果地址匹配,则准备接收数据。
   - 接收帧头`0xA5`,确认数据包开始。
   - 接收数据长度字段,了解后续数据的长度。
   - 接收数据载荷。
   - 接收CRC校验码,计算接收到的数据的CRC并与接收到的CRC校验码进行比较,以验证数据的完整性。
   - 接收帧尾`0x5A`,确认数据包结束。
   - 如果CRC验证通过,数据被认为有效;否则,数据包被视为无效,可能需要请求重传。

3 实例代码(伪代码)

以下是一个简化版的伪代码示例,展示如何在微控制器中构建和发送这样一个数据包:```python实现:

# 定义帧头和帧尾
FRAME_HEADER = 0xA5
FRAME_FOOTER = 0x5A

# 构建数据包
def build_packet(data):
    packet = [FRAME_HEADER]
    packet.append(len(data))
    packet.extend(data)
    crc = calculate_crc(packet[:-1]) # 不包括CRC位自身
    packet.append(crc)
    packet.append(FRAME_FOOTER)
    return packet

# 计算CRC校验码
def calculate_crc(data):
    # 这里可以使用现成的CRC库,或实现自己的CRC算法
    # 例如,使用CRC-8标准
    crc = 0x00
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x80:
                crc = (crc << 1) ^ 0x07
            else:
                crc <<= 1
        crc &= 0xFF
    return crc

# 发送数据包
def send_packet(packet):
    i2c.start()
    i2c.send(device_address | WRITE_BIT) # 设定写方向
    for byte in packet:
        i2c.send(byte)
    i2c.stop()

# 使用示例
data_to_send = [0x12, 0x34, 0x56, 0x78] # 示例数据
packet = build_packet(data_to_send)
send_packet(packet)

请注意,上述代码是简化的示例,实际的实现可能需要考虑更多细节,如错误处理、超时管理和硬件特性。同时,CRC算法的实现可能因具体应用而异,这里仅展示了CRC-8的一个简单版本。

将上述实例转换为C语言实现,我们可以创建一个简单的I2C通信程序,该程序在微控制器(如使用AVR或STM32芯片的Arduino或ESP32)上运行。这里,我将使用一个假想的I2C库来发送和接收数据,实际上你可以替换成你所使用微控制器的实际I2C库,例如Arduino的Wire库,STM32的HAL库,或者AVR的TWI库。

4 C语言实现的发送端例子:

#include <stdint.h>
#include <stdbool.h>

// I2C库的模拟实现,你需要替换成实际的I2C库函数
void i2c_start(void);
void i2c_stop(void);
void i2c_send(uint8_t data);
bool i2c_receive(uint8_t *data);

#define FRAME_HEADER 0xA5
#define FRAME_FOOTER 0x5A
#define WRITE_BIT 0x00 // 假设写方向位为0

// 计算CRC-8校验码
uint8_t calculate_crc(const uint8_t *data, size_t length) {
    uint8_t crc = 0x00;
    for (size_t i = 0; i < length; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x80) {
                crc = (crc << 1) ^ 0x07;
            } else {
                crc <<= 1;
            }
        }
        crc &= 0xFF;
    }
    return crc;
}

// 构建数据包
void build_and_send_packet(const uint8_t *data, size_t data_length) {
    uint8_t packet[data_length + 4]; // 包含帧头、长度、CRC和帧尾
    uint8_t crc;

    // 添加帧头
    packet[0] = FRAME_HEADER;
    
    // 添加数据长度
    packet[1] = data_length;
    
    // 添加数据
    for (size_t i = 0; i < data_length; i++) {
        packet[i + 2] = data[i];
    }
    
    // 计算CRC
    crc = calculate_crc(packet, data_length + 2);
    
    // 添加CRC
    packet[data_length + 2] = crc;
    
    // 添加帧尾
    packet[data_length + 3] = FRAME_FOOTER;

    // 发送数据包
    i2c_start();
    i2c_send(YOUR_DEVICE_ADDRESS | WRITE_BIT); // 替换为你的设备地址
    for (size_t i = 0; i < data_length + 4; i++) {
        i2c_send(packet[i]);
    }
    i2c_stop();
}

int main() {
    uint8_t data_to_send[] = {0x12, 0x34, 0x56, 0x78}; // 示例数据
    build_and_send_packet(data_to_send, sizeof(data_to_send));
    return 0;
}

请注意,上述代码中的YOUR_DEVICE_ADDRESS需要替换为你实际的I2C设备地址,而且i2c_start(), i2c_stop(), i2c_send()i2c_receive()函数需要替换为你的微控制器I2C库对应的函数。

此外,这个例子假设了一个简单的CRC-8校验码算法,实际应用中可能需要根据你的具体需求调整CRC算法,例如使用CRC-16或CRC-32等更复杂的校验码。在实际项目中,你可能还会需要添加错误处理、超时机制以及可能的应答处理等更多功能。

5 接收端实例:

在I2C通信中,接收端(通常是传感器或其他外围设备)需要能够识别带有帧头、数据长度、数据、CRC校验和帧尾的数据包。下面是一个使用C语言实现的接收端代码示例,它接收和验证来自发送端的数据包。

这个示例假设使用一个简单的I2C库,该库提供了基本的I2C接收功能。实际应用中,你将需要替换成适合你硬件平台的I2C库函数,如Arduino的Wire库、STM32的HAL库或AVR的TWI库等。

1#include <stdint.h>
2#include <stdbool.h>
3
4// I2C库的模拟实现,你需要替换成实际的I2C库函数
5void i2c_start(void);
6void i2c_stop(void);
7bool i2c_receive(uint8_t *data);
8void i2c_send_ack(void);
9void i2c_send_nack(void);
10
11#define FRAME_HEADER 0xA5
12#define FRAME_FOOTER 0x5A
13#define BUFFER_SIZE 64 // 假设最大的数据包长度为64字节
14
15// 计算CRC-8校验码
16uint8_t calculate_crc(const uint8_t *data, size_t length) {
17    uint8_t crc = 0x00;
18    for (size_t i = 0; i < length; i++) {
19        crc ^= data[i];
20        for (uint8_t j = 0; j < 8; j++) {
21            if (crc & 0x80) {
22                crc = (crc << 1) ^ 0x07;
23            } else {
24                crc <<= 1;
25            }
26        }
27        crc &= 0xFF;
28    }
29    return crc;
30}
31
32// 接收和处理数据包
33bool receive_and_process_packet(uint8_t *received_data, size_t *data_length) {
34    uint8_t buffer[BUFFER_SIZE];
35    uint8_t header;
36    uint8_t length;
37    uint8_t footer;
38    uint8_t received_crc;
39    uint8_t calculated_crc;
40    bool is_valid = false;
41
42    // 接收帧头
43    if (!i2c_receive(&header)) {
44        return false; // 接收失败
45    }
46    if (header != FRAME_HEADER) {
47        i2c_send_nack(); // 不是正确的帧头,发送NACK
48        return false;
49    }
50    i2c_send_ack();
51
52    // 接收数据长度
53    if (!i2c_receive(&length)) {
54        return false; // 接收失败
55    }
56    i2c_send_ack();
57
58    // 接收数据
59    for (uint8_t i = 0; i < length; i++) {
60        if (!i2c_receive(buffer + i)) {
61            return false; // 接收失败
62        }
63        i2c_send_ack();
64    }
65
66    // 接收CRC
67    if (!i2c_receive(&received_crc)) {
68        return false; // 接收失败
69    }
70    i2c_send_ack();
71
72    // 接收帧尾
73    if (!i2c_receive(&footer)) {
74        return false; // 接收失败
75    }
76    if (footer != FRAME_FOOTER) {
77        i2c_send_nack(); // 不是正确的帧尾,发送NACK
78        return false;
79    }
80    i2c_send_ack();
81
82    // 计算CRC
83    calculated_crc = calculate_crc(buffer, length);
84    
85    // 验证CRC
86    if (calculated_crc == received_crc) {
87        is_valid = true;
88    } else {
89        i2c_send_nack(); // CRC验证失败,发送NACK
90    }
91
92    // 如果数据包有效,复制数据到输出缓冲区
93    if (is_valid) {
94        memcpy(received_data, buffer, length);
95        *data_length = length;
96    }
97
98    return is_valid;
99}
100
101int main() {
102    uint8_t received_data[BUFFER_SIZE];
103    size_t data_length;
104
105    // 这里只是一个示例,实际应用中你需要在中断服务程序或轮询模式下调用receive_and_process_packet
106    if (receive_and_process_packet(received_data, &data_length)) {
107        // 数据包有效,处理数据
108        // ...
109    } else {
110        // 数据包无效或接收失败
111        // ...
112    }
113
114    return 0;
115}

在这个示例中,receive_and_process_packet函数接收一个数据包,验证帧头、数据长度、CRC和帧尾,并返回一个布尔值表示数据包是否有效。如果数据包有效,它会将接收到的数据复制到received_data数组中,并通过data_length参数返回实际接收到的数据长度。

请注意,上述代码中的i2c_receive()函数模拟了接收一个字节,实际应用中你需要使用你所使用的微控制器I2C库的对应函数。此外,i2c_send_ack()i2c_send_nack()函数分别用于发送应答和非应答信号,确保通信的正确性和完整性。

在实际项目中,你可能还需要处理诸如错误检测、重试机制和更复杂的数据处理等功能。此外,I2C通信通常在中断服务程序或轮询模式下进行,而不是在main函数中直接调用,以适应实时通信的需求。

6 FAQ:是不是有了crc校验就不需要帧头和帧尾了

CRC(循环冗余校验)和帧头、帧尾在数据通信中各自扮演着不同的角色,它们共同作用于数据的完整性和正确性的保障,但功能并不完全重叠。

CRC校验的作用

CRC校验主要用于检测数据传输中的错误,尤其是突发错误。它通过在数据后面附加一个校验码,接收端可以根据接收到的数据重新计算CRC并与接收到的校验码进行比较,如果两者匹配,则认为数据在传输过程中没有发生错误。CRC校验码的计算方法基于多项式除法,能够有效地检测出多种常见的数据错误。

帧头和帧尾的作用

帧头和帧尾则用于界定数据包的边界,帮助接收端识别一个完整的数据包。在连续的数据流中,帧头和帧尾可以作为开始和结束的标志,使得接收端能够正确地分离出一个个独立的数据包。这对于避免数据包的混乱和重叠至关重要,尤其是在存在噪声干扰或多个设备共享同一总线的环境中。

结合使用的原因

即使使用了CRC校验,仍然需要帧头和帧尾来明确数据包的边界,这是因为CRC校验本身并不能解决数据包的分割和定位问题。CRC校验码只在数据包内部工作,它不能区分一个数据包的结束和下一个数据包的开始。因此,即使CRC校验通过,如果接收端不知道一个数据包何时开始或结束,它也无法正确地处理接收到的数据。

综合运用

在实际通信协议设计中,通常会结合使用CRC校验和帧头/帧尾,以达到最佳的错误检测和数据包边界识别效果。CRC校验确保了数据的完整性,而帧头和帧尾则确保了数据包的正确分割和识别。例如,Ethernet、CAN总线、Modbus RTU等许多工业和网络通信协议中,都同时使用了CRC校验和特殊的帧头/帧尾标识符。

因此,CRC校验和帧头/帧尾是互补的,它们一起构成了数据通信中错误检测和数据包管理的重要组成部分。在设计通信协议时,应当综合考虑这些因素,以实现高效、可靠的数据传输。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值