使用stm32F4实现DSHOT1200协议
最近本人在做省重点大创时使用了一款新型电调,XRotor Micro 30A / 40A BLHeli_32 DShot1200,苦于网上的资料不足,本人在实验室实现了STM32F4与电调的通信,以供大家进行参考。如有不当之处还望指正。
DSHOT
DOSHOT是一款有别于传统调节占空比的方式来发送电机油门的控制信号的数字信号协议。传统的PWM波形式看起来是一种数字信号的传输,但是本质上是一种模拟量的传输,如果接收端的时钟精度不高,那么就会导致发送和输出端出现一定的偏差,PWM波甚至会在一定干扰信号的扰动下出现畸形,那么DSHOT这样的数字信号就应运而生。
DSHOT协议简介
DSHOT协议一帧信号由16位组成:
1、油门值:
前11位二级制数表示油门的大小,其范围为0-2047。
2、回传标志位:
第12位,由0和1表示,0为不回传数据,1位回传数据,相交于传统的电调这种电调多了回传信息,更有利于开发者进行开发。
3、校验位:
第13-16位组成, 其数值为是将前12位分成3组,每组4位,将三组数据进行亦或计算得出的数值即为校验位。
每一位的数值均由0和1组成,由PWM波的占空比进行数值的表示。
以DSHOT600为例:
位长度(总正时周期)为 1.67 微秒(T0H = T0L 或 T1H = T1L)。
对于位为 0,脉冲宽度为 625 纳秒(T0H = 脉冲高时位值为零)
对于位为 1,脉冲宽度为 1250 纳秒(T1H = 脉冲高时位值为 1)
位 0 和位 1 值的脉冲长度差异的原因是,它允许在确定该值时具有相当大的容差。
高低电平的时间可以有一定的误差。
图示为一帧数据。
DSHOT协议的优点:
**1、传输速度快
目前常用的DShot协议有:DShot600,DShot300,DShot150 ,DShot1200
•DShot600 – 1200k bits/Sec
•DShot600 – 600k bits/Sec
•DShot300 – 300k bits/Sec
•DShot150 – 150k bits/Sec
2、无需进行电调进行校准
传统的pwm方波形式的电调在使用前都需要进行校准,较为麻烦。而dshot协议的电调,仅需在使用前输出几秒的低电平即油门值为0即可。
3、更精确的传输信号,更强大的抗干扰能力
拥有校验位,可以确定数据的准确性。
DSHOT协议的代码实现
DSHOT协议的PWM波精度已经达到纳秒级别的了,为了减小芯片的负荷,我们在用定时器输出时采用DMA的方式。
主函数
int main(void)
{
SysTick_Init();
BEEP_Init();
LED_Init();
TIM1_Motor_PWM_Init(2000-1,168-1);
TIM3_Steer1_2_PWM_Init(20000-1,84-1);
MPU_Init();
USART2_Init(100000);
USART3_Init(115200);
USART1_Init(115200);
TIM7_Init(1000,84-1);
while(1)
{
sbus_re_calculate(sbus_adu); //计算SUBS协议的数据
pwmWriteDigital(Send_data,sbus_adu[3]); //解算一帧数据
motor_send(Send_data); //采用DMA发送
}
};
实现PWM_DMA以及解算协议代码
#include "motor.h"
#include "GPIO.h"
#include "Mymath.h"
#include "bsp_SysTick.h"
#define Motor_PIN GPIO_Pin_11
#define Motor_GPIO_PORT GPIOA
#define Motor_GPIO_CLK RCC_AHB1Periph_GPIOA
#define Motor_TIM TIM1
#define Low_vol 7
#define How_vol 14
/******************************************************************************************************************
* void TIM1_Motor_PWM_Init(u16 arr,u16 psc)
*Description : TIM1µç»úPWM_dma³õʼ»¯
*Arguments : arr£º×Ô¶¯ÖØ×°Öµ psc£ºÊ±ÖÓÔ¤·Ö
*Returns : none
*Notes : none
*******************************************************************************************************************
*/
uint16_t data[18];
uint16_t Send_data[16];
void TIM1_Motor_PWM_Init() //PWM³õʼ»¯
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //ʹÄÜ PORTB ʱÖÓ
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //ʹÄÜ TIM3 ʱÖÓ
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); //ʹÄÜ DMA ʱÖÓ
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //¸´Óù¦ÄÜ
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ÍÆÍìÊä³ö
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_TIM1); //ÅäÖà PA11Ϊ¶¨Ê±Æ÷1¸´ÓÃ
TIM_TimeBaseStructure.TIM_Period = (20 - 1); //通过设置预分频系数和重装载值得到周期为0.83微秒
TIM_TimeBaseStructure.TIM_Prescaler = 7-1; //·ÖƵÊý
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC4Init(TIM1, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //ʹÄÜÔ¤×°ÔØ
TIM_DMACmd(TIM1, TIM_DMA_CC4, ENABLE); //ʹÄÜ TIM1 CC2 DMA
TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1,ENABLE); //ÔÊÐíÔÚ¶¨Ê±Æ÷¹¤×÷ʱÏòARRµÄ»º³åÆ÷ÖÐдÈëÐÂÖµ£¬ÒÔ±ãÔÚ¸üÐÂʼþ·¢ÉúÊ±ÔØÈ븲¸ÇÒÔǰµÄÖµ
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
DMA_DeInit(DMA2_Stream4);
DMA_InitStructure.DMA_Channel = DMA_Channel_6;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&TIM1->CCR4);
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)data;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //´æ´¢Æ÷µ½ÍâÉèģʽ
DMA_InitStructure.DMA_BufferSize = 18;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream4, &DMA_InitStructure);
DMA_ITConfig(DMA2_Stream4, DMA_IT_TC, ENABLE);
}
/******************************************************************************************************************
* motor_send(uint16_t *motor)
*Description : DMA·¢Ëͺ¯Êý
*Arguments : motor·¢Ë͵ÄÊý¾Ý
*Returns : none
*Notes : none
*******************************************************************************************************************
*/
void motor_send(uint16_t *motor)
{
uint16_t memaddr;
uint16_t buffersize;
int i = 0 ;
buffersize = 18; // number of bytes needed is #LEDs * 24 bytes + 42 trailing bytes
memaddr = 0; // reset buffer memory index
for(i = 0; i < 16 ; i++)
data[i] = motor[i];
data[16] = 0;
data[17] = 0;
DMA_SetCurrDataCounter(DMA2_Stream4, buffersize); // load number of bytes to be transferred
DMA_Cmd(DMA2_Stream4, ENABLE); // enable DMA channel 6
TIM_Cmd(TIM1, ENABLE); // enable Timer 1
while(!DMA_GetFlagStatus(DMA2_Stream4,DMA_FLAG_TCIF4)) ; // wait until transfer complete
TIM_Cmd(TIM1, DISABLE); // disable Timer1
DMA_Cmd(DMA2_Stream4, DISABLE); // disable DMA channel 6
DMA_ClearFlag(DMA2_Stream4,DMA_FLAG_TCIF4); // clear DMA2 Channel 6 transfer complete flag
}
/******************************************************************************************************************
* u16 add_checksum_and_telemetry(u16 packet, u8 telem)
*Description : УÑéλ½âËã
*Arguments : packetÓÍÃÅÖµ telem»Ø´«Î»
*Returns : none
*Notes : none
*******************************************************************************************************************
*/
u16 add_checksum_and_telemetry(u16 packet, u8 telem) {
u16 packet_telemetry = (packet << 1) | (telem & 1);
u8 i;
u16 csum = 0;
u16 csum_data = packet_telemetry;
for (i = 0; i < 3; i++) {
csum ^= csum_data; // xor data by nibbles
csum_data >>= 4;
}
csum &= 0xf;
return (packet_telemetry << 4) | csum; //append checksum
}
/******************************************************************************************************************
* pwmWriteDigital(uint16_t *esc_cmd, int value)
*Description : Éú³É16λÊý¾Ý
*Arguments : esc_cmdÉú³ÉµÄλ valueÓÍÃÅÖµ
*Returns : none
*Notes : none
*******************************************************************************************************************
*/
void pwmWriteDigital(uint16_t *esc_cmd, int value)
{
value = ( (value > 2047) ? 2047 : value );
value = add_checksum_and_telemetry(value, 0);
esc_cmd[0] = (value & 0x8000) ? How_vol : Low_vol ;
esc_cmd[1] = (value & 0x4000) ? How_vol : Low_vol ;
esc_cmd[2] = (value & 0x2000) ? How_vol : Low_vol ;
esc_cmd[3] = (value & 0x1000) ? How_vol : Low_vol ;
esc_cmd[4] = (value & 0x0800) ? How_vol : Low_vol ;
esc_cmd[5] = (value & 0x0400) ? How_vol : Low_vol ;
esc_cmd[6] = (value & 0x0200) ? How_vol : Low_vol ;
esc_cmd[7] = (value & 0x0100) ? How_vol : Low_vol ;
esc_cmd[8] = (value & 0x0080) ? How_vol : Low_vol ;
esc_cmd[9] = (value & 0x0040) ? How_vol : Low_vol ;
esc_cmd[10] = (value & 0x0020) ? How_vol : Low_vol ;
esc_cmd[11] = (value & 0x0010) ? How_vol : Low_vol ;
esc_cmd[12] = (value & 0x8) ? How_vol : Low_vol;
esc_cmd[13] = (value & 0x4) ? How_vol : Low_vol;
esc_cmd[14] = (value & 0x2) ? How_vol : Low_vol;
esc_cmd[15] = (value & 0x1) ? How_vol : Low_vol;
}
SUBS协议代码不再赘述。
利用数字信号进行电机的输出,大大提升了转速的精度,并且让电机的旋转更加细腻,值得我们进行推广。