写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节包括前几节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火B站账号:野火官方B站账号链接。
《STM32F10x芯片参考手册-中文版》、《STM32F10x Cortex-M3编程手册-英文版》、《CM3权威指南-中文版》、《野火开发板资料》等;
1、简单了解SysTick;
2、写一个毫秒级的延时函数,并尝试用其闪烁LED灯;
SysTick–系统定时器,存在于内核,嵌套在NVIC,它是一个24bit的向下递减的计数器,并且每计数一次的时间为1/SYSCLK,通常情况下,STM32F103中SYSCLK使用72M;当重装载数值寄存器倒数计数到0时,系统定时器就会产生一次中断,以此循环往复;
另外提一句,这个系统定时器在《芯片参考手册-中文版》中的内容其实就一句话,就是下面这句:
系统嘀嗒校准值固定为9000,当系统嘀嗒时钟设定为9MHz(HCLK/8的最大值),产生1ms时间基准。
这里的系统滴答说的就是SysTick;
官方固件库中的关于SysTick寄存器的结构体定义
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x00 SysTick控制及状态寄存器 */
__IO uint32_t LOAD; /*!< Offset: 0x04 SysTick重装载数值寄存器 */
__IO uint32_t VAL; /*!< Offset: 0x08 SysTick当前数值寄存器 */
__I uint32_t CALIB; /*!< Offset: 0x0C SysTick校准数值寄存器 */
} SysTick_Type;
该部分内容请参考《STM32F10x Cortex-M3编程手册-英文版》;
表9-1:STK_CTRL(只有4位有效)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
16 | COUNTFLAG | RW | 0 | 如果在上次读取该寄存器后,SysTick已经计数到0,则该位置1,当读取该位后,该位将自动清0 |
2 | CLKSOURCE | RW | 0 | 选择时钟源: 0,表示选择AHB/8后的时钟作为时钟源 1,表示选择AHB作为时钟源 |
1 | TICKINT | RW | 0 | 0:SysTick倒数计数到0时,无动作 1:SysTick倒数计数到0时,产生SysTick异常请求(中断) 可以通过读取COUNTFLAG标志位是否置1来确定倒数计数是否到0 |
0 | ENABLE | RW | 0 | 0:不使能SysTick 1:使能SysTick |
上述表格中时钟源选择位有两个选项:AHB和AHB/8,这两个选项在时钟树上可以看到,下图是关于这部分的截图,没截全整个时钟树,整个时钟树请看《STM32F10x芯片参考手册-中文版》的第6章RCC的6.2中的图8:
图9-2:(黄框部分)
表9-2:STK_LOAD(前24位有效)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
[23:0] | RELOAD | RW | 0 | 用于存放当SysTick计数器启用并倒数计数到0时,需要重新装载到STK_VAL的值(预设值),最大2^24 |
表9-3:STK_VAL(前24位有效)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
[23:0] | CURRENT | RW | 0 | 包含SysTick的当前值: 执行读操作时,将返回SysTick的当前值 执行写操作时,将被清0,同时STK_CTRL寄存器中的COUNTFLAG位清0 |
本寄存器描述将英文描述截图也放出来,主要因为描述中并未像中文描述那样一目了然,下表中的中文描述也是笔者根据英文翻译和其他资料得来的,英文原文放出方便各位自己理解;
表9-3:STK_CALIB(前24位及最后两位有效)
该程序在官方库文件core_cm3.h
中有定义,其中有英文释义,这里将英文释义稍微翻译一下,以便更好的理解:
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* 判断预设值,不能大于2^24,重装载寄存器24位最多2^24 */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* 设置重装载寄存器的初值 */
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* 设置中断优先级 */
SysTick->VAL = 0; /* 设置当前数值寄存器,清除上次数据 */
// 设置系统定时器的时钟源为 AHBCLK=72M
// 使能系统定时器中断
// 使能定时器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0); /* 配置成功返回0 */
}
上述程序使用时只需要直接调用即可,调用时设置好形参数据(预设值),之后系统就会根据预设值开始计数,倒计时到0后产生中断,然后重装载数据寄存器将数据重新装载到计数器中,使其再次计数,往复循环;
另外这个函数中对中断优先级进行了设置,这个优先级实质上是内核的优先级,所以没有像外设那样的抢占优先级和子优先级的说法,不过在进行优先级比较时,可以按照外设那样进行比较,内核的优先级可以设置成0~15,共4bit,按照外设优先级的比较方式,系统预设的优先级是15,这个优先级是最低的,当然这个优先级可以手动修改,本文程序就不修改了;
/*为指定的中断设置优先级。
*中断号可以为正以指定外部(设备特定)中断,
*也可以为负以指定内部(核心)中断。*/
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* 设置Cortex-M3系统中断的优先级 */
else {
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* 设置外设中断的优先级 */
}
IRQn:中断号,其定义请看库文件stm32f10x.h中的typedef enum IRQn定义,这是一个枚举类型,其中定义了可以使用的中断编号,内容较多这里不再放出,SysTick配置库函数中使用的SysTick_IRQn就在这个枚举中定义了;
priority:优先级,在SysTick配置库函数中使用的变量声明请看下面第③点;
core_cm3.h中的部分声明如下
/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) /*!< SysTick LOAD: RELOAD Mask */
/* SysTick Control / Status Register Definitions */
#define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */
#define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */
#define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) /*!< SysTick CTRL: ENABLE Mask */
stm32f10x.h中的部分声明如下
#define __NVIC_PRIO_BITS 4 /*!< STM32 uses 4 Bits for the Priority Levels */
根据SysTick的特性以及固件库配置函数我们可以定义延时函数,这时需要用到下面两个函数,以便更加准确的设定延时时间:
t=RELOAD/CLK,
t:时间;RELOAD:重装载数值寄存器的值;CLK:系统频率,这里设定系统频率为72M;
所以可以得到以下两个时间计算公式:
t=72/72M=1us,//设置1微妙
t=72000/72M=1ms;//设置1毫秒
SysTick配置库函数SysTick_Config()可以直接调用,如果正确调用,则说明SysTick各寄存器已经配置完成,此时系统开始计时,我们只需要等待系统计时完成,然后判断标志位COUNTFLAG即可;
>>使用SysTick_Config()初始化SysTick的寄存器,并设定重装载数值寄存器的值
>>设定for循环,判断标志位,用于判断系统是否延时完成
>>延时完成,关闭SysTick使能,关闭延时
程序9-1:bsp_systick.c中的程序
#include "bsp_systick.h"
void SysTick_Delay_us(uint32_t us)//微秒定义
{
uint32_t i;//计时变量
SysTick_Config(72);//系统时钟为72M,设置72正好是1us
for(i = 0; i < us; i++)
{//计时到0以后,CRTL寄存器第16位将被置1
while(!(SysTick->CTRL & (1<<16)));//判断STK_CTRL第16位是否为1,为1则跳出循环
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;//延时完成,关掉定时器使能
}
void SysTick_Delay_ms(uint32_t ms)//微秒定义
{
uint32_t i;//计时变量
SysTick_Config(72000);//系统时钟为72M,设置72000正好是1ms
for(i = 0; i < ms; i++)
{//计时到0以后,CRTL寄存器第16位将被置1
while(!(SysTick->CTRL & (1<<16)));//判断STK_CTRL第16位是否为1,为1则跳出循环
}
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;//延时完成,关掉定时器使能
}
程序9-2:bsp_systick.h中的程序
#ifndef __BSP_SYSTICK_H
#define __BSP_SYSTICK_H
#include "stm32f10x.h"
#include "core_cm3.h"
void SysTick_Delay_us(uint32_t us);
void SysTick_Delay_ms(uint32_t ms);
#endif /*__BSP_SYSTICK_H*/
上述程序中除了调用SysTick配置库函数以外,还设置了for循环,这个循环是为了判断系统是否已经延时完成,其内部的while循环就是判断条件,当STK_CTRL第16位为1时,说明系统已经计时完成,此时可以将使能关闭,使能关闭则计时结束,如果第16为不为1,则一直循环,直到该位置1;
LED的初始化程序和主程序如下:
程序9-3:bsp_led.c
#include "bsp_led.h"
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct; //定义变量,方便赋值
RCC_APB2PeriphClockCmd(LED_G_GPIO_CLK,ENABLE); //打开APB2时钟,GPIO挂载在APB2
GPIO_InitStruct.GPIO_Pin = (LED_G_GPIO_PIN); //设置需要用到的管脚,LED_G_GPIO_PIN看.h文件
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //设置输出模式为推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //设置输出速率为50MHz,LED_G_GPIO_CLK看.h文件
GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStruct); //加上&,方便取值 //初始化GPIO
}
程序9-4:bsp_led.h
#ifndef _BSP_LED_H
#define _BSP_LED_H
#include "stm32f10x.h" //要包含固件库的.h文件
#define LED_G_GPIO_PIN GPIO_Pin_0 //定义绿灯管脚号
#define LED_G_GPIO_PORT GPIOB //定义用到的GPIO
#define LED_G_GPIO_CLK RCC_APB2Periph_GPIOB //定义RCC时钟寄存器
//下面几个声明可以不管,直接按照main中屏蔽掉的部分写也行
//下面用到的“\”符号为续行符,其后面不能由任何东西,意为这行下面的一行跟这行是一起的,分行写看起来比较清晰
#define ON 1
#define OFF 0
#define LED(a) if(a) \
GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); \
else \
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);
void LED_GPIO_Config(void); //.c文件中的函数声明
#endif /*_BSP_LED_H*/
程序9-4:main.c
#include "stm32f10x.h" // 相当于51单片机中的 #include
#include "bsp_led.h"
#include "bsp_systick.h"
int main(void)
{
// 来到这里的时候,系统的时钟已经被配置成72M。
LED_GPIO_Config(); //GPIO初始化
while(1)
{
LED(OFF); //关灯
SysTick_Delay_ms(500);
LED(ON); //开灯
SysTick_Delay_ms(500);
}
}
本文内容还存在很多不足,等再有新的内容或者是修改之处,会直接在本文添加修改,感谢观看。