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

前言

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

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

CubeMX基本配置

第一步:选择芯片

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

image


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

image

第二步:配置时钟

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

image


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

image

第三步:配置DEBUG调试

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

image

第四步:生成工程文件

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

image


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


2.Code Generator

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

image

工程文件管理

Drivers文件夹管理

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

BSP:存放与硬件板卡相关的板载级别驱动。
DSP:存放与数字信号处理相关的算法和函数(个人习惯)。
MODULE:存放外部模块或组件的驱动程序。
SYSTEM:存放系统级别的驱动和功能,例如头文件管理、SysTick定时器、中断管理以及各种延时等等。

image

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

image


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

image


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

image

魔术棒配置

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

"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。

image

"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文件夹管理中已经配置过了,不记得的可以返回去看看。

image

"Debug"

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

image

image

image

image

扳手🔧设置

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

设置编码格式

image

设置字体

image

基本文件添加

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

image


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

image

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

内存保护单元配置

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

image

这里简单解释一下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 的读写操作实时完成,避免缓冲导致的“写丢失”问题(如断电时缓冲数据未写入硬件)。
 */

imageimageimageimageimage

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

串口配置

CubeMX中配置

image

程序模板

新建文件"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

image


测试

main.c:

点击查看代码

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

效果

image

数字信号处理-DSP库配置

Keil中添加DSP库

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

image

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"

image

预编译宏

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

image

必要头文件

在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一下查看变量的值,可以看到已经移植成功了,还是非常简单的。

image

附加(delay.c/ .h,加到SYSTEM当中)

delay.c

点击查看代码
#include "delay.h"

static uint32_t g_fac_us = 0;       /* us延时倍乘数 */

/**
 * @brief     初始化延迟函数
 * @param     sysclk: 系统时钟频率, 即CPU频率(HCLK), 等于系统主频, 单位 Mhz
 * @retval    无
 */  
void delay_init(uint16_t sysclk)
{
    g_fac_us = sysclk;                                  /* 不论是否使用OS,g_fac_us都需要使用 */
}

/**
 * @brief     延时nus
 * @note      无论是否使用OS, 都是用时钟摘取法来做us延时
 * @param     nus: 要延时的us数
 * @note      nus取值范围: 0 ~ (2^32 / fac_us) (fac_us一般等于系统主频, 自行套入计算)
 * @retval    无
 */ 
void delay_us(uint32_t nus)
{
    uint32_t ticks;
    uint32_t told, tnow, tcnt = 0;
    uint32_t reload = SysTick->LOAD;        /* LOAD的值 */
    ticks = nus * g_fac_us;                 /* 需要的节拍数 */

    told = SysTick->VAL;                    /* 刚进入时的计数器值 */
    while (1)
    {
        tnow = SysTick->VAL;
        if (tnow != told)
        {
            if (tnow < told)
            {
                tcnt += told - tnow;        /* 这里注意一下SYSTICK是一个递减的计数器就可以了 */
            }
            else
            {
                tcnt += reload - tnow + told;
            }
            told = tnow;
            if (tcnt >= ticks) 
            {
                break;                      /* 时间超过/等于要延迟的时间,则退出 */
            }
        }
    }
} 

/**
 * @brief     延时nms
 * @param     nms: 要延时的ms数 (0< nms <= (2^32 / fac_us / 1000))(fac_us一般等于系统主频, 自行套入计算)
 * @retval    无
 */
void delay_ms(uint16_t nms)
{
    delay_us((uint32_t)(nms * 1000));                   /* 普通方式延时 */
}
/**
 * @brief     通过系统滴答定时器实现毫秒级延时
 * @param     ms    需要延时的毫秒数
 * @retval    无
 * @note      该函数利用全局变量`uwTick`(系统滴答计数器)实现延时。
 *            在延时期间,函数通过忙等待循环阻塞,直到达到指定的毫秒数。
 */
void delay_tick_ms(uint32_t nms)
{
	uint32_t ms_uwTick = uwTick; /* 记录延时开始时的滴答数 */
	while(uwTick - ms_uwTick < nms); /* 忙等待,直到达到指定延时 */
}


