GD32学习笔记3(PWM控制LED,LED:嘿嘿又是我)

系列文章目录

第一章:GD32学习笔记1(高难度工程,点亮一个LED灯)
第二章:GD32学习笔记2(按键控制LED灯)
第三章:GD32学习笔记3(PWM控制LED,LED:嘿嘿又是我)


前言

大家有没有发现单片机在一般情况下输出的电压都是一定的,那么当它连接LED灯后,LED的亮度就是一定的,那如果要控制LED不那么亮,该怎么搞呢?因为一个问题是明确的,管脚要么输出1,要么输出0。但是如果频繁的在0和1之间切换,让LED闪起来,当闪烁的频率超过人眼能观察的极限时,我们看起来就像是灯光变暗了一样,假如在极短的单位时间内,10%的时间输出1,90%的时间输出0,那么灯光就只有直接输出高电平亮度的10%,有了这样一个理论,我们就可以开始编程实践了。

一、PWM?介系个嘛呀?

以下内容来自百度百科:
在这里插入图片描述
在这里插入图片描述
对于脉冲,我个人的理解,就是单片机首先发出一个低电平,在这个低电平上突然产生一个高电平,在示波器上显示就是一个竖杠,当把这个竖杠放大来看,发现它是一个没有底的矩形,电平由低变高再变低,就会有所谓的上升沿和下降沿,而PWM波就是一个个这样的矩形组成的,且分布有规律,每两个矩形间的距离相等,即第一个上升沿到第二个上升沿的距离是相等的,下降沿也是。
这样的波形刚好能够满足我们在前言里面提到的控制LED灯光信号的要求,它在一个周期(第一个上升沿到第二个上升沿的时间)内有精确的高电平和低电平各自所占的时间,由于是单片机产生,它的工作频率(一秒钟产生了多少个上升沿)可以轻松超过人眼的极限,我们只需要调整上升沿到下降沿间隔的时间,也即是高电平的时间,就可以调整灯光亮度,高电平时间在整个周期内存在的时间用专业的话将叫占空比,自此,PWM的几大参数就讲完了:

名称含义
频率一秒钟能产生上升沿或下降沿的数量
周期第一个高/低电平到第二个高/低电平的时间
占空比高电平与周期之比
幅度由单片机输出能力决定

二、单片机如何输出PWM

1.关键在于时间

在单片机的外设之中有一个十分重要的东西叫定时器,GD32在程序中管它叫TIMER,ST的芯片习惯叫TIM,其实指的是一个东西,我们都知道单片机的工作是有时钟频率的,以GD32F4为例,它的定时器1最大频率是168MHz,在我的板子上外接了一个25MHz的晶振,进行4倍倍频,能够使定时器1达到最大100MHz,也就是说我每秒钟可以让单片机进行最大100000000次计数。
在这里插入图片描述

2.是不是所有的管脚都能使用定时器

从数据手册上可以得知各个定时器通道能够映射到哪个管脚,也即是这个管脚能够复用哪个功能,我们接LED灯的管脚是PC6,数据手册显示PC6可以复用TIMER2_CH0,也即是定时器2的0通道。这样理论和硬件准备就都完成了。
在这里插入图片描述

三、添加新的源文件gd32f4xx_timer.c

从工程分组管理添加官方库里面的gd32f4xx_timer.c,所有与定时器有关的函数都在这里面,后面对定时器进行初始化设置需要用到的函数,都要从这里面调用。

四、代码实现

代码实现还是那老三步:管脚、外设、主函数
我们在昨天的笔记里面已经写好了一个按键控制LED的工程,今天直接从上面改,要实现的功能就是通过按键改变PWM的占空比,然后将这个PWM从PC6输出,用来控制LED。

1.修改管脚初始化函数

上一章的管脚初始化中,我们已经写好了PA0输入、带上拉,这部分不用改,关键在于控制LED灯的PC6,原本的PC6是输出模式、推挽输出,但是今天要复用定时器功能,就不能直接输出了。

/*!
    \brief      set GPIO mode
    \param[in]  gpio_periph: GPIO port 
                only one parameter can be selected which is shown as below:
      \arg        GPIOx(x = A,B,C,D,E,F,G,H,I)
    \param[in]  mode: GPIO pin mode
      \arg        GPIO_MODE_INPUT: input mode
      \arg        GPIO_MODE_OUTPUT: output mode
      \arg        GPIO_MODE_AF: alternate function mode
      \arg        GPIO_MODE_ANALOG: analog mode
    \param[in]  pull_up_down: GPIO pin with pull-up or pull-down resistor
      \arg        GPIO_PUPD_NONE: floating mode, no pull-up and pull-down resistors
      \arg        GPIO_PUPD_PULLUP: with pull-up resistor
      \arg        GPIO_PUPD_PULLDOWN:with pull-down resistor
    \param[in]  pin: GPIO pin
                one or more parameters can be selected which are shown as below:
      \arg        GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     none
*/
void gpio_mode_set(uint32_t gpio_periph, uint32_t mode, uint32_t pull_up_down, uint32_t pin)

再把gpio_mode_set这个函数拿出来复习一下,此前mode这个参数我们只用了INPUT和OUTPUT,今天要将它设置为AF复用模式,第三个参数依然是无电阻。
当GPIO模式被设置为复用之后,就要使用一个新函数了:GPIO复用功能设置

/*!
    \brief      set GPIO alternate function
    \param[in]  gpio_periph: GPIO port 
                only one parameter can be selected which is shown as below:
      \arg        GPIOx(x = A,B,C,D,E,F,G,H,I)
    \param[in]  alt_func_num: GPIO pin af function
      \arg        GPIO_AF_0: SYSTEM
      \arg        GPIO_AF_1: TIMER0, TIMER1
      \arg        GPIO_AF_2: TIMER2, TIMER3, TIMER4
      \arg        GPIO_AF_3: TIMER7, TIMER8, TIMER9, TIMER10
      \arg        GPIO_AF_4: I2C0, I2C1, I2C2
      \arg        GPIO_AF_5: SPI0, SPI1, SPI2, SPI3, SPI4, SPI5
      \arg        GPIO_AF_6: SPI1, SPI2, SAI0 
      \arg        GPIO_AF_7: USART0, USART1, USART2
      \arg        GPIO_AF_8: UART3, UART4, USART5, UART6, UART7
      \arg        GPIO_AF_9: CAN0, CAN1, TLI, TIMER11, TIMER12, TIMER13
      \arg        GPIO_AF_10: USB_FS, USB_HS
      \arg        GPIO_AF_11: ENET
      \arg        GPIO_AF_12: EXMC, SDIO, USB_HS
      \arg        GPIO_AF_13: DCI
      \arg        GPIO_AF_14: TLI
      \arg        GPIO_AF_15: EVENTOUT
    \param[in]  pin: GPIO pin
                one or more parameters can be selected which are shown as below:
      \arg        GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     none
*/
void gpio_af_set(uint32_t gpio_periph, uint32_t alt_func_num, uint32_t pin)

在官方的注释中给了很详细的参数功能说明,一共能设置16种复用功能参数,在前面的数据手册里面已经找到了PC6能复用TIMER2,所以我们将alt_func_num这个参数设置为GPIO_AF_2。
改好这一点后,你就获得了如下的管脚初始化函数:

void GPIO_Config(void)
{
	rcu_periph_clock_enable(RCU_GPIOA);
	rcu_periph_clock_enable(RCU_GPIOC);
	/*LED*/
	gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_6);
    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
	gpio_af_set(GPIOC,GPIO_AF_2,GPIO_PIN_6);
	/*KEY*/
	gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
    gpio_output_options_set(GPIOA, GPIO_PUPD_NONE, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
}

2.定时器初始化函数

打开新添加的源文件,好家伙里面的函数辣么多,但其实我们实现今天的功能,用不到几个,为了节省篇幅,我先把定时器初始化函数先粘贴出来,再解释一下为什么用它,参数为什么要这么设置:

void TIMER_Config(void)
{
	timer_oc_parameter_struct timer_ocintpara;
    timer_parameter_struct timer_initpara;
    rcu_periph_clock_enable(RCU_TIMER2);
    timer_deinit(TIMER2);

    timer_initpara.prescaler         = 99;	                                    
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;                      
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;                        
    timer_initpara.period            = 19999;                                   
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;                        
    timer_initpara.repetitioncounter = 0;                                       
    timer_init(TIMER2,&timer_initpara);

    timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;
    timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;
    timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;
    timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
    timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;
    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;

    timer_channel_output_config(TIMER2,TIMER_CH_0,&timer_ocintpara);
    timer_channel_output_mode_config(TIMER2,TIMER_CH_0,TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER2,TIMER_CH_0,TIMER_OC_SHADOW_DISABLE);
	timer_primary_output_config(TIMER2, ENABLE);
    timer_auto_reload_shadow_enable(TIMER2);

    timer_enable(TIMER2);
}

