利用STM32F103最小系统做C2接口离线烧录器

2023-11-15

前言

在做BLHeli_S电调的时候需要用下载器给EFM8BB21下载程序,这可能需要买C8051下载器。下载器下载也很麻烦。这就萌生出做一个C2接口的离线下载器的想法。

离线下载器想法:

1. 支持U盘功能,可以通过USB连接到电脑然后把固件文件复制到下载器上。

2. 使用C8T6后面的64K作为文件系统的存储区,这样可以实现PC-离线下载器同步交互。

3. 通过C2接口离线下载固件到EFM8BBxx

材料准备

准备一个STM32F103C8开发板,我用的是这样的,这种我相信很多人手头都有。需要有USB接口,这很重要因为可以直接复制固件到离线下载中。

软件实现

USB+MSC+FATFS的教学可以查看我之前发布的博客

STM32F103C8用内部Flash做一个优盘(USB+MSC+FATFS)

在这上面需要添加烧录IO口配置

配置IO口,这里主要是配置LED和C2接口的时钟和数据线。C2CK我写成CLK0,C2D我写成DIO0。至于为什么接0,是用完他可以多通道烧录。但是我这里只做了单通道烧录。有兴趣可以搞搞。这里先不公开。串口使用UART1,用于调试和烧录信息打印。

Hex文件解析

主要是解析Hex文件,生成烧录工程文件。解析过程我直接贴源码并做一些必要的解析。

Hex文件的帧格式网上有很多,我这里就不再说了。但是需要知道这几个关键数据/命令起始位。

enum 
{
	HexRowLenPos  = 1,//本行长度起始位
	HexAddrH8Pos  = 3,//本行偏移地址高8位起始位
	HexAddrL8Pos  = 5,//本行偏移地址低8个起始位
	HexRowTypePos = 7,//本行类似起始位
	HexDataPos    = 9,//本行数据起始位
};

解析Hex文件生成烧录工程文件,其中工程文件 = N个固件包。

1个固件包 = 4字节本包烧录用的绝对地址 + 4字节本包数据长度 + 8字节保留 + 数据

4字节本包烧录用的绝对地址和4字节本包数据长度都是高位在前。

对于release.bin的存储结构。可以按下图理解,也就是说地址是1024整数倍将设置为1个固件包,而烧录地长度是根据在这个1024的地址里面有多少连续的数据。比如BB21的烧录地址是从0开始的,那么0-0x3FF这1024个数据就是第一个数据包,如果这个地址只有100个数据是有效的那100-1023这个范围的数据就会默认填充为0xFF。考虑到有Bootloader的情况(BootLoader程序+APP程序,中间往往都是0xFF),当一个固件1024字节都是0xFF,将会不记录这个数据包,避免进行无效的烧录。 

 

config.c源码

#ifndef _CONFIG_H_
#define _CONFIG_H_
#include "stm32f1xx_hal.h"

#define     DebugPrintf                  //Debug总输出
#define     FLASH_SIZE                  128
#define     FMC_SECTOR_SIZE             1024
#define     FLASH_PAGE_NBR              64
#define     FLASH_START_ADDR        	(0x08000000+((FLASH_SIZE-FLASH_PAGE_NBR)*1024))
#define     StorageFwStart              16                              //1个固件包中信息长度,也是固件开始位
#define     StorageFwLen                1024                            //1个固件包中固件长度
#define     StorageLen                  StorageFwStart+StorageFwLen     //1个固件包长度,一个完成的固件由多个固件包组成

void User_Main(void);
#endif

decode.c源码

#include "fatfs.h"
#include "decode.h"
#include "config.h"
#include "Programmer.h"

//#define DebugPrintfHex          //Debug输出Hex有效数据
//#define DebugPrintfSaveHex      //Debug输出保存情况
//#define DebugPrintfStorageInfo  //Debug输出保存的固件段的地址和长度
//#define DebugPrintfReadUDiskio  //Debug输出读取hex的长度和上一次剩余的长度
enum 
{
	HexRowLenPos  = 1,
	HexAddrH8Pos  = 3,
	HexAddrL8Pos  = 5,
	HexRowTypePos = 7,
	HexDataPos    = 9,
};

