完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章教程为大家讲解定时器应用之高精度单次延迟实现,支持TIM2,TIM3,TIM4和TIM5。实际项目中用到的地方较多,如Modbus帧符间隔,定时采集一段时间波形等。
35.1 初学者重要提示
35.2 定时器单次延迟驱动设置
35.3 定时器板级支持包(bsp_timer.c)
35.4 定时器驱动移植和使用
35.5 实验例程框架
35.6 实验例程说明(MDK)
35.7 实验例程说明(IAR)
35.8 总结
单次定时器要实现1us的精度,可以直接将定时器时钟设置为1MHz,这样定时器每计数1次就是1us。对于16位定时器最大值就是0xFFFF微秒,而32位定时器就是0xFFFFFFFF微秒。
剩下的问题就是单次延迟时间到了可以及时执行相应功能,那么就可以开启一个CC捕获比较中断。而延迟时间可以直接通过设置CCR比较捕获寄存器实现。比如当前定时器的计数值是1000,我们要实现10us的单次延迟,我们就可以直接设置CCR的数值为1000 + 10 =1010即可,等1010的计数值到了,就会触发CC捕获比较中断。
单次延迟支持TIM2,TIM3,TIM4和TIM5,其中TIM2和TIM5是32位定时器,而TIM3和TIM4是16位定时器。每个定时器都有4个通道,可以独立配置使用,互不影响。
/*
定义用于硬件定时器的TIM, 可以使 TIM2 - TIM5
*/
#define USE_TIM2
//#define USE_TIM3
//#define USE_TIM4
//#define USE_TIM5
#ifdef USE_TIM2
#define TIM_HARD TIM2
#define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM2_CLK_ENABLE()
#define TIM_HARD_IRQn TIM2_IRQn
#define TIM_HARD_IRQHandler TIM2_IRQHandler
#endif
#ifdef USE_TIM3
#define TIM_HARD TIM3
#define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM3_CLK_ENABLE()
#define TIM_HARD_IRQn TIM3_IRQn
#define TIM_HARD_IRQHandler TIM3_IRQHandler
#endif
#ifdef USE_TIM4
#define TIM_HARD TIM4
#define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM4_CLK_ENABLE()
#define TIM_HARD_IRQn TIM4_IRQn
#define TIM_HARD_IRQHandler TIM4_IRQHandler
#endif
#ifdef USE_TIM5
#define TIM_HARD TIM5
#define RCC_TIM_HARD_CLK_ENABLE() __HAL_RCC_TIM5_CLK_ENABLE()
#define TIM_HARD_IRQn TIM5_IRQn
#define TIM_HARD_IRQHandler TIM5_IRQHandler
#endif
/* 保存 TIM定时中断到后执行的回调函数指针 */
static void (*s_TIM_CallBack1)(void);
static void (*s_TIM_CallBack2)(void);
static void (*s_TIM_CallBack3)(void);
static void (*s_TIM_CallBack4)(void);
这里把几个关键的地方阐释下:
单次定时器的初始化代码如下:
/*
******************************************************************************************************
* 函 数 名: bsp_InitHardTimer
* 功能说明: 配置 TIMx,用于us级别硬件定时。TIMx将自由运行,永不停止.
* TIMx可以用TIM2 - TIM5 之间的TIM, 这些TIM有4个通道, 挂在 APB1 上,输入时钟
* =SystemCoreClock / 2
* 形 参: 无
* 返 回 值: 无
******************************************************************************************************
*/
void bsp_InitHardTimer(void)
{
TIM_HandleTypeDef TimHandle = {0};
uint32_t usPeriod;
uint16_t usPrescaler;
uint32_t uiTIMxCLK;
TIM_TypeDef* TIMx = TIM_HARD;
RCC_TIM_HARD_CLK_ENABLE(); /* 使能TIM时钟 */
/*-----------------------------------------------------------------------
bsp.c 文件中 void SystemClock_Config(void) 函数对时钟的配置如下:
System Clock source = PLL (HSE)
SYSCLK(Hz) = 400000000 (CPU Clock)
HCLK(Hz) = 200000000 (AXI and AHBs Clock)
AHB Prescaler = 2
D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)
D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)
D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)
因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
APB4上面的TIMxCLK没有分频,所以就是100MHz;
APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17
APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
----------------------------------------------------------------------- */
if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM15) || (TIMx == TIM16) || (TIMx == TIM17))
{
/* APB2 定时器时钟 = 200M */
uiTIMxCLK = SystemCoreClock / 2;
}
else
{
/* APB1 定时器 = 200M */
uiTIMxCLK = SystemCoreClock / 2;
}
usPrescaler = uiTIMxCLK / 1000000 - 1; /* 分频比 = 1 */
if (TIMx == TIM2 || TIMx == TIM5)
{
usPeriod = 0xFFFFFFFF;
}
else
{
usPeriod = 0xFFFF;
}
/*
设置分频为usPrescaler后,那么定时器计数器计1次就是1us
而参数usPeriod的值是决定了最大计数:
usPeriod = 0xFFFF 表示最大0xFFFF微妙。
usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微妙。
*/
TimHandle.Instance = TIMx;
TimHandle.Init.Prescaler = usPrescaler;
TimHandle.Init.Period = usPeriod;
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
{
Error_Handler(__FILE__, __LINE__);
}
/* 配置定时器中断,给CC捕获比较中断使用 */
{
HAL_NVIC_SetPriority(TIM_HARD_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(TIM_HARD_IRQn);
}
/* 启动定时器 */
HAL_TIM_Base_Start(&TimHandle);
}
这里把几个关键的地方阐释下:
usPeriod = 0xFFFF 表示最大0xFFFF微秒。
usPeriod = 0xFFFFFFFF 表示最大0xFFFFFFFF微秒。
下面是定时器的启动代码,使用TIM2-5做单次定时器使用, 定时时间到后执行回调函数。可以同时启动4个定时器,互不干扰。
/*
******************************************************************************************************
* 函 数 名: bsp_StartHardTimer
* 功能说明: 使用TIM2-5做单次定时器使用, 定时时间到后执行回调函数。可以同时启动4个定时器,互不干扰。
* 定时精度正负1us (主要耗费在调用本函数的执行时间,函数内部进行了补偿减小误差)
* TIM2和TIM5 是32位定时器。定时范围很大
* TIM3和TIM4 是16位定时器。
* 形 参: _CC : 捕获通道几,1,2,3, 4
* _uiTimeOut : 超时时间, 单位 1us. 对于16位定时器,最大 65.5ms; 对于32位定时器,最大 4294秒
* _pCallBack : 定时时间到后,被执行的函数
* 返 回 值: 无
******************************************************************************************************
*/
void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack)
{
uint32_t cnt_now;
uint32_t cnt_tar;
TIM_TypeDef* TIMx = TIM_HARD;
/* H743速度较快,无需补偿延迟,实测精度正负1us */
cnt_now = TIMx->CNT;
cnt_tar = cnt_now + _uiTimeOut; /* 计算捕获的计数器值 */
if (_CC == 1)
{
s_TIM_CallBack1 = (void (*)(void))_pCallBack;
TIMx->CCR1 = cnt_tar; /* 设置捕获比较计数器CC1 */
TIMx->SR = (uint16_t)~TIM_IT_CC1; /* 清除CC1中断标志 */
TIMx->DIER |= TIM_IT_CC1; /* 使能CC1中断 */
}
else if (_CC == 2)
{
s_TIM_CallBack2 = (void (*)(void))_pCallBack;
TIMx->CCR2 = cnt_tar; /* 设置捕获比较计数器CC2 */
TIMx->SR = (uint16_t)~TIM_IT_CC2; /* 清除CC2中断标志 */
TIMx->DIER |= TIM_IT_CC2; /* 使能CC2中断 */
}
else if (_CC == 3)
{
s_TIM_CallBack3 = (void (*)(void))_pCallBack;
TIMx->CCR3 = cnt_tar; /* 设置捕获比较计数器CC3 */
TIMx->SR = (uint16_t)~TIM_IT_CC3; /* 清除CC3中断标志 */
TIMx->DIER |= TIM_IT_CC3; /* 使能CC3中断 */
}
else if (_CC == 4)
{
s_TIM_CallBack4 = (void (*)(void))_pCallBack;
TIMx->CCR4 = cnt_tar; /* 设置捕获比较计数器CC4 */
TIMx->SR = (uint16_t)~TIM_IT_CC4; /* 清除CC4中断标志 */
TIMx->DIER |= TIM_IT_CC4; /* 使能CC4中断 */
}
else
{
return;
}
}
这里把几个关键的地方阐释下:
看了源码后,也许会有读者会问,程序里面直接将定时器计数器CNT清零后设置新的计数是否可行。答案是不行的,因为我们要实现四个通道可以同时使用,如果CNT清零,将影响其它通道的使用。
定时器中断服务程序主要用于处理 CC捕获比较中断,启动单次延迟后,时间到了将执行中断服务程序里面的回调函数。用户可以在这个回调函数里面实现要做的功能。
/*
******************************************************************************************************
* 函 数 名: TIMx_IRQHandler
* 功能说明: TIM 中断服务程序
* 形 参:无
* 返 回 值: 无
******************************************************************************************************
*/
void TIM_HARD_IRQHandler(void)
{
uint16_t itstatus = 0x0, itenable = 0x0;
TIM_TypeDef* TIMx = TIM_HARD;
itstatus = TIMx->SR & TIM_IT_CC1;
itenable = TIMx->DIER & TIM_IT_CC1;
if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
{
TIMx->SR = (uint16_t)~TIM_IT_CC1;
TIMx->DIER &= (uint16_t)~TIM_IT_CC1; /* 禁能CC1中断 */
/* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
s_TIM_CallBack1();
}
itstatus = TIMx->SR & TIM_IT_CC2;
itenable = TIMx->DIER & TIM_IT_CC2;
if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
{
TIMx->SR = (uint16_t)~TIM_IT_CC2;
TIMx->DIER &= (uint16_t)~TIM_IT_CC2; /* 禁能CC2中断 */
/* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
s_TIM_CallBack2();
}
itstatus = TIMx->SR & TIM_IT_CC3;
itenable = TIMx->DIER & TIM_IT_CC3;
if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
{
TIMx->SR = (uint16_t)~TIM_IT_CC3;
TIMx->DIER &= (uint16_t)~TIM_IT_CC3; /* 禁能CC2中断 */
/* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
s_TIM_CallBack3();
}
itstatus = TIMx->SR & TIM_IT_CC4;
itenable = TIMx->DIER & TIM_IT_CC4;
if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
{
TIMx->SR = (uint16_t)~TIM_IT_CC4;
TIMx->DIER &= (uint16_t)~TIM_IT_CC4; /* 禁能CC4中断 */
/* 先关闭中断,再执行回调函数。因为回调函数可能需要重启定时器 */
s_TIM_CallBack4();
}
}
中断服务程序里面四个通道的处理方式是一样的,这里以通道1为例进行说明。
定时器单次延迟驱动文件bsp_timer.c供用户调用的主要是如下两个函数:
注意,当用户调用了函数bsp_InitTimer,此函数里面会调用bsp_InitHardTimer,用户无需再单独调用进行初始化。
函数原型:
void bsp_InitHardTimer(void)
函数描述:
此函数主要用于初始化定时器的单次延迟功能。us级别硬件定时,TIMx将自由运行,永不停止。
注意事项:
函数原型:
void bsp_StartHardTimer(uint8_t _CC, uint32_t _uiTimeOut, void * _pCallBack)
函数描述:
使用TIM2-5做单次定时器使用, 定时时间到后执行回调函数。可以同时启动4个定时器通道,互不干扰。定时精度正负1us(主要耗费在调用本函数的执行时间)。
函数参数:
注意事项:
使用举例:
可以看本章节配套的实例。
定时器的移植比较简单:
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
配套例子:
V7-020_定时器四个比较捕获通道实现微妙级单次延迟(驱动支持TIM2-TIM5)
实验目的:
实验内容:
实验操作:
FMC扩展引脚23的位置:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
实际执行时间测量:
在不做任何误差补偿的情况下,误差在正负1微妙内,下面是延迟5微妙的实际执行时间:
下面是延迟10微妙的实际执行时间:
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init();
/*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
}
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
/* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
/*
*********************************************************************************************************
* 函 数 名: CPU_CACHE_Enable
* 功能说明: 使能L1 Cache
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache();
/* 使能 D-Cache */
SCB_EnableDCache();
}
主功能:
主程序实现如下操作:
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t ucKeyCode; /* 按键代码 */
bsp_Init(); /* 硬件初始化 */
PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */
bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
/* 进入主程序循环体 */
while (1)
{
bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
/* 判断定时器超时时间 */
if (bsp_CheckTimer(0))
{
/* 每隔100ms 进来一次 */
bsp_LedToggle(2);
}
/* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下,实现一个5微秒的单次延迟 */
bsp_StartHardTimer(1 ,5, (void *)TIM_CallBack2);
HC574_TogglePin(GPIO_PIN_23);
break;
case KEY_DOWN_K2: /* K2键按下,实现一个10微秒的单次延迟 */
bsp_StartHardTimer(1 ,10, (void *)TIM_CallBack2);
HC574_TogglePin(GPIO_PIN_23);
break;
case KEY_DOWN_K3: /* K3键按下,实现一个100微秒的单次延迟 */
bsp_StartHardTimer(1 ,100, (void *)TIM_CallBack2);
HC574_TogglePin(GPIO_PIN_23);
break;
default:
/* 其它的键值不处理 */
break;
}
}
}
}
注意回调函数的处理:
/*
*********************************************************************************************************
* 函 数 名: TIM_CallBack2
* 功能说明: 定时器中断的回调函数,此函数被bsp_StartHardTimer所调用。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void TIM_CallBack2(void)
{
HC574_TogglePin(GPIO_PIN_23);
bsp_LedToggle(4);
}
配套例子:
V7-020_定时器四个比较捕获通道实现微妙级单次延迟(驱动支持TIM2-TIM5)
实验目的:
实验内容:
实验操作:
FMC扩展引脚23的位置:
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
实际执行时间测量:
在不做任何误差补偿的情况下,误差在正负1微妙内,下面是延迟5微妙的实际执行时间:
下面是延迟10微妙的实际执行时间:
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init();
/*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitTimer(); /* 初始化滴答定时器 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
}
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
/*
*********************************************************************************************************
* 函 数 名: MPU_Config
* 功能说明: 配置MPU
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
/* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
/*
*********************************************************************************************************
* 函 数 名: CPU_CACHE_Enable
* 功能说明: 使能L1 Cache
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
/* 使能 I-Cache */
SCB_EnableICache();
/* 使能 D-Cache */
SCB_EnableDCache();
}
主功能:
主程序实现如下操作:
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: c程序入口
* 形 参: 无
* 返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
uint8_t ucKeyCode; /* 按键代码 */
bsp_Init(); /* 硬件初始化 */
PrintfLogo(); /* 打印例程名称和版本等信息 */
PrintfHelp(); /* 打印操作提示 */
bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
/* 进入主程序循环体 */
while (1)
{
bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
/* 判断定时器超时时间 */
if (bsp_CheckTimer(0))
{
/* 每隔100ms 进来一次 */
bsp_LedToggle(2);
}
/* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
ucKeyCode = bsp_GetKey(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下,实现一个5微秒的单次延迟 */
bsp_StartHardTimer(1 ,5, (void *)TIM_CallBack2);
HC574_TogglePin(GPIO_PIN_23);
break;
case KEY_DOWN_K2: /* K2键按下,实现一个10微秒的单次延迟 */
bsp_StartHardTimer(1 ,10, (void *)TIM_CallBack2);
HC574_TogglePin(GPIO_PIN_23);
break;
case KEY_DOWN_K3: /* K3键按下,实现一个100微秒的单次延迟 */
bsp_StartHardTimer(1 ,100, (void *)TIM_CallBack2);
HC574_TogglePin(GPIO_PIN_23);
break;
default:
/* 其它的键值不处理 */
break;
}
}
}
}
注意回调函数的处理:
/*
*********************************************************************************************************
* 函 数 名: TIM_CallBack2
* 功能说明: 定时器中断的回调函数,此函数被bsp_StartHardTimer所调用。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void TIM_CallBack2(void)
{
HC574_TogglePin(GPIO_PIN_23);
bsp_LedToggle(4);
}
本章节就为大家讲解这么多,单次延迟在实际项目中用到的地方较多,如Modbus帧符间隔,定时采集一段时间波形等,望初学者务必掌握。