【STM32H7】基于CubeMX从零开始搭建的HAL库工程模板(包含串口重定向和DSP库)

前言

此次基本工程使用的芯片为STM32H743IIT6
Keil和CubeMX以及H7硬件支持包自行在网上搜索下载,都下最新版即可。
这里提供一个下载H7硬件支持包的网址:ARM

在这里我们会从零开始(除了下载安装),配置一个完整的方便管理的H7基本工程,下面是基本工程要达到的目标:
1.配置好内存保护单元
2.建立相应文件夹便于管理
3.可以正常使用串口进行外部调试
4.配置好DSP库

CubeMX基本配置

第一步:选择芯片

1.从搜索框中搜索"STM32H743IIT6"双击即可。


2.这里说的是强烈建议内核为M7的设备预先配置内存保护单元(MPU),是否应用此类默认配置?
由于我们这里是从零开始的配置,并且默认配置也并不是我们所希望的那样,所以我们这里就选择"否"。

第二步:配置时钟

1.点击左边栏RCC,高速时钟和低速时钟都选择外部时钟晶振。


2.配置时钟树,直接配置为最大主频480MHz即可,后面的APB和AHB总线也可以达到最大频率。

第三步:配置DEBUG调试

选择使用通信线最少的串行线模式。

第四步:生成工程文件

1. Project
这里根据下图配置即可。
工程结构:推荐选择Advanced,更方便管理工程。
堆栈设置:需要修改堆栈必须在这修改,不然每次重新生成工程堆栈都会恢复,这里保持默认堆栈设置就好。


下面是两个结构的区别:
Basic结构:

Advanced结构:


2.Code Generator

对应图片配置完成后,点击右上角GENERATE CODE生成代码。

工程文件管理

Drivers文件夹管理

打开工程文件,在Drivers文件夹下面新建如下几个文件(仅仅是我的习惯):

BSP:存放与硬件板卡相关的板载级别驱动。

DSP:存放与数字信号处理相关的算法和函数(个人习惯)。

MODULE:存放外部模块或组件的驱动程序。

SYSTEM:存放系统级别的驱动和功能,例如头文件管理、SysTick定时器、中断管理以及各种延时等等。

并且在每一个新建的文件夹当中再新建两个文件夹,分别命名为"Src"和"Inc"用于存放.c和.h文件:


接着我们打开Keil5中的小方块,根据刚刚新建的文件夹,在组管理这里添加进去,点击OK,就可以在侧边栏中看到新建的几个文件。


接下来就是要添加头文件的链接了,跟着图片操作即可:

魔术棒配置

这里只配置我认为必要的,往后遇到一些问题可以再自行搜索更改某些配置。

"Target"

ARM Compiler(ARM编辑器): 由于博主安装的是V5.39版本的Keil,所以默认安装的编辑器已经是AC6了,而且也比较推荐使用AC6去编译。
Floating Point Hardware(硬件浮点加速): H7是有双精度硬件浮点型加速功能的,我们直接选择其即可。
IROM(内部永久内存)/IRAM(内部暂存内存): 这里指的都是片上(on-chip)的内存,H7的Flash首地址为0x8000000,大小是0x200000,即2MB;H7的片上RAM分为了很多个部分,这个我们后面再讲,默认的RAM内存选择DTCM,首地址0x2000 0000,大小0x20000,即128KB,还有AXI SRAM的内存,首地址是0x2400 0000,大小0x80000,即512KB。

"C/C++"

Define(宏): 复制后面的整段代码上去即可USE_HAL_DRIVER,STM32H743xx,ARM_MATH_MATRIX,ARM_MATH_LOOPUNROLL,ARM_MATH_CM7,DATA_IN_ExtSDRAM,USE_PWR_LDO_SUPPLY
Optimization(优化等级): 我一般习惯选择最大优化。再勾上后面的优化时间Link-Time Optimization。
Include Paths(包含路径): 上面Drivers文件夹管理中已经配置过了,不记得的可以返回去看看。

"Debug"

选择好自己的烧录器后,就直接进入Setting设置烧录器选项:

扳手🔧设置

这里可以设置的有:编码格式、字体主题、缩进、语法提示和检测功能等等。

设置编码格式

设置字体

基本文件添加

1.在Drivers/SYSTEM/Inc文件夹下新建一个"headers.h"文件
将以下程序复制进去:

点击查看代码

复制代码
#ifndef __HEADERS_H
#define __HEADERS_H

/* 主函数预处理指令 */
#include "main.h"

/* DSP库函数 */

/* 片上外设库函数 */

/* C语言库函数 */
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "stdbool.h"
#include "math.h"

