在前篇文中( http://pizgchen.blogspot.tw/2017/03/arduino-1.html )我們說明了 Arduino 有哪些類型的記憶體,其中有提到一個關鍵字「PROGRAM」,本文旨在說明如何使用 PROGRAM 這個變數修飾符。
使用 PROGRAM 的時機
Arduno UNO 只有 2k bytes 的 SRAM 空間讓您存放及運作變數,若是您要將大量的訊息送到序列監控視窗顯示出來,或是您要設置一個供給查表法使用的大資料量表格,這時您有可能會使用超過 2k bytes 的空間。
在您使用超過 2k bytes 的 SRAM 空間時,也許編譯時沒有異狀,但這並不表示您的程式能夠正常運作。
要解決 SRAM 空間不足的問題,我們可以將這些大量資料從 SRAM "搬到" Flash (程式碼就是儲存在 Flash),在需要運作變數時再將這些資料從 Flash "搬回" SRAM 內。那麼如何讓 Arduino 知道哪些資料將會被這樣使用,答案就是使用 PROGRAM 這個變數修飾符來宣告。
如何使用 PROGRAM
PROGRAM 變數修飾符是被定義在 pgmspace.h 之中,它是 AVR 架構中 pgmspace.h 函式庫眾多函式的其中之一。所以您要使用 PROGRAM 之前,必須在程式的開頭包含入這個函式庫,如下:
#include <avr/pgmspace.h>
然後,使用下列句法給值
const dataType variableName [ ] PROGRAM = {data0, data1, data2...};
dataType - 表資料型態。
variableName - 表變數名稱。
PROGRAM 只是一個變數修飾符,它沒有被規定應該放在哪個位置,所以下列三種宣告方式都可以通過編譯器的解析:
const dataType variableName [ ] PROGRAM = {data0, data1, data2...};
const PROGRAM dataType variableName [ ] = {data0, data1, data2...};
const dataType PROGRAM variableName [ ] = {data0, data1, data2...};
雖然 PROGRAM 可以使用在單一變數上,但大多情況我們會用它來處理大量的資料,尤其是大量的陣列資料。下列表格讓您瞭解與比較不同的資料型態會佔用多少記憶體:
再提醒您一次,我們需要兩個步驟來使用 PROGRAM,首先用 PROGRAM 宣告一個變數並給值,然後要使用這些變數的值時再將它從 Flash 讀回到 SRAM。
範例
下列程式片段在示範如何讀寫 char (1 byte) 和 int (2 bytes) 到 PROGRAM。
#include <avr/pgmspace.h>
// save some unsigned ints
const PROGMEM uint16_t charSet[] = { 65000, 32796, 16843, 10, 11234};
// save some chars
const char signMessage[] PROGMEM = {"I AM PREDATOR, UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
unsigned int displayInt;
int k; // counter variable
char myChar;
void setup() {
Serial.begin(9600);
while (!Serial);
// put your setup code here, to run once:
// read back a 2-byte int
for (k = 0; k < 5; k++)
{
displayInt = pgm_read_word_near(charSet + k);
Serial.println(displayInt);
}
Serial.println();
// read back a char
int len = strlen_P(signMessage);
for (k = 0; k < len; k++)
{
myChar = pgm_read_byte_near(signMessage + k);
Serial.print(myChar);
}
Serial.println();
}
void loop() {
// put your main code here, to run repeatedly:
}
字串陣列
將資料設置為字串陣列通常是最方便使用的,尤其是要將大量的文字顯示到 LCD 之中時。因為字串本身就是一個字元陣列,而字串陣列實際上就是一個二維的字元陣列。
傾向於將大型的結構化資料放到 Flash 之中,往往是比較好的方式。下列程式碼就在說明這樣的概念:
#include <avr/pgmspace.h>
// save some unsigned ints
const PROGMEM uint16_t charSet[] = { 65000, 32796, 16843, 10, 11234};
// save some chars
const char signMessage[] PROGMEM = {"I AM PREDATOR, UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
unsigned int displayInt;
int k; // counter variable
char myChar;
void setup() {
Serial.begin(9600);
while (!Serial);
// put your setup code here, to run once:
// read back a 2-byte int
for (k = 0; k < 5; k++)
{
displayInt = pgm_read_word_near(charSet + k);
Serial.println(displayInt);
}
Serial.println();
// read back a char
int len = strlen_P(signMessage);
for (k = 0; k < len; k++)
{
myChar = pgm_read_byte_near(signMessage + k);
Serial.print(myChar);
}
Serial.println();
}
void loop() {
// put your main code here, to run repeatedly:
}
注意
請注意,為了要使用 PROGRAM,您必須將變數指定為全域變數(Global)或靜態變數(Static)。
下列程式碼若是被放在函式裏面,它將不會運作
const char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n";
下列程式碼若是被放在函式裏面,它也會運作
const static char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"
F() 函式
如果您在程式中有使用這樣的句法
Serial.print("Write something on the Serial Monitor");
這串要印出的文字通常是被存放在記憶體之中,如果您印出非常大量這樣的文字到 Serial Monitor,將會很容易把記憶體耗光。如果您有閒置的 Flash 記憶體空間,您可以很輕易地將要印出的文字存放到 Flash 裏,如下:
Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));
相關連結
淺談記憶體1 -- Memory http://pizgchen.blogspot.tw/2017/03/arduino-1.html
Arduino PROGRAM https://www.arduino.cc/en/Reference/PROGMEM
2017年3月13日 星期一
[Arduino] 淺談記憶體1 -- Memory
要善用 Arduino 就必須要徹底了解它的硬體架構與記憶體管理。本章旨在說明 Arduino 的記憶體,硬體架構則是在其它地方說明。
在基於 AVR 的 Arduino 板子上有三種可以儲存資料的地方:
1.快閃記憶體(Flash)。程式碼是儲存在這個地方。
2.靜態隨機存取記憶體(SRAM)。程式在運行時創建和操作變數的地方。
3.EEPROM。這個地方可以讓程式設計師長期存放資料。
儲存在 Flash 和 EEPROM 裏的資料,即使關閉電源它也不會消失;但儲存在 SRAM 裏的資料,一旦關閉電源它就會消失。
Arduino UNO 上面的 ATmega328 晶片有下列記憶體容量:
Flash 32k bytes (但其中 5k 已被使用於 bootloader)
SRAM 2k bytes
EEPROM 1k bytes
由上表可以知道,您的程式碼有 27k bytes 的記憶空間可用,但用來處理變數的記憶空間(SRAM)只有 2k bytes,假如我們在程式之中宣告一個字串變數
char message[] = "I support the Cape Wind project.";
這個字串有 32 個字元,每一個字元會占用 1 byte 的 SRAM,32 個字元將會占用 32 bytes 的 SRAM,再加上一個看不見的結尾字元 "\0",總共占用掉 33 bytes 的 SRAM。
2k bytes 等於 2048 bytes,2048 減 33 還剩 2015 bytes,或許這在你看來並非用掉了很多 SRAM,但是當你有一大篇字串要顯示或處理時,特別是在使用查表法時,這 2k bytes 的空間將會很快地被用完。
如果你把 SRAM 用完了,你的程序可能會以意想不到的方式失敗:它會顯示上傳成功,但不會運行,或者運行奇怪。要檢查這是否發生,你可以嘗試註釋掉或縮短程式中的字串或其他數據結構(在不更改程式碼情況下)。然後,如果它運行成功了,就表示你可能已用完了 SRAM。
您可以做一些事情來解決上述問題:
如果你是使用桌上型電腦或筆記型電腦來跟 Arduino 作溝通,那麼你可以嘗試將數據或需要計算的資料這樣的工作轉移到電腦上面,藉以減少 Arduino 的負擔。
如果有用到查表法或有大型資料,請使用存儲所需值的最小資料型態來宣告:例如 int 會佔用 2 個 bytes,而 byte 只會占用 1 個 byte (但是只能存儲較小範圍的值)。
如果程式在運行時不需要修改字符串或數據,則可以將它們存儲在 Flash (程式) 記憶體中,而不是 SRAM ;要做到這一點,可以使用 PROGMEM 關鍵字(註A)。
註A:LED_Cube 程式碼中使用大量的查表資料用來顯示 LED 圖案,此時就用到了 PROGRAM 關鍵字。
相關連結
淺談記憶體2 -- PROGRAM http://pizgchen.blogspot.tw/2017/03/arduino-2-program.html
Arduino Memory https://www.arduino.cc/en/tutorial/memory
各類型 Arduino 板子的記憶體大小 http://playground.arduino.cc/Learning/Memory
葉難 http://yehnan.blogspot.tw/2013/08/arduino.html
Adafruit https://cdn-learn.adafruit.com/downloads/pdf/memories-of-an-arduino.pdf
在基於 AVR 的 Arduino 板子上有三種可以儲存資料的地方:
1.快閃記憶體(Flash)。程式碼是儲存在這個地方。
2.靜態隨機存取記憶體(SRAM)。程式在運行時創建和操作變數的地方。
3.EEPROM。這個地方可以讓程式設計師長期存放資料。
儲存在 Flash 和 EEPROM 裏的資料,即使關閉電源它也不會消失;但儲存在 SRAM 裏的資料,一旦關閉電源它就會消失。
Arduino UNO 上面的 ATmega328 晶片有下列記憶體容量:
Flash 32k bytes (但其中 5k 已被使用於 bootloader)
SRAM 2k bytes
EEPROM 1k bytes
由上表可以知道,您的程式碼有 27k bytes 的記憶空間可用,但用來處理變數的記憶空間(SRAM)只有 2k bytes,假如我們在程式之中宣告一個字串變數
char message[] = "I support the Cape Wind project.";
這個字串有 32 個字元,每一個字元會占用 1 byte 的 SRAM,32 個字元將會占用 32 bytes 的 SRAM,再加上一個看不見的結尾字元 "\0",總共占用掉 33 bytes 的 SRAM。
2k bytes 等於 2048 bytes,2048 減 33 還剩 2015 bytes,或許這在你看來並非用掉了很多 SRAM,但是當你有一大篇字串要顯示或處理時,特別是在使用查表法時,這 2k bytes 的空間將會很快地被用完。
如果你把 SRAM 用完了,你的程序可能會以意想不到的方式失敗:它會顯示上傳成功,但不會運行,或者運行奇怪。要檢查這是否發生,你可以嘗試註釋掉或縮短程式中的字串或其他數據結構(在不更改程式碼情況下)。然後,如果它運行成功了,就表示你可能已用完了 SRAM。
您可以做一些事情來解決上述問題:
如果你是使用桌上型電腦或筆記型電腦來跟 Arduino 作溝通,那麼你可以嘗試將數據或需要計算的資料這樣的工作轉移到電腦上面,藉以減少 Arduino 的負擔。
如果有用到查表法或有大型資料,請使用存儲所需值的最小資料型態來宣告:例如 int 會佔用 2 個 bytes,而 byte 只會占用 1 個 byte (但是只能存儲較小範圍的值)。
如果程式在運行時不需要修改字符串或數據,則可以將它們存儲在 Flash (程式) 記憶體中,而不是 SRAM ;要做到這一點,可以使用 PROGMEM 關鍵字(註A)。
註A:LED_Cube 程式碼中使用大量的查表資料用來顯示 LED 圖案,此時就用到了 PROGRAM 關鍵字。
相關連結
淺談記憶體2 -- PROGRAM http://pizgchen.blogspot.tw/2017/03/arduino-2-program.html
Arduino Memory https://www.arduino.cc/en/tutorial/memory
各類型 Arduino 板子的記憶體大小 http://playground.arduino.cc/Learning/Memory
葉難 http://yehnan.blogspot.tw/2013/08/arduino.html
Adafruit https://cdn-learn.adafruit.com/downloads/pdf/memories-of-an-arduino.pdf
2017年3月12日 星期日
[玩轉光立方] LED Cube 4x4x4 for Arduino UNO -- 程式4 點點繁星
在使用查表法做出圖案之前,我們要中途來玩一個小東西,模擬天上的一閃一閃的星星,或是也可以說是模擬一閃一閃的螢火蟲。
要隨機點亮 LED 會用到 Arduino 的亂數相關函式,基本認識及用法請詳 http://pizgchen.blogspot.tw/2017/03/arduino.html
要隨機點亮 LED 會用到 Arduino 的亂數相關函式,基本認識及用法請詳 http://pizgchen.blogspot.tw/2017/03/arduino.html
程式碼1
//2017-03-12 LED_Cube4_04.ino
//以層方式,隨機點亮 LED
#define CUBE_SIZE 4
#define PLANE_SIZE CUBE_SIZE*CUBE_SIZE
#define PLANE_TIME 0
#define LED_TIME 2
int LEDPin[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, A0, A1};
int PlanePin[] = {A2 , A3, A4, A5};
void setup()
{
int pin;
for (pin = 0; pin < PLANE_SIZE; pin++) {
pinMode(LEDPin[pin], OUTPUT);
digitalWrite(LEDPin[pin], LOW);
}
for (pin = 0; pin < CUBE_SIZE; pin++) {
pinMode(PlanePin[pin], OUTPUT);
digitalWrite(PlanePin[pin], HIGH);
}
randomSeed(A0);
}
void loop()
{
for (int plane = 0; plane < CUBE_SIZE; plane++) {
digitalWrite(PlanePin[plane], LOW);
for (int col = 0; col < CUBE_SIZE; col++) {
for (int row = 0; row < CUBE_SIZE; row++)
{
int k = random(100);
if (k < 50) {
digitalWrite(LEDPin[col * CUBE_SIZE + row], HIGH);
delay(LED_TIME);
digitalWrite(LEDPin[col * CUBE_SIZE + row], LOW);
}
}
}
delay(PLANE_TIME);
digitalWrite(PlanePin[plane], HIGH);
}
}
來看一下影片
您可以改變一下 LED_TIME 的值,觀察是不是有不同的顯示效果。
程式碼2
程式也可以改成下列這樣,顯示的順序會與上方程式不同。
//2017-03-12 LED_Cube4_05.ino
//以柱方式,隨機點亮 LED
#define CUBE_SIZE 4
#define PLANE_SIZE CUBE_SIZE*CUBE_SIZE
#define PLANE_TIME 0
#define LED_TIME 20
int LEDPin[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, A0, A1};
int PlanePin[] = {A2 , A3, A4, A5};
void setup()
{
int pin;
for (pin = 0; pin < PLANE_SIZE; pin++) {
pinMode(LEDPin[pin], OUTPUT);
digitalWrite(LEDPin[pin], LOW);
}
for (pin = 0; pin < CUBE_SIZE; pin++) {
pinMode(PlanePin[pin], OUTPUT);
digitalWrite(PlanePin[pin], HIGH);
}
randomSeed(A0);
}
void loop()
{
for (int col = 0; col < CUBE_SIZE; col++) {
for (int row = 0; row < CUBE_SIZE; row++) {
for (int plane = 0; plane < CUBE_SIZE; plane++) {
digitalWrite(PlanePin[plane], LOW);
int k = random(100);
if (k < 50) {
digitalWrite(LEDPin[col * CUBE_SIZE + row], HIGH);
delay(LED_TIME);
digitalWrite(LEDPin[col * CUBE_SIZE + row], LOW);
}
delay(PLANE_TIME);
digitalWrite(PlanePin[plane], HIGH);
}
}
}
}
程式碼3
很明顯地,上面 2 支程式都讓 LED 顯示的太快了,一點兒也不像星星或螢火蟲閃爍。這回我們將更進一步讓它更接近真實,將原本順序顯示的層改為隨機,我把原本的程式碼 Mark 起來沒有刪除,目的是讓您參考比對。
//2017-03-12 LED_Cube4_06.ino
//以隨機柱方式,隨機點亮 LED
#define CUBE_SIZE 4
#define PLANE_SIZE CUBE_SIZE*CUBE_SIZE
#define PLANE_TIME 200
#define LED_TIME 50
int LEDPin[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, A0, A1};
int PlanePin[] = {A2 , A3, A4, A5};
void setup()
{
int pin;
for (pin = 0; pin < PLANE_SIZE; pin++) {
pinMode(LEDPin[pin], OUTPUT);
digitalWrite(LEDPin[pin], LOW);
}
for (pin = 0; pin < CUBE_SIZE; pin++) {
pinMode(PlanePin[pin], OUTPUT);
digitalWrite(PlanePin[pin], HIGH);
}
randomSeed(A0);
}
void loop()
{
for (int col = 0; col < CUBE_SIZE; col++) {
for (int row = 0; row < CUBE_SIZE; row++) {
// for (int plane = 0; plane < CUBE_SIZE; plane++) {
int plane = random(CUBE_SIZE);
digitalWrite(PlanePin[plane], LOW);
int k = random(100);
if (k < 50) {
digitalWrite(LEDPin[col * CUBE_SIZE + row], HIGH);
delay(LED_TIME);
digitalWrite(LEDPin[col * CUBE_SIZE + row], LOW);
}
delay(PLANE_TIME);
digitalWrite(PlanePin[plane], HIGH);
// }
}
}
}
程式碼4
下列程式是把上面程式精簡化,它們倆顯示的效果看起來相同。
//精簡以隨機柱方式,隨機點亮 LED
#define CUBE_SIZE 4
#define PLANE_SIZE CUBE_SIZE*CUBE_SIZE
#define PLANE_TIME 200
#define LED_TIME 50
int LEDPin[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, A0, A1};
int PlanePin[] = {A2 , A3, A4, A5};
void setup()
{
int pin;
for (pin = 0; pin < PLANE_SIZE; pin++) {
pinMode(LEDPin[pin], OUTPUT);
digitalWrite(LEDPin[pin], LOW);
}
for (pin = 0; pin < CUBE_SIZE; pin++) {
pinMode(PlanePin[pin], OUTPUT);
digitalWrite(PlanePin[pin], HIGH);
}
randomSeed(A0);
}
void loop()
{
int plane = random(CUBE_SIZE);
int cube = random(PLANE_SIZE);
int k = random(100);
digitalWrite(PlanePin[plane], LOW);
if (k < 50) {
digitalWrite(cube, HIGH);
delay(LED_TIME);
digitalWrite(cube, LOW);
}
delay(PLANE_TIME);
digitalWrite(PlanePin[plane], HIGH);
}
程式碼5
這是最終版本,在這兒將所有的數值全部隨機取出,顯示效果比前面幾個版本更佳了。
//2017-03-12 LED_Cube4_08.ino
//將所有數值亂數化,隨機點亮 LED
#define CUBE_SIZE 4
#define PLANE_SIZE CUBE_SIZE*CUBE_SIZE
int PLANE_TIME = 100;
int LED_TIME = 1000;
int LEDPin[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, A0, A1};
int PlanePin[] = {A2 , A3, A4, A5};
void setup()
{
int pin;
for (pin = 0; pin < PLANE_SIZE; pin++) {
pinMode(LEDPin[pin], OUTPUT);
digitalWrite(LEDPin[pin], LOW);
}
for (pin = 0; pin < CUBE_SIZE; pin++) {
pinMode(PlanePin[pin], OUTPUT);
digitalWrite(PlanePin[pin], HIGH);
}
randomSeed(A0);
}
void loop()
{
int plane = random(CUBE_SIZE);
int cube = random(PLANE_SIZE);
int k = random(100);
digitalWrite(PlanePin[plane], LOW);
if (k < 50) {
digitalWrite(cube, HIGH);
delay(random(LED_TIME));
digitalWrite(cube, LOW);
}
delay(random(PLANE_TIME));
digitalWrite(PlanePin[plane], HIGH);
}
來看一下影片
相關連結
[Arduino] 亂數
我們常常會需要用隨機的方式處理一件事情,最常見的就是樂透開獎。不同的是,樂透開獎採用機械式產生亂數,它是用風吹亂漂浮的球,當閘門打開時球順著管道飛出並掉落到盤中,完成隨機取樣的工作;而在 Arduino 的互動裝置中,我們也可以隨機產生一個數值,讓這個互動裝置隨機做出不同的反應。
如果您會 C 語言的亂數用法,那麼用 Arduino 隨機取出一個數值自然就難不倒您。
Arduino 提供兩個亂數相關的函式:random 、randomSeed
random 有兩個用法:
1. random(最大值)
random(100); 表最小值是 0,最大值是 100。但這個實際上只會產生 0~99 的整數。
2. random(最小值,最大值)
random(20, 50); 表最小值是 20,最大值是 50。但這個實際上只會產生 20~49 的整數。
程式碼
我們可以將下列程式碼 Upload 到 Arduino 裏,藉以觀察它產生的亂數是哪些。
void setup() {
Serial.begin(9600);
}
void loop() {
int i = random(100);
Serial.println(i);
delay(1000);
}
您可以打開序列埠監控視窗(Serial monitor),將可以看到如下畫面:
但是,如果您重覆關閉並開啟這個視窗,您會發現到它雖然有隨機產生一些數值,但這些數值在出現的順序上並沒有改變。這是因為 random 這個函式初始會以一個所謂的亂數種子(Random Seed)來產生產生亂數,在我們並未改變這個亂數種子之前,亂數會以一定的順序產生。然而,這並非我們真正需要的亂數,因為它數值的出現是可預期的。
要改變上述狀況,在使用 random 函式之前,可以先使用 randomSeed 函式指定
一個亂數種子。我們從類比腳位取出一個數值(它的值介於 0~1023 之間)當作亂數種子。所以程式可以改成這樣
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0));
}
void loop() {
int i = random(100);
Serial.println(i);
delay(1000);
}
您可以多次開關序列埠監控視窗(Serial monitor),將可以發現到每次開啟視窗時產生的數值都不一樣了,這才是我們真正需要的。
產生 0~100 之間 2 的倍數的亂數
將每次隨機產生的數值乘以 2,就可以產生 2 的倍數的亂數,程式碼如下:
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0));
}
void loop() {
int i = random(50) * 2;
Serial.println(i);
delay(1000);
}
延伸問題
1.如何模擬普克牌洗排,隨機排列1~13的順序。
2.如何模擬普克牌洗排,隨機排列4種花色1~13的順序,花色可以A~D表示,例如A12表黑桃Q,B7表紅心7...。
相關連結
Arduino Reference Random https://www.arduino.cc/en/Reference/Random
如果您會 C 語言的亂數用法,那麼用 Arduino 隨機取出一個數值自然就難不倒您。
Arduino 提供兩個亂數相關的函式:random 、randomSeed
random 有兩個用法:
1. random(最大值)
random(100); 表最小值是 0,最大值是 100。但這個實際上只會產生 0~99 的整數。
2. random(最小值,最大值)
random(20, 50); 表最小值是 20,最大值是 50。但這個實際上只會產生 20~49 的整數。
程式碼
我們可以將下列程式碼 Upload 到 Arduino 裏,藉以觀察它產生的亂數是哪些。
void setup() {
Serial.begin(9600);
}
void loop() {
int i = random(100);
Serial.println(i);
delay(1000);
}
您可以打開序列埠監控視窗(Serial monitor),將可以看到如下畫面:
但是,如果您重覆關閉並開啟這個視窗,您會發現到它雖然有隨機產生一些數值,但這些數值在出現的順序上並沒有改變。這是因為 random 這個函式初始會以一個所謂的亂數種子(Random Seed)來產生產生亂數,在我們並未改變這個亂數種子之前,亂數會以一定的順序產生。然而,這並非我們真正需要的亂數,因為它數值的出現是可預期的。
要改變上述狀況,在使用 random 函式之前,可以先使用 randomSeed 函式指定
一個亂數種子。我們從類比腳位取出一個數值(它的值介於 0~1023 之間)當作亂數種子。所以程式可以改成這樣
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0));
}
void loop() {
int i = random(100);
Serial.println(i);
delay(1000);
}
您可以多次開關序列埠監控視窗(Serial monitor),將可以發現到每次開啟視窗時產生的數值都不一樣了,這才是我們真正需要的。
產生 0~100 之間 2 的倍數的亂數
將每次隨機產生的數值乘以 2,就可以產生 2 的倍數的亂數,程式碼如下:
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0));
}
void loop() {
int i = random(50) * 2;
Serial.println(i);
delay(1000);
}
延伸問題
1.如何模擬普克牌洗排,隨機排列1~13的順序。
2.如何模擬普克牌洗排,隨機排列4種花色1~13的順序,花色可以A~D表示,例如A12表黑桃Q,B7表紅心7...。
相關連結
Arduino Reference Random https://www.arduino.cc/en/Reference/Random
2017年3月3日 星期五
[聲音] 用蜂鳴器播放超級瑪莉(Mario)音樂
一只無源蜂鳴器和一些小零件就能讓您的 Arduino 發出美妙的聲音。
蜂鳴器大略區分為有源與無源,有源蜂鳴器指的是通電它就會自己發出聲音;而無源蜂鳴器指的是雖然有通電,但它不會自己發出聲音,需要外部裝置傳送特定頻率的方波給它才會發出聲音。
這個專案會讓 Arduino 連續播放瑪莉歐曲子。
零件清單
1. Arduino UNO or nano *1
2. 蜂鳴器 *1
3. NPN電晶體(2N2222...) *1
4. 1K電阻 *1
5. 麵包板 *1
6. 杜邦線若干
電路圖
簡易接線如圖一。比較完善的接線如圖二。
電路接線
請注意蜂鳴器有正負極之分,蜂鳴器負極需接電晶體的C腳(集極)。
電晶體的B腳(基極)接 Arduino D3 腳位。
程式碼
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define melodyPin 3
#define ledPin 13
//Mario main theme melody
int melody[] = {
NOTE_E7, NOTE_E7, 0, NOTE_E7,
0, NOTE_C7, NOTE_E7, 0,
NOTE_G7, 0, 0, 0,
NOTE_G6, 0, 0, 0,
NOTE_C7, 0, 0, NOTE_G6,
0, 0, NOTE_E6, 0,
0, NOTE_A6, 0, NOTE_B6,
0, NOTE_AS6, NOTE_A6, 0,
NOTE_G6, NOTE_E7, NOTE_G7,
NOTE_A7, 0, NOTE_F7, NOTE_G7,
0, NOTE_E7, 0, NOTE_C7,
NOTE_D7, NOTE_B6, 0, 0,
NOTE_C7, 0, 0, NOTE_G6,
0, 0, NOTE_E6, 0,
0, NOTE_A6, 0, NOTE_B6,
0, NOTE_AS6, NOTE_A6, 0,
NOTE_G6, NOTE_E7, NOTE_G7,
NOTE_A7, 0, NOTE_F7, NOTE_G7,
0, NOTE_E7, 0, NOTE_C7,
NOTE_D7, NOTE_B6, 0, 0
};
//Mario main them tempo
int tempo[] = {
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
9, 9, 9,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
9, 9, 9,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
};
//Underworld melody
int underworld_melody[] = {
NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4,
NOTE_AS3, NOTE_AS4, 0,
0,
NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4,
NOTE_AS3, NOTE_AS4, 0,
0,
NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4,
NOTE_DS3, NOTE_DS4, 0,
0,
NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4,
NOTE_DS3, NOTE_DS4, 0,
0, NOTE_DS4, NOTE_CS4, NOTE_D4,
NOTE_CS4, NOTE_DS4,
NOTE_DS4, NOTE_GS3,
NOTE_G3, NOTE_CS4,
NOTE_C4, NOTE_FS4, NOTE_F4, NOTE_E3, NOTE_AS4, NOTE_A4,
NOTE_GS4, NOTE_DS4, NOTE_B3,
NOTE_AS3, NOTE_A3, NOTE_GS3,
0, 0, 0
};
//Underwolrd tempo
int underworld_tempo[] = {
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
6, 18, 18, 18,
6, 6,
6, 6,
6, 6,
18, 18, 18, 18, 18, 18,
10, 10, 10,
10, 10, 10,
3, 3, 3
};
void setup(void)
{
pinMode(melodyPin, OUTPUT);//buzzer
pinMode(ledPin, OUTPUT);//led indicator when singing a note
}
void loop()
{
//sing the tunes
sing(1);
sing(1);
sing(2);
}
int song = 0;
void sing(int s) {
// iterate over the notes of the melody:
song = s;
if (song == 2) {
Serial.println(" 'Underworld Theme'");
int size = sizeof(underworld_melody) / sizeof(int);
for (int thisNote = 0; thisNote < size; thisNote++) {
// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000 / underworld_tempo[thisNote];
buzz(melodyPin, underworld_melody[thisNote], noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
buzz(melodyPin, 0, noteDuration);
}
} else {
Serial.println(" 'Mario Theme'");
int size = sizeof(melody) / sizeof(int);
for (int thisNote = 0; thisNote < size; thisNote++) {
// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000 / tempo[thisNote];
buzz(melodyPin, melody[thisNote], noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
buzz(melodyPin, 0, noteDuration);
}
}
}
void buzz(int targetPin, long frequency, long length) {
digitalWrite(13, HIGH);
long delayValue = 1000000 / frequency / 2; // calculate the delay value between transitions
//// 1 second's worth of microseconds, divided by the frequency, then split in half since
//// there are two phases to each cycle
long numCycles = frequency * length / 1000; // calculate the number of cycles for proper timing
//// multiply frequency, which is really cycles per second, by the number of seconds to
//// get the total number of cycles to produce
for (long i = 0; i < numCycles; i++) { // for the calculated length of time...
digitalWrite(targetPin, HIGH); // write the buzzer pin high to push out the diaphram
delayMicroseconds(delayValue); // wait for the calculated delay value
digitalWrite(targetPin, LOW); // write the buzzer pin low to pull back the diaphram
delayMicroseconds(delayValue); // wait again or the calculated delay value
}
digitalWrite(13, LOW);
}
採購資訊
Arduino nano 開發板 http://goods.ruten.com.tw/item/show?21642069565073
Arduino UNO & nano 兩用擴展板 http://goods.ruten.com.tw/item/show?21628077602506
無源蜂鳴器 http://goods.ruten.com.tw/item/show?21709402547944
NPN電晶體 2N2222 http://goods.ruten.com.tw/item/show?21649653098711
1K電阻 http://goods.ruten.com.tw/item/show?21650756477303
麵包板 http://goods.ruten.com.tw/item/show?21643156259891
杜邦線 http://goods.ruten.com.tw/item/show?21629160028063
蜂鳴器大略區分為有源與無源,有源蜂鳴器指的是通電它就會自己發出聲音;而無源蜂鳴器指的是雖然有通電,但它不會自己發出聲音,需要外部裝置傳送特定頻率的方波給它才會發出聲音。
這個專案會讓 Arduino 連續播放瑪莉歐曲子。
零件清單
1. Arduino UNO or nano *1
2. 蜂鳴器 *1
3. NPN電晶體(2N2222...) *1
4. 1K電阻 *1
5. 麵包板 *1
6. 杜邦線若干
電路圖
簡易接線如圖一。比較完善的接線如圖二。
(圖一)
(圖二)
電路接線
請注意蜂鳴器有正負極之分,蜂鳴器負極需接電晶體的C腳(集極)。
電晶體的B腳(基極)接 Arduino D3 腳位。
程式碼
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define melodyPin 3
#define ledPin 13
//Mario main theme melody
int melody[] = {
NOTE_E7, NOTE_E7, 0, NOTE_E7,
0, NOTE_C7, NOTE_E7, 0,
NOTE_G7, 0, 0, 0,
NOTE_G6, 0, 0, 0,
NOTE_C7, 0, 0, NOTE_G6,
0, 0, NOTE_E6, 0,
0, NOTE_A6, 0, NOTE_B6,
0, NOTE_AS6, NOTE_A6, 0,
NOTE_G6, NOTE_E7, NOTE_G7,
NOTE_A7, 0, NOTE_F7, NOTE_G7,
0, NOTE_E7, 0, NOTE_C7,
NOTE_D7, NOTE_B6, 0, 0,
NOTE_C7, 0, 0, NOTE_G6,
0, 0, NOTE_E6, 0,
0, NOTE_A6, 0, NOTE_B6,
0, NOTE_AS6, NOTE_A6, 0,
NOTE_G6, NOTE_E7, NOTE_G7,
NOTE_A7, 0, NOTE_F7, NOTE_G7,
0, NOTE_E7, 0, NOTE_C7,
NOTE_D7, NOTE_B6, 0, 0
};
//Mario main them tempo
int tempo[] = {
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
9, 9, 9,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
9, 9, 9,
12, 12, 12, 12,
12, 12, 12, 12,
12, 12, 12, 12,
};
//Underworld melody
int underworld_melody[] = {
NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4,
NOTE_AS3, NOTE_AS4, 0,
0,
NOTE_C4, NOTE_C5, NOTE_A3, NOTE_A4,
NOTE_AS3, NOTE_AS4, 0,
0,
NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4,
NOTE_DS3, NOTE_DS4, 0,
0,
NOTE_F3, NOTE_F4, NOTE_D3, NOTE_D4,
NOTE_DS3, NOTE_DS4, 0,
0, NOTE_DS4, NOTE_CS4, NOTE_D4,
NOTE_CS4, NOTE_DS4,
NOTE_DS4, NOTE_GS3,
NOTE_G3, NOTE_CS4,
NOTE_C4, NOTE_FS4, NOTE_F4, NOTE_E3, NOTE_AS4, NOTE_A4,
NOTE_GS4, NOTE_DS4, NOTE_B3,
NOTE_AS3, NOTE_A3, NOTE_GS3,
0, 0, 0
};
//Underwolrd tempo
int underworld_tempo[] = {
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
3,
12, 12, 12, 12,
12, 12, 6,
6, 18, 18, 18,
6, 6,
6, 6,
6, 6,
18, 18, 18, 18, 18, 18,
10, 10, 10,
10, 10, 10,
3, 3, 3
};
void setup(void)
{
pinMode(melodyPin, OUTPUT);//buzzer
pinMode(ledPin, OUTPUT);//led indicator when singing a note
}
void loop()
{
//sing the tunes
sing(1);
sing(1);
sing(2);
}
int song = 0;
void sing(int s) {
// iterate over the notes of the melody:
song = s;
if (song == 2) {
Serial.println(" 'Underworld Theme'");
int size = sizeof(underworld_melody) / sizeof(int);
for (int thisNote = 0; thisNote < size; thisNote++) {
// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000 / underworld_tempo[thisNote];
buzz(melodyPin, underworld_melody[thisNote], noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
buzz(melodyPin, 0, noteDuration);
}
} else {
Serial.println(" 'Mario Theme'");
int size = sizeof(melody) / sizeof(int);
for (int thisNote = 0; thisNote < size; thisNote++) {
// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000 / tempo[thisNote];
buzz(melodyPin, melody[thisNote], noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
buzz(melodyPin, 0, noteDuration);
}
}
}
void buzz(int targetPin, long frequency, long length) {
digitalWrite(13, HIGH);
long delayValue = 1000000 / frequency / 2; // calculate the delay value between transitions
//// 1 second's worth of microseconds, divided by the frequency, then split in half since
//// there are two phases to each cycle
long numCycles = frequency * length / 1000; // calculate the number of cycles for proper timing
//// multiply frequency, which is really cycles per second, by the number of seconds to
//// get the total number of cycles to produce
for (long i = 0; i < numCycles; i++) { // for the calculated length of time...
digitalWrite(targetPin, HIGH); // write the buzzer pin high to push out the diaphram
delayMicroseconds(delayValue); // wait for the calculated delay value
digitalWrite(targetPin, LOW); // write the buzzer pin low to pull back the diaphram
delayMicroseconds(delayValue); // wait again or the calculated delay value
}
digitalWrite(13, LOW);
}
採購資訊
Arduino nano 開發板 http://goods.ruten.com.tw/item/show?21642069565073
Arduino UNO & nano 兩用擴展板 http://goods.ruten.com.tw/item/show?21628077602506
無源蜂鳴器 http://goods.ruten.com.tw/item/show?21709402547944
NPN電晶體 2N2222 http://goods.ruten.com.tw/item/show?21649653098711
1K電阻 http://goods.ruten.com.tw/item/show?21650756477303
麵包板 http://goods.ruten.com.tw/item/show?21643156259891
杜邦線 http://goods.ruten.com.tw/item/show?21629160028063
[Servo] 舵機(伺服馬達)使用前的檢查
舵機品質良莠不齊,如果專揀便宜的購買,買到山寨版的機率更是大。山寨版的舵機不是不能用,端看你怎麼用,才能夠不會燒掉,可以讓它「永保安康」。
剛拿到手的舵機不要急著將它安裝到機構件上,您應該先檢查它是否可以正常運作,檢查的項目如下:
1.外觀是否完整? 外殼是否破損? 電線是否破損? 轉軸是否正常? 零件是否齊全?
2.轉軸旁的塑膠殼上有一個小突起,可以用刀子將它削平,以免妨礙舵機轉動。
3.通電是否會轉動? (程式碼在本文下方)
4.如果馬達有轉動,而轉軸沒有轉動,有可能內部齒輪崩齒。
5.觀察舵機是來回轉 180 度,還是只有 90 度(有些舵機只能轉 90 度)。這款 SG90 舵機通常都無法轉足 180 度,請找出它可以轉動的最小和最大角度。建議轉動角度設定為 10~170度。
6.用手觸摸外殼,感覺溫度是否升高? 如果會燙手,請趕快拔除電源,以免舵機燒毀。
程式碼
您可以將舵機插到 Arduino 板子的 D2~D13 任一埠,舵機轉軸會160度來回轉動。
#include <Servo.h>
Servo myservo[12];
void setup()
{
for (int i = 0; i < 12; i++)
myservo[i].attach(i + 2);
}
void loop()
{
int i, pos;
for (pos = 10; pos <= 170; pos += 1)
{
for (i = 0; i < 12; i++)
myservo[i].write(pos);
delay(15);
}
for (pos = 170; pos >= 10; pos -= 1)
{
for (i = 0; i < 12; i++)
myservo[i].write(pos);
delay(15);
}
}
剛拿到手的舵機不要急著將它安裝到機構件上,您應該先檢查它是否可以正常運作,檢查的項目如下:
1.外觀是否完整? 外殼是否破損? 電線是否破損? 轉軸是否正常? 零件是否齊全?
2.轉軸旁的塑膠殼上有一個小突起,可以用刀子將它削平,以免妨礙舵機轉動。
3.通電是否會轉動? (程式碼在本文下方)
4.如果馬達有轉動,而轉軸沒有轉動,有可能內部齒輪崩齒。
5.觀察舵機是來回轉 180 度,還是只有 90 度(有些舵機只能轉 90 度)。這款 SG90 舵機通常都無法轉足 180 度,請找出它可以轉動的最小和最大角度。建議轉動角度設定為 10~170度。
6.用手觸摸外殼,感覺溫度是否升高? 如果會燙手,請趕快拔除電源,以免舵機燒毀。
程式碼
您可以將舵機插到 Arduino 板子的 D2~D13 任一埠,舵機轉軸會160度來回轉動。
#include <Servo.h>
Servo myservo[12];
void setup()
{
for (int i = 0; i < 12; i++)
myservo[i].attach(i + 2);
}
void loop()
{
int i, pos;
for (pos = 10; pos <= 170; pos += 1)
{
for (i = 0; i < 12; i++)
myservo[i].write(pos);
delay(15);
}
for (pos = 170; pos >= 10; pos -= 1)
{
for (i = 0; i < 12; i++)
myservo[i].write(pos);
delay(15);
}
}
相關連結
SG90 Datasheet http://www.micropik.com/PDF/SG90Servo.pdf
MG90S Datasheet http://www.electronicoscaldas.com/datasheet/MG90S_Tower-Pro.pdf
SG90 Datasheet http://www.micropik.com/PDF/SG90Servo.pdf
MG90S Datasheet http://www.electronicoscaldas.com/datasheet/MG90S_Tower-Pro.pdf
採購資訊
訂閱:
文章 (Atom)





