第八课:树莓派PWM(脉宽调制)

第一课:什么是树莓派
第二课:基于树莓派的10个经典项目
第三课:购买您的第一个树莓派
第四课:如何安装树莓派系统
第五课:树莓派C语言编程手册
第六课:树莓派led控制
第七课:树莓派按键控制

什么是PWM

PWM是Pulse Width Modulation (PWM)的缩写,意思是脉冲宽度调制,那什么是脉冲呢?

什么是脉冲

脉冲是一种信号,我们平常听到 “脉搏” 这个词,我们这里的脉冲和这个脉搏其实很相似,脉搏是跳动的,有规律的,脉冲也是这样,是有规律的,这个规律在编程领域脚频率,或者叫周期。如果要给脉冲下一个定义的话:
脉冲:脉冲是一种电压反复在高和低(H和L)之间改变的电信号。
就像下面这张图:
在这里插入图片描述

什么是脉宽调制

脉宽很好理解,就是脉冲的宽度,高电平表示有脉冲信号,低电平表示没有脉冲信号,这个宽度就是高电平的持续时间,或者叫长度也行。
那么,调制呢,就是这个高电平的持续时间是可以调整的,可以通过程序改变,动态的改变,当然也可以通过硬件电路实现调整,比如我们用得比较多的旋钮,可以把台灯调到最亮,或者最暗。

树莓派如何实现

有了上面的知识铺垫,就可以来理解树莓派的实现原理了。
首先,树莓派有专门的函数来设置pwm功能
这两个函数的使用,非常简单,第一个设置功能,我们用过,但这里要注意的是:只能使用第1号脚(wiringPi模式)。
,第二个函数用于调制PWM宽度。很好理解,范围是0~1024.
在这里插入图片描述

实验方法

我们只需要接一个led灯到1号引脚,然后配置PWM_OUTPUT功能,接着就是用一个循环语句通过pwmWrite调制这个引脚的pwm值,就行了。G和B不需要连接。
在这里插入图片描述

实现呼吸灯

  1. 包含头文件
    #include <wiringPi.h>
    #include <stdlib.h>
    #define R 1 //红色灯接到wiringPi编码的#1号脚
  2. 设置wiringPi编码模式
    if(wiringPiSetup()<0)
    {
    perror(“树莓派启动失败…”);
    exit(-1);
    }
  3. 设置引脚功能
    pinMode(R,PWM_OUTPUT);//设置引脚为pwm功能
    4)实现呼吸灯
    while(1){
    //渐—亮
    for(i=0;i<1024;i++)
    {
    pwmWrite(R,i);//设置PWM脉冲宽度值
    delayMicroseconds(10000);//延时100ms
    }
    //渐—暗
    for(i=1024;i>0;i–)
    {
    pwmWrite(R,i);//设置PWM脉冲宽度值
    delayMicroseconds(10000);//延时100ms
    }
    }
    完整代码:
pi@xiajiashan:~/pi-c-blog$ cat pwm_led.c 
#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
#define R       1   //wiringPi编码模式  1号脚---pwm
int main()
{
  int i;
  if(wiringPiSetup()<0)
  {
    exit(-1);
  }
  pinMode(R,PWM_OUTPUT);//设置R为PWM输出功能
  while(1){
       //渐---亮
       for(i=0;i<1024;i++)
       {
           pwmWrite(R,i);//设置PWM脉冲宽度值
           delayMicroseconds(10000);//延时100ms
       }
       //渐---暗
       for(i=1024;i>0;i--)
       {
           pwmWrite(R,i);//设置PWM脉冲宽度值
           delayMicroseconds(10000);//延时100ms
       }
  }
  return 0;
}
pi@xiajiashan:~/pi-c-blog$ 

编译运行

在这里插入图片描述
需要sudo执行
效果请看视频教程
https://edu.csdn.net/course/play/28051/383622?spm=1002.2001.3001.4143

为什么只有第1脚才能实现pwm

API手册上说,只有第1脚才支持PWM_OUTPUT功能,这是因为树莓派cpu只开放一个引脚作为外部pwm功能,但是如果其他引脚也要实现pwm怎么办呢?比如我要让RGB三个灯实现呼吸效果怎么办?

软件PWM

软件pwm实际上是通过普通IO口控制高低电平的持续时间来模拟PWM功能的。
相关函数如下:
int softPwmCreate (int pin, int initialValue, int pwmRange);创建软件PWM
第一个参数是引脚编号;
第二,第三个参数是脉冲宽度范围,initialValue必须大于或等于0,pwmRange没有做硬性规定
整个源码如下:

/*
 * softPwm.c:
 *	Provide many channels of software driven PWM.
 *	Copyright (c) 2012-2017 Gordon Henderson
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://projects.drogon.net/raspberry-pi/wiringpi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    wiringPi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with wiringPi.
 *    If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

#include <stdio.h>
#include <malloc.h>
#include <pthread.h>

#include "wiringPi.h"
#include "softPwm.h"

// MAX_PINS:
//	This is more than the number of Pi pins because we can actually softPwm.
//	Once upon a time I let pins on gpio expanders be softPwm'd, but it's really
//	really not a good thing.

#define	MAX_PINS	64

// The PWM Frequency is derived from the "pulse time" below. Essentially,
//	the frequency is a function of the range and this pulse time.
//	The total period will be range * pulse time in µS, so a pulse time
//	of 100 and a range of 100 gives a period of 100 * 100 = 10,000 µS
//	which is a frequency of 100Hz.
//
//	It's possible to get a higher frequency by lowering the pulse time,
//	however CPU uage will skyrocket as wiringPi uses a hard-loop to time
//	periods under 100µS - this is because the Linux timer calls are just
//	not accurate at all, and have an overhead.
//
//	Another way to increase the frequency is to reduce the range - however
//	that reduces the overall output accuracy...

#define	PULSE_TIME	100

static volatile int marks         [MAX_PINS] ;
static volatile int range         [MAX_PINS] ;
static volatile pthread_t threads [MAX_PINS] ;
static volatile int newPin = -1 ;


/*
 * softPwmThread:
 *	Thread to do the actual PWM output
 *********************************************************************************
 */

static void *softPwmThread (void *arg)
{
  int pin, mark, space ;
  struct sched_param param ;

  param.sched_priority = sched_get_priority_max (SCHED_RR) ;
  pthread_setschedparam (pthread_self (), SCHED_RR, &param) ;

  pin = *((int *)arg) ;
  free (arg) ;

  pin    = newPin ;
  newPin = -1 ;

  piHiPri (90) ;

  for (;;)
  {
    mark  = marks [pin] ;
    space = range [pin] - mark ;

    if (mark != 0)
      digitalWrite (pin, HIGH) ;
    delayMicroseconds (mark * 100) ;

    if (space != 0)
      digitalWrite (pin, LOW) ;
    delayMicroseconds (space * 100) ;
  }

  return NULL ;
}


/*
 * softPwmWrite:
 *	Write a PWM value to the given pin
 *********************************************************************************
 */

void softPwmWrite (int pin, int value)
{
  if (pin < MAX_PINS)
  {
    /**/ if (value < 0)
      value = 0 ;
    else if (value > range [pin])
      value = range [pin] ;

    marks [pin] = value ;
  }
}


/*
 * softPwmCreate:
 *	Create a new softPWM thread.
 *********************************************************************************
 */

int softPwmCreate (int pin, int initialValue, int pwmRange)
{
  int res ;
  pthread_t myThread ;
  int *passPin ;

  if (pin >= MAX_PINS)
    return -1 ;

  if (range [pin] != 0)	// Already running on this pin
    return -1 ;

  if (pwmRange <= 0)
    return -1 ;

  passPin = malloc (sizeof (*passPin)) ;
  if (passPin == NULL)
    return -1 ;

  digitalWrite (pin, LOW) ;
  pinMode      (pin, OUTPUT) ;

  marks [pin] = initialValue ;
  range [pin] = pwmRange ;

  *passPin = pin ;
  newPin   = pin ;
  res      = pthread_create (&myThread, NULL, softPwmThread, (void *)passPin) ;

  while (newPin != -1)
    delay (1) ;

  threads [pin] = myThread ;

  return res ;
}


/*
 * softPwmStop:
 *	Stop an existing softPWM thread
 *********************************************************************************
 */

void softPwmStop (int pin)
{
  if (pin < MAX_PINS)
  {
    if (range [pin] != 0)
    {
      pthread_cancel (threads [pin]) ;
      pthread_join   (threads [pin], NULL) ;
      range [pin] = 0 ;
      digitalWrite (pin, LOW) ;
    }
  }
}

主要实现在线程函数里面

