AM32开源代码之代码分析 - DSHOT/BDSHOT

news2024/11/13 19:33:25

AM32开源代码之代码分析 - DSHOT/BDSHOT

  • 1. 源由
  • 2. 协议设计
    • 2.1 位格式
    • 2.2 帧结构
    • 2.3 CRC计算
    • 2.4 帧格式(eRPM)
      • 2.4.1 DSHOT
      • 2.4.2 BDSHOT
    • 2.5 EDT编码策略
    • 2.5 eRPM传输
  • 3. 框架设计
    • 3.1 初始化
    • 3.2 动态过程
      • 3.2.1 飞控触发
      • 3.2.2 定时触发
    • 3.3 协议检测
      • 3.3.1 detectInput
      • 3.3.2 checkDshot
      • 3.3.3 checkServo
    • 3.4 报文处理
      • 3.4.1 computeDshotDMA
      • 3.4.2 make_dshot_package
  • 4. 总结
  • 5. 参考资料

1. 源由

现在的ESC电子调速器,除了ESC电传通过数字形式反馈当前工作状态。

控制和数据反馈也离不开DSHOT和BDSHOT协议,因此,本章我们研读下关于DSHOT和BDSHOT在AM32代码上的视线。

2. 协议设计

2.1 位格式

DSHOT

在这里插入图片描述
在这里插入图片描述

注:BDSHOT正好是DSHOT的反向。

2.2 帧结构

  • 11 bit throttle: 2048 possible values. 0 is reserved for disarmed. 1-47 are reserved for special commands. Leaving 48 to 2047 (2000 steps) for the actual throttle value
  • 1 bit telemetry request - if this is set, telemetry data is sent back via a separate channel
  • 4 bit CRC: (Cyclic Redundancy) Check to validate data (throttle and telemetry request bit)

2.3 CRC计算

计算公式:

crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F

举例:

value = 100000101100
(>>4) = 000010000010 # right shift value by 4
(^) = 100010101110 # XOR with value
(>>8) = 000000001000 # right shift value by 8
(^) = 100010100110 # XOR with previous XOR
(0x0F) = 000000001111 # Mask 0x0F
(&) = 000000000110 # CRC

2.4 帧格式(eRPM)

在双向DSHOT和DSHOT模式下,电子调速器(ESC)发送的eRPM遥测帧都是16位值。

eeemmmmmmmmmcccc

  • 12位:eRPM数据
  • 3位:需要左移的位数,以获取以微秒为单位的周期
  • 9位:周期基数
  • 4位:CRC

2.4.1 DSHOT

在这里插入图片描述

2.4.2 BDSHOT

在这里插入图片描述

2.5 EDT编码策略

扩展DSHOT遥测(EDT, Extended DShot Telemetry)是DSHOT“标准”中相对较晚添加的一项功能。它允许在eRPM帧内传输额外的遥测数据,这意味着不需要额外的线来从电子调速器(ESC)传输额外的遥测数据到飞控器。

pppp mmmmmmmm

在这里插入图片描述

注:这个功能需要ESC和飞控器双方都支持。Bleujay、BLHeli_32、AM32在它们的最新版本中都支持这一功能。

其主要方法就是通过左移的方式将RPM值最高位不为零(不存在如下帧,若存在该帧则是遥测帧):

eee 0mmmmmmmm

2.5 eRPM传输

实际上并不是直接将这个值发送回飞控器。

  1. 应用了GCR编码,并且通过将半字节(4位一组)按照以下表格进行映射,将16位值映射为20位值
    在这里插入图片描述
  2. 将GCR映射为21位值,这个新值以0开始,剩余的位由以下两条规则设置:

如果GCR数据中的当前位是1:新值中的当前位是前一个新位的反转
如果GCR数据中的当前位是0:新值中的当前位与前一个新位相同

e.g.

16 bit: 1000001011000110
Nibbles: 1000 0010 1100 0110
Hex: 0x8 0x2 0xC 0x6
Mapped: 0x1A 0x12 0x1E 0x16
GCR: 11010 10010 11110 10110
mapped: 010011 00011 01011 00100

解码方式:

gcr = (value ^ (value >> 1));

3. 框架设计

3.1 初始化

首先,进行IO&Timer初始化(G071代码有部分DMA初始化)

main
 └──> initCorePeripherals
     └──> MX_TIM3_Init

#define INPUT_PIN LL_GPIO_PIN_6
#define INPUT_PIN_PORT GPIOA

然后,DMA挂中断钩子函数

