|
前言
无论是 arduino 还是 51 还是啥单片机或 STEAM 教育,很多的音乐方面的练习就是生硬的机械的刺耳的哔哔声,如果让单片机发出悦耳一点的声音多好啊。比如清脆美妙的音乐盒的声音。
经过多番搜索,功夫不负有心人,找到了一些相关的内容能够让 arduino 发出漂亮的声音。于是打算动手做一个音乐盒。
电子的音乐盒自然想让功能多一点。想到最早的留声机那美丽的外观,就连现在很多音乐盒也用的留声机的外观呢,那么我们的电子音乐盒也做成留声机外观吧。
既然都做成留声机外观了,那不如也做出可以更换唱片的功能呗。其实就是机械的音乐盒也有可以更换唱片的,看上去非常的昂贵哦,也有用纸带的,这两种方式都是通过打孔来制作音乐音符。那么我们发挥电子的优势,把打孔的音符简化为用笔涂黑,使用反射式红外传感器(就是循迹小车的循迹传感器)来检测音符,从而播放自己画出来的唱片的音乐。
材料 1. arduino pro mini 一块 2. 360度云台一个 3. 360度舵机一个 4. 喇叭一个,最好带功放 5. 电位器模块一个 6. 洞洞板 2 片 7. 反射式红外传感器8个 8. 2K可调电阻8个 9. 支架一套 10. 铜柱、螺丝若干 11. 导线等焊接材料 12. 排针及排母若干 13. 做装饰喇叭的牛皮纸一张 14. 做唱片的白纸若干
制作过程
- 音符传感器的制作
硬件小白,在制作的时候一直打退堂鼓,为了使做出来的外观像个留声机/唱片机,检测输入音符的这个“唱臂”也要做的细一些,不能直接用整块板,切这洞洞板就费了九牛之力,还要切成三片。这也算是平生第一次做的三层板了,还是立体的呢!把走线藏在里面,最后做出来的成品还算过得去,对于非专业的自己,对这个成果已经很满意了。
- 底座的制作
机械小白,看到论坛有云台硬件申请,看起来刚好像是一个唱片的机构,直接拿来当底座用,装个电位器用来控制转盘的旋转速度,以根据不同的曲风控制乐曲的播放快慢,加一个支架用来装“唱臂”,装喇叭。测试的时候用的 arduino UNO,为了把所有部件都塞进底座,最后组装的时候使用 arduino pro mini。为了连线,焊接了个洞洞板 IO 扩展板,这个其实非常简单,就是把排针往洞洞板上焊上就行了。
- 大喇叭的制作
留声机最大的特点就是有一个大喇叭,3D 小白,不会建模,就用纸张做一个喽。那天在垃圾桶发现丢弃的一个文件袋,这颜色还真是和留声机的铜喇叭有几分相似,就从垃圾桶里捡出了这个袋子,经过尝试,剪剪粘粘做出了一个大喇叭。当然,这仅仅是一个外观的装饰用的,如果真正的那个喇叭可以调整安装位置的话,这个装饰的喇叭倒是可以用来做个扩音的功能。
- 组装成品及绘制唱片
唱片根据云台的大小切个圆纸片,根据传感器的大小距离画出八个同心圆,然后用量角器等分圆。分的太细的话,对传感器来说精度和灵敏度要求比较高,最后还是用一个不那么细分的唱片来测试,上面画的是《小星星》的音符,才两句半的乐曲,将就一下吧……这里没有体现出可以同时发出多个音符的特点,实际上可以同时涂三个音哦。把大喇叭装上,拍一张唯美的照片,感觉还过得去啊。
注意传感器阵列那里每两个之间用了个黑纸隔开,以避免邻近的红外光的干扰。
在弄这个传感器的时候突然有一个大胆的想法,直接拿一个条码扫码器来扫描应该也是个好主意,精度可以更好呐!
程序流程
- 参考大神们的实现,调整和封装了一下音符播放的功能
- 读取传感器状态,播放对应的音符
- 读取电位器数据,控制转速
代码
这里附上两个代码,
一个是不需要唱片输入的程序内置音乐的程序,简单起见只固定一首乐曲,大家可以参考格式自己输入喜欢的音乐哦。这里的示例音乐是《千与千寻》的《与我同在》,挑的简单版本,没有很多的伴奏音,只有偶尔的双音,就这样已经让我手工输入了很久,保守估计40分钟吧,需要做一个工具来简化输入啊。网上参考来源的大神用的是 midi 文件导出的方式,我是为了做唱片输入,所以将格式做了调整,两份代码的音乐格式不通用哦。
另一个是使用手绘唱片来输入播放乐曲的代码,不过由于电机声挺大的,其实最终效果不怎么理想,仅仅是实现它,验证了一下想法。
大家可以使用第一个程序来做实验玩,只需要拿个喇叭一头接地一头接 UNO 的 11 引脚就可以开始了。
完整程序大家下载附件,下面展示主程序,头文件比较长影响版面就略过。
ArduinoMusicBoxFixed.zip
(9.13 KB, 下载次数: 46)
ArduinoMusicBox.zip
(8.11 KB, 下载次数: 22)
- 内置音乐版本的代码
[mw_shl_code=arduino,true]// ArduinoMusicBoxFixed.ino
// 实现一个 Arduino 音乐盒,内置固定音乐版本
// By seesea, 2019-03-20
//
// 很多的 arduino 示例做音乐播放或电子琴什么的,就只是一个 tone() 哔哔哔的声音,就想着怎样才能让 arduino 播放好听一点的声音呢。
// 在逛一些论坛的时候有人在讨论日本的一个大神做的 avr 程序播放的很好听,但是没有源码。
// 后来多番搜索有了一些收获,下面是几个比较相关的链接可以参考,按相关程度排序:
// 1. arduino 音乐盒播放(页面末有代码下载链接),本程序参考来源:
// https://www.nutsvolts.com/magazine/article/april2012_Lindley
// 2. arduino wav 声音播放器,arduino 联合创始人的文章,值得一看,上一链接作者的代码我看应该很大程度参考自这里:
// https://www.elektormagazine.com/files/attachment/331
// 3. makezine 的教学,浅显易懂,推荐学习:
// https://makezine.com/projects/ma ... no-sound-synthesis/
// 4. 日本大神的音乐盒,主要是原理介绍值得学习:
// http://elm-chan.org/works/mxb/report.html
// 5. 直接数字合成器,理论讲解很详细:
// http://www.cs.nott.ac.uk/~pszjm2/?p=674
// 6. 声音采样知识介绍:
// http://www.quintadicopertina.com ... ng-digitized-audio/
// 7. arduino 声音合成库,功能强大:
// http://sensorium.github.io/Mozzi
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/atomic.h>
#include "wavetableData.h"
#include "pitchs.h"
#include "songData.h"
#define AUDIO_OUTPUT_PIN 11
#define GENERATOR_COUNT 2
#define SAMPLE_RATE 32000
#define SUSTAIN_LENGTH 128
#define SHIFT_BIT_NUM 8
#define SENSOR_COUNT 8
#define POTENTIOMETER_PIN A0
#define MOTOR_PWM_PIN 6
const uint16_t WAVETABLE_LENGTH = sizeof(waveTableData) / sizeof(waveTableData[0]);
const uint16_t ENVELOPTABLE_LENGTH = sizeof(envelopeData) / sizeof(envelopeData[0]);
// 注意,使用8个传感器,如果不是,需要调整相关代码
// 功能按顺序为:高八度标记,音符 7 6 5 4 3 2 1
const uint8_t sensorPins[SENSOR_COUNT] = { 2, 3, 4, 5, 7, 8, 9, 10 };
// 声音合成器结构
// 包括:相位步进,相位累计,包络数据表的索引
// 后续如需扩展多乐器什么的,可以在这里增加乐器波表指针
struct
{
uint16_t phaseStep;
uint32_t phaseAccumulator;
uint16_t envelopeIndex;
} generators[GENERATOR_COUNT];
// 初始化
void systemInit()
{
// cli();
pinMode(AUDIO_OUTPUT_PIN, OUTPUT);
digitalWrite(AUDIO_OUTPUT_PIN, LOW);
initSensorPins();
for (int i = 0; i < GENERATOR_COUNT; i++)
{
generators.phaseStep = 0;
generators.phaseAccumulator = 0;
generators.envelopeIndex = 0;
}
initTimers();
// sei();
}
// 配置定时器
// 注意此函数调用前关中断
// 比较底层,直接照搬大神的
// 参考:
// https://www.nutsvolts.com/magazine/article/april2012_Lindley
// https://www.elektormagazine.com/files/attachment/331
void initTimers()
{
/*
* Timer 1 Configuration
* Set up Timer 1 to generate interrupt at SAMPLE_RATE.
*/
// Set CTC mode (Clear Timer on Compare Match)
// Have to set OCR1A *after*, otherwise it gets reset to 0!
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A).
// OCR1A is a 16-bit register, so we have to do this with
// interrupts disabled to be safe.
OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 32000 = 500
// Enable interrupt when TCNT1 == OCR1A
TIMSK1 |= _BV(OCIE1A);
/*
* Timer 2 Configuration
* Set up Timer 2 to do pulse width modulation at frequency of 62,500 Hz
*/
// Set fast PWM mode
TCCR2A |= _BV(WGM21) | _BV(WGM20);
TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A. Arduino pin 11
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler so PWM frequency is 16000000 / 256 or 62,500 Hz
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set initial pulse width to zero.
OCR2A = 0;
}
// 定时器 1 的中断处理函数
// 参考:https://www.nutsvolts.com/magazine/article/april2012_Lindley
ISR(TIMER1_COMPA_vect)
{
int32_t pwmSample = 0;
uint32_t phase;
uint32_t waveTableIndex;
int32_t sampleValue;
uint8_t envlopeValue;
for (uint8_t i = 0; i < GENERATOR_COUNT; i++)
{
phase = generators.phaseAccumulator; // 取出步进累计值
waveTableIndex = (phase >> SHIFT_BIT_NUM); // 去除低位做为波表的索引
sampleValue = (int8_t) pgm_read_byte(waveTableData + waveTableIndex);
envlopeValue = pgm_read_byte(envelopeData + generators.envelopeIndex);
pwmSample += sampleValue * envlopeValue; // 把波形值和包络相乘做为 pwm 的参数(需去除低位),注意的是这是所有的声音合成器的综合
// 累加相位步进值
// 如果到波表的结尾了,则开始增加包络的索引,进行声音大小的改变
generators.phaseAccumulator += generators.phaseStep;
if (generators.phaseAccumulator >= ((uint32_t) WAVETABLE_LENGTH) << SHIFT_BIT_NUM)
{
generators.phaseAccumulator -= (((uint32_t) SUSTAIN_LENGTH) << SHIFT_BIT_NUM);
if (generators.envelopeIndex < ENVELOPTABLE_LENGTH - 1)
{
generators.envelopeIndex++;
}
}
}
// 去除低位,留高位,缩小数值到波表的长度内
pwmSample >>= (SHIFT_BIT_NUM + 2);
// 加上偏移量避免负数后,设置给定时器 2
OCR2A = (uint8_t)(127 + pwmSample);
}
// 将音符频率换算为相位步进值
// 计算公式为:2^32 * 频率 / 采样率
// 2^32 来源是相位累加 generator_t.phaseAccumulator 是 32 位的
inline unsigned int hzToPhaseStep(float hz)
{
return (unsigned int) (hz * 4.096);
}
// 传入音符对应的频率来播放音符
void playPitch(uint16_t pitchFreq)
{
static int8_t generatorIndex = 0;
uint16_t step = hzToPhaseStep(pitchFreq);
++generatorIndex;
if (generatorIndex >= GENERATOR_COUNT) {
generatorIndex = 0;
}
// 注意关中断进行设置
cli();
generators[generatorIndex].phaseStep = step;
generators[generatorIndex].phaseAccumulator = 0;
generators[generatorIndex].envelopeIndex = 0;
sei();
}
// 初始化传感器引脚,设置为内部上拉的输入
void initSensorPins()
{
for (int i = 0; i < SENSOR_COUNT; ++i)
{
pinMode(sensorPins, INPUT_PULLUP);
}
}
// 目前限制在8个传感器,所以用8位的变量返回值
// 返回值的每一位表示一个传感器的状态
uint8_t getAllSensorsInput()
{
uint8_t sensorsStatus = 0;
for (int i = 0; i < SENSOR_COUNT; ++i)
{
sensorsStatus <<= 1;
if (digitalRead(sensorPins) == LOW)
{
sensorsStatus |= 0x01;
}
}
return sensorsStatus;
}
void readSensorAndPlayNote()
{
// 存放至少 16 个音符,这里为两个八度 C 大调音阶,可根据自己的乐曲定制相应的音符
static const uint16_t noteBox[] = {
NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3,
NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4
};
static uint8_t lastSensorStatus = 0;
uint8_t sensorStatus = getAllSensorsInput();
uint8_t pitchOffset = 0;
// 除了高八度标记位外的其它音符位没有变化的时候,认为还没扫描到下一次音符输入,不做任何操作
if ((0xEF & sensorStatus) == lastSensorStatus)
return;
// 高八度
if ((0x80 & sensorStatus) != 0)
pitchOffset = 7;
// 从低到高检查每一位标志来进行音符播放
sensorStatus &= 0xEF;
for (uint8_t i = 0; i < SENSOR_COUNT - 1; ++i)
{
if (sensorStatus & 0x01)
{
playPitch(noteBox[i + pitchOffset]);
}
sensorStatus >>= 1;
}
// 记录旧值用于下次比较
// 注意:sensorStatus 已经是 sensorStatus & 0xEF 了
lastSensorStatus = sensorStatus;
}
void playSong(const uint16_t *songAddr)
{
int index = 0;
uint16_t note = 0;
uint16_t beatTime = 0;
unsigned long lastTime = millis();
while (true)
{
delay(beatTime);
/*
Serial.println(millis());
Serial.println("-----------");
if (millis() - lastTime < beatTime)
continue;
*/
note = pgm_read_word_near(songAddr + index);
beatTime = pgm_read_word_near(songAddr + index + 1);
if (note < 0)
break;
playPitch(note);
index += 2;
lastTime = millis();
/*
Sefrial.println(note);
Serial.println(beatTime);
Serial.println(lastTime);
*/
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
systemInit();
playSong(songAlwaysWithMe);
}
void loop()
{
}
[/mw_shl_code] - 唱片式音乐盒代码
[mw_shl_code=arduino,true]// ArduinoMusicBox.ino
// 实现一个 Arduino 音乐盒
// By seesea, 2019-03-20
//
// 很多的 arduino 示例做音乐播放或电子琴什么的,就只是一个 tone() 哔哔哔的声音,就想着怎样才能让 arduino 播放好听一点的声音呢。
// 在逛一些论坛的时候有人在讨论日本的一个大神做的 avr 程序播放的很好听,但是没有源码。
// 后来多番搜索有了一些收获,下面是几个比较相关的链接可以参考,按相关程度排序:
// 1. arduino 音乐盒播放(页面末有代码下载链接),本程序参考来源:
// https://www.nutsvolts.com/magazine/article/april2012_Lindley
// 2. arduino wav 声音播放器,arduino 联合创始人的文章,值得一看,上一链接作者的代码我看应该很大程度参考自这里:
// https://www.elektormagazine.com/files/attachment/331
// 3. makezine 的教学,浅显易懂,推荐学习:
// https://makezine.com/projects/ma ... no-sound-synthesis/
// 4. 日本大神的音乐盒,主要是原理介绍值得学习:
// http://elm-chan.org/works/mxb/report.html
// 5. 直接数字合成器,理论讲解很详细:
// http://www.cs.nott.ac.uk/~pszjm2/?p=674
// 6. 声音采样知识介绍:
// http://www.quintadicopertina.com ... ng-digitized-audio/
// 7. arduino 声音合成库,功能强大:
// http://sensorium.github.io/Mozzi
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <util/atomic.h>
#include "wavetableData.h"
#include "pitchs.h"
#define AUDIO_OUTPUT_PIN 11
#define GENERATOR_COUNT 3
#define SAMPLE_RATE 32000
#define SUSTAIN_LENGTH 128
#define SHIFT_BIT_NUM 8
#define SENSOR_COUNT 8
#define POTENTIOMETER_PIN A0
#define MOTOR_PWM_PIN 6
const uint16_t WAVETABLE_LENGTH = sizeof(waveTableData) / sizeof(waveTableData[0]);
const uint16_t ENVELOPTABLE_LENGTH = sizeof(envelopeData) / sizeof(envelopeData[0]);
// 注意,使用8个传感器,如果不是,需要调整相关代码
// 功能按顺序为:高八度标记,音符 7 6 5 4 3 2 1
const uint8_t sensorPins[SENSOR_COUNT] = { 2, 3, 4, 5, 7, 8, 9, 10 };
// 声音合成器结构
// 包括:相位步进,相位累计,包络数据表的索引
// 后续如需扩展多乐器什么的,可以在这里增加乐器波表指针
struct
{
uint16_t phaseStep;
uint32_t phaseAccumulator;
uint16_t envelopeIndex;
} generators[GENERATOR_COUNT];
// 初始化
void systemInit()
{
cli();
pinMode(AUDIO_OUTPUT_PIN, OUTPUT);
digitalWrite(AUDIO_OUTPUT_PIN, LOW);
initSensorPins();
for (int i = 0; i < GENERATOR_COUNT; i++)
{
generators.phaseStep = 0;
generators.phaseAccumulator = 0;
generators.envelopeIndex = 0;
}
initTimers();
sei();
}
// 配置定时器
// 注意此函数调用前关中断
// 比较底层,直接照搬大神的
// 参考:
// https://www.nutsvolts.com/magazine/article/april2012_Lindley
// https://www.elektormagazine.com/files/attachment/331
void initTimers()
{
/*
* Timer 1 Configuration
* Set up Timer 1 to generate interrupt at SAMPLE_RATE.
*/
// Set CTC mode (Clear Timer on Compare Match)
// Have to set OCR1A *after*, otherwise it gets reset to 0!
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A).
// OCR1A is a 16-bit register, so we have to do this with
// interrupts disabled to be safe.
OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 32000 = 500
// Enable interrupt when TCNT1 == OCR1A
TIMSK1 |= _BV(OCIE1A);
/*
* Timer 2 Configuration
* Set up Timer 2 to do pulse width modulation at frequency of 62,500 Hz
*/
// Set fast PWM mode
TCCR2A |= _BV(WGM21) | _BV(WGM20);
TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A. Arduino pin 11
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler so PWM frequency is 16000000 / 256 or 62,500 Hz
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set initial pulse width to zero.
OCR2A = 0;
}
// 定时器 1 的中断处理函数
// 参考:https://www.nutsvolts.com/magazine/article/april2012_Lindley
ISR(TIMER1_COMPA_vect)
{
int32_t pwmSample = 0;
uint32_t phase;
uint32_t waveTableIndex;
int32_t sampleValue;
uint8_t envlopeValue;
for (uint8_t i = 0; i < GENERATOR_COUNT; i++)
{
phase = generators.phaseAccumulator; // 取出步进累计值
waveTableIndex = (phase >> SHIFT_BIT_NUM); // 去除低位做为波表的索引
sampleValue = (int8_t) pgm_read_byte(waveTableData + waveTableIndex);
envlopeValue = pgm_read_byte(envelopeData + generators.envelopeIndex);
pwmSample += sampleValue * envlopeValue; // 把波形值和包络相乘做为 pwm 的参数(需去除低位),注意的是这是所有的声音合成器的综合
// 累加相位步进值
// 如果到波表的结尾了,则开始增加包络的索引,进行声音大小的改变
generators.phaseAccumulator += generators.phaseStep;
if (generators.phaseAccumulator >= ((uint32_t) WAVETABLE_LENGTH) << SHIFT_BIT_NUM)
{
generators.phaseAccumulator -= (((uint32_t) SUSTAIN_LENGTH) << SHIFT_BIT_NUM);
if (generators.envelopeIndex < ENVELOPTABLE_LENGTH - 1)
{
generators.envelopeIndex++;
}
}
}
// 去除低位,留高位,缩小数值到波表的长度内
pwmSample >>= (SHIFT_BIT_NUM + 2);
// 加上偏移量避免负数后,设置给定时器 2
OCR2A = (uint8_t)(127 + pwmSample);
}
// 将音符频率换算为相位步进值
// 计算公式为:2^32 * 频率 / 采样率
// 2^32 来源是相位累加 generator_t.phaseAccumulator 是 32 位的
inline unsigned int hzToPhaseStep(float hz)
{
return (unsigned int) (hz * 4.096);
}
// 传入音符对应的频率来播放音符
void playPitch(uint16_t pitchFreq)
{
static int8_t generatorIndex = 0;
uint16_t step = hzToPhaseStep(pitchFreq);
++generatorIndex;
if (generatorIndex >= GENERATOR_COUNT) {
generatorIndex = 0;
}
// 注意关中断进行设置
cli();
generators[generatorIndex].phaseStep = step;
generators[generatorIndex].phaseAccumulator = 0;
generators[generatorIndex].envelopeIndex = 0;
sei();
}
// 初始化传感器引脚,设置为内部上拉的输入
void initSensorPins()
{
for (int i = 0; i < SENSOR_COUNT; ++i)
{
pinMode(sensorPins, INPUT_PULLUP);
}
}
// 目前限制在8个传感器,所以用8位的变量返回值
// 返回值的每一位表示一个传感器的状态
uint8_t getAllSensorsInput()
{
uint8_t sensorsStatus = 0;
for (int i = 0; i < SENSOR_COUNT; ++i)
{
sensorsStatus <<= 1;
if (digitalRead(sensorPins) == LOW)
{
sensorsStatus |= 0x01;
}
}
return sensorsStatus;
}
void readSensorAndPlayNote()
{
// 存放至少 16 个音符,这里为两个八度 C 大调音阶,可根据自己的乐曲定制相应的音符
static const uint16_t noteBox[] = {
NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3,
NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4
};
static uint8_t lastSensorStatus = 0;
uint8_t sensorStatus = getAllSensorsInput();
uint8_t pitchOffset = 0;
// 除了高八度标记位外的其它音符位没有变化的时候,认为还没扫描到下一次音符输入,不做任何操作
if ((0xEF & sensorStatus) == lastSensorStatus)
return;
// 高八度
if ((0x80 & sensorStatus) != 0)
pitchOffset = 7;
// 从低到高检查每一位标志来进行音符播放
sensorStatus &= 0xEF;
for (uint8_t i = 0; i < SENSOR_COUNT - 1; ++i)
{
if (sensorStatus & 0x01)
{
playPitch(noteBox[i + pitchOffset]);
}
sensorStatus >>= 1;
}
// 记录旧值用于下次比较
// 注意:sensorStatus 已经是 sensorStatus & 0xEF 了
lastSensorStatus = sensorStatus;
}
void setup()
{
systemInit();
Serial.begin(9600);
}
void loop()
{
readSensorAndPlayNote();
analogWrite(MOTOR_PWM_PIN, map(analogRead(POTENTIOMETER_PIN), 0, 1024, 0, 255));
}
[/mw_shl_code]
演示视频
参考文档
按与本制作相关程度排序
|
|