/*------------------------ SYS ------------------------*/

/*------------------------ DSP ------------------------*/
/* 个人函数 */

/*------------------------ BSP ------------------------*/
/* 外设 */

/* 协议/算法 */

/*------------------------ MODULE ------------------------*/
/* 模块启用管理 */

/* 模块 */

/*------------------------ 全局系数 ------------------------*/
#define pi		3.14159265358979323846f	/* 定义圆周率的近似值,方便计算正弦波等波形时使用 */
#define ZOOM	(3.3f / 65535.0f)					/* ADC模数转换缩放系数 */
#define IZOOM	(65535.0f / 3.3f)					/* ADC模数转换逆缩放系数 */


#endif

2.再在main.h中增加下图一句代码声明这个头文件:

串口和内存保护单元的CubeMX配置

内存保护单元配置

内存保护单元可以在侧边栏"CORETX_M7"中找到。

这里简单解释一下MUP内存保护单元,具体的配置跟着下图配置就可以了:

Speculation default mode(推测默认模式): 用于控制 CPU 是否启用指令预取和推测执行,高性能场景推荐开启以提升效率,严格实时性、低功耗或有时序依赖的场景建议关闭以保证确定性。
CPU ICache(高级指令缓存)/CPU DCache(高级数据缓存): 因为H7芯片的主频远远高出SRAM的频率,即SRAM的传输速度跟不上芯片了。AXI SRAM、SRAM1、SRAM2的频率一般在240MHz,而H7芯片的频率已经达到了480MHz(Cache的工作频率也是480MHz),这里为了提高CPU对SRAM的访问速度,所以引入了Cache。它可以将CPU常用的数据存储起来,当CPU去计算其他数据的时候,它就可以去替CPU完成对SRAM的读写操作。所以Cache是为了解决CPU获取SRAM数据慢的问题而存在的。
MPU Control Mode(内存保护单元控制模式): 其寄存器有三位,第0位为是否使能MPU,第1位为是(1)否(0)在硬故障期间使能MPU,第2位是当我们访问没有配置的内存区域时,是(0)否(1)会引起错误。这里我们选择第三个选项:使能MPU,且当访问没有配置的内存区域的时候不会引起错误,并且在硬错误的时候失能MPU。
下面是整个每一个区域的MPU配置的详细说明(给了代码和提示词让AI生成的):
点击查看代码