main
 └──> initCorePeripherals
     └──> MX_DMA_Init
         └──> DMA1_Channel1_IRQHandler

最后,ADC相关报文钩子函数

main
 └──> enableCorePeripherals
     └──> EXTI4_15_IRQHandler

3.2 动态过程

3.2.1 飞控触发

DMA1_Channel1_IRQHandler
 └──> transfercomplete
  1. 检查 armed && dshot_telemetry 是否同时为真

    • 条件: out_put == 0/1 判断
    • 操作:
    • 0:调用sendDshotDma接收报文;待处理标识compute_dshot_flag = 1
    • 1:调用receiveDshotDma接收报文;待处理标识compute_dshot_flag = 2
  2. 检查 inputSet 是否为 1

    • 条件: inputSet == 0/1 判断
    • 操作:
    • 0:调用detectInput 检查输入协议
    • 1:根据协议进行相关报文处理
transfercomplete
|
|-- 如果 (armed && dshot_telemetry)
|   |-- 如果 (out_put)
|   |   |-- 调用 receiveDshotDma()
|   |   |-- compute_dshot_flag = 2
|   |   |-- 返回
|   |-- 否则
|   |   |-- 调用 sendDshotDma()
|   |   |-- compute_dshot_flag = 1
|   |   |-- 返回
|
|-- 如果 (inputSet == 0)
|   |-- 调用 detectInput()
|   |-- 调用 receiveDshotDma()
|   |-- 返回
|
|-- 如果 (inputSet == 1)
|   |-- 如果 (dshot_telemetry)
|   |   |-- 如果 (out_put)
|   |   |   |-- 调用 make_dshot_package(e_com_time)
|   |   |   |-- 调用 computeDshotDMA()
|   |   |   |-- 调用 receiveDshotDma()
|   |   |   |-- 返回
|   |   |-- 否则
|   |   |   |-- 调用 sendDshotDma()
|   |   |   |-- 返回
|   |
|   |-- 否则 (dshot_telemetry 为 false)
|   |   |-- 如果 (dshot == 1)
|   |   |   |-- 调用 computeDshotDMA()
|   |   |   |-- 调用 receiveDshotDma()
|   |   |
|   |   |-- 如果 (servoPwm == 1)
|   |       |-- 如果 (getInputPinState())
|   |       |   |-- buffersize = 3
|   |       |-- 否则
|   |           |-- buffersize = 2
|   |           |-- 调用 computeServoInput()
|   |           |-- 调用 receiveDshotDma()
|   |
|   |-- 如果 (!armed)
|       |-- 如果 (dshot && (average_count < 8) && (zero_input_count > 5))
|       |   |-- average_count++
|       |   |-- average_packet_length += (dma_buffer[31] - dma_buffer[0])
|       |   |-- 如果 (average_count == 8)
|       |       |-- dshot_frametime_high = (average_packet_length >> 3) + (average_packet_length >> 7)
|       |       |-- dshot_frametime_low = (average_packet_length >> 3) - (average_packet_length >> 7)
|       |
|       |-- 如果 (adjusted_input < 0)
|       |   |-- adjusted_input = 0
|       |
|       |-- 如果 (adjusted_input == 0 && calibration_required == 0)
|       |   |-- zero_input_count++
|       |-- 否则
|           |-- zero_input_count = 0
|           |-- 如果 (adjusted_input > 1500)
|           |   |-- 如果 (getAbsDif(adjusted_input, last_input) > 50)
|           |   |   |-- enter_calibration_count = 0
|           |   |-- 否则
|           |       |-- enter_calibration_count++
|           |
|           |   |-- 如果 (enter_calibration_count > 50 && (!high_calibration_set))
|           |       |-- 调用 playBeaconTune3()
|           |       |-- calibration_required = 1
|           |       |-- enter_calibration_count = 0
|           |-- last_input = adjusted_input
|
|-- 结束

3.2.2 定时触发

EXTI4_15_IRQHandler
 └──> processDshot
  1. 检查 compute_dshot_flag 是否为 1

    • 条件: compute_dshot_flag == 1
    • 操作:
      • 调用 computeDshotDMA() 函数。这通常会处理与 DShot 相关的数据计算或操作。
      • compute_dshot_flag 设置为 0,以重置标志位,表示处理已完成。
  2. 检查 compute_dshot_flag 是否为 2

    • 条件: compute_dshot_flag == 2
    • 操作:
      • 调用 make_dshot_package(e_com_time) 函数。这通常会生成 DShot 数据包,并可能涉及到对信号或数据的打包处理。
      • compute_dshot_flag 设置为 0,以重置标志位,表示处理已完成。
      • 使用 return 语句跳出函数,防止执行后续的 setInput() 调用。
  3. 调用 setInput()

    • 仅在 compute_dshot_flag 不为 1 或 2 时执行。该函数的作用是设置输入数据或处理其他与输入相关的操作。由于 return 已在第二个条件中使用,因此 setInput() 只在没有其他标志位处理的情况下执行。
processDshot()
|
|-- if (compute_dshot_flag == 1)
|   |
|   |-- computeDshotDMA()
|   |-- compute_dshot_flag = 0
|
|-- if (compute_dshot_flag == 2)
|   |
|   |-- make_dshot_package(e_com_time)
|   |-- compute_dshot_flag = 0
|   |-- return
|
|-- setInput()

3.3 协议检测

3.3.1 detectInput

  • 计算最小脉冲宽度平均脉冲宽度:这些值可以用于检测和分析输入信号的特性。
  • 条件检查:根据不同的标志位,调用相应的函数进行 DShot 或伺服 PWM 的检查。这确保了根据输入模式适当地处理信号。
detectInput()
|
|-- smallestnumber = 20000
|-- average_signal_pulse = 0
|-- lastnumber = dma_buffer[0]
|-- for (int j = 1; j < 31; j++)
|   |
|   |-- if (dma_buffer[j] - lastnumber > 0)
|   |   |
|   |   |-- if ((dma_buffer[j] - lastnumber) < smallestnumber)
|   |   |   |-- smallestnumber = dma_buffer[j] - lastnumber
|   |   |
|   |   |-- average_signal_pulse += (dma_buffer[j] - lastnumber)
|   |
|   |-- lastnumber = dma_buffer[j]
|-- average_signal_pulse = average_signal_pulse / 32
|
|-- if (dshot == 1)
|   |-- checkDshot()
|
|-- if (servoPwm == 1)
|   |-- checkServo()
|
|-- if (!dshot && !servoPwm)
|   |-- checkDshot()
|   |-- checkServo()
  1. 初始化变量

    • smallestnumber 设置为 20000
    • average_signal_pulse 设置为 0
    • lastnumber 设为 dma_buffer[0]
  2. 循环计算信号脉冲

    • 循环范围:j = 1j < 31 (共 30 次)
    • 操作:
      • 如果 dma_buffer[j] - lastnumber 大于 0(即当前值大于前一个值):
        • 如果当前脉冲宽度 dma_buffer[j] - lastnumber 小于 smallestnumber,则更新 smallestnumber
        • 累加当前脉冲宽度到 average_signal_pulse
      • 更新 lastnumber 为当前 dma_buffer[j]
  3. 计算平均信号脉冲

    • average_signal_pulse 除以 32 以获取平均值
  4. 根据标志位执行不同的检查

    • 如果 dshot == 1,调用 checkDshot() 函数
    • 如果 servoPwm == 1,调用 checkServo() 函数
    • 如果既 dshotservoPwm 都为 false,则分别调用 checkDshot()checkServo() 函数

3.3.2 checkDshot

  • 根据信号特征启用 DShot 模式: 根据信号的最小脉冲宽度和平均脉冲宽度来决定是否启用 DShot 模式,并设置相关的计时器和缓冲区参数。
  • 动态配置计时器: 根据 CPU 频率来调整计时器的预分频器,以确保适当的计时精度。
  • 配置缓冲区: 设置 buffer_paddingbuffersize,这些参数影响信号的处理和存储。
