在简单地学习了定时器的捕捉功能后,进入实践环节。首先学习边沿对边沿方式,以做简单的固定占空比模式进行测试。按照以下方案编程:
1、定时器2负责产生固定占空比的PWM,由PB11输出。无中断处理。
2、定时器1负责捕捉,设置为捕捉模式,捕捉任何边沿,由PB10输入。有中断处理。
3、短接PB10和PB11,并接入逻辑分析仪,捕捉PWM信号,作为第三方验证。
为此,编写以下程序:
#include "CH58x_common.h"
#define PWM_BUF_SIZE 100
#define FREQ_SYS 60000000
#define PWM_CYCLE (60000) // 1Ms 周期 (1kHz)
// LED 接在 PB4
#define LED_PIN GPIO_Pin_4
/* 捕获缓冲区 */
__attribute__((aligned(4))) uint32_t CapBuf[PWM_BUF_SIZE];
// 占空比数组: 5%, 10%, 15%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90%
uint32_t PwmBuf[PWM_BUF_SIZE] = {3000, 6000, 9000, 12000, 18000, 24000, 30000, 36000, 42000, 48000, 54000, 1000};
// 捕获标志
volatile uint8_t capFlag = 0;
volatile uint8_t capLen = 0;
volatile int32_t capH = 0, capL = -1;
volatile uint8_t capReady = 0;
volatile uint8_t firstSkip = 1; // 丢弃启动伪数据
volatile uint8_t lastHigh = 0; // 处理超时拆段累加
volatile int8_t status = -1; // 捕捉状态
int main(void) {
SetSysClock(CLK_SOURCE_PLL_60MHz); // 主频 60MHz
/* 串口1打印:PA9-TX PA8-RX */
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
UART1_DefInit();
GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeOut_PP_5mA);
/* ========== 1. TMR2 输出 PWM,PB11 ========== */
PRINT("TMR2...\n");
GPIOB_ResetBits(GPIO_Pin_11);
GPIOB_ModeCfg(GPIO_Pin_11, GPIO_ModeOut_PP_5mA);
GPIOPinRemap(ENABLE, RB_PIN_TMR2); // PB11 复用为 TMR2
TMR2_PWMInit(High_Level, PWM_Times_1); // 默认高电平有效,数据不重复
TMR2_PWMCycleCfg(200); // 设置周期2.5μs
TMR2_PWMActDataWidth(60); // 设置高电平宽度(占空比)1μs
//TMR2_DMACfg(ENABLE, (uint16_t)(uint32_t)&PwmBuf[0], (uint16_t)(uint32_t)&PwmBuf[10], Mode_LOOP);
TMR2_Enable();
//=======================================TMR1
PRINT("TMR1...\n");
// 配置TMR1为捕捉功能,捕捉TMR2输出的PWM信号(PB11),需要短接 PB10 和 PB11
GPIOB_ModeCfg(GPIO_Pin_10, GPIO_ModeIN_PU); // 捕获输入(外部接线:PB11 -> PB10)
GPIOPinRemap(ENABLE, RB_PIN_TMR1); // PB10 复用为 TMR1
TMR1_CapInit(Edge_To_Edge); // 任意沿触发捕获
TMR1_CAPTimeoutCfg(0xFFFFFF); // 捕获超时时间(26位计数器)
// RB_TMR_IE_DATA_ACT : 数据激活(捕捉模式指每次捕捉到新数据,PWM 模式指数值触发导致有效电平结束)中断使能位
// RB_TMR_IE_CYC_END : 周期结束(捕捉模式指超时,PWM 模式和定时模式指周期结束)中断使能位: 1:使能中断;0:禁止中断。
TMR1_ITCfg(ENABLE, RB_TMR_IE_DATA_ACT); // 捕获到数据产生中断
PFIC_EnableIRQ(TMR1_IRQn);
PRINT("Loop...\n");
while(1) {
if(capReady) {
uint32_t h, l;
PFIC_DisableIRQ(TMR1_IRQn);
h = capH; l = capL;
// 主循环读取 capH/capL 后,需要手动将 capReady 置0,否则会重复处理同一组数据。
capReady = 0;
status = 0;
PFIC_EnableIRQ(TMR1_IRQn);
PRINT("high = %d, low = %d , period = %d , duty = %d%%\n", h, l, (h + l), h * 100 / (h + l));
//PRINT("high = %d us, low = %d us, period = %d us, duty = %d%%\n", h / 60, l / 60, (h + l) / 60, h * 100 / (h + l));
}
}
}
/* TMR1 捕获中断服务函数 */
// 若使用蓝牙协议栈,建议将 TMR1_IRQn 优先级设为高于蓝牙中断,防止响应延迟导致FIFO溢出(RB_TMR_IF_FIFO_OV)。
__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void TMR1_IRQHandler(void) {
// GOPIO口翻转
GPIOB_InverseBits(GPIO_Pin_4);
// 判断中断是否由"数据有效"事件触发(即捕捉到一个新的边沿)
if(TMR1_GetITFlag(RB_TMR_IF_DATA_ACT)) {
// 直接从 R32_TMR1_FIFO 读取32位捕捉数据(无需调用 TMR1_CAPGetData(),二者等效)
// 代码没有循环读取 R8_TMR1_FIFO_COUNT,这意味着默认FIFO中每次只有一个数据(通常由捕捉触发频率决定)
uint32_t d = R32_TMR1_FIFO;
TMR1_ClearITFlag(RB_TMR_IF_DATA_ACT);
// 定时器使能后,第一次捕捉到的计数值不代表真实的边沿间隔,它只是从0计数到第一个边沿的时间(相当于从启动到第一个边沿的偏移量)。
// 如果保留这个值,会导致高/低电平测量出现一个莫名其妙的初始值。
// 全局变量 firstSkip 初始为 1。
// 第一次进入中断时,直接丢弃当前数据,将 firstSkip 置 0 并返回。
// 第二次及以后的捕捉数据才是有效的边沿间隔值。
if(firstSkip) {
firstSkip = 0;
return;
} // 丢弃第一个伪数据
// 判断 bit25 的值(1为高电平,0为低电平)
if(d & 0x02000000) // bit25=1:高电平宽度
{
// d & 0x01FFFFFF:提取低25位,即为实际的计数值(单位 = 系统时钟周期)
// 变量 lastHigh 用于记录上一次测量完成的电平类型(1=高,0=低)
if(lastHigh) capH += d & 0x01FFFFFF; // 超时拆成多段,累加
else capH = d & 0x01FFFFFF;
// 高电平状态值
lastHigh = 1;
status = 0; // 允许采集下降沿
}
else // 低电平宽度
{
if (status == -1) return; // 保证先从采集到上升沿开始
if(!lastHigh) capL += d & 0x01FFFFFF;
else capL = d & 0x01FFFFFF;
// 低电平状态值
lastHigh = 0;
capReady = 1; // 凑齐 高+低 = 一个完整周期
}
}
// RB_TMR_IF_CYC_END : 周期结束(捕捉模式指超时,PWM 模式和定时模式指周期结束,计数模式指计数溢出)标志位
// ? 超时中断:重置状态机,准备重新同步
if(TMR1_GetITFlag(RB_TMR_IF_CYC_END)) {
TMR1_ClearITFlag(RB_TMR_IF_CYC_END);
// 重置状态机
status = -1; // 强制从下一个上升沿重新开始
firstSkip = 1; // 重置伪数据丢弃标志(可选)
capH = 0; // 清空累计值
capL = 0; // 清空累计值
capReady = 0; // 清空就绪标志
}
// GOPIO口翻转
//GPIOB_InverseBits(GPIO_Pin_4);
}CH582M可以工作在60MHz下,其系统时钟为16.67ns.为了验证可以捕捉的PWM信号,做了一下几种组合(前一个数据为PWM的周期脉冲数,后一个数据为高电平持续的脉冲数,这里的脉冲指的是系统时钟脉冲)。
一、周期为150,高电平为60
TMR2_PWMCycleCfg(150); // 设置周期2.5μs
TMR2_PWMActDataWidth(60); // 设置高电平宽度1μs
逻辑分析仪获得的波形:

串口输出信息:

得出的占空比虽然是对的,但测量出的计数值是错误的,被测成为高电平持续120个脉冲,周期为300个脉冲。
二、周期为200,高电平为60
TMR2_PWMCycleCfg(200); // 设置周期3.3μs
TMR2_PWMActDataWidth(60); // 设置高电平宽度1μs


这个结果是正常的。
三、周期为180,高电平为60
TMR2_PWMCycleCfg(180); // 设置周期3μs
TMR2_PWMActDataWidth(60); // 设置高电平宽度1μs


测量结果不稳定,大部分是错的。
实际上,周期低于200的,高电平低于60的模式,都是不正常,甚至于根本就没法完成测量,但实际输出都是正常的。测量中,通过测量PB4,获取到的TMR1的中断处理周期在800ns左右。

结论:
根据上面的实验,最终得出以下结论:如果PWM的周期低于200个脉冲(约 3.3 μs),高电平持续时间低于60(1 μs)的,CH582M是无法准确测量的。
另外,以DMA方式也做了一些实验,包括DMA方式产生不同占空比混杂的PWM输出,及DMA方式捕捉,均没有成功,哪怕周期和占空比是否符合可测量的范围。
还有是PWM周期特别长,以至于即使产生了测量溢出的PWM信号,我没有做深入测试,主要是这个周期高平持续时间最短的需要能达到2.8s左右,很少有这么长的PWM波。
我要赚赏金