/*********************************************************************
-
-   函数名: uint8_t AsciiToHex(uint8_t *DataBuf)
-   功能  : 将DataBuf中的无符号十六进制字符串转为十进制数
-           如DataBuf="00 1A",则返回0x1a
-   输入  : 需要转换的首字节地址
-   返回  : 转换后的数据
-
**********************************************************************/
uint8_t AsciiToHex(uint8_t *DataBuf)
{
    int value=0;
    uint8_t i;
    for(i=0;i<2;i++)
    {
        if(DataBuf[i]<='9'&&DataBuf[i]>='0')
        {
            value=value*16+(int)(DataBuf[i]-'0'); //'0'
        }
        else if(DataBuf[i]<='f'&&DataBuf[i]>='a')
        {
            value=value*16+(int)(DataBuf[i]-'a'+10);
        }
        else
        {
            value=value*16+(int)(DataBuf[i]-'A'+10);
        }
    }
    return value;//返回转换后的数值

}
/*********************************************************************
-
-   函数名: FRESULT SaveHextoProjectFile(uint32_t FlashStartAddr,
            uint32_t FlashDataLen,uint8_t *HexBuf,const TCHAR* path)
-   功能  : 将解析后的HEX文件内容和偏移地址写入工程文件、
-           格式:4个字节偏移地址+4个字节数据长度+24字节保留+1024字节Hex内容
-   输入  : FlashStartAddr:HEX内容偏移地址,4个字节,高位在前
-           FlashDataLen:数据长度,4个字节,高位在前
-           *HexBuf:Hex文件地址,1024字节
-           * path:工程文件绝对地址
-   返回  : 文件操作结果
-
**********************************************************************/
FRESULT SaveHextoProjectFile(uint32_t FlashStartAddr,uint32_t FlashDataLen,uint8_t *HexBuf,const TCHAR* path)
{
	static FIL  Savefsrc;
	FRESULT     Saveres;
    UINT        Savebr;
	uint8_t FlashMessage[StorageFwStart];		//4个字节偏移地址+4个字节数据长度+8字节保留
	
	FlashMessage[0] = FlashStartAddr>>24 & 0xff;
	FlashMessage[1] = FlashStartAddr>>16 & 0xff;
	FlashMessage[2] = FlashStartAddr>>8 & 0xff;
	FlashMessage[3] = FlashStartAddr>>0 & 0xff;
	
	FlashMessage[4] = FlashDataLen>>24 & 0xff;
	FlashMessage[5] = FlashDataLen>>16 & 0xff;
	FlashMessage[6] = FlashDataLen>>8 & 0xff;
	FlashMessage[7] = FlashDataLen>>0 & 0xff;
	memset(&FlashMessage[8],0xff,StorageFwStart-8);//默认为0xff
	Saveres = f_open( &Savefsrc , path, FA_WRITE);
	if ( Saveres == FR_OK )
	{
#ifdef DebugPrintf
#ifdef DebugPrintfSaveHex
		printf("f_open project OK \n\n");
#endif
#endif
		Saveres = f_lseek(&Savefsrc,f_size(&Savefsrc));
		if ( Saveres == FR_OK )
		{
			Saveres = f_write(&Savefsrc, FlashMessage, sizeof(FlashMessage), &Savebr);
			if ( Saveres == FR_OK )
			{
#ifdef DebugPrintf
#ifdef DebugPrintfSaveHex
				printf("f_write FlashMessage OK \n\n");
#endif
#endif
			}
			Saveres = f_write(&Savefsrc, HexBuf, 1024, &Savebr);
#ifdef DebugPrintf
#ifdef DebugPrintfStorageInfo
            printf("------- FlashAddr %.8x FlashLen = %d  ------\n",FlashStartAddr,FlashDataLen);
#endif
#ifdef DebugPrintfHex
            for(int ii=0;ii<1024;ii++)
            {
                if(ii%16 == 0)
                    printf("\n");
                printf(" %.2x ",HexBuf[ii]);	
            }
            printf("\n");
            printf("\n");
#endif
#endif
			if ( Saveres == FR_OK )
			{
#ifdef DebugPrintf
#ifdef DebugPrintfSaveHex
				printf("f_write FlashData OK \n\n");
#endif
#endif
			}
		}
	}
	f_close(&Savefsrc);
	return Saveres;
}
/*********************************************************************
-
-   函数名: uint8_t SaveProjectFile(FlashType *ProjectFile)
-   功能  : 保存工程文件在SD卡
-   输入  : *ProjectFile:烧录器工程项目结构体指针
-   返回  : 保存状态,0:保存失败,1:保存成功
-
**********************************************************************/
FRESULT     Saveres;
uint8_t SaveProjectFile(FlashType *ProjectFile)
{
    const char ProjectAddr[]={"0:/FW/release.bin"};
    const char HexAddr[]={"0:/FW/release.hex"};
    
    uint32_t CurrHex_i=0;   //循环变量,每一次读取256字节,然后解析出来
    uint8_t StartAddrFlag=0;//开始地址标志位,遇到新的保存地址,会标志为1,如0x400,0x800,如0x10000等,
	uint32_t sizelen;		//每次读取hex文件的长度
	uint8_t RowType=0;		//当前行记录的类型
	uint8_t ActiveLen;		//每一行Hex文件的有效长度,在每一行的第1-2字节
    uint8_t LastLen=0;      //每次读取256字节解析后不后一行的数据,会叠加在下一次读数的前面
	static uint8_t ReadData[310];	//每次读取的hex数据的保存缓冲区,需要> 读取字节数。
    uint32_t ReadHexLen=0;          //读取指针,用于记录上一次读取的地点
	uint32_t NewRowFlashAddr=0;     //新解析行的偏移地址
    uint32_t LastRowFlashAddr=0;    //上一次解析行的偏移地址
	uint32_t BasisFlashAddr=0;		//基础地址
	uint32_t FlashAddr=0;		    //要烧录的偏移地址
	static uint8_t FlashData[1024]; //要烧录到目标芯片的flash数据,这些数据都是去除地址的,起始地址是FlashAddr
	uint16_t FlashLen=0;	        //待烧录的长度,一般1024字节烧录一次,或者遇到跨度大的地址会烧录一次
    
	Saveres = f_unlink(ProjectAddr);
    Saveres = f_open( &USERFile ,ProjectAddr, FA_CREATE_NEW  | FA_WRITE);
    f_lseek(&USERFile,0);
    if ( Saveres == FR_OK )
    {  
        f_close(&USERFile);//关闭工程文件
		memset(FlashData,0xff,1024);//默认为0xff
		while(1)
		{
			Saveres = f_open( &USERFile , HexAddr, FA_READ);//打开HEX文件
			if ( Saveres == FR_OK )
			{
				f_lseek(&USERFile,ReadHexLen);                      //设置读取指针指向固件地址
				f_read(&USERFile,&ReadData[LastLen],256,&sizelen);  //读取HEX文件的256字节。
#ifdef DebugPrintf
                if(sizelen != 0)
                {
                    printf("Decode progress = %.2f%c\n",100.0*USERFile.fptr/USERFile.fsize,'%');
                }
#ifdef DebugPrintfReadUDiskio
				printf("sizelen = %d LastLen = %d\n",sizelen,LastLen);
#endif
#endif
				ReadHexLen = USERFile.fptr;							//记录本次读取的位置,这也是下一次要读取的开始位置
				f_close(&USERFile);									//读取完毕关闭文件
                
				sizelen += LastLen;     							//加上上一次解析的残余数据
				LastLen = 0;										//把上一次数据转移后头部后,LastLen清零
                
                //开始解析Hex数据
				for(CurrHex_i=0;CurrHex_i<sizelen;CurrHex_i++)  	//解析上一次解析的残余数据+本次读取的数据
				{
					if(ReadData[CurrHex_i] == 0x3a)					//检测Hex文件的行头
					{
						ActiveLen = AsciiToHex(&ReadData[CurrHex_i+HexRowLenPos]);//获取本行有效长度
						if((sizelen-CurrHex_i) >= (11+ActiveLen*2))//检测剩余未解析的数据是否大于或者等于本行的整帧长度
						{
							RowType = AsciiToHex(&ReadData[CurrHex_i+HexRowTypePos]);//获取本行类型
							//处理行类型
							switch(RowType)
							{
								case 0: //flash数据行
										//获取本行地址
									NewRowFlashAddr = (AsciiToHex(&ReadData[CurrHex_i+HexAddrH8Pos]) << 8) | AsciiToHex(&ReadData[CurrHex_i+HexAddrL8Pos]);
                                    if(StartAddrFlag == 0)
                                    {
                                        FlashAddr = NewRowFlashAddr;//记录为烧录偏移地址
                                        StartAddrFlag = 1;
                                    }
                                    //新的行地址比上一行的地址多48字节,说明跨扇区存储了,多见包含BootLoader的hex文件
                                    //需要把之前解析的数据先存储起来
									if((NewRowFlashAddr - LastRowFlashAddr>48) && FlashLen != 0)
									{
                                        FlashAddr = (FlashAddr/1024)*1024;//或者整K地址。有的FlashAddr不是规则是1024整数倍,这里转为1024整数倍
										SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
										FlashLen = 0;   //标志需要烧录的数据长度为0
										FlashAddr = NewRowFlashAddr;//记录新的烧录偏移地址
										memset(FlashData,0xff,1024);//默认为0xff
									}
                                    //开始解析这一行数据
									for(uint8_t k=0;k<ActiveLen;k++)
									{
                                        //如果数据够1024字节,则开始存储一次
										if(FlashLen == 1024)
										{
                                            FlashAddr = (FlashAddr/1024)*1024;
											SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
											FlashLen = 0;   //标志需要烧录的数据长度为0
											memset(FlashData,0xff,1024);//默认为0xff
											FlashAddr = NewRowFlashAddr + k;//记录新的烧录地址
										}
										//获取本行长度
										FlashData[(NewRowFlashAddr + k)%1024] = AsciiToHex(&ReadData[CurrHex_i+HexDataPos + k * 2]);
										FlashLen = (NewRowFlashAddr + k)%1024 +1;//更新需要烧录的长度
									}
									CurrHex_i += (ActiveLen*2);//更新解析的长度
									LastRowFlashAddr = NewRowFlashAddr + ActiveLen;//把这次的最后一个字节的地址记录为最后一行地址
								break;
								case 1:;break;// 01 HEX文件结束记录
								case 2:;break;// 02 扩展段地址记录
								case 3:;break;// 03 开始段地址记录
								case 4:		  // 04 扩展线性地址记录,用于扩展地址
                                    //遇到扩展行,需要把之前解析的数据先存储起来
                                    if(FlashLen != 0)
                                    {
                                        FlashAddr = (FlashAddr/1024)*1024;
                                        SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
                                        FlashLen = 0;   //标志需要烧录的数据长度为0
                                        memset(FlashData,0xff,1024);//默认为0xff
                                        StartAddrFlag = 0;//记录新的烧录偏移地址
                                        FlashAddr = 0;
                                    }
									//获取基地址
									BasisFlashAddr = (AsciiToHex(&ReadData[CurrHex_i+HexDataPos]) << 8 | AsciiToHex(&ReadData[CurrHex_i + HexDataPos+2]))<<16 & 0xffff0000;
									LastRowFlashAddr = 0;
								break;
								case 5:;break;// 05 开始线性地址记录
							}
						}
						else
						{
							//没有有效的数据帧长度,把余下的数据复制到数组最前面
							for(LastLen = 0;CurrHex_i<sizelen;CurrHex_i++)
							{
								ReadData[LastLen++] = ReadData[CurrHex_i];
							}
						}
					}
				}
				if((sizelen-LastLen) == 0 && RowType == 0x01)//检测是否为结束行
				{
					FlashAddr = (FlashAddr/1024)*1024;
					SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
					FlashLen = 0;
					FlashAddr = NewRowFlashAddr;
					memset(FlashData,0xff,1024);
					break;
				}
			}
		}
        return 1;//保存成功
    }
    else if ( Saveres == FR_EXIST )
    {
		f_close(&USERFile);
        return 2;//文件已存在,如果想新建,需要删除
    }
    else
    {
		f_close(&USERFile);
        return 0;//保存失败
    }
}