checkDshot()
|
|-- if ((smallestnumber >= 1) && (smallestnumber < 4) && (average_signal_pulse < 60))
|   |
|   |-- ic_timer_prescaler = 0
|   |-- if (CPU_FREQUENCY_MHZ > 100)
|   |   |-- output_timer_prescaler = 1
|   |-- else
|   |   |-- output_timer_prescaler = 0
|   |-- dshot = 1
|   |-- buffer_padding = 14
|   |-- buffersize = 32
|   |-- inputSet = 1
|
|-- if ((smallestnumber >= 4) && (smallestnumber <= 8) && (average_signal_pulse < 100))
|   |
|   |-- dshot = 1
|   |-- ic_timer_prescaler = 1
|   |-- if (CPU_FREQUENCY_MHZ > 100)
|   |   |-- output_timer_prescaler = 3
|   |-- else
|   |   |-- output_timer_prescaler = 1
|   |-- buffer_padding = 7
|   |-- buffersize = 32
|   |-- inputSet = 1
  1. 第一个条件判断

    • 条件:
      • smallestnumber 在 1 到 4 之间(包括 1,不包括 4)
      • average_signal_pulse 小于 60
    • 操作:
      • ic_timer_prescaler 设为 0
      • 根据 CPU_FREQUENCY_MHZ 判断:
        • 如果 CPU 频率大于 100 MHz,output_timer_prescaler 设为 1
        • 否则,output_timer_prescaler 设为 0
      • 设置 dshot 标志为 1(启用 DShot 模式)
      • buffer_padding 设为 14
      • buffersize 设为 32
      • inputSet 设为 1(表示输入设置已完成)
  2. 第二个条件判断

    • 条件:
      • smallestnumber 在 4 到 8 之间(包括 4 和 8)
      • average_signal_pulse 小于 100
    • 操作:
      • dshot 标志设为 1(启用 DShot 模式)
      • ic_timer_prescaler 设为 1
      • 根据 CPU_FREQUENCY_MHZ 判断:
        • 如果 CPU 频率大于 100 MHz,output_timer_prescaler 设为 3
        • 否则,output_timer_prescaler 设为 1
      • buffer_padding 设为 7
      • buffersize 设为 32
      • inputSet 设为 1(表示输入设置已完成)

3.3.3 checkServo

  • 根据信号特征启用伺服 PWM 模式: 根据信号的最小脉冲宽度来决定是否启用伺服 PWM 模式,并配置相关参数。
  • 调整计时器设置: 动态设置计时器预分频器,以匹配 CPU 频率和信号要求。
  • 配置处理参数: 设置 buffersize 为 2,这可能意味着伺服信号只需要少量数据进行处理和存储。
checkServo()
|
|-- if (smallestnumber > 200 && smallestnumber < 20000)
|   |
|   |-- servoPwm = 1
|   |-- ic_timer_prescaler = CPU_FREQUENCY_MHZ - 1
|   |-- buffersize = 2
|   |-- inputSet = 1
  1. 条件判断

    • 条件:
      • smallestnumber 大于 200 且小于 20000
  2. 操作:

    • 启用伺服 PWM 模式:servoPwm 设为 1,表示启用伺服 PWM 模式。
    • 设置计时器预分频器:ic_timer_prescaler 设为 CPU_FREQUENCY_MHZ - 1,以便适应 CPU 频率的要求。
    • 配置缓冲区大小:buffersize 设为 2,以适应伺服 PWM 信号的处理。
    • 标记输入设置完成:inputSet 设为 1,表示输入设置已完成。

3.4 报文处理

3.4.1 computeDshotDMA

注:貌似关于指令部分的代码对照命令表格尚未都完成。关于16bit长度脉冲宽度方面的内容尚没有完全明白,后续再跟进!!!

computeDshotDMA()
|
|-- dshot_frametime = dma_buffer[31] - dma_buffer[0]
|-- halfpulsetime = dshot_frametime >> 5
|
|-- if ((dshot_frametime > dshot_frametime_low) && (dshot_frametime < dshot_frametime_high))
|   |
|   |-- signaltimeout = 0
|   |-- for (int i = 0; i < 16; i++)
|   |   |
|   |   |-- dpulse[i] = ((dma_buffer[j + (i << 1) + 1] - dma_buffer[j + (i << 1)]) > (halfpulsetime))
|   |
|   |-- uint8_t calcCRC = ((dpulse[0] ^ dpulse[4] ^ dpulse[8]) << 3 | (dpulse[1] ^ dpulse[5] ^ dpulse[9]) << 2 | (dpulse[2] ^ dpulse[6] ^ dpulse[10]) << 1 | (dpulse[3] ^ dpulse[7] ^ dpulse[11]))
|   |-- uint8_t checkCRC = (dpulse[12] << 3 | dpulse[13] << 2 | dpulse[14] << 1 | dpulse[15])
|   |
|   |-- if (!armed)
|   |   |
|   |   |-- if (dshot_telemetry == 0)
|   |       |
|   |       |-- if (getInputPinState())
|   |           |
|   |           |-- high_pin_count++
|   |           |-- if (high_pin_count > 100)
|   |               |
|   |               |-- dshot_telemetry = 1
|   |
|   |-- if (dshot_telemetry)
|   |   |
|   |   |-- checkCRC = ~checkCRC + 16
|   |
|   |-- int tocheck = (dpulse[0] << 10 | dpulse[1] << 9 | dpulse[2] << 8 | dpulse[3] << 7 | dpulse[4] << 6 | dpulse[5] << 5 | dpulse[6] << 4 | dpulse[7] << 3 | dpulse[8] << 2 | dpulse[9] << 1 | dpulse[10])
|   |
|   |-- if (calcCRC == checkCRC)
|   |   |
|   |   |-- signaltimeout = 0
|   |   |-- dshot_goodcounts++
|   |   |
|   |   |-- if (dpulse[11] == 1)
|   |   |   |
|   |   |   |-- send_telemetry = 1
|   |
|   |   |-- if (tocheck > 47)
|   |   |   |
|   |   |   |-- if (EDT_ARMED)
|   |   |       |
|   |   |       |-- newinput = tocheck
|   |   |       |-- dshotcommand = 0
|   |   |       |-- command_count = 0
|   |   |
|   |   |-- if ((tocheck <= 47) && (tocheck > 0))
|   |   |   |
|   |   |   |-- newinput = 0
|   |   |   |-- dshotcommand = tocheck
|   |   |
|   |   |-- if (tocheck == 0)
|   |       |
|   |       |-- if (EDT_ARM_ENABLE == 1)
|   |           |
|   |           |-- EDT_ARMED = 0
|   |       |-- newinput = 0
|   |       |-- dshotcommand = 0
|   |       |-- command_count = 0
|   |
|   |-- if ((dshotcommand > 0) && (running == 0) && armed)
|   |   |
|   |   |-- if (dshotcommand != last_command)
|   |   |   |
|   |   |   |-- last_command = dshotcommand
|   |   |   |-- command_count = 0
|   |   |
|   |   |-- if (dshotcommand < 5)
|   |   |   |
|   |   |   |-- command_count = 6
|   |   |
|   |   |-- command_count++
|   |   |-- if (command_count >= 6)
|   |       |
|   |       |-- command_count = 0
|   |       |
|   |       |-- switch (dshotcommand)
|   |           |
|   |           |-- case 1: play_tone_flag = 1
|   |           |-- case 2: play_tone_flag = 2
|   |           |-- case 3: play_tone_flag = 3
|   |           |-- case 4: play_tone_flag = 4
|   |           |-- case 5: play_tone_flag = 5
|   |           |-- case 7: dir_reversed = 0; forward = 1 - dir_reversed
|   |           |-- case 8: dir_reversed = 1; forward = 1 - dir_reversed
|   |           |-- case 9: bi_direction = 0
|   |           |-- case 10: bi_direction = 1
|   |           |-- case 12: saveEEpromSettings(); play_tone_flag = 1 + dir_reversed
|   |           |-- case 13: dshot_extended_telemetry = 1; send_extended_dshot = 0b111000000000; if (EDT_ARM_ENABLE == 1) { EDT_ARMED = 1; }
|   |           |-- case 14: dshot_extended_telemetry = 0; send_extended_dshot = 0b111011111111
|   |           |-- case 20: forward = 1 - dir_reversed
|   |           |-- case 21: forward = dir_reversed
|   |       |-- last_dshot_command = dshotcommand
|   |       |-- dshotcommand = 0
|   |
|   |-- else
|       |
|       |-- dshot_badcounts++
  1. 初始化与计算

    • dshot_frametime 计算 DSHOT 帧的时间。
    • halfpulsetime 是帧时间的一半,右移 5 位。
  2. Dshot 信号验证

    • 条件: 如果 dshot_frametime 在指定范围内(dshot_frametime_lowdshot_frametime_high)。
      • 设置超时计数器为 0
      • 计算脉冲数组: 用于 CRC 校验的 dpulse 数组,根据脉冲宽度判断高低电平。
      • 计算 CRC:
        • calcCRC 计算实际的 CRC 值。
        • checkCRC 计算从数据中提取的 CRC 值。
      • 校验 CRC:
        • 如果 dshot_telemetry 为 1,反转并加 16 到 checkCRC
  3. 处理 Dshot 数据

    • 条件: 如果 CRC 校验成功。
      • 设置超时计数器为 0,增加 dshot_goodcounts
      • 处理数据:
        • 发送遥测数据: 如果 dpulse[11] 为 1,设置 send_telemetry
        • 处理命令:
          • 如果 tocheck 大于 47 且 EDT_ARMED,更新 newinputdshotcommand
          • 如果 tocheck 在 0 和 47 之间,设置 newinput 为 0 并更新 dshotcommand
          • 如果 tocheck 为 0,根据 EDT_ARM_ENABLE 更新状态并重置命令。
      • 处理 Dshot 命令:
        • 如果 dshotcommand 大于 0 且 running 为 0 且 armed,处理 Dshot 命令,根据 dshotcommand 更新状态或执行操作。
    • CRC 校验失败: 增加 dshot_badcounts

