【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


版权声明:本文为qq_45714830原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。