复制代码
/*
 * 本代码使用内存保护单元(MPU)对 STM32 不同内存区域进行保护配置,旨在通过精细化的权限与属性管控,增强系统安全性和稳定性,
 * 确保各内存区域仅按预设规则被访问。以下是对各个内存区域配置的详细说明:
 * 
 * 1. 第一个内存区域(DTCM)
 *    - 保护范围:整个 DTCM,共 128K 字节。
 *    - 用途:该区域为紧密耦合数据存储器,具有极低的访问延迟,通常用于存储对速度要求高的关键数据(如实时运算变量、中断服务程序临时数据)。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER0,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0x20000000,此为 DTCM 在 STM32 内存映射中的固定起始位置。
 *      - 子区域禁止:0x0,不禁止任何子区域(表示整个 128KB 区域均生效保护配置)。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,定义内存类型为普通可缓存/缓冲类型,符合 DTCM 硬件特性。
 *      - 大小:MPU_REGION_SIZE_128KB,明确保护范围覆盖整个 DTCM 区域。
 *      - 访问权限:MPU_REGION_FULL_ACCESS,允许对该区域进行读、写操作,无权限限制。
 *      - 指令执行:允许执行指令,程序可从该区域读取并执行代码(如紧急情况下的快速启动代码)。
 *      - 共享属性:不可共享,防止多核心/外设同时访问导致的数据竞争,保障数据一致性。
 *      - 缓存属性:允许缓存,利用 CPU 缓存机制进一步降低 DTCM 数据的访问延迟。
 *      - 缓冲属性:允许缓冲,通过写缓冲优化数据写入效率,减少 CPU 等待时间。
 * 
 * 2. 第二个内存区域(AXI SRAM)
 *    - 保护范围:整个 AXI SRAM,共 512K 字节。
 *    - 用途:该区域通过 AXI 总线与系统连接,具备高带宽特性,通常用于存储大容量中间数据(如算法处理的批量数据、外设 DMA 传输缓存)。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER1,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0x24000000,此为 AXI SRAM 在 STM32 内存映射中的固定起始位置。
 *      - 子区域禁止:0x0,不禁止任何子区域,保护范围覆盖整个 512KB 区域。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,符合 AXI SRAM 普通内存类型定义。
 *      - 大小:MPU_REGION_SIZE_512KB,明确保护范围覆盖整个 AXI SRAM 区域。
 *      - 访问权限:MPU_REGION_FULL_ACCESS,允许对该区域进行读、写操作,无权限限制。
 *      - 指令执行:允许执行指令,程序可从该区域读取并执行代码。
 *      - 共享属性:可共享,支持多核心/外设(如 DMA、GPU)同时访问,提升系统资源利用率。
 *      - 缓存属性:允许缓存,利用缓存降低高频访问数据的延迟。
 *      - 缓冲属性:禁止缓冲,避免写缓冲导致的多设备访问数据不一致(如 DMA 直接读取时的"脏数据"问题)。
 * 
 * 3. 第三个内存区域(SRAM1~SRAM3)
 *    - 保护范围:整个 SRAM1~SRAM3,共 512K 字节。
 *    - 用途:该区域为 STM32 核心常用静态随机存取存储器,通用性强,通常用于存储普通全局变量、函数栈、外设配置参数等。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER2,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0x30000000,此为 SRAM1~SRAM3 在 STM32 内存映射中的固定起始位置。
 *      - 子区域禁止:0x0,不禁止任何子区域,保护范围覆盖整个 512KB 区域。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,符合普通 SRAM 内存类型定义。
 *      - 大小:MPU_REGION_SIZE_512KB,明确保护范围覆盖整个 SRAM1~SRAM3 区域。
 *      - 访问权限:MPU_REGION_FULL_ACCESS,允许对该区域进行读、写操作,方便日常数据交互。
 *      - 指令执行:允许执行指令,程序可从该区域读取并执行代码(如 Bootloader 跳转代码)。
 *      - 共享属性:不可共享,防止多设备并发访问干扰普通变量的读写,保障数据独立性。
 *      - 缓存属性:允许缓存,提升 CPU 对该区域数据的访问速度。
 *      - 缓冲属性:允许缓冲,通过写缓冲优化数据写入效率,减少 CPU 等待。
 * 
 * 4. 第四个内存区域(SRAM4)
 *    - 保护范围:整个 SRAM4,共 64K 字节。
 *    - 用途:该区域为独立的小型 SRAM,通常用于存储敏感数据(如加密密钥、硬件配置参数)或预留为特定功能缓冲区,需严格限制访问。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER3,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0x38000000,此为 SRAM4 在 STM32 内存映射中的固定起始位置。
 *      - 子区域禁止:0x0,不禁止任何子区域,保护范围覆盖整个 64KB 区域。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,符合 SRAM4 普通内存类型定义。
 *      - 大小:MPU_REGION_SIZE_64KB,明确保护范围覆盖整个 SRAM4 区域。
 *      - 访问权限:MPU_REGION_NO_ACCESS,禁止对该区域进行任何读、写操作,从硬件层面阻断非授权访问,保障敏感数据安全。
 *      - 指令执行:允许执行指令,但因访问权限为"无访问",实际无法执行代码(权限优先级高于执行允许)。
 *      - 共享属性:不可共享,进一步隔离该区域,防止外部设备非法访问。
 *      - 缓存属性:允许缓存,若后续开放权限,可通过缓存提升访问效率。
 *      - 缓冲属性:允许缓冲,若后续开放权限,可通过写缓冲优化数据写入。
 * 
 * 5. 第五个内存区域(MCU LCD 屏所在的 FMC 区域)
 *    - 保护范围:MCU LCD 屏连接的 FMC 外部存储区域,共 256M 字节。
 *    - 用途:该区域为 FMC(灵活内存控制器)管理的外部设备映射地址,用于与 LCD 屏进行数据交互(如写入显示图像数据、读取 LCD 状态)。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER4,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0x60000000,此为 STM32 FMC 外部设备映射的常用起始地址(LCD 屏通常挂载于此)。
 *      - 子区域禁止:0x0,不禁止任何子区域,保护范围覆盖整个 256MB FMC 区域。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,符合 FMC 外部设备的内存类型定义。
 *      - 大小:MPU_REGION_SIZE_256MB,满足 LCD 屏高分辨率图像数据(如 4K 图像)的存储需求。
 *      - 访问权限:MPU_REGION_FULL_ACCESS,允许对该区域进行读、写操作(如写入像素数据、读取 LCD 控制寄存器)。
 *      - 指令执行:允许执行指令,但 FMC 区域为外部设备地址,实际不支持代码执行(硬件层面限制)。
 *      - 共享属性:不可共享,防止多设备同时访问 LCD 导致的显示错乱。
 *      - 缓存属性:禁止缓存,避免缓存中的"旧数据"与 LCD 实际显示需求不一致(如动态图像更新时的残影问题)。
 *      - 缓冲属性:禁止缓冲,确保数据实时传输到 LCD 屏,保障显示画面的时效性。
 * 
 * 6. 第六个内存区域(SDRAM 区域)
 *    - 保护范围:外部 SDRAM 存储区域,共 32M 字节。
 *    - 用途:该区域为大容量同步动态随机存取存储器,通常用于存储超大数据量(如视频流、日志文件、大型算法的数据集)。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER5,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0xC0000000,此为 STM32 外部 SDRAM 常用的内存映射起始地址。
 *      - 子区域禁止:0x0,不禁止任何子区域,保护范围覆盖整个 32MB SDRAM 区域。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,符合 SDRAM 内存类型定义。
 *      - 大小:MPU_REGION_SIZE_32MB,满足中大容量数据存储需求(如 1080P 视频的临时缓存)。
 *      - 访问权限:MPU_REGION_FULL_ACCESS,允许对该区域进行读、写操作,支持大数据量交互。
 *      - 指令执行:允许执行指令,但 SDRAM 访问延迟较高,通常不用于代码执行。
 *      - 共享属性:不可共享,防止多核心/外设并发访问导致的 SDRAM 总线冲突。
 *      - 缓存属性:允许缓存,对 SDRAM 中高频访问的数据(如重复使用的数据集)进行缓存,降低平均访问延迟。
 *      - 缓冲属性:允许缓冲,通过写缓冲批量处理 SDRAM 写入请求,减少 CPU 等待总线的时间。
 * 
 * 7. 第七个内存区域(NAND FLASH 区域)
 *    - 保护范围:外部 NAND FLASH 存储区域,共 256M 字节。
 *    - 用途:该区域为非易失性存储器,通常用于存储固化数据(如程序固件、配置文件、历史日志),断电后数据不丢失。
 *    - 具体配置:
 *      - 区域编号:MPU_REGION_NUMBER6,标识该配置对应的 MPU 区域序号。
 *      - 基地址:0x80000000,此为 STM32 外部 NAND FLASH 常用的内存映射起始地址。
 *      - 子区域禁止:0x0,不禁止任何子区域,保护范围覆盖整个 256MB NAND FLASH 区域。
 *      - 内存类型扩展字段(TEX):MPU_TEX_LEVEL0,符合 NAND FLASH 外部存储的内存类型定义。
 *      - 大小:MPU_REGION_SIZE_256MB,满足大量非易失性数据的长期存储需求。
 *      - 访问权限:MPU_REGION_FULL_ACCESS,允许对该区域进行读、写操作(如烧录固件、读取配置文件)。
 *      - 指令执行:允许执行指令,但 NAND FLASH 读速度慢且需地址映射,通常不用于代码执行。
 *      - 共享属性:不可共享,防止多设备同时操作 NAND FLASH 导致的擦写错误或数据损坏。
 *      - 缓存属性:禁止缓存,避免缓存中的数据与 NAND FLASH 实际数据不一致(如 NAND FLASH 擦写后缓存未更新)。
 *      - 缓冲属性:禁止缓冲,确保 NAND FLASH 的读写操作实时完成,避免缓冲导致的"写丢失"问题(如断电时缓冲数据未写入硬件)。
 */