BasisFlashAddr是基础地址,一般来说Hex文件的开头都会用扩展线性地址命令(0x04)给定基础地址。FlashAdd烧录偏移地址,所以BasisFlashAddr | FlashAdd就是烧录用的绝对地址。

FlashAddr = (FlashAddr/1024)*1024。这个是把烧录用的绝对地址归到1024的整数倍。

SaveHextoProjectFile函数是将固件包写到存储区中,在解析中有4种情况调用这个函数。

第一种情况,在相邻2行中,地址超过48字节时则需要把已解析的数据保存到工程文件中。这里主要是为了解决BootLoader+App的Hex文件,往往APP和BootLoader之间的中会预留一些flash空间。这部分空间是不需要写的。这也是为什么需要解析hex文件,而不能直接使用软件进行Hex转bin的原因。

if((NewRowFlashAddr - LastRowFlashAddr>48) && FlashLen != 0)
{
    FlashAddr = (FlashAddr/1024)*1024;//或者整K地址。有的FlashAddr不是规则是1024整数倍,这里转为1024整数倍
    SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
    FlashLen = 0;   //标志需要烧录的数据长度为0
    FlashAddr = NewRowFlashAddr;//记录新的烧录偏移地址
    memset(FlashData,0xff,1024);//默认为0xff
}

第二种情况,当解析出来的数据等于1024字节时,需要把已解析的数据保存到工程文件中,这点应该很容易理解。

//如果数据够1024字节,则开始存储一次
if(FlashLen == 1024)
{
    FlashAddr = (FlashAddr/1024)*1024;
    SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
    FlashLen = 0;   //标志需要烧录的数据长度为0
    memset(FlashData,0xff,1024);//默认为0xff
    FlashAddr = NewRowFlashAddr + k;//记录新的烧录地址
}

第三种情况,遇到扩展线性地址命令(0x04),需要把之前解析的数据先存储起来,并且记录新的基地址。什么是扩展线性地址命令?简单的说就是hex行的偏移地址已经到达0xFFFF了,下一步是0x0000了,但是在此之前需要使用扩展线性地址命令告知我下一次偏移地址是在哪一个地址进行偏移。

比如 :020000040001F9,这个就是扩展线性地址命令,基地址就是0x0001<<16,也就是0x10000。

case 4:		  // 04 扩展线性地址记录,用于扩展地址
    //遇到扩展行,需要把之前解析的数据先存储起来
    if(FlashLen != 0)
    {
        FlashAddr = (FlashAddr/1024)*1024;
        SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
        FlashLen = 0;   //标志需要烧录的数据长度为0
        memset(FlashData,0xff,1024);//默认为0xff
        StartAddrFlag = 0;//记录新的烧录偏移地址
        FlashAddr = 0;
    }
    //获取基地址
    BasisFlashAddr = (AsciiToHex(&ReadData[CurrHex_i+HexDataPos]) << 8 | AsciiToHex(&ReadData[CurrHex_i + HexDataPos+2]))<<16 & 0xffff0000;
    LastRowFlashAddr = 0;
break;

第四种情况,当遇到hex结束行和hex文件没有结束行但是文件读取完,需要把之前解析的数据先存储起来。hex结束行::00000001FF

if((sizelen-LastLen) == 0 && RowType == 0x01)//检测是否为结束行
{
    FlashAddr = (FlashAddr/1024)*1024;
    SaveHextoProjectFile(BasisFlashAddr | FlashAddr,FlashLen,FlashData,ProjectAddr);//将转换后的HEX数据写进工程文件
    FlashLen = 0;
    FlashAddr = NewRowFlashAddr;
    memset(FlashData,0xff,1024);
    break;
}

编程器的实现

这里是最简单,也就是IO口的初始化和输出输入函数实现。ProgrammerWork就是编程器工作入口,因为现在编程器只有一种编程接口,所以没有写switch选择不同的编程接口。

Programmer.h源码。编程结构体也在这里。

在代码中已经限制开启通道为1。直接改这里的EnableChNumMax是不行的~~~

#ifndef __programmer_H__
#define __programmer_H__
#include "stm32f1xx_hal.h"
#include "main.h"
#include "stdio.h"
#include "string.h"

#define EnableChNumMax      1         //定义编程器通道

typedef struct
{
    uint16_t FlashOk[EnableChNumMax]; //烧录成功数量统计
    uint8_t Status[EnableChNumMax];   //烧录完成标志位
	uint8_t Start;                    //烧录器编程

    uint8_t FW_Buf[1040];             //固件buff
    
}FlashType;

extern FlashType FlashTarget;
extern uint8_t EnableChNum;

