STM32F103C8T6实现DSHOT600协议

Dshot是一种飞控和电调之间的数字通讯协议,DSHOT600 就是每秒传输600k比特,DSHOT300就是每秒传输300K比特,个人感觉Dshot600有点类似红外协议,用电平时间表示“0”“1”。

0:高电平大概占据625ns,
1:高电平大概占据1250ns,
一个bit周期大概为1.67us。
(有一些误差是可以接受的)

Dshot600一帧为18bit,
0-10bit为油门数据(高位在前),
11bit设置为“0”(好像有个什么功能,我不care),
12-15bit为CRC校验位,
16-17bit为两个周期的低电平,表示帧间间隔。

#define ESC_BIT_0     11
#define ESC_BIT_1     22
#define MOTOR_BITLENGTH 29
#define ESC_CMD_BUFFER_LEN 18 

u8 ESC_CMD[ESC_CMD_BUFFER_LEN]={0};
用c8t6定时器3通道1输出周期为1.67us的PWM波,配置如下:
//定时器初始化
TIM_TimeBaseStruct.TIM_Period = MOTOR_BITLENGTH;   //ESC_BIT_0 : 0.625us/(1/18) = 11;ESC_BIT_1 : 1.250us/(1/18) = 22;
TIM_TimeBaseStruct.TIM_Prescaler = 3;  //4分频; 72/4=18mhz ;1.67/(1/18)= 30;
TIM_TimeBaseStruct.TIM_ClockDivision = 0;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;//向上计数到 TIM_Period的值
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStruct);  //配置这里是设置了PWM的周期 
//pwm 初始化
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//pwm模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//允许输出pwm //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High ;//比较之后,输出高电平
TIM_OCInitStructure.TIM_Pulse = 0;//其他地方改变,
TIM_OC1Init(TIM3,&TIM_OCInitStructure);//通道1
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能预装载,若果不使能 可能会有杂波    
TIM_DMACmd( TIM3, TIM_DMA_CC1, ENABLE );    
TIM_Cmd(TIM3,ENABLE);//使能定时器3   
由协议可知,一次要发18个PWM周期,也就是要每1.67us改变一下PWM占空比,为了减轻CPU压力,占空比数据放在ESC_CMD数组,DMA将数据自动搬运到定时器3的CCR1寄存器,配置如下:
DMA_DeInit( DMA1_Channel6 );   // DMA复位
// tim3 ch1
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&TIM3->CCR1; //外设地址(u32)&TIM3->CCR1 0x40000434
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ESC_CMD;  //内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //dma传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = ESC_CMD_BUFFER_LEN; //设置DMA在传输时缓冲区的长度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置DMA的外设递增模式,一个外设
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //设置DMA的内存递增模式,
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设寄存器16bit 
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;   //内存数据字长 8bit
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //设置DMA的传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置DMA的优先级别
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //设置DMA的2个memory中的变量不互相访问 
DMA_Init( DMA1_Channel6, &DMA_InitStructure );
DMA_Cmd( DMA1_Channel6, ENABLE );

代码实现DSHOT600(参考了github上的代码):

——————————————–2018-5-2————————————————————————-

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
}
void pwmWriteDigital(u8 motor,u8 *esc_cmd, u16 value)
{
    value = ( (value > 2047) ? 2047 : value );
    value = add_checksum_and_telemetry(value, 0);
    esc_cmd[0]  = (value & 0x8000) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[1]  = (value & 0x4000) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[2]  = (value & 0x2000) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[3]  = (value & 0x1000) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[4]  = (value & 0x0800) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[5]  = (value & 0x0400) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[6]  = (value & 0x0200) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[7]  = (value & 0x0100) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[8]  = (value & 0x0080) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[9]  = (value & 0x0040) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[10] = (value & 0x0020) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[11] = (value & 0x0010) ? ESC_BIT_1 : ESC_BIT_0;     
    esc_cmd[12] = (value & 0x8) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[13] = (value & 0x4) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[14] = (value & 0x2) ? ESC_BIT_1 : ESC_BIT_0;
    esc_cmd[15] = (value & 0x1) ? ESC_BIT_1 : ESC_BIT_0;

    switch(motor)
    {
        case 1:    DMA_SEND(DMA1_Channel7); break;//启动DMA 发送油门数据到电调
        case 2:    DMA_SEND(DMA1_Channel1); break;
        case 3:    DMA_SEND(DMA1_Channel4); break;
        case 4:    DMA_SEND(DMA1_Channel5); break;  
        default: break;
    }
}

——————————————–2018-5-2————————————————————————-

初始化ESC_CMD数组时,ESC_CMD[16], ESC_CMD[17]默认为0,不用每次都更新

还是来个波形图吧(油门value=1800时):

这里写图片描述

注意点:

1.两帧间的间隔不要太久,比如1000ms发一帧是不会有效果的(别问我为什么知道),我试过50ms可以控制电调
2.解锁电调要先连续发0油门,也就是上面的value=0,几秒钟
3.配置PWM时要使能预装载,若果不使能可能会有杂波
4.DMA配置DMA_PeripheralDataSize时,外设寄存器是多少位就填多少位,和要发送给它的数据没有关系
5.我的电调油门最大只能到1800,再打就刹车,这和dshot600协议的上限2047有一些差异,不知道什么原因。

——————————————–2018-5-2————————————————————————-

5.我的电调油门最大只能到1800,再打就刹车,这和dshot600协议的上限2047有一些差异,不知道什么原因。
原因是:之前的代码有bug,所以只能到1800,CRC的实现存在问题。

——————————————————–分割线,正文到此结束———————————————————————

说些废话

之前玩过平衡小车,于是,将电调完全等同于电机驱动,刚开始就想当然地随便设置了一个PWM周期,随便设置了几个占空比,去控制电调,结果就是一脸懵逼,甚至想放弃,问淘宝店家,哎,你会发现他们只是“卖东西的”!!!,然后,就百度呗,才发现电调也要按协议控制,什么oneshot,dshot,pwm一堆,对比之后,选择了DSHOT600,那么问题来了,这个协议怎么实现呢,继续百度才是更揪心的,大部分帖子都是教你怎么玩,没有细致到代码级,不过还好有GITHUB,翻到了DSHOT600的代码,有了协议描述,为什么一定还要看代码?因为CRC有很多种,我百度到的所有文章都没有说用的是哪一种CRC.

你可能感兴趣的:(学习笔记)