下面是一个基于 PSoC™ 4000S (CY8C4045AZI-S413) 实现单次触发输出(单脉冲模式)的详细方案,解决你在长触发周期下 PWM 组件自动复位导致输出异常的问题:
核心问题分析:
你之前尝试的"使用输入触发重新加载 PWM" 对于短周期(触发频率高)有效,但当触发周期 > PWM 周期时,PWM 的计数器会自动回零 (TC),导致输出引脚在预定的开启时间结束前就被错误地拉低或再次触发。你需要的是每个输入触发信号精确产生一个单次脉冲输出(One-Shot Pulse)。
推荐解决方案:
利用 CSD (CapSense Sigma-Delta) 模块中的可编程计数器,配合外部中断和系统中断。PSoC 4000S 的 CSD 模块可以配置为通用计数器。
? 硬件组件配置 (在 PSoC Creator / ModusToolbox™)
CSD 组件:
- 添加组件:添加
CSD 组件 (通常命名为 CSD 或 Counter)。
- 配置为计数器模式:
Counter Mode:选择 Counter (通常是递增计数)。
Period:设置为你预期的最大输出脉冲宽度(以时钟周期为单位)。例如,如果系统时钟 (IMO) 是 24 MHz,你需要一个最大 10ms 的脉冲,则 Period = (24,000,000 Hz) * (0.010 s) = 240,000。由于计数器宽度限制(如 16 或 32 位),可能需要使用预分频。
Enable Interrupt:勾选启用终端计数 (TC) 中断。
Capture Mode:选择 Software Capture 或根据测量需求配置。
Trigger Mode:选择 Software Trigger。这将由外部中断服务程序启动计数器。
Run Mode:选择 Continuous。计数器在达到设定周期后会停止并触发中断。
- 计数器时钟源 (
Clk):选择高精度时钟(如 IMO 或其预分频版本)。这直接影响输出脉冲的时间精度 ⚡️。
外部中断引脚 (EXT_INT) 组件:
- 添加组件:添加
Pin 组件用于输入触发信号。在 Pin 的属性配置中启用 中断 支持。
- 配置中断:
中断类型:选择检测边缘中断。
边沿:选择 上升沿 或 下降沿 (根据触发信号有效边沿而定)。
- 命名引脚(如
trig_in)。
输出引脚组件:
- 添加组件:添加另一个
Pin 组件用于输出脉冲信号。
- 配置输出:选择
数字输出,初始化状态设为 0 (低电平)。命名引脚(如 pulse_out)。
? 代码实现思路 (C 语言)
main.c文件的核心逻辑如下:
#include "project.h" // 包含生成的 API 头文件
// 计算脉冲宽度的函数(根据测量的触发频率)
uint16_t CalculatePulseWidth(uint32_t triggerPeriod_Clks) {
// 应用你的特定公式计算输出脉冲宽度(以时钟周期为单位)
// 示例 1: 固定脉宽 (e.g., 1000 clock cycles)
return 1000;
// 示例 2: 线性或非线性计算(基于 triggerPeriod_Clks)
// 注意范围检查!不能超过 CSD 计数器的最大可设置周期。
}
// 全局变量
volatile uint32_t lastTriggerCounter = 0; // 上次触发计数器值
volatile uint32_t triggerPeriod = 0; // 计算出的触发周期(时钟周期)
volatile uint8_t newTriggerDetected = 0; // 新触发标志
// 输入触发引脚的中断服务程序 (ISR)
CY_ISR(trig_in_ISR) {
uint32_t currentCount = CSD_ReadCounter(); // 读取当前计数器值
// 计算相邻触发之间的时间差(即输入信号周期)
if (lastTriggerCounter != 0) {
triggerPeriod = currentCount - lastTriggerCounter;
newTriggerDetected = 1; // 设置新触发标志
}
lastTriggerCounter = currentCount;
trig_in_ClearInterrupt(); // 清除中断标志
}
// CSD 计数器终端计数(TC)中断服务程序
CY_ISR(csd_ISR) {
pulse_out_Write(0); // 关闭输出脉冲
CSD_ClearInterrupt(CSD_INTR_TC); // 清除计数器中断标志
}
int main(void) {
CyGlobalIntEnable; // 启用全局中断
// 启动组件
CSD_Start();
trig_in_ISR_StartEx(trig_in_ISR); // 启动输入触发 ISR
CSD_ISR_StartEx(csd_ISR); // 启动计数器中断
for (;;) {
if (newTriggerDetected) { // 检查是否有新的有效触发
CyGlobalIntDisable; // 临时禁用中断(临界区开始)
newTriggerDetected = 0; // 清除标志
// 步骤 1: 根据最新触发周期计算本次输出脉宽(时钟周期)
uint16_t pulseWidthClks = CalculatePulseWidth(triggerPeriod);
// 步骤 2: 安全设置新的 CSD 计数器周期(确保不会溢出)
if (pulseWidthClks > CSD_MAX_PERIOD) pulseWidthClks = CSD_MAX_PERIOD;
CSD_WritePeriod(pulseWidthClks); // 更新输出脉宽
// 步骤 3: 停止并重置计数器
CSD_Stop();
CSD_WriteCounter(0); // 计数器清零
// 步骤 4: 在禁用状态下设置比较值(如果需要)
// CSD_WriteCompare(...); // 如果使用比较输出功能则设置
// 步骤 5: 确保输出初始化为关闭状态(防止瞬时高电平)
pulse_out_Write(0);
// 步骤 6: 启动输出脉冲(上升沿)
pulse_out_Write(1); // 开启输出脉冲
// 步骤 7: 启动计数器开始精确计时脉宽
CSD_Start();
CyGlobalIntEnable; // 恢复中断(临界区结束)
}
// 这里可以放置其他低优先级任务或进入低功耗模式
// CyDelay(1); // 示例延时
}
}
? 关键机制说明
输入频率测量:在外部触发信号的每次有效边沿(在 trig_in_ISR 中):
- 读取 CSD 计数器的当前值。
- 计算与前一次触发之间的计数器差值,得到输入触发信号的实际周期(以
Clk 的时钟周期为单位)。
- 设置一个标志
newTriggerDetected 并记录计算的周期 (triggerPeriod)。
- CSD 计数器始终在自由运行,作为精确的时间基准。
脉冲输出控制:
- Main Loop 监听标志:主循环不断检查
newTriggerDetected 是否置位。
- 计算脉冲宽度 (
CalculatePulseWidth): 根据你自定义的逻辑,使用测量的 triggerPeriod(输入频率)计算所需的输出脉冲宽度。
- 安全配置计数器:
- 禁止中断(进入临界区)。
- 停止运行的 CSD 计数器。
- 将计算出的新脉宽(转换为时钟周期数)写入计数器的周期寄存器 (
CSD_WritePeriod)。
- 将计数器值清零 (
CSD_WriteCounter(0))。
- 启动脉冲:
- 确保输出引脚处于关闭状态(避免瞬时脉冲)。
- 将输出引脚手动置为高电平 (
pulse_out_Write(1))。
- 启动 CSD 计数器 (
CSD_Start())。
- 恢复中断。
- 结束脉冲:
- 当 CSD 计数器达到设定的周期 (
TC) 时,csd_ISR 被调用。
- 中断服务程序将输出引脚手动置为低电平 (
pulse_out_Write(0))。
- 计数器会自动停止计数(因为设置为连续运行但在达到 TC 后停止)。下次触发时会被主循环重新配置并启动。
? 优势分析
- 单次脉冲:每次输入触发仅产生一个精确的输出脉冲(宽度可编程),无论触发信号的频率是多少。
- 高精度:脉宽由硬件计数器(CSD)的时钟源决定(例如 24 MHz IMO,精度 < 42ns)。
- 低延迟:从检测到输入触发边沿到启动输出脉冲(设置引脚高电平)之间的延迟是极小且固定的(主要发生在
ISR 后的主循环临界区代码内)。
- 灵活性:计算脉宽的
CalculatePulseWidth 函数可以轻松实现复杂的映射关系。
? 重要考虑因素
- 延迟:从检测到触发到实际设置
pulse_out 高电平之间有延迟(主要是主循环检查标志的等待时间 + 配置计数器的软件开销)。这在大多数情况下是可以接受的。
- 最大脉宽:由
CSD_WritePeriod() 允许设置的最大值限制(取决于计数器位数和时钟分频)。
- 最小脉宽/触发间隔:如果输入触发频率极高,需确保
CalculatePulseWidth + 计数器配置的时间小于最小的输入触发周期。否则会丢失触发或输出重叠。
- 首次触发:处理第一个触发信号时 (
lastTriggerCounter == 0),尚未能计算有效周期。可初始化为默认值或忽略。
- 中断优先级:确保
csd_ISR 的优先级高于 trig_in_ISR,以防止脉宽结束中断被延迟。
- 临界区:在主循环里更新计数器配置和启动脉冲时禁用中断是必要的,以防止执行过程被打乱。
? 调试技巧
- 系统时钟 (
Clk):初始设置为 IMO=24MHz 或 ILO。
- 计数器范围:通过预分频选择合适频率。
- 中断处理速度:在 ISR 中避免复杂操作。
- 主循环频率:
for(;;) 循环中不要添加过长延时。
- 输出监控:用示波器观察输入触发 (
trig_in) 和输出脉冲 (pulse_out) 信号。
- 全局变量保护:在多个线程访问时使用原子操作。
此方案充分利用了 PSoC 4000S 的硬件资源(CSD 作为高精度定时器,中断处理快速响应),有效解决了 PWM 组件在长触发周期下的问题,实现了可靠的单次触发脉冲输出功能。? 每次当你收到输入触发信号时,系统都会精确计算并输出一个“量身定制”的单次脉冲!
下面是一个基于 PSoC™ 4000S (CY8C4045AZI-S413) 实现单次触发输出(单脉冲模式)的详细方案,解决你在长触发周期下 PWM 组件自动复位导致输出异常的问题:
核心问题分析:
你之前尝试的"使用输入触发重新加载 PWM" 对于短周期(触发频率高)有效,但当触发周期 > PWM 周期时,PWM 的计数器会自动回零 (TC),导致输出引脚在预定的开启时间结束前就被错误地拉低或再次触发。你需要的是每个输入触发信号精确产生一个单次脉冲输出(One-Shot Pulse)。
推荐解决方案:
利用 CSD (CapSense Sigma-Delta) 模块中的可编程计数器,配合外部中断和系统中断。PSoC 4000S 的 CSD 模块可以配置为通用计数器。
? 硬件组件配置 (在 PSoC Creator / ModusToolbox™)
CSD 组件:
- 添加组件:添加
CSD 组件 (通常命名为 CSD 或 Counter)。
- 配置为计数器模式:
Counter Mode:选择 Counter (通常是递增计数)。
Period:设置为你预期的最大输出脉冲宽度(以时钟周期为单位)。例如,如果系统时钟 (IMO) 是 24 MHz,你需要一个最大 10ms 的脉冲,则 Period = (24,000,000 Hz) * (0.010 s) = 240,000。由于计数器宽度限制(如 16 或 32 位),可能需要使用预分频。
Enable Interrupt:勾选启用终端计数 (TC) 中断。
Capture Mode:选择 Software Capture 或根据测量需求配置。
Trigger Mode:选择 Software Trigger。这将由外部中断服务程序启动计数器。
Run Mode:选择 Continuous。计数器在达到设定周期后会停止并触发中断。
- 计数器时钟源 (
Clk):选择高精度时钟(如 IMO 或其预分频版本)。这直接影响输出脉冲的时间精度 ⚡️。
外部中断引脚 (EXT_INT) 组件:
- 添加组件:添加
Pin 组件用于输入触发信号。在 Pin 的属性配置中启用 中断 支持。
- 配置中断:
中断类型:选择检测边缘中断。
边沿:选择 上升沿 或 下降沿 (根据触发信号有效边沿而定)。
- 命名引脚(如
trig_in)。
输出引脚组件:
- 添加组件:添加另一个
Pin 组件用于输出脉冲信号。
- 配置输出:选择
数字输出,初始化状态设为 0 (低电平)。命名引脚(如 pulse_out)。
? 代码实现思路 (C 语言)
main.c文件的核心逻辑如下:
#include "project.h" // 包含生成的 API 头文件
// 计算脉冲宽度的函数(根据测量的触发频率)
uint16_t CalculatePulseWidth(uint32_t triggerPeriod_Clks) {
// 应用你的特定公式计算输出脉冲宽度(以时钟周期为单位)
// 示例 1: 固定脉宽 (e.g., 1000 clock cycles)
return 1000;
// 示例 2: 线性或非线性计算(基于 triggerPeriod_Clks)
// 注意范围检查!不能超过 CSD 计数器的最大可设置周期。
}
// 全局变量
volatile uint32_t lastTriggerCounter = 0; // 上次触发计数器值
volatile uint32_t triggerPeriod = 0; // 计算出的触发周期(时钟周期)
volatile uint8_t newTriggerDetected = 0; // 新触发标志
// 输入触发引脚的中断服务程序 (ISR)
CY_ISR(trig_in_ISR) {
uint32_t currentCount = CSD_ReadCounter(); // 读取当前计数器值
// 计算相邻触发之间的时间差(即输入信号周期)
if (lastTriggerCounter != 0) {
triggerPeriod = currentCount - lastTriggerCounter;
newTriggerDetected = 1; // 设置新触发标志
}
lastTriggerCounter = currentCount;
trig_in_ClearInterrupt(); // 清除中断标志
}
// CSD 计数器终端计数(TC)中断服务程序
CY_ISR(csd_ISR) {
pulse_out_Write(0); // 关闭输出脉冲
CSD_ClearInterrupt(CSD_INTR_TC); // 清除计数器中断标志
}
int main(void) {
CyGlobalIntEnable; // 启用全局中断
// 启动组件
CSD_Start();
trig_in_ISR_StartEx(trig_in_ISR); // 启动输入触发 ISR
CSD_ISR_StartEx(csd_ISR); // 启动计数器中断
for (;;) {
if (newTriggerDetected) { // 检查是否有新的有效触发
CyGlobalIntDisable; // 临时禁用中断(临界区开始)
newTriggerDetected = 0; // 清除标志
// 步骤 1: 根据最新触发周期计算本次输出脉宽(时钟周期)
uint16_t pulseWidthClks = CalculatePulseWidth(triggerPeriod);
// 步骤 2: 安全设置新的 CSD 计数器周期(确保不会溢出)
if (pulseWidthClks > CSD_MAX_PERIOD) pulseWidthClks = CSD_MAX_PERIOD;
CSD_WritePeriod(pulseWidthClks); // 更新输出脉宽
// 步骤 3: 停止并重置计数器
CSD_Stop();
CSD_WriteCounter(0); // 计数器清零
// 步骤 4: 在禁用状态下设置比较值(如果需要)
// CSD_WriteCompare(...); // 如果使用比较输出功能则设置
// 步骤 5: 确保输出初始化为关闭状态(防止瞬时高电平)
pulse_out_Write(0);
// 步骤 6: 启动输出脉冲(上升沿)
pulse_out_Write(1); // 开启输出脉冲
// 步骤 7: 启动计数器开始精确计时脉宽
CSD_Start();
CyGlobalIntEnable; // 恢复中断(临界区结束)
}
// 这里可以放置其他低优先级任务或进入低功耗模式
// CyDelay(1); // 示例延时
}
}
? 关键机制说明
输入频率测量:在外部触发信号的每次有效边沿(在 trig_in_ISR 中):
- 读取 CSD 计数器的当前值。
- 计算与前一次触发之间的计数器差值,得到输入触发信号的实际周期(以
Clk 的时钟周期为单位)。
- 设置一个标志
newTriggerDetected 并记录计算的周期 (triggerPeriod)。
- CSD 计数器始终在自由运行,作为精确的时间基准。
脉冲输出控制:
- Main Loop 监听标志:主循环不断检查
newTriggerDetected 是否置位。
- 计算脉冲宽度 (
CalculatePulseWidth): 根据你自定义的逻辑,使用测量的 triggerPeriod(输入频率)计算所需的输出脉冲宽度。
- 安全配置计数器:
- 禁止中断(进入临界区)。
- 停止运行的 CSD 计数器。
- 将计算出的新脉宽(转换为时钟周期数)写入计数器的周期寄存器 (
CSD_WritePeriod)。
- 将计数器值清零 (
CSD_WriteCounter(0))。
- 启动脉冲:
- 确保输出引脚处于关闭状态(避免瞬时脉冲)。
- 将输出引脚手动置为高电平 (
pulse_out_Write(1))。
- 启动 CSD 计数器 (
CSD_Start())。
- 恢复中断。
- 结束脉冲:
- 当 CSD 计数器达到设定的周期 (
TC) 时,csd_ISR 被调用。
- 中断服务程序将输出引脚手动置为低电平 (
pulse_out_Write(0))。
- 计数器会自动停止计数(因为设置为连续运行但在达到 TC 后停止)。下次触发时会被主循环重新配置并启动。
? 优势分析
- 单次脉冲:每次输入触发仅产生一个精确的输出脉冲(宽度可编程),无论触发信号的频率是多少。
- 高精度:脉宽由硬件计数器(CSD)的时钟源决定(例如 24 MHz IMO,精度 < 42ns)。
- 低延迟:从检测到输入触发边沿到启动输出脉冲(设置引脚高电平)之间的延迟是极小且固定的(主要发生在
ISR 后的主循环临界区代码内)。
- 灵活性:计算脉宽的
CalculatePulseWidth 函数可以轻松实现复杂的映射关系。
? 重要考虑因素
- 延迟:从检测到触发到实际设置
pulse_out 高电平之间有延迟(主要是主循环检查标志的等待时间 + 配置计数器的软件开销)。这在大多数情况下是可以接受的。
- 最大脉宽:由
CSD_WritePeriod() 允许设置的最大值限制(取决于计数器位数和时钟分频)。
- 最小脉宽/触发间隔:如果输入触发频率极高,需确保
CalculatePulseWidth + 计数器配置的时间小于最小的输入触发周期。否则会丢失触发或输出重叠。
- 首次触发:处理第一个触发信号时 (
lastTriggerCounter == 0),尚未能计算有效周期。可初始化为默认值或忽略。
- 中断优先级:确保
csd_ISR 的优先级高于 trig_in_ISR,以防止脉宽结束中断被延迟。
- 临界区:在主循环里更新计数器配置和启动脉冲时禁用中断是必要的,以防止执行过程被打乱。
? 调试技巧
- 系统时钟 (
Clk):初始设置为 IMO=24MHz 或 ILO。
- 计数器范围:通过预分频选择合适频率。
- 中断处理速度:在 ISR 中避免复杂操作。
- 主循环频率:
for(;;) 循环中不要添加过长延时。
- 输出监控:用示波器观察输入触发 (
trig_in) 和输出脉冲 (pulse_out) 信号。
- 全局变量保护:在多个线程访问时使用原子操作。
此方案充分利用了 PSoC 4000S 的硬件资源(CSD 作为高精度定时器,中断处理快速响应),有效解决了 PWM 组件在长触发周期下的问题,实现了可靠的单次触发脉冲输出功能。? 每次当你收到输入触发信号时,系统都会精确计算并输出一个“量身定制”的单次脉冲!
举报