STM32 Internal Flash DFU芯片内部flash代码升级

这次要讲讲如何用USB升级单片机代码。以前曾经做过串口升级,网络升级,升级的基本原理都类似,只不过升级的工具不同罢了,串口升级当然是用串口了,网络升级用的是TCP/IP,USB升级当然用的是USB了。下面就来讲讲USB升级的实现。
修改部分部分都在USB_User组里:
STM32 Internal Flash DFU芯片内部flash代码升级 - ziye334 - ziye334的博客我们一个一个文件讲过来。
首先讲讲hw_config.c,这个文件跟之前工程差不多。由于演示的需要,我们在这个文件里初始化一个按键引脚,并定义按键读取函数,该按键决定代码是否升级,如果程序一开始,该按键按下,则进入升级模式,否则跳转到升级程序代码处:

/******************************************************************************* * Function Name : DFU_Button_Config. * Description : 配置DFU模式选择按键. * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_Button_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DFU, ENABLE); /* 配置DFU按键*/ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = DFU_ENTER_PIN; GPIO_Init(DFU_ENTER, &GPIO_InitStructure); } /******************************************************************************* * Function Name : DFU_Button_Read. * Description : 读取DFU按键是否按下 * Input : None. * Output : None. * Return : 返回0:按键按下;1:按键没有按下 *******************************************************************************/ uint8_t DFU_Button_Read (void) { return GPIO_ReadInputDataBit(DFU_ENTER, DFU_ENTER_PIN); }

usb_desc.c这个文件自然要修改的,USB的功能属性等全在这里定义。首先必须关注下设备描述度符,这里有一点需要强调,就是厂商ID域的值必须为0483,否则电脑不识别USB,产品ID可以自定义。

/* USB标准设备描述符*/ uint8_t DFU_DeviceDescriptor[DFU_SIZ_DEVICE_DESC] = { 0x12, /*bLength:长度,设备描述符的长度为18字节*/ USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:类型,设备描述符的编号是0x01*/ 0x00, /*bcdUSB:所使用的USB版本为2.0*/ 0x02, 0x00, /*bDeviceClass:设备所使用的类代码*/ 0x00, /*bDeviceSubClass:设备所使用的子类代码*/ 0x00, /*bDeviceProtocol:设备所使用的协议*/ 0x40, /*bMaxPacketSize:最大包长度为64字节*/ 0x83, /*idVendor:厂商ID为0x1234*/ 0x04, 0x11, /*idProduct:产品ID为0x1010*/ 0xDF, 0x00, /*bcdDevice:设备的版本号为2.00*/ 0x02, 1, /*iManufacturer:厂商字符串的索引*/ 2, /*iProduct:产品字符串的索引*/ 3, /*iSerialNumber:设备的序列号字符串索引*/ 0x01 /*bNumConfiguration:设备有1种配置*/ }; /* DFU设备描述符 */

接下去是配置描述符集合,配置描述符不需要修改。但之后的接口描述符的 bNumEndpoints域(该接口所使用的端点数)需要设置成0,因为USB DFU值需要端点0,不不需要其他的端点;接口描述符的bInterfaceClass(该接口所使用的类)域的值为0xFE,表示使用DFU类接口;接口描述符的bInterfaceSubClass(该接口所用的子类)设置成0x01,表示boot用途;接口描述符的nInterfaceProtocol设置成0x2,即DFU模式;最后还要设置接口字符串描述符的索引值为4。接下去是DFU功能描述符,这里设置bmAttribute域USB的属性为0x0B(具体意义看下面代码);DetachTimeOut(超时时间)设置成0XFF,表示超时时间为255ms;接下去设置TransferSize:(传输的长度)为0x400,注意这里是用两字节小端模式表示。

