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











2019年12月28日 星期六

[WS2812] 1-Bit WS2812 RGB LED

很久以前有做過 8-Bit WS2812 RGB LED,最近有朋友跟我回應不知道如何做 1-Bit WS2812 RGB LED,今天趁著假日閒閒沒事做來解決這個問題。



準備材料

1. 1-Bit WS2812 RGB LED  燈珠
2. 公排針 3P
3. 杜邦線母母頭 3P



焊接

取出燈珠和公排針將他們焊在一起。

在焊接之前請您注意下列事項:
1. 由於 WS2812 這類的 LED 燈珠可以多顆串接使用,它們在數據傳輸方面都有一個方向性,Din 是輸入端;Dout 是輸出端。
2. 燈珠和公排針在平躺時沒有等高,所以我隨手取一片 3mm 的壓克力墊著,您也可以拿其他東西墊。



另外,為了可以多顆續接燈珠,您也可以拿 3P 母排針焊在燈珠的 Dout 端,不過我沒有在這裡示範。建議您可以自行練習,多焊幾組燈珠串接在一起。


電路接線

Arduino     燈珠
5V              5V
GND          GND
D6              Din


下載與設定函式庫

2. 將它解壓縮,複製到 Arduino IDE 路徑下的 libries 資料夾裡,並更名為 Adafruit_NeoPixel。


程式

這個函式庫裡面有很多不錯的範例程式,我們只要找到  strandtest.ino 這個程式,用它來測試燈珠就可以了。

1. 在 Arduino IDE 裡用滑鼠點擊 File > Examples > Adafruit NeoPixel > strandtest。


2. 在程式碼裡找到這一行,確定燈珠是要接到 Arduino 的脚位 6。

#define LED_PIN    6

3. 在程式碼裡找到這一行

#define LED_COUNT   60

由於我們現在只接 1 顆燈珠,所以要將它改成

#define LED_COUNT   1

4. 現在您可以上傳程式了。


觀看影片



採購資訊

1. 1-Bit WS2812 RGB LED https://goods.ruten.com.tw/item/show?21721367976483



2019年12月18日 星期三

[機構] DIY推桿(2)

我們在 前一篇文 介紹了一款伺服馬達機構,簡易做出了具有稍稍精確行程的推桿,
本篇文將繼續介紹它的進化版本。


這款進化版本改良了它的推桿穩定性,在推桿中間開挖了一條導槽,使得推桿在運動時更加順利穩定。



材料介紹

此部分是為了一位台大的學生寫的,他說他不認識螺帽,無法分辨。
1. 螺絲規格可以用尺量螺桿粗細和長度,目視的話粗的是 M3,細的是 M2。
2. 螺帽規格可以用尺量螺帽孔洞直徑,目視的話大的是 M3,小的是 M2。
3. 如果想暸解更多螺絲性質,例如螺頭外觀有區分為平頭、圓頭...,螺牙有區分為粗牙與細牙,還有材質成分是一般金屬或不銹鋼做的(不鏽鋼還有區分為很多成分組成),還有表面處理是鍍鋅、鍍鎳還是鍍黑,最重要的還有它的機械性質...,建議可以去看一下機械便覽維基百科

提醒您:如果經過上述方法還是無法幫助您認識螺絲、螺帽,表示這個商品不值得您購買,建議您跟賣家溝通辦理退貨退款。

4. 壓克力顏色有很多種,它還可以區分為不透明、半透明和全透明。本套件壓克力雖然標示厚度為 3mm,但市售品通常不會做足到 3mm,甚至有些只有 2.7mm。如果您認為厚度不足是賣家欺騙您,建議您可以跟賣家溝通辦理退貨退款。如果想瞭解更多壓克力,建議可以去看一下維基百科

5. 伺服馬達也稱為舵機,如果您認為商品說明它可以轉180度,但實際上卻不到180度,建議您可以跟賣家溝通辦理退貨退款。這個東西更複雜也更專業了,請原諒我無法花太多篇幅詳細說明,如果想瞭解更多伺服馬達,建議可以去看一下維基百科

再次提醒您:如果經過上述說明,您還是有一丁點不能理解,表示這個商品不值得您購買,建議您跟賣家溝通辦理退貨退款。



組裝步驟

提醒您:
1. 請先移除所有壓克力雙面的保護紙膜,以利推桿順利運動。撰寫本文時,因壓克力是透明或黑色的不易顯示組裝細節,因此我們才保留保護紙膜。

2. 請先將伺服馬達設置到 90 度狀態,詳細步驟可參考 http://pizgchen.blogspot.com/2018/07/blog-post.html



Step1 準備壓克力主板、 MG90S 伺服馬達、2只 M2 螺絲和 2只 M2 螺帽。


