STM32 Internal Flash DFU芯片内部flash代码升级
这次要讲讲如何用USB升级单片机代码。以前曾经做过串口升级,网络升级,升级的基本原理都类似,只不过升级的工具不同罢了,串口升级当然是用串口了,网络升级用的是TCP/IP,USB升级当然用的是USB了。下面就来讲讲USB升级的实现。
修改部分部分都在USB_User组里:
我们一个一个文件讲过来。
首先讲讲hw_config.c,这个文件跟之前工程差不多。由于演示的需要,我们在这个文件里初始化一个按键引脚,并定义按键读取函数,该按键决定代码是否升级,如果程序一开始,该按键按下,则进入升级模式,否则跳转到升级程序代码处:
usb_desc.c这个文件自然要修改的,USB的功能属性等全在这里定义。首先必须关注下设备描述度符,这里有一点需要强调,就是厂商ID域的值必须为0483,否则电脑不识别USB,产品ID可以自定义。 /* USB标准设备描述符*/ DFU_DeviceDescriptor[DFU_SIZ_DEVICE_DESC]= 0x12/*bLength:长度,设备描述符的长度为18字节*/ USB_DEVICE_DESCRIPTOR_TYPE/*bDescriptorType:类型,设备描述符的编号是0x01*/ 0x00/*bcdUSB:所使用的USB版本为2.0*/ 0x02/*bDeviceClass:设备所使用的类代码*/ /*bDeviceSubClass:设备所使用的子类代码*/ /*bDeviceProtocol:设备所使用的协议*/ 0x40/*bMaxPacketSize:最大包长度为64字节*/ 0x83/*idVendor:厂商ID为0x1234*/ 0x040x11/*idProduct:产品ID为0x1010*/ 0xDF/*bcdDevice:设备的版本号为2.00*/ 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 */ DFU_ConfigDescriptorDFU_SIZ_CONFIG_DESC0x09/*bLength:长度,设备字符串的长度为9字节*/ USB_CONFIGURATION_DESCRIPTOR_TYPE/*bDescriptorType:类型,配置描述符的类型编号为0x2*/ DFU_SIZ_CONFIG_DESC/*wTotalLength:配置描述符的总长度为41字节*/ 0x01/*bNumInterfaces:配置所支持的接口数量1个*/ /*bConfigurationValue:该配置的值*/ /*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***********/ /*bLength:长度,接口描述符的长度为9字节 */ USB_INTERFACE_DESCRIPTOR_TYPE/* bDescriptorType:接口描述符的类型为0x4 */ /*bInterfaceNumber:该接口的编号*/ /*bAlternateSetting:该接口的备用编号 */ /*bNumEndpoints:该接口所使用的端点数*/ 0xFE/*bInterfaceClass该接口所使用的类为 DFU*/ /*bInterfaceSubClass:该接口所用的子类 1=BOOT,0=no boot */ /*nInterfaceProtocol :DFU模式*/接下去的语言字符串描述符、厂商字符串描述符、产品字符串描述符、序列号字符串描述符都不详细介绍了。 /* 语言ID描述符 */ DFU_StringLangIdDFU_SIZ_STRING_LANGID DFU_SIZ_STRING_LANGID/*bLength:本描述符的长度为4字节*/ USB_STRING_DESCRIPTOR_TYPE/*bDescriptorType:字符串描述符的类型为0x03*/ /*bString:语言ID为0x0409,表示美式英语*/ 0x04 /* LangID = 0x0409: U.S. English*/ /*厂商字符串描述符*/ DFU_StringVendorDFU_SIZ_STRING_VENDOR DFU_SIZ_STRING_VENDOR/*bLength:厂商字符串描述符的长度*/ 'B'0'y'':''z''i''e''3''4'0/*自定义*/ }; /*产品的字符串描述符*/ DFU_StringProductDFU_SIZ_STRING_PRODUCT DFU_SIZ_STRING_PRODUCT/* bLength:产品的字符串描述符*/ /* bDescriptorType:字符串描述符的类型为0x03*/ 'z'0/*产品序列号的字符串描述符*/ DFU_StringSerialDFU_SIZ_STRING_SERIAL DFU_SIZ_STRING_SERIAL/* bLength:产品序列号*/ '1''2''5''6''7'};重点介绍的是接口字符串描述符,这个字符串描述符定义了升级的硬件信息,如下是我们的接口描述符: /*接口字符串描述符*/ DFU_StringInterface0DFU_SIZ_STRING_INTERFACE0= DFU_SIZ_STRING_INTERFACE00x03// Interface 0: "@Internal Flash /0x08000000/12*001Ka,500*001Kg" '@''I''n''t''r''a''l'/* 18 */ ' ''F''s''h'/* 16 */ '/''0''x''8'/* 22 */ '*''K'',''g'/* 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各个状态之间的变化转移,代码我还是点模糊,也不准备详细讲,直接贴代码,部分已将做了注解:
接下去要讲的是flash_if.c这个文件,这个文件实现的前提是工程已经添加了stm32f10x_flash.c库文件。这个文件只有四个文件:FLASH_If_Init(),FLASH_If_Erase(),FLASH_If_Write(),FLASH_If_Read()。首先讲讲 FLASH_If_Init(),因为flash是程序存储的地方,一上电就已经可以使用,所以用不着初始化了,在这个函数里可以是空函数: FLASH_If_Init MAL_OK} 然后是FLASH_If_Erase(uint32_t SectorAddress),这个函数调用flash的擦除页函数FLASH_ErasePage(): FLASH_If_EraseSectorAddress FLASH_ErasePage} 至于FLASH_If_Write()这个函数就稍稍复杂点了,这个函数需要判断要写的长度是否子对齐,如果字不对齐,则需要填充成字对齐,然后在写入flash中,这里需要注意的是我们的STM32是32位的,无论是写还是读都要字对齐。
FLASH_If_WriteDataLength idx DataLength0x3/* Not an aligned data */
foridx <0xFFFC idx++)
idx/* Data received are Word multiple */
FLASH_ProgramWord*(*)());
SectorAddress} 然后是读函数uint8_t *FLASH_If_Read (uint32_t SectorAddress,uint32_t DataLength),我们这里直接返回要读的地址,这个函数只有在校验的时候会调用。
FLASH_If_Read *)(}
接下去就是dfu_mal.c了。这个文件包括MAL_Init()、MAL_Erase()、MAL_Write()、MAL_Read()、MAL_GetStatus():
MAL_Init//Internal Flash初始化
* Function Name : MAL_Erase
* Description : 擦除扇区
* Input : None
* Output : None
* Return : None
MAL_MASK//参看地址
INTERNAL_FLASH_BASE://如果是在INTERNAL FLASH地址池内
pMAL_Erase //擦写函数指针指向FLASH_If_Erase
default MAL_FAIL pMAL_Erase//指向擦除函数
* Function Name : MAL_Write
* Description : 写扇区
MAL_Write //查看地址
pMAL_Write //写函数指针指向FLASH_If_Write
pMAL_Write//调用写扇区函数
* Function Name : MAL_Read
* Description : 度扇区
* Return : Buffer pointer
MAL_Read pMAL_Read FLASH_If_Read//读函数指针指向FLASH_If_Read
//调用如扇区函数
* Function Name : MAL_GetStatus
* Description : 获取状态
* Return : MAL_OK
Cmdbuffer//更具地址查找定时表的对应的选项
x >>260x03/* 0x000000000 --> 0 */
/* 0x640000000 --> 1 */
/* 0x080000000 --> 2 */
y Cmd
SET_POLLING_TIMINGTimingTablex][y]);/* x: 擦除/写 定时 */
/* y: Media */
}
最后要说的main函数了:
typedef(*pFunction)(); pFunction Jump_To_ApplicationJumpAddress;
int main BSP_Init(); printf(" |===============================================|rn"" STM32 DFU 程序开始 rn""|===============================================|rn"); DFU_Button_Read()(((*(__IO uint32_t*)0x2FFE00000x20000000//检验跳转地址区域是否正确 JumpAddress*(ApplicationAddress//获取跳转程序的RESET中断向量地址 Jump_To_Application//强制转换成函数指针 __set_MSP(*(//将该地址写入R14 //调用函数,当函数调用结束时,PC指正指向R14的地址 } FLASH_Unlock/* Enter DFU mode */ //程序指向到这句话,说明DFU跳转不成功 STATUS_ERRFIRMWARE USB_Configuration//初始化USB while//LED1闪烁 LED1_ToggleDelay_ms1000} 这里有三个地方需要特别说明下:
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。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |