【Tiva_C系列】一、ARM Cortex-M4F 处理器

0 引言

  到目前为止,ARM 共推出 8 个版本的指令集体系结构版本。V4 版架构是 ARM7 和 ARM9 两个产品系列采用的体系结构;而 ARM11 则是 V6 版本的架构;今天的 ARM Cortex 则是 V7 版本的架构。
  ARM Cortex 系列又分成 3 个子系列,分别是 Cortex A 系列、Cortex M 系列和 Cortex R 系列。其中 A 系列是针对高端的市场,如平板电脑,手机等(A 是 Application 的首字母)。 M 系列是面向低端工业控制市场的,其中 M 就是 Microcontroller 的首字母,它实际上是取代 ARM7,用来同 8 位/16 位单片机竞争的。R 系列是面向实时性要求高的应用的,R 即 Real time 的首字母。目前 A 和 M 系列获得了巨大成功,几乎所有做 SOC 芯片的厂商都买了 ARM 的 Cortex A 或 Cortex M 的 CPU,在此基础上做出自己的芯片销售。
  Cortex-M4 是一个 32 位处理器内核。内部的数据总线是 32 位的,寄存器是 32 位的,存储器接口也是 32 位的。Cortex -M4 采用三级流水线的哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行执行。Cortex-M4 还提供一个可选的存储保护单元(MPU),为操作系统提供特权模式。此外,Cortex-M4 还集成了一个高性能的中断控制器
NVIC(可嵌套向量中断控制器)。Cortex-M4处理器如果还包含了单精度浮点运算单元(FPU),则被称为 Cortex-M4F 处理器。
  ARM Cortex-M4 采用了 16 位和 32 位指令并存的 Thumb-2 指令集,这样处理器就不用再 ARM 和 Thumb 两种状态间来回切换了。

1 Cortex-M4处理器和基于Cortex-M4的MCU

  Cortex-M4 处理器是 MCU 的中央处理单元(CPU)。完整的基于 Cortex-M4 的 MCU 还需要很多其它组件。在芯片制造商得到 Cortex-M4 处理器内核的使用授权后,它们就可以把 Cortex-M4 内核用在自己的芯片设计中,添加存储器,外设,I/O 以及其它功能模块。不同厂家设计出的单片机会有不同的配置,包括存储器容量、类型、外设等都各具特色。
在这里插入图片描述

图1 Cortex-M4F 处理器内核和 MCU

2 Cortex-M4F处理器结构

  Cortex-M4F 处理器包括一个处理器内核 CM4、各内核外设(私有外设)、调试接口及多条总线接口。
在这里插入图片描述

图2 Cortex-M4F 处理器

  1) SysTick
  24 位的递减定时器,可被用作实时操作系统 (RTOS) 的节拍定时器,或者作为一个简单的定时器。
  2) 嵌套式向量化中断控制器(NVIC)
  一个嵌入的中断控制器,支持低延迟中断处理。
  3) 存储器保护单元 (MPU)
  Cortex-M4 有一个可选的存储器保护单元,用于定义不同存储区域的存储器访问权限(如只支持特权下的访问或全访问)和存储器属性(如可缓冲、可缓存)。配置 MPU 后,就可以对特权级访问和用户级访问存储器分别施加不同的访问限制。
  MPU 有很多使用方法:如操作系统使用 MPU 来保护操作系统内核数据和其他特权任务的数据,以防止被恶意用户程序破坏;还可以将不同任务的存储区域隔离开来。MPU 可以把某些内存区设置成只读,从而避免了那里的内容意外被更改;一句话,它会使嵌入式系统变得更加健壮,更加可靠。MPU 默认为禁止。
  4) 浮点单元 (FPU)
   完全支持单精度浮点的加、减、乘、除、乘加以及平方根操作。它还可用于转换定点和浮点数据格式。
  5) 调试跟踪接口
  Cortex-M4 提供一个完整的硬件调试方案,可以实现程序执行控制,包括停机(halting)、单步执行(stepping)、指令断点、数据观察点、寄存器和存储器访问、性能速写(profiling)以及各种跟踪机制。调试跟踪组件包括 Flash 补丁和断点单元(FPB)、指令最追踪宏单元(ITM)、数据观察点和跟踪(DWT)、嵌入式跟踪宏单元(ETM)和跟踪端口的接口单元(TPIU)。
  6) 总线接口
  Cortex-M4 内部有若干个总线接口,以使 CM4 能同时取指和访问内存。
  I-Code 总线和 D-Code 总线负责对代码存储区的访问,前者用于取指,后者用于查表等操作。
  系统总线(System Bus)用于访问内存和外设,覆盖的区域包括 SRAM,片上外设,片外 RAM,片外扩展设备,以及系统级存储区的部分空间。
  私有外设总线(PPB)负责一部分私有外设的访问,主要就是访问调试组件。

