十四.PWM输出


PWM的原理我这里就不再说了,脉冲宽度调制,通过改变周期和产空比满足负载不同的功率需求。

I.MX6UL的PWM功能

I.MX6UL的PWM和处理器内核对接的时候遵循外设总线协议,PWM和其他模块之间只有时钟信号(CCM模块)和重启信号(SRC模块)相关(还有中断处理),还有一个单独的输出信号。功能特性如下:

  • 16位的向上计数器、时钟源可选择。
  • 4X16位的FIFO,可以降低中断资源
  • 可配置的输出高低电平方式
  • 可以生产回滚或比较中断

I.MX6UL提供了8组PWM,每组的输出信号可以在几个引脚之间通过复用配置选择。,整体的框架结构如下:

I.MX6UL的PWM产生流程是这样的:

时钟选择器决定了计数器的工作频率,和前面一样,我们使用外设时钟周期(IPG_CLK,66MHz)

12位分频器,可以实现1~4097分频,假设我们选用66分频,每次counter计数的时候就是1us

Period Resgiter,周期寄存器,16位,当计数器的值和周期寄存器的值相等时,产生中断(这个中断可以不生成,类似于溢出中断)。计数器值等于周期,从0开始重新计数。

Sample Register,采样寄存器,16位。这个寄存器的值决定信号的占空比。这个比较重要,我们后面单独讲一下。但是意思就是计数器的值和SampleRegister的值相等时信号反转。

SampleRegister和FIFO

占空比是通过SampleRegister交给FIFO的,注意FIFO深度是4组,实际信号的占空比是通过FIFO的值去确定的,所以FIFO需要不停的写入新的值才能不断的生成新的信号。FIFO在任何时间都可以进行写操作,但要读取值必须在PWM使能的条件下。因为FIFO的深度是4,写入数据的时候要注意防止上溢,否则FWE异常(FIFO Write Error)。同理,我们还要检测FIFO的内部元素数量,防止FIFO空了无法生成新的信号。这个过程可以通过中断产生,当FIFO内元素低于指定值就可以生成中断,中断中给FIFO写入新的数据。还有几个注意

  • 我们只要对SampleRegister进行读操作,FIFO数据就会减少;
  • PWM在被Disable后,FIFO的元素就不再减少了;
  • 如果PWM被进行软复位,FIFO里所有内容会被清除

回滚和比较事件

当计数器到PWM_PR寄存器+2的值时会重置到0然后重新开始计数。这个过程和定时中断的计数器一样。这个事件可以当做一次回滚,回滚时输出可以根据设置去置0、1或无反应;这个过程也可以产生一个中断(前提是使能中断),当计数器值累计到sample值,输出状态会按设置进行更改,这是个比较事件,也可以触发一次中断。总之就是信号在到Period的时候反转一下,到Sample值时候再反转一下, 构成一个信号周期。

寄存器说明

I.MX6UL提供了8组PWM,每组使用6个寄存器

 其中CNR、PR和SAR是3个16位的寄存器,保存了计数器、Sample和Period的值,后面不在讲了 ,剩下的我们再看看

PWMCR

控制寄存器,

FWM[27:26]:FIFO剩余多少会触发FIFO空中断,一般设置为2,如果1的话比较危险,可能会FIFO空报错

POUTC[19:18]:输出配置,设置在回滚和比较事件时输出状态

CLKSRC[17:16]:时钟源,我们一般选择外设时钟源,值为01

SWR[3]:软复位,设置为1时软复位,复位完成后自动回0

REPEAT[2:1]:Sample重复,可以设置每个Sample值重复使用的次数

EN[0]:PWM使能

PWMSR

状态寄存器

FWE[6]:FIFO写错误

CMP[5]:比较事件

ROV[4]:回滚事件

FE[3]:FIFO空状态

FIFOAV[2:0]:FIFO内元素数量

中断使能

中断使能一共有3个bit可以用

CIE[2]:比较中断使能

RIE[1]:回滚中断使能

FIE[0]:FIFO空中断使能,FIFO元素小于FWM指定值触发中断。

PWMPR

周期寄存器,计算方法如下

PWMO:PWM输出信号频率

PCLK:经过分频器分频后的时钟周期

period:写入PR寄存器的值

注意实际周期是period+1,当我们写入PR的值为0xFFFF时实际效果和0xFFFE是一样的。

代码编写

代码很简单,主要就是注意定一个全局变量pwm_duty,每次调用设置占空比的时候都会把这个变量修改,这个变量主要作用是给中断服务使用。开始调试时忘了这个变量,一直测不到输出,加了通过不停添加打印节点发现只有duty的值只有4个满足设置要求,应该对应的就是FIFO的深度。肯定是在中断服务有问题,后来发现中断调用函数时传参的值是0,才发现这个问题。

