基于usb的IAP + vcom输出log
(1) 基于usb vcom的IAP
STM32自带IAP(in applacation programming) 功能,通过BOOT引脚选择启动模式,从系统存储器启动即可进入。ST原生的IAP需要通过uart下载,使用起来有一定局限性,下载速度、可靠性都不理想。
本文讨论基于USB Vcom(virtual comport)的IAP 实现,手里的STM32F103C8T6支持USB 2.0 full-speed,同时USB口也兼具供电作用。使用USB接口进行IAP有几个好处:
<1> 使用方便,目标板仅需连一根USB线,普通用户即可轻松操作.
<2> 高速下载,USB full-speed理论传输速率是12Mbps;同时Vcom是可靠传输,每包数据均由CRC校验,出错随即重传.
<3> 附带log输出作用,当然,目标板和PC端的交互是可以任意的.
USB spec比较复杂,从0开始自己实现显然不明智,幸好ST官方有USB库,本文使用的是STM32_USB-FS-Device_Lib_V4.0.0 ,对于其中的project VirtualComport_Loopback进行扩展。需要实现与PC端的handshake,接收Host发送的数据并写入内部flash,过程中的log传回到PC。
<1> 数据发送函数
uint32_t CDC_Send_DATA (uint8_t *ptrBuffer, uint8_t Send_length);
长度不能超过VIRTUAL_COM_PORT_DATA_SIZE (64) ,发送完成packet_sent 标志置1.
<2> 数据接收函数
uint32_t CDC_Receive_DATA(void)
这个函数仅是设置ENDP3 valid,可以接收数据,真正数据接收完成放在Receive_Buffer,长度为Receive_length,并将标志packet_receive置1.
有了发送和接收函数就可以和PC端交互了,接收bin文件及传回log都是都过这两个函数实现。
官方的IAP需要通过BOOT引脚才能进入,本文通过与PC handshake自动识别是进入IAP还是正常的APP。Handshake过程比较简单:PC端检测VCOM口,当有一个VCOM生成就发送标志’ 0xa5’,目标板收到标志即确认进入IAP模式,并返回ACK ‘0x5a’,PC检测到’0x5a’就开始发送bin文件。与通过引脚方式相比这中方式需要多花一些时间进行handshake,但是好处也是显而易见的:该方式无需人工干预,可以自动下载。
(2) 写入Flash
IAP程序收到bin数据后写入flash,STM32内部的flash写入前需要unlock,一次写入2bytes(half word)。写flash的步骤是
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);
......
FLASH_ProgramHalfWord(......);
......
FLASH_ClearFlag(FLASH_FLAG_BSY|FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);
FLASH_Lock();
PS.读flash数据比较简单,直接通过地址读取”(__IO u32) (Address)”
(3) 跳转到user APP
跳转的程序比较简单,网上都可以找到,官方的也有。需要注意的是:跳转前要设置control 寄存器到默认,关闭中断以防止意外情况,最好将用到的设备deinit。跳转的code 如下:
void jump_to_user()
{
u32 JumpAddress;
pFunction Jump_To_Application;
if (((*(__IO u32 *)FW_START) & 0x2FFE0000 ) == 0x20000000)
{
print_s("Execute user Program\r\n", 22);
JumpAddress = *(__IO u32*) (FW_START + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_PSP(*(__IO u32*) FW_START);
__set_CONTROL(0);
__set_MSP(*(__IO u32*) FW_START);
__ASM volatile("CPSID I");
Jump_To_Application();
//Never come back
}
else
{
print_s("no user Program\r\n", 17);
}
}
(4) 传回log到PC
直接将需要输出的log通过VCOM发送就可以传到PC端,这里使用的是sync方式,数据真实传输完毕才会返回,防止log太多冲掉前面的。
void print_s(u8 *ptrBuffer, u8 Send_length)
{
if (bDeviceState == CONFIGURED){
CDC_Send_DATA (ptrBuffer, Send_length);
while(packet_sent != 1);
}
}
其中,packet_sent 标志要添加volatile声明,不然会被编译器优化为dead loop.
(5) user APP的注意点
IAP程序下载的user APP放的地址不一定是flash首地址,所以user project的memory map 和 vector table要一起修改。
在project option对话框的Target标签,将起始地址 &大小修改为需要的配置,这里预留给IAP程序16K,地址从0x8004000 开始,大小48K。从生成的map文件可以看到函数地址都相应改变了:
Reset_Handler 0x080068f1 Thumb Code
ADC1_2_IRQHandler 0x08006907 Thumb Code
......
另外,由于IAP下载的是bin文件,要执行自定义命令从axf生产bin。
在main函数入口向量表地址也要对应设置,否则中断无法执行.
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x4000);
(6) RTT vcom输出log
RTT原来使用的是uart输出log,同样,这里可以使用USB vcom输出log,将ST官方usb lib库加到RTT中即可,修改rt_kprintf()函数通过vcom输出log.
void rt_print(const char *str)
{
u8 *ptr = (u8 *)str;
char send_length = rt_strlen(str);
if(bDeviceState == CONFIGURED && 1 == tool_exist)
{
CDC_Send_DATA (ptr , send_length>=VIRTUAL_COM_PORT_DATA_SIZE-1 ? VIRTUAL_COM_PORT_DATA_SIZE-1: send_length);
}
}
总结,使用基于USB的IAP可以很方便的实现软件升级,通过handshake机制使得自动下载得以实现,并且可以同时输出log信息。该方案的不足是IAP程序本身占用宝贵的flash空间,handshake需要耗费一些时间。
未解决的问题:
<1> RTT自带有USB协议栈实现,花了两天时间,解决各种build error,最终还是无法识别usb,而用ST官方lib就没问题。
<2> 跳转后的user APP无法使用usb功能,无法识别usb,而直接下载执行则没问题。
这两个问题从打印log可以看到是一样的现象,USB istr一直报SUSPend & Expected Start Of Frame中断,猜测可能是因为:开发板上的DP直接连到了3.3v,这样一插上PC都会识别到,但是usb中断还没来得及执行。这就导致目标板没有响应PC,最终无法识别。 而lib库的code可以看到DP上拉电阻是在PowerOn()函数才会加上,PowerOff()会去掉。有时间试试把开发板上焊死的上拉电阻断开,通过GPIO来控制。