void T_CLK_0(void);
void T_CLK_1(void);
void T_DAT_0(void);
void T_DAT_1(void);
void ICP_DAT_OUT_ENABLE(void);
void ICP_DAT_OUT_DISABLE(void);
GPIO_PinState ICP_DAT_Read(uint8_t Ch);

void ProgrammerInit(void);
void ProgrammerIOInit(void);
void ProgrammerWork(FlashType *Target);
void SetTargetPower(uint8_t VoltageType);


#endif

Programmer.c源码

#include "c2_flash.h"
#include "Programmer.h"

uint8_t EnableChNum = EnableChNumMax;
FlashType FlashTarget;

/*********************************************************************
-
-   函数名: void ProgrammerIOInit(void)
-   功能  : 目标编程IO口初始化
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void ProgrammerIOInit(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = CLK0_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(CLK0_GPIO_Port, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = DIO0_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DIO0_GPIO_Port, &GPIO_InitStruct);
}

/*********************************************************************
-
-   函数名: void T_CLK_0(void)
-   功能  : 设置CLK输出为0
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void T_CLK_0(void)
{
    CLK0_GPIO_Port->BSRR = (uint32_t)CLK0_Pin << 16u;
}

/*********************************************************************
-
-   函数名: void T_CLK_1(void)
-   功能  : 设置CLK输出为1
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void T_CLK_1(void)
{
    CLK0_GPIO_Port->BSRR = CLK0_Pin;
}

/*********************************************************************
-
-   函数名: void T_DAT_0(void)
-   功能  : 设置DAT输出为0
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void T_DAT_0(void)
{
    DIO0_GPIO_Port->BSRR = (uint32_t)DIO0_Pin << 16u;
}

/*********************************************************************
-
-   函数名: void T_DAT_1(void)
-   功能  : 设置DAT输出为1
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void T_DAT_1(void)
{
    DIO0_GPIO_Port->BSRR = DIO0_Pin;
}

/*********************************************************************
-
-   函数名: uint8_t ICP_DAT_Read(uint8_t Ch)
-   功能  : 读取DAT线的数据
-   输入  : Ch:需要读取的线号,范围0-7
-   返回  : DAT线的数据
-
**********************************************************************/
GPIO_PinState ICP_DAT_Read(uint8_t Ch)
{
    return ((DIO0_GPIO_Port->IDR & DIO0_Pin) == (uint32_t)GPIO_PIN_RESET)?GPIO_PIN_RESET:GPIO_PIN_SET;
}

/*********************************************************************
-
-   函数名: void ICP_DAT_OUT_ENABLE(void)
-   功能  : 设置DAT线为输出模式
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void ICP_DAT_OUT_ENABLE(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = DIO0_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DIO0_GPIO_Port, &GPIO_InitStruct);
}

/*********************************************************************
-
-   函数名: void ICP_DAT_OUT_DISABLE(void)
-   功能  : 设置DAT线为输入模式
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void ICP_DAT_OUT_DISABLE(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = DIO0_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DIO0_GPIO_Port, &GPIO_InitStruct);
}

/*********************************************************************
-
-   函数名: void ProgrammerWork(FlashType *Target)
-   功能  : 编程器根据厂家和对应的型号加载文件进行烧录工作
-   输入  : Target:烧写信息结构体地址
-   返回  : 无
-
**********************************************************************/
void ProgrammerWork(FlashType *Target)
{
    C2_Programmer(Target);
}

/*********************************************************************
-
-   函数名: void ProgrammerInit(void)
-   功能  : 编程器初始化
-   输入  : 无
-   返回  : 无
-
**********************************************************************/
void ProgrammerInit(void)
{
	FlashTarget.Start = 0;
	memset(FlashTarget.FW_Buf,0x00,sizeof(FlashTarget.FW_Buf));
    ProgrammerIOInit();
}

C2接口实现

主要是实现C2烧录接口,源码太长,我就不贴上来了。C2编程参考资料:https://axxonshare.s3.amazonaws.com/an127_original_working.pdf

需要注意的是,当要写入/读取256字节的时候,写入/读取长度需要设置为0。 这样也就限制了C2编程一次最多写入/读取256字节,如果要写入/读取1024字节,则需要分4次进行。

开始离线烧录

写了一个User_Main,方便使用,

#include "main.h"
#include "fatfs.h"
#include "decode.h"
#include "Programmer.h"

/****************************************************************************************
-
-   函数名: void LedBlink(uint8_t Num)
-   功能  : LED闪烁
-   输入  : Num:    LED闪烁次数
-   返回  : 无
-
****************************************************************************************/
void LedBlink(uint8_t Num)
{
    for(uint8_t i=0;i<Num;i++)
    {
        LED0_GPIO_Port->BSRR = (uint32_t)LED0_Pin << 16u;
        HAL_Delay(100);
        LED0_GPIO_Port->BSRR = LED0_Pin;
        HAL_Delay(200);
    }
    HAL_Delay(500);
}