3 存储器映射

  总体来说,Cortex‐M4 支持 4GB 存储空间,如图所示地被划分成若干区域。
在这里插入图片描述

图3 Cortex‐M4 存储器映射

  从图中可见,不像其它的 ARM 架构,它们的存储器映射由半导体厂家说了算,CortexM4 预先定义好了“粗线条的”存储器映射。通过把片上外设的寄存器映射到外设区,就可以简单地以访问内存的方式来访问这些外设的寄存器,从而控制外设的工作。
  处于最高地址的系统级存储区,是 Cortex‐M4 内核的私有空间——包括中断控制器 NVIC、MPU 以及各种调试组件。所有这些设备均使用固定的地址。通过把这些地址定死,就至少在内核水平上,为应用程序的移植扫清了障碍。

4 处理器模式和软件执行的权限级别

  Cortex-M4 处理器具有两种处理器操作模式,还支持两级特权级别。
  两种操作模式分别为:处理模式(handler mode)和线程模式(thread mode)。引入两个模式的本意,是用于区别普通状态程序的代码和中断服务程序的代码。
  线程模式(Thread Mode):不处于中断服务状态时,处理器在线程模式下。
  处理模式(Handler Mode):用于处理中断,当处理器完成中断的处理之后返回到线程模式。
  另外,Cortex-M4 有两级权限级别:特权级和用户级。
  用户级:对系统控制空间(SCS,0xE000E000—0xE000EFFF,包括 NVIC、SysTick、 MPU 以及代码调试控制所用的寄存器)的访问将被禁止。除此之外,还禁止使用MRS/MSR 指令访问除了 APSR 之外的特殊功能寄存器。
  特权级:在特权级下,软件可以使用所有的指令和访问所有的资源。
  在线程模式下,软件执行可以处于特权级或用户级下。在处理模式下,软件执行总是在特权级下。在复位后,处理器进入线程模式+特权级。
在这里插入图片描述
  特权级可通过置位 CONTROL[0]来进入用户级。CONTROL[0]只有在特权级下才能访问,用户级下的代码不能试图修改 CONTROL[0]来回到特权级。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指(SVC)”来触发“SVC 异常”,该中断服务函数可以视具体情况而修改 CONTROL[0],才能在返回到线程模式后进入特权级。
在这里插入图片描述

图4 特权级和用户级转换

  通过引入特权级和用户级,就能够在硬件水平上限制某些不受信任的或者还没有调试好的程序,不让它们随便地配置涉及要害的寄存器,因而系统的可靠性得到了提高。进一步地,如果配了 MPU,它还可以作为特权机制的补充——保护关键的存储区域不被破坏,这些区域通常是操作系统的区域。
  举例来说,操作系统的内核通常都在特权级下执行,所有没有被 MPU 禁掉的存储器都可以访问。在操作系统开启了一个用户程序后,通常都会让它在用户级下执行,从而使系统不会因某个用户程序的崩溃或恶意破坏而受损。
至于简单应用的裸机程序(无操作系统),往往一直处于特权级,而不进入用户级。

5 内核寄存器

  CM4 的内核寄存器如图 2-5 所示,包括 13 个通用寄存器(R0-R12),堆栈指针 SP、链接寄存器 LR、程序计数器 PC 和 5 个特殊寄存器(状态寄存器 PSR、控制寄存器 CONTROL、优先级屏蔽寄存器 PRIMASK、故障屏蔽寄存器FAULTMASK、基本优先级屏蔽寄存器 BASEPRI)。在 MCU 的数据手册(如 TM4C1231H6PGE 中文数据手册)第 2 章 Cortex-M4F 处理器的 2.3.4 节寄存器描述中可以查询到所有内核寄存器的详细信息。
