PWM实验

硬件分析

控制GPIO1_IO08即可控制LCD屏幕的背光。

简介

打开《I.MX6ULL 参考手册》的第40 章“Chapter 40 Pulse Width Modulation(PWM)”,I.MX6U一共有8 路PWM 信号,每个PWM 包含一个16 位的计数器和一个4 x 16 的数据FIFO,I.MX6U的PWM 外设结构如图所示:

../../_images/image-20210824101159405.png

  1. 选择器,用于选择 PWM信号的时钟源,一共有三种时钟源:

    • ipg_clk

    • ipg_clk_highfreq

    • ipg_clk_32k

  2. 12位的分频器,可以对①中选择的时钟源进行分频。

  3. PWM的 16位计数器寄存器,保存着 PWM的计数值。

  4. PWM的 16位周期寄存器,此寄存器用来控制 PWM的频率。

  5. PWM的 16位采样寄存器,此寄存器用来控制 PWM的占空比。

  6. PWM的中断信号, PWM是提供中断功能的,如果使能了相应的中断的话就会产生中断。

  7. PWM对应的输出 IO,产生的 PWM信号就会从对应的 IO中输出, 开发板的LCD背光控制引脚连接在 I.MX6U的 GPIO1_IO8上, GPIO1_IO8可以复用为 PWM1_OUT

PWM特性

PWM频率

PWM的 16位计数器是个向上计数器,此计数器会从0X0000开始计数,直到计数值等于寄存器PWMx_PWMPR(x=1~8) + 1,然后计数器就会重新从 0X0000开始计数,如此往复。所以寄存器PWMx_PWMPR可以设置 PWM的频率。

PWM极性和占空比

在一个周期内, PWM从 0X0000开始计数的时候, PWM引脚先输出高电平 (默认情况下, 可以通过配置输出低电平 )。采样 FIFO中保存的采样值会在每个时钟和计数器值进行比较,当 采样值和计数器相等的话,PWM引脚就会改为输出低电平 (默认情况下,同样可以通过配置输出 高电平 )。计数器会持续计数,直到和周期寄存器 PWMx_PWMPR(x=1~8) + 1的值相等。一个周期结束如下图所示:

../../_images/image-20210824103938297.png

注意:PWMSAR值是采样值,会先存进FIFO里面,实际上是FIFO里面的数据和PWM计数值比较,相等的时候翻转io,并且FIFO中可以存放多个PWMSAR采样值,需要人为设定。

PWM占空比FIFO

当我们向 PWMx_PWMSAR寄存器写入采样值的时候,

  1. 如果 FIFO没满的话其值会被存储到 FIFO中。

  2. 如果 FIFO满的时候写入采样值就会导致寄存器 PWMx_PWMSR的位 FWE(bit6)置1,表示 FIFO写错误, FIFO里的值也并不会改变。

FIFO可以在任何时候写入,但是只有在PWM使能的情况下读取。寄存器 PWMx_SR的位FIFOAV(bit2:0)记录着当前 FIFO中有多少个数据。从采样寄存器 PWMx_PWMSAR读取一次数据, FIFO里的数据就会减一,每产生一个周期的 PWM信号, FIFO里面的数据就会减一,相当于被用掉了。

PWM有个 FIFO空中断,当FIFO为空的时候就会触发此中断,可以在此中断处理函数中向 FIFO写入数据。

