美文网首页
Arduino制作简易音乐节拍器(二) 多线程任务

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

作者: StormerX | 来源:发表于2020-03-27 00:20 被阅读0次

    在上一篇教程中,我们踩到一个坑,因为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

    相关文章

      网友评论

          本文标题:Arduino制作简易音乐节拍器(二) 多线程任务

          本文链接:https://www.haomeiwen.com/subject/pkyhuhtx.html