好的,基于手册中获取的 PWM 输入捕获原理、ADC 配置和串口配置信息,我来编写完整的演示程序。
STC32G12K128 演示程序 — PWM输入捕获2路信号 + ADC周期转换 + 串口输出
程序功能说明
- PWM输入捕获:使用 PWMA 的通道1和通道2测量第1路信号(P1.0/PWM1P)的周期和占空比;使用 PWMB 的通道1和通道2测量第2路信号(P2.0/PWM5)的周期和占空比
- ADC周期转换:定时器0定时触发,周期性地对 ADC 通道0(P1.0)进行转换
- 串口输出:通过串口1(P3.0 RxD, P3.1 TxD)将测量结果发送到上位机
硬件连接
| 功能 |
引脚 |
说明 |
| 第1路信号输入 |
P1.0 (PWM1P) |
PWMA 通道1/2 捕获 |
| 第2路信号输入 |
P2.0 (PWM5) |
PWMB 通道1/2 捕获 |
| ADC输入 |
P1.1 (ADC1) |
模拟电压采样 |
| 串口1 TxD |
P3.1 |
数据发送 |
| 串口1 RxD |
P3.0 |
数据接收(本程序未用) |
完整代码
/*
* ============================================================
* 文件名: main.c
* 芯片 : STC32G12K128
* 时钟 : 内部IRC 40MHz
* 功能 :
* 1. PWMA 输入捕获第1路信号(P1.0/PWM1P),测量周期和占空比
* 2. PWMB 输入捕获第2路信号(P2.0/PWM5),测量周期和占空比
* 3. ADC 周期转换通道1(P1.1),定时器0定时100ms启动
* 4. 串口1(115200bps, 8N1)输出测量结果
* 编译 : Keil C251
* 注意 : 使用 STC32G12K128 头文件,需在 Target 中设置 Code Rom Size 为 Large
* ============================================================
*/
#include "stc32g.h"
/* 宏定义 */
#define FOSC 40000000UL // 系统时钟 40MHz
#define PWM_FREQ 1000000UL // PWM计数器时钟 1MHz (40MHz/40)
#define T0_1MS (FOSC / 1000) // 定时器0 1ms 计数值(1T模式)
/* 全局变量 */
volatile unsigned char g_t0_100ms_flag = 0; // 100ms 定时标志
volatile unsigned int g_adc_result = 0; // ADC 转换结果
/* PWMA 捕获结果(第1路信号,P1.0/PWM1P) */
volatile unsigned int g_pwma_period = 0; // 周期值
volatile unsigned int g_pwma_duty = 0; // 占空比值
volatile unsigned char g_pwma_cap_ok = 0; // 捕获完成标志
/* PWMB 捕获结果(第2路信号,P2.0/PWM5) */
volatile unsigned int g_pwmb_period = 0; // 周期值
volatile unsigned int g_pwmb_duty = 0; // 占空比值
volatile unsigned char g_pwmb_cap_ok = 0; // 捕获完成标志
/* 串口发送缓冲区 */
unsigned char g_uart_tx_buf[64];
unsigned char g_uart_tx_len = 0;
/* 函数声明 */
void delay_ms(unsigned int ms);
void UART1_Init(void);
void UART1_SendString(unsigned char *str);
void Timer0_Init(void);
void ADC_Init(void);
unsigned int ADC_Read(unsigned char channel);
void PWMA_Cap_Init(void);
void PWMB_Cap_Init(void);
/* ======================== 延时函数 ======================== */
void delay_ms(unsigned int ms)
{
unsigned int i, j;
for (i = 0; i < ms; i++)
{
for (j = 0; j < 4000; j++); // 约 1ms @ 40MHz
}
}
/* ======================== 串口1初始化 ======================== */
void UART1_Init(void)
{
/* 使用定时器2作为波特率发生器,115200bps @ 40MHz */
/* 波特率 = SYSclk / 4 / (65536 - RL) => RL = 65536 - SYSclk/4/115200 */
/* 40MHz / 4 / 115200 ≈ 86.8, 取整 87 */
/* 实际波特率 = 40000000 / 4 / 87 = 114942 ≈ 115200 */
SCON = 0x50; // 模式1: 8位UART, 允许接收
AUXR |= 0x01; // S1BRT = 1, 选择定时器2作为波特率发生器
AUXR |= 0x04; // T2x12 = 1, 定时器2 1T模式
T2L = (65536 - (unsigned int)(FOSC / 4 / 115200)) & 0xFF;
T2H = (65536 - (unsigned int)(FOSC / 4 / 115200)) >> 8;
AUXR |= 0x10; // T2R = 1, 启动定时器2
TI = 0; // 清除发送中断标志
}
/* ======================== 串口1发送字符串 ======================== */
void UART1_SendString(unsigned char *str)
{
while (*str)
{
SBUF = *str++;
while (TI == 0); // 等待发送完成
TI = 0;
}
}
/* ======================== 定时器0初始化 ======================== */
/* 定时100ms,用于周期启动ADC */
void Timer0_Init(void)
{
/* 定时器0 16位自动重装载模式,1T模式 */
/* 计数值 = FOSC / 1000 * 100 = FOSC / 10 */
/* 40MHz / 10 = 4000000, 需用16位定时器级联或降低分频 */
/* 改用 12T 模式: 计数值 = FOSC / 12 / 10 = 333333, 超出16位范围 */
/* 采用 1T 模式 + 软件计数: 定时1ms, 软件计100次 */
AUXR |= 0x80; // T0x12 = 1, 定时器0 1T模式
TMOD &= 0xF0; // 设置定时器0为模式0 (16位自动重装载)
TMOD |= 0x00; // 模式0
/* 1ms 计数值 = FOSC / 1000 = 40000 */
TL0 = (65536 - T0_1MS) & 0xFF;
TH0 = (65536 - T0_1MS) >> 8;
ET0 = 1; // 使能定时器0中断
TR0 = 1; // 启动定时器0
}
/* ======================== ADC初始化 ======================== */
void ADC_Init(void)
{
/* 打开 ADC 电源 */
ADC_CONTR = 0x80; // ADC_POWER = 1, 其余位为0
delay_ms(1); // 等待 ADC 电源稳定(约1ms)
/* 配置 ADC 时序 */
/* 设置 ADC 时钟 = SYSclk / 2 / (SPEED+1) */
/* SPEED = 14 => ADC时钟 = 40MHz / 2 / 15 ≈ 1.33MHz */
ADCCFG = 0x20; // RESFMT=1 (右对齐), SPEED=0000 (SYSclk/2/1)
ADCCFG = 0x2E; // RESFMT=1, SPEED=1110 (SYSclk/2/15)
/* 设置采样时间 */
P_SW2 |= 0x80; // 使能访问 XFR
ADCTIM = 0x2A; // CSSETUP=0, CSHOLD=01(2), SMPDUTY=01010(11)
P_SW2 &= 0x7F; // 关闭 XFR 访问
}
/* ======================== ADC读取 ======================== */
unsigned int ADC_Read(unsigned char channel)
{
unsigned int result;
/* 设置通道并启动转换 */
ADC_CONTR = (ADC_CONTR & 0xF0) | channel; // 设置通道
ADC_CONTR |= 0x40; // ADC_START = 1, 启动转换
/* 等待转换完成 */
while (!(ADC_CONTR & 0x20)); // 等待 ADC_FLAG = 1
ADC_CONTR &= ~0x20; // 清除 ADC_FLAG
/* 读取结果(右对齐) */
result = ((unsigned int)ADC_RES << 8) | ADC_RESL;
return result;
}
/* ======================== PWMA 输入捕获初始化 ======================== */
/* 测量第1路信号(P1.0/PWM1P)的周期和占空比 */
/* 使用通道1捕获上升沿(周期),通道2捕获下降沿(占空比) */
void PWMA_Cap_Init(void)
{
P_SW2 |= 0x80; // 使能访问 XFR
/* 配置 PWM1P 引脚为输入模式 */
P1M0 &= ~0x01; // P1.0 设置为高阻输入
P1M1 |= 0x01;
/* 关闭 PWMA */
PWMA_CR1 = 0x00;
/* 设置 PWMA 时钟分频:40MHz / 40 = 1MHz */
PWMA_PSCRH = 0x00;
PWMA_PSCRL = 39; // 分频值 = 39, 实际分频 = 39+1 = 40
/* 设置自动重装载值(最大计数值),16位最大值 */
PWMA_ARRH = 0xFF;
PWMA_ARRL = 0xFF;
/* 通道1配置:捕获模式,映射到 TI1,上升沿捕获 */
PWMA_CCMR1 = 0x01; // CC1S=01, 通道1作为输入, 映射到TI1
/* 通道2配置:捕获模式,映射到 TI1FP2,下降沿捕获 */
PWMA_CCMR2 = 0x02; // CC2S=10, 通道2作为输入, 映射到TI1FP2
/* 配置捕获极性 */
PWMA_CCER1 = 0x00; // 先清零
PWMA_CCER1 |= 0x01; // CC1E=1, CC1P=0, 通道1上升沿捕获
PWMA_CCER1 |= 0x10; // CC2E=1, CC2P=1, 通道2下降沿捕获
/* 配置触发源为 TI1FP1,复位触发模式 */
PWMA_SMCR = 0x00; // 先清零
PWMA_SMCR |= 0x54; // SMS=100 (复位触发模式), TS=101 (选择TI1FP1)
/* 使能捕获/比较中断 */
PWMA_IER = 0x00;
PWMA_IER |= 0x02; // CC1IE = 1, 使能通道1捕获中断
PWMA_IER |= 0x04; // CC2IE = 1, 使能通道2捕获中断
/* 使能 PWMA 计数器 */
PWMA_CR1 |= 0x01; // CEN = 1, 使能计数器
P_SW2 &= 0x7F; // 关闭 XFR 访问
}
/* ======================== PWMB 输入捕获初始化 ======================== */
/* 测量第2路信号(P2.0/PWM5)的周期和占空比 */
/* 使用通道1(PWM5)捕获上升沿(周期),通道2(PWM6)捕获下降沿(占空比) */
void PWMB_Cap_Init(void)
{
P_SW2 |= 0x80; // 使能访问 XFR
/* 配置 PWM5 引脚(P2.0)为输入模式 */
P2M0 &= ~0x01; // P2.0 设置为高阻输入
P2M1 |= 0x01;
/* 配置 PWM6 引脚(P2.1)为输入模式(用于捕获下降沿) */
P2M0 &= ~0x02; // P2.1 设置为高阻输入
P2M1 |= 0x02;
/* 关闭 PWMB */
PWMB_CR1 = 0x00;
/* 设置 PWMB 时钟分频:40MHz / 40 = 1MHz */
PWMB_PSCRH = 0x00;
PWMB_PSCRL = 39; // 分频值 = 39, 实际分频 = 39+1 = 40
/* 设置自动重装载值 */
PWMB_ARRH = 0xFF;
PWMB_ARRL = 0xFF;
/* 通道1(PWM5)配置:捕获模式,映射到 TI1,上升沿捕获 */
PWMB_CCMR1 = 0x01; // CC1S=01, 通道1作为输入, 映射到TI1
/* 通道2(PWM6)配置:捕获模式,映射到 TI1FP2,下降沿捕获 */
PWMB_CCMR2 = 0x02; // CC2S=10, 通道2作为输入, 映射到TI1FP2
/* 配置捕获极性 */
PWMB_CCER1 = 0x00; // 先清零
PWMB_CCER1 |= 0x01; // CC1E=1, CC1P=0, 通道1上升沿捕获
PWMB_CCER1 |= 0x10; // CC2E=1, CC2P=1, 通道2下降沿捕获
/* 配置触发源为 TI1FP1,复位触发模式 */
PWMB_SMCR = 0x00; // 先清零
PWMB_SMCR |= 0x54; // SMS=100 (复位触发模式), TS=101 (选择TI1FP1)
/* 使能捕获/比较中断 */
PWMB_IER = 0x00;
PWMB_IER |= 0x02; // CC1IE = 1, 使能通道1捕获中断
PWMB_IER |= 0x04; // CC2IE = 1, 使能通道2捕获中断
/* 使能 PWMB 计数器 */
PWMB_CR1 |= 0x01; // CEN = 1, 使能计数器
P_SW2 &= 0x7F; // 关闭 XFR 访问
}
/* ======================== 主函数 ======================== */
void main(void)
{
unsigned int adc_val;
unsigned long freq1, duty1, freq2, duty2;
unsigned char str_buf[80];
/* 关闭所有中断 */
EA = 0;
/* 设置系统时钟为内部 40MHz */
/* STC32G 上电默认使用内部高速IRC,无需额外配置 */
/* 初始化外设 */
UART1_Init();
delay_ms(10);
UART1_SendString("STC32G12K128 PWM Capture + ADC Demo\r\n");
UART1_SendString("System Clock: 40MHz\r\n");
/* 初始化定时器0 */
Timer0_Init();
/* 初始化 ADC */
ADC_Init();
/* 初始化 PWMA 输入捕获(第1路信号,P1.0/PWM1P) */
PWMA_Cap_Init();
/* 初始化 PWMB 输入捕获(第2路信号,P2.0/PWM5) */
PWMB_Cap_Init();
/* 使能总中断 */
EA = 1;
/* 主循环 */
while (1)
{
/* 每100ms处理一次 */
if (g_t0_100ms_flag)
{
g_t0_100ms_flag = 0;
/* ---- ADC 转换 ---- */
adc_val = ADC_Read(1); // 读取 ADC 通道1 (P1.1)
g_adc_result = adc_val;
/* ---- 计算第1路信号频率和占空比 ---- */
if (g_pwma_cap_ok)
{
/* 频率 = PWM时钟 / 周期值 */
/* PWM时钟 = 1MHz */
if (g_pwma_period > 0)
{
freq1 = 1000000UL / (unsigned long)g_pwma_period;
}
else
{
freq1 = 0;
}
/* 占空比 = 占空比值 / 周期值 * 100% */
if (g_pwma_period > 0)
{
duty1 = (unsigned long)g_pwma_duty * 10000UL / (unsigned long)g_pwma_period;
}
else
{
duty1 = 0;
}
g_pwma_cap_ok = 0;
}
else
{
freq1 = 0;
duty1 = 0;
}
/* ---- 计算第2路信号频率和占空比 ---- */
if (g_pwmb_cap_ok)
{
if (g_pwmb_period > 0)
{
freq2 = 1000000UL / (unsigned long)g_pwmb_period;
}
else
{
freq2 = 0;
}
if (g_pwmb_period > 0)
{
duty2 = (unsigned long)g_pwmb_duty * 10000UL / (unsigned long)g_pwmb_period;
}
else
{
duty2 = 0;
}
g_pwmb_cap_ok = 0;
}
else
{
freq2 = 0;
duty2 = 0;
}
/* ---- 串口输出 ---- */
/* 格式: CH1: freq=1000Hz duty=50.00% CH2: freq=2000Hz duty=33.33% ADC=2048 */
sprintf(str_buf,
"CH1: freq=%luHz duty=%lu.%02lu%% CH2: freq=%luHz duty=%lu.%02lu%% ADC=%u\r\n",
freq1, duty1 / 100, duty1 % 100,
freq2, duty2 / 100, duty2 % 100,
adc_val);
UART1_SendString(str_buf);
}
}
}
/* ======================== 定时器0中断服务函数 ======================== */
/* 每1ms中断一次,计100次产生100ms标志 */
void Timer0_ISR(void) interrupt 1
{
static unsigned int t0_cnt = 0;
t0_cnt++;
if (t0_cnt >= 100) // 100ms
{
t0_cnt = 0;
g_t0_100ms_flag = 1;
}
}
/* ======================== PWMA 捕获中断服务函数 ======================== */
/* 第1路信号(P1.0/PWM1P)捕获完成 */
void PWMA_Cap_ISR(void) interrupt 26
{
unsigned char sr;
P_SW2 |= 0x80; // 使能访问 XFR
sr = PWMA_SR1; // 读取中断状态寄存器
if (sr & 0x02) // CC1IF (通道1捕获中断)
{
/* 通道1捕获到上升沿,CCR1 中保存的是周期值 */
g_pwma_period = ((unsigned int)PWMA_CCR1H << 8) | PWMA_CCR1L;
PWMA_SR1 &= ~0x02; // 清除 CC1IF 标志
}
if (sr & 0x04) // CC2IF (通道2捕获中断)
{
/* 通道2捕获到下降沿,CCR2 中保存的是占空比值 */
g_pwma_duty = ((unsigned int)PWMA_CCR2H << 8) | PWMA_CCR2L;
PWMA_SR1 &= ~0x04; // 清除 CC2IF 标志
g_pwma_cap_ok = 1; // 标记捕获完成
}
P_SW2 &= 0x7F; // 关闭 XFR 访问
}
/* ======================== PWMB 捕获中断服务函数 ======================== */
/* 第2路信号(P2.0/PWM5)捕获完成 */
void PWMB_Cap_ISR(void) interrupt 27
{
unsigned char sr;
P_SW2 |= 0x80; // 使能访问 XFR
sr = PWMB_SR1; // 读取中断状态寄存器
if (sr & 0x02) // CC1IF (通道1捕获中断)
{
/* 通道1捕获到上升沿,CCR1 中保存的是周期值 */
g_pwmb_period = ((unsigned int)PWMB_CCR1H << 8) | PWMB_CCR1L;
PWMB_SR1 &= ~0x02; // 清除 CC1IF 标志
}
if (sr & 0x04) // CC2IF (通道2捕获中断)
{
/* 通道2捕获到下降沿,CCR2 中保存的是占空比值 */
g_pwmb_duty = ((unsigned int)PWMB_CCR2H << 8) | PWMB_CCR2L;
PWMB_SR1 &= ~0x04; // 清除 CC2IF 标志
g_pwmb_cap_ok = 1; // 标记捕获完成
}
P_SW2 &= 0x7F; // 关闭 XFR 访问
}
程序说明
1. PWM 输入捕获原理
根据手册第26章"16位高级PWM定时器"的说明,STC32G 的高级 PWM 支持PWM输入捕获模式,可同时测量信号的周期和占空比:
- 将外部信号同时接入同一组 PWM 的两路通道
- 通道1配置为上升沿捕获(捕获周期值到 CCR1)
- 通道2配置为下降沿捕获(捕获占空比值到 CCR2)
- 触发模式控制器配置为复位触发模式(SMS=100),每次上升沿复位计数器
2. 信号测量范围
- PWM 计数器时钟 = 40MHz / 40 = 1MHz
- 最大可测周期 = 65535 / 1MHz ≈ 65.5ms(对应约 15.26Hz)
- 最小可测周期 = 2 / 1MHz = 2μs(对应 500kHz)
- 占空比分辨率 = 1 / 65535 ≈ 0.0015%
3. ADC 转换
- 定时器0每 100ms 触发一次 ADC 转换
- ADC 时钟 ≈ 1.33MHz,12位精度,右对齐格式
- 转换结果通过串口输出
4. 串口输出格式
CH1: freq=1000Hz duty=50.00% CH2: freq=2000Hz duty=33.33% ADC=2048
使用说明
- 将第1路被测信号连接到 P1.0(PWM1P)
- 将第2路被测信号连接到 P2.0(PWM5)
- 将模拟电压连接到 P1.1(ADC1)
- 串口1(P3.0/P3.1)连接 USB转串口模块,波特率 115200
- 用 Keil C251 编译下载到 STC32G12K128
注意:被测信号的频率范围建议在 15Hz ~ 500kHz 之间,且信号电平需与单片机 I/O 电平兼容(0~Vcc)。如果信号幅度不匹配,需加电平转换或分压电路。
🔍 搜索 "帮产生一个 STC32G12K128的演示程序 利用 PWM测量外部2路输入信号的周期和占空比 ADC 周期转换后"
📌 相关论坛讨论
- 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
- STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:
- STC8H1K08 如何测量2路外部信号的周期和占空比,还有 PWM输出
⏱️ 查找用时 37.0s | 总回答用时 78.1s