跟着配置下来内存保护单元就配置完成了,不需要写程序。接下来就是串口调试的配置。

串口配置

CubeMX中配置

程序模板

新建文件"bsp_usart.c"和"bsp_usart.h",复制以下程序到这两个文件中。还有一些需要修改的跟着以下图片/程序操作即可。
bsp_usart.c
点击查看代码

复制代码
#include "bsp_usart.h"

/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不用选择use MicroLIB */
/* 如printf("HIGH:%d us\r\n", temp);打印"HIGH:temp\n" */

#if 1
#if (__ARMCC_VERSION >= 6010050)           /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");   /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE  不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
  int handle;
  /* Whatever you require here. If the only file you are using is */
  /* standard output using printf() for debugging, no file handling */
  /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
  ch = ch;
  return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
  x = x;
}

char *_sys_command_string(char *cmd, int len)
{
  return NULL;
}

/************************** 以下是多串口 printf 重定向函数 **************************/
/* FILE 在 stdio.h 里面定义 */
FILE __stdout;

UART_HandleTypeDef* Current_USART_Handle = NULL;
Current_USART_Indx Current_USART_Printf_Indx = USART_NONE;

/* 
 * 简介:重定义 fputc 函数,用于将字符输出到当前设置的 USART
 * 参数:
 * ch - 要发送的字符
 * f  - 文件指针(在此实现中未使用)
 * 返回值:发送的字符(或 EOF 如果出错)
 */
