STM32G0 TIM BURST DMA DSHOT发波笔记
使用TIM1 CH1 发波DSHOT:




对于STM32G030F6P6定时器输入频率为64MHZ

设置分频值5 定时器计数频率为12.8MHz
定时器计数周期为20,定时器更新频率为0.64MHz,定时器更新周期为1.5625us

使用DSHOT600协议实现
中断和DMA配置:


DSHOT协议实现
定时器相关参数:
#define DSHOT_MOTOR_BIT_0 7
#define DSHOT_MOTOR_BIT_1 14
#define DSHOT_MOTOR_BITLENGTH 20
对于BIT1高电平使用14个定时器tick实现
对于BIT0高电平使用7个定时器tick实现
定时器计数周期为20
定时DSHOT相关参数:
首先是一个DSHOT数据帧的bit长度:
#define DSHOT_DMA_BUFFER_SIZE 18 /* resolution + frame reset (2us) */
正常来说一个DSHOT只含有16bit数据,但是因为使用TIM BURST DMA方法需要对帧尾进行复位,否则会持续发波,所以在尾部添加了两个bit,实际比较数值为0.
随后我们定义一个DSHOT BURST DMA缓存:
// 4个通道 DMA BURST BUFFER
uint32_t dmaBurstBuffer[DSHOT_DMA_BUFFER_SIZE * 4];//18x4 = 72 words
考虑到实际的使用情况,我们按照实际发波4个DSHOT通道的情况设计。
这里的传输大小可以使用uint16_t也可以使用uint32_t,但是实际上只要内存情况足够,最好还是使用uint32_t。
所以一个通道18字,一共4个通道总共72字的缓存区。
随后是DSHOT数据包的生成:
uint16_t prepareDshotPacket(const uint16_t value, bool requestTelemetry)
{
uint16_t packet = (value << 1) | (requestTelemetry ? 1 : 0);
// compute checksum
int csum = 0;
int csum_data = packet;
for (int i = 0; i < 3; i++) {
csum ^= csum_data; // xor data by nibbles
csum_data >>= 4;
}
csum &= 0xf;
packet = (packet << 4) | csum;// append checksum
return packet;
}
这部分比较简单,也不是重点,具体可以参考DSHOT协议实现。
总之这个函数就是传入油门数值和遥测请求位,之后这个函数返回一个uint16_t表示16bit的DSHOT数据包。
随后就是需要根据数据包填充DMA缓存区:
void load_dma_buffer(uint32_t *dmaBuffer, int stride, uint16_t packet)//OK
{
int i;
for (i = 0; i < 16; i++) {// i: bit index
dmaBuffer[i * stride] = (packet & 0x8000) ? DSHOT_MOTOR_BIT_1 : DSHOT_MOTOR_BIT_0; // MSB first
packet <<= 1;
}
dmaBuffer[i++ * stride] = 0;// 填充最后两个bit对应的buffer
dmaBuffer[i++ * stride] = 0;// 填充最后两个bit对应的buffer
}
其中dmaBuffer并不是总和我们定义的72个字的那个地址一致。
这个load_dma_buffer函数只负责对于缓存区中一个通道的数值进行填充。
例如我们要填充第一个通道,则这个函数传入的地址将是:&(dmaBurstBuffer[0])
第二个通道:&(dmaBurstBuffer[1])
第三个通道: &(dmaBurstBuffer[2])
第四个通道:&(dmaBurstBuffer[3])
因为在这个函数中,我们传入stride的数值是4,表示每填充一个就跳过三个。
因为实际上DMA BURST传输的时候,DMA连续读取缓存中的内容,每次定时器更新就读取4个字,然后依次填充到CCR1 CCR2 CCR3 CCR4
这样一共填充18次,整个传输完毕。
而这样的一次填充4个字,其实就是填充4个DSHOT数据包的其中一个bit的比较值,需要总共18次,才可以填充完毕四个通道的整个DSHOT数据包。
而4个通道数据包的整个发送过程,仅仅需要触发一次中断,所以是对CPU十分友好的。
随后说明一下发送一个DSHOT帧的启动流程:
首先是定义一个函数:
// htim: htim1
// BurstBaseAddress: TIM_DMABASE_CCR1
// BurstRequestSrc: TIM_DMA_UPDATE
// BurstBuffer: buffer
// BurstLength: TIM_DMABURSTLENGTH_4TRANSFERS
// DataLength: 0-65535
HAL_StatusTypeDef dshot_dma_burst_start(TIM_HandleTypeDef *htim, uint32_t BurstBaseAddress,
uint32_t BurstRequestSrc, const uint32_t *BurstBuffer,
uint32_t BurstLength, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;
if (htim->DMABurstState == HAL_DMA_BURST_STATE_BUSY){
return HAL_BUSY;
}
else if (htim->DMABurstState == HAL_DMA_BURST_STATE_READY){
if ((BurstBuffer == NULL) && (BurstLength > 0U)){
return HAL_ERROR;
}
else{
htim->DMABurstState = HAL_DMA_BURST_STATE_BUSY;
}
}
else{
}
htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = my_dma_cplt_callback;
/* Enable the DMA channel */
if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_UPDATE], (uint32_t)BurstBuffer,
(uint32_t)&htim->Instance->DMAR, DataLength) != HAL_OK)
{
/* Return error status */
return HAL_ERROR;
}
if (status == HAL_OK)
{
htim->Instance->DCR = (BurstBaseAddress | BurstLength);/* Configure the DMA Burst Mode */
__HAL_TIM_ENABLE_DMA(htim, BurstRequestSrc); /* Enable the TIM DMA Request */
}
return status;
}
其实这个函数是从HAL库改过来的,主要是定义了一下回调函数,因为HAL库原来的回调函数不太好用。。。
调用这个函数表示启动一次DMA BURST传输,如果TIM出现DMA请求,则会立即开始传输。
对于定时器部分,我们只需要在系统启动的时候初始化一下时基单元和PWM通道输出即可:
void dshot_init(void)
{
HAL_TIM_Base_Start(&DSHOT_TIMER_HANDLE);
HAL_TIM_PWM_Start(&DSHOT_TIMER_HANDLE, TIM_CHANNEL_1);
}
注意这里的就是常规的启动方式,不需要IT或者DMA方式 Start。
在dshot_dma_burst_start中我们定义了回调函数:
htim->hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = my_dma_cplt_callback;
我们对这个函数进行实现:
void my_dma_cplt_callback(DMA_HandleTypeDef *hdma)
{
TIM_HandleTypeDef *htim = (TIM_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
if (htim->hdma[TIM_DMA_ID_UPDATE]->Init.Mode == DMA_NORMAL)
{
htim->State = HAL_TIM_STATE_READY;
}
HAL_TIM_PeriodElapsedCallback(htim);
}
其中调用了HAL库为我们定义的weak函数:
// DMA BURST 传输回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_DMABurst_WriteStop(&DSHOT_TIMER_HANDLE, TIM_DMA_UPDATE);// 不加这个会导致BURST BUSY
}
主要内容就是停止了BURST传输,否则在下一次传输启动的时候会出现BURST BUSY。
对于stm32g0xx_it.c里面的函数,不需要进行修改。
浙公网安备 33010602011771号