//-----------------------------------------------------------------
// 程序描述:
//     基于程序延时的不同延时时基的程序:2us,10us,250us,1ms,5ms,50ms
// 作    者: 凌智电子
// 开始日期: 2014-01-28
// 完成日期: 2014-01-29
// 修改日期: 2014-04-13
// 当前版本: V2.0.5
// 历史版本:
//   - V2.0: 基于STM32的延时:ns,10us,250us,1ms,5ms,50ms
//   -2.0.1: (2014-02-07)整理格式
//	- 2.0.2: (2014-02-10)实际测试延时长度,修改部分延时参数
//   -2.0.3: (2014-02-15)时钟滴答和手工计算延时分开
//  - 2.0.4: (2014-02-16)头文件中不包含其他头文件
//	- 2.0.5: (2014-04-13)添加2us延时函数
// 调试工具: 凌智STM32核心开发板 、LZE_ST_LINK2
// 说    明: 
//     (1)调试使用的系统时钟频率Fsysclk=72MHz;
//		 (2) 使用两种延时方式
//							A STM32的时钟滴答精确延时 
//							B 使用手工延时函数
//-----------------------------------------------------------------

//-----------------------------------------------------------------
// 功能程序区
//-----------------------------------------------------------------
//-----------------------------------------------------------------
// void TimingDelay_Decrement(void)
//-----------------------------------------------------------------
// 
// 函数功能: 延时计数器递减函数
// 入口参数: 无 
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项: 此函数被中断函数调用
//					 时钟嘀嗒使用系统时钟源72MHz,
//
//-----------------------------------------------------------------
void TimingDelay_Decrement(void)
{
}
//-----------------------------------------------------------------
// void Delay_ns (unsigned char t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为ns的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  
//
//测得延时如下:(SYSCLK=72MHz) 
//													延时个数		延时				误差
//				Delay_ns(1):       17       236ns				500ns
//				Delay_ns(2):	     26       361ns				550ns
//				Delay_ns(10)														1.625us
//
//-----------------------------------------------------------------
void Delay_ns (uint8_t t)
{
	do {
			;
	} while (--t); 
}

//-----------------------------------------------------------------
// void Delay_1us (unsigned char t)
//-----------------------------------------------------------------
// 函数功能: 时基为1us的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  时钟周期:1/72mhz
//						目标1us:-->要72个时钟周期
//						-->要观察执行了Delay_1us函数前后的state差是否为72个周期
//						-->通过多次更改,使得误差越来越小
/*
//i=7,测得延时如下:(SYSCLK=72MHz) 
												延时个数		   延时			  	误差
		Delay_1us(1):	  	     70				 972ns				28ns		
		Delay_1us(2):	  	 	  131				1.819us				181ns
		Delay_1us(10):		    619			 8.5972us				1.4us
		Delay_1us(20):			 1229			 17.069us				2.9us
		Delay_1us(100): 	   6109				84.84us				15.16us
		
		i=4!!!					i=9				i=8
		1us:1us				1.98us!		1.88us
		5us:3,22us			5.6us			5.16us	
		10us:6us				10.86us		9.89us
		50us:28.22us		52.5us		47.6us
		100us:					104.6us   95.4us
		200us:					209us			189.5
		500us:					522us 		478us
		结论:i=9,误差+4.6%,1us延时误差大
				 i=8,误差-4.6%,1us延时误差大
*/
//-----------------------------------------------------------------
void Delay_1us (uint16_t t)
{
	uint8_t i=0;
	
	do
	{
		i=8;											// i=7,t=0.986us,误差14ns
		do
		{
		} while (--i);	
	}while(--t);
}

//-----------------------------------------------------------------
// void Delay_2us (u16 t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为2us的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  
/*
		i=14,实际比理论小3%
		i=15,实际比理论大3%
		i=14,	理论:实际 		 i=15, 实际			 i=13
					2us   2.36us					2.5us		 2.2
					4us	  4.24us				  4.5us		 4us
					6us   6.2us						6.6us		 5.8us
					8us   8.1us						8.6us		 7.6us
					10us	10us						10.6us	 9.4us
					20us  19.6us				  20.8us	 18.4us
					50us	49us					  52us		 45.2us
					100us	97us						103us		 90us
					200us 192us						204us		 180us
					400us	384us	 					408us		 360us
					500us	480us						510us    448us
					1ms	  950us         1.020ms		 890us
*/
//-----------------------------------------------------------------
void Delay_2us (uint16_t t)
{
	uint8_t i=0;
	
	do
	{
		i=15;																		// i=7,t=0.986us,误差14ns
		do
		{
		} while (--i);	
	}while(--t);
}