C语言语法性的东西我们不讲,这个初始化函数里面最关键的参数是三个:prescaler(预分频),period(周期值),repetitioncounter(重装载值)。
我们前面讲过了现在的PC6有最大100MHz的频率,那要想输出一个方波该怎么搞,我们先不管占空比,先把频率和周期搞定,我们先做一个周期20ms,频率50Hz的pwm。控制LED其实对频率和周期没什么要求,但考虑到以后可能会写跟电机控制有关的内容,这个部分就不挖坑了(我感觉说了好几句废话,频率跟周期其实是一个东西,互为倒数关系而已)。
预分频的作用是把当前工作频率先做个除法,周期值就是把得到的预分频得到频率再做一个除法(那它为什么要叫周期值呢?)就得到了最终PWM波的频率,所以我们先把预分频设置为100(0~99就是100,从0开始计数),把周期值设置为20000,100MHz/100/20000=50Hz。有小伙伴可能要问了,为啥是100和20000呢?10和200000不行吗?或者1000和2000不行吗?这个一会讲占空比再说。
再使用timer_channel_output_mode_config函数将定时器通道配置为PWM输出模式,这个模式是怎么实现的就不讲了,太底层(其实我也不明白)。

3.按键控制PWM占空比函数

根据昨天的介绍,最外面的一层判断是通过输入状态来判断按键是否被按下,再通过一个100ms的延时函数进行消抖,再通过以下这个函数调节PWM占空比:

/*!
    \brief      configure TIMER channel output pulse value
    \param[in]  timer_periph: please refer to the following parameters
    \param[in]  channel:
                only one parameter can be selected which is shown as below:
      \arg        TIMER_CH_0: TIMER channel0(TIMERx(x=0..4,7..13))
      \arg        TIMER_CH_1: TIMER channel1(TIMERx(x=0..4,7,8,11))
      \arg        TIMER_CH_2: TIMER channel2(TIMERx(x=0..4,7))
      \arg        TIMER_CH_3: TIMER channel3(TIMERx(x=0..4,7))
    \param[in]  pulse: channel output pulse value,0~65535
    \param[out] none
    \retval     none
*/
void timer_channel_output_pulse_value_config(uint32_t timer_periph, uint16_t channel, uint32_t pulse)

它的关键参数就一个pulse(输出通道脉冲值),占空比也就是这个值与前面周期值之比,在一个周期内,占空比决定高电平所占时间。这里把前面的小坑填上,在官方的注释中,输出通道脉冲值可取值的范围是0~65535,如果我们将周期值设置的远远大于65535,那会导致输出通道脉冲值即使取到最大值,占空比也会很小,也就是占空比的可调范围变小了,达不到0% ~100%,如果周期值取得很小,那占空比可调级数就会变小吗,比如周期值为10,调节PWM占空比就要以10%为基数,所以周期值与输出通道脉冲值最好在一个数量级,且不能太大或太小。
这次我希望达到的功能是每按下一次按键,PWM的占空比就增加20%,达到100%时再按下按键,占空比归0,控制函数如下:

void Key_Control(void)
{
	uint16_t i = 0;
	while(1)
	{
		timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,i);
		if(RESET == gpio_input_bit_get(GPIOA, GPIO_PIN_0))
		{
			delay_1ms(100);
			if(RESET == gpio_input_bit_get(GPIOA, GPIO_PIN_0))
			{
				i += 4000;
				if(i > 20000)
				{
					i = 0;
				}
			}
		}	
	}
}

4.主函数

咱们的主函数还是简洁明了,几个初始化函数之后,直接调用按键控制函数:

int main(void)
{
	systick_config();
	GPIO_Config();
	TIMER_Config();
	Key_Control();
}

由于在按键控制函数中有了while(1),主函数里面连while(1)都省了

五、来看看效果

1.灯光效果

PWM-LED效果

2.波形效果

PWM-LED波形


总结

其实今天有两个点有点混乱,第一个是为什么PWM的占空比会等于输出通道脉冲值与前面周期值之比,我理解的输出通道脉冲值应该是指在同一个周期内输出多少个脉冲,这些脉冲的宽度都是1个预分频后的时钟周期,即1us,那么周期值是指将20000个预分频后的时钟周期放在一个周期里,所以波形上就是20ms,数据上是对的上的;第二个点是外部晶振频率是25MHz,为什么倍频系数为4,在代码里面和数据手册里面没有找到解释这个的地方,但找到了以下这段代码,在官方提供的系统函数中,倍频应该是8倍到200MHz,就很奇怪。

/*!
    \brief      configure the system clock to 200M by PLL which selects HXTAL(25M) as its clock source
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void system_clock_200m_25m_hxtal(void)

然后就是要注意配置频率和占空比的方法。(我好像又挖了个坑,重装载值是不是没讲?算了以后再说)

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值