/****************************************************************************************
-
-   函数名: void User_Main(void)
-   功能  : 用户main循环
-   输入  : 无
-   返回  : 无
-
****************************************************************************************/
void User_Main(void)
{
    char path[4]= {"0:"};
    uint8_t BurnFlag=0;//烧录标志位
    uint32_t MainLoopTime=0;
    FRESULT FATFS_Status;
    
    FATFS_Status = f_mount(&USERFatFS, path, 1);//挂载存储器
    f_mkdir("0:/FW");                           //创建FW文件夹,用于存放Hex文件
    //打开工程文件,这个版本的FATFS不给创建.project结尾的文件似的,只能用bin文件代替
    FATFS_Status = f_open(&USERFile ,"0:/FW/release.bin",FA_READ);
    f_close(&USERFile);//关闭文件
    //开始检测是否存在release.bin文件,不存在则创建
    if(FATFS_Status == FR_OK)
    {
        BurnFlag =1;//工程文件存在,标记可以烧录,LED亮
        LED0_GPIO_Port->BSRR = (uint32_t)LED0_Pin << 16u;
        
    }
    else//工程文件不存在,开始解析
    {
        LED0_GPIO_Port->BSRR = LED0_Pin;//LED灭
        FATFS_Status = f_open(&USERFile ,"0:/FW/release.hex",FA_READ);//打开release.hex
        f_close(&USERFile);//关闭文件
        if(FATFS_Status == FR_OK)
        {
            //文件存在,开始解析
            printf("Start decode hex\n");
            if(SaveProjectFile(&FlashTarget) == 1)//判断解析结果。
            {
                BurnFlag = 1;//解析成功,标记可以烧录,LED亮
            }
            else
            {
                BurnFlag = 0;//解析失败,标记不能烧录
            }
        }
    }
    ProgrammerInit();               //编程器初始化
    MainLoopTime = HAL_GetTick();   //获取系统当前时间
    while (BurnFlag)                //循环自动烧录
    {
        //每1s钟更新一次状态
        if(HAL_GetTick() - MainLoopTime >= 1000)
        {
            MainLoopTime = HAL_GetTick();
            if(FlashTarget.Status[0] == 1) //烧录成功,请更换新的芯片
            {
                LedBlink(3);//  LED三闪
                printf("Programmer ok,Please connect IC\n");
            }
            else//当前没有连接芯片,请连接芯片
            {
                LedBlink(1);//LED单闪
                printf("Please connect IC,Auto mode programming\n");
            }
        }
        //开始烧录,自动检测芯片自动烧录
        ProgrammerWork(&FlashTarget);
    }
    while(1)
    {
        if(HAL_GetTick() - MainLoopTime >= 1000)
        {
            printf("not found release.hex  !!!!\n");
            MainLoopTime = HAL_GetTick();
            HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
        }
    }
}


测试

在config.h打开

#define     DebugPrintf                  //Debug总输出

烧录程序,然后连接串口助手,波特率115200。

没有放置release.hex文件运行时,串口信息

放入Hex文件,因为没有按键做选择,所以放入的hex文件一定要重命名为release.hex。

 如果想看解析hex文件过程,可以在decode.c文件打开以下宏

#define DebugPrintfHex          //Debug输出Hex有效数据
#define DebugPrintfSaveHex      //Debug输出保存情况
#define DebugPrintfStorageInfo  //Debug输出保存的固件段的地址和长度
#define DebugPrintfReadUDiskio  //Debug输出读取hex的长度和上一次剩余的长度

按复位键运行。

 

 

开始烧录

 

参考代码 

有积分支持的可以从这里下载例程:利用STM32F103最小系统做C2接口离线烧录器烧录EFM8BBXX

没有积分可以从:传送门下载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

