概要
在现代汽车中,LIN(局域内部网络)通信协议广泛应用于车载网络系统。LIN 是一种低成本的串行通信协议,主要用于车载应用中的简单设备通信。本文中,我们将介绍如何使用 AT32 微控制器和 DMA(直接存储器访问)实现 LIN 主机的读写。
LIN 协议概述
LIN 协议的基本框架包括以下几个关键要素:
- 主机与从机:LIN 网络中有一个主机和多个从机。主机负责控制通信,调度从机的工作。
- 数据帧:数据通过 LIN 帧进行传输,每个帧包含标识符、数据和校验位
- 波特率:LIN 通常使用 20 Kbps 的波特率,适合短距离通信。。
代码结构
我们的代码分为几个主要部分:
- 初始化函数:配置 GPIO 和 USART 以支持 LIN 通信。
- DMA 配置:设置 DMA 通道以实现高效的数据传输。
- LIN 帧接收与发送:实现接收和发送 LIN 数据帧的功能。
初始化函数
在 lin_init() 函数中,我们首先启用相关的时钟并配置 GPIO 引脚以支持 UART 通信。我们将 TX 引脚配置为推挽输出模式,RX 引脚配置为输入模式。然后,我们配置 USART,包括波特率和数据格式:
void lin_init(void)
{
gpio_init_type gpio_init_struct;
// 启用 USART1 和 GPIOA 的时钟
crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
// 配置 TX 引脚
gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_9;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
// 配置 RX 引脚
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_10;
gpio_init(GPIOA, &gpio_init_struct);
// 配置 LIN 芯片选择引脚
gpio_bits_set(GPIOA, GPIO_PINS_8);
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_8;
gpio_init(GPIOA, &gpio_init_struct);
// 配置 USART
usart_init(USART1, Lin_Speed, USART_DATA_8BITS, USART_STOP_1_BIT);
usart_transmitter_enable(USART1, TRUE);
usart_receiver_enable(USART1, TRUE);
usart_parity_selection_config(USART1, USART_PARITY_NONE);
usart_break_bit_num_set(USART1, USART_BREAK_11BITS);
usart_lin_mode_enable(USART1, TRUE);
usart_enable(USART1, TRUE);
usart_dma_transmitter_enable(USART1, TRUE);
usart_dma_receiver_enable(USART1, TRUE);
wk_dma1_channel1_init();
wk_dma1_channel2_init();
}
DMA 配置
我们通过 DMA 实现高效的数据接收和发送。wk_dma1_channel1_init() 和 wk_dma1_channel2_init() 函数中,我们分别初始化用于接收和发送的 DMA 通道:
void wk_dma1_channel1_init(void)
{
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
dma_init_type dma_init_struct;
dma_reset(DMA1_CHANNEL1);
dma_default_para_init(&dma_init_struct);
dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_LOW;
dma_init(DMA1_CHANNEL1, &dma_init_struct);
dma_flexible_config(DMA1, FLEX_CHANNEL1, DMA_FLEXIBLE_UART1_RX);
}
void wk_dma1_channel2_init(void)
{
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
dma_init_type dma_init_struct;
dma_reset(DMA1_CHANNEL2);
dma_default_para_init(&dma_init_struct);
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init(DMA1_CHANNEL2, &dma_init_struct);
dma_flexible_config(DMA1, FLEX_CHANNEL2, DMA_FLEXIBLE_UART1_TX);
}
接收 LIN 帧
在 LIN_DMA_ReceiveFrame() 函数中,我们使用 DMA 接收 LIN 帧并进行校验。通过计算接收到的数据的校验和,确保数据的完整性和准确性:
void LIN_DMA_ReceiveFrame(uint8_t id, uint8_t *data, uint8_t dataLength) {
dma_channel_enable(DMA1_CHANNEL1, FALSE);
dma_data_number_set(DMA1_CHANNEL1, (dataLength + 4));
wk_dma_channel_config(DMA1_CHANNEL1, (uint32_t)&USART1->dt, (uint32_t)rxBuffer, dataLength + 4);
dma_channel_enable(DMA1_CHANNEL1, TRUE);
uint8_t Receive_id = rxBuffer[2] & 0x3f;
uint16_t receivedChecksum = rxBuffer[dataLength + 3];
uint16_t checksum = 0;
// 计算校验和
if (Lin_CheckSum_Mode == CheckSum_Enhance) {
checksum += rxBuffer[2];
}
for (uint8_t i = 0; i < dataLength; i++) {
checksum += rxBuffer[i + 3];
if (checksum >= 256) {
checksum = (checksum + 1) & 0xFF;
}
}
checksum = (uint8_t)~checksum;
// 校验结果
if (receivedChecksum == checksum && Receive_id == id) {
for (uint8_t i = 0; i < dataLength; i++) {
data[i] = rxBuffer[i + 3];
}
}
}
发送 LIN 数据帧
在 LIN_DMA_SendFrame() 函数中,我们构建要发送的 LIN 数据帧并通过 DMA 发送。该函数计算校验和并将数据打包发送到 LIN 网络:
void LIN_DMA_SendFrame(uint8_t id, uint8_t *data, uint8_t dataLength) {
uint16_t checksum = 0;
txBuffer[0] = 0x55; // 帧头
txBuffer[1] = lin_Check_ProtectedID(id, dataLength); // 计算 PID
for (uint8_t i = 0; i < dataLength; i++) {
txBuffer[i + 2] = data[i];
checksum += data[i];
if (checksum >= 256) {
checksum = (checksum + 1) & 0xFF;
}
}
if (Lin_CheckSum_Mode == CheckSum_Enhance) {
checksum += txBuffer[1];
}
checksum = (uint8_t)~checksum; // 反码
txBuffer[dataLength + 2] = checksum; // 添加校验和
usart_break_send(USART1);
while (dma_data_number_get(DMA1_CHANNEL2)); // 等待 DMA 完成
// 禁用 DMA 通道并设置数据长度
dma_channel_enable(DMA1_CHANNEL2, FALSE);
dma_data_number_set(DMA1_CHANNEL2, (dataLength + 3));
while (usart_flag_get(USART1, USART_TDC_FLAG) == RESET); // 等待发送完成
// 配置 DMA 通道
wk_dma_channel_config(DMA1_CHANNEL2, (uint32_t)&USART1->dt, (uint32_t)txBuffer, dataLength + 3);
dma_channel_enable(DMA1_CHANNEL2, TRUE);
while (dma_flag_get(DMA1_FDT2_FLAG) == RESET);
if(dataLength == 0) {
Lin_Mode = LIN_Mode_MasterRead; // 更新模式
}
}
小结
在这篇文章中,我们探讨了如何使用 AT32 实现 LIN 主机的读写功能。通过适当的初始化、DMA 配置、数据帧的接收和发送,我们成功地实现了 LIN 通信。
工程链接:https://download.youkuaiyun.com/download/TLY983074808/89633351