Arduino制作简易音乐节拍器(二) 多线程任务

在上一篇教程中,我们踩到一个坑,因为OLED的内容显示拖慢了节拍器的速度。
今天我们使用多线程任务来解决这个问题,把OLED的显示和节拍器计时发声分成2个线程,分别独立运行互不干扰。

什么叫多线程?那就先说什么是单线程。把代码都写到loop()函数里,顺序执行,后面的代码必须等前面的代码执行完毕才可以运行,包括delay(),后面的代码也要等着,这就是单线程。
多线程就是把2个或者更多的代码分别独立拿出去运行,彼此不用等待。

Arduino里执行多线程的方法有很多,今天就使用一个封装好的类库来轻松的实现多线程。

这个类库叫做SCoop, 代码从Github下载:https://github.com/fabriceo/SCoop

代码下载后解压,把其中的SCoop文件夹复制到Arduino的libraries文件夹。复制完毕后重启Arduino IDE使类库加载。

接下来,先搭建个简单的电路。

一个电位器,A0口接收它的电压值; 9号引脚和2号引脚pinMode设为OUTPUT,分别控制LED1和LED2的闪烁。


下面写一段代码,来实现LED1和LED2分别以不同的速率闪烁,同时,旋转电位器还可以调整LED1的闪烁速度,而LED2并不受影响。

 

#include <SCoop.h>//引入头文件
 
int speedTask1 = 0;

defineTask(Task1);//定义线程1
defineTask(Task2);//定义线程2

//Task1线程自己的的setup和loop
void Task1::setup()
{
  pinMode(9, OUTPUT);
}

void Task1::loop()
{
  digitalWrite(9, HIGH);
  
  //要注意,线程里应使用sleep()做延迟,这样只是该线程延迟不会影响其它。
  //如果使用delay()那么全局都会被阻塞延迟。
  sleep(speedTask1); 
  
  digitalWrite(9, LOW);
  sleep(speedTask1); //要用sleep()重要的事情说2遍
}


//Task2线程自己的的setup和loop
void Task2::setup()
{
  pinMode(2, OUTPUT); 
}
void Task2::loop()
{
  digitalWrite(2, HIGH);
  sleep(1000); //要用sleep()重要的事情说3遍
  digitalWrite(2, LOW);
  sleep(1000); 
}

 
 
void setup() {

  //执行多线程的setup(),此时线程并没有开始运行哦,
  //别看它的方法叫start(),其实叫setup或者ready更合适。
  mySCoop.start(); 
  
} 

void loop()
{
  //从电位器读取值,speedTask1影响task1中LED的闪烁速度
  speedTask1 = analogRead(A0);

  //多线程的loop()开始运行
  yield();
} 
 
怎么样,是不是感觉到多线程带来的好处了? 下面我们修改上一篇教程的代码,给节拍器也加上多线程。

我把改进版的代码贴在下面,代码都有注释。

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <SCoop.h>

//define---------------------------------------------------

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels  
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define BPM_MIN 20
#define BPM_MAX 240
#define PIN_BPM A0
#define PIN_BEEP 9
 

//end define---------------------------------------------------


//---------------------------------------------------
 
int bpm = 0;
char strBPM[3];

 

defineTask(Task1);//定义线程1
defineTask(Task2);//定义线程2


//Task1线程自己的的setup和loop
void Task1::setup()
{ 
}

void Task1::loop()
{  
  bpm = map(analogRead(A0), 0, 1023, BPM_MIN, BPM_MAX);
  
  itoa(bpm, strBPM, 10); //把数字转成字符串
 
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
 
  display.setTextSize(2);    
  display.setCursor(0, 10); //显示的坐标位置
  display.println(F("BPM: "));
 
  display.setTextSize(3); 
  display.setCursor(60, 10); //显示的坐标位置
  display.println(strBPM);
  display.display();      // Show text

  //稍作停顿,按理说这里不用停顿
  //不过好像是这款OLED驱动代码的问题
  //不停顿会有异常,下次换块OLED试试看
  sleep(100);    
}


//Task2线程自己的的setup和loop
void Task2::setup()
{ 
}
void Task2::loop()
{
  //BPM即每分钟有多少拍,那么一分钟(60000毫秒)除以BPM就是每拍的时间
  //再除以4,就是四分之一拍的时间长度
  float tick = 60000 / bpm /4;  
    
  tone(PIN_BEEP, 440);  //播放440Hz的声音        
  sleep(tick);          //播放时为四分之一拍
    
  noTone(PIN_BEEP); //停止播放声音
  sleep(tick*3);    //空四分之三拍的时间
}

//---------------------------------------------------



void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  } 
  // Clear the buffer 这里如果不清理buffer,默认的显示内容为Adafruit类库的LOGO
  display.clearDisplay();
  
  pinMode(PIN_BPM, INPUT);
  pinMode(PIN_BEEP, OUTPUT);

  delay(1000);
  
  mySCoop.start();
}

void loop() {
  
  yield();  

} 

测试结果,节拍信号速度正常准确。

好了,第二版的节拍器到这里就做好了。

改进内容:

  1. 使用多线程,将OLED内容显示和节拍器发音的工作分别分为2个独立线程运行。
  2. 修正了节拍速度不准确的问题。

现在节拍器速度正常,旋转调节电位器时候响应速度也快了。


又到了挖坑时间,现在提出更多的需求。

  1. 目前节拍器不能表现出节奏型,比如当前的拍子是3拍的还是4拍的?如何切换节奏型?
  2. 每小节第一拍的声音要有所区别。
  3. 节拍器没有开始和停止功能。
  4. BPM的值会因为电位器而抖动,即便没有拧动电位器。

喜欢自己动脑动手的小童鞋可以先自己试试去实现上述功能。下一篇我来讲讲这些需求如何实现,就先讲到这里啦 ヾ( ̄▽ ̄)ByeBye

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容