2020年1月28日 星期二

[RF] nRF24L01 2.4GHz 無線通訊(二) -- 加入兩個雙軸按鍵搖桿

本文旨在說明於 nRF24L01 的發射端加入兩個雙軸按鍵搖桿。我想使用左搖桿來控制水彈槍的 2 個伺服馬達和 1 個發射按鈕,使用右搖桿來控制小車的 4 個麥克納姆輪。



如果您還不熟悉 nRF24L01 模組基本的收發運作,請詳

[RF] nRF24L01 2.4GHz 無線通訊(一) -- 基本發射與接收



電路接線

發射端電路接線:

Arduino     nRF24L01    左搖桿     右搖桿
5V                                   5V            5V
3.3V           VCC
GND          GND            GND        GND
A0(註1)                          VRY     
A1(註1)                          VRX
A2(註1)                                           VRY
A3(註1)                                           VRX
D0                                   SW
D1                                                    SW
D7              CE
D8              CSN
D11            MOSI
D12            MISO
D13            SCK
(不接)        IRQ

註1:由於雙軸按鍵搖桿擺放的方式不同,會影響X軸與Y軸的配置,因此此處的腳位您需要配合自己的需求來調整。



接收端電路接線:

Arduino     nRF24L01
3.3V           VCC
GND          GND
D7              CE
D8              CSN
D11            MOSI
D12            MISO
D13            SCK
(不接)        IRQ



程式

發射端程式:

//RF_Transmitter01.ino
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>

// Define the digital inputs
#define jB1 0  // Joystick button 1
#define jB2 1  // Joystick button 2

unsigned long currentTime, previousTime;
unsigned long elapsedTime = 100;

RF24 radio(7, 8);   // nRF24L01 (CE, CSN)
const byte address[6] = "00001"; // Address

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
};

Data_Package data; //Create a variable with the above structure

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

  // Define the radio communication
  radio.begin();
  radio.openWritingPipe(address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);

  // Activate the Arduino internal pull-up resistors
  pinMode(jB1, INPUT_PULLUP);
  pinMode(jB2, INPUT_PULLUP);
 
  // Set initial default values
  data.j1PotX = 127; // Values from 0 to 255. When Joystick is in resting position, the value is in the middle, or 127. We actually map the pot value from 0 to 1023 to 0 to 255 because that's one BYTE value
  data.j1PotY = 127;
  data.j2PotX = 127;
  data.j2PotY = 127;
  data.j1Button = 1;
  data.j2Button = 1;

  previousTime = currentTime;
}

void loop() {
  // Read all analog inputs and map them to one Byte value
  data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into a BYTE value from 0 to 255
  data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255);
  data.j2PotX = map(analogRead(A3), 0, 1023, 0, 255);
  data.j2PotY = map(analogRead(A2), 0, 1023, 0, 255);

  // Read all digital inputs
  data.j1Button = digitalRead(jB1);
  data.j2Button = digitalRead(jB2);

  // Send the whole data from the structure to the receiver
  currentTime = millis();
  if (currentTime - previousTime > elapsedTime) {
    radio.write(&data, sizeof(Data_Package));
    previousTime = currentTime;
  }
}


接收端程式:

//RF_Receiver01.ino
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8);   // nRF24L01 (CE, CSN)
const byte address[6] = "00001";
unsigned long previousTime = 0;
unsigned long currentTime = 0;
unsigned long elapsedTime = 100;

// Max size of this struct is 32 bytes
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
};

Data_Package data; //Create a variable with the above structure

int  PotX1, PotY1, PotX2, PotY2;
byte But1, But2;

void setup() {
  Serial.begin(9600);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening(); //  Set the module as receiver
  resetData();
}

void loop() {
   // Check whether there is data to be received
  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package));

    PotX1 = data.j1PotX;
    PotY1 = data.j1PotY;
    But1 = data.j1Button;
    PotX2 = data.j2PotX;
    PotY2 = data.j2PotY; 
    But2 = data.j2Button; 
 
    Serial.print(PotX1); Serial.print(" ");
    Serial.print(PotY1); Serial.print(" ");
    Serial.print(But1);  Serial.print(" ");
    Serial.print(PotX2); Serial.print(" ");
    Serial.print(PotY2); Serial.print(" ");
    Serial.println(But2); 
  }
}

