最近需要在STM32F407ZET6上面做网络升级固件的功能。调试过程中出现了一些怪现象,遂做个调试记录。
一、弄清楚你的芯片的flash大小,做好bootloader和app的地址区分。
一开始我被参考手册中的信息误导了。参考手册是针对一个系列的,会把最大容量的显示出来。具体是多少还是要看你自己的系列所占大小。
可以看到一共有12个扇区,总计1024KB的大小。然后想都没想就按这个地址长度来规划,结果是可想而知的。我的芯片是STM32F407ZET6,flash大小只有512KB,从选型手册上可以看到。
所以,正确的地址范围应该为:0x08000000-0x0807FFFF。然后划分bootloader占用128KB,即0x20000的大小,APP占用后面所有的可用地址,范围为0x08020000-0x0807FFFF,共384KB。
二、从ST官网上面找到关于通过LWIP实现IAP的例程。这里面是官方通过https和tftp进行IAP升级的代码,具有参考价值。
从这个例程着手,可以减少代码时间。我打算先从TFTP着手,实现IAP升级。
三、准备2份代码,一份是bootloader的,一份是APP的,前提条件:bootloader代码必须要保证lwip工作正常,能正常获取IP,UDP,TCP工作正常。APP代码没有特殊要求。下面是注意点。
bootloader在keil中地址分布
APP在keil中的地址分布
app代码的起始部分,请手动设置代码的入口地址:
NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x20000);
四、bootloader中添加TFTP server的功能。
我从下载的官网例程中复制了需要的几个文件:tftpserver.c tftpserver.h flash_if.c flash_if.h,分别是tftpserver和flash的功能代码。加入我自己的工程中,下面开始分析代码。
五、先了解下TFTP的知识。
TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,基于UDP实现。提供不复杂、开销不大的文件传输服务。端口号为69。
六、修改代码。
tftpserver.c中提供了完整的TFTP功能,可以直接拿来使用,但是需要做一些修改。用户只需要在初始化的地方调用IAP_tftpd_init()函数即可。
void IAP_tftpd_init(void)
{
err_t err;
unsigned port = 69; /* 69 is the port used for TFTP protocol initial transaction */
/* create a new UDP PCB structure */
UDPpcb = udp_new();
if (!UDPpcb)
{
/* Error creating PCB. Out of Memory */
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Can not create pcb");
#endif
return ;
}
/* Bind this PCB to port 69 */
err = udp_bind(UDPpcb, IP_ADDR_ANY, port);
if (err == ERR_OK)
{
/* Initialize receive callback function */
udp_recv(UDPpcb, IAP_tftp_recv_callback, NULL);
}
else
{
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Can not bind pcb");
#endif
}
}
创建了一个UDP连接,绑定了69端口,指定了接收回调函数为IAP_tftp_recv_callback
static void IAP_tftp_recv_callback(void *arg, struct udp_pcb *upcb, struct pbuf *pkt_buf,
struct ip_addr *addr, u16_t port)
{
tftp_opcode op;
struct udp_pcb *upcb_tftp_data;
err_t err;
#ifdef USE_LCD
uint32_t i;
char filename[40],message[40], *ptr;
#endif
/* create new UDP PCB structure 创建了一个新的连接,用于处理接收的烧录数据*/
upcb_tftp_data = udp_new();
if (!upcb_tftp_data)
{
/* Error creating PCB. Out of Memory */
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Can not create pcb");
#endif
return;
}
/* bind to port 0 to receive next available free port */
/* NOTE: This is how TFTP works. There is a UDP PCB for the standard port
* 69 which al transactions begin communication on, however, _all_ subsequent
* transactions for a given "stream" occur on another port */
/*解释了为什么要绑定到端口0,是为了接收任意端口的数据,69端口是传输开始使用的,传输开始后就使用
的非69端口*/
err = udp_bind(upcb_tftp_data, IP_ADDR_ANY, 0);
if (err != ERR_OK)
{
/* Unable to bind to port */
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Can not bind pcb");
#endif
return;
}
//开始解析TFTP数据包的前2字节操作码
op = IAP_tftp_decode_op(pkt_buf->payload);
if (op != TFTP_WRQ)
{
/* remove PCB */
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Bad TFTP opcode ");
#endif
udp_remove(upcb_tftp_data);
}
else//操作码是写请求,即上传数据
{
#ifdef USE_LCD
ptr = pkt_buf->payload;
ptr = ptr +2;
/*extract file name info */
i= 0;
while (*(ptr+i)!=0x0)
{
i++;
}
strncpy(filename, ptr, i+1);//获取上传的文件名
/* Set the LCD Text Color */
LCD_SetTextColor(White);
LCD_Clear(Black);
LCD_DisplayStringLine(Line0, (uint8_t*)" IAP using TFTP ");
sprintf(message, "File: %s",filename);
LCD_DisplayStringLine(Line3, (uint8_t*)message);
/* Set the LCD Text Color */
LCD_DisplayStringLine(Line8, (uint8_t*)"State: Erasing...");
#endif
/* Start the TFTP write mode 进入接收处理上传数据的函数*/
IAP_tftp_process_write(upcb_tftp_data, addr, port);
}
pbuf_free(pkt_buf);
}
static int IAP_tftp_process_write(struct udp_pcb *upcb, struct ip_addr *to, int to_port)
{
tftp_connection_args *args = NULL;//TFTP数据报的数据封装
/* This function is called from a callback,
* therefore interrupts are disabled,
* therefore we can use regular malloc */
args = mem_malloc(sizeof *args);
if (!args)
{
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Memory error");
#endif
IAP_tftp_cleanup_wr(upcb, args);
return 0;
}
args->op = TFTP_WRQ;
args->to_ip.addr = to->addr;//TFTP 客户端的IP地址
args->to_port = to_port;//TFTP 客户端的端口
/* the block # used as a positive response to a WRQ is _always_ 0!!! (see RFC1350) */
args->block = 0;
args->tot_bytes = 0;
/* set callback for receives on this UDP PCB (Protocol Control Block) */
udp_recv(upcb, IAP_wrq_recv_callback, args);//设置接收到烧录数据后的回调函数
//下面进行一些初始化,在烧录数据到来之前进行一些设置。
total_count =0;//记录上传过来的文件字节数
/* init flash */
FLASH_If_Init();//内部FLASH解锁
/* erase user flash area */
FLASH_If_Erase(USER_FLASH_FIRST_PAGE_ADDRESS);//擦除APP部分的地址区域。
//APP起始地址赋给Flash_Write_Address 变量
Flash_Write_Address = USER_FLASH_FIRST_PAGE_ADDRESS;
/* initiate the write transaction by sending the first ack */
//发送第一个ACK,TFTP每次收发都需要服务端的应答才能继续
IAP_tftp_send_ack_packet(upcb, to, to_port, args->block);
#ifdef USE_LCD
LCD_DisplayStringLine(Line8, (uint8_t*)"State: Programming..");
#endif
return 0;
}
下面进行重点的回调函数分析,这个回调函数IAP_wrq_recv_callback是处理烧录文件的数据的,TFTP客户端发送过来的烧录数据,都是在该函数内进行处理。
static void IAP_wrq_recv_callback(void *_args, struct udp_pcb *upcb, struct pbuf *pkt_buf, struct ip_addr *addr, u16_t port)
{
tftp_connection_args *args = (tftp_connection_args *)_args;
uint32_t data_buffer[128];
u16 count=0;
#ifdef USE_LCD
char message[40];
#endif
//111 做一个判断是否相等???
if (pkt_buf->len != pkt_buf->tot_len)//470,516
{
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)"Invalid data length");
#endif
return;
}
/* Does this packet have any valid data to write? */
if ((pkt_buf->len > TFTP_DATA_PKT_HDR_LEN) &&
(IAP_tftp_extract_block(pkt_buf->payload) == (args->block + 1)))
{
/* copy packet payload to data_buffer */
/// 2222 复制数据到data_buffer中
pbuf_copy_partial(pkt_buf, data_buffer, pkt_buf->len - TFTP_DATA_PKT_HDR_LEN,
TFTP_DATA_PKT_HDR_LEN);
// 333 接收的计数值增加
total_count += pkt_buf->len - TFTP_DATA_PKT_HDR_LEN;
// 444 计算数据长度
count = (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN)/4;
if (((pkt_buf->len - TFTP_DATA_PKT_HDR_LEN)%4)!=0)
count++;
/* Write received data in Flash */
FLASH_If_Write(&Flash_Write_Address, data_buffer ,count);
//数据包的当前块序号+1
/* update our block number to match the block number just received */
args->block++;
/* update total bytes */
(args->tot_bytes) += (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN);
/* This is a valid pkt but it has no data. This would occur if the file being
written is an exact multiple of 512 bytes. In this case, the args->block
value must still be updated, but we can skip everything else. */
}
else if (IAP_tftp_extract_block(pkt_buf->payload) == (args->block + 1))
{
/* update our block number to match the block number just received */
args->block++;
}
// 回复ACK数据包
/* Send the appropriate ACK pkt*/
IAP_tftp_send_ack_packet(upcb, addr, port, args->block);
/* If the last write returned less than the maximum TFTP data pkt length,
* then we've received the whole file and so we can quit (this is how TFTP
* signals the EndTransferof a transfer!)
*/
if (pkt_buf->len < TFTP_DATA_PKT_LEN_MAX)//最后一包的数据小于TFTP_DATA_PKT_LEN_MAX值512
{ //表示是最后的数据包
IAP_tftp_cleanup_wr(upcb, args);
pbuf_free(pkt_buf);
#ifdef USE_LCD
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line5, (uint8_t*)"Tot bytes Received:");
sprintf(message, "%d bytes ",total_count);
LCD_DisplayStringLine(Line6, (uint8_t*)message);
LCD_DisplayStringLine(Line8, (uint8_t*)"State: Prog Finished ");
#endif
}
else
{
#ifdef USE_LCD
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line9, (uint8_t*)" Reset the board ");
#endif
pbuf_free(pkt_buf);
return;
}
}
在111这个位置,判断数据是否相等的条件下,始终发现不相等,一个是470,一个516,直接return了,导致客户端不停发送第一包数据,直到超时停止发送数据。从wireshark抓包可以看得出。Block 0表示初始上传后连接后的第一个ACK回复,接着客户端开始发送Block1的烧录数据,然后在上面的回调函数中,判断数据不想的,直接返回了,没有进行数据处理。
TFTP客户端每次发送一包数据的真实数据大小为512字节。再加上2字节操作码,2字节块编号,所以pkt_buf->tot_len值为516没错。但是pkt_buf->len 为什么是470字节?这2个值怎么会相等?无奈之下我只好把这个比较条件注释。接着往下看。
在222处,将接收的数据复制到data_buffer中。这里复制的大小是pkt_buf->len - TFTP_DATA_PKT_HDR_LEN,按照上面的,那复制的大小岂不是470-4=466个字节,真实的数据是512字节啊!这里我觉得应该是pkt_buf->tot_len- TFTP_DATA_PKT_HDR_LEN而非是pkt_buf->len - TFTP_DATA_PKT_HDR_LEN,遂改之。下面凡是pkt_buf->len全部改为pkt_buf->tot_len,再次测试,终于没有问题了。
起初我认为官方的例程里面是不会有问题的,直接用就行。虽然这个问题解决了,但是还是有不清楚的地方,特别是111处,希望有知道的兄弟指明。