stm32的下载方式有很多种,最简单的是SW下载,不过这种方式会占用两个IO口,虽然可以程序中复用,但是在pcb设计、后期升级等也有一定的不便,所以加入DFU下载方式。

准备工作:

  1. 去st官网下载DFU工具STSW-STM32080
  2. 官网下载比较慢而且麻烦,本地下载-en.stsw-stm32080_.zip

2.jpg

这是我最近的一个项目,一个迷你的热成像仪,程序下载的方式是DFU

1.PNG
启动USB
2.PNG
使用官方的DUF功能集
在这里需要配置两个地方,首先是APP的运行地址,首先要知道单片机初始化后程序是从0x08000000开始运行的,而我这里设置APP的运行地址是0x08004000,那么DFU的程序就放在了0x08000000-0x08004000,只有0x4000个字节,我设置的比较小,可以调大。
第二个需要设置的地方就是下一项,USB描述


单片机内部flash是分为好几块的,上面我设置的是01*016Ka表示受保护不可写,因为这一块是存放DFU程序的,后面的Kg表示可写。这一块比较抽象,往下看图就明白了。


生成工程
在初始化程序的时候拉低USB D+引脚可以让PC重新枚举设备,就不用插插拔拔了

void reset_usb()
{
    GPIO_InitTypeDef GPIO_InitStruct;
    /*拉低USB D+ 引脚 使PC重新枚举USB设备 */
  GPIO_InitStruct.Pin = USB_DM_Pin|USB_DP_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    HAL_GPIO_WritePin(GPIOA,USB_DM_Pin,GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA,USB_DP_Pin,GPIO_PIN_RESET);
    //HAL_GPIO_WritePin(GPIOA,USB_DP_Pin,GPIO_PIN_SET);
    HAL_Delay(1);
}

在主循环最下面加入如下代码

reset_usb();  //开机拉低USB D+引脚使得PC重新枚举设备        
if((k3)&&(k4))//如果开机的时候这两个按钮同时按下,进入DUF模式
{
    MX_USB_DEVICE_Init();                                  //初始化USB
    Lcd_Init();                                            //初始化屏幕  如果有的话
    LCD_Clear(BLACK);                                      //屏幕中显示一些提示信息
    LCD_ShowString(0,0," DFU Mode (STM32F103RFx)",WHITE);
    LCD_ShowString(0,16," Thermal Imaging",WHITE);
    LCD_ShowString(0,16*2," DFU Version   2020Y 08M 30D",WHITE);
    LCD_ShowString(0,16*3," Program by Kevin.",WHITE);
    LCD_ShowString(0,16*4," wuwenfengmi@qq.com",WHITE);
    LCD_ShowString(0,16*5," wuwenfengmi@gmail.com",WHITE);
    LCD_ShowString(0,16*6," Welcome to 'lmve.net'",WHITE);
    
    LCD_ShowString(0,16*8," APP Address: 0x8004000",WHITE);
    LCD_ShowString(0,16*9," Vector Table Offset: 0x4000",WHITE);
    
    
    while(1)                                              //DFU下载住程序可以什么都不做
                                                          //也可以整个流水灯下去看起来牛逼一点
    {
        
    }
}

//如果那两个按钮没按下就跳转到APP  

uint32_t SpInitVal; //要跳转到程序的SP初值.
uint32_t JumpAddr; //要跳转到程序的地址.即,要跳转到程序的入口
void (*pFun)(void); //定义一个函数指针.用于指向APP程序入口
SpInitVal = *(uint32_t *)USBD_DFU_APP_DEFAULT_ADD;
JumpAddr = *(uint32_t *)( USBD_DFU_APP_DEFAULT_ADD + 4); //设置复位中断向量
if((SpInitVal & 0x2FFE0000)==0x20000000)//检查栈顶地址是否合法.
{
    __set_MSP(SpInitVal); //设置SP.,堆栈栈顶地址
    pFun = (void (*)(void))JumpAddr; //生成跳转函数.将复位中断向量地址做为函数指针
    (*pFun) (); //执行函数,实现跳转.不再返回.
}


万事俱备 还欠东风。
3.PNG
打开usbd_dfu_if.c 完善程序下载的实现方式

/* Private functions ---------------------------------------------------------*/
/**
  * @brief  Memory initialization routine.
  * @retval USBD_OK if operation is successful, MAL_FAIL else.
  */
uint16_t MEM_If_Init_FS(void)
{
  /* USER CODE BEGIN 0 */
    HAL_StatusTypeDef flash_ok = HAL_ERROR;
    //Make the memory open
    while(flash_ok!= HAL_OK) 
    {
        flash_ok = HAL_FLASH_Unlock ();
    }
    return(USBD_OK);
  /* USER CODE END 0 */
}

/**
  * @brief  De-Initializes Memory
  * @retval USBD_OK if operation is successful, MAL_FAIL else
  */