其原理分析如下:
假如,你传的是是0和100,那么它的最大频率是100HZ,这个是怎么计算出来的呢:
根据官方说法:

// The PWM Frequency is derived from the “pulse time” below. Essentially,
// the frequency is a function of the range and this pulse time.
// The total period will be range * pulse time in µS, so a pulse time
// of 100 and a range of 100 gives a period of 100 * 100 = 10,000 µS
// which is a frequency of 100Hz.
//
// It’s possible to get a higher frequency by lowering the pulse time,
// however CPU uage will skyrocket as wiringPi uses a hard-loop to time
// periods under 100µS - this is because the Linux timer calls are just
// not accurate at all, and have an overhead.
//
// Another way to increase the frequency is to reduce the range - however
// that reduces the overall output accuracy…

这段话的意思是:因为系统原因,cpu只能做到100us粒度的延时,所以,也就是只能做到最大10K的频率。
如果我们在函数softPwmCreate()第2个和第3个参数传0和100,那么就是100x100us,也就是10x1000us=10ms=100HZ。
在这里插入图片描述
对应到这个图,就是当创建pwm对象的时候,高电平的时间是0,低电平的时间是100,也就是整个占空比是100,没有高电平,轮子不转,灯不亮。
那怎么去调制这个高电平的宽度呢?是通过softPwmWrite实现的。
在这里插入图片描述
在这个函数里面,这个函数是我们创建了软件pwm对象之后,给我们动态调制的。
当我们设置value=30(不能超过创建时候的范围)
那么就是高电平的时间是30/100 = 3ms,低电平的时间是7ms.
,当然你可以设置范围到1000,那么实际上这个值越大,周期是越长的,频率也就是越小的。
比如你设置softPwmCreate(,0,1000)
里面是这样的,1000*100us = 100ms=10HZ
,图是这样的,你可调整的范围更大了(softPwmWrite),但是你要等到100ms之后才能给第二次值
在这里插入图片描述

如何通过软件PWM实现三色灯的呼吸功能

因为上面原理性的东西已经介绍得很详细了,这里就只给大家源码,下面有注释

pi@xiajiashan:~/pi-c-blog$ cat -n soft_pwm_led.c 
     1  #include <wiringPi.h>
     2  #include <stdio.h>
     3  #include <stdlib.h>
     4  #define R       0   //wiringPi编码模式  0号脚---软件pwm
     5  #define G       2   //wiringPi编码模式  2号脚---软件pwm
     6  #define B       3   //wiringPi编码模式  3号脚---软件pwm
     7  int main()
     8  {
     9    int i;
    10    if(wiringPiSetup()<0)
    11    {
    12      exit(-1);
    13    }
    14    pinMode(R,OUTPUT);//设置R为输出
    15    pinMode(G,OUTPUT);//设置G为输出
    16    pinMode(B,OUTPUT);//设置B为输出
    17    softPwmCreate(R,0,100);
    18    softPwmCreate(G,0,100);
    19    softPwmCreate(B,0,100);
    20    while(1){
    21         //渐---亮
    22         for(i=0;i<100;i++)
    23         {
    24             softPwmWrite(R,i);//设置PWM脉冲宽度值
    25             softPwmWrite(G,i);//设置PWM脉冲宽度值
    26             softPwmWrite(B,i);//设置PWM脉冲宽度值
    27             delayMicroseconds(10000);//延时100ms
    28         }
    29         //渐---暗
    30         for(i=100;i>0;i--)
    31         {
    32             softPwmWrite(R,i);//设置PWM脉冲宽度值
    33             softPwmWrite(G,i);//设置PWM脉冲宽度值
    34             softPwmWrite(B,i);//设置PWM脉冲宽度值
    35             delayMicroseconds(10000);//延时100ms
    36         }
    37    }
    38    return 0;
    39  }
pi@xiajiashan:~/pi-c-blog$ 

END

想通过视频学习的可以购买我的视频教程:
https://edu.csdn.net/course/play/28051/383622?spm=1002.2001.3001.4143
希望本文能帮到各位,如果怕忘了就收藏吧!
第一课:什么是树莓派
第二课:基于树莓派的10个经典项目
第三课:购买您的第一个树莓派
第四课:如何安装树莓派系统
第五课:树莓派C语言编程手册
第六课:树莓派led控制
第七课:树莓派按键控制

猜你喜欢

转载自blog.csdn.net/qq_27320195/article/details/107535749