void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  data.j1PotX = 127;
  data.j1PotY = 127;
  data.j2PotX = 127;
  data.j2PotY = 127;
  data.j1Button = 1;
  data.j2Button = 1; 
}

在上傳發射端和接收端的程式之後,您可以打開接收端的 Serial Monitor(註2),您就可以看到如下圖所示。

註2:在打開 Serial Monitor 之前,或許您需要讓接收端成為目前的 COM Port。


理想中雙軸按鍵搖桿在置中時(不搖動它)所呈現的數值應該是 127,但由上圖我們可以觀察到它並非是這個值。雖然如此,它對於我們在控制伺服馬達和麥克納姆輪上面的影響似乎不大。

您可以搖動搖桿或按下搖桿上的按鍵,觀察 Serial Monitor 上的數值是否符合您的期望,如果不是,您可能要稍微調整一下電路接線(註1)。


相關網頁

https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/
https://howtomechatronics.com/projects/diy-arduino-rc-transmitter/


採購資訊

1100M nRF24L01+PA+LNA無線模組 https://goods.ruten.com.tw/item/show?21934545524992

雙軸按鍵搖桿 https://goods.ruten.com.tw/item/show?21628077440247

多功能雙軸按鍵搖桿固定板 https://goods.ruten.com.tw/item/show?21805664364959


2020年1月27日 星期一

[RF] nRF24L01 2.4GHz 無線通訊(一) -- 基本發射與接收

一般市售 nRF24L01 2.4G 無線通訊模組常見的有這兩款,其中一種板載天線,另一種外接天線,外接天線這款製造商號稱在無障礙情況下水平通訊距離可達 1100M。

本文僅在說明此模組的基本應用---基本發射與接收,後續將會說明如何製作一個可供麥克納姆輪遙控車使用的無線遙控發射、接收器,詳 https://pizgchen.blogspot.com/2020/01/rf-nrf24l01-24ghz.html




準備材料

請準備 Arduino Uno(或 Nano) 開發板和 nRF24L01 模組各 2 組。
其中一組作為發射端,另一組作為接收端。


電路接線

在電路接線前請務必先認識模組的腳位,如下圖



腳位說明如下 :

 腳位 說明
 VCC 3.3V
 GND Ground
 CE Chip Enable Tx/Rx
 CSN Chip Select Node
 SCK SPI ClocK
 MISO  Master In Slave Out (Send)
 MOSI Master Out Slave In (Receive)
 IRQ Interrupt ReQuest


接線:

Arduino            nRF24L01
3.3V(註1)        VCC
GND                GND
D8                    CSN
D7(註2)           CE
D11                  MOSI
D12                  MISO
D13                  SCK
(不接)              IRQ

註1:提醒您,此模組的電壓是 3.3V,切記不可接到5V。

註2:經實驗,如果依照下圖把 nRF24L01 的 CE 脚位接到 Arduino 的 D9 腳位,將無法運作。因此將腳位改接到 D7。




安裝程式庫

Step1 點擊 Arduino IDE 裡的下拉功能表 Sketch > Include Library > Manage Libraries...

Step2 在右上角落欄位裡輸入 nRF24L01


Step3 將滑桿往下拉找到「RF24」,並點擊「Install」鈕安裝它。


程式

發射端程式和接收端程式需要分別上傳到兩組 Arduino。如果您是使用同一部電腦連接兩組 Arduino,請注意在上傳程式前要切換 COM Port。

(以下程式碼摘自小狐狸事務所)


發射端程式:

//nRF24L01_Sender.ino
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); //指定 Arduino Nano 腳位對應 nRF24L01 之 (CE, CSN)
const byte address[6] = "00001";  //節點位址為 5 bytes + \0=6 bytes

int counter = 0; //Hello 計數器

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  radio.openWritingPipe(address);  //開啟寫入管線
  radio.setPALevel(RF24_PA_MIN);   //設為低功率, 預設為 RF24_PA_MAX
  radio.stopListening();  //傳送端不需接收, 停止傾聽
}

void loop() {
  const char text[32];  //宣告用來儲存欲傳送之字串
  sprintf(text, "Hello World %d", counter);  //將整數嵌入字串中
  Serial.println(text);
  radio.write(&text, sizeof(text));   //將字串寫入傳送緩衝器
  ++counter;
  delay(1000);
}


接收端程式:

//nRF24L01_Receiver.ino
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <printf.h>