uint16_t MEM_If_DeInit_FS(void)
{
  /* USER CODE BEGIN 1 */
    HAL_StatusTypeDef flash_ok = HAL_ERROR;
    //Close the memory
    flash_ok = HAL_ERROR;
    while(flash_ok!= HAL_OK) 
    {
        flash_ok = HAL_FLASH_Lock ();
    }
    return(USBD_OK);
  /* USER CODE END 1 */
}

/**
  * @brief  Erase sector.
  * @param  Add: Address of sector to be erased.
  * @retval 0 if operation is successful, MAL_FAIL else.
  */
#define USBD_DFU_APP_END_ADD        0x0801DFFF
uint16_t MEM_If_Erase_FS(uint32_t Add)
{
  /* USER CODE BEGIN 2 */
    
  uint32_t NbOfPages = 0;
    uint32_t PageError = 0;
    FLASH_EraseInitTypeDef pEraseInit;
    
    NbOfPages = (USBD_DFU_APP_END_ADD - USBD_DFU_APP_DEFAULT_ADD)/FLASH_PAGE_SIZE;
    
    pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    pEraseInit.PageAddress = USBD_DFU_APP_DEFAULT_ADD;
    pEraseInit.NbPages = NbOfPages;      //erase all pages of APP
    
    if(HAL_FLASHEx_Erase(&pEraseInit,&PageError)!= HAL_OK){return 1;}
  return (USBD_OK);
  /* USER CODE END 2 */
}

/**
  * @brief  Memory write routine.
  * @param  src: Pointer to the source buffer. Address to be written to.
  * @param  dest: Pointer to the destination buffer.
  * @param  Len: Number of data to be written (in bytes).
  * @retval USBD_OK if operation is successful, MAL_FAIL else.
  */
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 3 */
    uint32_t i =0;    
    for(i=0;i<Len;i+=4)
    {
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,(uint32_t)(dest+i),*(uint32_t*)(src+i))== HAL_OK)
        {
            /*Check the written value*/
            if(*(uint32_t*)(src+i) != *(uint32_t*)(dest+i))
            {
                /*Flash content doesn't match SRAM content*/
                return 2;
            }
        }
        else
        {
            return 1;
        }
    }
  return (USBD_OK);
  /* USER CODE END 3 */
}

/**
  * @brief  Memory read routine.
  * @param  src: Pointer to the source buffer. Address to be written to.
  * @param  dest: Pointer to the destination buffer.
  * @param  Len: Number of data to be read (in bytes).
  * @retval Pointer to the physical address where data should be read.
  */
uint8_t *MEM_If_Read_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 4 */
  uint32_t i = 0;
    uint8_t *psrc = src;    
    for(i=0;i<Len;i++)
    {
        dest[i] = *psrc++;
    }
    return (uint8_t*)(dest);
  /* USER CODE END 4 */
}

/**
  * @brief  Get status routine
  * @param  Add: Address to be read from
  * @param  Cmd: Number of data to be read (in bytes)
  * @param  buffer: used for returning the time necessary for a program or an erase operation
  * @retval USBD_OK if operation is successful
  */
#define FLASH_ERASE_TIME            (uint16_t)50 
#define FLASH_PROGRAM_TIME           (uint16_t)50
uint16_t MEM_If_GetStatus_FS(uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 5 */
  switch (Cmd)
  {
    case DFU_MEDIA_PROGRAM:
    buffer[1] = (uint8_t)FLASH_PROGRAM_TIME;
        buffer[2] = (uint8_t)(FLASH_PROGRAM_TIME << 8);
        buffer[3] = 0;
    break;

    case DFU_MEDIA_ERASE:
    default:
    buffer[1] = (uint8_t)FLASH_ERASE_TIME;
    buffer[2] = (uint8_t)(FLASH_ERASE_TIME << 8);
    buffer[3] = 0;
    break;
  }
  return (USBD_OK);
  /* USER CODE END 5 */
}

一些细节
4.PNG
因为上面我设置的DUF程序大小只有0x4000个字节 所以下载的时候设置一下,保证不会超。
然后就可以下载到单片机测试了
不出意外的话下载到单片机进入duf模式打开官方软件就能识别到
5.PNG
6.PNG
点开可以看到第一块flash是不可写的,和上面USB描述一样。

DFU写完,开始写APP
APP的编写和普通单片机开发一样只需要改两个地方
(这是APP的工程)
7.PNG
APP的运行地址改为 DFU那设置的地址
8.PNG
还有中断偏移表
然后编译APP生成HEX
9.PNG
打开HEX转DFU文件的软件
10.PNG
吧DFU识别到的值抄过去
剩下的就简单多了 打开HEX。。点击Generate转换成dfu文件,
回到Dfuse Demo这个软件,点击Choose打开duf文件,点击Upgrade下载,下载完手动重启单片机测试APP程序。。

最后修改:2020 年 10 月 24 日
声明:无闻风博客|版权所有,违者必究|如未注明,均为原创| 转载:转载请注明原文链接