//-----------------------------------------------------------------
// void Delay_10us (u8 t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为10us的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  
/* 
//j=7,u=11,测得延时如下:(SYSCLK=72MHz) 
												延时个数						延时				误差  实际
			Delay_10us(1):	      731  				10.153us		153ns 	10.45
			Delay_10us(2):	   	 1454 				20.194us		194ns		20.5
			Delay_10us(10):		   7238 			 100.527us		527ns		101.8
			Delay_10us(20):			14468			 	 200.944us 		944ns  	201.4
			Delay_10us(100): 	  72338				1004.694us		  4us		1.004ms
*/
//-----------------------------------------------------------------
void Delay_10us (uint16_t t)
{
	uint8_t i,j;
   
	do {
		j = 7;						// j=6,i=13,737,10.236us,误差236ns
		do {							// j=7,i=11,731,10.152us,误差152ns
			i = 11; 
			do {									 
       } while (--i);	
		} while (--j);		
	} while (--t); 
}	

//-----------------------------------------------------------------
// void Delay_250us (u8 t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为250us的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  
/* 
//j=66,u=30,测得延时如下:(SYSCLK=72MHz) 
														延时个数	    	延时				误差	实际
			Delay_250us(1):	        18035		250.486us			486ns		251us
			Delay_250us(2):	   		  36062		500.861us			861ns		501us
			Delay_250us(10):	  	 180338		  2.504ms				4us		2500us
			Delay_250us(20):	   	 360698 	  5.009ms				9us	 	5.01ms
			Delay_250us(100):   	1803428		  25.047ms	 	 47us		25.05ms
*/
//-----------------------------------------------------------------
void Delay_250us (unsigned char t)
{
	unsigned char i,j;
   
	do {
		j = 66;						// j=66,i=30, 18035,250.486us
		do {
			i = 30;
			do {									
         } while (--i);	
		} while (--j);		
	} while (--t); 
}

//-----------------------------------------------------------------
// void Delay_882us (void)
//-----------------------------------------------------------------
//
// 函数功能: 延时882us
// 入口参数: 无
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无     
// 注意事项: 延时时间为880us,误差为2us,实际测试:882us,误差0
//-----------------------------------------------------------------
void Delay_882us (void)			  			
{	
	uint16_t i,j;   
	j = 101;						// j=101,i=88,63431,880.986us
	do {
		i = 88;
		do {									 
		} while (--i);	
	} while (--j);	
}

//-----------------------------------------------------------------
// void Delay_1ms (unsigned char t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为1ms的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项: 
/* 			 
   手工延时:
   j=119,u=67,测得延时如下:(SYSCLK=72MHz) 
														延时个数					延时			误差
				Delay_1ms(1):	       72158				1.002ms		 	 2us	
				Delay_1ms(2):	   		 144304				2.004ms			 4us
				Delay_1ms(5):	     	 360752				5.010ms			10us
				Delay_1ms(10):	  	 721498			 10.020ms			20us		
				Delay_1ms(20):	   	1442989			 20.041ms			41us				
				Delay_1ms(40):	  	2885874			 40.082ms 		82us	
				Delay_1ms(100):   	7214936			100.207ms			207us	
*/
//-----------------------------------------------------------------
void Delay_1ms (__IO uint32_t t)
{
	uint16_t i,j;
   
	do {
		j = 119;	
		do {
			i = 67;																// 1.002278ms ,误差2us
			do {} while (--i);										// 
		} while (--j);		
	} while (--t); 
}

//-----------------------------------------------------------------
// void Delay_5ms (unsigned char t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为5ms的延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  
/* 
   j=625,u=63,测得延时如下:(SYSCLK=72MHz) 
													  	延时个数		  	延时			误差
				Delay_5ms(1):	        360166			5.0023ms		 2us	
				Delay_5ms(2):	   			720324			10.004ms 		 4us
				Delay_5ms(5):	     		1800795			25.011ms		11us			
				Delay_5ms(10):	  		3601588			50.022ms		22us	
				Delay_5ms(20):	   		7203168		 100.044ms		44us	
				Delay_5ms(40):	  	 14406118		 200.084ms		84us
				Delay_5ms(100):   	 36015742		 500.218ms	 218us		
*/
//-----------------------------------------------------------------
void Delay_5ms (uint16_t t)
{
	uint16_t i,j;
   
	do {
		j = 625;	//j=625,u=63,误差2.30us
		do {
			i =63; 
			do {			
		   } while (--i);
		} while (--j);		
	} while (--t); 
}

