【CubeMX-HAL库】STM32H743—学习笔记
目录
简介:
学习资料:
跳转目录:
一、工程创建
二、板载LED
三、串口调试
1.串口发送重定向模板
2.串口接收中断接收数据
①BSP_UART.c
②main.c
3.串口空闲中断接收不定长数据
①BSP_UART.c
②stm32h7xx_it.c
③main.c
4.定时器判断接收不定长数据
①BSP_UART.c
②main.c
四、定时器设置
五、PWM配置
六、RTC实时时钟
1.RTC实时时钟/日历
2.备份寄存器
七、ADC采集
1.ADC引脚采集
2.内部温度传感器
①CubeMX配置
②KEIL配置
八、IAP在应用编程
1.APP生成
Ⅰ.设置APP程序起始地址和存储空间
①APP_FLASH
②SRAM_APP
Ⅱ.设置中断向量表偏移量
①APP_FLASH
②SRAM_APP
Ⅲ.运行fromelf.exe生成.bin文件
①APP_FLASH
②SRAM_APP
2.BootLoader
Ⅰ.参考代码
①BSP_UART.c
②BSP_UART.h
③BOOT.c
④BOOT.h
⑤main.c
Ⅱ.演示效果
简介:
本系列使用硬件:
1.核心板:反客科技的STM32H743IIT6核心板
2.屏幕:耀元鸿科技的7寸TFT液晶显示屏IPS高清RGB接口GT911电容触摸屏(50P,1024×600像素)
3.转接板:因50P接口与市面大多支持LTDC的开发板RGB接口引脚不统一,所以自己做了一款50P转40P的RGB接口转接板。
学习资料:
LVGL官网
百问网-LVGL中文开发手册
跳转目录:
【CubeMX-HAL库】STM32H743—学习笔记
【CubeMX-HAL库】STM32H743—FMC配置SDRAM
【CubeMX-HAL库】STM32H743—LTDC配置RGB接口屏幕
【CubeMX-HAL库】STM32H743—DMA2D刷屏
【CubeMX-HAL库】STM32H743—GT9XX、FT5XXX电容触摸芯片
【CubeMX-HAL库】STM32H743—手把手教你LVGL移植
【CubeMX-HAL库】软件、硬件SPI+DMA驱动TFT彩屏(LVGL)
【CubeMX-HAL库】STM32H743—手把手教你GUI-Guider
【CubeMX-HAL库】STM32H743—内部FLASH
【CubeMX-HAL库】STM32H743—NOR Flash配置
【CubeMX-HAL库】STM32H743—SD卡配置
【CubeMX-HAL库】STM32H743—FatFS文件系统
【CubeMX-HAL库】STM32H743—NES游戏机移植
后续继续补充……
其他笔记跳转链接:【CubeMX-HAL库】STM32F407—无刷电机学习笔记
一、工程创建
本实验通过Cube MX配置使用Keil5编写程序代码。
①打开Cube MX创建新工程,在搜索框输入STM32H743IIT6选择对应芯片。
②在系统核心配置中选择RCC->打开外部时钟源HSE和LSE。
③在DEBUG栏中使能SW引脚。
④因为芯片功耗发热比较严重,所以本次将时钟频率设置为400MHz。
⑤设置文件路径及工程名,配置生成Keil-MDK文件。
⑥ 选择复制必要的文件,并且’.c/.h‘独立分开后点击"GENERATE CODE"生成代码。
⑦打开生成的Keil工程,可以先将编码设置为UTF-8格式(LVGL中字库大部分为UTF-8编码,防止之后乱码),进入魔术棒勾选使用LIB库,选择对应的下载器并勾复位并运行,然后编译工程,顺便将部件框都拖到习惯的位置,编译成功后即可下载程序。
二、板载LED
①通过原理图可知,核心板上板载LED端口为PH7(低电平点亮)。
②所以通过Cube MX搜索PH7端口设置为推挽输出,初始电平高电平熄灭,并设置LED标签。
③在Keil中添加LED闪烁代码,编译成功后下载运行。
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//LED端口反转电平
HAL_Delay(1000);//延时1秒
三、串口调试
①打开USART1配置为异步串口模式,并配置NVIC设置中断优先级。
②顺便可以打开串口的DMA功能进行测试。
1.串口发送重定向模板
#include "stdio.h"
#if !defined(__MICROLIB)
//不使用微库的话就需要添加下面的函数
#if (__ARMCLIB_VERSION <= 6000000)
//如果编译器是AC5 就定义下面这个结构体
struct __FILE
{
int handle;
};
#endif
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
#endif
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
while ((USART1->ISR & 0X40) == 0);/* 等待上一个字符发送完成 */
USART1->TDR = (uint8_t)ch;/* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
若将Cache使能,则在DMA传输中会出现问题,所以在发送前先进行Cache清除操作。
uint8_t data[]="串口DMA测试OK\r\n";
SCB_CleanDCache();/*在做将数据从SRAM拷贝到DTCM区之前,先做个D-Cache的清除操作,将D-Cache里的数据写回到实际存储区SRAM里。*/
HAL_UART_Transmit_DMA(&huart1,data,sizeof(data));
当我们用串口接收的时候经常会遇到分包的问题。即难以确定是否接收完一个完整的包。
通常遇到这类问题有几种解决方案:
1.通过规定字符的长度来确定报文包:比如串口modbus协议。
2.添加包头包尾标识来确定报文包,比如GPS报文。
3.通过定时器来确定是否接收完成,当接收完数据开启定时器,等待一段时间若未收到数据,则表示一个包完成。
4.有些单片机支持空闲中断,利用空闲中断完成一个包的判别。
5.其他方法。
2.串口接收中断接收数据
①BSP_UART.c
uint8_t USART_RX_BUF[USART_Rx_BUF_LEN];//接收缓冲,最大USART_Rx_LEN个字节
uint8_t USART_Rx_Data;//HAL库使用的串口接收缓冲
/*接收状态---------------------
bit15, 接收完成标志 -
bit14, 接收到0x0d -
bit13~0,接收到的有效字节数目*/
uint16_t USART_RX_STA = 0;//接收状态标记
uint32_t USART_RX_CNT = 0;//接收的字节数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart->Instance == USART1)
{
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d(\n)
{
if(USART_Rx_Data!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000;//接收完成了
}
else//还没收到0X0D(\r)
{
if(USART_Rx_Data==0x0d)USART_RX_STA|=0x4000;//接收到\r置一
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=USART_Rx_Data;
USART_RX_STA++;
if(USART_RX_STA>(USART_Rx_BUF_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
}
}
②main.c
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
if(USART_RX_STA & 0x8000)//接收到数据
{
USART_RX_CNT = USART_RX_STA & 0x3fff;//计算长度
HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,USART_RX_CNT,1000);//发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC) != SET);//等待发送完成
printf("\r\n");
USART_RX_STA = 0;
}
3.串口空闲中断接收不定长数据
改写串口空闲中断回调函数可以参考:STM32利用串口空闲中断来分包(HAL库)
本次在串口接收回调函数中开启IDLE中断,通过在串口中断函数中判断IDLE状态,如果为空闲则说明接收完成。
①BSP_UART.c
uint8_t USART_RX_BUF[USART_Rx_BUF_LEN];//接收缓冲,最大USART_Rx_LEN个字节,起始地址为0X24000000
uint8_t USART_Rx_Data;//HAL库使用的串口接收缓冲
/*接收状态---------------------
bit15, 接收完成标志 -
bit14~0,接收到的有效字节数目*/
uint16_t USART_RX_STA = 0;//接收状态标记
uint32_t USART_RX_CNT = 0;//接收的字节数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart->Instance == USART1)
{
if((RS485_USART_RX_STA & 0x80) == 0)//接收未完成
{
if(USART_RX_STA>(USART_Rx_BUF_LEN-1))
USART_RX_STA=0;//接收数据错误,重新开始接收
USART_RX_BUF[USART_RX_STA&0X8FFF]=USART_Rx_Data;
USART_RX_STA++;
}
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//启用串口空闲中断
}
}
②stm32h7xx_it.c
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)
{
USART_RX_STA |= 0x8000;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
}
/* USER CODE END USART1_IRQn 1 */
}
③main.c
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
if(USART_RX_STA & 0x8000)//接收到数据
{
USART_RX_CNT = USART_RX_STA & 0x7fff;//计算长度
HAL_UART_Transmit(&huart1,USART_RX_BUF,USART_RX_CNT,1000);//发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC) != SET);//等待发送完成
printf("\r\n");
USART_RX_STA = 0;
}
但是在接收大容量数据时容易接收错误。
4.定时器判断接收不定长数据
开启一个500ms的定时器中断。
①BSP_UART.c
uint8_t USART_RX_BUF[USART_Rx_BUF_LEN];//接收缓冲,最大USART_Rx_LEN个字节
uint8_t USART_Rx_Data;//HAL库使用的串口接收缓冲
/*接收状态---------------------
bit15, 接收完成标志 -
bit14, 接收到0x0d -
bit13~0,接收到的有效字节数目*/
uint16_t USART_RX_STA = 0;//接收状态标记
uint32_t USART_RX_CNT = 0;//接收的字节数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart->Instance == USART1)
{
if(USART_RX_CNT<USART_Rx_BUF_LEN)
{
TIM6->CNT=0;
TIM6->CR1|=1<<0; //使能定时3
USART_RX_BUF[USART_RX_CNT] = USART_Rx_Data;
USART_RX_CNT++;
}
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
USART_RX_STA|=1<<15;//标记接收完成
__HAL_TIM_CLEAR_FLAG(&htim6,TIM_EventSource_Update);//清除TIM6更新中断标志
TIM6->CR1&=~(1<<0);//关闭定时6
}
}
②main.c
HAL_TIM_Base_Start_IT(&htim6);//TIM6_500ms_INT
TIM6->CR1&=~(1<<0);//Close_TIM6
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
if(USART_RX_STA & 0x8000)//接收到数据
{
printf("Code_Length:%dBytes\r\n",USART_RX_CNT);
USART_RX_CNT = 0;
USART_RX_STA = 0;
}
四、定时器设置
由STM32H743数据手册中的时钟树可知,TIM6挂载在APB1总线上,我们本次系统时钟为400MHz,到达APB1外设时钟为100MHz,APB1定时器时钟为200MHz。所以预分频器设置为20000-1,自动重装载值设为10000-1;200MHz/20K/10K=1Hz,所以最终定时器中断周期为1秒钟。
HAL_TIM_Base_Start_IT(&htim6);//开启定时器中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断回调函数
{
if(htim->Instance == TIM6)
{
printf("定时器OK\r\n");
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
}
}
五、PWM配置
由STM32H743数据手册中的时钟树可知,TIM8挂载在APB2总线上,我们本次系统时钟为480MHz,到达APB2外设时钟为120MHz,APB2定时器时钟为240MHz。所以预分频器设置为1200-1,自动重装载值设为100-1;240MHz/1200/100=2KHz,即可输出2KHz的信号。
HAL_TIMEx_PWMN_Start(&htim8,TIM_CHANNEL_2);//使能PWM互补通道
TIM8->CCR2 = 50;//设置PWM占空比
六、RTC实时时钟
1.RTC实时时钟/日历
开启外部低速晶振LSE。
激活内部时钟源,激活日历,开启两个闹钟和周期唤醒中断。
RTC参数设置
RTC_TimeTypeDef sTime;//时间结构体
RTC_DateTypeDef sDate;//日期结构体
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)//周期唤醒回调函数
{
if(HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK)
{
HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);
printf("RTC Date = 20%02d:%02d:%02d %d\r\n",sDate.Year,sDate.Month,sDate.Date,sDate.WeekDay);
printf("RTC Time = %02d:%02d:%02d\r\n",sTime.Hours,sTime.Minutes,sTime.Seconds);
}
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)//闹钟A事件回调函数
{
printf("Alarm A every Tuesday (0:0:0) trigger: \r\n");
}
void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)//闹钟B事件回调函数
{
printf("Alarm B (xx:xx:10) trigger: \r\n");
}
2.备份寄存器
if((HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0) == 0x5050))//读取备份寄存R0已经写过
{
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0, RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)//使能周期唤醒
{
Error_Handler();
}
return;//提前退出函敿,不初始化时间和日期
}
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x5050);
七、ADC采集
STM32H743xx 系列有 3 个 ADC, 都可以独立工作,其中 ADC1 和 ADC2 还可以组成双重模式(提高采样率)。 STM32H743 的 ADC 分辨率高达 16 位,每个 ADC 具有多达 20 个的采集通道,这些通道的 A/D 转换可以单次、连续、扫描或间断模式执行。 ADC 的结果可以左对齐或右对齐方式存储在 32 位数据寄存器中。
STM32H743 的 ADC 最大的转换速率为 4.5Mhz,也就是转换时间为 0.22us( 12 位分辨率时),不要让 ADC 的时钟超过 36M,否则将导致结果准确度下降。
ADC校准问题:STM32L0 ADC使用HAL库关于校准问题的说明
1.ADC引脚采集
2.内部温度传感器
①CubeMX配置
ADC时钟本次配置为50MHz。
使能ADC3的温度传感器通道。
为了温度采集更加稳定,配置采样时间810.5个时钟周期。 由图标可知ADC在16位采样率时采样周期为8.5个ADC时钟周期,总共819个时钟周期,1/50MHz*819=16.38us。
②KEIL配置
由手册可知STM32H743的温度算法与普通芯片有差异,通过读取寄存器中的数值然后计算当前温度。
#ifndef __Temperature_H__
#define __Temperature_H__
#include "main.h"
#define TS_CAL1 *((__IO uint16_t *)(0X1FF1E820))//30°C下获得的温度传感器校准值
#define TS_CAL2 *((__IO uint16_t *)(0X1FF1E840))//110°C下获得的温度传感器校准值
float ADC_Get_MCU_Temperature()
{
uint16_t adc_value;
float temp;
HAL_ADC_Start(&hadc3);
HAL_ADC_PollForConversion(&hadc3,10);
adc_value = HAL_ADC_GetValue(&hadc3);
temp = (float)((float)(70.0f/(TS_CAL2-TS_CAL1)) * (adc_value-TS_CAL1)) + 30;
return temp;
}
#endif
注意:使用前先进行ADC的校准,然后才能读取,否则误差极大。
HAL_ADCEx_Calibration_Start(&hadc3,ADC_CALIB_OFFSET,ADC_SINGLE_ENDED);//校准ADC
printf("%0.2f\r\n",ADC_Get_MCU_Temperature());
八、IAP在应用编程
1.APP生成
Ⅰ.设置APP程序起始地址和存储空间
/*
1.APP要在BootLoader程序后面
2.内存不能出现重叠
3.偏移量是0x200(512B)的倍数
4.但是H743一个ROM大小128KB,擦除一个扇区容易把BootLoader也删了,所以最好用0x20000(128KB)为倍数
本APP内存地址Ox08020000,与起始地址偏移量0x20000(128KB)
程序大小0x60000(384KB)
与BootLoader共512KB
SRAM地址与大小和BootLoader共用
*/
①APP_FLASH
②SRAM_APP
Ⅱ.设置中断向量表偏移量
/*设置NVIC的向量表偏移寄存器,VTOR低9位保留,即[8:0]保留
偏移量必须是0或者0X100的倍数*/
SCB->VTOR = base_addr | (offset & (uint32_t)0XFFFFFE00);
①APP_FLASH
②SRAM_APP
Ⅲ.运行fromelf.exe生成.bin文件
fromelf --bin -o "$L@L.bin" "#L
根据当前工程的.axf,生成一个.bin的文件。并存放在axf文件相同的目录下,即工程的OBJ文件夹里面。keil无法生成axf文件之解决方法
D:\Keil5\ARM\ARMCLANG\bin\fromelf.exe --bin -o .\CaoGao\CaoGao.bin .\CaoGao\CaoGao.axf
①APP_FLASH
②SRAM_APP
2.BootLoader
Ⅰ.参考代码
①BSP_UART.c
#include "BSP_UART.h"
#if !defined(__MICROLIB)
//不使用微库的话就需要添加下面的函数
#if (__ARMCLIB_VERSION <= 6000000)
//如果编译器是AC5 就定义下面这个结构体
struct __FILE
{
int handle;
};
#endif
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
#endif
//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
while ((USART1->ISR & 0X40) == 0);/* 等待上一个字符发送完成 */
USART1->TDR = (uint8_t)ch;/* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
uint8_t USART_RX_BUF[USART_Rx_BUF_LEN] __attribute__ ((at(0X24001000)));//接收缓冲,最大USART_Rx_LEN个字节
uint8_t USART_Rx_Data;//HAL库使用的串口接收缓冲
/*接收状态---------------------
bit15, 接收完成标志 -
bit14, 接收到0x0d -
bit13~0,接收到的有效字节数目*/
uint16_t USART_RX_STA = 0;//接收状态标记
uint32_t USART_RX_CNT = 0;//接收的字节数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口中断回调函数
{
if(huart->Instance == USART1)
{
if(USART_RX_CNT<USART_Rx_BUF_LEN)
{
TIM6->CNT=0;
TIM6->CR1|=1<<0; //使能定时3
USART_RX_BUF[USART_RX_CNT] = USART_Rx_Data;
USART_RX_CNT++;
}
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
USART_RX_STA|=1<<15;//标记接收完成
__HAL_TIM_CLEAR_FLAG(&htim6,TIM_EventSource_Update);//清除TIM6更新中断标志
TIM6->CR1&=~(1<<0);//关闭定时6
}
}
②BSP_UART.h
#ifndef __BSP_UART_H__
#define __BSP_UART_H__
#include "main.h"
#include "usart.h"
#include "stdio.h"
#include "string.h"
#include "tim.h"
#define USART_Rx_BUF_LEN (21*1024)//最大接受字节数
extern uint8_t USART_RX_BUF[USART_Rx_BUF_LEN];//接收缓冲,最大USART_Rx_LEN个字节,起始地址为0X24000000
extern uint8_t USART_Rx_Data;//HAL库使用的串口接收缓冲
extern uint16_t USART_RX_STA;//接收状态标记
extern uint32_t USART_RX_CNT;//接收的字节数
#endif
③BOOT.c
关于函数指针的介绍可以参考:初识 typedef void(*Func)(void)
#include "BOOT.h"
typedef void (*iapfun)(void);//定义一个函数类型的参数
iapfun jump2app;
uint32_t iapbuf[512];//2K字节缓存
void IAP_Write_APP_Bin(uint32_t appaddr,uint8_t *appbuf,uint32_t appsize)//在指定地址开始,写入bin(字节)
{
uint32_t t;//计数中间变量
uint16_t i = 0;
uint32_t temp;//缓存数据中间变量
uint32_t fwaddr = appaddr;//当前写入的地址
uint8_t *dfu = appbuf;
for(t = 0;t < appsize;t += 4)
{
temp = (uint32_t)dfu[3] << 24;//将Bin中四字节地址的Byte合并为一个uint32_t类型数据
temp |= (uint32_t)dfu[2] << 16;
temp |= (uint32_t)dfu[1] << 8;
temp |= (uint32_t)dfu[0];
dfu += 4;//偏移4个字节
iapbuf[i++] = temp;//2K字节缓存存储Bin数据
if(i == 512)//达到512Byte
{
i = 0;
STMFLASH_Write(fwaddr,iapbuf,512);//写入数据
fwaddr += 2048;//偏移2048 512*4=2048
}
}
if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去
}
#if defined(__clang__) //使用V6编译器(clang)
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
//设置栈顶地址
//addr:栈顶地址
void __attribute__((noinline)) MSR_MSP(uint32_t addr)
{
__asm__("msr msp, r0 \t\n"
"bx r14");
}
#elif defined (__CC_ARM) //使用V5编译器(ARMCC)
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0//set Main Stack value
BX r14
}
#endif
void IAP_Load_APP(uint32_t appaddr)//跳转到应用程序段
{
if(((*(__IO uint32_t *)appaddr)&0x2FF00000)==0x24000000)//检查栈顶地址是否合法.
{
printf("Run to APP\r\n");
jump2app=(iapfun)*(__IO uint32_t*)(appaddr+4);//用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(__IO uint32_t*)appaddr);//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app();//跳转到APP
}
}
void IAP_Load_Boot(uint32_t addr)//跳转到指定程序地址
{
uint32_t i=0;
void (*SysBootJump)(void);//声明一个函数指针
__IO uint32_t BootAddr = addr;//'0x1FF09800'STM32H7的系统BootLoader地址
__set_PRIMASK(1);//关闭全局中断
SysTick->CTRL = 0;//关闭滴答定时器,复位到默认值
SysTick->LOAD = 0;
SysTick->VAL = 0;
HAL_RCC_DeInit();//设置所有时钟到默认状态,使用HSI时钟
for (i = 0; i < 8; i++)//关闭所有中断,清除所有中断挂起标志
{
NVIC->ICER[i]=0xFFFFFFFF;
NVIC->ICPR[i]=0xFFFFFFFF;
}
__set_PRIMASK(0);//使能全局中断
//跳转到系统BootLoader,首地址是MSP,地址+4是复位中断服务程序地址
SysBootJump = (void (*)(void)) (*((uint32_t *) (BootAddr + 4)));
__set_MSP(*(uint32_t *)BootAddr);//设置主堆栈指针
__set_CONTROL(0);//在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针
SysBootJump();//跳转到系统BootLoader
while(1);//跳转成功的话,不会执行到这里,用户可以在这里添加代码
}
④BOOT.h
#ifndef __BOOT_H__
#define __BOOT_H__
#include "main.h"
#include "BSP_UART.h"
#include "FLASH.h"
//保留0X08000000~0X0801FFFF的空间为Bootloader使用(共128KB)
#define FLASH_APP1_ADDR 0x08020000//第一个应用程序起始地址(存放在FLASH)
void IAP_Write_APP_Bin(uint32_t appaddr,uint8_t *appbuf,uint32_t applen);//在指定地址开始,写入bin(字节)
void IAP_Load_APP(uint32_t appaddr);//跳转到APP程序执行
void IAP_Load_Boot(uint32_t addr);//跳转到指定程序地址
#endif
⑤main.c
HAL_TIM_Base_Start_IT(&htim6);//TIM6_500ms_INT
TIM6->CR1&=~(1<<0);//Close_TIM6
HAL_UART_Receive_IT(&huart1,&USART_Rx_Data,1);//启动接收中断
if(USART_RX_STA & 0x8000)//接收到数据
{
printf("Code_Length:%dBytes\r\n",USART_RX_CNT);
if(USART_RX_CNT == 21368)//FLASH_APP_SIZE
{
if(((*(__IO uint32_t *)(0x24001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{
IAP_Write_APP_Bin(FLASH_APP1_ADDR,USART_RX_BUF,USART_RX_CNT);//更新FLASH代码
printf("Firmware update complete!\r\n");
}else
{
printf("NO_FLASH_APP\r\n");
}
if(((*(__IO uint32_t*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{
IAP_Load_APP(FLASH_APP1_ADDR);//执行FLASH APP代码
}else
{
printf("Can't run FLASH APP!\r\n");
}
}
else if(USART_RX_CNT == 15812)//SRAM_APP
{
if(((*(__IO uint32_t*)(0x24001000+4))&0xFF000000)==0x24000000)//判断是否为0X24XXXXXX.
{
IAP_Load_Boot(0x24001000);//SRAM地址
}else
{
printf("Can't run SRAM APP\r\n");
}
}
USART_RX_CNT = 0;
USART_RX_STA = 0;
}
Ⅱ.演示效果
发送FLASH_APP后开始运行ADC读取内部温度传感器数据代码。
发送SRAM_APP后开始运行SDRAM测试实验。
作者:muub