day15:系统定时器Systick
SysTick:系统定时器,24位,只能递减,存在于内核,嵌套在NVIC中,所有的Cortex-M内核的单片机都具有这个定时器。
counter在时钟的驱动下,从reload初值开始往下递减计数到0,产生中断和置位COUNTFLAG标志。然后又从reload值开始重新递减计数,如此循环。
代码:
1、拷贝固件库编程模板目录:FWLIB-Template,复制day11的模板:https://www.cnblogs.com/josephcnblog/articles/8955631.html
2、工程结构
3、代码
(1)bsp_led.h
/* 和LED功能模块相关的程序 */ #ifndef __BSP_LED_H__ #define __BSP_LED_H__ #include "stm32f10x.h" /*宏定义*/ #define GPIO_CLK_D4 RCC_APB2Periph_GPIOC // 时钟 #define GPIO_PORT_D4 GPIOC // C端口 #define GPIO_PIN_D4 GPIO_Pin_2 // PC2引脚 /*参数宏定义*/ /* digitalTOGGLE(p,i)是参数宏定义,p表示LED的端口号,ODR是数据输出寄存器, 查stm32f10x的官方中文手册的第8.2章的ODR寄存器,要点亮LED,根据原理图,要输出低电平0, C语言中,^表示异或,即a^b表示a和b不同时输出为1,相同时输出为0,比如0^1=1,1^1=0,0^0=0, 这里为什么操作ODR,p是什么?查看stm32f10x.h文件,搜索GPIO_TypeDef就会明白, i是LED的引脚对应的位电平,经过digitalTOGGLE(p,i) {p->ODR ^= i;}之后, 第一次p为0,i一直为1,第一次异或结果输出1,第二次输出0,第三次输出1,这样间断输出010101,灯不断亮灭 */ #define digitalTOGGLE(p,i) {p->ODR ^= i;} #define LED1_TOGGLE digitalTOGGLE(GPIO_PORT_D4,GPIO_PIN_D4) /*配置GPIO*/ void LED_GPIO_Config(void); #endif /*__BSP_LED_H__*/
(2)bsp_led.c
/* 和LED功能模块相关的程序头文件 */ #include "./led/bsp_led.h" /* 绝对路径,也可在Options for target中设置头文件 */ /* GPIO初始化 */ void LED_GPIO_Config(void) { /*外设结构体*/ GPIO_InitTypeDef GPIO_InitStruct_D4; /*第一步:打开外设的时钟,看stm32f10x_rcc.c这个文件的RCC_APB2PeriphClockCmd函数介绍 */ RCC_APB2PeriphClockCmd(GPIO_CLK_D4, ENABLE); /* 第二步:配置外设的初始化结构体 */ GPIO_InitStruct_D4.GPIO_Pin = GPIO_PIN_D4; // PC2的那盏LED灯(D4)的引脚 GPIO_InitStruct_D4.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式 GPIO_InitStruct_D4.GPIO_Speed = GPIO_Speed_10MHz; // 引脚速率 /* 第三步:调用外设初始化函数,把配置好的结构体成员写到寄存器里面 */ GPIO_Init(GPIO_PORT_D4, &GPIO_InitStruct_D4); }
(3)bsp_systick.h
#ifndef __BSP_SYSTICK_H__ #define __BSP_SYSTICK_H__ #include "stm32f10x.h" /* 精确延迟ms毫秒(这里不是软件延迟,软件延迟不精确,这里是硬件延迟,是系统中断Systick延迟) */ void SysTick_Delay_ms(uint32_t ms); #endif /* __BSP_SYSTICK_H__ */
(4)bsp_systick.c
#include "./systick/bsp_systick.h" #if 0 /* 以下代码只是用于解读,不算真正代码,这里不编译! */ /* 这个函数在core_cm3.h文件中第1694行处定义 */ static __INLINE uint32_t SysTick_Config(uint32_t ticks) { /* reload 寄存器为24位,最大值为2^24 */ if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); // 如果传入的值大于2^24,则结束函数 /* 初始化reload寄存器的值:ticks & SysTick_LOAD_RELOAD_Msk = ticks SysTick->LOAD:是SysTick系统定时器的重装载值,-1是因为从0开始计数 */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* 配置systick的中断优先级 */ NVIC_SetPriority(SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* 计数器值清0,SysTick->VAL:是系统定时器的当前值 */ SysTick->VAL = 0; /* (1)配置systick的时钟为AHB的时钟,查底层代码,SysTick_CTRL_CLKSOURCE_Msk是1<<2,即100 (2)使能systick的中断,SysTick_CTRL_TICKINT_Msk是1<<1,即10 (3)使能systick,SysTick_CTRL_ENABLE_Msk是1<<0,即1 故:0100|0010|0001=0111,SysTick->CTRL是32位的,前面都是0,后面四位是0111 */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0); } #endif /* 毫秒延迟函数 */ void SysTick_Delay_ms(uint32_t ms) { uint32_t i; /* 机器周期是1/72M,72000即(1/72M)*72000=1000us=1ms,即一个Systick中断将产生1ms延迟的时钟 */ SysTick_Config(72000); /* 循环ms次,每次循环产生1毫秒的延迟时间,即ms毫秒的延迟时间 1、SysTick->CTRL:获取到系统时钟的CRTL寄存器(控制与状态寄存器),这个寄存器的特点是 当Systick计数到0时,位16的COUNTFLAG置1; 2、1<<16:即1000 0000 0000 0000; 3、(SysTick->CTRL)&(1<<16):表示如果CTRL的位16(即最高位)为0时,结果的最高位为0, 当CTRL的位16为1时,结果的最高位为1,所以结果取决于CTRL的最高位为0还是为1,因为 SysTick_Config(72000);已经配置了每次中断延迟为1ms,下面的while循环会不断地读取系统的 CTRL值,当其值为0时,也即1ms的计数到了, 则SysTick->CTRL的位16返回1(查STM32F10xxx Cortex-M3编程手册-英文版.pdf的4.5.1小结,在本篇结尾有该文件的截图), 所以while(条件)的条件为假,退出循环,进行下一次循环,这样每次循环都延迟1ms(通过系统Systick 的中断延迟来产生的延迟时间),循环500次就产生了500ms的时间。 */ for(i=0; i<ms; i++) { while(!((SysTick->CTRL)&(1<<16))); } /* 让SysTick->CTRL失能,查STM32F10xxx Cortex-M3编程手册-英文版.pdf的4.5.1小结可知, SysTick->CTRL寄存器的位0为0时,寄存器为disabled,为1时寄存器为enabled,这里让其 失能,让所有位均置0,所以是:与或等于取反,用符号表示为:&=~1 SysTick_CTRL_ENABLE_Msk:默认为1,~1即为0,经过&=之后,所有位均被置0 */ SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; }
(5)main.c
/* 编写一个毫秒和微秒延迟函数,让LED灯不断闪烁。微秒几乎用不上 */ #include "stm32f10x.h" #include "./systick/bsp_systick.h" #include "./led/bsp_led.h" int main(void) { // LED初始化 LED_GPIO_Config(); /* D4的LED会相隔500ms不断闪烁 */ while(1) { /* 配置ODR寄存器 */ GPIO_SetBits(GPIO_PORT_D4, GPIO_PIN_D4); // D4的LED SysTick_Delay_ms(500); // 延迟500ms GPIO_ResetBits(GPIO_PORT_D4, GPIO_PIN_D4); // D4的LED SysTick_Delay_ms(500); // 延迟500ms } }
实验现象:D4的那盏LED灯隔500ms不断闪烁
================================== 以上是使用SysTick->CTRL的位段16(COUNTFLAG)来进行延迟
================================== 下面使用中断服务函数来产生中断
(1)在bsp_systick.h中添加下面的函数
/* 使用中断服务函数来延迟 */ void SysTick_Delay_ms_INT(uint32_t ms);
(2)在bsp_systick.c中添加
/* ---------- 使用中断服务函数来产生中断 ---------- */ /* 全局变量,表示1ms的计数值 volatile:CPU在读取变量的时候,第一次是从内存中读取的,之后就不再从内存读取了,它会将 此变量存放到缓存中,直接从缓存中读取,这里使用volatile,是避免CPU从缓存中读取变量,这样 可能会产生计数值isr_ms的脏读(计数来不及更新,读的是未更新前的数据),所以加上volatile, 表示让CPU每次都从内存中读取isr_ms的变量值,这样每次读到的都是更新后的最新的计数值。 */ volatile uint32_t isr_ms; void SysTick_Delay_ms_INT(uint32_t ms) { /* 变量isr_ms的初始值 */ isr_ms = ms; /* 配置中断延迟时间为1ms */ SysTick_Config(72000); /* 在stm32f10x_it.c文件中的中断服务函数SysTick_Handler()中 每次产生中断,isr_ms变量就自减,直到isr_ms为0时跳出循环,这时完成了ms次中断, 产生了ms毫秒的中断延迟时间。 */ while(isr_ms); /* 失能systick */ SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; }
(3) 在stm32f10x_it.c的SysTick_Handler(void)函数中添加,第138行
/** * @brief This function handles SysTick Handler. * @param None * @retval None */ /* extern修饰变量表示此变量已经在其他文件中定义了 */ extern volatile uint32_t isr_ms; /* 此变量在bsp_systick.c中声明(第79行) */ void SysTick_Handler(void) { /* 每产生一次中断,isr_ms值就减1 */ isr_ms--; }
(4)修改main.c
/* 编写一个毫秒和微秒延迟函数,让LED灯不断闪烁。微秒几乎用不上 */ #include "stm32f10x.h" #include "./systick/bsp_systick.h" #include "./led/bsp_led.h" int main(void) { // LED初始化 LED_GPIO_Config(); /* D4的LED会相隔500ms不断闪烁 */ while(1) { /* 配置ODR寄存器 */ GPIO_SetBits(GPIO_PORT_D4, GPIO_PIN_D4); // D4的LED //SysTick_Delay_ms(500); // 延迟500ms SysTick_Delay_ms_INT(500); GPIO_ResetBits(GPIO_PORT_D4, GPIO_PIN_D4); // D4的LED //SysTick_Delay_ms(500); // 延迟500ms SysTick_Delay_ms_INT(500); } }
实验现象:将程序烧录到单片机,实验现象和上面的使用Systick->CTRL的标志位16的COUNTFLAG判断0或1来产生中断的结果一样
每次隔500ms产生亮或灭
【 附件 】
STM32F10xxx Cortex-M3编程手册-英文版.pdf