见博客:stm32f103c8t6新建固件库模板(可自取)
实验程序已经发布到百度网盘,本文末有链接可以自取
中断查看这篇博客STM32中断应用概括
一、利用固件库模板点灯(附模板及案例程序
SysTick-系统定时器是属于CM3的一个外设,内嵌在NVIC中,系统定时器是一个24bit的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK,我们一般设置SYSCLK的值为72M。当重装载数值寄存器的值递减到0,系统定时器就产生一次中断,
SysTick-系统定时器有4个寄存器,只需要配置前三个寄存器,最后一个是校准寄存器不需要使用。
寄存器名称 | 寄存器描述 |
---|---|
CTRL | SysTick控制及状态寄存器 |
LOAD | SysTick重装载数值寄存器 |
VAl | SysTick当前数值寄存器 |
CALIB | SysTick校准数值寄存器 |
利用SysTick产生一秒的延时,LED以1s的频率闪烁
stm32f103c8t6自带PC13的LED
设置重装载寄存器的值
清除当前数值寄存器的值
配置控制及状态寄存器
一、利用固件库模板点灯(附模板及案例程序)
SysTick 配置库函数
// 这个 固件库函数 在 core_cm3.h 中
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
// reload 寄存器为 24bit,最大值为 2^24
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
// 配置 reload 寄存器的初始值
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
// 配置中断优先级为 1<<4 -1 = 15,优先级为最低
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
// 配置 counter 计数器的值
SysTick->VAL = 0;
// 配置 systick 的时钟为 72M
// 使能中断
// 使能 systick
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
sysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
用固件库编程的时候我们只需要调用库函数 SysTick_Config()
即可,形参 ticks 用来设置重装载寄存器的值,最大不能超过重装载寄存器的值 224,当重装载寄存器的值递减到 0 的时候产生中断,然后重装载寄存器的值又重新装载往下递减计数,以此循环往复。紧随其后设置好中断优先级,最后配置系统定时器的时钟等于 AHBCLK=72M,使能定时器和定时器中断,这样系统定时器就配置好了,一个库函数搞定。SysTick_Config() 库函数主要配置了 SysTick 中的三个寄存器: LOAD、 VAL 和 CTRL,有关具体的部分看代码注释即可。
配置SysTick 中断优先级
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if ((int32_t)IRQn < 0) {
SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] =(uint8_t) ((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
} else {
NVIC->IP[((uint32_t)(int32_t)IRQn)] =(uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}
函数首先先判断形参 IRQn 的大小,如果是小于 0,则表示这个是系统异常,系统异常的优先级由内核外设 SCB 的寄存器 SHPRx 控制,如果大于 0 则是外部中断,外部中断的优先级由内核外设 NVIC 中的 IPx 寄存器控制。因为 SysTick 属于内核外设,跟普通外设的中断优先级有些区别,并没有抢占优先级和子优先级的说法。在 STM32F103 中,内核外设的中断优先级由内核 SCB 这个外设的寄存器: SHPRx(x=1.2.3)来配置。有关 SHPRx 寄存器的详细描述可参考《Cortex-M3 内核编程手册》 4.4.8 章节。
下面我们简单介绍下这个寄存器。
SPRH1-SPRH3 是一个 32 位的寄存器,但是只能通过字节访问,每 8 个字段控制着一个内核外设的中断优先级的配置。在 STM32F103 中,只有位 7:4 这高四位有效,低四位没有用到,所以内核外设的中断优先级可编程为: 0~15
,只有 16 个可编程优先级,数值越小,优先级越高。如果软件优先级配置相同,那就根据他们在中断向量表里面的位置编号来决定优先级大小,编号越小,优先级越高。
如果我同时使用了 systick 和片上外设呢?而且片上外设也刚好需要使用中断,那 systick 的中断优先级跟外设的中断优先级怎么设置?会不会因为 systick 是内核里面的外设,所以它的中断优先级就一定比内核之外的外设的优先级高?
外设在设置中断优先级的时候,首先要分组
,然后设置抢占优先级和子优先级
。而 systick 这类内核的外设在配置的时候,只需要配置一个寄存器即可,取值范围为 0~15。既然配置方法不同,那如何区分两者的优先级?下面举例说明。
比如配置一个外设的中断优先级分组为 2,抢占优先级为 1,子优先级也为 1, systick 的优先级为固件库默认配置的 15。当我们比较内核外设和片上外设的中断优先级的时候,我们只需要抓住NVIC 的中断优先级分组不仅对片上外设有效,同样对内核的外设也有效。我们把 systick 的优先级 15 转换成二进制值就是 1111(0b),又因为 NVIC 的优先级分组 2,那么前两位的 11(0b) 就是3,后两位的 11(0b) 也是 3。无论从抢占还是子优先级都比我们设定的外设的优先级低。如果当两个的软件优先级都配置成一样,那么就比较他们在中断向量表中的硬件编号,编号越小,优先级越高。
中断在之前STM32中断应用概括已经讲过,不清楚的可以继续查看。
#include "sysTick.h"
/**
* @brief 启动系统滴答定时器 SysTick
* @param 无
* @retval 无
*/
void SysTick_Init( void )
{
/* SystemFrequency / 1000 1ms中断一次
* SystemFrequency / 100000 10us中断一次
* SystemFrequency / 1000000 1us中断一次
*/
if ( SysTick_Config(SystemCoreClock / 100) )//10ms
{
/* Capture error */
while (1);
}
// 关闭滴答定时器
//SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
// 使能滴答定时器 10ms中断一次
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
SysTick 定时器的计数器是向下递减计数的,计数一次的时间 TDEC=1/CLKAHB,当重装载寄存器中的值 VALUELOAD 减到 0 的时候,产生中断,可知中断一次的时间 TINT=VALUELOAD*TDEC=VALUELOAD/CLKAHB,其中 CLKAHB =72MHZ。如果设置 VALUELOAD 为 72,那中断一次的时间TINT=72/72M=1us。不过 1us 的中断没啥意义,整个程序的重心都花在进出中断上了,根本没有时间处理其他的任务 。
SysTick_Config()的形我们配置为 SystemCoreClock / 1000000=72M/100000=72
,从刚刚分析我们知道这个形参的值最终是写到重装载寄存器 LOAD 中的,从而可知我们现在把 SysTick 定时器中断一次的时间 TINT=72/72M=1us
。
微秒延时
void CPU_TS_Tmr_Delay_US( __IO uint32_t us)
{
uint32_t i;
SysTick_Config(SystemCoreClock/1000000);///1000 h毫秒
for (i=0; i<us; i++) {
// 当计数器的值减小到 0 的时候, CRTL 寄存器的位 16 会置 1
while ( !((SysTick->CTRL)&(1<<16)) );
}
// 关闭 SysTick 定时器
SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
}
这个就是delay的延时函数,使用SysTick延时
#define Delay_ms(ms) CPU_TS_Tmr_Delay_MS(ms)
#define Delay_us(us) CPU_TS_Tmr_Delay_US(us)
#define Delay_s(s) CPU_TS_Tmr_Delay_S(s)
void CPU_TS_Tmr_Delay_US(uint32_t us);
#define CPU_TS_Tmr_Delay_MS(ms) CPU_TS_Tmr_Delay_US(ms*1000)
#define CPU_TS_Tmr_Delay_S(s) CPU_TS_Tmr_Delay_MS(s*1000)
#define TASK_DELAY_NUM 2 //总任务个数,可以自己根据实际情况修改
#define TASK_DELAY_0 100 //任务0延时 100*10毫秒
#define TASK_DELAY_1 10 //任务1延时 10*10毫秒后执行:
uint32_t Task_Delay_Group[TASK_DELAY_NUM]; //任务数组,用来计时、并判断是否执行对应任务
// - 标志置 1表示完成读取,在主循环处理数据
// - 标志置 0表示未完成读取
// - 标志置-1表示读取错误
int read_LED_finish;
void SysTick_Handler(void)
{
//让任务计数,中断一次任务组分别加一,等到判断与设定的任务时间一致时进入任务
int i;
for(i=0; i<TASK_DELAY_NUM; i++)
{
Task_Delay_Group[i] ++; //任务计时,时间到后执行
}
/* 处理任务0,判断 */
if(Task_Delay_Group[0] >= TASK_DELAY_0) //判断是否执行任务0
{
Task_Delay_Group[0] = 0; //置0重新计时
read_LED_finish=1;
}
/* 处理任务1 */
if(Task_Delay_Group[1] >= TASK_DELAY_1) //判断是否执行任务1
{
//任务1代码
}
}
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "stm32f10x.h"
#include "led.h"
#include "sysTick.h"
extern int read_LED_finish;
int main()
{
/********************************************************************************
* Delay_init(); //本实验使用的是SysTick时钟
* CPU_TS_TmrInit(); //已经使能宏,不需要初始化
* uart1_init(115200); //串口初始化为115200,需要在usart.h中使能
* uart3_init(115200); //串口初始化为115200
********************************************************************************/
/* 初始化 */
LED_GPIO_Config();
SysTick_Init();
while(1)
{
//1.如果中断时间到达,设置中断
if(read_LED_finish)
{
GPIOC->ODR^=GPIO_Pin_13; //反转电平
read_LED_finish=0; //清除标志
}
// //2.通过SysTick的延时函数
// PCout(13)=0;
// Delay_s(1); //已经在delay.h中初始化
// PCout(13)=1;
// Delay_s(1);
}
}
主函数中初始化了 LED 和 SysTick,然后在一个 while 循环中以判断SysTick的中断函数标志read_LED_finish
让 LED 闪烁。
另外一种更简洁的定时编程
使用SysTick的延时函数。我敢肯定这样的写法,初学者肯定会更喜欢,因为它直接,套路浅。
本文选择的是ST_Link烧录工具
如果没有ID号看博客:ST-Link V2烧录问题(已解决)
链接:https://pan.baidu.com/s/1pRNl-S8fGg0nK88D238yAg 提取码:0000