AM32开源代码之代码分析 - bootloader

1. 源由

之前就说过关于bootloader的问题:

目前,总算是抽象、汇总到一份代码了。

2. 框架

main
│
├── bl_clock_config()           // 配置时钟
│
├── bl_timer_init()             // 初始化定时器
│
├── bl_gpio_init()              // 初始化GPIO
│
├── #ifdef BOOTLOADER_TEST_CLOCK
│   └── test_clock()            // 如果定义了BOOTLOADER_TEST_CLOCK,则测试时钟
│
├── checkForSignal()            // 检查信号
│
├── gpio_mode_set_input(input_pin, GPIO_PULL_NONE) // 设置GPIO输入模式,不使用内部上拉/下拉电阻
│
├── #ifdef USE_ADC_INPUT
│   └── jump()                  // 如果定义了USE_ADC_INPUT,直接跳转到应用程序
│
├── deviceInfo[3] = pin_code     // 将 pin_code 存储到 deviceInfo 数组的第3个位置
│
├── #ifdef UPDATE_EEPROM_ENABLE
│   └── update_EEPROM()         // 如果定义了UPDATE_EEPROM_ENABLE,更新EEPROM
│
└── while (1)                   // 主循环
    ├── receiveBuffer()         // 接收数据缓冲
    └── if (invalid_command > 100)
        └── jump()              // 如果 invalid_command 超过100,则跳转到应用程序
  1. bl_clock_config(): 配置时钟,以初始化系统时钟源和频率。
  2. bl_timer_init(): 初始化定时器,用于系统中的时间控制。
  3. bl_gpio_init(): 初始化GPIO(通用输入/输出)端口,配置所需的引脚。
  4. test_clock(): 如果定义了 BOOTLOADER_TEST_CLOCK,则执行时钟测试功能。
  5. checkForSignal(): 检查外部信号,以确定是否继续执行。
  6. gpio_mode_set_input(input_pin, GPIO_PULL_NONE): 将指定的 input_pin 设置为输入模式,不使用内部上拉或下拉电阻。
  7. jump(): 如果定义了 USE_ADC_INPUT,直接跳转到应用程序。
  8. deviceInfo[3] = pin_code: 将 pin_code 存储在 deviceInfo 数组的第三个位置。
  9. update_EEPROM(): 如果定义了 UPDATE_EEPROM_ENABLE,则更新EEPROM中的数据。
  10. while(1): 主循环,持续执行以下操作。
    • receiveBuffer(): 从缓冲区接收数据。
    • if (invalid_command > 100): 如果无效命令的计数超过100,执行 jump() 跳转到应用程序。

3. 重要函数

3.1 receiveBuffer

典型的基于字符流的报文包处理方式:

receiveBuffer
│
├── count = 0                            // 初始化计数器为0
│
├── messagereceived = 0                  // 初始化消息接收状态为0
│
├── memset(rxBuffer, 0, sizeof(rxBuffer))// 清空接收缓冲区 rxBuffer
│
├── for(int i = 0; i < sizeof(rxBuffer); i++) // 循环遍历 rxBuffer 的每个字节
│   ├── serialreadChar()                 // 读取一个字符到 rxbyte 中
│   │
│   ├── if(incoming_payload_no_command)  // 判断是否为无命令的有效载荷
│   │   ├── if(count == payload_buffer_size+2) // 检查计数器是否达到有效载荷缓冲区大小+2
│   │   │   └── break                    // 如果达到,跳出循环
│   │   └── rxBuffer[i] = rxbyte         // 否则,将 rxbyte 存入 rxBuffer
│   │       └── count++                  // 增加计数器
│   │
│   └── else                             // 如果不是无命令的有效载荷
│       ├── if(bl_timer_us() > 250)      // 检查定时器是否超过250微秒
│       │   ├── count = 0                // 如果超过,重置计数器
│       │   └── break                    // 跳出循环
│       └── rxBuffer[i] = rxbyte         // 否则,将 rxbyte 存入 rxBuffer
│           └── if(i == 257)             // 检查当前索引是否为257
│               └── invalid_command += 20 // 如果是,增加无效命令计数器(需要100才能触发跳转,但在下一个设置地址命令时会重置)
│
└── decodeInput()                        // 解析接收到的数据
  1. count = 0: 初始化计数器 count,用于记录接收到的字节数。
  2. messagereceived = 0: 初始化消息接收状态,表示还没有接收到完整的消息。
  3. memset(rxBuffer, 0, sizeof(rxBuffer)): 将 rxBuffer 缓冲区清零,准备接收新数据。
  4. for(int i = 0; i < sizeof(rxBuffer); i++): 循环遍历 rxBuffer,准备接收数据。
    • serialreadChar(): 读取一个字符,并存储在 rxbyte 中。
    • if(incoming_payload_no_command): 判断是否是无命令的有效载荷。
      • if(count == payload_buffer_size+2): 如果计数器 count 达到有效载荷缓冲区大小+2,跳出循环。
      • rxBuffer[i] = rxbyte: 否则,将读取的字符 rxbyte 存入 rxBuffer 的当前索引 i 处。
      • count++: 增加计数器 count
    • else: 如果不是无命令的有效载荷。
      • if(bl_timer_us() > 250): 检查是否已经过了 250 微秒。
        • count = 0: 如果超过了 250 微秒,重置计数器 count
        • break: 跳出循环,停止接收数据。
      • rxBuffer[i] = rxbyte: 否则,将读取的字符 rxbyte 存入 rxBuffer 的当前索引 i 处。
      • if(i == 257): 如果当前索引 i 等于 257。
        • invalid_command += 20: 增加 invalid_command 计数器,累积无效命令数量。当达到 100 时,会触发跳转,但在下一个设置地址命令时会重置。
  5. decodeInput(): 完成数据接收后,调用 decodeInput() 函数解析接收到的数据。

3.2 decodeInput

整个函数结构通过多个条件分支来解析和执行不同的命令,同时还处理了一些错误情况,比如CRC校验失败或者无效命令。

decodeInput
│
├── if (incoming_payload_no_command)                              // 检查是否接收无命令的有效载荷
│   ├── len = payload_buffer_size                                 // 设置有效载荷的长度
│   ├── if (checkCrc(rxBuffer,len))                               // 检查CRC是否有效
│   │   ├── memset(payLoadBuffer, 0, sizeof(payLoadBuffer))       // 清空 payLoadBuffer 缓冲区
│   │   ├── for(int i = 0; i < len; i++)                          // 将 rxBuffer 的内容复制到 payLoadBuffer
│   │   │   └── payLoadBuffer[i] = rxBuffer[i]
│   │   ├── send_ACK()                                            // 发送 ACK 确认
│   │   ├── incoming_payload_no_command = 0                       // 重置 incoming_payload_no_command 标志
│   │   └── return                                                // 返回,结束函数执行
│   └── else
│       ├── send_BAD_CRC_ACK()                                    // 发送 CRC 错误的确认消息
│       └── return                                                // 返回,结束函数执行
│
├── cmd = rxBuffer[0]                                             // 从 rxBuffer 中读取命令字节
│
├── if (rxBuffer[16] == 0x7d)                                     // 检查索引 16 是否为 0x7d
│   ├── if (rxBuffer[8] == 13 && rxBuffer[9] == 66)               // 检查索引 8 和 9 是否符合特定模式
│   │   ├── sendDeviceInfo()                                      // 发送设备信息
│   │   └── rxBuffer[20] = 0                                      // 重置索引 20
│   └── return                                                    // 返回,结束函数执行
│
├── if (rxBuffer[20] == 0x7d)                                     // 检查索引 20 是否为 0x7d
│   ├── if (rxBuffer[12] == 13 && rxBuffer[13] == 66)             // 检查索引 12 和 13 是否符合特定模式
│   │   ├── sendDeviceInfo()                                      // 发送设备信息
│   │   └── rxBuffer[20] = 0                                      // 重置索引 20
│   └── return                                                    // 返回,结束函数执行
│
├── if (rxBuffer[40] == 0x7d)                                     // 检查索引 40 是否为 0x7d
│   ├── if (rxBuffer[32] == 13 && rxBuffer[33] == 66)             // 检查索引 32 和 33 是否符合特定模式
│   │   ├── sendDeviceInfo()                                      // 发送设备信息
│   │   └── rxBuffer[20] = 0                                      // 重置索引 20
│   └── return                                                    // 返回,结束函数执行
│
├── if (cmd == CMD_RUN)                                           // 如果命令是 CMD_RUN
│   └── if ((rxBuffer[1] == 0) && (rxBuffer[2] == 0) && (rxBuffer[3] == 0))
│       └── invalid_command = 101                                 // 如果满足条件,设置无效命令计数为 101
│
├── if (cmd == CMD_PROG_FLASH)                                    // 如果命令是 CMD_PROG_FLASH
│   ├── len = 2                                                   // 设置数据长度为 2
│   ├── if (!checkCrc((uint8_t*)rxBuffer, len))                   // 检查 CRC 是否有效
│   │   ├── send_BAD_CRC_ACK()                                    // 如果无效,发送 CRC 错误的确认消息
│   │   └── return                                                // 返回,结束函数执行
│   ├── if (!checkAddressWritable(address))                      // 检查地址是否可写
│   │   ├── send_BAD_ACK()                                        // 如果不可写,发送 BAD ACK
│   │   └── return                                                // 返回,结束函数执行
│   ├── if (!save_flash_nolib((uint8_t*)payLoadBuffer, payload_buffer_size, address))
│   │   └── send_BAD_ACK()                                        // 如果写入失败,发送 BAD ACK
│   ├── else
│   │   └── send_ACK()                                            // 否则,发送 ACK 确认
│   └── return                                                    // 返回,结束函数执行
│
├── if (cmd == CMD_SET_ADDRESS)                                   // 如果命令是 CMD_SET_ADDRESS
│   ├── len = 4                                                   // 设置数据长度为 4
│   ├── if (!checkCrc((uint8_t*)rxBuffer, len))                   // 检查 CRC 是否有效
│   │   ├── send_BAD_CRC_ACK()                                    // 如果无效,发送 CRC 错误的确认消息
│   │   └── return                                                // 返回,结束函数执行
│   ├── invalid_command = 0                                       // 重置无效命令计数器
│   ├── address = STM32_FLASH_START + ((rxBuffer[2] << 8 | rxBuffer[3]) << ADDRESS_SHIFT) // 设置地址
│   ├── send_ACK()                                                // 发送 ACK 确认
│   └── return                                                    // 返回,结束函数执行
│
├── if (cmd == CMD_SET_BUFFER)                                    // 如果命令是 CMD_SET_BUFFER
│   ├── len = 4                                                   // 设置数据长度为 4
│   ├── if (!checkCrc((uint8_t*)rxBuffer, len))                   // 检查 CRC 是否有效
│   │   ├── send_BAD_CRC_ACK()                                    // 如果无效,发送 CRC 错误的确认消息
│   │   └── return                                                // 返回,结束函数执行
│   ├── if (rxBuffer[2] == 0x01)
│   │   └── payload_buffer_size = 256                             // 设置有效载荷缓冲区大小为 256
│   └── else
│       └── payload_buffer_size = rxBuffer[3]                     // 否则,使用 rxBuffer 中的值设置大小
│   ├── incoming_payload_no_command = 1                           // 设置 incoming_payload_no_command 标志
│   ├── address_expected_increment = 256                          // 设置地址预期增量为 256
│   ├── setReceive()                                              // 调用 setReceive 函数
│   └── return                                                    // 返回,结束函数执行
│
├── if (cmd == CMD_KEEP_ALIVE)                                    // 如果命令是 CMD_KEEP_ALIVE
│   ├── len = 2                                                   // 设置数据长度为 2
│   ├── if (!checkCrc((uint8_t*)rxBuffer, len))                   // 检查 CRC 是否有效
│   │   ├── send_BAD_CRC_ACK()                                    // 如果无效,发送 CRC 错误的确认消息
│   │   └── return                                                // 返回,结束函数执行
│   ├── setTransmit()                                             // 切换到传输模式
│   ├── serialwriteChar(0xC1)                                     // 发送 0xC1 表示错误命令
│   ├── setReceive()                                              // 切换到接收模式
│   └── return                                                    // 返回,结束函数执行
│
├── if (cmd == CMD_ERASE_FLASH)                                   // 如果命令是 CMD_ERASE_FLASH
│   ├── len = 2                                                   // 设置数据长度为 2
│   ├── if (!checkCrc((uint8_t*)rxBuffer, len))                   // 检查 CRC 是否有效
│   │   ├── send_BAD_CRC_ACK()                                    // 如果无效,发送 CRC 错误的确认消息
│   │   └── return                                                // 返回,结束函数执行
│   ├── if (!checkAddressWritable(address))                      // 检查地址是否可写
│   │   ├── send_BAD_ACK()                                        // 如果不可写,发送 BAD ACK
│   │   └── return                                                // 返回,结束函数执行
│   ├── send_ACK()                                                // 发送 ACK 确认
│   └── return                                                    // 返回,结束函数执行
│
├── if (cmd == CMD_READ_EEPROM)                                   // 如果命令是 CMD_READ_EEPROM
│   └── eeprom_req = 1                                            // 设置 eeprom 请求标志
│
├── if (cmd == CMD_READ_FLASH_SIL)                                // 如果命令是 CMD_READ_FLASH_SIL
│   ├── len = 2                                                   // 设置数据长度为 2
│   ├── if (!checkCrc((uint8_t*)rxBuffer, len))                   // 检查CRC 是否有效
│   │   ├── send_BAD_CRC_ACK()                                    // 如果无效,发送 CRC 错误的确认消息
│   │   └── return                                                // 返回,结束函数执行
│   ├── count++                                                   // 增加计数器
│   ├── uint16_t out_buffer_size = rxBuffer[1]                    // 设置输出缓冲区大小
│   ├── if (out_buffer_size == 0)
│   │   ├── out_buffer_size = 256                                 // 如果大小为 0,则设置为 256
│   ├── address_expected_increment = 128                          // 设置地址预期增量为 128
│   ├── setTransmit()                                             // 切换到传输模式
│   ├── uint8_t read_data[out_buffer_size + 3]                    // 创建读取数据缓冲区
│   ├── memset(read_data, 0, sizeof(read_data))                   // 清空读取数据缓冲区
│   ├── read_flash_bin((uint8_t*)read_data, address, out_buffer_size) // 从 Flash 中读取数据
│   ├── makeCrc(read_data, out_buffer_size)                       // 计算 CRC
│   ├── read_data[out_buffer_size] = calculated_crc_low_byte      // 存储 CRC 低字节
│   ├── read_data[out_buffer_size + 1] = calculated_crc_high_byte // 存储 CRC 高字节
│   ├── read_data[out_buffer_size + 2] = 0x30                     // 存储 ACK
│   ├── sendString(read_data, out_buffer_size + 3)                // 发送读取的数据
│   ├── setReceive()                                              // 切换到接收模式
│   └── return                                                    // 返回,结束函数执行
│
├── setTransmit()                                                 // 切换到传输模式
│
└── serialwriteChar(0xC1)                                         // 发送 0xC1 表示错误命令
    ├── invalid_command++                                         // 增加无效命令计数器
    └── setReceive()                                              // 切换到接收模式
  1. incoming_payload_no_command: 判断是否正在处理无命令的有效载荷,并根据CRC校验结果处理数据或发送错误消息。
  2. cmd = rxBuffer[0]: 解析收到的命令字节,并根据不同的命令字节执行相应的操作。
  3. CMD_RUN: 启动主应用程序,并根据接收到的参数更新无效命令计数。
  4. CMD_PROG_FLASH: 编程Flash存储器,包括CRC校验、地址可写性检查以及数据保存等操作。
  5. CMD_SET_ADDRESS: 设置目标地址,并根据CRC校验结果确认或拒绝该操作。
  6. CMD_SET_BUFFER: 设置接收缓冲区的大小,并初始化接收过程。
  7. CMD_KEEP_ALIVE: 发送“保持连接”消息,以防止连接超时。
  8. CMD_ERASE_FLASH: 擦除Flash存储器中的数据。
  9. CMD_READ_EEPROM: 设置EEPROM读取请求标志。
  10. CMD_READ_FLASH_SIL: 从Flash存储器中读取数据,并将其发送回客户端。

