目录
- 主要内容
- 7.1 PWM的基本原理
- 7.2 基本PWM信号
- 周期
- 占空比
- 7.3 调节电压变化
- 7.4 模式输出(PWM)指令和默认频率
- 7.4.1 analogWrite()函数
- 7.4.2 analogWrite()函数语法
- 示例
- 7.5 pwm频率
- 7.5.1 默认频率
- 7.5.2 改变PWM的输出频率
- 任务1:调光器
- 任务2:随机数字与烛光效果
- 随机指令:
- 实验程序:
- 任务3: 通过串口调整灯光亮度
- 比如:Arduino收到字符串 '168\n'
- 活动4:按钮PWM控制LED亮度
- 实现说明
- 活动5:使用atoi()转换字符串成数字
- 实验说明
- 实验程序
- 3.7 补充阅读材料:
- 1. PWM
- 2. LED的正确接法
- 3. 电位器:
脉冲宽度调制或PWM是用于改变脉冲串中的脉冲宽度的常用技术。PWM有许多应用,如控制伺服和速度控制器,限制电机和LED的有效功率。
主要内容
- Arduino输出模拟信号,也就是可变电压输出。
- pwm技术原理及实现方法
- 利用不同技术实现pwm
7.1 PWM的基本原理
脉冲宽度调制基本上是一个随时间变化而变化的方波。基本的PWM信号如下图所示。

