在 Arduino 中使用亂數大概都會是以下的寫法, 利用空接的類比腳位輸入值當亂數種子:
void setup() {
Serial.begin(9600);
randomSeed(analogRead(A0));
for(int i = 0; i < 5; i++) {
Serial.print(random(300));
Serial.print(" ");
}
Serial.println("");
}
void loop() {
}
以下是按 5 次重置鈕執行 5 次的結果, 的確符合透過亂數種子讓每次產生的亂數沒有固定順序的期望:
10 38 62 81 237
19 104 42 108 265
239 57 235 210 70
274 55 112 206 279
110 247 121 80 226
由於利用類比輸入值設定亂數種子是產生亂數前的固定步驟, 我們可以將亂數物件化, 將設定亂數種子的工作擺到建構器 (constructor) 中, 就不需要自己設定亂數種子了, 像是這樣:
class RndGen {
RngGen() {
randomSeed(analogRead(A0));
}
public:
long rnd(long maxNum) {
return random(maxNum);
}
};
RndGen r;
void setup() {
Serial.begin(9600);
for(int i = 0; i < 5; i++) {
Serial.print(r.rnd(300));
Serial.print(" ");
}
Serial.println("");
}
void loop() {
}
不過實際上執行後卻發現每次執行產生的亂數順序都一樣:
7 49 173 158 130
7 49 173 158 130
7 49 173 158 130
7 49 173 158 130
7 49 173 158 130
執行順序很重要
之所以會發生前述問題, 主要是因為 Arduino 是 C++ 程式, 執行時會先建立全域物件, 再執行 main()
主函式, 而 ADC 功能的初始設定是在 main()
中才完成, 因此上例中 r
的建構器使用類比輸入值設定亂數種子時根本還無法進行 ADC, 所以會得到相同的亂數種子, 我們可以透過以下的例子來驗證:
class adc {
public:
int adcs[5];
adc() {
for(int i = 0; i < 5; i++)
adcs[i] = analogRead(A0);
}
void print(void) {
for(int i = 0; i < 5; i++) {
Serial.print(adcs[i]);
Serial.print(" ");
}
Serial.println("");
}
};
adc a;
void setup() {
Serial.begin(9600);
a.print();
}
void loop() {
}
底下是按 5 次重置鈕執行 5 次的結果:
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
你可以看到不論怎麼讀, 類比輸入值都是 0。
等等, 你說的main()
在哪裡?
你可能會想說, 我寫 Arduino 這麼久, 只看過 setup()
、loop()
, 哪來的 main()
?其實 Arduino 程式的基本架構已經固定好, 你可以在 hardware\arduino\avr\cores\arduino\main.cpp 找到 main()
的原始碼:
int main(void)
{
init();
initVariant();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
在後半段可以看到它會叫用我們自己寫的 setup()
和 loop()
, 而在開始的地方會叫用 init()
, 它就是許多初始工作進行的地方, init()
的原始碼可以在相同路徑下的 wiring.c 中找到:
void init()
{
// this needs to be called before setup() or some functions won't
// work there
...
#if defined(ADCSRA)
// set a2d prescaler so we are inside the desired 50-200 KHz range.
...
}
這個函式大部分都是組合語言, 我們不會深入探悉, 只是標示出設定 ADC 的地方。
套用 Arduino 的設計模式解決問題
了解問題所在後, 就可以解決問題了。大部分的 Arduino 程式庫若是以物件的形式呈現, 都會為類別加上 begin()
讓使用者設定初始狀態, 像是我們常用的Serial.begin()
、Wire.begin()
等等, 就可以把跟硬體相關的設定延到 main()
執行完 init()
後再進行, 依照此模式, 我們就可以將產生亂數的物件改成以下的設計方式:
class RndGen {
public:
begin() {
randomSeed(analogRead(A0));
}
long rnd(long maxNum) {
return random(maxNum);
}
};
RndGen r;
void setup() {
Serial.begin(9600);
r.begin();
for(int i = 0; i < 5; i++) {
Serial.print(r.rnd(300));
Serial.print(" ");
}
Serial.println("");
}
void loop() {
}
執行後就會發現亂數順序又變回我們需要的亂了:
156 1 208 275 41
142 203 56 206 28
205 97 184 40 263
233 46 135 178 289
219 195 36 109 276
小結
建議大家在設計自己的程式庫時, 都可以遵循 Arduino 的模式, 加入 begin()
, 一方面可以避免本文提到的硬體設定問題, 一方面則可以和既有的 Arduino 程式庫一致, 方便使用者用習慣的方式寫程式。
Top comments (0)