PWM寄存器

  1. PWM1_PWMCR 控制寄存器

    ../../_images/image-20210824104706320.png

    • FWM(bit27): FIFO水位线,用来设置 FIFO空余位置为多少的时候表示 FIFO为空。

      • 0: FIFO空余位置大于等于 1的时候 FIFO为空;

      • 1: FIFO空余位置大于等于 2的时候 FIFO为空;

      • 2: FIFO空余位置大于等于 3的时候FIFO为空;

      • 3: FIFO空余位置大于等于 4的时候 FIFO为空。

    • STOPEN( 此位用来设置停止模式下 PWM是否工作,

      • 0:在停止模式下PWM继续工作,

      • 1:停止模式下关闭 PWM。

    • DOZEN:此位用来设置休眠模式下 PWM是否工作

      • 0:在休眠模式下PWM继续工作,

      • 1:示休眠模式下关闭 PWM。

    • WAITEN: 此位用来设置等待模式下 PWM是否工作,

      • 0:在等待模式下PWM继续工作,

      • 1:等待模式下关闭 PWM。

    • DEGEN:此位用来设置调试模式下 PWM是否工作,

      • 0:在调试模式下PWM继续工作,

      • 1:调试模式下关闭 PWM。

    • BCTR:字节交换控制位,用来控制 16位的数据进入 FIFO的字节顺序。

      • 为 0的时候不进行字节交换,

      • 为 1的时候进行字节交换。HCRT( 半字交换控制位,用来决定从 32位 IP总线接口传输来的哪个半字数据写入采样寄存器的低 16位中。

    • POUTC:bit19: PWM输出控制控制位,用来设置 PWM输出模式,

      • 为 0的时候表示PWM先输出高电平,当计数器值和采样值相等的话就输出低电平。

      • 为 1的时候相反,当为 2或者 3的时候 PWM信号不输出。本章我们设置为 0,也就是一开始输出高电平,当计数器值和采样值相等的话就改为低电平,这样采样值越大高电平时间就越长,占空比就越大。

    • CLKSRC:bit17: PWM时钟源选择,

      • 0:关闭;

      • 1:选择 ipg_clk为时钟源;

      • 2:选择 ipg_clk_highfreq为时钟源;

      • 3:选择 ipg_clk_32k为时钟源。

      本章我们设置为 1,也就是选择 ipg_clk为 PWM的时钟源,因此 PWM时钟源频率为 66MHz

    • PRESCALER:bit15: 分频值,可设置为 0~4095,对应着 1~4096分频。

    • SWR:软件复位,向此位写 1就复位 PWM,此位是自清零的,当复位完成以后此位会自动清零。

    • REPEAT:bit2: 重复采样设置,此位用来设置 FIFO中的每个数据能用几次。可设置 0~3 分别表示 FIFO中的每个数据能用 1~4次。本章我们设置为 0,即 FIFO中 的每个数据只能用一 次。

    • EN( PWM使能位,

      • 1:使能 PWM,

      • 0:关闭 PWM

  2. PWM1_PWMIR 中断使能寄存器

    ../../_images/image-20210824105407388.png

    • CIE(bit2):比较中断使能位,

      • 1:使能比较中断,

      • 0:关闭比较中断。

    • RIE(bit1):翻转中断使能位,当计数器值等于采样值并回滚到 0X0000的时候就会产生此中断,

      • 1:使能翻转 中断,

      • 0:关闭翻转中断。

    • FIE(bit0) :FIFO空中断,

      • 1:使能,

      • 0:关闭。

  3. PWM1_PWMSR 状态寄存器

    ../../_images/image-20210824105600680.png

    • FWE:FIFO写错误事件,为 1的时候表示发生了 FIFO写错误。

    • CMP:FIFO比较事件发标志位,为 1的时候表示发生 FIFO比较事件。

    • ROV:翻转事件标志位,为 1的话表示翻转事件发生。

    • FE:FIFO空标志位,为 1的时候表示 FIFO位空。

    • FIFOAV: 此位记录 FIFO中的有效数据个数,有效值为 0~4,分别表示 FIFO中有0~4个有效数据。

  4. PWM1_PWMPR 周期寄存器

    ../../_images/image-20210824105803679.png

    可以看出,寄存器 PWM1_PWMPR只有低 16位有效,当 PWM计数器的值等于 PERIOD+1的时候就会从 0X0000重新开始计数,开启另一个周期。 PWM的频率计算公式如下: PWMO(Hz) = PCLK(Hz) / (PERIOD + 2) 其中 PCLK是最终进入PWM的时钟频率,假如 PCLK的频率为1MHz,现在我们要产生一个频率为 1KHz的 PWM信号,那么就可以设置

     PERIOD = 1000000 / 1000 – 2 = 998
    
  5. PWM1_PWMSAR 采样寄存器

    ../../_images/image-20210824105936239.png

    此寄存器也是只有低 16位有效,为采样值。通过这个采样值即可调整占空比,当计数器的值小于 SAMPLE的时候输出高电平 (或低电平 )。当计数器值大于等于 SAMPLE,小于寄存器PWM1_PWMPR的 PERIO的时候输出低电平 (或高电平 )。同样在上面的例子中,假如我们要设置 PWM信号的占空比为 50%,那么就可以将 SAMPLE设置为

    (PERIOD + 2) / 2 = 1000 / 2=500
    

实验

因为 FIFO中的采样值每个周期都会少一个,所以需要不断的向 FIFO中写入采样值,防止其为空。我们可以使能 FIFO空中断,这样当 FIFO为空的时候就会触发相应的中断,然后在中断处理函数中向 FIFO写入采样值。

参考【19.bsp_pwm】工程

源码分析

bsp_pwm.c

#include "bsp_pwm.h"
#include "bsp_int.h"
#include "stdio.h"



/* 背光设备 */
struct backlight_dev_struc backlight_dev;

/*
 * @description	: pwm1中断处理函数
 * @param		: 无
 * @return 		: 无
 */
void pwm1_irqhandler(void)
{

	if(PWM1->PWMSR & (1 << 3)) 	/* FIFO为空中断 */
	{
		/* 将占空比信息写入到FIFO中,其实就是设置占空比 */
		pwm1_setduty(backlight_dev.pwm_duty); 
		PWM1->PWMSR |= (1 << 3); /* 写1清除中断标志位 */ 
	}
}

/*
 * @description	: 初始化背光PWM
 * @param		: 无
 * @return 		: 无
 */
void pwm1_init(void)
{
	unsigned char i = 0;
	
	/* 1、背光PWM IO初始化 */
	IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT, 0); /* 复用为PWM1_OUT */

	/* 配置PWM 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_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT, 0XB090);
	
	/* 2、初始化PWM1		*/
	/*
   	 * 初始化寄存器PWMCR
   	 * bit[27:26]	: 01  当FIFO中空余位置大于等于2的时候FIFO空标志值位
   	 * bit[25]		:
 0  停止模式下PWM不工作
   	 * bit[24]		: 0	  休眠模式下PWM不工作
   	 * bit[23]		: 0   等待模式下PWM不工作
   	 * bit[22]		: 0   调试模式下PWM不工作
   	 * bit[21]		: 0   关闭字节交换
   	 * bit[20]		: 0	  关闭半字数据交换
   	 * bit[19:18]	: 00  PWM输出引脚在计数器重新计数的时候输出高电平
   	 *					  在计数器计数值达到比较值以后输出低电平
   	 * bit[17:16]	: 01  PWM时钟源选择IPG CLK = 66MHz
   	 * bit[15:4]	: 65  分频系数为65+1=66,PWM时钟源 = 66MHZ/66=1MHz
   	 * bit[3]		: 0	  PWM不复位
   	 * bit[2:1]		: 00  FIFO中的sample数据每个只能使用一次。
   	 * bit[0]		: 0   先关闭PWM,后面再使能
	 */
	PWM1->PWMCR = 0;	/* 寄存器先清零 */
	PWM1->PWMCR |= (1 << 26) | (1 << 16) | (65 << 4);

	/* 设置PWM周期为1000,那么PWM频率就是1M/1000 = 1KHz。 */
	pwm1_setperiod_value(1000);

	/* 设置占空比,默认50%占空比   ,写四次是因为有4个FIFO */
	backlight_dev.pwm_duty = 50;
	for(i = 0; i < 4; i++)
	{
		pwm1_setduty(backlight_dev.pwm_duty);	
	}
	
	/* 使能FIFO空中断,设置寄存器PWMIR寄存器的bit0为1 */
	PWM1->PWMIR |= 1 << 0;
	system_register_irqhandler(PWM1_IRQn, (system_irq_handler_t)pwm1_irqhandler, NULL);	/* 注册中断服务函数 */
	GIC_EnableIRQ(PWM1_IRQn);	/* 使能GIC中对应的中断 */
	PWM1->PWMSR = 0;			/* PWM中断状态寄存器清零 */
	
	pwm1_enable();				/* 使能PWM1 */

	
}

/*
 * @description	: 使能PWM
 * @param		: 无
 * @return 		: 无
 */
void pwm1_enable(void)
{
	PWM1->PWMCR |= 1 << 0;	 
}

/*
 * @description		: 设置Sample寄存器,Sample数据会写入到FIFO中,
 * 					  所谓的Sample寄存器,就相当于比较寄存器,假如PWMCR中的POUTC
 *				  	  设置为00的时候。当PWM计数器中的计数值小于Sample的时候
 *					  就会输出高电平,当PWM计数器值大于Sample的时候输出底电平,
 *					  因此可以通过设置Sample寄存器来设置占空比
 * @param -  value	: 寄存器值,范围0~0XFFFF
 * @return 			: 无
 */
void pwm1_setsample_value(unsigned int value)
{
	PWM1->PWMSAR = (value & 0XFFFF);	
}

/*
 * @description		: 设置PWM周期,就是设置寄存器PWMPR,PWM周期公式如下
 *					  PWM_FRE = PWM_CLK / (PERIOD + 2), 比如当前PWM_CLK=1MHz
 *					  要产生1KHz的PWM,那么PERIOD = 1000000/1K - 2 = 	998
 * @param -  value	: 周期值,范围0~0XFFFF
 * @return 			: 无
 */
void pwm1_setperiod_value(unsigned int value)
{
	unsigned int regvalue = 0;

	if(value < 2)
		regvalue = 2;
	else 
		regvalue = value - 2;
	PWM1->PWMPR = (regvalue & 0XFFFF);
}

/*
 * @description		: 设置PWM占空比
 * @param -  value	: 占空比0~100,对应0%~100%
 * @return 			: 无
 */
void pwm1_setduty(unsigned char duty)
{
	unsigned short preiod;
	unsigned short sample;

	backlight_dev.pwm_duty = duty;
	preiod = PWM1->PWMPR + 2;
	sample = preiod * backlight_dev.pwm_duty / 100;
	pwm1_setsample_value(sample);
}

main.c

    pwm1_init();                 /* 初始化背光PWM		*/

	while(1)					
	{	
		if(KEY_READ()==1){
			delay_ms(50);
			if(KEY_READ()==1){
				key_press_flg=1;
			}

		}
		if(key_press_flg){
			key_press_flg = 0;
			duty += 10;					/* 占空比加10% */
			if(duty > 100){
				duty = 10;				/* 如果占空比超过100%,重新从10%开始 */
			}
			printf("duty=%d\r\n",duty);
			pwm1_setduty(duty);		/* 设置占空比 */

		}
			

	}

实验现象

可以通过key案件,来调整duty,从而看到lcd屏幕背光亮度的调节。