ARM Cortex-M学习笔记:初识Systick定时器

开发环境:
mdk:凯尔 5.30
stm32立方体mx:v6.4.0
单片机:stm32f103zet6
cortex-m的内核中包含systick定时器了,只要是cortex-m系列的mcu就会有systick,因此这是通用的,下面详细分析。
4.1 systick工作原理分析systick 定时器被捆绑在 nvic 中,用于产生 systick 异常(异常号:15)。 在以前,操作系统和所有使用了时基的系统都必须有一个硬件定时器来产生需要的“滴答”中断,作为整个系统的时基。 滴答中断对操作系统尤其重要。 例如,操作系统可以为多个任务分配不同数目的时间片,确保没有一个任务能霸占系统; 或者将每个定时器周期的某个时间范围赐予特定的任务等,操作系统提供的各种定时功能都与这个滴答定时器有关。 因此,**需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。 **
cortex-m3 在内核部分包含了一个简单的定时器——systick。 因为所有的 cm3 芯片都带有这个定时器,软件在不同芯片生产厂商的 cm3 器件间的移植工作就得以简化。 该定时器的时钟源可以是内部时钟(fclk,cm3 上的自由运行时钟),或者是外部时钟( cm3 处理器上的 stclk 信号)。 不过,stclk 的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能大不相同。 因此,需要阅读芯片的使用手册来确定选择什么作为时钟源。 在 stm32 中 systick 以 hclk(ahb 时钟)或 hclk/8 作为运行时钟,见上图。
systick 定时器能产生中断,cm3 为它专门开出一个异常类型,并且在向量表中有它的一席之地。 它使操作系统和其他系统软件在 cm3 器件间的移植变得简单多了,因为在所有 cm3 产品间,systick 的处理方式都是相同的。 systick 定时器除了能服务于操作系统之外,还能用于其他目的,如作为一个闹铃、用于测量时间等。 **systick 定时器属于cortex ** 内核部件 ,可以参考《arm cortex-m3 权威指南》((英)josephyiu 著,宋岩译,北京航空航天大学出版社出版)或“stm32xxx-cortex-m3programmingmanual” (这是 st 官方提供的电子版编程手册,可以在 st 官网下载)来了解。
4.2 systick寄存器分析在传统的嵌入式系统软件按中通常实现 delay(n) 函数的方法为:
for(i = 0; i ctrl&= ~ systick_ctrl_enable_msk;}本函数实际上只是调用了 systick_config()函数,它是属于内核层的 cortex-m3 通用函数,位于 core_cm3.h 文件中。 若调用 systick_config()配置 systick 不成功,则进入死循环,初始化 systick 成功后,先关闭定时器,在需要的时候再开启。 systick_config() 函数无法在stm32 外设固件库文件中找到其使用方法。 所以我们在 keil 环境下直接跟踪这个函数到 core_cm3.h 文件,查看函数的定义。
static __inline uint32_tsystick_config(uint32_t ticks){ if (ticks > systick_load_reload_msk) return (1); /* reload value impossible */ systick->load = (ticks &systick_load_reload_msk) - 1; /* setreload register */ nvic_setpriority (systick_irqn, (1ctrl =systick_ctrl_clksource_msk | systick_ctrl_tickint_msk | systick_ctrl_enable_msk; /* enable systickirq and systick timer */ return (0); /* functionsuccessful */}在这个函数定义的前面有关于它的注释,如果我们不想去研究它的具体实现,可以根据这段注释了解函数的功能:这个函数启动了 systick ; 并把它配置为计数至 0 时引起中断; 输入的参数 ticks 为两个中断之间的脉冲数,即相隔 ticks 个时钟周期会引起一次中断; 配置 systick 成功时返回 0,出错时返回 1。 但是,这段注释并没有告诉我们它把 systick 的时钟设置为 ahb 时钟还是 ahb/8,这是一个十分关键的问题,于是,我们将对这个函数的具体实现进行分析,与大家再分享一下如何分析底层库函数。 分析底层库函数,要有 systick 定时器工作分析的知识准备。
检查输入参数systick_config() 第 3 行代码是检查输入参数 ticks,因为 ticks 是脉冲计数值,要被保存到重载寄存器 stk_load 寄存器中,再由硬件把 stk_load 值加载到当前计数值寄存器 stk_val 中使用,stk_load 和 stk_val 都是 24 位的,所以当输入参数 ticks 大于其可存储的最大值时, 将由这行代码检查出错误并返回。
位指示宏及位屏蔽宏检查 ticks 参数没有错误后,就稍稍处理一下把 ticks-1 赋值给 stk_load 寄存器,要注意的是减 1,若 stk_val 从 ticks−1 向下计数至 0,实际上就经过了 ticks 个脉冲。 这句赋值代码使用了宏systick_load_reload_msk,与其他库函数类似,这个宏是用来指示寄存器的特定位置或进行位屏蔽的。
/* systick control / status register definitions */#define systick_ctrl_countflag_pos 16 /*!< systick ctrl: countflag position */#define systick_ctrl_countflag_msk (1ul <#define systick_ctrl_clksource_pos 2 /*!< systick ctrl: clksource position */#define systick_ctrl_clksource_msk (1ul <#define systick_ctrl_tickint_pos 1 /*!< systick ctrl: tickint position */#define systick_ctrl_tickint_msk (1ul <#define systick_ctrl_enable_pos 0 /*!< systick ctrl: enable position */#define systick_ctrl_enable_msk (1ul ctrl |= systick_ctrl_enable_msk; while(timingdelay != 0);}使能了 systick 之后,就使用 while(timingdelay != 0)语句等待 timingdelay 变量变为 0,这个变量是在中断服务函数中被修改的。 因此,我们需要编写相应的中断服务程序,在本实验室中我们配置为 10μs 中断一次,每次中断把 timingdelay 减 1。 中断程序在 stm32f10x_it.c 中实现。void systick_handler(void){ timingdelay_decrement(); }systick中断属于系统异常向量,在stm32f10x_it.c文件中已经默认有了它的中断服务函数systick_handler(),但内容为空。 我们找到这个函数,其调用了用户函数timingdelay_decrement()。 后者是由用户编写的一个应用程序。void timingdelay_decrement(void){ if (timingdelay != 0x00) { timingdelay--; }}每次进入 systick 中断就调用一次timingdelay_decrement()函数,使全局变量timingdelay 自减一次。 用户函数 delay_us ()在timingdelay 被减至0时,才退出延时循环,即我们对 timingdelay 赋的值为要中断的次数。 所以总的延时时间:t 延时 = t 中断周期 x timingdelay至此,systick 的精确延时功能讲解完毕。4.4 systick定时器实现-hal库4.4.1 stm32cube配置工程关于如何使用stm32cube新建工程在前文已经讲解过了,这里直说配置gpio部分内容。 本文要实现流水灯,其实输出为初始化设置为高电平还是低电平都可以,因为流水灯需要不断反转。 在上一节笔者已经讲过了。1.gpio配置我们将pb0、pg6、pg7配置输出模式(高电平、低电平均可)、输出速率、上/下拉等,默认即可。2.时钟源配置3.时钟配置4.sys配置(滴答定时器配置)以上配置和gpio流水灯是一样的,本文只具体讲解systick的内容。4.4.2 systick定时器具体代码分析systick属于内核部分,相关的寄存器定义与库函数都在内核相关的文件core_cm3.h中,在上标准库函数版本中已经分析过了。 那么hal库函数是如何初始化systick的呢? 在hal_init()函数中调用了hal_inittick()函数,这才是systick初始化入口。__weak hal_statustypedefhal_inittick(uint32_t tickpriority){ /* configure the systick to have interrupt in 1ms time basis*/ if (hal_systick_config(systemcoreclock / (1000u / uwtickfreq)) > 0u) { return hal_error; } /* configure the systick irq priority */ if (tickpriority < (1ul << __nvic_prio_bits)) { hal_nvic_setpriority(systick_irqn,tickpriority, 0u); uwtickprio = tickpriority; } else { return hal_error; } /* return function status */ return hal_ok;}hal_systick_config()函数和标准库函数差不多,默认中断周期是1ms,hal_tick_freq_default是一个宏定义表示计数的频率,默认是1,也就是1khz,也就是1/1000,那么中断一次的时间为72000000/1000/1*(1/72000000)=1ms。 那么我们要延时1s怎么做呢。 我们在上一节流水灯使用了hal_delay()函数,函数原型如下。__weak void hal_delay(uint32_tdelay){ uint32_t tickstart = hal_gettick(); uint32_t wait = delay; /* add a freq to guarantee minimum wait */ if (wait < hal_max_delay) { wait += (uint32_t)(uwtickfreq); } while ((hal_gettick() - tickstart) < wait) { }}在函数中hal_delay(),(hal_gettick() -tickstart) < wait用于延时的中断周期数,在systick初始化函数中,中断间隔为1ms,hal_delay ()函数的传入参数delay表示多少个中断周期,也就是我们最终的延时,我们传入delay = 500,那么最终的延时就是500ms。我们再来看看hal_gettick()函数。__weak uint32_thal_gettick(void){ return uwtick;}hal_gettick()函数很简单,不断获取uwtick得值,这是一个全局变量,可以发现在hal_inctick()函数中使用过。 那么hal_inctick()函数被那个函数调用了呢?__weak void hal_inctick(void){ uwtick += uwtickfreq;}不难发现,在stm32f1xx_it.c中间中的systick_handler()函数中调用了hal_inctick()函数,systick_handler()也就是滴答定时器的中断服务函数,也就是中断一次会调用一次,也就会uwtick变量累加一次,最终uwtick累加到delay次,表示此次延时结束。void systick_handler(void){ /* user code begin systick_irqn 0 */ /* user code end systick_irqn 0 */ hal_inctick(); /* user code begin systick_irqn 1 */ /* user code end systick_irqn 1 */}好了,使用stm32cube配置systick定时器的延时就讲解完成了,在主函数是使用延时函数控制led就是流水灯了。int main(void){ /* user code begin 1 */ /* user code end 1 */ /* mcuconfiguration--------------------------------------------------------*/ /* reset of all peripherals, initializes the flash interface and thesystick. */ hal_init(); /* user code begin init */ /* user code end init */ /* configure the system clock */ systemclock_config(); /* user code begin sysinit */ /* user code end sysinit */ /* initialize all configured peripherals */ mx_gpio_init(); /* user code begin 2 */ /* user code end 2 */ /* infinite loop */ /* user code begin while */ while (1) { /* user code end while */ /* user code begin 3 */ hal_gpio_togglepin(gpiob,gpio_pin_0); hal_delay(500); hal_gpio_togglepin(gpiob,gpio_pin_0); hal_gpio_togglepin(gpiog,gpio_pin_6); hal_delay(500); hal_gpio_togglepin(gpiog,gpio_pin_6); hal_gpio_togglepin(gpiog,gpio_pin_7); hal_delay(500); hal_gpio_togglepin(gpiog,gpio_pin_7); } /* user code end 3 */}4.5实验现象将编译好的程序下载到板子中,可以看到三个led灯不同地闪烁。

索尼大投半导体图像传感器产业
自恢复保险丝PTC的过流保护原理解析
使用隔离放大器实现高成本效益的充分IGBT保护方案
又一家大型PCB公司入选2018国家技术创新示范企业名单
单元串联型变频器的特点
ARM Cortex-M学习笔记:初识Systick定时器
PCB寄生电容的影响、计算公式和消除措施
4路光栅尺磁栅尺编码器5MHz高速差分信号转RS485/232/WiFi模块 WJ167
mate40系列或将成为华为史上的绝代产品
emmc和固态硬盘的区别分析
联智通达RK3288/RK3399安卓主板如何实现双屏异显
高通获批给HW供应5G芯片,华为5G要启动了?
又一家无人驾驶公司融资5200美元,英伟达也在其中
利用作物株高测量仪来提升玉米的产量
中兴通讯获ISO14064-1:2018温室气体排放核查证书
LXT384 PCM接口单元芯片的三种环回形式分析
【硬见小百科】磁珠磁环的失效与选型
代币概念是大多数利用区块链技术开发的社会和经济创新的核心
区块链和比特币将重振日本经济
智能供配电系统的主要应用领域范围是什么