//-----------------------------------------------------------------
// void Delay_50ms (u8 t)
//-----------------------------------------------------------------
//
// 函数功能: 时基为50ms的延时
// 例子提示: 调用Delay_50ms(20),得到1s延时
// 入口参数: 无符号8bit整数
// 返回参数: 无
// 全局变量: 无
// 调用模块: 无
// 注意事项:  
/* 
  j=1000,i=513,测得以下延时: (SYSCLK=72MHz)
													时钟个数					延时			 误差
			Delay_50ms(1):	    3599596	  		49.994ms			 6us
			Delay_50ms(2):	    7199150				99.988ms			12us
			Delay_50ms(5):	    17997911			249.970ms			30us
			Delay_50ms(10):	  	35995845			499.942ms			58us
			Delay_50ms(20):	   	71991715			999.885ms			115us
			Delay_50ms(40):	  	143983456		 1999.770ms			230us
			Delay_50ms(100):  	359958683		 4999.426ms		  574us
*/
//-----------------------------------------------------------------
void Delay_50ms (uint8_t t)
{
	uint16_t i,j;
   
	do {
		j = 1000;					// j=1000,i=513-->6us
		do {
			i = 513;
			do 
			{										
      } while (--i);
		} while (--j);		
	} while (--t); 
}

//-----------------------------------------------------------------
// void Delay(__IO uint32_t nCount)
//-----------------------------------------------------------------
// 
// 函数功能: 粗略延时
// 入口参数: 延时长度 
// 返回参数: 无
// 注意事项: 
//   -__IO 就是volatile, 加上这个后可以避免延迟函数被编译器优化掉
//				一个系统时钟:1/72MHZ=13.89ns,经过23个周期:319ns
/*
  	j=1000,i=513,测得以下延时: (SYSCLK=72MHz)
										时钟个数					延时				 误差		实际
			Delay(1):		    22						305ns								639ns
			Delay(2):		    29					  402ns								736ns
			Delay(5):		    50						694ns								1.028us
			Delay(10):	  	85				 1.1805us								1.514us
			Delay(20):	   155	 			 2.1527us								2.486us
			Delay(40):	   295				 4.0972us								4.4305us
			Delay(100):	   715				 9.9305us								10.26us
*/
//-----------------------------------------------------------------
void Delay(__IO uint32_t nCount)
{
  for(; nCount != 0; nCount--);// 23个
}

//-----------------------------------------------------------------
// End Of File
//-----------------------------------------------------------------

delay.h

点击查看代码
#ifndef __DELAY_H
#define __DELAY_H

#include "headers.h"

extern void delay_init(uint16_t sysclk);   	/* 初始化延迟函数 */
extern void delay_ms(uint16_t nms);        	/* 延时nms */
extern void delay_us(uint32_t nus);        	/* 延时nus */
extern void delay_tick_ms(uint32_t ms);			/* 滴答延时nms*/
extern void TimingDelay_Decrement(void);
extern void Delay_ns (unsigned char t);			/* ns延时 */
extern void Delay_1us (uint16_t t);					/* 延时时基:1us */
extern void Delay_2us (uint16_t t);					/* 延时时基:2us */
extern void Delay_10us (uint16_t t);   			/* 延时时基:10us */
extern void Delay_882us (void);							/* 延时822us */
extern void Delay_250us (unsigned char t);  /* 延时时基:250us */
extern void Delay_5ms (uint16_t t);					/* 延时时基:5ms */
extern void Delay_1ms (__IO uint32_t t);		/* 延时时基:1ms */
extern void Delay_50ms (unsigned char t);		/* 延时时基:50ms */
extern void Delay(__IO uint32_t nCount);

#endif


总结

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

后续分散式管理内存部分配置

【STM32H743IIT6 系列】理清 xxRAM、xxROM、xxFlash 的核心作用,附 H7 系列五种内存详解,以及超便捷的内存区域管理方法

博客导航

博客导航

posted @ 2025-09-26 20:32  膝盖中箭卫兵  阅读(293)  评论(0)    收藏  举报
ORCID iD icon https://orcid.org/0000-0001-5102-772X