/* USB配置描述符集合(配置、接口、端点、类、厂商)(Configuration, Interface, Endpoint, Class, Vendor */ uint8_t DFU_ConfigDescriptor[DFU_SIZ_CONFIG_DESC] = { 0x09, /*bLength:长度,设备字符串的长度为9字节*/ USB_CONFIGURATION_DESCRIPTOR_TYPE, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/ DFU_SIZ_CONFIG_DESC, /*wTotalLength:配置描述符的总长度为41字节*/ 0x00, 0x01, /*bNumInterfaces:配置所支持的接口数量1个*/ 0x01, /*bConfigurationValue:该配置的值*/ 0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/ 0xC0, /* bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒 D7:保留必须为1,D6:是否自供电,D5:是否支持远程唤醒,D4~D0:保留设置为0*/ 0x32, /*从总线上获得的最大电流为100mA */ // 0x96, /*MaxPower:设备需要从总线上获取多少电流,单位为2mA,0x96表示300mA*/ /****** Descriptor of DFU interface 0 Alternate setting 0***********/ 0x09, /*bLength:长度,接口描述符的长度为9字节 */ USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType:接口描述符的类型为0x4 */ 0x00, /*bInterfaceNumber:该接口的编号*/ 0x00, /*bAlternateSetting:该接口的备用编号 */ 0x00, /*bNumEndpoints:该接口所使用的端点数*/ 0xFE, /*bInterfaceClass该接口所使用的类为 DFU*/ 0x01, /*bInterfaceSubClass:该接口所用的子类 1=BOOT, 0=no boot */ 0x02, /*nInterfaceProtocol :DFU模式*/

4, /* iInterface:接口字符串描述符的索引 */ /******************** DFU功能描述符********************/ 0x09, /*blength:DFU描述符的长度为9字节*/ 0x21, /*功能描述符的类型为0x21*/ 0x0B, /*bmAttribute bitCanDnload = 1 (bit 0) 下载性能 bitCanUpload = 1 (bit 1) 上传性能 bitManifestationTolerant = 0 (bit 2) 设备在心事阶段后是否可通过USB通讯 bitWillDetach = 1 (bit 3) 接收到DFU_DETACH命令时会进行总线detach-attac Reserved (bit4-6) 保留 bitAcceleratedST = 0 (bit 7) 该为置1,设备将会上传速度增加到4096字节/命令*/ 0xFF, /*DetachTimeOut:超时时间 255 ms*/ 0x00, wTransferSizeB0, wTransferSizeB1, /* TransferSize:传输的长度为1024 Byte*/ 0x1A, /* bcdDFUVersion:DFU协议版本*/ 0x01 };

接下去的语言字符串描述符、厂商字符串描述符、产品字符串描述符、序列号字符串描述符都不详细介绍了。

/* 语言ID描述符 */ uint8_t DFU_StringLangId[DFU_SIZ_STRING_LANGID] = { DFU_SIZ_STRING_LANGID, /*bLength:本描述符的长度为4字节*/ USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的类型为0x03*/ 0x09, /*bString:语言ID为0x0409,表示美式英语*/ 0x04 }; /* LangID = 0x0409: U.S. English*/ /*厂商字符串描述符*/ uint8_t DFU_StringVendor[DFU_SIZ_STRING_VENDOR] = { DFU_SIZ_STRING_VENDOR, /*bLength:厂商字符串描述符的长度*/ USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的类型为0x03*/ 'B' , 0, 'y', 0, ':' , 0, 'z' , 0, 'i', 0, 'y', 0,'e', 0,'3', 0, '3', 0, '4', 0 /*自定义*/ }; /*产品的字符串描述符*/ uint8_t DFU_StringProduct[DFU_SIZ_STRING_PRODUCT] = { DFU_SIZ_STRING_PRODUCT, /* bLength:产品的字符串描述符*/ USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的类型为0x03*/ 'z', 0, 'i', 0, 'y', 0, 'e', 0, '3', 0, '3', 0 ,'4',0/*自定义*/ }; /*产品序列号的字符串描述符*/ uint8_t DFU_StringSerial[DFU_SIZ_STRING_SERIAL] = { DFU_SIZ_STRING_SERIAL, /* bLength:产品序列号*/ USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的类型为0x03*/ '1', 0, '2', 0, '3', 0,'4', 0,'5', 0, '6', 0, '7', 0 /*自定义*/ };

重点介绍的是接口字符串描述符,这个字符串描述符定义了升级的硬件信息,如下是我们的接口描述符:

/*接口字符串描述符*/ uint8_t DFU_StringInterface0[DFU_SIZ_STRING_INTERFACE0] = { DFU_SIZ_STRING_INTERFACE0, 0x03, // Interface 0: "@Internal Flash /0x08000000/12*001Ka,500*001Kg" '@', 0, 'I', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'n', 0, 'a', 0, 'l', 0, /* 18 */ ' ', 0, 'F', 0, 'l', 0, 'a', 0, 's', 0, 'h', 0, ' ', 0, ' ', 0, /* 16 */ '/', 0, '0', 0, 'x', 0, '0', 0, '8', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, /* 22 */ '/', 0, '1', 0, '2', 0, '*', 0, '0', 0, '0', 0, '1', 0, 'K', 0, 'a', 0, /* 18 */ ',', 0, '5', 0, '0', 0, '0', 0, '*', 0, '0', 0, '0', 0, '1', 0, 'K', 0, 'g', 0, /* 20 */ };

可以看到上面的注释: // Interface 0: "@Internal Flash /0x08000000/12*001Ka,500*001Kg",这就是这个接口描述符的所表达的信息,我们接下去就详细介绍 接口描述符的数组内容的信息:
—— @:表示这是一个特殊的映射描述符(避免按照标准描述符解码)
—— /:表示不同区之间的分隔符
—— 最大8位的地址,以“0X”开头
—— 最大两位的扇区编号
—— *:扇区数量和扇区大小之间的分隔符
—— 最大3位的扇区大小(0~999)
—— 1位扇区大小单位:有效输入是:B(字节),K(千),M(兆)
—— 1位的扇区类型:
a(0x41):可读
b(0x42):可擦除
c(0x43):可读可擦写
d(0x44):可写
e(0x45):可读可写
f (0x46):可写可擦除
g(0x47):可读可写可擦除
如上面的”  @Internal Flash /0x08000000/12*001Ka,500*001Kg"表示的意思是:存储器的名字为"Internal Flash",起始地址是0x08000000,12*1K的空间可读,500*1K的空间可读可写可擦除。顺便说明下我是用的芯片是STM32F103ZET6,flash空间是512K.

usb_istr.c这个文件和usb_pwr.c这个两个文件不需要修改。
usb_prop.c的文件修改量比较大,这个文件描述了DFU各个状态之间的变化转移,代码我还是点模糊,也不准备详细讲,直接贴代码,部分已将做了注解:

#include "usb_lib.h" #include "hw_config.h" #include "usb_conf.h" #include "usb_prop.h" #include "usb_desc.h" #include "usb_pwr.h" #include "dfu_mal.h" uint32_t wBlockNum = 0, wlength = 0; uint32_t Manifest_State = Manifest_complete; uint32_t Pointer = ApplicationAddress; //程序擦写、读写的基地址 DEVICE Device_Table = //端点信息 { EP_NUM, //已使用的端点数 1 //还可以使用的端点数 }; DEVICE_PROP Device_Property = //注册一些DFU相关的处理函数 { DFU_init, //初始化 DFU_Reset, //复位 DFU_Status_In, //状态输入 DFU_Status_Out, //状态输出 DFU_Data_Setup, //带数据阶段的建立 DFU_NoData_Setup, //不带数据阶段的建立 DFU_Get_Interface_Setting, //获取接口值 DFU_GetDeviceDescriptor, //获取设备描述符 DFU_GetConfigDescriptor, //获取配置描述符 DFU_GetStringDescriptor, //获取字符串描述符 0, //DFU_EP0Buffer bMaxPacketSize0 //最大包大小*/ }; USER_STANDARD_REQUESTS User_Standard_Requests = //标准请求 { DFU_GetConfiguration, //获取配置值请求 DFU_SetConfiguration, //设置配置值请求 DFU_GetInterface, //获取接口值请求 DFU_SetInterface, //设置接口值请求 DFU_GetStatus, //获取状态请求 DFU_ClearFeature, //清除特性请求 DFU_SetEndPointFeature, //设置断点特性请求 DFU_SetDeviceFeature, //设置设备特性请求 DFU_SetDeviceAddress //设置设备地址请求 }; ONE_DESCRIPTOR Device_Descriptor = //注册设备描述符信息 { (uint8_t*)DFU_DeviceDescriptor, //设备描述符地址 DFU_SIZ_DEVICE_DESC }; ONE_DESCRIPTOR Config_Descriptor = //注册配置描述符信息 { (uint8_t*)DFU_ConfigDescriptor, //配置描述符地址 DFU_SIZ_CONFIG_DESC }; ONE_DESCRIPTOR DFU_String_Descriptor[5] = //注册字符串描述符 { { (uint8_t*)DFU_StringLangId, DFU_SIZ_STRING_LANGID },//语言字符串 { (uint8_t*)DFU_StringVendor, DFU_SIZ_STRING_VENDOR },//厂商字符串 { (uint8_t*)DFU_StringProduct, DFU_SIZ_STRING_PRODUCT },//产品字符串 { (uint8_t*)DFU_StringSerial, DFU_SIZ_STRING_SERIAL },//序列号字符串 { (uint8_t*)DFU_StringInterface0, DFU_SIZ_STRING_INTERFACE0 } //接口描述符 }; /* Extern variables ----------------------------------------------------------*/ extern uint8_t DeviceState ; extern uint8_t DeviceStatus[6]; /* Private function prototypes -----------------------------------------------*/ /* Private functions ---------------------------------------------------------*/ /******************************************************************************* * Function Name : DFU_init. * Description : DFU初始化. * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_init(void) { DEVICE_INFO *pInfo = &Device_Info; Get_SerialNum(); //获取序列号 pInfo->Current_Configuration = 0; //初始化当前配置值 PowerOn(); //连接设备 USB_SIL_Init(); //执行基本的设备初始化操作 USB_Interrupts_Config(); //使能USB中断 bDeviceState = UNCONNECTED; } /******************************************************************************* * Function Name : DFU_Reset. * Description : DFU 复位 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_Reset(void) { Device_Info.Current_Configuration = 0; /* Current Feature initialization */ pInformation->Current_Feature = DFU_ConfigDescriptor[7]; //获取当前的特性 _SetBTABLE(BTABLE_ADDRESS); /* Initialize Endpoint 0 */ _SetEPType(ENDP0, EP_CONTROL); //设置端点0为控制端点 _SetEPTxStatus(ENDP0, EP_TX_NAK); //设置端点0发送为EP_TX_NAK _SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置端点0的接收地址 SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); //设置端点0的最大接收包长度 _SetEPTxAddr(ENDP0, ENDP0_TXADDR); //设置端点0的发送地址 SetEPTxCount(ENDP0, Device_Property.MaxPacketSize); //设置端点0的最大发送包长度 Clear_Status_Out(ENDP0); //清除端点0状态 SetEPRxValid(ENDP0); //设置端点接收有效 /* Set this device to response on default address */ SetDeviceAddress(0); //设置设备默认地址 /* Set the new control state of the device to Attached */ bDeviceState = ATTACHED; } /******************************************************************************* * Function Name : DFU_SetConfiguration. * Description : 更新设备配置状态 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_SetConfiguration(void) { DEVICE_INFO *pInfo = &Device_Info; if (pInfo->Current_Configuration != 0) { /* Device configured */ bDeviceState = CONFIGURED; } } /******************************************************************************* * Function Name : DFU_SetConfiguration. * Description : 更新设备的编址状态 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_SetDeviceAddress (void) { bDeviceState = ADDRESSED; } /******************************************************************************* * Function Name : DFU_Status_In. * Description : DFU状态输入函数 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_Status_In(void) {} /******************************************************************************* * Function Name : DFU_Status_Out. * Description : DFU状态输出函数 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_Status_Out (void) { DEVICE_INFO *pInfo = &Device_Info; uint32_t Addr; if (pInfo->USBbRequest == DFU_GETSTATUS) { if (DeviceState == STATE_dfuDNBUSY) //当前状态为正在状态 { if (wBlockNum == 0) //块数为0 { if ((MAL_Buffer[0] == CMD_GETCOMMANDS) && (wlength == 1))//获取更多设备性能以及DFU支持哪些设备 {} else if (( MAL_Buffer[0] == CMD_SETADDRESSPOINTER ) && (wlength == 5))//设置地址指针 { Pointer = MAL_Buffer[1]; Pointer += MAL_Buffer[2] << 8; Pointer += MAL_Buffer[3] << 16; Pointer += MAL_Buffer[4] << 24; } else if (( MAL_Buffer[0] == CMD_ERASE ) && (wlength == 5)) //发送擦除带地址扇区命令 { Pointer = MAL_Buffer[1]; //获取下载的地址 Pointer += MAL_Buffer[2] << 8; Pointer += MAL_Buffer[3] << 16; Pointer += MAL_Buffer[4] << 24; MAL_Erase(Pointer); //擦除该地址 } } else if (wBlockNum > 1) //块数大于1 { Addr = ((wBlockNum - 2) * wTransferSize) + Pointer;//获取写入存储器数据的地址 MAL_Write(Addr, wlength); //写入数据 } wlength = 0; wBlockNum = 0; DeviceState = STATE_dfuDNLOAD_SYNC; //设置状态为下载同步 DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; return; } else if (DeviceState == STATE_dfuMANIFEST) //显示进程 { DFU_write_crc(); //写CRC校验 return; } } return; } /******************************************************************************* * Function Name : DFU_Data_Setup. * Description : 处理带数据的特殊类请求 * Input : RequestNb. * Output : None. * Return : USB_SUCCESS or USB_UNSUPPORT. *******************************************************************************/ RESULT DFU_Data_Setup(uint8_t RequestNo) { uint8_t *(*CopyRoutine)(uint16_t); CopyRoutine = NULL; if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))//类请求,请求的接收者是接口 { if (RequestNo == DFU_UPLOAD && (DeviceState == STATE_dfuIDLE || DeviceState == STATE_dfuUPLOAD_IDLE ))//设备空闲,上传空闲 { CopyRoutine = UPLOAD; //函数指针指向UPLOAD函数 } else if (RequestNo == DFU_DNLOAD && (DeviceState == STATE_dfuIDLE || DeviceState == STATE_dfuDNLOAD_IDLE))//设备空闲,下载空闲 { DeviceState = STATE_dfuDNLOAD_SYNC; //设置设备状态为下载同步 CopyRoutine = DNLOAD; //函数指针指向DNDOWN函数 } else if (RequestNo == DFU_GETSTATE) //DFU_GETSTATE请求 { CopyRoutine = GETSTATE; //函数指针指向GETSTATE函数 } else if (RequestNo == DFU_GETSTATUS) //DFU_GETSTATUS请求 { CopyRoutine = GETSTATUS; //函数指针指向GETSTATUS函数 } else { return USB_UNSUPPORT; } } else { return USB_UNSUPPORT; } if (CopyRoutine == NULL) { return USB_UNSUPPORT; } pInformation->Ctrl_Info.CopyData = CopyRoutine;//注册这个函数指针 pInformation->Ctrl_Info.Usb_wOffset = 0; (*CopyRoutine)(0); //指向概述指针所指向的函数 return USB_SUCCESS; } /******************************************************************************* * Function Name : DFU_NoData_Setup. * Description : Handle the No data class specific requests. * Input : Request Nb. * Output : None. * Return : USB_SUCCESS or USB_UNSUPPORT. *******************************************************************************/ RESULT DFU_NoData_Setup(uint8_t RequestNo) { if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))//类请求 { /*DFU_NDLOAD*/ if (RequestNo == DFU_DNLOAD) //DFU_DNLOAD请求 { /* End of DNLOAD operation*/ if (DeviceState == STATE_dfuDNLOAD_IDLE || DeviceState == STATE_dfuIDLE )//下载空闲或设备空闲 { Manifest_State = Manifest_In_Progress; //设置显示状态为显示进程 DeviceState = STATE_dfuMANIFEST_SYNC; //设置显示同步 DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; DeviceStatus[4] = DeviceState; return USB_SUCCESS; } } /*DFU_UPLOAD*/ else if (RequestNo == DFU_UPLOAD) //DFU_UPLOAD命令 { DeviceState = STATE_dfuIDLE; //设置设备空闲 DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; DeviceStatus[4] = DeviceState; return USB_SUCCESS; } /*DFU_CLRSTATUS*/ else if (RequestNo == DFU_CLRSTATUS) //DFU_CLRSTATUS命令 { if (DeviceState == STATE_dfuERROR) //dfu错误 { DeviceState = STATE_dfuIDLE; //清除错误状态 DeviceStatus[0] = STATUS_OK;/*bStatus*/ DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; /*bwPollTimeout=0ms*/ DeviceStatus[4] = DeviceState;/*bState*/ DeviceStatus[5] = 0;/*iString*/ } else { /*State Error*/ DeviceState = STATE_dfuERROR; //设置设备状态为升级错误 DeviceStatus[0] = STATUS_ERRUNKNOWN;/*bStatus*/ DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; /*bwPollTimeout=0ms*/ DeviceStatus[4] = DeviceState;/*bState*/ DeviceStatus[5] = 0;/*iString*/ } return USB_SUCCESS; } /*DFU_ABORT*/ else if (RequestNo == DFU_ABORT) //DFU_ABORT终止命令 { if (DeviceState == STATE_dfuIDLE || DeviceState == STATE_dfuDNLOAD_SYNC || DeviceState == STATE_dfuDNLOAD_IDLE || DeviceState == STATE_dfuMANIFEST_SYNC || DeviceState == STATE_dfuUPLOAD_IDLE ) //设别空闲或上传同步或下载空闲或显示同步或上传空闲 { DeviceState = STATE_dfuIDLE; //设置设别空闲 DeviceStatus[0] = STATUS_OK; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; /*bwPollTimeout=0ms*/ DeviceStatus[4] = DeviceState; DeviceStatus[5] = 0; /*iString*/ wBlockNum = 0; wlength = 0; } return USB_SUCCESS; } } return USB_UNSUPPORT; } /* End of DFU_NoData_Setup */ /******************************************************************************* * Function Name : DFU_GetDeviceDescriptor. * Description : 获取设备描述符 * Input : Length. * Output : None. * Return : 返回设备描述符的地址 *******************************************************************************/ uint8_t *DFU_GetDeviceDescriptor(uint16_t Length) { return Standard_GetDescriptorData(Length, &Device_Descriptor); } /******************************************************************************* * Function Name : DFU_GetConfigDescriptor. * Description : 获取配置描述符 * Input : Length. * Output : None. * Return : 返回配置描述符的地址 *******************************************************************************/ uint8_t *DFU_GetConfigDescriptor(uint16_t Length) { return Standard_GetDescriptorData (Length, &Config_Descriptor); } /******************************************************************************* * Function Name : DFU_GetStringDescriptor. * Description : 根据索引获取字符串描述 * Input : Length. * Output : None. * Return : 返回字符串描述符的地址 *******************************************************************************/ uint8_t *DFU_GetStringDescriptor(uint16_t Length) { uint8_t wValue0 = pInformation->USBwValue0; if (wValue0 > 8) { return NULL; } else { return Standard_GetDescriptorData(Length, &DFU_String_Descriptor[wValue0]); } } /******************************************************************************* * Function Name : DFU_Get_Interface_Setting. * Description : 测试是否支持接口和备用接口 * Input : - Interface : 接口号 * - AlternateSetting : 备用接口号 * Output : None. * Return : USB_SUCCESS or USB_UNSUPPORT. *******************************************************************************/ RESULT DFU_Get_Interface_Setting(uint8_t Interface, uint8_t AlternateSetting) { if (AlternateSetting > 3) { return USB_UNSUPPORT; /* In this application we don't have more than 3 AlternateSettings */ } else if (Interface > 2) { return USB_UNSUPPORT; /* In this application we have only 1 interfaces */ } return USB_SUCCESS; } /******************************************************************************* * Function Name : UPLOAD * Description : 上传 * Input : Length. * Output : None. * Return : Pointer to data. *******************************************************************************/ uint8_t *UPLOAD(uint16_t Length) { DEVICE_INFO *pInfo = &Device_Info; //获取设备的信息 uint8_t B1, B0; //16bit的高8位和低8位 uint16_t offset, returned; uint8_t *Phy_Addr = NULL; uint32_t Addr = 0; B0 = pInfo->USBwValues.bw.bb0; //获取请求的wValue的低8位数据 B1 = pInfo->USBwValues.bw.bb1; //获取请求的wValue的高8位数据 wBlockNum = (uint16_t)B1; wBlockNum = wBlockNum * 0x100; wBlockNum += (uint16_t)B0; //更新wBlockNum B0 = pInfo->USBwLengths.bw.bb0; //获取请求的wLength的低8位数据 B1 = pInfo->USBwLengths.bw.bb1; //获取请求的wLength的高8位数据 wlength = (uint16_t)B0; wlength = wlength * 0x100; wlength += (uint16_t)B1; //更新wLength offset = pInformation->Ctrl_Info.Usb_wOffset;//获取数据偏移量 if (wBlockNum == 0) //wBlockNum等于0,表示不传输数据 { if (wlength > 3) { DeviceState = STATE_dfuIDLE ; } else { DeviceState = STATE_dfuUPLOAD_IDLE; } DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; MAL_Buffer[0] = CMD_GETCOMMANDS; //Get命令 MAL_Buffer[1] = CMD_SETADDRESSPOINTER; //设置地址指针 MAL_Buffer[2] = CMD_ERASE; //擦除含地址的的扇区 if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = 3 ; return NULL; } return(&MAL_Buffer[0]); } else if (wBlockNum > 1) //wBlockNum大于1 { DeviceState = STATE_dfuUPLOAD_IDLE ; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; Addr = ((wBlockNum - 2) * wTransferSize) + Pointer; //计算要上传的数据在存储器中的地址 Phy_Addr = MAL_Read(Addr, wlength);//读取数据 returned = wlength - offset; //获取上传数据的起始地址 if (Length == 0) //要上传的长度为0 { pInformation->Ctrl_Info.Usb_wLength = returned ; return NULL; } return(Phy_Addr + offset); } else //即wBlockNum=1,不支持的wBlockNum { DeviceState = STATUS_ERRSTALLEDPKT; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; return NULL; } } /******************************************************************************* * Function Name : DNLOAD * Description : 下载 * Input : Length. * Output : None. * Return : 返回指向数据的指针 *******************************************************************************/ uint8_t *DNLOAD (uint16_t Length) { DEVICE_INFO *pInfo = &Device_Info; //获取设备信息 uint8_t B1, B0; //16bit的高8位和低8位 uint16_t offset, returned; B0 = pInfo->USBwValues.bw.bb0; //获取请求的wValue的低8位数据 B1 = pInfo->USBwValues.bw.bb1; //获取请求的wValue的高8位数据 wBlockNum = (uint16_t)B1; wBlockNum = wBlockNum * 0x100; wBlockNum += (uint16_t)B0; //更新wBlockNum B0 = pInfo->USBwLengths.bw.bb0; //获取请求的wLength的低8位数据 B1 = pInfo->USBwLengths.bw.bb1; //获取请求的wLength的高8位数据 wlength = (uint16_t)B0; wlength = wlength * 0x100; wlength += (uint16_t)B1; //更新wLength offset = pInfo->Ctrl_Info.Usb_wOffset; DeviceState = STATE_dfuDNLOAD_SYNC; DeviceStatus[4] = DeviceState; returned = wlength - offset; //指向数据的起始地址 if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = returned ; return NULL; } return((uint8_t*)MAL_Buffer + offset); } /******************************************************************************* * Function Name : GETSTATE. * Description : 获取STATE请求函数. * Input : Length. * Output : None. * Return : Pointer to data. *******************************************************************************/ uint8_t *GETSTATE(uint16_t Length) { if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = 1 ; return NULL; } else return(&DeviceState); } /******************************************************************************* * Function Name : GETSTATUS. * Description : 获取Status请求函数 * Input : Length. * Output : None. * Return : Pointer to data. *******************************************************************************/ uint8_t *GETSTATUS(uint16_t Length) { switch (DeviceState) { case STATE_dfuDNLOAD_SYNC: if (wlength != 0) { DeviceState = STATE_dfuDNBUSY; DeviceStatus[4] = DeviceState; if ((wBlockNum == 0) && (MAL_Buffer[0] == CMD_ERASE)) { MAL_GetStatus(Pointer, 0, DeviceStatus); } else { MAL_GetStatus(Pointer, 1, DeviceStatus); } } else /* (wlength==0)*/ { DeviceState = STATE_dfuDNLOAD_IDLE; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; } break; case STATE_dfuMANIFEST_SYNC : if (Manifest_State == Manifest_In_Progress) { DeviceState = STATE_dfuMANIFEST; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 1; /*bwPollTimeout = 1ms*/ DeviceStatus[2] = 0; DeviceStatus[3] = 0; //break; } else if (Manifest_State == Manifest_complete && Config_Descriptor.Descriptor[20] & 0x04) { DeviceState = STATE_dfuIDLE; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; //break; } break; default : break; } if (Length == 0) { pInformation->Ctrl_Info.Usb_wLength = 6 ; return NULL; } else return(&(DeviceStatus[0])); } /******************************************************************************* * Function Name : DFU_write_crc. * Description : DFU 写CRC * Input : None. * Output : None. * Return : None. *******************************************************************************/ void DFU_write_crc(void) { Manifest_State = Manifest_complete; if (Config_Descriptor.Descriptor[20] & 0x04) { DeviceState = STATE_dfuMANIFEST_SYNC; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; return; } else { DeviceState = STATE_dfuMANIFEST_WAIT_RESET; DeviceStatus[4] = DeviceState; DeviceStatus[1] = 0; DeviceStatus[2] = 0; DeviceStatus[3] = 0; Reset_Device(); return; } }

接下去要讲的是flash_if.c这个文件,这个文件实现的前提是工程已经添加了stm32f10x_flash.c库文件。这个文件只有四个文件:FLASH_If_Init(),FLASH_If_Erase(),FLASH_If_Write(),FLASH_If_Read()。首先讲讲 FLASH_If_Init(),因为flash是程序存储的地方,一上电就已经可以使用,所以用不着初始化了,在这个函数里可以是空函数:

uint16_t FLASH_If_Init(void) { return MAL_OK; }

然后是FLASH_If_Erase(uint32_t SectorAddress),这个函数调用flash的擦除页函数FLASH_ErasePage():

uint16_t FLASH_If_Erase(uint32_t SectorAddress) { FLASH_ErasePage(SectorAddress); return MAL_OK; }

至于FLASH_If_Write()这个函数就稍稍复杂点了,这个函数需要判断要写的长度是否子对齐,如果字不对齐,则需要填充成字对齐,然后在写入flash中,这里需要注意的是我们的STM32是32位的,无论是写还是读都要字对齐。

uint16_t FLASH_If_Write(uint32_t SectorAddress, uint32_t DataLength) { uint32_t idx = 0; if (DataLength & 0x3) /* Not an aligned data */ { for (idx = DataLength; idx < ((DataLength & 0xFFFC) + 4); idx++) { MAL_Buffer[idx] = 0xFF; } } /* Data received are Word multiple */ for (idx = 0; idx < DataLength; idx = idx + 4) { FLASH_ProgramWord(SectorAddress, *(uint32_t *)(MAL_Buffer + idx)); SectorAddress += 4; } return MAL_OK; }

然后是读函数uint8_t *FLASH_If_Read (uint32_t SectorAddress, uint32_t DataLength),我们这里直接返回要读的地址,这个函数只有在校验的时候会调用。

uint8_t *FLASH_If_Read (uint32_t SectorAddress, uint32_t DataLength) { return (uint8_t*)(SectorAddress); }

接下去就是dfu_mal.c了。这个文件包括MAL_Init()、MAL_Erase()、MAL_Write()、MAL_Read()、MAL_GetStatus():

uint16_t MAL_Init(void) { FLASH_If_Init(); //Internal Flash初始化 return MAL_OK; } /******************************************************************************* * Function Name : MAL_Erase * Description : 擦除扇区 * Input : None * Output : None * Return : None *******************************************************************************/ uint16_t MAL_Erase(uint32_t SectorAddress) { switch (SectorAddress & MAL_MASK) //参看地址 { case INTERNAL_FLASH_BASE: //如果是在INTERNAL FLASH地址池内 pMAL_Erase = FLASH_If_Erase; //擦写函数指针指向FLASH_If_Erase break; default: return MAL_FAIL; } return pMAL_Erase(SectorAddress); //指向擦除函数 } /******************************************************************************* * Function Name : MAL_Write * Description : 写扇区 * Input : None * Output : None * Return : None *******************************************************************************/ uint16_t MAL_Write (uint32_t SectorAddress, uint32_t DataLength) { switch (SectorAddress & MAL_MASK) //查看地址 { case INTERNAL_FLASH_BASE: //如果是在INTERNAL FLASH地址池内 pMAL_Write = FLASH_If_Write; //写函数指针指向FLASH_If_Write break; default: return MAL_FAIL; } return pMAL_Write(SectorAddress, DataLength);//调用写扇区函数 } /******************************************************************************* * Function Name : MAL_Read * Description : 度扇区 * Input : None * Output : None * Return : Buffer pointer *******************************************************************************/ uint8_t *MAL_Read (uint32_t SectorAddress, uint32_t DataLength) { switch (SectorAddress & MAL_MASK) //查看地址 { case INTERNAL_FLASH_BASE: //如果是在INTERNAL FLASH地址池内 pMAL_Read = FLASH_If_Read; //读函数指针指向FLASH_If_Read break; default: return 0; } return pMAL_Read (SectorAddress, DataLength);//调用如扇区函数 } /******************************************************************************* * Function Name : MAL_GetStatus * Description : 获取状态 * Input : None * Output : None * Return : MAL_OK *******************************************************************************/ uint16_t MAL_GetStatus(uint32_t SectorAddress , uint8_t Cmd, uint8_t *buffer) { //更具地址查找定时表的对应的选项 uint8_t x = (SectorAddress >> 26) & 0x03 ; /* 0x000000000 --> 0 */ /* 0x640000000 --> 1 */ /* 0x080000000 --> 2 */ uint8_t y = Cmd & 0x01; SET_POLLING_TIMING(TimingTable[x][y]); /* x: 擦除/写 定时 */ /* y: Media */ return MAL_OK; }

最后要说的main函数了:

typedef void (*pFunction)(void); uint8_t DeviceState; uint8_t DeviceStatus[6]; pFunction Jump_To_Application; uint32_t JumpAddress;

int main(void) { BSP_Init(); printf(" |===============================================|\r\n"); printf(" STM32 DFU 程序开始 \r\n"); printf("|===============================================|\r\n");

if (DFU_Button_Read() != 0x00) { if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //检验跳转地址区域是否正确 { JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); //获取跳转程序的RESET中断向量地址 Jump_To_Application = (pFunction) JumpAddress; //强制转换成函数指针 __set_MSP(*(__IO uint32_t*) ApplicationAddress); //将该地址写入R14 Jump_To_Application(); //调用函数,当函数调用结束时,PC指正指向R14的地址 } }

FLASH_Unlock(); /* Enter DFU mode */ DeviceState = STATE_dfuERROR; //程序指向到这句话,说明DFU跳转不成功 DeviceStatus[0] = STATUS_ERRFIRMWARE; DeviceStatus[4] = DeviceState; USB_Configuration(); //初始化USB while(1) { //LED1闪烁 LED1_Toggle(); Delay_ms(1000); } }

这里有三个地方需要特别说明下:
1、程序跳转的实现。我们在hw_config.h中定义了升级地址:#define ApplicationAddress 0x08005000,在主函数中,首次按会检查该升级地址是否有效if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000),这句话其实是检查该升级地址是否在BANK1的地址范围内,因为flash在BANK1。 可以看到上面定义了一个函数指针类型的函数结构: typedef void (*pFunction)(void);还定了一个跳转地址变量 uint32_t JumpAddress;可以看到 JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);这句话 JumpAddress赋值为 升级地址+4的地址,可以知道该地址指向升级代码的主堆栈,至于有人问道:为什么不直接使用 ApplicationAddress呢?原因很简单,因为 ApplicationAddresss是个常数,无法直接使用,所以这里定义了 JumpAddress这个变量。 然后又用该函数结构定义了一个函数 pFunction Jump_To_Application;,代码 Jump_To_Application = (pFunction) JumpAddress;中函数指针 Jump_To_Application指向 JumpAddress地址处。接着代码调用了__set_MSP(*(__IO uint32_t*) ApplicationAddress);这句话的意思是将 ApplicationAddress的地址写入R14寄存器。最后调用 Jump_To_Application()函数,当该函数调用返回是,PC指针会回去R14的保存的地址,然后程序跳到该地址执行。这就是程序跳转的实现。

2、一定要给flash解锁,否则升级程序无法烧写到flash中,所以必须调用 FLASH_Unlock();这个函数。

3、一定要给定设备的状态。一开始 DeviceState必须设置成 STATE_dfuERROR, DeviceStatus[0]必须设置为 STATUS_ERRFIRMWARE。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值