DIY STM32 离线下载器(仅SWD下载模式)
一、材料准备
USB-A公座、0.91寸OLED显示屏、按键、W25Q16存储芯片、STM32F103C8T6控制芯片、其他器件若干。
二、硬件设计

三、软件设计
1.模拟U盘功能实现
具体实现方式可查看之前的博客《STM32+W25QXX实现模拟U盘-HAL库》
博客链接:https://blog.csdn.net/qq_45714830/article/details/124324749
2.FATFS文件系统移植
FATFS文件系统源码下载地址
http://elm-chan.org/fsw/ff/00index_e.html
下载完成后将文件夹中的diskio.c diskio.h ff.c ff.h ffconf.h复制放入自己的工程当中
修改diskio.h文件部分代码
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* FatFs lower layer API */
#include "w25qxx.h"
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv)
{
case 0 :
stat = 0;
return stat;
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv)
{
case 0 :
stat = W25QXX_Init();
return stat;
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
switch (pdrv)
{
case 0 :
for (; count > 0; count--)
{
W25QXX_Read(buff, sector * 512, 512);
sector++;
buff += 512;
}
return RES_OK;
}
return RES_PARERR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
switch (pdrv)
{
case 0 :
for (; count > 0; count--)
{
W25QXX_Write((uint8_t *)buff, sector * 512, 512);
sector++;
buff += 512;
}
return RES_OK;
}
return RES_PARERR;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
switch (pdrv)
{
case 0 :
switch (cmd) {
case GET_SECTOR_COUNT:
*(DWORD * )buff = 4096;
break;
case GET_SECTOR_SIZE :
*(WORD * )buff = 512;
break;
case GET_BLOCK_SIZE :
*(DWORD * )buff = 1;
break;
}
return RES_OK;
}
return RES_PARERR;
}
/*
*********************************************************************************************************
* 函 数 名: get_fattime
* 功能说明: 获得系统时间,用于改写文件的创建和修改时间。
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
DWORD get_fattime(void)
{
/* 如果有全局时钟,可按下面的格式进行时钟转换. 这个例子是2013-01-01 00:00:00 */
return 0; /* Sec = 0 */
}
3、CMSIS-DAP文件移植
这里的文件是查看其他博主的源代码粘贴复制的,没有做修改直接使用的
4、HEX文件转BIN文件实现
Hex文件是可以烧录到MCU中,被MCU执行的一种文件格式。如果用记事本打开可发现,整个文件以行为单位,每行以冒号开头,内容全部为16进制码(以ASCII码形式显示)。Hex文件可以按照如下的方式进行拆分来分析其中的内容:
例如 “:1000080080318B1E0828092820280B1D0C280D2854”可以被看作“0x10 0x00 0x08 0x00 0x80 0x31 0x8B 0x1E 0x08 0x28 0x09 0x28 0x20 0x28 0x0B 0x1D 0x0C 0x28 0x0D 0x28 0x54”
第一个字节 0x10表示本行数据的长度;
第二、三字节 0x00 0x08表示本行数据的起始地址;
第四字节 0x00表示数据类型,数据类型有:0x00、0x01、0x02、0x03、0x04、0x05。
‘00’ Data Rrecord:用来记录数据,HEX文件的大部分记录都是数据记录
‘01’ End of File Record: 用来标识文件结束,放在文件的最后,标识HEX文件的结尾
‘02’ Extended Segment Address Record: 用来标识扩展段地址的记录
‘03’ Start Segment Address Record:开始段地址记录
‘04’ Extended Linear Address Record: 用来标识扩展线性地址的记录
‘05’ Start Linear Address Record:开始线性地址记录
然后是数据,最后一个字节 0x54为校验和。
校验和的算法为:计算0x54前所有16进制码的累加和(不计进位),检验和 = 0x100 - 累加和
在上面的后2种记录,都是用来提供地址信息的。每次碰到这2个记录的时候,都可以根据记录计算出一个“基”地址。对于后面的数据记录,计算地址的时候,都是以这些“基”地址为基础的。
HEX文件都是由记录(RECORD)组成的。在HEX文件里面,每一行代表一个记录。记录的基本格式为:
Record mark ‘:’ 1 byte
Length 1 byte
Load offset 2 bytes
Record type 1 byte
INFO or DATA n bytes
CHKSUM 1byte
代码实现
#include "hex2bin.h"
#include <stdio.h>
#include "ff.h"
#include "string.h"
static uint8_t HexCharToBinBinChar(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'a' && c <= 'z')
return c - 'a' + 10;
else if (c >= 'A' && c <= 'Z')
return c - 'A' + 10;
return 0xff;
}
RESULT_STATUS HexFile2BinFile(char *src, char *dest)
{
FIL f_hex, f_bin;
unsigned int d,d1;
char buffer_hex[1024];
uint8_t buffer_bin[200];
uint8_t bin[200];
uint16_t size,type;
uint16_t i,j,k;
uint8_t flg = 0;
f_open(&f_hex, (const TCHAR *)src, FA_READ);
f_open(&f_bin, (const TCHAR *)dest, FA_OPEN_ALWAYS | FA_WRITE);
j=0;
while(1)
{
f_read(&f_hex,&buffer_hex,1024,&d);
for(i=0;i<d;i++)
{
if(buffer_hex[i] == 0x3A && flg == 0)
flg = 1;
else if(flg == 1)
{
bin[j++] = HexCharToBinBinChar(buffer_hex[i]);
if(j == 8)
{
size = bin[0]<<4 | bin[1];
type = bin[6]<<4 | bin[7];
j = 0;
if(type == 0)
flg = 2;
else if(type == 1)
{
f_close(&f_bin);
return F_RES_OK;
}
else
flg = 0;
}
}
else if(flg == 2)
{
bin[j++] = HexCharToBinBinChar(buffer_hex[i]);
if(j == (size*2))
{
for(k=0;k<size;k++)
buffer_bin[k] = bin[2*k]<<4 | bin[2*k+1];
f_write(&f_bin,&buffer_bin,size,&d1);
flg = 0;
j=0;
}
}
}
}
}
5、主函数(文件选择实现、更改下载地址实现)
5.1 主函数
int main(void)
{
uint8_t RES_FS = 0;
uint8_t addr[4];
HAL_Init();
SystemClock_Config();
OLED_Init();
MX_USB_DEVICE_Init();
MX_KEY_Init();
MX_LED_Init();
STMFLASH_Read(FLASH_SAVE_ADDR,(uint16_t *)addr,4);
flash_addr = ((addr[0]<<8 |addr[1])<<8|addr[2])<<8|addr[3];
//FATFS文件系统挂载
algo_init(); //下载算法初始化
RES_FS = f_mount(&fs,"",1); //挂载文件系统
if (RES_FS == FR_OK);
else if (RES_FS == FR_NO_FILESYSTEM) //如果是新芯片还没有文件系统
{
OLED_ShowString(0, 2, "Fatfs Format..", 12);
f_mkfs("", 0, work, sizeof(work));
OLED_ShowString(0, 2, "Format Finished", 12);
}
else
OLED_ShowString(0, 2, "Fatfs Failed..", 12);
// f_unlink("write.bin");
//读取文件名到文件列表
if (f_opendir(&DirInfo, (const TCHAR *)"0:") == FR_OK) //读取根目录下文件信息
{
f_readdir(&DirInfo, &FileInfo);
while (f_readdir(&DirInfo, &FileInfo) == FR_OK) //读文件信息到文件状态结构体中
{
if (!FileInfo.fname[0])
break;
strcpy(Name_Buffer[name_cnt], FileInfo.fname);
if(strstr(Name_Buffer[name_cnt], ".BIN"))
{
name_cnt++;
if(name_cnt>=20) //最多保存20个文件名
break;
}
if(strstr(Name_Buffer[name_cnt], ".HEX"))
{
name_cnt++;
if(name_cnt>=20) //最多保存20个文件名
break;
}
}
}
while (1)
{
menu();
}
/* USER CODE END 3 */
}
void menu(void)
{
static uint8_t mode_status = 0;
switch(mode)
{
case 0://功能选择
select_function();
break;
case 1://离线下载模式
Downloader();
break;
case 2://flash下载地址
FlashAddr_Set();
break;
}
if(mode != mode_status)
{
mode_status = mode;
OLED_Clear();
}
}
5.2 文件选择实现
void Downloader(void)
{
uint8_t key =0;
key = KEY_Scan();
if(key == KEY0_PRES)
{
Select_file++;
OLED_ShowString(0,2," ",12);
}
else if(key == KEY1_PRES)//返回主页面
mode = 0;
else if(key == KEY2_PRES)
Auto_Fash();
if(name_cnt == 0)
Select_file = 0;
else
{
if(Select_file>=name_cnt)
Select_file=0;
else if(Select_file<0)
Select_file=name_cnt-1;
}
OLED_ShowMenu(1);
OLED_ShowString(0,2,(uint8_t*)Name_Buffer[Select_file],12);
if(name_cnt == 0)
OLED_ShowNum(90,0,Select_file,2,12);
else
OLED_ShowNum(90,0,Select_file+1,2,12);
OLED_ShowString(102,0,"/",12);
OLED_ShowNum(114,0,name_cnt,2,12);
}
5.3 flash选择地址设置
void FlashAddr_Set(void)
{
static uint8_t k=0,k1=0;
static uint8_t setmode =0;
uint8_t addr[8];
uint8_t key=0,i;
uint8_t ad[4];
addr[7] = (flash_addr/0x01)%16;
addr[6] = (flash_addr/0x10)%16;
addr[5] = (flash_addr/0x100)%16;
addr[4] = (flash_addr/0x1000)%16;
addr[3] = (flash_addr/0x10000)%16;
addr[2] = (flash_addr/0x100000)%16;
addr[1] = (flash_addr/0x1000000)%16;
addr[0] = (flash_addr/0x10000000)%16;
OLED_ShowMenu(2);
OLED_ShowString(0,2,"0x",12);
while(1)
{
for(i=0;i<8;i++)
{
if(addr[i]<10)
OLED_ShowChar(18+6*i,2,addr[i]+48,12);
else
OLED_ShowChar(18+6*i,2,addr[i]+65-10,12);
}
key = KEY_Scan();
if(key == KEY0_PRES)
{
if(setmode == 0)
{
OLED_ShowString(18+6*k,3,"|",12);
setmode = 1;
}
else if(setmode == 1)
{
addr[k]++;
if(addr[k]>15)
addr[k]=0;
}
}
else if(key == KEY1_PRES)
{
if(setmode == 0)
{
mode = 0;
break;
}
else if(setmode == 1)
{
k++;
if(k>=8)
k=0;
}
}
else if(key == KEY2_PRES)
{
if(setmode == 0)
{
flash_addr = 0;
ad[0] = addr[0]<<4|addr[1];
ad[1] = addr[2]<<4|addr[3];
ad[2] = addr[4]<<4|addr[5];
ad[3] = addr[6]<<4|addr[7];
flash_addr = ((ad[0]<<8|ad[1])<<8|ad[2])<<8|ad[3];
STMFLASH_Write(FLASH_SAVE_ADDR,(uint16_t *)ad,4);
HAL_Delay(1000);
mode = 0;
k=0;
break;
}
else if(setmode == 1)
{
setmode = 0;
OLED_ShowString(18+6*k1,3," ",12);
}
}
if(k1 != k)
{
OLED_ShowString(18+6*k1,3," ",12);
OLED_ShowString(18+6*k,3,"|",12);
k1 = k;
}
}
}
keil工程文件:https://download.csdn.net/download/qq_45714830/85724498