int fputc(int ch, FILE *f)
{
  if(Current_USART_Handle == NULL){          /* 如果当前没有设置 USART 句柄,则返回 EOF 表示错误 */
    return EOF;
  }
  /* 根据当前设置的 USART 句柄,选择对应的 USART 外设发送字符 */
  if(Current_USART_Handle == &huart1){
    while ((USART1->ISR & 0X40) == 0);        /* 等待 USART1 发送完成,然后发送字符 */
    USART1->TDR = (uint8_t)ch;             		/* 将要发送的字符 ch 写入到 DR 寄存器 */
  }
  return ch;                            /* 返回发送的字符 */
}
/* 
 * 简介:设置当前使用的 USART
 * 参数:indx - 要设置的 USART 索引
 * 这个参数可以是:USARTx_IDX,其中 x 可以是 1~3
 * 使用举例:(必须要将其放在 printf 函数前面,指定其中一个串口)
 *    Set_Current_USART(USART1_IDX);
 *    printf("我是串口 1\r\n");
 */
void Set_Current_USART(Current_USART_Indx indx)
{
  switch(indx)
  {
    case USART1_IDX:
      Current_USART_Handle = &huart1;
      Current_USART_Printf_Indx = USART1_IDX;
      break;
    default:
      Current_USART_Handle = NULL;
      Current_USART_Printf_Indx = USART_NONE;
      break;
  }
}
#endif
/************************************************************************************/

bsp_usart.h
点击查看代码

复制代码
#ifndef __BSP_USART_H
#define __BSP_USART_H

#include "headers.h"

/******************** 以下是多路 USART 串口 printf 重定向 ********************/
/* 定义 USART 索引枚举 */
typedef enum {
    USART_NONE,        /* 无 USART */
    USART1_IDX,        /* USART1 索引 */
    USART2_IDX,        /* USART2 索引 */
		USART3_IDX,        /* USART3 索引 */
		USART6_IDX,        /* USART6 索引 */
} Current_USART_Indx;

extern UART_HandleTypeDef* Current_USART_Handle;        /* 当前某个 USART 的句柄 */
extern Current_USART_Indx Current_USART_Printf_Indx;    /* 当前某个 USART 的索引 */
void Set_Current_USART(Current_USART_Indx indx);        /* 函数声明,用于设置当前使用的 USART */
/****************************************************************************/

/* 串口一 */
#define UART1_LEN  64
/* 串口二 */
#define UART2_LEN  64
/* 串口三 */
#define UART3_LEN  64


#endif

headers.h


测试

main.c:
点击查看代码

复制代码
  while (1)
  {
	Set_Current_USART(USART1_IDX);
	printf("HelloWorld!\r\n");
  }

效果

数字信号处理-DSP库配置

Keil中添加DSP库

在Keil中添加DSP库更方便,而且版本也是最新的,更方便管理和更新。不太建议在CubeMX中添加DSP库

DSP-lib库文件

我的.lib文件的路径为: STM32H7xx_Projects\STM32H743IIT6_V\BaseProject\Drivers\CMSIS\DSP\Lib\ARM

arm_cortexM7b_math.lib: 大端模式,不支持浮点加速
arm_cortexM7bfdp_math.lib: 大端模式,支持双精度浮点加速
arm_cortexM7bfsp_math.lib: 大端模式,支持单精度浮点加速
arm_cortexM7l_math.lib: 小端模式,不支持浮点加速
arm_cortexM7lfdp_math.lib: 小端模式,支持双精度浮点加速
arm_cortexM7lfsp_math.lib: 小端模式,支持单精度浮点加速

STM32默认使用小端模式,并且我们的H7支持双精度浮点加速,故我们选择"arm_cortexM7lfdp_math.lib"

预编译宏

前面已经添加过了,所以我们这里不用管。

必要头文件

在headers.h文件中增加以下语句(尽量放在前面):
这三个头文件已经包含了所有的DSP库函数。
点击查看代码

复制代码
/* CMSIS-DSP库 */
#include "arm_math.h"
#include "arm_const_structs.h"
#include "DSP/window_functions.h"

测试

主函数中加入: float test = arm_sin_f32(PI / 6.0);
Debug一下查看变量的值,可以看到已经移植成功了,还是非常简单的。

总结

至此,我们的基本工程也就配置完毕了,假如发现有什么问题的话欢迎提问!