28. GPT——通用PWM定时器¶
28.1. PWM简介¶
PWM 的全称是脉冲宽度调制(Pulse Width Modulation),简称脉宽调制,通俗的讲就是调节脉冲的宽度。 其原理是通过将有效的电信号分散成离散形式从而来降低电信号所传递的平均功率, 根据面积等效法则,可以通过对改变脉冲的时间宽度,来等效地获得所需要合成的相应幅值和频率的波形。 PWM 有着非常广泛的应用,比如直流电机控制、开关电源、逆变器等等。
PWM 波形的基本信息如下图所示。
脉冲周期(T):单位是时间,如纳秒(ns)、微秒(us)、毫秒(ms)等。
脉冲频率(f):单位是赫兹(Hz)、千赫兹(KHz)等。频率与脉冲周期成倒数关系,f=1/T。
脉冲宽度(W):简称“脉宽”,一般指脉冲中高电平持续的时间。单位是时间。
占空比(D):脉宽除以脉冲周期的值。
以上各信息之间的关系满足下列公式:
28.2. GPT简介¶
通用 PWM 定时器(GPT,General PWM Timer)是 RA MCU 的其中一种 32/16 位的定时器外设。 在 GPT 当中,可分为 GPT32 和 GPT16,它们最主要的区别是计数器的不同。 GPT32 是 32 位的定时器,包含的计数器是 32 位的,所能计数的范围为:0 ~ 0xFFFF_FFFF; 而 GPT16 是 16 位的定时器,包含的计数器是 16 位的,所能计数的范围为:0 ~ 0xFFFF。
定时器(Timer)最基本的功能就是定时,比如定时发送串口数据、定时采集AD数据、定时触发中断处理其它事务等等。 如果把定时器与 GPIO 引脚结合起来使用的话可以实现更加丰富的功能, 可以对输入信号进行计数,可以测量输入信号的脉冲宽度,可以输出单个脉冲、PWM 等波形,等等。 通过定时器生成 PWM 波形信号来控制电机状态是工业控制的普遍方法,这方面知识非常值得深入了解。
GPT 模块可用于计数事件、测量外部输入信号、作为通用计时器并产生周期性中断、以及输出周期性或 PWM 信号到 GTIOC 引脚。 GPT 也可用于输出单个脉冲,但是注意这是通过软件来实现的,GPT 硬件本身不支持输出单个脉冲(One-Shot)功能。 当使用单个脉冲(One-Shot)模式时,必须要开启中断,计时器需要在脉冲周期结束后在 ISR 中断服务函数中被停止。
RA MCU 的不同型号之间可能拥有不同数量的 GPT 定时器,例如:
野火启明6M5开发板板载的 RA6M5 芯片拥有共 10 个GPT定时器(即10个通道), 其中包括 4 个32位GPT定时器(GPT32n (n=0~3))和 6 个16位GPT定时器(GPT16m (m=4~9))。
野火启明4M2开发板板载的 RA4M2 芯片拥有共 8 个GPT定时器(即8个通道), 其中包括 4 个32位GPT定时器(GPT32n (n=0~3))和 4 个16位GPT定时器(GPT16m (m=4~7))。
野火启明2L1开发板板载的 RA2L1 芯片拥有共 10 个GPT定时器(即10个通道), 其中包括 4 个32位GPT定时器(GPT32n (n=0~3))和 6 个16位GPT定时器(GPT16m (m=4~9))。
RA6M5/RA2L1 的 GPT 定时器通道与模块名如下图所示:
RA4M2 的 GPT 定时器通道与模块名如下图所示:
以 RA6M5 为例,GPT 定时器的详细功能参数如下表所示:
参数 |
描述 |
---|---|
功能 |
|
计数时钟源 |
|
中断源(9个源) |
|
以 RA6M5 为例,GPT 的 I/O 引脚及其功能用途如下表所示:
通道 |
引脚名称 |
I/O 方向 |
功能 |
---|---|---|---|
共用 |
GTETRGx |
Input |
外部触发输入引脚 x (input through the POEG) x: A ~ D |
GPT32n n: 0 ~ 3 |
GTIOCnA |
Input/Output |
GPT 的输入输出引脚 A |
GTIOCnB |
Input/Output |
GPT 的输入输出引脚 B |
|
GPT16m m: 4 ~ 9 |
GTIOCnA |
Output |
GPT 的输入输出引脚 A |
GTIOCnB |
Output |
GPT 的输入输出引脚 B |
|
GPT_OPS |
GTIU |
Input |
霍尔传感器 U 相输入 |
GTIV |
Input |
霍尔传感器 V 相输入 |
|
GTIW |
Input |
霍尔传感器 W 相输入 |
|
GTOUUP |
Output |
BLDC 电机控制三相PWM输出(U相上桥臂) |
|
GTOULO |
Output |
BLDC 电机控制三相PWM输出(U相下桥臂) |
|
GTOUUP |
Output |
BLDC 电机控制三相PWM输出(V相上桥臂) |
|
GTOULO |
Output |
BLDC 电机控制三相PWM输出(V相下桥臂) |
|
GTOUUP |
Output |
BLDC 电机控制三相PWM输出(W相上桥臂) |
|
GTOULO |
Output |
BLDC 电机控制三相PWM输出(W相下桥臂) |
28.3. GPT的框图分析¶
如下图所示,为 RA6M5 芯片 GPT 外设模块的结构框图:
28.3.1. 计数器¶
见图中标注 ① 处。
GTCNT 是 GPT 定时器模块内部的计数器,实际上,计数器是实现定时器外设的各种功能的基础。 因此,了解计数器的规格和功能非常重要。
对于 RA6M5,共有 10 个 GPT 定时器(GPT0~9),而 GPT 又分为 GPT32 和 GPT16。 GPT32 有4个(GPT0~3),计数器为 32bit,在上图中用 GPT320~GPT323 来表示; 而 GPT16 有6个(GPT4~9),计数器为 16bit,在上图中用 GPT164~GPT169 来表示。
GPT 的计数器支持递增计数,递减计数和递增/递减计数(即递增与递减计数轮流进行)。
28.3.2. 时钟输入¶
见图中标注 ② 处。
GPT 定时器的时钟输入可以选择内部的 PCLKD 分频后输入或者选择通过 GTETRGn 引脚输入外部时钟。 这两类中只能选择一个,若选择外部时钟输入则定时器不能对内部时钟输入进行计数。
PCLKD / n(n = 1/2/4/8/16/32/64/256/1024)
GTETRGA, GTETRGB, GTETRGC, GTETRGD(通过 POEG)
注:PCLKD / 1 表示的是不分频。
28.3.3. 周期设置和周期设置缓冲寄存器¶
见图中标注 ③ 处。
GTPR 周期设置寄存器是一个可读写的寄存器,用户可通过该寄存器设置计数器 GTCNT 的最大计数值,计数超过该值就会溢出,因此该值决定了计数器的计数周期。 GTPR 的有效大小与 GTCNT(16位或32位)相同。如果 GTPR 的有效大小为16位,则用户读取其高16位始终为0,并且其高16位的写入作会被忽略。
GTPBR 周期设置缓冲寄存器同样也是一个可读写的寄存器,用作 GTPR 的缓冲寄存器,GPT 计数器每计数溢出一次,就会将 GTPBR 的值写入 GTPR。 GTPBR 的有效大小也与 GTCNT(16位或32位)相同。如果 GTPBR 的有效大小为16位,则用户读取其高16位始终为0,并且其高16位的写入作会被忽略。
28.3.4. 控制寄存器¶
见图中标注 ④ 处。
这部分的寄存器较多、也较复杂,它们都属于用于控制定时器功能的寄存器。 而我们主要关注的是定时器的实际功能,对其过于深入的实现细节不做过多研究,因此这里不对这些寄存器进行详细讲解。
28.3.5. 比较器和比较/输入捕获寄存器¶
见图中标注 ⑤ 处。
这部分包含一个比较器和6个 GTCCRx 寄存器(x = A,B,C,D,E,F)。 这6个 GTCCRx 寄存器的功能并不完全相同:
GTCCRA 和 GTCCRB 是用于输出比较和输入捕捉的寄存器。
GTCCRC 和 GTCCRE 可用作比较匹配寄存器,也可以分别作为 GTCCRA 和 GTCCRB 的缓冲寄存器 (构成 GTCCRA 和 GTCCRB 的单缓冲寄存器)。
GTCCRD 和 GTCCRF 可用作比较匹配寄存器,也可以分别作为 GTCCRC 和 GTCCRE 的缓冲寄存器 (构成 GTCCRA 和 GTCCRB 的双缓冲寄存器:即 GTCCRC 和 GTCCRD 作为 GTCCRA 的双缓冲,GTCCRE 和 GTCCRF 作为 GTCCRB 的双缓冲)。
比如,在普通的 PWM 输出模式下,比较器将计数器 GTCNT 与 GTCCRA 和 GTCCRB 进行比较,若匹配(比较结果相等), 则根据 GTIOR(General PWM Timer I/O Control Register)寄存器的 GTIOA[4:0] 和 GTIOB[4:0] 的配置来切换 GTIOCA 和 GTIOCB 的输出电平。 可以将 GTIOCA 和 GTIOCB 切换为低电平、高电平、或者反转电平。
输入捕获与输出比较功能将在稍后详细介绍。
28.3.6. 中断请求信号¶
见图中标注 ⑥ 处。
图中显示的是 GPT0 的中断请求信号,中断请求信号用于产生中断、或者通过 ELC 链接到其他模块。
GPT 提供以下中断源:
GTCCR 输入捕捉/比较匹配
GTCNT 计数器上溢(超出 GTPR 设置的值)/下溢
周期计数功能完成
每个中断源都有自己的状态标志。 当一个中断源信号产生时,相关的状态标志会被硬件自动设置为 1。 状态标志可以通过写入 0 来清除。需要注意的是,如果标志设置和标志清除同时发生,标志清除优先于标志设置。
28.3.7. IO输入输出引脚¶
见图中标注 ⑦ 处。
GTIOCnA 和 GTIOCnB 是 GPT 的 IO 输入输出引脚,用于信号输出和输入捕获。 它们还配备了噪声滤波器(Noise Filter),噪声滤波器以采样时钟对输入信号进行采样,并去除长度小于3个采样周期的脉冲。 用户可设置是否启用噪声滤波器。
噪声滤波器功能的时序图如下所示:
28.3.9. 输出相位切换¶
见图中标注 ⑨ 处。
输出相位切换(GPT_OPS)功能通过输出相位切换控制寄存器(OPSCR)进行控制,用于实现轻松控制无刷直流电机运行的功能。 图中的三相 PWM 波形生成器接收来自比较器的 GPT320.GTIOC0A 的 PWM 信号输入后,输出三相 PWM 用于驱动直流无刷电机。
需要注意的是:GPT_OPS 功能在 RA6M5 中只有一个,并不是每个 GPT 定时器都对应有一个,也就是说通过该功能只能轻松控制一个直流无刷电机。
28.4. GPT比较匹配功能详解¶
比较匹配主要是用来实现输出 PWM 波形功能的一种机制。 其原理简单来讲就是通过比较 GTCNT 计数器的值与 GTCCRA 或 GTCCRB 的值,当比较匹配发生时,会生成相应的比较匹配事件信号, 同时,GPT 可以切换 GTIOCnA 或 GTIOCnB 输出引脚的输出信号,可以选择输出低电平、高电平或翻转输出。 在 GTCNT 完成一个周期的计数时,也可以切换 GTIOCnA 或 GTIOCnB 输出引脚的输出信号。
PWM 输出就是对外输出脉宽(即占空比)可调的方波信号, 信号频率由周期设定寄存器 GTPR 的值决定,占空比由比较寄存器 GTCCR 的值决定。
GPT 的输出模式有:
锯齿波 PWM 模式(Saw-wave PWM mode)(该模式下单缓冲或双缓冲都可使用)
锯齿波单脉冲模式(Saw-wave one-shot pulse mode)(该模式下使用固定的缓冲操作)
三角波 PWM 模式1(Triangle-wave PWM mode 1)(波谷32位传输)(该模式下单缓冲或双缓冲都可使用)
三角波 PWM 模式2(Triangle-wave PWM mode 2)(波峰和波谷32位传输)(该模式下单缓冲或双缓冲都可使用)
三角波 PWM 模式3(Triangle-wave PWM mode 3)(波谷64位传输)(该模式下使用固定的缓冲操作)
我们在前面有提到过每个GPT定时器模块内部都有 6个 GTCCRx 寄存器(x = A,B,C,D,E,F), 下面就先来了解一下有关 GTCCRx 的缓冲操作。
单缓冲操作:
以 GTIOCA 输出为例,若需要修改占空比,则需要在 GTCCRC 写入要修改的比较值, GTCNT 计数完成后则会将 GTCCRC 的值写入 GTCCRA,如下图所示。
双缓冲操作:
在三角波 PWM 输出模式下,GTCCRA / GTCCRB 各有两个缓存,缓存传输的顺序分别为:
GTCCRD -> GTCCRC -> GTCCRA
GTCCRF -> GTCCRE -> GTCCRB
以 GTIOCA 为例,如下图所示。
28.4.1. 锯齿波PWM模式(普通PWM模式)¶
这里的锯齿波 PWM 模式即我们通常一般所说的 PWM 输出模式,该模式的效果是输出一个普通的 PWM 波形。 其原理是:GTCNT 进行递增计数,当 GTCNT 与 GTCCRA / GTCCRB 比较相等时, 输出到 GTIOCnA / GTIOCnB 引脚的 PWM 波形发生变化(一般是翻转电平), 然后当 GTCNT 计数到 GTPR 周期设置寄存器的值相等后,在下一个时钟计数是计数器清零, 此时输出到 GTIOCnA / GTIOCnB 引脚的 PWM 波形也发生变化。 在这整个过程中,GTCNT 计数器的计数值以锯齿波的方式变化(计数器递增计数或递减计数)。
需要注意,这里的锯齿波指的是,GTCNT 计数器的值以锯齿波的方式变化,也就是 GTCNT 向上递增计数或向下递减计数, 并不是指 GTIOCnA 和 GTIOCnB 这两个 IO 引脚输出锯齿波, 记住无论在哪个 PWM 模式下,IO 引脚输出的都是逻辑值为 0 或 1 的高低电平。
当 GTCNT 与 GTCCRA / GTCCRB 相等,以及 GTCNT 完成一个周期的计数时,IO引脚切换为高电平、低电平、翻转电平或者维持电平不变。 具体可以由寄存器 GTIOR 的位段 GTIOB / GTIOA 控制,这个位段的控制逻辑如下表所示。 b4为1时,初始化电平为高电平,其他与下表相同,为节省篇幅,省略这一部分。
在锯齿波 PWM 模式下可使用单缓冲操作或者双缓冲操作。 在单缓冲操作模式下,GTCCRC 作为 GTCCRA 的缓存,GTCCRE 作为 GTCCRB 的缓存。 如下图所示的示例是在 GTCNT 向上计数,发生比较匹配时输出高电平,计数周期结束时输出低电平, 即 GTIOA / GTIOB 的 b3~b0 依次为 0110 时,GTIOCxA / GTIOCxB 引脚的输出时序。
锯齿波 PWM 模式的输出波形时序图如下图所示。
28.4.2. 三角波PWM模式1(波谷32位传输)¶
这里的三角波同样指的是,GTCNT 内的值以三角波的方式变化,也就是 GTCNT 向上递增计数接着向下计数递减计数。 其他地方与锯齿波 PWM 模式基本相同。 如下图所示的示例是在初始化时 GTIOCnA 输出低电平,GTIOCnB 输出高电平,当 GTCCRA / GTCCRB 发生比较匹配时, GTIOCxA / GTIOCxB 反转电平,GTIOCxA/GTIOCxB 的输出时序。
三角波 PWM 模式 1 的输出波形时序图如下图所示。
28.4.3. 三角波PWM模式2(波峰和波谷32位传输)¶
该模式与三角波 PWM 模式1的区别在于在波峰和波谷都会传输缓冲。也属于单缓冲操作。
三角波 PWM 模式 2 的输出波形时序图如下图所示。
28.4.5. 设置死区时间¶
死区时间可通过设置 GTCCRA / GTCCRB 来配置。 自动死区时间设置功能可用于锯齿波单脉冲模式和所有三角波 PWM 模式。
28.5. GPT输入捕获功能详解¶
输入捕获是定时器的一项非常重要的功能。通过输入捕获功能, 我们可以测量高低电平脉冲的脉宽、信号的周期、频率和 PWM 占空比等。
在检测到在 GTICASR 和 GTICBSR 中设置的硬件源时,可以将 GTCNT 计数器值传输到 GTCCRA 或 GTCCRB,这便是 GPT 的输入捕获功能。 “输入捕获”根据“输入”来触发“捕获” GTCNT 计数器的值,更加具体地说: 硬件在检测到我们所设置的硬件源时,“捕获” GTCNT 计数器的值并转存到 GTCCRA 或 GTCCRB 寄存器。
我们可以设置如下硬件源来触发执行输入捕获:
在 GTETRGA 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGA 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGB 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGB 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGC 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGC 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGD 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
在 GTETRGD 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnB 输入为 0 时,在 GTIOCnA 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnB 输入为 1 时,在 GTIOCnA 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnB 输入为 0 时,在 GTIOCnA 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnB 输入为 1 时,在 GTIOCnA 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnA 输入为 0 时,在 GTIOCnB 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnA 输入为 1 时,在 GTIOCnB 引脚输入的上升沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnA 输入为 0 时,在 GTIOCnB 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
当 GTIOCnA 输入为 1 时,在 GTIOCnB 引脚输入的下降沿启用或禁用 GTCCRA / GTCCRB 的输入捕捉。
下图所示的示例清晰地展示了输入捕获的功能。 在该示例中,GTCNT 计数器通过计数时钟进行递增计数, 并且设置为在 GTIOCnA 输入引脚的两个边沿执行 GTCCRA 的输入捕捉,在 GTIOCnB 输入引脚的上升沿执行 GTCCRB 的输入捕捉。
28.6. 实验1:基本定时功能¶
本实验较为简单,我们将使用 GPT 定时器进行定时并产生中断请求触发中断,然后通过这个中断,切换 LED 的电平。
28.6.1. 硬件设计¶
本次实验需要使用到 LED 灯来展示定时的效果,LED 灯具体的电路讲解请读者参考本教程第8章“8.4. 实验:使用寄存器点亮LED灯”小节的内容。 这里不再赘述。
注:本实验仅用到 LED1~3 当中的其中一盏。
28.6.2. 软件设计¶
28.6.2.1. 新建工程¶
由于本实验需要用到LED,也会用到串口打印提示信息, 因此我们在前面串口通信章节的“实验1:UART收发回显”例程的基础上修改程序。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “27_GPT_Basic_Timing”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “27_GPT_Basic_Timing”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “gpt” 文件夹, 再进入 “gpt” 文件夹里面新建源文件和头文件:“bsp_gpt_timing.c” 和 “bsp_gpt_timing.h”。 工程文件结构如下。
27_GPT_Basic_Timing
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ gpt
│ ├─ bsp_gpt_timing.c
│ └─ bsp_gpt_timing.h
└─ hal_entry.c
28.6.2.2. FSP配置¶
首先打开 FSP 配置界面,在 Stacks 中加入 GPT,如下图所示。
我们使用 GPT0 来实现定时和触发中断功能, 需要修改的只有“General”和“Interrupts”部分,其他按照默认即可,如下图所示。
在上图中我们可以将 GPT 模块属性进行区域性地划分为: “Common”、“General”、“Output”、“Input”、“Interrupts”、“Extra Features”、“Pins”这几个部分。
本实验主要配置“General”和“Interrupts”这几个部分的属性,因此在这里会对它们进行详细讲解。 而“Common”和“Extra Features”暂未用到,因此这里不需要对其进行配置。 由于本实验不需要 GPT 的 IO 输出输入,故“Output”和“Input”以及“Pins”会放在后面的实验讲解。 其中,“Output” 属性会在后面的实验2中使用到 PWM 输出功能时进行讲解, “Input” 属性会在实验3中使用到输入捕获功能时进行讲解。
“Common”部分的配置属性:
属性 |
描述 |
---|---|
Parameter Checking |
参数检查(选择是否生成包含参数检查的代码)。 |
Pin Output Support |
支持引脚输出。 “Enabled”或“Disabled” 用于使能或禁止 GTIOCx 输出PWM信号。 “Enabled with Extra Features” 用于支持三角波输出模式, 以及用于支持“Extra Features”部分的特性。 |
Write Protect Enabled |
GPT寄存器的写保护。选择使能/禁止写保护并应用于所有GPT通道。 若使能了这一项,则无法对GPT寄存器进行写操作,需要使用 r_gpt_write_protect_disable() 函数取消写保护,才能写入寄存器。 |
Clock Source |
时钟源选择。 只能选择 PCLKD 作为内部时钟源。 |
“General”部分的配置属性:
属性 |
描述 |
---|---|
Name |
模块名字。 |
Channel |
通道选择。指定硬件通道,也就是指定使用哪一个GPT定时器。 |
Mode |
可以选择周期计数,单次计数,PWM模式,三角波对称PWM模式和三角波不对称PWM模式。 Periodic One-Shot PWM One-Shot Pulse Triangle-Wave Symmetric PWM Triangle-Wave Asymmetric PWM Triangle-Wave Asymmetric PWM (Mode 3) |
Period |
计数器的计数周期。 |
Period Unit |
计数器计数周期的单位。 |
“Interrupts”部分的配置属性:
属性 |
描述 |
---|---|
Callback |
中断回调函数名。 |
Overflow/Crest Interrupts Priority |
选择上溢中断的优先级。如果选择 Disabled 则禁止该中断。 该中断亦是三角波的波峰中断。 |
Capture A Interrupts Priority |
选择捕获A中断的优先级。如果选择 Disabled 则禁止该中断。 |
Capture B Interrupts Priority |
选择捕获B中断的优先级。如果选择 Disabled 则禁止该中断。 |
Underflow/Trough Interrupts Priority |
选择下溢中断的优先级。如果选择 Disabled 则禁止该中断。 该中断亦是三角波的波谷中断。 |
“Extra Features”部分的配置属性:
属性 |
描述 |
---|---|
Output Disable > Output Disable POEG Trigger |
选择一个信号触发禁用 POEG。 |
Output Disable > POEG Link |
选择哪一个 POEG 连接到该 GPT 通道。 |
Output Disable > GTIOCA Disable Setting |
GTIOCA 的禁用设定。 |
Output Disable > GTIOCB Disable Setting |
GTIOCB 的禁用设定。 |
ADC Trigger > Start Event Trigger (Channels with GTINTAD only) |
选择触发 ADC 转换的时机 |
Dead Time > Dead Time Count Up (Raw Counts) |
选择死区时间(应用到向上计数) |
Dead Time > Dead Time Count Down (Raw Counts) (Channels with GTDVD only) |
选择死区时间(应用到向下计数) |
ADC Trigger (Channels with GTADTRA only) > ADC A Compare Match (Raw Counts) |
设置生成 GPTn AD TRIG A 事件的比较匹配值。 |
ADC Trigger (Channels with GTADTRB only) > ADC B Compare Match (Raw Counts) |
设置生成 GPTn AD TRIG B 事件的比较匹配值。 |
Interrupt Skipping (Channels with GTITC only) > Interrupt to Count |
选择跳过中断的计数源。中断跳过计数器在每个源事件之后递增。 当中断跳过计数器非零时,将跳过所有顶部/溢出中断和槽/底部中断。 |
Interrupt Skipping (Channels with GTITC only) > Interrupt Skip Count |
选择要跳过的中断数。 |
Interrupt Skipping (Channels with GTITC only) > Skip ADC Events |
当中断跳过计数不为零时,选择要禁止的 ADC 事件。 |
Extra Features |
选择是否使能该通道的“Extra Features”(额外特性) |
28.6.2.3. GPT初始化函数¶
GPT 初始化函数用于初始化 GPT 模块,并且启动 GPT 定时器,该函数如下所示。
/* GPT 初始化函数 */
void GPT_Timing_Init(void)
{
/* 初始化 GPT0 模块 */
R_GPT_Open(&g_timer_gpt0_ctrl, &g_timer_gpt0_cfg);
/* 启动 GPT0 定时器 */
R_GPT_Start(&g_timer_gpt0_ctrl);
}
在 GPT 初始化函数里面,首先通过 R_GPT_Open 函数初始化 GPT 模块, 随后调用 R_GPT_Start 函数来启动 GPT 定时器。
28.6.2.4. GPT中断回调函数¶
我们在 FSP 配置里配置了 GPT 的中断,溢出中断优先级为10,GPT 中断回调函数如下。
#include "led/bsp_led.h"
/* GPT 中断回调函数 */
void gpt0_timing_callback(timer_callback_args_t * p_args)
{
/* 定时器溢出事件 */
if (TIMER_EVENT_CYCLE_END == p_args->event)
{
/* 翻转 LED1 */
LED1_TOGGLE; //每秒翻转一次
}
}
该中断回调函数非常地简单,直接在回调函数里面使用 if 语句判断触发中断的事件是否是定时器溢出事件, 若是定时器溢出事件则翻转一次 LED1 引脚的电平。 由于定时器被我们配置为1秒的计时周期,因此每1秒会溢出一次,控制 LED1 的引脚的电平会每秒钟翻转一次。
28.6.2.5. hal_entry入口函数¶
以启明6M5开发板的 RA6M5 工程为例,hal_entry 入口函数如下所示。
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "gpt/bsp_gpt_timing.h"
void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
GPT_Timing_Init(); // GPT 初始化
printf("这是一个 GPT 的基本定时功能实验\r\n");
while(1)
{
printf("The program is running ...\r\n");
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
28.6.3. 下载验证¶
编译并下载到开发板后,复位板子让程序运行,然后可以观察到板载 LED1(红色) 每秒钟翻转一次状态,即以两秒为周期在缓慢闪烁。
28.7. 实验2:PWM输出¶
28.7.1. 硬件设计¶
本次实验需要使用到 LED 灯来展示定时的效果,LED 灯具体的电路讲解请读者参考本教程第8章“8.4. 实验:使用寄存器点亮LED灯”小节的内容。 这里不再赘述。
注:本实验仅用到 LED1~3 当中的其中一盏。
野火启明6M5开发板例程使用的 PWM 输出引脚为额外引出的 IO 引脚: P600,如下图所示。
注:
野火启明6M5开发板例程使用的 PWM 输出引脚为额外引出的 IO 引脚: P600 (GTIOC6B)。
野火启明4M2开发板例程使用的 PWM 输出引脚为额外引出的 IO 引脚: P405 (GTIOC1A)。
野火启明2L1开发板例程使用的 PWM 输出引脚为额外引出的 IO 引脚: P115 (GTIOC4A)。
28.7.2. 软件设计¶
28.7.2.1. 新建工程¶
由于本实验需要用到LED,也会用到串口打印提示信息, 因此我们在前面串口通信章节的“实验1:UART收发回显”例程的基础上修改程序。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “27_GPT_PWM_Output”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “27_GPT_PWM_Output”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “gpt” 文件夹, 再进入 “gpt” 文件夹里面新建源文件和头文件:“bsp_gpt_pwm_output.c” 和 “bsp_gpt_pwm_output.h”。 工程文件结构如下。
27_GPT_PWM_Output
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ gpt
│ ├─ bsp_gpt_pwm_output.c
│ └─ bsp_gpt_pwm_output.h
└─ hal_entry.c
28.7.2.2. FSP配置¶
下面以野火启明6M5开发板为例来讲解相关的 FSP 配置。
因为 PWM 输出需要使用 IO 引脚进行输出,因此需要先在“Pins”配置页中为 GPT 配置引脚, 我们将 GPT6 的 GTIOC6B 信号输出连接到 P600 引脚,如下图所示。
然后在“Stacks”配置页中加入 GPT 模块,并对其作如下图所示的配置。
上图中框起来的部分是需要我们去修改的区域,其他的配置属性按照默认即可。 图中需要更改的配置如下:
Pin Output Support:这一项配置允许输出 PWM 信号到引脚,我们改为使能引脚输出。
Name 和 Channel:这两项分别设置 GPT 模块名字为 “g_timer_gpt6” 和选择第 6 个 GPT 定时器(第6个通道)。
Mode:配置 GPT 的工作模式为 PWM 输出模式。
Period 和 Period Unit:我们将PWM频率设为 20 KHz, 因此“Period”设置为 20,单位“Period Unit”设置为 Kilohertz,即千赫兹(KHz)。
GTIOCB Output Enabled:使能 GTIOCB 输出。
GTIOCB Stop Level:设置定时器停止时 GTIOCB 输出的电平为低电平。
GTIOC6B:选择连接到 P600 引脚,这个软件会自动设置的,我们只要确认了就好。
GPT的“Output”部分的属性描述如下表所示。
属性 |
描述 |
---|---|
Custom Waveform > GTIOA/GTIOB > Initial Output Level |
设置GPT初始化时 GTIOCxA/GTIOCxB 的电平(低电平或高电平)。 |
Custom Waveform > GTIOA/GTIOB > Cycle End Output Level |
完成一个计数周期时 GTIOCxA/GTIOCxB 的输出电平(保持不变、输出高、输出低或反转电平)。 |
Custom Waveform > GTIOA/GTIOB > Compare Match Output Level |
发生比较匹配时 GTIOCxA/GTIOCxB 的输出电平(保持不变、输出高、输出低或反转电平)。 |
Custom Waveform > GTIOA/GTIOB > Retain Output Level at Count Stop |
在计数停止时保持 GTIOCxA/GTIOCxB 电平。 |
Custom Waveform > Custom Waveform Enable |
使能自定义波形的配置。 |
Duty Cycle Percent (only applicable in PWM mode) |
设定 PWM 占空比(仅用于 PWM 模式) |
GTIOCA Output Enabled |
使能 GTIOCA 输出到相应引脚 |
GTIOCA Stop Level |
当 GPT 定时器停止时 GTIOCA 的输出电平 |
GTIOCB Output Enabled |
使能 GTIOCB 输出到相应引脚 |
GTIOCB Stop Level |
当 GPT 定时器停止时 GTIOCB 的输出电平 |
28.7.2.3. GPT初始化函数¶
void GPT_PWM_Init(void)
{
/* 初始化 GPT 模块 */
R_GPT_Open(&g_timer_gpt6_ctrl, &g_timer_gpt6_cfg);
/* 启动 GPT 定时器 */
R_GPT_Start(&g_timer_gpt6_ctrl);
/* 重新设置占空比为 80% */
GPT_PWM_SetDuty(80);
}
28.7.2.4. 设置PWM占空比函数¶
/** 自定义函数:设置PWM占空比
@param duty 占空比范围:0~100 %
*/
void GPT_PWM_SetDuty(uint8_t duty)
{
timer_info_t info;
uint32_t current_period_counts;
uint32_t duty_cycle_counts;
if (duty > 100)
duty = 100; //限制占空比范围:0~100
/* 获得GPT的信息 */
R_GPT_InfoGet(&g_timer_gpt6_ctrl, &info);
/* 获得计时器一个周期需要的计数次数 */
current_period_counts = info.period_counts;
/* 根据占空比和一个周期的计数次数计算GTCCR寄存器的值 */
duty_cycle_counts = (uint32_t)(((uint64_t) current_period_counts * duty) / 100);
/* 最后调用FSP库函数设置占空比 */
R_GPT_DutyCycleSet(&g_timer_gpt6_ctrl, duty_cycle_counts, GPT_IO_PIN_GTIOCB);
}
28.7.2.5. hal_entry入口函数¶
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "gpt/bsp_gpt_pwm_output.h"
void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
GPT_PWM_Init(); // GPT 初始化
printf("这是一个 GPT 的PWM输出功能实验\r\n");
printf("使用示波器测量 P600 输出的PWM波形\r\n");
// LED1 闪烁指示程序正在运行...
while(1)
{
LED1_ON;
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
LED1_OFF;
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
28.7.3. 下载验证¶
以野火启明6M5开发板为例, 编译并下载程序后,复位开发板使程序重新运行,然后使用示波器测量 P600 引脚输出的PWM波形。 实现现象如下。
频率 20 KHz,占空比为 50% 的PWM波形:
频率 20 KHz,占空比为 80% 的PWM波形:
小技巧
人眼对于每11毫秒闪烁一次(约83赫兹)基本感觉不到闪烁,每13毫秒闪烁一次(约66赫兹)感觉到轻微频闪。 那么我们平时见到的LED灯,当它的频率大于50Hz的时候,人眼就会产生视觉暂留效果,基本就看不到闪烁了,而是一个常亮的LED灯。 因此,我们可以通过PWM让LED高频闪烁,并控制PWM占空比实现LED的亮度变化, 通过这个原理可以实现呼吸灯的效果,感兴趣的读者可自行尝试去实现它。
28.8. 实验3:输入捕获——脉宽和周期测量¶
上一个实验使用了 GPT 的输出功能,本次实验要使用 GPT 的输入功能,通过 GPT 的输入捕获进行脉宽和周期测量。 其测量的原理如下图所示。
首先启动 GPT 定时器,在上升沿 A 处进行捕获,记此时时间为 A; 紧接着在下降沿 B 处进行捕获,记此时时间为 B; 最后再次在上升沿 C 处进行捕获,记此时时间为 C 或者 A’。 设 PWM 的周期为 T,占空比为 D,频率为 f,则:
28.8.1. 硬件设计¶
以野火启明6M5开发板为例, 本实验使用 P600 和 P603 这两个引脚,P600 用于输出PWM信号,P603 用于输入捕获。 将 P600 与 P603 使用杜邦线连接,引脚在引出排针处,如下图所示。
注:
野火启明6M5开发板例程选用的 PWM 输出引脚为:P600 (GTIOC6B);选用的输入捕获引脚为:P603 (GTIOC7A)。
野火启明4M2开发板例程选用的 PWM 输出引脚为:P405 (GTIOC1A);选用的输入捕获引脚为:P414 (GTIOC0B)。
野火启明2L1开发板例程选用的 PWM 输出引脚为:P115 (GTIOC4A);选用的输入捕获引脚为:P113 (GTIOC2A)。
本次实验需要将 PWM 输出引脚与输入捕获引脚使用杜邦线连接起来。
28.8.2. 软件设计¶
28.8.2.1. 新建工程¶
由于本实验的目标是测量PWM的周期、占空比,因此我们在上一个实验的基础上修改程序。
- 对于 e2 studio 开发环境:
拷贝一份我们之前的 e2s 工程 “27_GPT_PWM_Output”, 然后将工程文件夹重命名为 “27_GPT_Input_Capture”,最后再将它导入到我们的 e2 studio 工作空间中。
- 对于 Keil 开发环境:
拷贝一份我们之前的 Keil 工程 “27_GPT_PWM_Output”, 然后将工程文件夹重命名为 “27_GPT_Input_Capture”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。
工程新建好之后,在工程根目录的 “src/gpt” 路径下面, 新建源文件和头文件:“bsp_gpt_input_capture.c” 和 “bsp_gpt_input_capture.h”。 工程文件结构如下。
27_GPT_Input_Capture
├─ ......
└─ src
├─ led
│ ├─ bsp_led.c
│ └─ bsp_led.h
├─ debug_uart
│ ├─ bsp_debug_uart.c
│ └─ bsp_debug_uart.h
├─ gpt
│ ├─ bsp_gpt_pwm_output.c
│ ├─ bsp_gpt_pwm_output.h
│ ├─ bsp_gpt_input_capture.c //新增文件
│ └─ bsp_gpt_input_capture.h //新增文件
└─ hal_entry.c
28.8.2.2. FSP配置¶
下面以野火启明6M5开发板为例来讲解相关的 FSP 配置。
由于在上一个实验中我们已经配置好了 P600 用于输出PWM信号, 因此本实验中只需要添加输入捕获需要使用到 IO 引脚 P603,需要在“Pins”配置页面中配置 GPT7 所使用的引脚。
在“Stacks”配置页面中加入 GPT,按如下图所示的配置进行设置。 需要注意的是,因为“Input”里面的各类配置项目若是展开来的话篇幅比较长,因此在图中并未展开出来, 我们要注意勾上以下几个选项:
- 展开 Input->Capture A Source 并勾上(在 GTIOCA 的上升沿启用输入捕获 A,无论 GTIOCB 引脚是高电平还是低电平):
GTIOCA Rising Edge While GTIOCB Low
GTIOCA Rising Edge While GTIOCB High
- 展开 Input->Capture B Source 并勾上(在 GTIOCA 的下降沿启用输入捕获 B,无论 GTIOCB 引脚是高电平还是低电平):
GTIOCA Falling Edge While GTIOCB Low
GTIOCA Falling Edge While GTIOCB High
GPT的“Input”选项卡的属性描述
属性 |
描述 |
---|---|
Count Up Source |
选择计数器向上计数的外部源。 如果某个外部向上计数源被选中,该定时器将在该源的驱动下进行计数, 而不是计数 PCLKD 周期。 |
Count Down Source |
选择计数器向下计数的外部源。 如果某个外部向下计数源被选中,该定时器将在该源的驱动下进行计数, 而不是计数 PCLKD 周期。 |
Start Source |
选择外部源来启动定时器。 |
Stop Source |
选择外部源来停止定时器。 |
Clear Source |
选择外部源来清除定时器。 |
Capture A Source |
选择外部源来触发一个捕获 A 事件。 |
Capture B Source |
选择外部源来触发一个捕获 B 事件。 |
Noise Filter A Sampling Clock Select |
GTIOCA 输入噪声滤波器的采样时钟选择。 |
Noise Filter B Sampling Clock Select |
GTIOCB 输入噪声滤波器的采样时钟选择。 |
28.8.2.3. GPT初始化¶
初始化 GPT 模块用于输入捕获的函数如下。 在这个函数中,我们先初始化 GPT 模块,然后调用 R_GPT_InfoGet 函数获取 GPT 的配置信息, 主要是为了获取计数器的计数周期,即 GPT 一个周期的计数次数,并保存到全局变量 period 中。
最后使能输入捕获,并启动 GPT 定时器。
timer_info_t info; //用于获取定时器参数信息
uint32_t period; //输入捕获计数器的计数周期
/* GPT初始化函数 */
void GPT_InputCapture_Init(void)
{
/* 初始化 GPT 模块 */
R_GPT_Open(&g_timer_gpt7_ctrl, &g_timer_gpt7_cfg);
/*获取当前参数*/
(void) R_GPT_InfoGet(&g_timer_gpt7_ctrl, &info);
/* 获取计数周期:GPT的一个周期的计数次数 */
period = info.period_counts;
/* 使能输入捕获 */
R_GPT_Enable(&g_timer_gpt7_ctrl);
/* 启动 GPT 定时器 */
R_GPT_Start(&g_timer_gpt7_ctrl);
}
28.8.2.4. GPT中断回调函数¶
GPT 输入捕获中断回调函数如下。
/* 保存所测量的PWM信号的信息 */
uint32_t pwm_period; //PWM周期
uint32_t pwm_high_level_time; //PWM高电平的时间
uint32_t pwm_freq; //PWM频率
uint32_t pwm_duty; //PWM占空比
/* GPT 输入捕获中断回调函数 */
void gpt7_input_capture_callback(timer_callback_args_t * p_args)
{
static uint32_t a_time; // A 上升沿捕获的时间
static uint32_t b_time; // B 下降沿捕获的时间
static uint32_t c_time; // C 上升沿捕获的时间(其实也就是 A 可以用A'表示)
static uint32_t overflow_times; //计数器溢出次数
static uint8_t one_period_flag=0; //用于表示是否完成对一个完整周期的测量
switch(p_args->event)
{
/* 捕获到上升沿 -- 有可能是 A 或者 C (A') 位置 */
case TIMER_EVENT_CAPTURE_A:
/* A 开始对某个周期进行测量 */
if (0 == one_period_flag)
{
a_time = p_args->capture; //记录捕获的时间 A
overflow_times = 0; //初始化计数器溢出次数
one_period_flag ++; //表示即将完成对某个周期的测量
}
/* C (A') 如果测量完了一个周期,则计算PWM信号周期和高电平的时间 */
else if (1 == one_period_flag)
{
c_time = p_args->capture + overflow_times * period; //记录捕获的时间 C
//计算 PWM 周期
pwm_period = c_time - a_time;
//清零所有标志位
overflow_times = 0;
one_period_flag = 0; //标志位清0,重新进入下一轮的测量
}
break;
/* 捕获到下降沿 -- 是 B 位置 */
case TIMER_EVENT_CAPTURE_B:
//如果是在测量周期内检测到下降沿
if (1 == one_period_flag)
{
b_time = p_args->capture + overflow_times * period; //记录捕获的时间 B
pwm_high_level_time = b_time - a_time; //计算高电平时间
}
break;
/* 定时器计数溢出事件 */
case TIMER_EVENT_CYCLE_END:
/* 输入捕获期间计数器溢出,则记录溢出次数+1 */
overflow_times++;
break;
default:
break;
}
}
在 GPT 中断回调函数中,我们使用 switch 语句判断触发中断的事件类型。 GPT 在引脚输入 PWM 信号的上升沿触发捕获 A 事件,在 PWM 信号的下降沿触发捕获 B 事件, 并在计数器溢出时触发定时器计数溢出事件。
警告
注意:建议不要在中断回调函数中打印数据,因为 PWM 频率一般很高, 而 printf 函数执行所消耗的时间比较长,可能会阻塞中断服务函数的运行。同时也应该时刻注意中断优先级的问题。
28.8.2.5. hal_entry入口函数¶
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "gpt/bsp_gpt_pwm_output.h"
#include "gpt/bsp_gpt_input_capture.h"
// 外部变量声明
extern timer_info_t info; //用于获取定时器参数信息
extern uint32_t pwm_period; //PWM周期
extern uint32_t pwm_high_level_time; //PWM高电平的时间
extern uint32_t pwm_freq; //PWM频率
extern uint32_t pwm_duty; //PWM占空比
void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
GPT_PWM_Init(); // GPT PWM输出初始化
GPT_InputCapture_Init(); // GPT 输入捕获初始化
printf("这是一个 GPT 的PWM输出 + 输入捕获功能实验\r\n");
printf("使用杜邦线连接 P600 和 P603 引脚,然后打开串口助手查看串口的打印信息\r\n");
while(1)
{
/* 计算PWM的频率 */
pwm_freq = info.clock_frequency / pwm_period;
/* 计算PWM的占空比 */
pwm_duty = pwm_high_level_time * 100 / pwm_period;
// 打印
printf("High=%d, Period=%d, ", pwm_high_level_time, pwm_period);
printf("Friquency = %dHz, Duty_Cycle = %d%%\r\n", pwm_freq, pwm_duty);
pwm_period = pwm_high_level_time = pwm_freq = 0; //打印完后旧数据清零
// LED1 闪烁指示程序正在运行...
LED1_TOGGLE;
// 间隔1s
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
28.8.3. 下载验证¶
以野火启明6M5开发板为例, 编译并下载程序后,复位开发板使程序重新运行,然后使用杜邦线连接 P600 和 P603 引脚,然后打开串口助手查看串口的打印信息。 串口会打印出 PWM 信号的频率和占空比等信息,实验现象如下图所示。