3.4.2 make_dshot_package

make_dshot_package(uint16_t com_time)
|
|-- 如果 (send_extended_dshot > 0)
|   |-- dshot_full_number = send_extended_dshot
|   |-- send_extended_dshot = 0
|-- 否则
|   |-- 如果 (!running || (com_time > 65535))
|   |   |-- com_time = 65535
|   |
|   |-- 计算数据的位移量
|   |   |-- 从 15 到 9 反向循环
|   |   |   |-- 如果 (com_time >> i == 1)
|   |   |   |   |-- shift_amount = i + 1 - 9
|   |   |   |   |-- 退出循环
|   |   |   |-- 否则
|   |   |       |-- shift_amount = 0
|   |
|   |-- 将通信时间左移并设置位移量
|   |   |-- dshot_full_number = ((shift_amount << 9) | (com_time >> shift_amount))
|   |
|   |-- 计算校验和
|   |   |-- uint16_t csum = 0
|   |   |-- uint16_t csum_data = dshot_full_number
|   |   |-- 循环 3 次
|   |   |   |-- csum ^= csum_data // 按半字节异或数据
|   |   |   |-- csum_data >>= 4
|   |   |-- csum = ~csum // 取反
|   |   |-- csum &= 0xf
|   |
|   |-- 将校验和添加到 dshot_full_number
|   |   |-- dshot_full_number = (dshot_full_number << 4) | csum
|   |
|   |-- GCR RLL 编码 16 位到 20 位
|   |   |-- gcrnumber = gcr_encode_table[(dshot_full_number >> 12)] << 15
|   |   |   |-- gcr_encode_table[(((1 << 4) - 1) & (dshot_full_number >> 8))] << 10
|   |   |   |-- gcr_encode_table[(((1 << 4) - 1) & (dshot_full_number >> 4))] << 5
|   |   |   |-- gcr_encode_table[(((1 << 4) - 1) & (dshot_full_number >> 0))]
|   |
|   |-- GCR RLL 编码 20 位到 21 位输出
|   |   |-- #if defined(MCU_F051) || defined(MCU_F031)
|   |   |   |-- gcr[1 + buffer_padding] = 64
|   |   |   |-- 循环 19 到 0
|   |   |   |   |-- gcr[buffer_padding + 20 - i + 1] = ((((gcrnumber & 1 << i)) >> i) ^ (gcr[buffer_padding + 20 - i] >> 6)) << 6
|   |   |   |-- gcr[buffer_padding] = 0
|   |   |
|   |   |-- #else
|   |   |   |-- gcr[1 + buffer_padding] = 128
|   |   |   |-- 循环 19 到 0
|   |   |   |   |-- gcr[buffer_padding + 20 - i + 1] = ((((gcrnumber & 1 << i)) >> i) ^ (gcr[buffer_padding + 20 - i] >> 7)) << 7
|   |   |   |-- gcr[buffer_padding] = 0
|
|-- 结束
  1. 处理扩展 Dshot 数据

    • 条件: 如果 send_extended_dshot 大于 0。
      • 设置 dshot_full_numbersend_extended_dshot
      • 重置 send_extended_dshot 为 0。
  2. 处理常规 Dshot 数据

    • 条件: 如果 running 为假或 com_time 大于 65535。
      • com_time 设置为 65535。
    • 计算 shift_amount
      • 循环从 15 到 9(即 Dshot 数据的最高位到最低位),确定数据的位移量。
      • 如果 com_time 的第 i 位为 1,计算 shift_amount 并跳出循环。
    • 计算 dshot_full_number
      • shift_amountcom_time 进行位移并组合为 dshot_full_number
  3. 计算校验和

    • 初始化 csum 为 0 和 csum_datadshot_full_number
    • 循环 3 次:
      • csum_data 进行异或操作,更新 csum
    • csum 进行取反并截断到 4 位。
    • 将校验和添加到 dshot_full_number 的末尾。
  4. GCR RLL 编码

    • 计算 gcrnumber
      • 使用 GCR 编码表将 dshot_full_number 编码为 20 位数字。
    • 根据不同 MCU 定义,处理 GCR 编码输出。
      • 对于 MCU_F051 或 MCU_F031:
        • 设置 gcr[1 + buffer_padding] 为 64。
        • gcrnumber 的每位数字编码到 gcr 中,应用适当的位移和异或操作。
        • gcr[buffer_padding] 设置为 0。
      • 对于其他 MCU:
        • 设置 gcr[1 + buffer_padding] 为 128。
        • gcrnumber 的每位数字编码到 gcr 中,应用适当的位移和异或操作。
        • gcr[buffer_padding] 设置为 0。

