GD32开发实战指南(基础篇) 第5章 跳动的心脏-Systick

开发环境:
mdk:keil 5.30
开发板:gd32f207i-eval
mcu:gd32f207ik
cortex-m的内核中包含systick定时器了,只要是cortex-m系列的mcu就会有systick,因此这是通用的,下面详细分析。
1 systick工作原理分析systick 定时器被捆绑在 nvic 中,用于产生 systick 异常(异常号 :15)。在以前,操作系统和所有使用了时基的系统都必须有一个硬件定时器来产生需要的“滴答”中断,作为整个系统的时基。滴答中断对操作系统尤其重要。例如,操作系统可以为多个任务分配不同数目的时间片,确保没有一个任务能霸占系统 ;或者将每个定时器周期的某个时间范围赐予特定的任务等,操作系统提供的各种定时功能都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。
cortex-m3 在内核部分包含了一个简单的定时器——systick。因为所有的 cm3 芯片都带有这个定时器,软件在不同芯片生产厂商的 cm3 器件间的移植工作就得以简化。该定时器的时钟源可以是内部时钟(fclk,cm3 上的自由运行时钟),或者是外部时钟。不过,外部时钟的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能大不相同。因此,需要阅读芯片的使用手册来确定选择什么作为时钟源。在 gd32 中systick 以 hclk(ahb 时钟)或 hclk/8 作为运行时钟,见上图。
systick 定时器能产生中断,cm3 为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其他系统软件在 cm3 器件间的移植变得简单多了,因为在所有 cm3 产品间,systick 的处理方式都是相同的。systick 定时器除了能服务于操作系统之外,还能用于其他目的,如作为一个闹铃、用于测量时间等。 systick 定时器属于cortex 内核部件 ,可以参考《arm cortex-m3 权威指南》((英)josephyiu 著,宋岩译,北京航空航天大学出版社出版)来了解。
2 systick寄存器分析在传统的嵌入式系统软件按中通常实现 delay(n) 函数的方法为:
for(i = 0; i ctrl &= ~ systick_ctrl_enable_msk; /* configure the systick handler priority */ nvic_setpriority(systick_irqn, 0x00u);}本函数实际上只是调用了 systick_config() 函数,它是属于内核层的 cortex-m3 通用函数,位于 core_cm3.h 文件中。若调用 systick_config() 配置 systick 不成功,则进入死循环,初始化 systick 成功后,先关闭定时器,在需要的时候再开启。systick_config() 函数无法在gd32 外设固件库文件中找到其使用方法。所以我们在 keil 环境下直接跟踪这个函数到 core_cm3.h 文件,查看函数的定义。
/** \\brief system tick configuration the function initializes the system timer and its interrupt, and starts the system tick timer. counter is in free running mode to generate periodic interrupts. \\param [in] ticks number of ticks between two interrupts. \\return 0 function succeeded. \\return 1 function failed. \\note when the variable __vendor_systickconfig is set to 1, then the function systick_config is not included. in this case, the file device.h must contain a vendor-specific implementation of this function. */__static_inline uint32_t systick_config(uint32_t ticks){ if ((ticks - 1) > systick_load_reload_msk) return (1); /* reload value impossible */ systick->load = ticks - 1; /* set reload register */ nvic_setpriority (systick_irqn, (1ctrl = systick_ctrl_clksource_msk | systick_ctrl_tickint_msk | systick_ctrl_enable_msk; /* enable systick irq and systick timer */ return (0); /* function successful */}在这个函数定义的前面有关于它的注释,如果我们不想去研究它的具体实现,可以根据这段注释了解函数的功能 :这个函数启动了 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 << systick_ctrl_countflag_pos) /*!< systick ctrl: countflag mask */#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 *//* 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 current register definitions */#define systick_val_current_pos 0 /*!< systick val: current position */#define systick_val_current_msk (0xfffffful << systick_val_current_pos) /*!< systick val: current mask *//* systick calibration register definitions */#define systick_calib_noref_pos 31 /*!< systick calib: noref position */#define systick_calib_noref_msk (1ul << systick_calib_noref_pos) /*!< systick calib: noref mask */#define systick_calib_skew_pos 30 /*!< systick calib: skew position */#define systick_calib_skew_msk (1ul << systick_calib_skew_pos) /*!< systick calib: skew mask */#define systick_calib_tenms_pos 0 /*!< systick calib: tenms position */#define systick_calib_tenms_msk (0xfffffful << systick_val_current_pos) /*!< systick calib: tenms mask *//*@}*/ /* end of group cmsis_cm3_systick */其中寄存器位指示宏 :systick_xxx_pos ,宏展开后即为 xxx 在相应寄存器中的位置,如控制 systick 时钟源的 systick_ctrl_clksource_pos ,宏展开为 2,这个寄存器位正是寄存器 stk_ctrl 中的 bit2。
而寄存器位屏蔽宏 :systick_xxx_msk,宏展开是 xxx 的位全部置 1 后,左移systick_xxx_pos 位。如控制 systick 时钟源的 systick_ctrl_clksource_msk,宏展开为“1ul << systick_ctrl_clksource_pos”, 把无符号长整型数值(ul) 1 左移 2 位, 得 到 了 一 个 只 有 bit2 :clksource 位被置 1,其他位为 0 的数值,这样的数值配合位操作 &(按位与)、| (按位或)可以很方便地修改寄存器的某些位。假如控制 clksource 需 要 4 个寄存器位,这个宏就应该被改为( 0xf ul ctrl进行修改,当然最好自定义systick_init()函数中修改。
使能、关闭定时器由于调用 systick_config() 函数之后,systick 定时器就被开启了,但我们在初始化的时候并不希望这样,而是根据需要再开启。所以在 systick_init() 函数中,调用完systick_config() 并配置好后,应先把定时器关闭了。systick 的开启和关闭由寄存器stk_ctrl 的 bit0 :enable 位来控制,使用位屏蔽宏以操作寄存器的方式实现。
systick->ctrl |= systick_ctrl_enable_msk; // 使能滴答定时器systick->ctrl &= ~ systick_ctrl_enable_msk; // 关闭滴答定时器定时时间的计算在调用systick_config()函数时,向它输入的参数为systemcoreclock / 100000,systemcoreclock为定义了系统时钟(sysclk)频率的宏,即等于 ahb的时钟频率。在本书的所有例程中ahb 都是被配置为 120mhz 的,也就是这个 systemcoreclock 宏展开为数值 12000 0000。
根据前面对 systick_config() 函数的介绍,它的输入参数为 systick 将要计时的脉冲数,经过 ticks 个脉冲(经过 ticks 个时钟周期)后将触发中断,触发中断后又重新开始计数。由此我们可以算出定时的时间,下面为计算公式 :
t=ticks×(1/f)
其中,t 为要定时的总时间 ;ticks 为 systick_config() 的输入参数 ;1/ f 即为systick 使用的时钟源的时钟周期,f 为该时钟源的时钟频率,当时钟源确定后为常数。
本例中使用时钟源为 ahb 时钟,其频率被配置为 120 mhz。调用函数时,把 ticks 赋值为 ticks=systemfrequency / 100000 =1200,表示 1200 个时钟周期中断一次 ;1/f 是时钟周期的时间,此时1/f =1/120 us,所以最终定时总时间 t=1200x(1/120),为1200 个时钟周期,正好是 10us。
systick 定时器的定时时间(配置为触发中断,即为中断周期)由 ticks 参数决定,最大定时周期不能超过 224 个。
编写中断服务函数一旦我们调用了 delay_us() 函数,systick 定时器就被开启,按照设定好的定时周期递减计数,当 systick 的计数寄存器的值减为 0 时,就进入中断函数,当中断函数执行完毕之后重新计时,如此循环,除非它被关闭。
/* brief delay a time param[in] count: count param[out] none retval none*/void delay_us(uint32_t count){ delay = count; // 使能滴答定时器 systick->ctrl |= systick_ctrl_enable_msk; while(0u != delay){ }}使能了 systick 之后,就使用while(0u != delay)语句等待 delay 变量变为 0,这个变量是在中断服务函数中被修改的。因此,我们需要编写相应的中断服务程序,在本实验室中我们配置为 10us 中断一次,每次中断把 delay 减 1。中断程序在 gd32f10x_it.c 中实现。
void systick_handler(void){ delay_decrement (); }systick中断属于系统异常向量,在gd32f10x_it.c文件中已经默认有了它的中断服务函数systick_handler(),但内容为空。我们找到这个函数,其调用了用户函数delay_decrement()。后者是由用户编写的一个应用程序。
/* brief delay decrement param[in] none param[out] none retval none*/void delay_decrement(void){ if(0u != delay){ delay--; }}每次进入 systick 中断就调用一次 delay_decrement()函数,使全局变量delay 自减一次。用户函数 delay_us ()在delay 被减至0时,才退出延时循环,即我们对 delay 赋的值为要中断的次数。所以总的延时时间 :
t 延时 = t 中断周期 x delay
至此,systick 的精确延时功能讲解完毕。
4 实验现象将编译好的程序下载到板子中,可以看到led灯不同地闪烁。

关于MATLAB的介绍和用途分析
浅谈SPI的通信线路
有哪些科技巨头垂涎物联网这块唐僧肉?
阿凡达时代!三万元组建3D家庭影院
智能工业传感器节点的构筑,传感器也要为MCU分担任务
GD32开发实战指南(基础篇) 第5章 跳动的心脏-Systick
几种单USB转多串口的方案
TI推出DLP LightCommander开发套件
国产芯片研发最新进展
现场压力容器的声发射源特征
比科奇获选担任全球小基站论坛执行董事
华为助力,卡塔尔沃达丰推出 5G家宽业务套餐
微雪电子树莓派摄像头简介
使用示波器测量电压的两种方法和需注意哪些事项
利用海藻酸钠粘结剂和水溶剂制备CuF2电极可以抑制CuF2在有机电解质中的溶解
全新Fluke 438-II电能质量与电机分析仪
刚发布就降价?苹果iPhone8是最心酸的苹果手机,发布不到一周跌破官网价
是谁占据了中国工业机器视觉的半壁江山
米尔科技MYD-Y7Z010/007S IO Cape简介
市场需求和应用场景不断扩大 中国有望吸引更多人工智能行业的人才