利用STM32F103最小系统做C2接口离线烧录器 的相关文章

  • HAL_Delay() 陷入无限循环

    我被 HAL Delay 函数困住了 当我调用此函数 HAL Delay 时 控制陷入无限循环 在寻找问题的过程中 我发现了这个 http www openstm32 org forumthread2145 threadId2146 htt
  • ARM 汇编分支到寄存器或内存内部的地址

    我想知道在 ARM 汇编中我可以使用哪条指令分支到存储在某个内存地址中的地址或标签 例如 我们可以使用B LABEL来跳转到LABEL 但现在目的地只能在运行时知道 并且它存储在某个已知的内存位置 是否有类似 B 地址 的东西 Thanks
  • 使用 ARM NEON 内在函数添加 alpha 和排列

    我正在开发一个 iOS 应用程序 需要相当快地将图像从 RGB gt BGRA 转换 如果可能的话 我想使用 NEON 内在函数 有没有比简单分配组件更快的方法 void neonPermuteRGBtoBGRA unsigned char
  • ARM Linux 如何模拟 PTE 的脏位、访问位和文件位?

    As per pgtable 2 level h https git kernel org cgit linux kernel git torvalds linux git tree arch arm include asm pgtable
  • DSP 库 - RFFT - 奇怪的结果

    最近我一直在尝试在我的STM32F4 Discovery评估板上进行FFT计算 然后将其发送到PC 我已经调查了我的问题 我认为我对制造商提供的 FFT 函数做错了 我正在使用 CMSIS DSP 库 现在我一直在用代码生成样本 如果工作正
  • 源和目标具有不同的 EABI 版本

    我正在尝试使用 ARM 工具链编译 so 文件 但是我不断收到这个错误 错误 源对象的 EABI 版本为 0 但目标对象的 EABI 版本为 5 我无法更改工具链中的任何内容 因为我必须使用给定的工具链 我以前从未见过这个错误 我使用了这个
  • ARM + gcc:不要使用一大块 .rodata 部分

    我想使用 gcc 编译一个程序 并针对 ARM 处理器进行链接时间优化 当我在没有 LTO 的情况下编译时 系统会被编译 当我启用 LTO 时 使用 flto 我收到以下汇编错误 错误 无效的文字常量 池需要更近 环顾网络 我发现这与我系统
  • ARM 调用约定是否允许函数不将 LR 存储到堆栈中?

    正如标题所示 我在理解 ARM 架构的调用约定时遇到问题 特别是 我仍然很难知道当你调用子程序时 LR 寄存器会发生什么 我认为 当您进入子程序时 处理 LR 寄存器的最明显 最安全的方法是将其存储到堆栈中 但该行为没有出现在文档中 因此我
  • 如何使用 Neon SIMD 将无符号字符转换为有符号整数

    如何转换变量的数据类型uint8 t to int32 t使用霓虹灯 我找不到执行此操作的任何内在因素 假设您想要将 16 x 8 位整数的向量转换为 4 个 4 x 32 位整数的向量 您可以通过首先解压缩为 16 位 然后再次解压缩为
  • 了解 ctags 文件格式

    我使用 Exhuberant ctags 来索引我的 c 项目中的所有标签 c project 是 Cortex M7 微控制器的嵌入式软件 结果是一个标签文件 我正在尝试阅读该文件并理解所写的内容 根据我找到的 ctags 和 Exhub
  • 如何设置 CMake 与 clang 交叉编译 Windows 上的 ARM 嵌入式系统?

    我正在尝试生成 Ninja makefile 以使用 Clang 为 ARM Cortex A5 CPU 交叉编译 C 项目 我为 CMake 创建了一个工具链文件 但似乎存在错误或缺少一些我无法找到的东西 当使用下面的工具链文件调用 CM
  • 如何模拟ARM处理器运行环境并加载Linux内核模块?

    我尝试加载我的vmlinux into gdb并使用 ARM 内核模拟器 但我不明白为什么我会得到Undefined target command sim 这是外壳输出 arm eabi gdb vmlinux GNU gdb GDB 7
  • ARM Chromebook 上的 Android 开发环境?

    我尝试了多次安装和使用安卓工作室 https developer android com studio index html on an ARM Chromebook C100P https archlinuxarm org platfor
  • 设备树不匹配:.probe 从未被调用

    我无法理解设备树的工作原理 或者具体来说为什么该驱动程序无法初始化 这是在 android 版本 3 10 的 rockchip 供应商内核中 驱动程序 看门狗 rk29 wdt c 为了可读性而减少 static const struct
  • 交叉编译armv5,但它创建v7二进制文件

    我设法为arm926ej s创建了一个目标文件我在 qemu 上使用 Debian Arm arm linux gnueabi gcc 4 4 static O c mcpu arm926ej s hello c o hello root
  • 上下文切换到安全模式(arm trustzone)的成本是多少

    我试图了解在arm中可信 安全 和非安全模式之间来回切换的成本 从非安全世界转移到安全世界时到底需要发生什么 我知道需要设置 ns 位 基于某些特殊指令 需要刷新和更新页表 刷新和更新处理器缓存 还有什么需要发生的吗 处理器缓存 它们是分段
  • GCC:如何在 MCU 上完全禁用堆使用?

    我有一个在基于 ARM Cortex M 的 MCU 上运行并用 C 和 C 编写的应用程序 我用gcc and g 编译它并希望完全禁用任何堆使用 在 MCU 启动文件中 堆大小已设置为 0 除此之外 我还想禁止代码中意外使用堆 换句话说
  • ARM 的内核 Oops 页面错误错误代码

    Oops 之后的错误代码给出了有关 ARM EX 中的恐慌的信息 Oops 17 1 PREEMPT SMP在这种情况下 17 给出了信息 在 x86 中它代表 bit 0 0 no page found 1 protection faul
  • 错误:-march= 开关的值错误

    我写了一个Makefile 但无法让它工作 我有一个选项应该选择编译到哪个处理器 然而 当我跑步时make从命令行它说 tandex tandex P 6860FX emulators nintendo sdks 3DS SDK HomeB
  • 你好世界,裸机 Beagleboard

    我正在尝试在我的 Beagleboard xm rev 上运行 hello world 类型的程序 C 通过调用 Cputs功能来自装配 到目前为止 我一直使用这个作为参考 http wiki osdev org ARM Beagleboa