在这里插入图片描述

图5 内核寄存器

  1) 通用寄存器
  R0~R12 寄存器: 是真正意义上的通用,在处理器运行过程中作数据的缓存。
  2) 堆栈指针 SP
  R13 为堆栈指针寄存器:堆栈指针是用于访问堆栈。CM4 物理上有两个堆栈指针: 主堆栈指针(MSP)和进程堆栈指针(PSP),R13 在任何时刻只能是其中一个。在复位后或处于处理模式时,使用 MSP;而 PSP 只能用于线程模式。栈指针的选择由控制寄存器(CONTORL)来决定。
  在采用裸机程序的简单应用中,往往只使用 MSP,而没有必要使用 PSP。而在嵌入式实时操作系统中,为了避免系统堆栈因用户程序的错误使用而毁坏,我们可以给用户程序专门分配一个堆栈,不让它共享操作系统内核的堆栈。在这种管理机制下,运行在线程模式的用户代码使用 PSP,而中断服务函数则使用 MSP。
  3) 链接寄存器 LR
  R14 链接寄存器(LR):当调用函数或子程序时,由 R14 存储返回地址。
  不像大多数其它处理器,ARM 为了减少访问内存的次数,把返回地址直接存储在寄存器中。这样足以使很多只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于 1 级,则需要把前一级的 R14 值压到堆栈里。在 ARM 上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。
  4) 程序计数器(PC)
  R15 程序计数器(PC):是程序运行的基础,具有自加的功能。该寄存器的 0 位始终为 0,因此,指令始终与字或半字边界对齐。
  5) 特殊寄存器
  特殊寄存器表示了处理器状态、定义了操作状态和中断/异常屏蔽。在开发简单应用时,需要访问这些寄存器的情形并不多。不过,在嵌入式操作系统或需要高级中断屏蔽特性时,就需要访问它们。
  xPSR 程序状态寄存器:系统级的处理器状态可分为 3 类,应用状态寄存器 (APSR)、中断状态寄存器(IPSR)、执行状态寄存器(EPSR),可组合起来构成一个 32 位的寄存器,统称为 xPSR,如表 2-1 所示。

表1 xPSR 程序状态寄存器

在这里插入图片描述

表2 xPSR 寄存器的部分位的功能

在这里插入图片描述

  中断屏蔽寄存器:分为三组,它们分别是 PRIMASK、FAULTMASK、BASEPRI。
  PRIMASK 为片上可配置优先级异常的总开关,该寄存器只有位 0 有效,当该位为 0 时响应所有可配置优先级异常; 当该位为 1 时屏蔽所有可配置优先级异常。
  FAULTMASK 寄存器为管理系统错误的总开关,该寄存器中有位 0 有效,当该位为 0 时,响应所有的异常; 为 1 时只响应 NMI,屏蔽其他所有异常。
  BASEPRI 寄存器用来屏蔽优先级等于和小于某一个数值的异常。
  控制寄存器 CONTROL:有两个作用,其一用于定义处理器权限级别,其二用于选择堆栈指针,如表所示。

表3 控制寄存器 CONTROL

在这里插入图片描述

6 异常和中断处理

  Cortex‐M4 中的嵌套向量中断控制器 NVIC (Nested Vectored Interrupt Controller),负责处理异常和中断配置、优先级以及中断屏蔽。
  在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外设中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。Cortex‐M4 处理器及 NVIC 在 handler 模式下处理所有的异常。当出现异常时,将产生中断,处理器状态被自动存储到堆栈,进入到中断服务程序 (ISR) ,ISR 结束后又自动从堆栈中恢复。
  NVIC 定义了 16 种系统异常和 240 路外设中断。通常芯片设计者可自由设计片上外设,一般具体的片上外设中断都不会用到 240 路。

表4 Cortex‐M4F 异常类型

在这里插入图片描述