4. 总结

从目前库上代码看,还有很多DSHOT命令相关的代码上没有合入am32-firmware/AM32, AM32的代码可能更多的还是需要从历史版本中查询AlkaMotors/AM32-MultiRotor-ESC-firmware。

  • Is there any link to bootloader? #62
  • What kind of situation will use second set of parameters from eeprom_address(0x0800F800), not default EEPROM_START_ADD(0x08007C00)? #55

5. 参考资料

【1】BLDC ESC 无刷直流电子调速器简介
【2】BLDC ESC 无刷直流电子调速器工作原理
【3】BLDC ESC 无刷直流电子调速器工作过程
【4】BLDC ESC 无刷直流电子调速器驱动方式
【5】AM32开源代码之工程结构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2055562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS day0820

ok了家人们今天学习Dom对象&#xff0c;和一个综合案例&#xff0c;一起去看看吧。 一.BOM对象 Browser Object Model 浏览器对象模型。 JavaScript 将浏览器的各 个组成部分封装为对象 Window &#xff1a;浏览器窗口对象。 对象表示浏览器中打开的窗口Navigator&#xff1…

(含华为案例) 企业数字化转型规划方案313页PDF限免下载!

一、前言 数字化转型势在必行&#xff0c;这已成为所有企业CIO的共识。但在现实中&#xff0c;很多数字化转型计划还是陷入重重困难&#xff0c;其原因大多在于企业内部对数字化转型的认知还不够透彻。尤其是对于那些业务正处于发展和上升期的公司&#xff0c;各个部门的负责人…

算法学习017 不同的二叉搜索树 c++算法学习 中小学算法思维学习 比赛算法题解 信奥算法解析

目录 C不同的二叉搜索树 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 六、推荐资料 C不同的二叉搜索树 一、题目要求 1、编程实现 给定一个整数n&#xff0c;求以1、2、3、......、n为节点组成的二叉搜索树有多少种…

网络瘫痪幕后黑手:如何应对TCP/IP端口消耗?

TCP/IP 协议是互联网通信的基础&#xff0c;它的稳定性和可靠性使我们使用互联网的十分重要的一点。在网络运行过程中&#xff0c; TCP/IP 的连接问题中&#xff0c; TCP/IP 端口耗尽故障是较为常见的一种。我们要及时有效地排除这些故障来保障网络的正常运行并确保业务顺利开展…

Windows下如何将mmdetection训练好的模型导出为onnx格式?

写在前面 注意:第一部分是踩坑记录,第二部分才是正确的导出步骤!!!! 踩坑方法记录 这一部分的方法看样子好像没啥问题,但是一步步繁琐的操作下来你会发现,你已经入坑了!!! 提醒大家,如果你正在按照这个方法导出模型,劝你尽快放弃,行不通【原因在于后续的pyth…

Linux网络配置的基本原理、常用命令以及实战操作

&#x1f600;前言 本篇博文是关于Linux网络配置的基本原理、常用命令以及实战操作&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您…

elasticsearch pipelineI详解:原理与使用

码到三十五 &#xff1a; 个人主页 在Elasticsearch的数据处理流程中&#xff0c;Pipeline API为数据的预处理和转换提供了强大的工具。随着Elasticsearch 5.x版本之后Ingest Node的引入&#xff0c;Pipeline API的引入为开发者们提供了更多的灵活性和便利性。本文将对Pipeline…

leetcode322. 零钱兑换,完全背包最值问题,附背包问题模板

leetcode322. 零钱兑换 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1 。 你可以认为每种…

人机环境系统智能已经超越了传统的空间智能和物理世界的概念

人机环境系统智能已经超越了传统的空间智能和物理世界的概念&#xff0c;进入了更为复杂的层次。在人机环境系统中&#xff0c;智能不仅涉及对物理世界的感知和理解&#xff0c;还包括对人类语言、情感、意图等的理解和生成。人工智能技术的应用&#xff0c;如自然语言处理、机…

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(三)---创建自定义激光雷达Componet组件

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车&#xff0c;并使用通过跨平台的方式进行ROS2和UE5仿真的通讯&#xff0c;达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础&#xff0c;Nav2相关的学习教程可以参考本人的其他博…

Kubernetes的快速安装

一、kubernetes的基本概念 1.kubernetes Kubernetes 是一个开源的开源的分布式编排技术&#xff0c;Kubernetes 致力于提供跨主机集群的自动部署、扩展、高可用以及运行应用程序容器的平台&#xff0c;其遵循主从式架构设计、组件可以分为工作节点 (Node) 组件&#xff0c;和控…

基础第3关:LangGPT结构化提示词编写实践

提示词&#xff1a; # Role: 伟大的数学家 ## Profile - author: LangGPT - version: 1.0 - language: 中文 - description: 一个伟大的数学家&#xff0c;能够解决任何的数学难题 ## Goals: 根据关键词进行描述&#xff0c;避免与已有描述重复。 ## Background: 你正在被…

2024网安创新大赛,美创科技产品方案双获奖!

2024年网络安全优秀创新成果大赛 “2024年网络安全优秀创新成果大赛”是国家网络安全宣传周重要活动之一。大赛由中央网信办指导、中国网络安全产业联盟&#xff08;CCIA&#xff09;主办。 近日&#xff0c;“2024年网络安全优秀创新成果大赛-杭州分站赛” 正式公布评选结果。…

强!小目标检测全新突破!检测速度快10倍,GPU使用减少73.4%

强&#xff01;小目标检测全新突破&#xff0c;提出Mamba-in-Mamba结构&#xff0c;通过内外两层Mamba模块&#xff0c;同时提取全局和局部特征&#xff0c;实现了检测速度快10倍&#xff0c;GPU使用减少73.4&#xff05;的显著效果&#xff01; 【小目标检测】是近年来在深度…

点灯案例练习(基于寄存器)

目录 一、需求描述 二、工程创建 二、硬件电路设计 三、软件设计 1、main.c 1、开启时钟 2、配置GPIOA的工作模式 3、设置PA1、PA8端口低电平 4、给死循环保持状态 2、最终代码如下 四、实验现象 前面&#xff0c;我们耗费大量时间&#xff0c;终于点亮了STM32板子上的…

WLAN基础知识(1)

WLAN&#xff1a; 无线局域网&#xff0c;无线技术&#xff1a;Wi-Fi、红外、蓝牙等 WLAN设备&#xff1a; 胖AP&#xff1a; 适用于家庭等小型网络&#xff0c;可独立配置&#xff0c;如&#xff1a;家用Wi-Fi路由器 瘦AP&#xff1a; 适用于大中型企业&#xff0c;需要配合AC…

【Kettle】新建转换工程

目录 一、新建一个转换工程1. 创建【转换】工程2. 创建输入对象并编辑步骤3. 创建输出对象并编辑步骤 二、运行转换工程和查看执行结果1. 运行转换工程2. 查看执行结果 一、新建一个转换工程 1. 创建【转换】工程 在 Kettle 欢迎界面中&#xff0c;依次点击【新建】->【转…

其实你就学不会 Python

标题党一下&#xff0c;Python 程序员成千上万&#xff0c;当然有很多人学得会。这里说的“你”&#xff0c;是指职场中的非专业人员。 职场人员一般会用 Excel 处理数据&#xff0c;但也会有很多无助的情况&#xff0c;比如复杂计算、重复计算、自动处理等&#xff0c;再遇上个…

中石油笔试25届秋招考什么?如何通过在线测评|附真题库面试攻略

职小豚 一、中石油公司介绍 嘿&#xff0c;小伙伴们&#xff01;今天咱们来聊聊大名鼎鼎的中石油。 中石油&#xff0c;那可是能源领域的巨无霸&#xff01;它就像一座庞大的能源宝库&#xff0c;为我们的生活和国家的发展源源不断地输送着动力。 中石油在国内外的油气勘探…

如何优雅的薅羊毛之Flux.1免费使用还支持中文prompt

我看硅基流动&#xff0c;现在免费用Flux.1的模型了&#xff0c;就注册了一个账号 但是Flux和之前的sd一样&#xff0c;中文理解力有问题 换哪个模型都不成&#xff0c;直接换英文提示词还行 放DIFY里串一下 我看tool里没有&#xff0c;那就自定义一个 DIFY要求schema要满足op…