完美解決STM32F407ZGT6使用Systic定時器實現延時
預備知識
STM32F4的系統滴答計時器的介紹及其說明。時間有限,這裡點到為止,詳情自行百度。
延時的原理:
因為在 ucos 下 systick 不能再被隨意更改,如果我們還想利用 systick 來做 delay_us 或者
delay_ms 的延時,就必須想點辦法了,這裡我們利用的是時鐘摘取法。
---這裡摘自正點原子探索者F4,詳細原理請自行百度。
實驗目標
使用Systic計時器實現精準延時,且不佔用OS中斷
延時系統具有通用性,可適用多種系統頻率
重點分析
本程式流程如下:
初始化其它相關硬體->獲取HCLK時鐘頻率->用HCLK時鐘頻率初始化Systic計時器->其它操作實現延時
問題診斷
剛開始測試的時候發現了一個問題,如下描述:
由於該程式修改自正點原子的原始碼,原始碼中系統時鐘是預先定義好的,使用起來沒有問題,但這裡系統頻率是呼叫庫函式實現的,雖然具有通用信,但卻存在問題。
庫函式中獲取時鐘頻率的函式RCC_GetClocksFreq正確使用有一個條件,就是外部時鐘25MHz
我們看一看該 函式的部分說明:
HSE_VALUE is a constant defined in stm32f4xx.h file (default value
25 MHz), user has to ensure that HSE_VALUE is same as the real
frequency of the crystal used. Otherwise, this function may
have wrong result.
意思就是外部晶振不為25MHz時算出的頻率會出錯。
表現在該工程中就是延時的時間是標準時間的三倍左右,即25/8
那麼如何解決這個問題?很簡單
stm32f4xx.h做以下修改
原始碼:
123行: #define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
改為:123行: #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
這裡是指預設的外部高速時鐘25Mhz,實際為8MHz,這裡為了使庫函式獲取的時鐘更準確
修改後,測試基本符合。
工程原始碼:
Delay.h
/**
******************************************************************************
* @file Delay.h
* @author 李航兵
* @version V1.2
* @date 18-June-2018
* @brief 這個標頭檔案包含了STM32F407ZGT6開發板的延時相關的函式.
******************************************************************************
* @attention
* 官方庫檔案裡有參考程式碼
* 為保證延時系統的精準,要求系統主頻率越高越好
* 為保證延時系統的完整支援,系統主頻率至少8MHz
* 使用CM4系統定時器,中斷優先順序最低
*
* @更新說明
* 支援us、ms級延時,且精度大大提高
* 支援OS,基本不影響OS的使用(下個版本新增)
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __DELAY_H
#define __DELAY_H
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_rcc.h"
#include "core_cm4.h"
#include "stm32f4xx_it.h"
#include "misc.h"
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
void Delay_Init(void); //延時系統初始化
void Delay_Us(u16 us); //微秒延時
void Delay_Ms(u16 ms); //毫秒延時
void Delay_Us_2(u32 us); //微秒延時,範圍更大
#endif /* __DELAY_H */
/************************ (C) COPYRIGHT 李航兵 *****END OF FILE****/
Led.h
/**
******************************************************************************
* @file Led.h
* @author 李航兵
* @version V1.0
* @date 14-June-2018
* @brief 這個標頭檔案包含了STM32F407ZGT6開發板的Led相關的函式.
******************************************************************************
* @attention
*硬體連線:
* Led1:PC0-VCC
* Led1:PD3-VCC
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __LED_H
#define __LED_H
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
/* Exported functions ------------------------------------------------------- */
void Led_Init(void); //Led初始化
void Led_On(u8 led); //開燈
void Led_Off(u8 led); //關燈
#endif /* __LED_H */
/************************ (C) COPYRIGHT 李航兵 *****END OF FILE****/
Delay.c
/**
******************************************************************************
* @file Delay.c
* @author 李航兵
* @version V1.2
* @date 18-June-2018
* @brief 這個檔案包含以下函式用於操作STM32F407ZGT6開發板的延時功能
* + 延時系統初始化
* + 微秒延時
* + 毫秒延時
*
* @verbatim
*
*
===========================================================================
##### How to use this driver #####
===========================================================================
[..]
(#) 初始化延時系統,呼叫Delay_Init()
(#) 延時
(++) 毫秒: Delay_Ms()
(++) 微秒: Delay_Init()
@endverbatim
******************************************************************************
* @attention
* 官方庫檔案裡有參考程式碼
* 為保證延時系統的精準,要求系統主頻率越高越好
* 為保證延時系統的完整支援,系統主頻率至少8MHz
* 使用CM4系統定時器,中斷優先順序最低
* 使用此檔案後,禁止再修改SysTick計時器及其設定
*
*
*
*
*
*
* <h2><center>© COPYRIGHT 2018 李航兵</center></h2>
* @更新說明
* 支援us、ms級延時,且精度大大提高
* 支援OS,基本不影響OS的使用(下個版本新增)
*
*
*
*
*
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "Delay.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
u8 per_us; //每1us定時器節拍
u32 per_ms; //每1ms節拍,注意168MHz下值為168000,需要32位,移植自STM32F0,此處謹慎
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief 延時系統初始化.
* @param None.
* @retval None
*/
void Delay_Init()
{
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
//這裡有個問題,本想自動化獲取時鐘頻率的,不料在該函式中有如下宣告
/*
HSE_VALUE is a constant defined in stm32f4xx.h file (default value
* 25 MHz), user has to ensure that HSE_VALUE is same as the real
* frequency of the crystal used. Otherwise, this function may
* have wrong result.
*/
//也就是後來測試時發現延時總是為輸入的3倍左右
//後修改了庫檔案,可參看對應目錄的readme檔案獲取修改記錄
if(SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000)) //1ms一次中斷
while(1);
per_ms=SysTick->LOAD; //每1ms節拍,亦即過載值
per_us=per_ms/1000; //每1us定時器節拍
}
/**
* @brief 微秒延時.
* @param 延時的微秒數,約定範圍1~390,“禁止其他值”.
* @note 存在一定誤差,主要是函式呼叫+部分計算.
* @retval None
*/
void Delay_Us(u16 us) //微秒延時
{
u32 ticks_old=SysTick->VAL; //前一個計數值
u32 ticks_new; //後一個計數值
u16 ticks_sum=0; //已經經過的節拍
u16 ticks_delta=us*per_us; //需要經過的節拍
if(us>390) return; //計時不允許超過390us,超過390us請使用Delay_Us_2
while(1)
{
ticks_new=SysTick->VAL;
if(ticks_new!=ticks_old)
{
if(ticks_new<ticks_old)ticks_sum+=ticks_old-ticks_new; //這裡注意一下SYSTICK是一個遞減的計數器就可以了.
else ticks_sum+=per_ms-ticks_new+ticks_old;
ticks_old=ticks_new;
if(ticks_sum>=ticks_delta)break; //時間超過/等於要延遲的時間,則退出.
}
}
}
/**
* @brief 毫秒延時.
* @param 延時的毫秒數,約定範圍1~25000,“禁止其他值”.
* @note 存在一定誤差,主要是函式呼叫+部分計算.
* @retval None
*/
void Delay_Ms(u16 ms) //毫秒延時
{
u32 ticks_old=SysTick->VAL; //前一個計數值
u32 ticks_new; //後一個計數值
u32 ticks_sum=0; //已經經過的節拍
u32 ticks_delta=ms*per_ms; //需要經過的節拍
if(ms>25000) return; //計時不允許超過25000ms,超過25000ms請多次使用
while(1)
{
ticks_new=SysTick->VAL;
if(ticks_new!=ticks_old)
{
if(ticks_new<ticks_old)ticks_sum+=ticks_old-ticks_new; //這裡注意一下SYSTICK是一個遞減的計數器就可以了.
else ticks_sum+=per_ms-ticks_new+ticks_old;
ticks_old=ticks_new;
if(ticks_sum>=ticks_delta)break; //時間超過/等於要延遲的時間,則退出.
}
}
}
/**
* @brief 微秒延時,範圍更大.
* @param 延時的微秒數,約定範圍1~25000000,“禁止其他值”.
* @note 存在一定誤差,主要是函式呼叫+部分計算.
* @retval None
*/
void Delay_Us_2(u32 us) //微秒延時,範圍更大
{
u32 ticks_old=SysTick->VAL; //前一個計數值
u32 ticks_new; //後一個計數值
u32 ticks_sum=0; //已經經過的節拍
u32 ticks_delta=us*per_us; //需要經過的節拍
if(us>25000000) return; //計時不允許超過25000000us
while(1)
{
ticks_new=SysTick->VAL;
if(ticks_new!=ticks_old)
{
if(ticks_new<ticks_old)ticks_sum+=ticks_old-ticks_new; //這裡注意一下SYSTICK是一個遞減的計數器就可以了.
else ticks_sum+=per_ms-ticks_new+ticks_old;
ticks_old=ticks_new;
if(ticks_sum>=ticks_delta)break; //時間超過/等於要延遲的時間,則退出.
}
}
}
/**
* @brief 系統定時器中斷.
* @param None.
* @note 一般給OS用.
* @retval None
*/
void SysTick_Handler(void) //系統定時器中斷
{
}
Led.c
/**
******************************************************************************
* @file Led.c
* @author 李航兵
* @version V1.0
* @date 14-June-2018
* @brief 這個文件包含了Led功能相關的函式.
******************************************************************************
* @brief 這個檔案包含以下函式用於操作STM32F407ZGT6開發板的Led燈
* + 初始化Led
* + 開燈
* + 關燈
*
* @verbatim
*
*
===========================================================================
##### How to use this driver #####
===========================================================================
[..]
(#) 初始化Led,呼叫Led_Init()
(#) 控制燈的亮滅
(++) 開燈: Led_On
(++) 關燈: Led_Off
@endverbatim
******************************************************************************
* @attention
*
* <h2><center>© COPYRIGHT 2018 李航兵</center></h2>
*硬體連線:
* Led1:PC0-VCC
* Led1:PD3-VCC
*使用該檔案後,PC0和PD3將會被佔用
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "Led.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief Led初始化.
* @note PC0和PD3將會被佔用
* @note 呼叫此函式會開啟GPIOC和啟GPIOD時鐘.
* @param None
* @retval None
*/
void Led_Init() //Led初始化
{
GPIO_InitTypeDef GPIO_Led;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
GPIO_Led.GPIO_Mode=GPIO_Mode_OUT;
GPIO_Led.GPIO_OType=GPIO_OType_PP;
GPIO_Led.GPIO_Pin=GPIO_Pin_0;
GPIO_Led.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_Led.GPIO_Speed=GPIO_High_Speed;
GPIO_Init(GPIOC,&GPIO_Led);
GPIO_Led.GPIO_Pin=GPIO_Pin_3;
GPIO_Init(GPIOD,&GPIO_Led);
Led_Off(1);
Led_Off(2);
}
/**
* @brief 開燈.
* @note
* @param led:Led的編號,可取1、2
* @retval None
*/
void Led_On(u8 led) //開燈
{
switch(led)
{
case 1:
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
break;
case 2:
GPIO_ResetBits(GPIOD,GPIO_Pin_3);
break;
}
}
/**
* @brief 關燈.
* @note
* @param led:Led的編號,可取1、2
* @retval None
*/
void Led_Off(u8 led) //關燈
{
switch(led)
{
case 1:
GPIO_SetBits(GPIOC,GPIO_Pin_0);
break;
case 2:
GPIO_SetBits(GPIOD,GPIO_Pin_3);
break;
}
}
/************************ (C) COPYRIGHT 李航兵 *****END OF FILE****/
Delay_Test.c
#include "Delay.h"
#include "Led.h"
void main()
{
int i;
Led_Init();
Delay_Init();
while(1)
{
Led_On(1);
for(i=0;i<1000;i++)
Delay_Us(300);
Led_Off(1);
Delay_Ms(1000);
Led_On(2);
Delay_Us_2(1000000);
Led_Off(2);
Delay_Ms(1000);
}
}
//void main()
//{
// int i;
// Led_Init();
// Delay_Init();
// while(1)
// {
// Led_Off(1);
// for(i=0;i<1000;i++)
// {
// Delay_Us(300);
// Delay_Us(300);
// Delay_Us(300);
// Delay_Us(100);
// }
// Led_On(1);
//
// for(i=0;i<1000;i++)
// {
// Delay_Us(300);
// Delay_Us(300);
// Delay_Us(300);
// Delay_Us(100);
// }
// }
//}
//void main()
//{
// int i;
// Led_Init();
// Delay_Init();
// while(1)
// {
// Led_Off(1);
// Delay_Ms(1000);
// Led_On(1);
//
// Delay_Ms(1000);
// }
//}
stm32f4xx.h這裡修改的地方前面已經說明
結果分析
經過測試,延時1s的時間基本準確