將 MG90S 伺服馬達置入矩形孔,用螺絲和螺帽固定。

提醒您:此處務必要將螺絲鎖緊(甚至可以點上膠水),避免日後鬆動還得將機構拆開才能鎖緊螺絲。



Step2 準備齒輪壓克力、伺服馬達塑膠搖臂、搖臂固定螺絲和 2只自攻螺絲。


用 2只自攻螺絲將搖臂固定到齒輪上面。

提醒您:自攻螺絲是舵機自附的,因螺絲比較粗而且塑料搖臂比較硬,所以需要用力鎖,但須注意避免螺絲突牙或是讓自己受傷。


這是鎖好後的背面。


使用 M2.5 螺絲將搖臂與齒輪安裝到伺服馬達上面並鎖緊。

提醒您:在將齒輪放置到伺服馬達上面時,建議讓搖臂與推桿方向成垂直角度,如此可以讓我們知道伺服馬達大概轉了幾度,並且也易於安裝推桿的位置。



Step3 準備 3只推桿導板和 2只 M2 螺絲。


依圖示用螺絲將 2只小導板夾住大導板固定。

提醒您:這 2只小導板外形是一樣的,但它的螺絲孔大小不一樣,螺絲方向必須從鬆孔穿過到緊孔。這部份由於是利用壓克力的緊孔來結合固定,因此需注意不要鎖緊過頭,以至於破壞緊孔的鎖固能力。


將導板安置到主板上



Step4 準備 4只尼龍柱、4只 M3 螺絲和推桿。


用螺絲將尼龍柱鎖緊固定,並將推桿安置到導板上方。

提醒您:在將推桿安置到導板上方時,請盡量將推桿中間對準搖臂。




Step5 準備蓋板和 4只 M3 螺絲,用螺絲將蓋板固定到尼龍柱上鎖緊。


這是完成圖。





提醒您:
1. 主板上其它螺絲孔可以讓您將推桿組件用 M3 螺絲固定到其它地方。
2. 程式部份請詳 前一篇文



採購資訊

推桿 https://goods.ruten.com.tw/item/show?21937759056891

2019年12月9日 星期一

[18650 電池盒] 行動電源盒 DIY


1:行動電源盒裝配的3個主要部件是電池、電路板、外殼。 

A)裝配前最好能測量好電壓,電池電壓相差最好不要超過0.3V,(一般新電池的電壓斗毆控制在3.7V左右)每個電池的電壓不要低過3.0V。

B)每個電池都有正,負極之分。不能插反,否則會燒電路板。
電池應並聯,即正極都對充電寶的彈片,負極都對彈簧。
警告:不能一正一反,那樣會直接短路,導至起火!直接結果是彈簧有時會燒斷,外殼塑料也能看到燒熔!這種情況不屬保修範圍。


電池的負極是平的,沒有小的孔

C)新舊電池不能混裝,容量不同的電池不能混裝,好壞電池不能混裝。這3種情況都會損壞電池,損壞電路板,均不屬保修範圍,大家一定要注意。

D)蓋蓋板,因為設計外殼和內框較緊,在裝配時先按下二頭,再壓進中間。

2:有關電池裝配成行動電源盒後能放出多少電量為正常?

一. 實際功率
目前市售的移動電源的電芯標准電壓一般都維持在3.7V左右,因此10000毫安的移動電源實際功率為3.7V×10000mAh=37000Wh,而手機充電的電壓為5V,根據能量守恆定律,在移動電源將輸出電流升壓為5V的狀態下,該移動電源的電容為37000Wh÷5V=7400mAh。

二. 轉化率
這裡的7400毫安並非手機所得的最終能量,在移動電源為手機充電的過程中,電路板升壓及安全芯片的運行等情況都會損耗一部分電量,因此移動電源的轉化率一般為85%左右,按照85%計算,該移動電源的實際輸出最大電量為7400mAh×85%=6290mAh。
簡單總結就是電池放電到手機的的容量打6折是正常狀態,當您裝上電池到充電寶後,放出的電量沒電池標稱的容量是正常的,一般不低如6折就是對的!

3)行動電源盒的充電時間?

一.充電器對行動電源盒充電,您裝好充電寶後,先用充電頭對行動電源盒充電.一般充8小時左右就能充滿.充電時間長點沒關系.電路會自動控制的,不會損壞行動電源盒。

二.充滿電後,就可以向手機或其他電器產品充電了.您的行動電源盒就能正常使用了!


產品規格:
輸入電壓:MicroUSB 5.0V±0.5V
輸入電流:1000mA
輸出電壓:USB 5.0V±0.5V
輸出電流:1000mA
保護功能: 短路、過流、過充、過放、欠壓保護