7.2 基本PWM信号
有很多术语与PWM相关:
- On-Time(导通时间) - 时间信号的持续时间较长。
- Off-Time(关断时间) - 时间信号的持续时间较短。
- Period(周期) - 表示为PWM信号的导通时间和关断时间的总和。
- Duty Cycle(占空比) - 它表示为在PWM信号周期内保持导通的时间信号的百分比。
周期
如图所示,Ton表示导通时间,Toff表示信号的关断时间。周期是导通和关断时间的总和,并按照以下公式计算: T t o t a l = T o n + T o f f T_{total}=T_{on}+T_{off} Ttotal=Ton+Toff
占空比
占空比用于计算为一段时间的导通时间。使用上面计算的周期,占空比计算为:
D = T o n T o n + T o f f = T o n T t o t a l D=\frac{T_{on}}{T_{on}+T_{off}}=\frac{T_{on}}{T_{total}} D=Ton+ToffTon=TtotalTon
7.3 调节电压变化
- 改变电路中的电压。
- 缺点:体积增大,电能变成热能
- 省电又环保的PWM变频技术
- 称为:脉冲宽度调制(Pulse Width Modulation)
- 通过改变脉冲宽度,将能仿真模拟电压高低变化的效果。
- 公式:
模 拟 输 出 电 压 = 脉 冲 宽 度 × 高 电 平 值 ⇒ 输 出 电 压 高 电 平 值 = 开 启 时 间 百 分 比 模拟输出电压 = 脉冲宽度 \times 高电平值 \Rightarrow \frac {输出电压}{高电平值} = 开启时间百分比 模拟输出电压=脉冲宽度×高电平值⇒高电平值输出电压=开启时间百分比
7.4 模式输出(PWM)指令和默认频率
7.4.1 analogWrite()函数
- analogWrite()函数将模拟值(PWM波)写入引脚。它可用于以不同的亮度点亮LED或以各种速度驱动电机。在调用analogWrite()函数之后,引脚将产生指定占空比的稳定方波,直到下一次调用analogWrite()或在相同引脚上调用digitalRead()或digitalWrite()。大多数引脚上的PWM信号频率约为490Hz。在Uno和类似的板上,引脚5和6的频率约为980Hz。Leonardo上的引脚3和11也以980Hz运行。
- Arduino 的模拟输入(analogRead)的范围值介于0~1023之间,而模拟输出(analogWrite)介于0 ~ 255。
- 在大多数Arduino板上(ATmega168或ATmega328),此功能在引脚3,5,6,9,10和11上工作。在Arduino Mega上,它在引脚2-13和44-46上工作。旧的Arduino ATmega8板仅支持引脚9,10和11上的 analogWrite()。
- Arduino Due支持引脚2至13以及引脚DAC0和DAC1上的 analogWrite()。与PWM引脚不同,DAC0和DAC1是数模转换器,用作真正的模拟输出。
- 在调用analogWrite()之前,不需要调用pinMode()将引脚设置为输出。
7.4.2 analogWrite()函数语法
analogWrite ( pin , value ) ;
//value − the duty cycle: between 0 (always off) and 255 (always on).
value - 占空比:0(始终导通)到255(始终关断)之间。
示例
int ledPin = 9; // LED connected to digital pin 9
int analogPin = 3; // potentiometer connected to analog pin 3
int val = 0; // variable to store the read valuevoid setup() {pinMode(ledPin, OUTPUT); // sets the pin as output
}void loop() {val = analogRead(analogPin); // read the input pinanalogWrite(ledPin, (val / 4)); // analogRead values go from 0 to 1023, // analogWrite values from 0 to 255
}
例如:
analogWrite(5, 168);
上面的指令将使第5引脚输出3.3V电压,因为 168 255 × 5 ≈ 3.3 \frac {168}{255}\times {5} \approx 3.3 255168×5≈3.3
7.5 pwm频率
7.5.1 默认频率
Arduino控制板预设采用1kHz 和 500Hz两组不同的PWM输出频率
- 引脚5、6:976.5625Hz
- 引脚3、11、9、10:490.196Hz
- 若PWM频率太低,电机会抖动,LED会闪烁
- 人耳可以感受20kHz以内的频率,为避免听到电机的抖动音,应调高PWM的输出频率
7.5.2 改变PWM的输出频率
Arduino的PWM输出频率是由ATmega微处理器内部的三个系统定时器:
- (Timer0~Timer2)决定的,Timer0(5、6)、Timer1(9、10)、Timer2(3、11),通过改变定时器的设置,来调整PWM的输出频率。
- Arduino的delay()、millis()、micros()等函数的基准时间,来自Timer0,若调整此定时器,将导致这些函数的延迟时间错乱。
下面的程序语句将Timer1(9、10端口输出)的PWM频率调整成31250Hz。
void setup() {TCCR1B = TCCR1B & 0b11111000 | 0x01;
}
关于参数的详细说明以及频率对照表
任务1:调光器
- 实验说明: 利用电位器的输入信号变化来调整LED亮度
- 实验材料:
| 物品 | 数量 |
|---|---|
| LED | 1个 |
| 电位器 | 1个 |
线性电位器是一个模拟量的电子元器件,模拟量和数字量有什么区别呢?简单的说,数字量只有0和1两种状态,对应的就是开和关,高电平和低电平。而模拟量则不一样,他的数据状态呈现线性状态例如1到1000。
我们将电位器接入了arduino控制板的A0模拟量检测口,arduino的模拟接口能够测量0-5V的电压,对应的返回值为0-1024,对电压变化的测量精度相对较高。
注意:本次试验我们使用的是精密线性电位器,电位器最好选用绕线的精密线性电位器,因为市面上的廉价的非线性电位器做这种实验时,数值漂移大,容易造成led闪烁,电阻成非线性变化,亮度变化不明显,容易产生和按钮调光实验那种层级感,影响试验效果。
3. 实验程序
byte potPin = A0;
byte ledPin = 11;
int potSpeed = 0;
byte val = 0;
void setup() {pinMode(ledPin OUTPUT);
}void loop() {potSpeed = analogRead(potPin);val = map(potSpeed, 0, 1023, 0, 255);analogWrite(ledPin, val);
}
任务2:随机数字与烛光效果
实验说明: 随机, 通过随机调整接在数字11端口的LED亮度,以及随机持续时间来模拟烛光效果。
随机指令:
random
byte rnd = random(200); //从0~199之间挑选一个数字,存入rnd
byte rnd = random(20, 50); //从20~49之间挑选一个数字,存入rnd
然而,Arduino每次挑选的数字并不是那么随意,为了提高不重复的比率,在每次执行random()函数之前,先执行randomSeed()函数,
实验程序:
byte ledPin = 11;
void setup() {pinMode(ledPin, OUTPUT);randomSeed(analogRead(A5); //空接端口的读取值很不稳定,适合于做randomSeed()参数。
}void loop() {analogWrite(ledPin, random(135) + 120);delay(random(200));
}
任务3: 通过串口调整灯光亮度
实验说明: 接收用户输入的0~255数值来改变接在11引脚的LED亮度。
用户通过按键的输入值是字符串格式,而模拟输出指令所需要的参数是数字格式。有两种方法把字符或字符串转换成数字。
- 将ASCII码减掉48。数字0的十进制ASCII码是48
‘2’ - ‘0’ ⇒ \Rightarrow ⇒ 50 - 48 ⇒ \Rightarrow ⇒ 2
‘2’ - 48 ⇒ \Rightarrow ⇒ 50 -48 ⇒ \Rightarrow ⇒ 2 - 使用atoi()函数指令,把字符串转换成数字。
比如:Arduino收到字符串 ‘168\n’
- 处理第一个字:
pwm = pwm * 10 + (_in - '0') - 处理第二个字:
pwm = pwm * 10 + (_in - '0') - 处理第三个字:
pwm = pwm * 10 + (_in - '0') - 读取到字符’\n’,转换完毕!
由于每次传递的数字长度不确定(如: ‘3’ 和‘168’),为了让接收端确认一串数字的结尾,在发送数据的后面加上‘\n’字符。
实验程序:
byte ledPin = 11;
void setup() {Serial.begin(9600);
}void loop() {int pwm = 0;byte _in;if (Serial.available()) {_in = Serial.read();while (_in != '\n') {if (_in >= '0' && _in <='9') {pwm = pwm * 10 + (_in - '0');}_in = Serial.read();}if (pwm >255) pwm = 255;analogWrite(ledPin, pwm);}
}
活动4:按钮PWM控制LED亮度
实现说明
setup()函数只需声明9号引脚为输出引脚,其他什么都不做。
调用analogWrite()函数(在loop函数内)需要两个参数:第一个为要操作的引脚,第二个时要写出的PWM 脉冲宽度 值。
为了让LED的熄灭和点亮有渐变效果,你需要逐渐将PWM值从0(全部时间不通电)逐渐增加到255(全部时间通电)。然后,再从255调到0,以完成一轮亮度渐变。在下方的工程中,PWM值用一个名为brightness的变量存储。loop函数每执行一次,就将brightness变量的值与fadeAmount变量的值相加,并将和再赋给brightness变量。
当brightness变量的值是0或255时,如果发现fadeAmount值为-5,它就应被设为5;若为5则设为-5。如此一来,下次进入循环时亮度就会跟着由弱转亮或由亮转弱。analogWrite()函数能极其快速的改变PWM值,因此在loop函数最后的delay控制了渐变速度。试试看改变delay的时间,看看它如何影响渐变效果。
代码:
int n=0;
void setup ()
{ pinMode(4,INPUT); pinMode(6,OUTPUT); //该端口需要选择有#号标识的数字口 pinMode(10,INPUT);
}
void loop()
{ int up =digitalRead(4); //读取4号口的状态 int down = digitalRead(10); //读取10号口的状态 if (up==HIGH) //判断4号口目前是否是高电平 { n=n+5; //每次累加值为5 if (n>=255) { n=255; } //限定最大值为255 analogWrite(6,n); //使用PWM控制6号口输出,变量n的取值范围是0-255 delay (300); } if (down==HIGH) //减少亮度 { n=n-5; if (n<=0) { n=0; } analogWrite(6,n); delay (300); }
}
活动5:使用atoi()转换字符串成数字
实验说明
先声明一个空字符串变量,每次收到的新字符,存入字符串变量(data),以及变量i,每次接收到新的字符,就将它存入字符串变量,最后通过atoi()转换。
实验程序
byte ledPin = 11;
void setup(){Serial.begin(9600);Serial.println("LED ready:");
}void loop() {int pwm;char data[4];byte i = 0;char chr;if (Serial.available()) {while ((chr = Serial.read()) != '\n') {if ( chr >= '0' && chr <= '9' && i < 3) {data[i] = chr;i++;}}data[i] = '\0';pwm = atoi(data);if (pwm > 255) pwm = 255;Serial.print("PWM: ");Serial.println(pwm);analogWrite(ledPin, pwm);}
}
3.7 补充阅读材料:
1. PWM
《LED亮度渐隐》你可以在Arduino IDE文件->工程库->示例程序->Analog菜单( File->Sketchbook->Examples->Analog)
LED亮度渐隐的例子向你展示如何通过PWM来输出近似的模拟信号。

脉宽调制技术(PWM)是通过数字信号的快速切换来获取近似的”模拟信号”的一种方式。数字信号有一个特点:它只有两个状态,高电平和低电平,因而它的输出在示波器上看起来不会有波动,都是方形的,因而我们把数字信号输出产生的数字信号称为方波。
方波是一种非正弦曲线的波形,通常会与电子和讯号处理时出现。理想方波只有“高”和“低”这两个值。电流或电压的波形为矩形的信号即为矩形波信号,高电平在一个波形周期内占有的时间比值称为占空比,占空比为50%的矩形波称之为方波。方波有低电平为零与为负之分,必要时,可加以说明“低电平为零”、“低电平为负”。【百度百科】
数字信号的快速开关可以产生近似的模拟信号。当一直处于打开状态时就是5V,当一直关闭时就是0V。通过改变固定一小段时间内高电平和低电平占的时间,我们就可以输出0V到5V的等效电压,也就是等效的模拟信号了。在这一小段时间内高电平所占的时间就叫做脉冲宽度,简称脉宽。为了获取不同的等效电压,你可以改变脉冲宽度(就是上面所说的改变固定一小段时间内高电平和低电平占的时间),对于脉冲宽度的调整就是所谓的脉宽调制技术(PWM)。 对引脚进行PWM操作,将PWM值从0逐渐调整到255,就相当于LED那条电路上的总电压从0V渐渐调整到了5V,LED的亮度也由最暗变最亮。
在这张图表中,绿色的线代表PWM的时间间隔(上文所提到的固定的一小段时间)。这个最大长度的倒数就是PWM的频率(频率等于时间的倒数)。因此,假如时间间隔是2ms,那么PWM频率就是500Hz左右。例如PWM频率为500Hz左右,则时间间隔是2ms。analogWrite() 输出的是0-5v的等效电压。若传入一个0-255的某个整数就代表引脚上将输出0V到5V的某个等效电压,例如
- analogWrite(255) 代表5V,高电平将占据100%的时间间隔。
- analogWrite(127) 就表示50%的时间间隔被高电平占据。
请将《LED亮度渐隐》一文中的例子下载到板子上。当例程开始运行后,请来回摇晃Arduino板。这样做的目的是为了让你不用示波器也能真切的看到PWM的真实面貌。因为人眼的视觉暂留让你在晃动过程中将点亮的板载LED看成连续的线条。当你对LED进行PWM操作时,这些线条的长度会根据脉宽变长变短。这样你就更加能体会到PWM的实质啦!
2. LED的正确接法
LED的工作电压约2V,但Arduino微电脑的输出电压是5V,我们应该在Arduino的输出和LED之间连接一个限流电阻,但有时我们把LED直接接在第13脚,这是因为Arduino处理器的输出电流不高(每个脚位可输出20mA,所有引脚最多40mA),第13引脚内接一个1kΩ电阻,因此不会烧毁LED。在实际的操作中,请串联一个220Ω~1kΩ的电阻,连接方式有:源流(source current)和潜流(sink current)两种。源流是当引脚的值为1时,电流由微处理器流出,经组件后至地端。潜流是当引脚的输出状态为0时,电流由Vcc流出,经组件后进入微处理器。无论哪一种接法,都要确认不要从微处理器流出或流入40mA以上的电流,否则该引脚可能会损毁。
3. 电位器:
在许多电工电子线路中,为了调节电流、电压的大小,改变控制电路的电性能,一般可使用可变电阻器(也称电位器、变阻器)来实现,通过调节改变电路中的电阻值来达到控制电流或电压的目的。 一般电位器有三只引脚,如图所示的是碳膜电位器内部结构图。其中中间引脚连接滑动片,其余两脚为固定臂,当滑动片在碳膜上滑动时,即可获得变化的电阻值,其阻值可在一定范围内连续可调。