通过Lua小程序,我们可以方便方便的在线方式做SPI接口方式固件烧录,也可以离线方式运行Lua小程序做烧录。
本次是说明是采用H7-TOOL的SPI接口连接我们V7板子做的操作说明。
【协议说明】
1、发送固件大小:符号‘*’ 来同步,然后发送固件大小,板子收到后,回复0x30表示擦除相应扇区大小成功,回复0x60表示擦除失败。
2、发送固件数据:符号‘$’ 来同步,然后发送固件数据,每次64字节大小,板子收到后,回复0x30表示数据编程成功,回复0x60表示擦除失败。如此反复,一直到发送完毕。
3、发送结束命令:符号‘#’ 表示传输结束,目标板可以加载到APP运行了。
我们这里SPI时钟设置为12.5MHz,空闲时SCK为0,上升沿采集数据。
【硬件接线】
H7-TOOL通过SPI接到V7板子的SPI接口上
[C] 纯文本查看 复制代码 _________________________ _____________________________
| ______________| |______________________ |
| | SPI1 | | SPI1 | |
| | | | | |
| | CLK(D5) |______________________|(PB3)CLK | |
| | | | | |
| | MISO(D3) |______________________|(PB4)MISO | |
| | | | | |
| | MOSI(D2) |______________________|(PB5)MOSI | |
| | | | | |
| |______________| |______________________| |
| __ | | |
| |__| | | |
| USER | | |
| GND|______________________|GND |
| | | |
|_H7-TOOL Master _________| |_STM32H7 Slave ______________|
【准备工作】
当前上位机还没有做专门的SPI接口脱机烧录一键下载界面,需要手动将Lua文件和app固件存到TOOL的eMMC
1、H7-TOOL进入虚拟U盘
上电首界面长按S键 -> 系统设置 -> USB eMMC磁盘, 进入eMMC模拟U盘后,在如下路径新建文件夹串口脱机烧录
将如下两个文件存到新建的文件夹下
app.bin (50.03 KB)
spibootloader.lua (3.8 KB)
2、将目标板程序下载到V7开发板
硬件SPI从机实现。
基于V7的SPI接口脱机烧录目标板程序.7z (7.64 MB)
【在线方式操作说明】
H7-TOOL可以采用USB,以太网或者WiFi方式连接上位机。
将前面lua小程序spibootloader.lua的内容复制到如下窗口:
点击下面的执行按钮就可以看到动图更新了:
【离线方式操作说明】
操作TOOL显示屏,进入Lua小程序界面:
执行spifirmware.lua小程序。
执行效果如下:
【Lua小程序简单说明】
注释非常详细:
[Lua] 纯文本查看 复制代码 -------------------------------------------------------
--
-- H7-TOOL 的SPI脱机烧录Lua小程序实现
--
-------------------------------------------------------
local str
local len
local bytes
local bin
local offset
local value
local count
local filesize
local byte0
local byte1
local byte2
local byte3
local filepath = "0:/H7-TOOL/Lua/SPI脱机烧录/app.bin" -- 表示SPI脱机烧录文件夹下存的文件
local filepath1 = "0:/H7-TOOL/Lua/SPI脱机烧录" -- 浏览SPI脱机烧录文件下存的文件
local ack
local i, m
local res
local str_offset
local str_offset1
local str_offset2
local rd
-------------------------------------------------------
-- 第1步:浏览串口脱机烧录文件夹下存的文件
-------------------------------------------------------
f_dir(filepath1)
print()
-------------------------------------------------------
-- 第3步:发送固件大小,方便目标板擦除相应大小扇区
-------------------------------------------------------
-- 获取固件大小
filesize=f_size(filepath)
print("============================================")
str= string.format("固件大小:%d",filesize)
print(str)
-- 将固件大小转换成四个字节
byte0 = ((filesize >> 0) & 0xFF)
byte1 = ((filesize >> 8) & 0xFF)
byte2 = ((filesize >> 16) & 0xFF)
byte3 = ((filesize >> 24) & 0xFF)
--配置SPI时钟12.5MHz,空闲时SCK为0,上升沿采集数据
spi_bus("init", 5, 0, 0)
--发送固件大小给目标板
--发送*号表示固件大小命令
--发送固件大小
--固定发送64字节,前5个字节是其它用途
str_offset = string.format("%02d", 69 - 5)
str= string.format("%c%c%c%c%c".."%"..str_offset.."s", 42, byte0, byte1, byte2, byte3, "A")
print(str)
spi_bus("send", str) --发送二进制字符串
rd = spi_bus("recive", 1)
delayms(3000)
if(rd == '\x30') then
print("扇区擦除执行完毕")
else
print("扇区擦除执行失败")
end
-------------------------------------------------------
-- 第4步:发送固件大小
-------------------------------------------------------
offset = 0
-- 第1个参数是路径,第2个参数的偏移地址,第3个参数读取大小
-- 返回值bytes表示读取的字节数,bin表示都回的数据
bytes, bin = f_read(filepath, 0, 64)
offset = offset + bytes
-- 读取数据为0,表示传输完毕
while(bytes > 0)
do
-- 发送$表示开始传输固件命令
-- 发送固件数据给目标板
-- 固定每次发送64个字节,前5个字节其它用途
count = 69 - 2 - bytes
str_offset = string.format("%02d", count)
str_offset1 = string.format("%"..str_offset.."s", "A")
str_offset2 = string.format("$%c", bytes)
str= str_offset2..bin..str_offset1
spi_bus("send", str) --发送二进制字符串
rd = spi_bus("recive", 1)
delayms(15)
-------获取返回值-----------------------
if(rd == '\x30') then -- 如果返回值是0x30,继续读取
bytes, bin = f_read(filepath, offset, 64) -- 继续读取数据
offset = offset + bytes
if(bytes ~= 0) then -- 读取不为0,打印发送的总字节数
print("发送固件:", offset)
end
else
print("扇区编程执行失败")
end
end
-------------------------------------------------------
-- 第5步:发送传输结束命令
-------------------------------------------------------
str_offset = string.format("%02d", 69 - 1)
str= string.format("#".."%"..str_offset.."s", "A")
spi_bus("send", str) --发送二进制字符串
spi_bus("recive", 1)
print("固件传输完成")
-------------------------------------------------------
-- end of file
-------------------------------------------------------
【目标板程序简单说明】
最关键的就是SPI程序处理:
[C] 纯文本查看 复制代码
/*
*********************************************************************************************************
* 函 数 名: DemoSpiSlave
* 功能说明: SPI 从机通信
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void DemoSpiSlave(void)
{
uint32_t SectorCount = 0;
uint32_t SectorRemain = 0;
uint32_t i;
uint32_t TotalSize = 0;
uint8_t ucState;
/***************设置SPI Flash片选上拉,防止影响 ***************/
{
GPIO_InitTypeDef gpio_init;
/* 打开GPIO时钟 */
__HAL_RCC_GPIOD_CLK_ENABLE();
gpio_init.Mode = GPIO_MODE_OUTPUT_PP; /* 设置推挽输出 */
gpio_init.Pull = GPIO_NOPULL; /* 上下拉电阻不使能 */
gpio_init.Speed = GPIO_SPEED_HIGH; /* GPIO速度等级 */
gpio_init.Pin = GPIO_PIN_13;
HAL_GPIO_Init(GPIOD, &gpio_init);
GPIOD->BSRR = GPIO_PIN_13;
}
sfDispMenu(); /* 打印命令提示 */
bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
/* 上电后,准备接收主机命令 */
g_spiTxBuf[69] = 0x30;
g_spiLen = 70;
bsp_spiTransfer();
while(1)
{
bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
/* 判断定时器超时时间 */
if (bsp_CheckTimer(0))
{
/* 每隔100ms 进来一次 */
bsp_LedToggle(2);
}
if (wTransferState != TRANSFER_WAIT)
{
/* 根据固件大小,做扇区擦除 ************/
if(g_spiRxBuf[0] == '*')
{
/* 获取文件大小 */
filesize = g_spiRxBuf[1] + (g_spiRxBuf[2] << 8) + (g_spiRxBuf[3] << 16) + (g_spiRxBuf[4] << 24);
uwAppSize = filesize;
for(int i = 0; i < 69; i++)
{
printf("%x ", g_spiRxBuf[i]);
}
/* 根据文件大小执行擦除 */
SectorCount = filesize/(128*1024);
SectorRemain = filesize%(128*1024);
printf("filesize = %d\r\n", filesize);
for(i = 0; i < SectorCount; i++)
{
bsp_EraseCpuFlash((uint32_t)(AppAddr + i*128*1024));
}
if(SectorRemain)
{
bsp_EraseCpuFlash((uint32_t)(AppAddr + i*128*1024));
}
/* 返回0x30,表示擦除成功 */
/* 上电后,准备接收主机命令 */
g_spiTxBuf[69] = 0x30;
g_spiLen = 70;
bsp_spiTransfer();
}
/* 传输完成命令 **************/
if(g_spiRxBuf[0] == '#')
{
JumpToApp();
}
/* 开始传输固件命令 **************/
if(g_spiRxBuf[0] == '$')
{
/* 接收数据个数 */
RecSize = g_spiRxBuf[1];
/* 编程内部Flash, */
ucState = bsp_WriteCpuFlash((uint32_t)(AppAddr + TotalSize), (uint8_t *)&g_spiRxBuf[2], RecSize);
TotalSize += RecSize;
printf("=====%d\r\n", TotalSize);
/* 如果返回非0,表示编程失败 */
if(ucState != 0)
{
/* 返回0x60,表示编程失败 */
g_spiTxBuf[69] = 0x60;
}
else
{
g_spiTxBuf[69] = 0x30;
}
/* 返回0x30,表示擦除成功 */
/* 上电后,准备接收主机命令 */
g_spiLen = 70;
bsp_spiTransfer();
}
}
}
}
【参考资料】
之前更新过三期BootLoader的视频教程,可以作为参考学习:
单片机bootloader专题,启动,跳转配置和调试下载的各种用法
https://www.armbbs.cn/forum.php?mod=viewthread&tid=112792
基于NAND,eMMC,SD卡和U盘的BootLoader实战,带CRC完整性校验
https://www.armbbs.cn/forum.php?mod=viewthread&tid=113053
单片机BootLoader的AES加密实战,含上位机和下位机代码全开源
https://www.armbbs.cn/forum.php?mod=viewthread&tid=113361
|