单片机/MCU论坛
直播中

麻酱

9年用户 1344经验值
擅长:MEMS/传感技术
私信 关注
[问答]

请问如何使用PSoC™ 4000S 制作单次 PWM?

我想用我的PSoC™ 4000S ( CY8C4045AZI-S413 ) 实现一个看似简单的功能。
目标是读取引脚上的外部触发信号,测量其频率,根据该频率计算另一个引脚的开启时间,并为该引脚设置开启时间。这意味着,输入触发信号的每个脉冲都会打开输出引脚并持续计算出的时间。
我的想法是使用 PWM 组件,因为与在 ISR 中设置引脚状态相比,它具有更精确的时间来设置引脚状态。

我尝试使用trigger_in重新加载PWM,只要输入触发器的周期短于PWM周期,这种方法就可以奏效。但如果它比 PWM 周期长,PWM 组件就会达到 TC 并重新开始计数,从而相应地设置输出信号。以下是可视化效果:


我怎样才能实现像达到 TC 后停止的单次 PWM 这样的功能?我认为PSoC™ 4000S 不存在此功能。

回帖(1)

卢表镜

2025-8-4 17:36:56

下面是一个基于 PSoC™ 4000S (CY8C4045AZI-S413) 实现单次触发输出(单脉冲模式)的详细方案,解决你在长触发周期下 PWM 组件自动复位导致输出异常的问题:


核心问题分析:
你之前尝试的"使用输入触发重新加载 PWM" 对于短周期(触发频率高)有效,但当触发周期 > PWM 周期时,PWM 的计数器会自动回零 (TC),导致输出引脚在预定的开启时间结束前就被错误地拉低或再次触发。你需要的是每个输入触发信号精确产生一个单次脉冲输出(One-Shot Pulse)。


推荐解决方案:
利用 CSD (CapSense Sigma-Delta) 模块中的可编程计数器,配合外部中断和系统中断。PSoC 4000S 的 CSD 模块可以配置为通用计数器。


? 硬件组件配置 (在 PSoC Creator / ModusToolbox™)




  1. CSD 组件:



    • 添加组件:添加 CSD 组件 (通常命名为 CSDCounter)。

    • 配置为计数器模式

      • 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 或其预分频版本)。这直接影响输出脉冲的时间精度 ⚡️。




  2. 外部中断引脚 (EXT_INT) 组件



    • 添加组件:添加 Pin 组件用于输入触发信号。在 Pin 的属性配置中启用 中断 支持。

    • 配置中断

      • 中断类型:选择检测边缘中断

      • 边沿:选择 上升沿下降沿 (根据触发信号有效边沿而定)。

      • 命名引脚(如 trig_in)。





  3. 输出引脚组件



    • 添加组件:添加另一个 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); // 示例延时
    }
}

? 关键机制说明




  1. 输入频率测量:在外部触发信号的每次有效边沿(在 trig_in_ISR 中):



    • 读取 CSD 计数器的当前值。

    • 计算与前一次触发之间的计数器差值,得到输入触发信号的实际周期(以 Clk 的时钟周期为单位)。

    • 设置一个标志 newTriggerDetected 并记录计算的周期 (triggerPeriod)。

    • CSD 计数器始终在自由运行,作为精确的时间基准。




  2. 脉冲输出控制



    • 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 函数可以轻松实现复杂的映射关系。


? 重要考虑因素



  1. 延迟:从检测到触发到实际设置 pulse_out 高电平之间有延迟(主要是主循环检查标志的等待时间 + 配置计数器的软件开销)。这在大多数情况下是可以接受的。

  2. 最大脉宽:由 CSD_WritePeriod() 允许设置的最大值限制(取决于计数器位数和时钟分频)。

  3. 最小脉宽/触发间隔:如果输入触发频率极高,需确保 CalculatePulseWidth + 计数器配置的时间小于最小的输入触发周期。否则会丢失触发或输出重叠。

  4. 首次触发:处理第一个触发信号时 (lastTriggerCounter == 0),尚未能计算有效周期。可初始化为默认值或忽略。

  5. 中断优先级:确保 csd_ISR 的优先级高于 trig_in_ISR,以防止脉宽结束中断被延迟。

  6. 临界区:在主循环里更新计数器配置和启动脉冲时禁用中断是必要的,以防止执行过程被打乱。


? 调试技巧



  1. 系统时钟 (Clk):初始设置为 IMO=24MHzILO

  2. 计数器范围:通过预分频选择合适频率。

  3. 中断处理速度:在 ISR 中避免复杂操作。

  4. 主循环频率for(;;) 循环中不要添加过长延时。

  5. 输出监控:用示波器观察输入触发 (trig_in) 和输出脉冲 (pulse_out) 信号。

  6. 全局变量保护:在多个线程访问时使用原子操作。


此方案充分利用了 PSoC 4000S 的硬件资源(CSD 作为高精度定时器,中断处理快速响应),有效解决了 PWM 组件在长触发周期下的问题,实现了可靠的单次触发脉冲输出功能。? 每次当你收到输入触发信号时,系统都会精确计算并输出一个“量身定制”的单次脉冲!

举报

更多回帖

发帖
×
20
完善资料,
赚取积分