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,则跳转到应用程序
- bl_clock_config(): 配置时钟,以初始化系统时钟源和频率。
- bl_timer_init(): 初始化定时器,用于系统中的时间控制。
- bl_gpio_init(): 初始化GPIO(通用输入/输出)端口,配置所需的引脚。
- test_clock(): 如果定义了
BOOTLOADER_TEST_CLOCK
,则执行时钟测试功能。 - checkForSignal(): 检查外部信号,以确定是否继续执行。
- gpio_mode_set_input(input_pin, GPIO_PULL_NONE): 将指定的
input_pin
设置为输入模式,不使用内部上拉或下拉电阻。 - jump(): 如果定义了
USE_ADC_INPUT
,直接跳转到应用程序。 - deviceInfo[3] = pin_code: 将
pin_code
存储在deviceInfo
数组的第三个位置。 - update_EEPROM(): 如果定义了
UPDATE_EEPROM_ENABLE
,则更新EEPROM中的数据。 - 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() // 解析接收到的数据
- count = 0: 初始化计数器
count
,用于记录接收到的字节数。 - messagereceived = 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): 如果计数器
count
达到有效载荷缓冲区大小+2,跳出循环。 - rxBuffer[i] = rxbyte: 否则,将读取的字符
rxbyte
存入rxBuffer
的当前索引i
处。 - count++: 增加计数器
count
。
- if(count == payload_buffer_size+2): 如果计数器
- else: 如果不是无命令的有效载荷。
- if(bl_timer_us() > 250): 检查是否已经过了 250 微秒。
- count = 0: 如果超过了 250 微秒,重置计数器
count
。 - break: 跳出循环,停止接收数据。
- count = 0: 如果超过了 250 微秒,重置计数器
- rxBuffer[i] = rxbyte: 否则,将读取的字符
rxbyte
存入rxBuffer
的当前索引i
处。 - if(i == 257): 如果当前索引
i
等于 257。- invalid_command += 20: 增加
invalid_command
计数器,累积无效命令数量。当达到 100 时,会触发跳转,但在下一个设置地址命令时会重置。
- invalid_command += 20: 增加
- if(bl_timer_us() > 250): 检查是否已经过了 250 微秒。
- serialreadChar(): 读取一个字符,并存储在
- 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() // 切换到接收模式
- incoming_payload_no_command: 判断是否正在处理无命令的有效载荷,并根据CRC校验结果处理数据或发送错误消息。
- cmd = rxBuffer[0]: 解析收到的命令字节,并根据不同的命令字节执行相应的操作。
- CMD_RUN: 启动主应用程序,并根据接收到的参数更新无效命令计数。
- CMD_PROG_FLASH: 编程Flash存储器,包括CRC校验、地址可写性检查以及数据保存等操作。
- CMD_SET_ADDRESS: 设置目标地址,并根据CRC校验结果确认或拒绝该操作。
- CMD_SET_BUFFER: 设置接收缓冲区的大小,并初始化接收过程。
- CMD_KEEP_ALIVE: 发送“保持连接”消息,以防止连接超时。
- CMD_ERASE_FLASH: 擦除Flash存储器中的数据。
- CMD_READ_EEPROM: 设置EEPROM读取请求标志。
- 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. 硬件端口
#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开源代码之工程结构