6.1 优先级

  优先级对于中断来说很关键的,它会影响一个中断何时可以响应。优先级的数值越小,则优先级越高。CM4 支持中断嵌套,使得高优先级中断会抢占低优先级中断。有 3 个系统异常:复位,NMI 以及硬 fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它中断。所有其它中断的优先级则都是可编程的(但不能编程为负数)。
  CM4 支持 3 个固定的高优先级和多达 256 级的可编程优先级,并且支持 128 级抢占,绝大多数芯片都会精简设计,通常的做法是裁掉表达优先级的几个低端有效位,减少优先级的级数。如:Tiva_C 系列微控制器中只有 8 级可编程优先级。
  CM4 中 NVIC 支持由软件指定的可配置的优先级(称为软件优先级),通过对中断优先级寄存器 PRIn 执行写操作,来将中断的优先级指定为 0-255。硬件优级随着中断号的增加而降低。0 优先级最高,255 优先级最低。指定软件优先级后,硬件优先级无效。例如: 如果将 INTISR[0]指定为优先级 1,INTISR[31]指定为优先级 0,则 INTISR[0]的优先级比 INTISR[31]低。
  为了对具有大量中断的系统加强优先级控制,CM4 支持优先级分组,每个优先级的高位为组(抢占)优先级,低位为同组内的子优先级。
  抢占优先级高的中断可以打断抢占优先级低的中断。例如:如果 B 的抢占优先级高于 A,在执行中断服务程序 A 的过程中,中断 B 到来,则 B 可以打断 A 的中断服务,执行完中断服务程序 B 再继续执行中断服务程序 A。
子优先级只在抢占优先级相同(同组)的中断中起作用,当抢占优先级相同的中断同时挂起时,此时子优先级高的中断会优先响应。
  例如:Tiva_C 中只有 8 级(0 到 7)可编程优先级,则其优先级可以用一个 3 位的二进制数表示,使用 PRIn 寄存器中的[7:5]位设置。如果我们将其最高 1 位设为组(抢占)优先级位,低 2 位为子优先级位,则 0-3 级的高位为 0,在同一组,具有抢占优先级 0,4-7 级的抢占优先级为 1,所以 0-3 级(优先级的数值更小)可以抢占 4-7 级。
  优先级分组在 APINT 寄存器中的 PRIGROUP 位域进行设置。如下表中,当 PRIGROUP 为 0x0-0x04 时,优先级 3 位全为组(抢占)优先级,则有 8 级组(抢占)优先级,只有 1 级子优先级。

表5 Tiva_C 中组(抢占)优先级和子优先级分组位置的关系

在这里插入图片描述

6.2 中断向量表

  当发生了中断并且要响应它时,CM4 需要定位中断服务函数(ISR)的入口地址。这些入口地址存储在“中断向量表”中。向量表默认从地址 0 开始,且各向量占用 4 字节。因此每个表项占用 4 字节,复位后的向量表如表所示。

表6 中断向量表

在这里插入图片描述
  向量表中的第 1 个字(地址 0x0000_0000)为指向堆栈栈顶的指针,复位时内核读取该地址的数据设置主堆栈。
  发生中断时,CM4 会从中断向量确定中断服务函数的地址,然后跳到中断服务函数执行。例如复位向量(地址 0x0000_0004)保存的是复位中断服务函数的起始地址。MCU 上电或复位时,会首先到地址 0x0000_0004 找到复位中断服务函数的地址,然后跳去执行。
  向量表重定位
  一般来说,向量表默认地址(0x0000 0000)处为启动存储器,是 FLASH 或 ROM 设备,在程序运行时是不能修改的。不过,有些应用可能需要在运行时修改向量表,因此 cortex-M4 提供了向量重定位的特性,将向量表放到 RAM 区,从而可以通过程序修改。
  通过设置向量偏移寄存器 VTABLE 来改向量表的位置,如表 2-7 所示。向量表起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是 2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有 32 个中断,则共有 32+16(系统异常)=48 个向量,向上增大到 2 的整次幂后值为 64,因此地址必须能被 64*4=256 整除,从而合法的起始地址是:0x0、0x100、0x200 等。

表7 向量偏移寄存器 VTABLE

在这里插入图片描述

