介绍
本地互连网络(LIN)是一种低成本的单线通信协议,广泛用于汽车应用中连接传感器和执行器。本文将探讨如何使用STM32微控制器实现LIN从设备,重点介绍如何通过DMA(直接内存访问)高效地传输和接收LIN帧。
工程概述
LIN从设备的实现涉及配置GPIO引脚、USART和DMA通道,以便于通信。该从设备根据特定的标识符(PID)进行响应,并进行校验和验证以确保数据完整性。我们将涵盖代码的关键组件,并解释它们的功能和配置。
初始化
lin_init()函数设置USART以进行LIN通信,配置TX和RX引脚,并初始化DMA通道以传输和接收数据。
void lin_init(void) {
gpio_init_type gpio_init_struct; // 初始化引脚配置结构体
// 启用时钟
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; // TX引脚
gpio_init_struct.gpio_pull = GPIO_PULL_NONE; // 无上下拉
gpio_init(GPIOA, &gpio_init_struct); // 初始化TX引脚
// RX引脚配置
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; // 输入模式
gpio_init_struct.gpio_pins = GPIO_PINS_10; // RX引脚
gpio_init(GPIOA, &gpio_init_struct); // 初始化RX引脚
// LIN芯片片选引脚配置
gpio_bits_set(GPIOA, GPIO_PINS_8); // 拉高LIN芯片片选
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_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); // 启用LIN模式
// 中断配置
nvic_irq_enable(USART1_IRQn, 0, 0); // 配置串口优先级
usart_enable(USART1, TRUE); // 使能USART1
usart_interrupt_enable(USART1, USART_BF_INT, TRUE); // 使能帧中断
// DMA配置
usart_dma_transmitter_enable(USART1, TRUE);
usart_dma_receiver_enable(USART1, TRUE);
wk_dma1_channel1_init(); // 初始化接收DMA通道
wk_dma1_channel2_init(); // 初始化发送DMA通道
}
DMA配置
DMA通道被配置为外设到内存和内存到外设的数据传输。这使得微控制器能够高效地处理数据,而无需CPU干预,这在实时应用中至关重要。
void wk_dma_channel_config(dma_channel_type* dmax_channely, uint32_t peripheral_base_addr, uint32_t memory_base_addr, uint16_t buffer_size) {
dmax_channely->dtcnt = buffer_size; // 数据传输计数
dmax_channely->paddr = peripheral_base_addr; // 外设地址
dmax_channely->maddr = memory_base_addr; // 内存地址
}
void wk_dma1_channel1_init(void) {
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE); // 启用DMA1时钟
dma_init_type dma_init_struct;
dma_reset(DMA1_CHANNEL1); // 重置DMA通道
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_struct.loop_mode_enable = FALSE; // 禁用循环模式
dma_init(DMA1_CHANNEL1, &dma_init_struct); // 初始化DMA通道
// 灵活功能使能
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); // 启用DMA1时钟
dma_init_type dma_init_struct;
dma_reset(DMA1_CHANNEL2); // 重置DMA通道
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_struct.priority = DMA_PRIORITY_LOW; // 优先级设置
dma_init_struct.loop_mode_enable = FALSE; // 禁用循环模式
dma_init(DMA1_CHANNEL2, &dma_init_struct); // 初始化DMA通道
// 灵活功能使能
dma_flexible_config(DMA1, FLEX_CHANNEL2, DMA_FLEXIBLE_UART1_TX); // 配置灵活功能
}
接收数据:
LIN_DMA_ReceiveFrame()函数处理LIN帧的接收。它从RX缓冲区读取数据,并验证校验和以确保数据完整性。接收到的PID决定操作模式(写或读)。
void LIN_DMA_ReceiveFrame(uint8_t id, uint8_t *data, uint8_t dataLength) {
memset(&rxBuffer, 0, sizeof(rxBuffer)); // 清空接收缓冲区
while (dma_data_number_get(DMA1_CHANNEL1)); // 等待DMA传输完成
dma_channel_enable(DMA1_CHANNEL1, FALSE); // 禁用DMA通道
dma_data_number_set(DMA1_CHANNEL1, 3); // 设置传输数据长度
wk_dma_channel_config(DMA1_CHANNEL1, (uint32_t)&USART1->dt, (uint32_t)rxBuffer, 3); // 配置DMA通道
dma_channel_enable(DMA1_CHANNEL1, TRUE); // 使能DMA通道
while(dma_flag_get(DMA1_FDT1_FLAG) == RESET); // 等待传输完成
if(rxBuffer[2] == LIN_Slave_Write_pid) {
Lin_Mode = LIN_Mode_SlaveWrite; // 设置为写模式
}
if(rxBuffer[2] == LIN_Slave_Read_pid) {
dma_channel_enable(DMA1_CHANNEL1, FALSE); // 禁用DMA通道
dma_data_number_set(DMA1_CHANNEL1, (dataLength + 1)); // 设置数据长度
wk_dma_channel_config(DMA1_CHANNEL1, (uint32_t)&USART1->dt, (uint32_t)rxBuffer, dataLength + 1); // 配置DMA通道
dma_channel_enable(DMA1_CHANNEL1, TRUE); // 使能DMA通道
while(dma_flag_get(DMA1_FDT1_FLAG) == RESET); // 等待传输完成
while (dma_data_number_get(DMA1_CHANNEL1)); // 等待DMA传输完成
dma_channel_enable(DMA1_CHANNEL1, FALSE); // 禁用DMA通道
for (uint8_t i = 0; i < dataLength; i++) {
data[i] = rxBuffer[i]; // 将接收到的数据拷贝到用户数据缓冲区
}
Lin_Mode = LIN_Mode_Slave_Idle; // 设置为空闲模式
}
}
接收数据:
LIN_DMA_SendFrame()函数准备并发送LIN数据帧。在发送之前,它根据数据和保护ID计算校验和。
void LIN_DMA_SendFrame(uint8_t id, uint8_t *data, uint8_t dataLength) {
uint8_t PID = lin_Check_ProtectedID(id, dataLength); // 计算PID
uint16_t checksum = 0;
txBuffer[0] = PID; // 设置PID
for (uint8_t i = 0; i < dataLength; i++) {
txBuffer[i + 1] = data[i]; // 拷贝数据
checksum += data[i]; // 计算校验和
}
checksum = (uint8_t)~checksum; // 计算校验和
txBuffer[dataLength + 1] = checksum; // 设置校验和
// 启动DMA传输
dma_channel_enable(DMA1_CHANNEL2, FALSE); // 禁用DMA通道
dma_data_number_set(DMA1_CHANNEL2, dataLength + 2); // 设置数据长度
wk_dma_channel_config(DMA1_CHANNEL2, (uint32_t)&USART1->dt, (uint32_t)txBuffer, dataLength + 2); // 配置DMA通道
dma_channel_enable(DMA1_CHANNEL2, TRUE); // 使能DMA通道
}
触发中断
USART中断处理程序(USART1_IRQHandler())检测同步间隔,并将LIN模式设置为接收数据。
void USART1_IRQHandler() {
if(usart_flag_get(USART1, USART_BFF_FLAG) != RESET) {
Lin_Mode = LIN_Mode_Slave_Receive; // 设置为接收模式
usart_flag_clear(USART1, USART_BFF_FLAG); // 清除中断标志
}
}
小结
本实现展示了如何在STM32微控制器上有效利用LIN协议,通过DMA高效处理数据。通过利用DMA,我们可以最小化CPU负担,同时确保快速和可靠的通信。这在汽车应用中尤其重要,因为时序和数据完整性至关重要。
工程链接:https://download.csdn.net/download/TLY983074808/89633351