注:部分命令目前尚未支持。

#define CMD_RUN             0x00
#define CMD_PROG_FLASH      0x01
#define CMD_ERASE_FLASH     0x02
#define CMD_READ_FLASH_SIL  0x03
#define CMD_VERIFY_FLASH    0x03
#define CMD_VERIFY_FLASH_ARM 0x04
#define CMD_READ_EEPROM     0x04
#define CMD_PROG_EEPROM     0x05
#define CMD_READ_SRAM       0x06
#define CMD_READ_FLASH_ATM  0x07
#define CMD_KEEP_ALIVE      0xFD
#define CMD_SET_ADDRESS     0xFF
#define CMD_SET_BUFFER      0xFE

4. 硬件端口

bootloader/main.c

#ifdef USE_PA2
#define input_pin        GPIO_PIN(2)
#define input_port       GPIOA
#define PIN_NUMBER       2
#define PORT_LETTER      0
#elif defined(USE_PB4)
#define input_pin         GPIO_PIN(4)
#define input_port        GPIOB
#define PIN_NUMBER        4
#define PORT_LETTER       1
#elif defined(USE_PA15)
#define input_pin         GPIO_PIN(15)
#define input_port        GPIOA
#define PIN_NUMBER        15
#define PORT_LETTER       0
#else
#error "Bootloader comms pin not defined"
#endif

注:支持PA2 PB4 PA15 串口引脚配置。

5. 编译方法

$ make AM32_F421_BOOTLOADER

注:支持E230 F031 F051 F415 F415_128K F421 G071 G071_64K L431 G431 MCU

6. 参考资料

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值