6.3 中断的进入与退出

  1) 中断进入
  入栈:当处理器发生中断时,首先把 8 个寄存器(xPSR、PC、LR、R12、R3、R2、R1、
R0)压入栈,由处理器自动完成。
  取向量:紧接着内核将根据向量表找出正确的中断向量,然后在服务程序的入口处预取指,处理器能将取指与取数据分别通过总线控制,使入栈与取指这两项工作能同时进行,以便快速进入中断。
  更新寄存器:入栈和取向量操作完成之后,在执行服务程序之前,还必须更新一系列寄存器,包括栈指针 SP、程序状态寄存器 PSR、链接寄存器 LR、程序计数器 PC,以及 NVIC 中的相关寄存器。
  2) 中断退出
  在 CM4 中,是通过把 LR 中的 EXC_RETURN 往 PC 里写来指示中断服务结束的。在启动了中断返回序列后,下述的处理就将进行:
  出栈:先前压入栈中的寄存器在这里恢复。内部的出栈顺序与入栈时的相对应,堆栈指针的值也改回先前的值。
  更新 NVIC 寄存器:伴随着中断的返回,它的活动位也被硬件清除。

6.4 尾链机制

  1) 尾链
  当处理器在响应某中断 ISR1 时,如果又发生其它中断 ISR2,但 ISR2 优先级不够高,则被阻塞。那么在 ISR1 执行返回后,系统处理 ISR2 时,倘若还是先 POP 然后又把 POP 出来的内容 PUSH 回去,则白白浪费 CPU 时间。因此,CM4 不会 POP 这些寄存器,而是在 6 个周期后,直接执行 ISR2。这么一来,看上去好像后一个中断把前一个的尾巴咬掉了,前前后后只执行了一次入栈/出栈操作。于是,这两个中断之间的“时间沟”变窄了很多,如图7 所示。
在这里插入图片描述

图7 尾链

  2) 中断迟来
  入栈的阶段,尚未执行其服务函数时,如果此时收到了高优先级中断的请求,则本次入栈就成了为高优先级中断所做的了—入栈后,将执行高优先级中断的服务函数。
  比如,若在响应某低优先级中断 1 的早期,检测到了高优先级中断 2,则只要中断 2 没有太晚,就能以“中断迟来”的方式处理——在入栈完毕后先执行 ISR2。如果中断 2 来得太晚,以至于已经执行了 ISR1 的指令,则按普通的抢占处理,这会需要更多的处理器时间和额外 32 字节的堆栈空间。
  在 ISR2 执行完毕后,则以“尾链”方式,来启动 ISR1 的执行。

6.5 NVIC 的寄存器

  在嵌入式中,对外设的操作主要是通过寄存器来实现的,包括查看外设的工作状态和
控制外设工作。外设寄存器都被分配了地址,可以象内存一样访问。NVIC寄存器的访问起始地址是0xE000_E000。所有NVIC的中断控制/状态寄存器都只能在特权级下访问。不过有一个例外,软件触发中断寄存器可以在用户级下访问以产生软件中断。
  NVIC的寄存器有:
  使能与禁止寄存器(ENn 和 DISn)
  挂起与解挂寄存器(PENDn 和 UNPENDn)
  优先级寄存器(PRIn)
  活动状态寄存器(ACTIVEn)
  软件触发中断寄存器(SWTRIG)
  另外,下列寄存器也对中断处理有重大影响:
  中断掩蔽寄存器(PRIMASK, FAULTMASK 以及 BASEPRI)
  向量表偏移寄存器(VTABLE)(改变向量表位置)
  应用程序中断及复位控制寄存器(APINT)(优先级分组)

6.6 中断的使能与禁止

  NVIC 能对片上 240 路外设中断的进行使能和禁止控制,使能是向 ENn 寄存器对应位写“1”,禁止是向 DISn 寄存器对应位写“1”。

6.7 中断的悬挂与活跃状态

  当中断请求发生时,正在处理同级或高优先级中断,或者被屏蔽,则中断不能立即得到响应,此时中断被挂起。中断的挂起状态可以通过“置位挂起寄存器(PENDn)”和“中断解挂寄存器(UNPENDn)”来读取,还可以通过写它们来手工挂起和清除挂起中断。挂起寄存器和解挂寄存器的用法与前面介绍的使能、禁止能寄存器完全相同。
  中断被挂起后,即使后来中断源取消了中断请求,已经被标记成挂起的中断也被记录下来。到了系统中它的优先级最高的时候,或者屏蔽被取消,就会得到响应。但如果在中断得到响应前,手工清除了中断挂起标志(写解挂寄存器),则中断被取消。
在这里插入图片描述

