[MSPM0开发]之八 MSPM0G3507 定时器的配置与使用(定时中断、输出比较PWM、正交编码器计数)
一、MSPM0G3507定时器概述
MSPM0 G 系列定时器有两种,通用定时器和高级定时器,高级定时器包含通用定时器的功能。
说明:
• “TIMx”表示在 TIMA 和 TIMA 上可用的一个常见功能。
• “TIMA”表示仅在 TIMA (高级定时器)上可用的一个功能。
• “TIMG”表示仅在 TIMG(通用定时器) 上可用的一个功能。
1.1 通用定时器TIMG
1.1.1 通用定时器TIMG
TIMG 模块包含由可编程预分频器驱动的 16 位和 32 位自动重新加载计数器, 以及两个用于多个捕获/比较、 PWM输出和间隔计时的捕获/比较 (CC) 块。 TIMG 还具有广泛的事件生成功能, 包括针对各种用例的计数器溢出、重新加载和捕获/比较操作。
1.1.2 通用定时器TIMG 特性
TIMG 的具体特性包括:
• 具有重复重新加载模式的16 位或 32位向上、向下或向上/向下计数器
• 用于对计数器时钟频率进行分频的 8 位可编程预分频器
• 最多两个独立通道, 用于:
–== 输出比较==
– 输入捕捉
– PWM 输出( 边沿对齐和中心对齐)
– 单次触发模式
• 用于加载寄存器的影子寄存器模式
• 用于 CC 寄存器的影子比较模式
• 支持用于定位和移动检测的正交编码器接口 (QEI) 和 3 输入霍尔传感器模式
• 支持同一电源域中不同 TIMx 实例之间的同步和交叉触发
• 支持中断/DMA 触发生成以及跨外设( 例如 ADC、 DAC 等) 触发功能
1.1.3 通用定时器TIMG 功能框图
1.2 高级定时器TIMA
1.2.1 高级定时器TIMA概述
TIMA 模块包含一个由可编程预分频器驱动的 16 位自动重新加载计数器, 以及最多四个用于多次捕获/比较、具有死区插入的 PWM 输出和间隔计时的捕获/比较 (CC) 块。 TIMA 具有广泛的事件生成功能, 可生成不同的计数器事件( 例如溢出事件、重新加载事件以及来自每个捕获/比较寄存器的事件) 。它还具有处理由内部或外部电路生成的故障信号以指示系统中故障的硬件设计。
1.2.2 高级定时器TIMA特性
每个 TIMA 实例的具体特性包括:
• 具有重复重新加载模式的 16位向上、向下或向上/向下计数器
• 可选和可配置的时钟源
• 用于对计数器时钟频率进行分频的 8 位可编程预分频器
•== 重复计数器==, 仅在计数器的给定周期数之后生成中断或事件
• 最多四个独立通道, 用于:
– 输出比较
– 输入捕捉
– PWM 输出( 边沿对齐和中心对齐)
– 单次触发模式
• 用于加载和 CC 寄存器的影子寄存器
• 具有可编程死区插入功能的互补 PWM 输出
• 非对称 PWM 输出
• 故障处理机制, 确保在遇到故障状况时, 输出信号处于用户定义的安全状态
• 支持同一电源域中不同 TIMx 实例之间的同步和交叉触发
• 支持中断触发生成以及跨外设( 例如 ADC 或 DAC) 触发功能
• 两个用于内部事件的额外捕捉/比较通道
1.2.3 通用定时器TIMA 功能框图
二、各定时器功能汇总表(含TIMA、TIMG)
通过上图可以看出,MSPM0 G系列最多包含15个通用定时器,最多两个高级定时器。
在 TIMA 中, 外部 PWM 通道是互补 PWM 输出信号对, 具有相对于 CC 块实例的死区生成功能,例如 TIMA0_C0 和 TIMA0_C0N。为了生成独立的 PWM 输出, 必须使用独立的同相通道, 例如TIMA0_C0 和 TIMA0_C1。
三、 driverLib库定时器相关例程概述
例程说明官方链接:https://dev.ti.com/tirex/explore/node?node=A__AA7HnkYfqpzQn0-hLCYBPg__MSPM0-SDK__a3PaaoK__LATEST
四、TIMx 周期重复计数定时中断模式
时钟配置:使用内部高速时钟,32MHz,未使用PLL
定时器配置:
添加定时器,在profile中直接选择快速实现定时中断,周期500ms的选项
接下来选择时钟源(已经配置好),根据选择的时钟源的频率配置各分频系数,使得最后计算定时周期计数值为整数即可(以周期值体现)
计数模式为 向下计数
中断配置自动选择为 zero event (向下计数到达0)
通过“Quick Profiles”中选择“Periodic with 500 ms Period and Zero Event”,然后选择“Timer Clock Source ”、“Timer Clock Divider ”、“Timer Clock Prescaler ”,然后就可以计算出500ms对应的计数值,如果时钟频率过高,无法配置500ms中断,则会有错误提示。
高级特性:
中断事件及中断优先级:
定时器选择:
然后配置LED引脚PB22。
保存
下一步修改中断服务函数:
复制tima_timer_mode_periodic_repeat_count示例程序中的定时中断服务函数,然后做以下修改:
原函数内容:
void TIMER_0_INST_IRQHandler(void)
{
switch (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMERA_IIDX_REPEAT_COUNT:
DL_GPIO_togglePins(GPIO_LEDS_PORT,
GPIO_LEDS_USER_LED_1_PIN | GPIO_LEDS_USER_TEST_PIN);
break;
default:
break;
}
}
修改case判断的标志为zero对应的标志,同时修改LED对应的port和pin
void TIMER_0_INST_IRQHandler(void) {
switch (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMERA_IIDX_ZERO:
DL_GPIO_togglePins(GPIO_GRP_0_PORT, GPIO_GRP_0_LED_PIN_22_PIN);
default:
break;
}
}
main.c中主函数如下:
int main(void) {
// 初始化系统配置,这是程序运行前的必要设置
SYSCFG_DL_init();
// 启用定时器0的中断,允许定时器0在计数完成时触发中断
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
// 配置系统控制,使能在退出中断时进入睡眠模式,以降低功耗
DL_SYSCTL_enableSleepOnExit();
// 启动定时器0的计数器,开始计数
DL_TimerA_startCounter(TIMER_0_INST);
// 无限循环,使程序持续运行
while (1) {
// 等待中断,进入低功耗模式直到下一次中断发生
__WFI();
}
}
在SYSCFG_DL_init() 函数中有对应低功耗模式配置:
SYSCONFIG_WEAK void SYSCFG_DL_SYSCTL_init(void)
{
//Low Power Mode is configured to be SLEEP0
DL_SYSCTL_setBORThreshold(DL_SYSCTL_BOR_THRESHOLD_LEVEL_0);
DL_SYSCTL_setFlashWaitState(DL_SYSCTL_FLASH_WAIT_STATE_2);
DL_SYSCTL_setSYSOSCFreq(DL_SYSCTL_SYSOSC_FREQ_BASE);
/* Set default configuration */
DL_SYSCTL_disableHFXT();
DL_SYSCTL_disableSYSPLL();
DL_SYSCTL_setHFCLKSourceHFXTParams(DL_SYSCTL_HFXT_RANGE_32_48_MHZ,0, false);
DL_SYSCTL_setULPCLKDivider(DL_SYSCTL_ULPCLK_DIV_1);
DL_SYSCTL_enableMFCLK();
DL_SYSCTL_setMCLKSource(SYSOSC, HSCLK, DL_SYSCTL_HSCLK_SOURCE_HFCLK);
}
运行结果:
LED每1秒闪烁一次。
五、 TIMA 输出pwm
时钟配置同上。
sysconfig中直接选择TIMER-PWM,然后直接在配置也配置参数即可:
选择4个通道
PWM参数配置
高级参数配置
引脚可以重映射到兼容的引脚:
生成初始化代码,修改主程序中的代码(参考:timx_timer_mode_pwm_edge_sleep_shadow例程):
#include "ti_msp_dl_config.h"
#define PWM_PERIOD_UPDATE_CNT (300U)
const uint32_t gPeriodVal[9] = {7999, 6999, 5999, 4999, 3999,2999, 1599, 999, 99};//用于修改周期,例程中么有使用
const uint32_t gDutyVal[9] = {7999, 6999, 5999, 4999, 3999,2999, 1599, 999, 99};//用于修改占空比
volatile uint32_t gIndex, gIndex1;
volatile uint32_t gUpdateCnt, gUpdateCnt1;
int main(void) {
SYSCFG_DL_init();//硬件初始化
gIndex = 0;
gUpdateCnt = PWM_PERIOD_UPDATE_CNT;
gIndex1 = 0;
gUpdateCnt1 = PWM_PERIOD_UPDATE_CNT;
NVIC_EnableIRQ(PWM_0_INST_INT_IRQN); //使能中断
DL_TimerA_startCounter(PWM_0_INST); // 开始定时器计数
DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN); // 用户LED
while (1) {
__WFI(); //低功耗
}
}
//在各自中断中修改占空比比较值
void PWM_0_INST_IRQHandler(void) {
switch (DL_TimerA_getPendingInterrupt(PWM_0_INST)) {
case DL_TIMERA_IIDX_CC0_DN: //通道0中断
if (gUpdateCnt == 0) {
gUpdateCnt = PWM_PERIOD_UPDATE_CNT;
if (gIndex > ((sizeof(gPeriodVal) / sizeof(uint32_t)) - 1)) {
gIndex = 0;
}
// DL_TimerA_setLoadValue(PWM_0_INST, gPeriodVal[gIndex]);
DL_TimerA_setCaptureCompareValue(PWM_0_INST, gDutyVal[gIndex],
DL_TIMERA_CAPTURE_COMPARE_0_INDEX);
gIndex++;
} else {
gUpdateCnt--;
}
case DL_TIMERA_IIDX_CC1_DN: // 通道1中断
if (gUpdateCnt1 == 0) {
gUpdateCnt1 = PWM_PERIOD_UPDATE_CNT;
if (gIndex1 > ((sizeof(gPeriodVal) / sizeof(uint32_t)) - 1)) {
gIndex1 = 0;
}
// DL_TimerA_setLoadValue(PWM_0_INST, gPeriodVal[gIndex1]);
DL_TimerA_setCaptureCompareValue(PWM_0_INST, gDutyVal[gIndex1],
DL_TIMERA_CAPTURE_COMPARE_1_INDEX);
gIndex1++;
} else {
gUpdateCnt1--;
}
default:
break;
}
}
运行结果:
程序运行后,两路输出pwm,连接LED后,两个LED等亮灭变化。
六、 PWM QEI编码器计数
在支持 QEI 的 TIMGx 实例中, 正交编码器接口 (QEI) 模式提供了一个连接正交编码器输出的接口。它可以对正交编码的数据进行解码, 以提供线性或旋转运动的相对定位和移动的相关信息。
QEI 由两个灰度编码的正交输入信号 PHA 和 PHB 以及一个索引输入信号 IDX 组成。所有输入信号都进入单个计数器的 CCP 输入, 使 PHA 和 PHB 映射到 CCP0 和 CCP1, 而 IDX 作为单独的输入引入。一个错误检测机制可以报告错误转换, 以避免不正确的信号解码。
QEI 用于解码来自光学位置编码器的信号。光学位置编码器通常输出 2 个信号 (PHA/PHB), 这些信号通常用于测量具有旋转轴的物理部件( 例如三相电机) 的旋转或线性移动。该接口提供的测量是递增的; 也就是说, 当发生移动时, 该接口能捕获到与上一个位置的相对变化。
在 QEI 模式下运行时, 计数器会累积增量更新, 从而根据初始位置推导出当前位置和累积的变化。初始位置可由IDX 输入信号( 3 信号 QEI 模式) 或其他软件方法( 软件直接设置初始位置) 确定。通过定义捕获条件, 捕获和比较寄存器可用于存储位置值。
当发生方向改变 (DC) 时, RIS 寄存器中会产生一个 DC 中断。
通过时序图基本可以看到QEI模式时的工作方式
开始计数后,根据PHA和PHB两相信号进行计数值的增减,计数值保存在CTR中;当方向发生变化时,可以产生DC中断事件;向上计数时,当计数值等于LOAD值时,CTR变为0;向下计数时,当计数值等于0时,CTR值变为LOAD.
QEI简要配置
#include "ti_msp_dl_config.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
void UART_printf(const char *format, ...) {
uint32_t length;
va_list args;
uint32_t i;
char TxBuffer[128] = {0};
va_start(args, format);
length = vsnprintf((char *)TxBuffer, sizeof(TxBuffer), (char *)format, args);
va_end(args);
for (i = 0; i < length; i++) {
DL_UART_Main_transmitDataBlocking(UART_0_INST, TxBuffer[i]);
}
}
int main(void) {
SYSCFG_DL_init();
NVIC_EnableIRQ(QEI_0_INST_INT_IRQN);
DL_TimerG_startCounter(QEI_0_INST);
UART_printf("helloWorld\r\n");
while (1) {
delay_cycles(3200000);
UART_printf("CTR=%d\r\n", DL_Timer_getTimerCount(QEI_0_INST));
}
}
/**
* @brief QEI_0_INST中断服务函数
*
* 本函数用于处理QEI(正交编码器接口)的中断请求。它通过检查挂起的中断类型来决定如何处理中断。
* 特别地,本函数关注于方向变化中断,当检测到方向变化时,会根据新的方向控制LED的亮灭。
*/
void QEI_0_INST_IRQHandler(void) {
// 根据挂起的中断类型执行相应的操作
switch (DL_TimerG_getPendingInterrupt(QEI_0_INST)) {
case DL_TIMER_IIDX_DIR_CHANGE:
// 当发生方向变化中断时,检查QEI的方向
if (DL_TimerG_getQEIDirection(QEI_0_INST) == DL_TIMER_QEI_DIR_DOWN)
// 如果方向为逆时针,熄灭用户LED1
DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
else
// 如果方向为顺时针,点亮用户LED1
DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
default:
// 默认情况下,不执行任何操作
break;
}
}
运行结果: