AM32开源Telemetry代码深度解析
AM32开源代码之代码分析 – Telemetry
1. 源由
ESC电传就是电子调速器用数字形式反馈当前工作状态的报文格式。
2. 框架设计
STM32G071G MCU设计图,使用PB6作为ESC的电传引脚。
2.1 初始化 telem_UART_Init
USART1 的初始化,并配置了相关的 GPIO 和 DMA:
- 初始化 USART 和 GPIO 结构体:准备配置结构体。
- 启用外设时钟:启用 USART1 和 GPIOB 的时钟。
- 配置 GPIO:
- 配置 GPIOB 的引脚 6 为 USART1 的替代功能,设置为开漏输出、上拉模式和低速。
- 配置 NVIC 中断:
- 设置 USART1 的中断优先级并启用中断。
- 配置 DMA:
- 设置 DMA 通道 3 的外设请求、数据传输方向、优先级、模式、地址递增、数据对齐等参数。
- 配置 USART:
- 设置 USART1 的预分频器、波特率、数据宽度、停止位、奇偶校验、传输方向、过采样等参数。
- 配置 FIFO 阈值和禁用 FIFO,配置半双工模式。
- 启用 USART:
- 启用 USART1,并等待发送和接收确认标志。
- 配置 DMA 传输:
- 设置 DMA 传输的源地址、目标地址和数据长度,启用 DMA 的传输完成和传输错误中断。
telem_UART_Init()
│
├── 初始化 USART 和 GPIO 结构体
│ ├── LL_USART_InitTypeDef USART_InitStruct = { 0 }
│ └── LL_GPIO_InitTypeDef GPIO_InitStruct = { 0 }
│
├── 启用外设时钟
│ ├── LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1) // 启用 USART1 外设时钟
│ └── LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB) // 启用 GPIOB 外设时钟
│
├── 配置 GPIO
│ └── GPIO_InitStruct 配置
│ ├── Pin = LL_GPIO_PIN_6 // 配置为引脚 6
│ ├── Mode = LL_GPIO_MODE_ALTERNATE // 配置为替代功能模式
│ ├── Speed = LL_GPIO_SPEED_FREQ_LOW // 设置速度为低
│ ├── OutputType = LL_GPIO_OUTPUT_OPENDRAIN // 设置为开漏输出
│ ├── Pull = LL_GPIO_PULL_UP // 上拉
│ └── Alternate = LL_GPIO_AF_0 // 设置为替代功能 AF0
│ └── LL_GPIO_Init(GPIOB, &GPIO_InitStruct) // 初始化 GPIOB 引脚 6
│
├── 配置 NVIC 中断
│ ├── NVIC_SetPriority(USART1_IRQn, 3) // 设置 USART1 中断优先级为 3
│ └── NVIC_EnableIRQ(USART1_IRQn) // 启用 USART1 中断
│
├── 配置 DMA
│ ├── LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_3, LL_DMAMUX_REQ_USART1_TX) // 设置 DMA 传输请求为 USART1_TX
│ ├── LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_3, LL_DMA_DIRECTION_MEMORY_TO_PERIPH) // 设置 DMA 传输方向为内存到外设
│ ├── LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PRIORITY_LOW) // 设置 DMA 优先级为低
│ ├── LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MODE_NORMAL) // 设置 DMA 模式为普通模式
│ ├── LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PERIPH_NOINCREMENT) // 设置外设地址不递增
│ ├── LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MEMORY_INCREMENT) // 设置内存地址递增
│ ├── LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PDATAALIGN_BYTE) // 设置外设数据大小为字节
│ └── LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MDATAALIGN_BYTE) // 设置内存数据大小为字节
│
├── 配置 USART
│ ├── USART_InitStruct 配置
│ │ ├── PrescalerValue = LL_USART_PRESCALER_DIV1 // 设置预分频器为 1
│ │ ├── BaudRate = 115200 // 设置波特率为 115200
│ │ ├── DataWidth = LL_USART_DATAWIDTH_8B // 设置数据宽度为 8 位
│ │ ├── StopBits = LL_USART_STOPBITS_1 // 设置停止位为 1 位
│ │ ├── Parity = LL_USART_PARITY_NONE // 设置无奇偶校验
│ │ ├── TransferDirection = LL_USART_DIRECTION_TX_RX // 设置传输方向为 TX/RX
│ │ └── OverSampling = LL_USART_OVERSAMPLING_16 // 设置过采样为 16
│ ├── LL_USART_Init(USART1, &USART_InitStruct) // 初始化 USART1
│ ├── LL_USART_SetTXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_8) // 设置 TX FIFO 阈值为 1/8
│ ├── LL_USART_SetRXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_8) // 设置 RX FIFO 阈值为 1/8
│ └── LL_USART_DisableFIFO(USART1) // 禁用 FIFO
│ └── LL_USART_ConfigHalfDuplexMode(USART1) // 配置半双工模式
│
├── 启用 USART
│ ├── LL_USART_Enable(USART1) // 启用 USART1
│ └── 等待 USART 启用完成
│ └── while ((!(LL_USART_IsActiveFlag_TEACK(USART1))) || (!(LL_USART_IsActiveFlag_REACK(USART1)))) {} // 等待发送和接收确认标志
│
└── 配置 DMA
├── LL_DMA_ConfigAddresses(
│ ├── DMA1, LL_DMA_CHANNEL_3, (uint32_t)aTxBuffer, // 设置 DMA 传输地址
│ └── LL_USART_DMA_GetRegAddr(USART1, LL_USART_DMA_REG_DATA_TRANSMIT),
│ └── LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_3)
├── LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, nbDataToTransmit) // 设置 DMA 传输数据长度
├── LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_3) // 启用 DMA 传输完成中断
└── LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_3) // 启用 DMA 传输错误中断
2.2 DMA发送数据
配置 USART1 的 DMA 发送操作,并设置了数据长度。主要步骤包括:
-
设置 USART1 传输方向为 TX:
LL_USART_SetTransferDirection(USART1, LL_USART_DIRECTION_TX)
:配置 USART1 的数据传输方向为发送(TX)。-
设置 DMA 数据长度:
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, nbDataToTransmit)
:设置 DMA 通道 3 的数据传输长度为nbDataToTransmit
。-
启用 USART1 的 DMA 发送请求:
LL_USART_EnableDMAReq_TX(USART1)
:启用 USART1 的 DMA 发送请求,允许通过 DMA 发送数据。-
启用 DMA 通道 3:
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_3)
:启用 DMA 通道 3,开始数据传输。-
恢复 USART1 传输方向为 RX:
LL_USART_SetTransferDirection(USART1, LL_USART_DIRECTION_RX)
:将 USART1 的传输方向恢复为接收(RX),准备接收数据。
send_telem_DMA()
│
├── 设置 USART1 传输方向为 TX
│ └── LL_USART_SetTransferDirection(USART1, LL_USART_DIRECTION_TX)
│
├── 设置 DMA 数据长度
│ └── LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, nbDataToTransmit)
│
├── 启用 USART1 的 DMA 发送请求
│ └── LL_USART_EnableDMAReq_TX(USART1)
│
├── 启用 DMA 通道以开始数据传输
│ └── LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_3)
│
└── 恢复 USART1 传输方向为 RX
└── LL_USART_SetTransferDirection(USART1, LL_USART_DIRECTION_RX)
2.3 CRC处理函数
uint8_t update_crc8(uint8_t crc, uint8_t crc_seed)
{
uint8_t crc_u, i;
crc_u = crc;
crc_u ^= crc_seed;
for (i = 0; i < 8; i++)
crc_u = (crc_u & 0x80) ? 0x7 ^ (crc_u << 1) : (crc_u << 1);
return (crc_u);
}
uint8_t get_crc8(uint8_t* Buf, uint8_t BufLen)
{
uint8_t crc = 0, i;
for (i = 0; i < BufLen; i++)
crc = update_crc8(Buf[i], crc);
return (crc);
}
2.4 创建电传报文
创建了一个用于数据传输的电报包,并将数据存储在 aTxBuffer
数组中。主要步骤包括:
-
设置温度:
aTxBuffer[0] = temp
:将温度值存储到aTxBuffer
的第一个字节。-
设置电压:
aTxBuffer[1] = (voltage >> 8) & 0xFF
:将电压的高字节存储到aTxBuffer
的第二个字节。aTxBuffer[2] = voltage & 0xFF
:将电压的低字节存储到aTxBuffer
的第三个字节。-
设置电流:
aTxBuffer[3] = (current >> 8) & 0xFF
:将电流的高字节存储到aTxBuffer
的第四个字节。aTxBuffer[4] = current & 0xFF
:将电流的低字节存储到aTxBuffer
的第五个字节。-
设置消耗:
aTxBuffer[5] = (consumption >> 8) & 0xFF
:将消耗的高字节存储到aTxBuffer
的第六个字节。aTxBuffer[6] = consumption & 0xFF
:将消耗的低字节存储到aTxBuffer
的第七个字节。-
设置电机转速:
aTxBuffer[7] = (e_rpm >> 8) & 0xFF
:将电机转速的高字节存储到aTxBuffer
的第八个字节。aTxBuffer[8] = e_rpm & 0xFF
:将电机转速的低字节存储到aTxBuffer
的第九个字节。-
计算并设置 CRC 校验码:
aTxBuffer[9] = get_crc8(aTxBuffer, 9)
:计算aTxBuffer
前 9 个字节的 CRC8 校验码,并将结果存储在第十个字节(aTxBuffer[9]
)。
makeTelemPackage(temp, voltage, current, consumption, e_rpm)
│
├── 设置温度
│ └── aTxBuffer[0] = temp
│
├── 设置电压
│ ├── aTxBuffer[1] = (voltage >> 8) & 0xFF // 电压高字节
│ └── aTxBuffer[2] = voltage & 0xFF // 电压低字节
│
├── 设置电流
│ ├── aTxBuffer[3] = (current >> 8) & 0xFF // 电流高字节
│ └── aTxBuffer[4] = current & 0xFF // 电流低字节
│
├── 设置消耗
│ ├── aTxBuffer[5] = (consumption >> 8) & 0xFF // 消耗高字节
│ └── aTxBuffer[6] = consumption & 0xFF // 消耗低字节
│
├── 设置电机转速
│ ├── aTxBuffer[7] = (e_rpm >> 8) & 0xFF // 电机转速高字节
│ └── aTxBuffer[8] = e_rpm & 0xFF // 电机转速低字节
│
└── 计算并设置 CRC 校验码
└── aTxBuffer[9] = get_crc8(aTxBuffer, 9) // 计算 CRC8 校验码并存入第 9 字节
3. 总结
这个好简单,就是吧通过报文将以下电子调速器的当前工作状态传送回去就完了。
4. 参考资料
【1】BLDC ESC 无刷直流电子调速器简介
【2】BLDC ESC 无刷直流电子调速器工作原理
【3】BLDC ESC 无刷直流电子调速器工作过程
【4】BLDC ESC 无刷直流电子调速器驱动方式
【5】AM32开源代码之工程结构
作者:lida2003