图8 中断的悬起和活跃状态

  当某中断的服务函数开始执行时,就称此中断进入了“活跃”状态,并且其挂起标志会被硬件自动清除,如图所示。中断的活跃状态可以在活动位寄存器ACTIVEn中查询。
  如果在某个中断挂起时,又来多个中断请求,这些中断请求会被忽略。
  如果中断请求一直保持,则该中断会在中断服务函数返回后再次被置为挂起状态。
  如果在服务函数执行时,中断请求被释放了,但是在中断服务函数返回前,又来了中断请求,则会重新挂起该中断。

6.8 中断编程示例

6.8.1 位运算和位域

  1) 位运算
  在计算机中,数据是以二进制的形式存储的,位运算就是直接对整数的二进制位进行操作。在对数据修改时,很多时候需要用到位运算。

表8 C语言中的位运算符

在这里插入图片描述
  2) 位域
  寄存器中有不同的字段,又叫位域,来表示不同的功能区域。例如:软件触发中断寄存器(SWTRIG)中的0-7位为INTID位域。向INTID位域写入一个8位的数值,会产生一个中断号为该数值的中断(如:写入01,则在IRQ1上产生中断)。(tm4c1231h6pge中文数据手册第134页)
在这里插入图片描述

图9 软件触发中断寄存器SWTRIG

  Tivaware 目录下的 inc/hw_nvic.h 文件中宏定义了 NVIC 相关寄存器和寄存器的各个位域,指出了寄存器的地址以及寄存器的位域。例如SWTRIG寄存器的宏定义如下:
  #define NVIC_SW_TRIG 0xE000EF00 // Software Trigger Interrupt
  其中0xE000EF00为SWTRIG寄存器在存储空间的地址。
  例如SWTRIG寄存器INTID位域的宏定义:
  #define NVIC_SW_TRIG_INTID_M 0x000000FF // Interrupt ID
  因为INTID位域为0-7位,相应位取1,其他位取0,故为0x000000FF:
  3) 寄存器的读写
  Tivaware 目录下的 inc/hw_types.h 定义了对硬件地址直接读写的方法,
  #define HWREG(x) (*((volatile uint32_t )(x)))
  #define HWREGH(x) (
((volatile uint16_t )(x)))
  #define HWREGB(x) (
((volatile uint8_t *)(x)))
  这是一个宏调用定义,其实是用指针的方法进行操作。其中x是寄存器宏定义,即寄存器地址。例如:读取SWTRIG寄存器的  值到变量temp中: temp=HWREG(NVIC_SW_TRIG);
  改写SWTRIG寄存器的值为变量temp中的值:
  HWREG(NVIC_SW_TRIG)=temp;
  如果将SWTRIG寄存器中的值改写为1,则:
  HWREG(NVIC_SW_TRIG)=0x01;
  4) 位域的读写
  对寄存器的访问往往只想访问寄存器的某个位域,比如只读取寄存器的一个位域,或者只改写寄存器的一个位域,而不影响其他位域。
  a) 只读取某位域的值,将寄存器的值直接和位域宏定义相与,则只读出该位域的值,其它位读出的为0,例  如:
  temp=HWREG(NVIC_SW_TRIG) & NVIC_SW_TRIG_INTID_M;
  b) 要将寄存器某位域全置1,其他位保持不变,则将寄存器的值和位域宏定义相或,例
  如:
  HWREG(NVIC_SW_TRIG) |= NVIC_SW_TRIG_INTID_M;
  c) 要将寄存器某位域清0,其他位保持不变,则将该位域宏定义取反后,和寄存器的值相与,例如:
  HWREG(NVIC_SW_TRIG) &= ~(NVIC_SW_TRIG_INTID_M);
  d) 要将寄存器某位域赋值,其他位保持不变,则需先将该位域的值清零,再与赋值相或(赋值需位移到该位域  的位置),例如将位域改写为变量temp中相应位的值,其它位不变:
  HWREG(NVIC_SW_TRIG) &= ~(NVIC_SW_TRIG_INTID_M);
  HWREG(NVIC_SW_TRIG) |= temp & NVIC_SW_TRIG_INTID_M;