文件结构

 c文件

/**
 * @file bsp_pwm.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-01-22
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include "bsp_pwm.h"
#include "stdio.h"
unsigned char pwm_duty;

/**
 * @brief PWM1初始化
 * 
 * @param period 周期 微秒
 * @param duty   占空比
 */
void pwm1_init(unsigned int period,unsigned int duty)
{   
    /**
     * IO初始化 电气性能:
     *bit [16]      : 0 HYS关闭
     *bit [15:14]   : 10 100K上拉
     *bit [13]      : 1 pull功能
     *bit [12]      : 1 pull/keeper使能
     *bit [11]      : 0 关闭开路输出
     *bit [7:6]     : 10 速度100Mhz
     *bit [5:3]     : 010 驱动能力为R0/2
     *bit [0]       : 0 低转换率
     */
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT,0);
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT,0xB090);

    PWM1->PWMCR = 0;                            //PWMCR清零
    PWM1->PWMCR |= (1<<26)|(1<<16)|(65<<4);     //PWCR[27:26](FWM)=01 [17:16](CLKSRC)=01 [15:4](PRESCALER)=65

    pwm1_setperiod(period);                     //设置周期    

    for(int i=0;i<4;i++){                       //通过SAM寄存器写入FIFO
        pwm1_setduty(duty);                 //定占空比
    }

    PWM1->PWMIR = (1<<0);                       //FIE=1,使能FIFO Empty中断
    system_register_irqHandler(PWM1_IRQn, (system_irq_handler_t)pwm1_irqhandler,NULL);  //中断函数注册

    GIC_EnableIRQ(PWM1_IRQn);                   //GIC使能
    PWM1->PWMSR = 0;                            //PWMSR寄存器清零
    PWM1->PWMCR |= (1<<0);                      //PWMCR[0](EN)=1,使能

}

/**
 * @brief 设置周期
 * 
 * @param value 
 */
void pwm1_setperiod(unsigned int value)
{
    unsigned int regvalue = 0;
    if (value<2){
        regvalue=2;
    }
    else{
        regvalue=value-2;
    }
    PWM1->PWMPR = (regvalue & 0xFFFF);
}

/**
 * @brief 设置占空比
 * 
 * @param duty 占空比
 */
void pwm1_setduty(unsigned char duty)
{
    unsigned short period;
    unsigned short sample;
    pwm_duty = duty;                //这行代码别忘了,全局变量pwm_duty,中断服务需要用到这个变量
    period = PWM1->PWMPR +2;
    sample = period *duty / 100;

    PWM1->PWMSAR = (sample & 0xFFFF);
}

void pwm1_irqhandler(unsigned int gcciar,void *userParam)
{
    if(PWM1->PWMSR &(1<<3)){
        pwm1_setduty(pwm_duty);
        PWM1->PWMSR |= (1<<3);
    }

}

代码很清楚了,备注可以直接看,就是几个寄存器的设置

头文件

/**
 * @file bsp_pwm.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-01-22
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#ifndef __BSP_PWM_H
#define __BSP_PWM_H

#include "imx6ul.h"
#include "bsp_int.h"

void pwm1_init(unsigned int period,unsigned int duty);
void pwm1_setperiod(unsigned int value);
void pwm1_setduty(unsigned char duty);
void pwm1_irqhandler(unsigned int gcciar,void *userParam);
#endif
bsp_pwm.h

使用

模块导入以后,可以在main函数中使用

int main(void)
{   
    int_init();
    imx6u_clkinit();
    clk_enable();
    delay_init();
    uart_init();
    key_init();
   
    unsigned char duty = 50;    
    unsigned char kv = 0;

    pwm1_init(1000,duty); 

    while(1){
    kv = key_getvalue();
    if(kv == KEY0_VALUE){
        duty += 5;
        if(duty>100){
            duty=5;
        }
        pwm1_setduty(duty);
        delay_ms(50);
        printf("duty:%d",duty);
        }
    }
    return 0;
}

在main函数中,还使用了按键,每次按键按下时占空比自增5%

初始化的时候定义的周期是1000,因为用的时钟源是66MHz,分频直接定死的值65对应66分频,周期就是1000微秒,输出频率1KHz很稳定。我用了个手持的示波器测了下输出的值,变化的过程是通过按键改变占空比的过程。这个输出频率我试过250K,很稳定,但是在低频(50Hz

左右)的时候波形不是特别好,高电平有个下降的趋势,不知道是不是示波器的原因还是怎么的。

相关