随机推荐

  • lattice 包的用法

    1 library lattice 加载包 d lt data frame x seq 0 14 y seq 1 15 z rep c a b c times 5 xyplot y x data d xy的散点图 xyplot y x z
  • 关于贷后的8个专业名词解析

    一 DPD day past due DPD的意思是逾期天数 指的是逾期用户在最早违期日期至目前日期的时间间隔 贷后催收时需要计算用户的逾期天数 并根据逾期的情况采用不同的催收手段 二 Mn M1 Mn的意思是逾期的期数 比如M1表示逾期一
  • 《Autodesk Revit二次开发基础教程》书籍终于上架了

    由Autodesk中国研究院Revit开发团队的几位同事一起编撰的 Autodesk Revit二次开发基础教程 于今天在天猫同济大学出版社旗舰店正式上架 购买链接在这里 https detail tmall com item htm u
  • TensorFlow训练模型的过程中打开tensorboard

    在训练的过程中 想通过tensorboard实时观察训练损失和验证集准确率 一直出错 打开tensorboard后在浏览器查看 然后训练就停止了 提示信息如下 File D ProgramData PycharmProjects tf le
  • Spring源码分析(一):Spring底层核心原理解析

    本节只讲结论 不做验证 后面会专门拉代码讲解验证 Spring的核心是IOC和AOP 大概有这么几个核心知识点 Bean的生命周期底层原理 依赖注入底层原理 初始化底层原理 推断构造方法底层原理 AOP底层原理 Spring事务底层原理 S
  • 大陆医生谈收入

    官网 ZY123 com 中医123 本人今年45岁 84年大本 87年研究生毕业 很正规的医学院校毕业 随后分到中部一家大型医院干了4年临床 91年先到欧洲的实验室混了几年 后来到临床上干了2年后回来了 也算 海龟 吧 先在广东的两家医院
  • 【基础教学】UiBot的下载、安装与使用

    鉴于很多小伙伴 可能刚刚关注UiBot 对这个平台还不是很了解 我们准备系统的讲解UiBot的相关操作 方便您对UiBot的认识与使用 目录 1 UiBot软件简介 2 UiBot能为您做什么 3 系统环境及配置要求 4 下载与安装 5 注
  • 堆排序(几个重点)

    https blog csdn net touch 2011 article details 6767673 几个重点 大小顶堆虽然逻辑形式是完全二叉树 但实际是以数组的形式存储 最后一个非叶子节点 最后一个有孩子的节点 的位置是 n 2
  • linux 获取进程输出流,linux后台进程与标准输出

    一 遇到问题 笔者在测试阶段 把服务拉到服务器上 部署之后 启动服务 但是没有启动成功 也没有报错信息 二 先理解一些概念 1 黑洞 dev null 这个就是黑洞 这是一个文件 这个文件是一个 只写 的文件 从里面读不出信息 为什么要使用
  • Elasticsearch 查询和聚合查询:基本语法和统计数量

    摘要 Elasticsearch是一个强大的分布式搜索和分析引擎 提供了丰富的查询和聚合功能 本文将介绍Elasticsearch的基本查询语法 包括预发查询和聚合查询 以及如何使用聚合功能统计数量 引言 Elasticsearch是一种开
  • C++实现查找字符串中的数字,并输出

    例如输入 dsafjoi3425sfsdjl5435asfkl 3400输出为 3425 5434 3400 include
  • Stata如何快速安装外部命令

    Stata如何快速安装外部命令 来自微信公众号 TidyFridy 1 之前在安装Stata外部命令时 访问外网速度很慢 安装SSC外部命令没有成功 出现过stacktrace not available 的提示 解决办法 Stata的安装
  • 数据结构课程设计-五子棋

    1 题目描述 五子棋的游戏规则是两人对弈 使用黑白两色棋子 轮流下在棋盘上 当一方先在横线 竖线 斜对角线方向形成五子连线 则取得胜利 2 设计要求 在内存中 设计数据结构存储游戏需要的数据 满足五子棋游戏的游戏规则 实现简单的人机对战功能
  • 条款28.理解引用折叠

    理解引用折叠 以下面这个模板为例 template
  • 华为校招机试题-去除多余空格-2023年

    题目描述 去除文本多余空格 但不去除配对单引号之间的多余空格 给出关键词的起始和结束下标 去除多余空格后刷新关键词的起始和结束下标 条件约束 1 不考虑关键词起始和结束位置为空格的场景 2 单词的的开始和结束下标保证涵盖一个完整的单词 即一
  • Pycharm的python console报错“Error running console” “错误:无法启动进程,指定的不是一个工作目录”

    1 依次选择 File 文件 gt setting 设置 gt Build Execution Deployment 构建 执行部署 gt Console gt Python Console找到python console设置 2 设置 1
  • 【简单】HJ14 字符串排序(Python、C++两种语言实现)

    题目 给定 n 个字符串 请对 n 个字符串按照字典序排列 数据范围 1 n 1000 1 n 1000 字符串长度满足 1 len 100 1 len 100 输入描述 输入第一行为一个正整数n 1 n 1000 下面n行为n个字符串 字
  • VsCode连接服务器并编辑服务器端代码

    打开 VsCode 安装 Remote SSH 插件 安装完成后 页面左侧插件下方会出现一个形如电脑的图标 页面右下角也会出现绿色的远程控制的标志 新建SSH连接 选择 远程资源管理器 点击 设置 齿轮状图标 选择配置文件并打开 按照实际信
  • 神经网络五:常用的激活函数

    本文就现在神经网络中主要的几个激活函数进行分析和讲解 比较几个激活函数的优缺点 在此特声明 本文的内容是来自 CS231n课程笔记翻译 神经网络笔记1 上 智能单元 知乎专栏 因本人有时会查阅这些相关的知识点 一直翻文档比较麻烦 特从文档中
  • 利用STM32F103最小系统做C2接口离线烧录器

    前言 在做BLHeli S电调的时候需要用下载器给EFM8BB21下载程序 这可能需要买C8051下载器 下载器下载也很麻烦 这就萌生出做一个C2接口的离线下载器的想法 离线下载器想法 1 支持U盘功能 可以通过USB连接到电脑然后把固件文