6.8.2 中断编程示例中断程序的编写包括:

  a) 包括中断的初始化(配置和使能),确定什么情况下产生中断。
  b) 中断服务函数的编写,产生中断后,会跳去执行中断服务函数,中断服务函数执行完后会返回到程序原来的  位置,继续执行。
  c) 修改中断向量表,告知中断服务函数的地址,使得发生中断时能找到中断服务函数。
  我们下面来编写一个IRQ1软件触发中断程序。在NVIC中有软件触发中断寄存器
(SWTRIG),对其写入相应IRQ的号码,即可触发相应的IRQ。例如写入1,即可触发IRQ1。
  1) 我们修改lab2的main.c如下,在while循环中,每延时一段时间,触发1次中断
  IRQ1: main.c
  #include <stdint.h>
  #include <stdbool.h>
  #include “inc/tm4c123gh6pm.h”
  #include “inc/hw_memmap.h”
  #include “inc/hw_types.h”
  #include “inc/hw_nvic.h”
  #include “driverlib/sysctl.h”
  #include “driverlib/gpio.h”
   int main(void)
  {
   SysCtlClockSet(SYSCTL_SYSDIV_4|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_
  OSC_MAIN);
   SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
   GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,
  GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);

   HWREG(NVIC_EN0) |= 0x00000002; //使能IRQ1
   while(1)
   {
   HWREG(NVIC_SW_TRIG) = 1; //触发中断IRQ1
   SysCtlDelay(2000000); //延时
   }
  }
  2) 在startup_ccs.c文件的后面,编写IRQ1IntHandler中断服务函数。每进一次中断服务函数,都会改变一次LED灯的状态。
  static void IRQ1IntHandler(void)
  { static unsigned char led_on;
   if(led_on == 0)
   {
   GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 0); led_on = 1;
   }
   else
   {
   GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 4); led_on = 0;
   }
  }
  注意,要在文件的前面添加包含头文件和中断服务函数的声明。
  #include <stdint.h>
  #include <stdbool.h>
  #include “inc/hw_memmap.h”
  #include “driverlib/gpio.h”

  static void IRQ1IntHandler(void);
  3) 我们需要修改中断向量表,将IRQ1的中断向量指向IRQ1的中断服务函数。
  在tm4c1231h6pm_startup_ccs.c文件中,定义了中断向量表和中断服务函数。我们可以看到,一般的中断向量都被定义为IntDefaultHandler,在文件后面我们可以看到 IntDefaultHandler中断服务函数的定义,这是一个不做任何处理的服务函数,进入后会陷入死循环。将IRQ1的中断向量改为IRQ1IntHandler。
在这里插入图片描述

图10 中断向量表

7 SysTick 定时器

  SysTick 定时器被捆绑在 NVIC 中,用于产生 SysTick 中断(中断号:15)。
  在以前,操作系统以及所有使用了时基的系统,都必须要一个硬件定时器来产生需要的 “滴答”中断,作为整个系统的时基。滴答中断对操作系统尤其重要。例如,操作系统可以为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。因为所有的 CM4 芯片都带有 SysTick 定时器,软件在不同 CM4 器件间的移植工作就得以简化。
  TM4C123GH6PM 的 SysTick 定时器有 3 个寄存器:STRELOAD、STCURRENT 和
STCTRL。
  SysTick 重载值寄存器(STRELOAD),当计数至零时将被重新装载的值(RELOAD)。
  SysTick 当前值寄存器(STCURRENT),读取时返回当前计数的值。

表9 SysTick 控制及状态寄存器(STCTRL)

在这里插入图片描述
  多次触发:计数器装入 STRELOAD 中保存的数值 RELOAD 并开始递减计数,每个时钟周期减 1。当计数器递减到 0 时,COUNT 位将置位,如果 INTEN 置位(中断使能了),将生成一个中断。然后,计数器重新装入 RELOAD 值并开始计数。
在这里插入图片描述

图11 systick 中断

  时钟选择:CLK_SRC 决定了时钟采用 PIOSC4 分频(4MHz),还是系统时钟。
  例如:在 tiva_C 中,如果设置 STRELOAD 为 1000000;CLK_SRC 设为 0,则时钟为精确内部振荡器 (PIOSC) 4 分频,即时钟频率为 4M;INTEN 设为 1,SysTick 中断启用; ENABLE 设为 1,定时器开始工作;SysTick 定时器将每 0.25 秒(1000000 次计数)产生 1 次中断。

  • 5
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值