嵌入式主机通过串口 ISP 升级程序
备注:该升级程序方法是单片机给单片机升级程序,因此,在升级程序前必须在主机当中写入待下载的程序。该方法比较方便无需通过电脑下载,适合户外携带维护和批量下载的情况。
由于使用 ISP 上位机升级程序需要借助电脑,在实际生产过程中会带来不便。因此实现了一种嵌入式 ISP 主机用于对目标 MCU 的程序进行升级。嵌入式主机根据 ISP 协议通过串口与 BootLoader 进行通信,从而实现对目标 MCU 程序的升级。
BOOTLOAD引导模式:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e33d9a65aeace7fe329a5bb6467441f4.png)
BOOT1接地情况下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cbaeb41004ecc17b39eea4ea875ff4bc.png)
硬件连接:
串口初始化
串口初始化:
/*!
\brief configure COM port
\param[in] com: COM on the board
\arg EVAL_COM1: COM1 on the board
\arg EVAL_COM2: COM2 on the board
\param[out] none
\retval none
*/
void gd_eval_com_init(uint32_t com)
{
uint32_t com_id = 0U;
if(EVAL_COM1 == com){
com_id = 0U;
}else if(EVAL_COM2 == com){
com_id = 1U;
}
/* enable GPIO clock */
rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);
/* enable USART clock */
rcu_periph_clock_enable(COM_CLK[com_id]);
/* connect port to USARTx_Tx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
COM_TX_PIN[com_id]);
/* connect port to USARTx_Rx */
gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,
COM_RX_PIN[com_id]);
/* USART configure */
usart_deinit(com);
usart_parity_config(com, USART_PM_EVEN);
usart_word_length_set(com, USART_WL_9BIT);
usart_baudrate_set(com, 57600U);
usart_receive_config(com, USART_RECEIVE_ENABLE);
usart_transmit_config(com, USART_TRANSMIT_ENABLE);
usart_enable(com);
}
控制管脚配置
/* enable the clock */
rcu_periph_clock_enable(RCU_GPIOA);
/* configure BOOT0_CTL port */
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
gpio_bit_reset(GPIOA, GPIO_PIN_0);
/* configure NRST_CTL port */
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
gpio_bit_set(GPIOA, GPIO_PIN_1);
升级程序
待下载 bin2 在下载之前存储到嵌入式主机 0x08004000 地址处,格式为 bin2=bin 文件大小(4
字节)+bin1 文件。可根据需要将 bin 文件存储在其他位置,bin 格式也可以自由设置。
升级主程序流程图:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/565fe46ad6687b380651e469fd507951.png)
升级步骤
1.将目标MCU Boot0拉高
void boot0_high(void)
{
gpio_bit_set(GPIOA, GPIO_PIN_0);
/* pull up the target reset pin */
gpio_bit_reset(GPIOA, GPIO_PIN_1);
gpio_bit_set(GPIOA, GPIO_PIN_1);
/* wait for the target to reset */
delay_1ms(300);
}
2.发送握手命令,当嵌入式主机收到0x79,说明握手成功。
ErrStatus usart_handshake(void)
{
uint16_t count = 0;
while(count++<10)
{
usart_data_transmit(USART0, 0x7F);
while(RESET == usart_flag_get(USART0, USART_FLAG_TC));
while((ERROR == ack_flag) && (timeout--))
{
}
if(0 != timeout)
{
ack_flag = ERROR;
return SUCCESS;
}
}
return ERROR;
}
3.发送页擦除命令
首先根据 GD32F4xx flash 结构以及 bin 文件的大小索引需要擦除的扇区。
static uint32_t f4_get_sector_number(uint32_t bin_size)
{
uint32_t sector_size[32] = {16, 16, 16, 16, 64, 128, 128, 128, 128, 128, 128, 128,
16, 16, 16, 16, 64, 128, 128, 128, 128, 128, 128, 128,
256, 256, 256, 256, 256, 256, 256};
uint32_t i,j;
uint32_t sector_number = 0;
uint32_t sector_address;
sector_address = 0x08000000 + bin_size;
for(i=0; i<31; i++){
uint32_t start_address = 0x08000000;
uint32_t end_address = 0x08000000;
for(j=0; j<=i; j++){
start_address += sector_size[j] * 1024;
end_address += sector_size[j] * 1024;
}
start_address -= sector_size[i] * 1024;
end_address -= 1;
if(sector_address>=start_address && sector_address<=end_address){
sector_number = i;
break;
}
}
return sector_number;
}
4.执行擦除命令
ErrStatus usart_send_erase_command(void)
{
uint32_t bin_size;
uint16_t sector_number = 0;
uint32_t i, temp;
/* extend erase command */
buffer_cmd[0] = 0x44;
buffer_cmd[1] = 0xFF - buffer_cmd[0];
/* get the bin size */
bin_size = *(uint32_t *)(0x8004000);
bin_size = bin_size;
sector_number = f4_get_sector_number(bin_size);
usart_buffer_send(buffer_cmd, 2);
if(SUCCESS != wait_ack()){
return ERROR;
}
buffer_cmd[0] = (sector_number>>8) & 0xff;
buffer_cmd[1] = sector_number & 0xff;
for(i=0; i<sector_number+1; i++){
temp = 2+2*i;
buffer_cmd[temp] = (i>>8) & 0xff;
buffer_cmd[temp+1] = i & 0xff;
}
buffer_cmd[temp+2] = data_xor(buffer_cmd, temp+2);
usart_buffer_send(buffer_cmd, temp+3);
if(SUCCESS != wait_ack()){
return ERROR;
}
return SUCCESS;
}
4.发生编程指令
根据bin文件大小发送命令
4.1 bin文件大小等于252字节
/*!
\brief send the program command
\param[in] pro_addr: the address of the target to be programmed
\param[in] bin_addr: the address where stored the bin file
\param[in] pro_size: the size to be programmed
\param[out] none
\retval ErrStatus
*/
ErrStatus usart_send_program_command(uint32_t pro_addr, uint32_t bin_addr, uint32_t pro_size)
{
uint8_t i;
buffer_cmd[0] = USART_CMD_PROGRAM;
buffer_cmd[1] = 0xFF - buffer_cmd[0];
usart_buffer_send(buffer_cmd, 2);
if(SUCCESS != wait_ack()){
return ERROR;
}
buffer_cmd[0] = (uint8_t)((pro_addr >> 24) & 0xff);
buffer_cmd[1] = (uint8_t)((pro_addr >> 16) & 0xff);
buffer_cmd[2] = (uint8_t)((pro_addr >> 8) & 0xff);
buffer_cmd[3] = (uint8_t)(pro_addr & 0xff);
buffer_cmd[4] = data_xor(buffer_cmd, 4);
usart_buffer_send(buffer_cmd, 5);
if(SUCCESS != wait_ack()){
return ERROR;
}
memset(buffer_cmd, 0, sizeof(buffer_cmd));
buffer_cmd[0] = pro_size-1;
for(i=0; i<pro_size; i=i+4){
*((uint32_t *)&buffer_cmd[1+i]) = *(uint32_t *)(bin_addr+4+i);
}
buffer_cmd[pro_size + 1] = data_xor(buffer_cmd, pro_size + 1);
usart_buffer_send(buffer_cmd, pro_size + 2);
if(SUCCESS != wait_ack()){
return ERROR;
}
memset(buffer_cmd, 0, sizeof(buffer_cmd));
return SUCCESS;
}
4.2 bin文件大252字节
由于 BootLoader 存储空间有限,当 bin 文件大于 252 字节时,需要对 bin 文件进行拆包。
/*!
\brief send the program command
\param[in] pro_addr: the address of the target to be programmed
\param[in] bin_addr: the address where stored the bin file
\param[out] none
\retval ErrStatus
*/
ErrStatus usart_program_bin(uint32_t pro_addr, uint32_t bin_addr)
{
uint32_t bin_size;
uint32_t i;
uint32_t pack_num, pack_size, res_size;
/* 4 bytes aligned */
pack_size = 252;
bin_size = (*(uint32_t *)(bin_addr));
pack_num = bin_size/pack_size;
res_size = bin_size % pack_size;
/* Unpacking the bin and then program to the flash of the target MCU */
for(i=0; i<pack_num; i++){
usart_send_program_command(pro_addr + i*pack_size, bin_addr + i*pack_size,
pack_size);
}
if(0 != res_size){
usart_send_program_command(pro_addr + pack_num*pack_size, bin_addr +
pack_num*pack_size, res_size);
}
return SUCCESS;
}
5.将目标 MCU 的 BOOT0 拉低,并对其进行复位。
将bin文件全部烧入完毕之后,需要将BOOT0拉低,并复位,就可以看到更新之后的功能程序了。
void boot0_low(void)
{
gpio_bit_reset(GPIOA, GPIO_PIN_0);
/* pull down the target reset pin */
gpio_bit_reset(GPIOA, GPIO_PIN_1);
gpio_bit_set(GPIOA, GPIO_PIN_1);
/* wait for the target to reset */
delay_1ms(300);
}
GD32F4系列ISP 串口下载就完成了。