【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

物联沃分享整理
物联沃-IOTWORD物联网 » 【CubeMX-HAL库】STM32H743—学习笔记

发表回复