RF24 radio(7, 8); //指定 Arduino Nano 腳位對應 nRF24L01 之 (CE, CSN)
const byte address[6] = "00001";  //節點位址為 5 bytes + \0=6 bytes

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  printf_begin();  //初始化 RF24 的列印輸出功能
  radio.openReadingPipe(0, address);  //開啟 pipe 0 之讀取管線
  radio.setPALevel(RF24_PA_MIN);  //設為低功率, 預設為 RF24_PA_MAX
  radio.startListening();  //接收端開始接收
  radio.printDetails();  //印出 nRF24L01 詳細狀態
  Serial.println("NRF24L01 receiver");
  Serial.println("waiting...");
}

void loop() {
  if (radio.available()) {  //偵測接收緩衝器是否有資料
    char text[32] = "";   //用來儲存接收字元之陣列
    radio.read(&text, sizeof(text));  //讀取接收字元
    Serial.println(text);
  }
}


發射端和接收端程式都上傳後,請開啟接收端的 Serial Monoitor (記得要切換到正確的 COM Port),將 Baud 調到 9600,如此您就可以看到如下圖,表示成功收發資料。



相關網頁

小狐狸事務所 http://yhhuang1966.blogspot.com/2017/10/arduino-nrf24l01.html
Wireless & IoT https://lastminuteengineers.com/nrf24l01-arduino-wireless-communication/


採購資訊

1100M nRF24L01+PA+LNA  無線通訊模組 https://goods.ruten.com.tw/item/show?21934545524992





2020年1月22日 星期三

[I2C] 掃描模組的 I2C 位址

我們都知道,很多設備或模組都可以利用 I2C 通訊方式連接在一起,這麼多設備或模組連接在一起需要有自己的位址以供識別。




當您拿到一塊模組時,如果忘了它的 I2C 位址,您可以利用以下的程式自動掃描出位址。


//I2C Scanner
#include <Wire.h>

void setup()
{
  Wire.begin();
  Serial.begin(9600);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}

void loop()
{
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);           // wait 5 seconds for next scan
}

[Display] Arduino 0.96吋 OLED 顯示器

這款 0.96吋 OLED 有白色和藍色兩款,雖然它尺寸有點小,但仍有 128x64 的畫素,可以顯示的訊息也不少,個人覺得把它用在小機器人身上倒是不錯的選擇。

它的通訊的方式是 IIC,如此讓 MCU 可以使用較少的腳位去跟它作溝通。
電路接線

Arduino            OLED
5V(註1)            VCC
GND                 GND
A5                     SCL
A4                     SDA

註1:這款 OLED 使用 3.3~5V 電壓都可以。


下載程式庫

Step1 下載 Adafruit GFX Library https://github.com/adafruit/Adafruit-GFX-Library

Step2 下載 Adafruit SSD1306 oled driver Library https://github.com/adafruit/Adafruit_SSD1306 (註2)

Step3 將下載回來的檔案都複製到 <Arduino>/libraries 資料夾裡,並分別更名為

Adafruit-GFX
Adafruit_SSD1306

Step4 退出 Arduino IDE 並重新開啟 IDE。


註2: 您也可以到 Library Manager 裡下載,輸入 OLED 關鍵字可以搜出一堆程式庫,如下


如果您稍微注意一下,可以看到類似的 SSD 程式庫有很多版本,這些版本都值得去研究,但在這裡我們就不多說了。


程式

請開啟範例程式 Files > Examples > Adafruit SSD1306 > ssd1306_128x64_i2c.ino

我們在最前面有提到,這塊 OLED 是使用 I2C 通訊,因此我們必須先找到它正確的通訊位址,您可以參考「掃描I2C位址 https://pizgchen.blogspot.com/2020/01/i2c-i2c.html」這篇文找到您的 OLED 正確的位址。

我的 OLED 通訊位址是 0x3C,因此我需要找到程式裡指定位址的地方,範例程式這裡是 0x3D,如下圖,


我需要把它改成 0x3C,完整程式如下:

/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x32 pixel display using I2C to communicate
 3 pins are required to interface (two I2C and one reset).

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source
 hardware by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

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

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

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

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C Address
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}


結果

觀看影片


相關連結

阿玉Maker研究區 https://sites.google.com/site/wenyumaker/09-oled-xian-shi-qi


採購資訊

0.96吋 OLED 顯示器 https://goods.ruten.com.tw/item/show?22004247243358