前言
在做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
没有积分可以从:传送门下载