【STM32H743IIT6 系列】基于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一下查看变量的值,可以看到已经移植成功了,还是非常简单的。
附加(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 系列五种内存详解,以及超便捷的内存区域管理方法
博客导航
本文来自博客园,作者:膝盖中箭卫兵,转载请注明原文链接:https://www.cnblogs.com/Skyrim-sssuuu/p/19114196