脱机烧录器(一)串口Xmode协议
前言
自己想做一个脱机烧录器已经很久了,网上开源的脱机烧录器很多,但是大多数都比较复杂,奈何网上大神们都不出手,自己又想做一个简便的脱机烧录,所以就自己尝试了做了下,经过几天不懈的努力下做了一个简陋版的脱机下载器:通过串口将.bin文件发送给STM32G070单片机,单片机将数据存到W25Q32里面,支持存储两个不同的程序,单片机通过SWD协议下载到目标芯片。
提示:以下是本篇文章正文内容,下面案例可供参考
一、Xmodem协议
XModem是很早的文件传输协议之一,是几乎所有的通讯程序支持的文件传输协议,它每包数据均传输128字节信息块。格式如下:
1.数据包包头,固定为01H。
2.数据包序列号,记录当前发送的是第几个数据包,单个文件超过ff个数据包时,从零开始计算。
3.数据包序列号的反码。
4.要发送的数据。
5.CRC16校验。
我使用的是128字节的Xmodem协议,加上包头,CRC校验,一包数据总共133个字节。如果传输数据不足128字节时,会用0X1A补充到数据包里面。接收数据时,接收方要发送0X0C告诉发送方,已经准备好接收数据,当接收方接收到数据后,要发送ASK指令(指令十六进制为0x06)告诉发送方已经正确接收到数据。如果数据接收不正确,则通过发送0x15告诉发送方,数据不对,发送方接收到0x15以后,会再次发送当前数据包,直到返回0x06为止,当数据发送完后,发送方会发送EOT指令(0x04),告诉接收方数据发送完毕了,接收方返回0x06后,通讯结束。
二、串口接收(DMA+空闲中断)
1.使用Cubemx配置串口
如下图所示:
主要配置串口空闲中断,以及串口DMA接收,因为要接收的数据比较多,只使用中断的话CPU会接收不及时,导致串口ORE错误,就算能够及时接收,过于频繁的中断会使CPU的效率降低(时间都浪费在了进出中断),所以此时使用DMA是最好的选择,使CPU不用干预,它就能把数据搬到你想要放的地方。
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除串口空闲中断,防止误触发
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能串口空闲中断
HAL_UART_Receive_DMA(&huart1,U1_RxBuff,U1_RX_MAX+1); //开启串口DMA接收
打开串口空闲中断之前先清除一下,开启串口DMA接收,U1_RX_MAX为255,这个值只需要大于Xmodem一包数据的最大值就可以。
2.串口缓冲区设置
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
// HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_IT(&huart1,UART_IT_IDLE) != RESET) //如果UART_IT_IDLE置位,表示一帧数据接受完毕
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除空闲中断
U1CB.URxCounter += (U1_RX_MAX + 1) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); //+=操作,将本次接受到的数据累计到URxCounter变量
U1CB.URxDataIN->end = &U1_RxBuff[U1CB.URxCounter - 1]; //
U1CB.URxDataIN ++;
if(U1CB.URxDataIN == U1CB.URxDataEND) {
U1CB.URxDataIN = &U1CB.URxDataPtr[0];
}
if(U1_RX_SIZE - U1CB.URxCounter >= U1_RX_MAX) { //
U1CB.URxDataIN->start = &U1_RxBuff[U1CB.URxCounter];
}else {
U1CB.URxDataIN ->start = U1_RxBuff;
U1CB.URxCounter = 0;
}
HAL_UART_DMAStop(&huart1); //如重置DMA接收数据长度,必须要在关闭DMA的条件进行,否则操作无效。
HAL_UART_Receive_DMA(&huart1,U1CB.URxDataIN->start,U1_RX_MAX+1); //开启DMA通道,等待下一次的数据接收
}
/* USER CODE END USART1_IRQn 1 */
}
当进入串口空闲中断时表示一帧数据接受完毕,U1CB.URxDataIN->end是一个指针,指向每次接受数据的末尾,U1CB.URxDataIN->start也是一个指针,指向每次接受数据开始的地址,他们都在同一个结构体UCB_URxBuffptr。
typedef struct{
uint8_t *start; //s用于标记起始位置
uint8_t *end; //e用于标记结束位置
}UCB_URxBuffptr; //se指针对结构体
串口接受放在了一个U1_RxBuff数组里面,大小为2048。在UCB_CB结构体中定义了一个URxDataPtr[NUM]的数组,NUM为10,初始化参数时让URxDataIN指向了URxDataPtr,在接受数据时,URxDataPtr[NUM]里面的start,end指针,指向每次接收数据的开始和结束如图所示:
typedef struct{
uint16_t URxCounter; //累计接收数据量
UCB_URxBuffptr URxDataPtr[NUM]; //se指针对结构体数组
UCB_URxBuffptr *URxDataIN; //指针 用于标记接收数据
UCB_URxBuffptr *URxDataOUT; //OUT指针 用于提取接收的数据
UCB_URxBuffptr *URxDataEND; //IN和OUT指针的结尾标志
}UCB_CB; //串口控制结构体
进入中断后,URxCounter表示本次接收的数据量,URxDataIN++,此时URxDataIN不等于URxDataOUT,表示缓冲区此时接收到数据了,需要进行处理。串口最后复位DMA重新开始下一轮接收。
三、Xmodem协议实现
if(StateFlag&XMODEMD_FLAG){ //如果 XMODEMD_FLAG 置位表示开始Xmodem协议接收数据
if((datalen==133)&&(data[0]==0x01)){ //判断 Xmodem协议一包总长133字节 且 第一个字节帧头是0x01
StateFlag &=~ XMODEMC_FLAG; //已经收到数据包了,所以清除 XMODEMC_FLAG,不再发送大写C
DataSta.XmodemCRC = Xmodem_CRC16(&data[3],128); //计算本次接收的数据包数据的CRC
if(DataSta.XmodemCRC == data[131]*256 + data[132]){ //计算的CRC 和 接收的CRC 比较,一样说明正确,进入if
DataSta.XmodemNB++; //已接收的数据包数量+1
memcpy(&DataSta.databuff[((DataSta.XmodemNB-1)%(PAGE_SIZE/128))*128],&data[3],128); //将本次接收的数据,暂存到DataSta.databuff缓冲区
if((DataSta.XmodemNB%(PAGE_SIZE/128))==0){ //如果已接收数据包数量是8的整数倍,说明都满1扇区1024字节,进入if
for(i=0;i<4;i++){ //W25Q32每次写入256字节,1扇区1024字节,需要循环4次写
W25QXX_Write_Page(&DataSta.databuff[i*256],FLASH_CAP * DataSta.W25Q16_BlockNB + (DataSta.XmodemNB/8 -1 ) * PAGE_SIZE + i * 256,256); //将接受的数据写入W25Q32
}
}
printf("\x06"); //正确,返回ACK给CRT软件
}else{ //如果CRC校验错误,进入else
printf("\x15"); //返回NCK给CRT软件
}
}
if((datalen==1)&&(data[0]==0x04)){ //如果收到1个字节数据 且 是0x04,进入if,说明收到EOT,表明数据已经发生完毕
printf("\x06"); //返回ACK给CRT软件
if((DataSta.XmodemNB%(PAGE_SIZE/128))!=0){ //判断是否还有不满1扇区1024字节的数据,如果有进入if,把剩余的小尾巴写入
for(i=0;i<4;i++){ //W25Q64每次写入256字节,1扇区1024字节,需要循环4次写
W25QXX_Write_Page(&DataSta.databuff[i*256],FLASH_CAP * DataSta.W25Q16_BlockNB + (DataSta.XmodemNB/8 )*PAGE_SIZE+i*256,256); //将接受的数据写入W25Q64
}
}
首先判断是否为Xmodem协议接收数据为133个字节第一个字节为0x01H,然后再判断数据传输是否出错,进行CRC校验,如果正确将数据拷贝到DataSta.databuff中,此数组用来保存写入W25Q32的数据,也就是我们的程序,然后返回0x06表示数据接收正确。如果错误,则返回0x15表示数据接收错误,需要重新发送。
总结
通过Xmodem协议将.bin文件能够发送给我们的单片机,单片机通过双缓冲区,来接受发送的数据,最后单片机通过SPI写到W25Q32里面进行保存,这样就实现了文件的传输,下一章讲解SWD协议的移植:SWD协议移植