2017年10月13日 星期五

Free Energy Light Bulbs 230v-using Magnet?

我在 youtube 看到下面這個多數人評為造假的影片, 看標題我也認為是假的, 小發電機不可能發出 230V, 而且永動機違反熱力學第二定律. 但怎麼看都看不出這影片的破綻, 難道視頻也有類似 P 圖的工具嗎?

# https://www.youtube.com/watch?v=4rPWx8aaXjY




影片中使用壓電打火機 (piezo igniter) 產生的短暫電壓激勵馬達啟動, 讓內外兩個環形磁鐵因吸斥作用而持續轉動, 看起來似乎是非常完美的永動機, 但這個小小的電弧初始能量真的能產生比它還大得多的電能嗎? 能持續運轉下去嗎?

永動機與 free energy 儘管違反已知的物理定律, 但自古以來總是吸引許多人前仆後繼投入這種神奇機器的發明. 其實我的態度是很開放的, 從不以有限的所知知識侷限想像力, 因為我們對這宇宙的了解還是很有限, 這世界有無限種可能. 實驗是檢驗真理的唯一工具, 或許最後徒勞無功, 但有空的話倒是很想來複製影片中的實驗. 材料可在露天購得 :

# 壓電材料、壓電、點火器配件、打火機配件
# 釹鐵硼強力磁鐵 超強磁鐵 23*16*5 釤鈷磁鐵 超長磁鐵

另外還有一個類似的影片, 但它使用一顆電解電容來儲能, 參考 :

# make free energy generator mini best of world 2017 new project




同樣底下留言也是一片 "fake!" "fakee!" "idiot!" 罵聲, 但就是沒看到有人捲起袖子複製實驗後再來罵. 人外有人, 天外有天, 最保險的態度就是保持 open-minded 的心胸, 避免成為見識淺薄的井底之蛙. 古典的熱力學在現代量子資訊理論衝擊下或許會有所變化也說不定. 科學就是永遠不排除有被推翻的可能.

關於永動機的探討, 參考 :

# 永動機有可能嗎?——《悖論:破解科學史上最複雜的9大謎團》
# 熱力學第二定律被打破:打造永動機或成為可能
# 愛因斯坦認為它永遠不會被推翻,如今被量子資訊理論逼到了死角

2017年10月11日 星期三

2017 年第 40 周記事

放了四天連假, 差點連周記都忘了寫了. 週三中秋只有一天就沒回鄉下了, 早上測試研究 nRF24L01 無線模組, 下午把後陽台壞掉的曬衣繩換新, 順便沖洗一下後陽台, 準備把洗衣機搬回來, 沖水後發現後陽台中間 3, 4 塊磁磚似乎低了約 1mm, 感覺有點積水. 聯絡師傅周四下午來看, 說要叫施工的人來修補. 這幾天想了一下, 反正只是一點點而已, 只要用拖把拖一下很快就乾了, 犯不著又再敲敲打打, 叫師傅不用再修補了.

姊姊週五晚上到高雄, 她說做統聯暈車不舒服, 我說以後都坐高鐵啦! 舒適又省時. 上回坐一次自強號覺得怎麼左右搖晃得厲害, 害我在火車上看書也不舒服, 以後我連台鐵都不想再坐哩. 週六與菁菁, 水某三人前往湖內明璜的魚塭參加同學會, 雖然天氣很熱, 但大家聊得好高興.

因為姐姐周一中午要參加同學會, 所以還是週日晚上就回高雄了. 其實本周在鄉下也沒時間做甚麼, 每周書都是帶去帶回, 帶安心地而已, 事實上只要帶一本書就夠了. 週日在鄉下圖書館找到下面這本好書 :

# 表裡日本, 蔡亦竹 (遠足文化出版)

作者為實踐大學應用日語系助理教授, 曾在日本留學生活多年, 對於日本歷史文化有深刻了解與研究. 本書以散文方式寫作, 雖然很有文化深度, 但讀來毫不費力氣, 我一天就看完了. 書中關於金閣寺的來龍去脈有精闢描寫, 原來足利義滿所建的金閣寺竟然與其架構一樣有三層特別的意義啊!

2017年10月8日 星期日

如何製做鉛酸電池

在 Youtube 看到下面這兩部尼泊爾人自製鉛酸電池的影片感到非常新奇, 原來鉛酸電池是這樣做出來的, 裡面所需要的部件大都是使用小坩鍋自行熔解廢鉛塊為液狀, 然後倒入模型中塑造再加工, 鉛是柔軟展性佳的有毒重金屬, 容易切割加工, 導電性低抗腐蝕性強, 熔點低約攝氏 328 度.

# Part 1 of 2 Local Battery Manufacturing in Nepalgunj




# Part 2 of 2 Local Battery Manufacturing in Nepalgunj




現代電池工廠製造程序已高度自動化, 例如下面是美國 USA Batery 公司的深循環鉛酸電池的詳細製程 :

# Deep Cycle Battery 101 manufacturing - OEM ending




鉛酸電池最早是 1859 年法國物理學家普蘭特 (Gaston Planté) 所發明, 由二氧化鉛當正極, 鉛板當負級, 防止正負極短路的中間隔板, 以及濃度 30~40% 的稀硫酸溶液組成. 普蘭特是巴黎工藝美術學院物理學教授, 早年曾在巴黎郊外發現了史前一種不飛鳥-冠恐鳥的化石, 這種已滅絕之大型不飛鳥便以其名字 Gaston 命名為加斯頓鳥. 另外, 月球上的普蘭特隕石坑也是因為他發明鉛酸電池的偉大貢獻而以其姓氏命名. 參考 :

# https://baike.baidu.com/item/普兰特/15385934

鉛酸電池一格標稱電壓為 2V, 可充電到 2.4V, 放電至 1.5V. 通常由六格鉛酸電池串聯組成標稱電壓 12V 的模組. 鉛酸電池具有內阻低, 供電流大特性, 但過充時會產生易燃的氫氣, 須注意排氣避免爆炸之危險. 長期低電量會使電池壽命縮短, 使用久了之後電池極板上會有硫酸鉛結晶使蓄電量降低, 可用脈衝式充電加以消除.

鉛酸電池正負極的二氧化鉛與鉛會與稀硫酸電解液產生硫酸鉛溶液與水而放電; 當施予電壓時, 硫酸鉛溶液與水又會還原成二氧化鉛, 鉛與稀硫酸溶液, 這個充放電的電化學反應是可逆的. 參考 :

# https://zh.wikipedia.org/wiki/铅酸蓄电池

其反應原理可參考下列影片 :

# Working Principle of Lead Acid Battery




參考 :

# DIY 一個成本 20 元的 360W 充電器 (1000W 亦同
# Lead-acid storage battery
# 大學教授把垃圾場金屬變成自製的超級電池!

2017年10月7日 星期六

魚塭同學會

老同學兩周前在群組邀同學會, 這次是在湖內的魚塭舉辦烤肉, 雖然周末要回鄉下, 但因為從未到過湖內區, 而且大家要聚在一起也不容易, 所以我考慮了一周, 前天才回覆要參加. 原本想載爸一起去, 但他說對烤肉沒興趣, 所以就只有我, 水某, 菁菁三人. 姊姊昨晚雖回到高雄, 但今日要跟同學去高美館聽演講不能去; 而二哥則是要去學校上 APCS 課, 早上載他去學校後, 順路上國一到路竹交流道下去, 大約 40 分鐘.

同學家的魚塭在湖內湖中路尾, 那裏一眼望去都是魚塭實在很難找, 只有巷沒有門牌號碼, 我繞了一圈才找到. 我們是第二早到, 馬上開始準備烤肉, 等其他同學一來即可享用. 這次感覺比上回去大仁哥餐廳要多一些, 畢業後至今才見面的過敏也遠從桃園下來, 其他都是留在南部的老班底, 從早上邊吃邊喝邊聊天, 直到下午四點才快樂賦歸, 今天實在太高興了!

Arduino 無線傳輸模組 NRF24L01 測試

最近有網友詢問 LoRa 無線傳輸問題, 讓我想起以前也買過一款很便宜的無線模組 NRF24L01, 清查採購紀錄發現我買過兩組 (4 個模組), 但是一直都沒拿來測試過, 也不知是否能正常運作. 兩年前買大約 40 元一片, 只要 30 元就買得到. 參考 :

# 露天 XLAN 電子零件購買清單
# [X-LAN] Arduino NRF24L01+ 功率加強版 2.4G 無線模組 $35
# 向露天賣家盼盼購買零件模組一批
# 【盼盼105】 NRF24L01+ 功率加強版 遠距離 2.4G 無線收發模組 Arduino 實驗用 $35
# 購買電子零件 (柏益 boyi101)
# NRF24L01+ 功率加強版 24L01 2.4G 無線模組 台灣 IC 距離比原廠遠1倍 $30

注意, 現在有極低價才 17~20 元一片的貼片式模組 (一坨圓圓看不到 IC 的)  :

# (快速發貨已含稅)超薄款 類NRF24L01 2.4G無線模組 1.27MM間距 貼片 $17
# 類NRF24L01+ 2.4G無線模組 2.54MM間距 w2 $19
# [37975] 2.4G 模組 貼片NRF24L01+模組 超小體積 w2 $20

這些便宜模組只是 "類" NRF24L01 而已, 不是 Nordic 的晶片, 採用的是中國製仿製品 BK2425, Arduino 程式無法完全通用, 須使用特別的函式庫 RMF7x, 被玩家評為 "Worst of the worst" 的產品, 參考 :

# library for ones of the worst of chinese nRF24l01+ "alternatives"

以下測試我參考了下面幾本書 :
  1. 用 Arduino 全面打造物聯網, 孫駿榮 (碁峰)
  2. Arduino 完全實戰手冊, 王冠勳譯, 博碩
NRF24L01 是 Nordic  開發的高度集成低功耗的 20 針腳 RF 無線傳輸收發晶片 (Transceiver), 運作在免執照的 2.4G ISM (Industrial, Scientific, Medical) 頻段, 屬於 VHF (S Band) 頻段 (1~2GHz 為 L Band HF, 2~4GHz 為 S Band VHF, 4~8GHz 為 C Band UHF), 可使用 2.4GHz ~ 2.525 GHz 的 126 個頻段 (即每頻段間隔 1MHz), 其分布如下 :

頻段 0 => 2400 MHz (RF24 頻段 1)
頻段 1 => 2401 MHz (RF24 頻段 2)
....
頻段 76 => 2476 MHz (RF24 頻段 77) 預設頻段
....
頻段 83 => 2483 MHz (RF24 頻段 84)
....
頻段 124 => 2524 MHz (RF24 頻段 125)
頻段 125 => 2525 MHz (RF24 頻段 126)

參考 RF24.cpp 第 680 行可知預設頻段是 76 :

  // Set up default configuration.  Callers can always change it later.
  // This channel should be universally safe and not bleed over into adjacent
  // spectrum.
  setChannel(76);    

nRF24L01 的每個頻段有 6 個通道 (Pipe), 亦即允許 6*126=756 個設備同時收發互不干擾, 最高傳輸速率 2Mbps. VCC 工作電壓 1.9~3.6V, 但其他接腳可與 5V 系統的微控器如 Arduino 等直接相連, 不需使用位準轉換器. 超低功耗設計在發射模式下發射功率 6dBm 時電流消耗為 9.0mA, 接收模式為 12.3mA, 比一顆 LED 耗電還低, 可使用電池供電.

NRF24L01 在空曠地區 (無遮蔽物), 以 250KPBS 速率傳輸可達 180~240 公尺, 但在室內有牆等遮蔽情況, 由於 2.4GHz 微波的繞射與穿透能力弱, 大概只能穿透一面牆, 最大發射功率下只能傳遞 5~10 公尺遠. 詳細規格參考官網 :

# http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01

NRF24L01 模組採用 SPI 介面可與 Arduino 等微控器介接, 其接腳配置如下 :




接腳說明如下 :

 接腳 說明
 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

其中比較重要的接腳是 CE 與 CSN, CE 是用來控制 nRF24L01 是在 Standby/Active 模式; 而 CSN 則是用來告訴 nRF24L01 所傳送的是 SPI 指令還是要送出去的資料.

其實這種 2*4 的接腳與 ESP8266 ESP-01 模組是一樣的, 所以上回為 ESP-01 製作的轉接板也可以用在 NRF24L01, 參考 :

# 製作 ESP-01 模組轉接板




特別注意, NRF24L01 的 VCC 最高允許 3.6V, 不可施加 5V 電源, 否則有燒毀之虞, 通常運作於 3.3V. 不過除 VCC 外的接腳卻可接受 5V 位準, 故可與 Arduino 直接相連沒問題.

SPI (Serial Peripheral Interface) 是源自 Motolora 的全雙工同步資料傳輸協定, 可以讓微控器與多個周邊裝置進行短距離高速通訊, 常用於 SD 卡或 LCD 螢幕等周邊模組. SPI 是一種基本上為四線制的主從式架構, 其中微控器通常當主設備 (Master), 透過 /CSN (或 /SS, Slave Select) 接腳控制互連的周邊從設備 (Slave), 由於採用硬體連線方式選擇, 因此每多一個從設備時, 主設備就需要多一個輸出腳去控制, 若有 n 個從設備, 則主設備需要 n+3 支腳與所有從設備相連接, 但可用解碼器節省 GPIO 腳. 反觀 I2C 則是採用軟體方式選擇 (協定之第一個 byte), 不論多少從設備只需三條線.

Source :Wiki

當 /CSN 或 /SS 為低準位時, 該 Slave 設備即被主設備選定可與其通訊, 同一時間只有一個 /CSN 腳會被主設備拉到低準位. SPI 資料傳輸是透過 MISO (Master In Slave Out) 與 MOSI (Master Out Slave In) 這兩支腳, 兩端資料傳輸是利用 SCLK (或 SCK) 時脈來進行同步.  參考 :

# 序列周邊介面 (SPI)
# 認識UART、I2C、SPI三介面特性
# SPI (Serial Peripheral Interface) 串列 (序列) 週邊介面
# 成大資工 Wiki : SPI

以下測試我使用 Arduino Nano 當微控器, Arduino Nano/UNO/Pro Mini 這四款板子的 NPU 為 ATMEGA328P, 內建 SPI 介面, 具有特定 SPI 硬體接腳如下 :

 Nano/UNO 功能 說明
 D10 /SS Slave Select
 D11 MOSI Master In Slave Out
 D12 MISO Master Out Slave In
 D13 SCK Sychronous Clock

參考 :

# http://www.pighixxx.com/test/pinouts/boards/nano.pdf

根據下面這篇文章說明, nRF24L01 模組的 (CE, CSN) 可以接 Arduino 的任何 DIO 腳, 但 RF32.h 函式庫建議 Arduino 應使用 (D7, D8) 連接 (CE, CSN), 因此以下測試中不會使用 D10 當 CSN 使用. 另外 nRF24L01 的中斷 IRQ 可接可不接, Arduino 有兩個硬體中斷 : INT0 (D2) 與 INT1 (D3), 要接的話可使用 INT0. 總結硬體接線如下 :




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


註 : 樹莓派則是使用 GPIO(22, 8), GPIO 連接器編號 (15, 24)

在軟體方面, Arduino 的 SPI.h 函式庫提供 setDataMode(), begin(), end(), transfer() 等函式來進行 SPI 通訊, 參考 :

# https://www.arduino.cc/en/Reference/SPI

不過在操作 NRF24L01 時不必直接處理 SPI 協定, 因為已經有人將 NRF24L01 的 SPI 操作寫成函式庫 RF24, 可在 Github 按 "Clone or Download/download ZIP" 下載 (RF24-master.zip), 解壓縮後放在 Arduino IDE 安裝目錄的 libraries 子目錄下 :

# https://github.com/nRF24/RF24

然後在程式中匯入 SPI, RF24, 與 nRF24L01 三個函式庫即可 :

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

使用 RF24 函式庫首先要建立一個 RF24 物件, 傳入參數 (CE, CSN) 指定 nRF24L01 模組的 CE 腳與 CSN 腳與 Arduino 的哪一個腳位互連, 在 RF32.h 的第 1426 列有指定各種板子的適當接腳, 對於 UNO/Nano/Pro Mini 這三種板子應該指定 (7, 8) :

RF24 radio(7, 8); // (CE, CSN) 建立 RF24 物件 radio

其次是要宣告一個長度為 6 的字元陣列以儲存 nRF24L01 的節點位址, 並在傳送端用 radio.openWritingPipe() 函數指定 nRF24L01 節點位址以便寫入資料;

char node_address[6]='00001';
radio.openWritingPipe(node_address);

參數 node_address 可以是任意 5 個字元組成的字串, 例如 '00001', '1node' 等, 但在宣告字元陣列以儲存位址字串時必須多 1 個 byte 儲存結尾字元 \0, 故長度為 6. 注意, 使用 Multi-ceiver 星狀網路架構時每一個傳送板位址的第一個 byte 必須不同才能識別, 例如要用 "10000" 與 "20000", 不要用 "00001" 與 "00002", 因為第一個 byte 都是 '0' 無法區別, 詳見測試 4.

接著在接收板這一端則須用 radio.openReadingPipe() 函數指定 nRF24L01 節點位址並綁定通道編號以便讀取從傳送端收到的資料 :

radio.openReadingPipe(pipe_num, node_address);

第一參數 pipe_num 為 0~5, 最多只能 6 個通道 (位址), 一對一送收情況下綁定哪一個通道無礙接收, 但在 Multi-ceiver 星狀結構下, 通道就代表了所綁定之位址, 不能共用通道. 第二參數 node_address 為接收端 nRF24L02 的節點位址, 注意, 此位址必須與傳送端之位址相同才能進行通訊.

設定傳輸速率可使用 setDataRate(speed), 傳入參數有四種速率可選 :
  1. RF24_250KBPS (250kbs)
  2. RF24_1MBPS (1Mbps)
  3. RF24_2MBPS (2Mbps)
要取得目前的速率設定可呼叫 getDataRate() 函數.

傳送資料是呼叫 write(text, sizeof(text)) 函數將字串 text 傳送出去. 注意, nRF24L01 一次最多只能傳送 32 個 bytes, 超過的會被切斷丟棄. 如果要傳送多於 32 bytes 資料必須自己弄個協定讓接收板在收到每筆 32 bytes 之資料後重新組合還原為原來的資料, 參考 :

# NRF24L01 XN297L 無線網路 區域遠距傳輸

更多函數用法 參考 :

# RF24 函式庫 API 

以下的測試主要是參考了下面這篇加以修改 :

# Arduino Wireless Communication – NRF24L01 Tutorial

其教學影片如下 :

# https://www.youtube.com/watch?v=7rcVeFFHcFM&t=275s




不過與原作有 2 個不同之處, 其一是此篇使用 Arduino Mega + nRF24L01 當 sender 每秒送出 "Hello World" 字串, 以及 Arduino Nano + nRF24L01 當 receiver 接收此字串. 我則是兩邊都使用 Arduino Nano. 其二是我在發送端使用了 sprintf() 來將計數器整數嵌入字元串列中, 這樣開啟序列埠監控視窗觀察接收端訊息時就可以看到不同的輸出字串, 關於 sprintf() 用法參考 :

# How to convert integer to string in C?
# C 語言秘技 (2) – 使用 sprintf 將結構字串化 (作者:陳鍾誠)


測試 1 : 兩個 NRF24L01 一送一收成對傳送訊息 (程式分傳送與接收)

Sender (發送端程式) :

#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);
  }

Receiver (接收端程式) :

#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);
    }
  }

注意接收端程式多匯入了 RF24.h 的輸出函數 printf.h, 這是在呼叫 radio.printDetails() 必須用到的, 否則 printDetails() 將不會輸出訊息.




在空曠無阻礙物環境下測試, 最低功率時 (RF24_PA_MIN) 實測傳輸距離僅約 5~7 公尺 (與藍芽差不多), 而最大功率時 (RF24_PA_MAX) 則可達 70~90 公尺之遠, 且信號微弱處與天線指向性有關. 顯然號稱 100 公尺實際上大概要打八折. 功率放大器設定函數 setPALevel() 總共有四種功率放大器 PA (Power Amplifier) 可選 :
  1. RF24_PA_MIN  (最小功率 -12dB)
  2. RF24_PA_LOW (低功率 -12dB)
  3. RF24_PA_HIGH (高功率 -6dB)
  4. RF24_PA_MAX (最大功率 0dB)
參考 :

# 關於nrf2401的傳輸距離 #7
# nRF24L01無線傳輸使用心得

接收端序列埠監控視窗輸出如下 :

NRF24L01 receiver
waiting...
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x3130303030 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xe7e7e7e7e7
RX_PW_P0-6 = 0x20 0x00 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x4c
RF_SETUP = 0x07
CONFIG = 0x0f
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
NRF24L01 receiver
waiting...
Hello World 0
Hello World 1
Hello World 2
Hello World 3
Hello World 4
Hello World 5
Hello World 6
Hello World 7
Hello World 8
Hello World 9
Hello World 10
Hello World 11
Hello World 12
Hello World 13
Hello World 14
Hello World 94
Hello World 99

注意, 雖然 Arduino 的 D9 被定義為 SPI 的 /SS 腳, 但是若在接收程式中指定 (9, 10) 作為 (CE, CSN) 將無法正常運作; 但在 sender 程式中卻沒問題, 不知原因為何?  比較安全的方法還是一律用 D7 與 D8 為宜.

如果要增加傳輸距離, 則要改用下面這種有外加天線設計的, 號稱可遠達 1.1 公里 :

# T58 ~1100米遠距離 NRF24L01 PA LNA的無線模塊,送天線 $112

或者加焊延長天線, 參考 :

# nRF24L01無線傳輸使用心得


上面測試 1 是傳送與接收程式不同, 必須在不同角色的模組上打上標記才知道這是傳送板還是接收板, 這樣很麻煩. 下面測試 2 改為使用 Arduino 的 D4 腳設定角色, 1 為接收板 (預設), 0 為傳送板, 這樣程式只要一套即可, 如下測試 2 所示 :


測試 2 : 兩個 NRF24L01 一送一收成對傳送訊息 (單一程式用 D4 決定送收)

#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","00002"}; //兩個節點位址,一個傳送,另一個接收
bool role=1; //1=sender (default), 0=receiver

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  printf_begin();  //初始化 RF24 的列印輸出功能

  radio.setPALevel(RF24_PA_MAX);  //設為高功率 (預設)
  pinMode(4, INPUT_PULLUP);  //D4=模式開關 (預設=1:傳送模式)
  role=digitalRead(4);  //讀取 D4 準位決定接收 or 傳送模式 (default)
  if (role==1) { //=1:傳送模式
    radio.stopListening();  //傳送模式:停止傾聽
    radio.openWritingPipe(address[1]); //使用位址 '00002'
    radio.openReadingPipe(0,address[0]); //pipe 0:使用位址 '00001'
    Serial.println("NRF24L01 sending...");
    }
  else { //=0:接收模式
    radio.startListening();  //接收端開始接收
    radio.openWritingPipe(address[0]);  //使用位址 '00001'
    radio.openReadingPipe(0,address[1]);  //pipe 0:使用位址 '00002'
    Serial.println("NRF24L01 receiving...");
    }
  radio.printDetails();
  }
void loop() {
  if (role == 1) { //傳送模式
    const char text[32];  //宣告用來儲存欲傳送之字串
    unsigned long us=micros();  //取得啟動後之微秒數
    sprintf(text, "Hello World %lu", us);  //將整數嵌入字串中
    Serial.println(text);
    if (!radio.write(&text, sizeof(text))) {  //將字串寫入傳送緩衝器
      Serial.println("Sending failed");
      }
    delay(1000); 
    }
  if (role == 0) { //=0:接收模式
    uint8_t pipe_num;   //通道號碼
    if (radio.available(&pipe_num)) {  //偵測接收緩衝器是否有資料
      char text[32] = "";   //用來儲存接收字元之陣列
      radio.read(&text, sizeof(text));  //讀取接收字元
      Serial.print("Pipe num=");  //顯示從哪一通道接收
      Serial.print(pipe_num);
      Serial.print(" ");
      Serial.println(text);  //顯示接收資訊
      }
    }
  }

此二合一程式中, 使用 role 變數來決定 nRF24L01 板子是做傳送板還是接收板, 預設是 1 為傳送板, 但在 setup() 中會去偵測 Arduino 的 D4 腳位準, 若為 0 (LOW) 為接收板; 否則為傳送板. 由於 D4 有啟動上拉電阻, 因此預設就是傳送板, 只有要當接收板時才需要將 D4 接地.

其次是讀寫位址部分, 此程式與測試 1 不同之處在於使用了 '00001' 與 '00002' 兩個位址, 當作為傳送板時使用 '00002' 位址寫入通道, 對方接收板也是用 '00002' 位址讀取通道; 而 '00001' 位址則是傳送板之讀取位址或接收板之寫入位址. 其實不管是傳送板或接收板, 在上面程式中都是使用 '00002' 位址, 使用兩個位址旨在說明不論是運作在哪一模式, 都可以同時開啟寫入與讀取通道, 因為 SPI 是全雙工的通訊協定.

此程式使用 micro() 函數傳回的開機後的微秒時戳來取代測試 1 中的 counter 功能, 由於是 unsigned long 型態, 所以在用 sprintf() 將時戳嵌入字串中時, 必須改用 'ul' 格式才行.  另外在接收模式中, 新增了 pipe_num 變數, 用來在呼叫 radio.available() 時取得讀取通道之編號.

傳送板序列埠監控視窗輸出訊息 :

NRF24L01 sending...
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x3130303030 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0x3230303030
RX_PW_P0-6 = 0x20 0x00 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x4c
RF_SETUP = 0x07
CONFIG = 0x0e
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
Hello World 392940
Sending failed
Hello World 1445444
Sending failed
Hello World 2477536
Sending failed
Hello World 3509600
Sending failed
Hello World 4541664
Sending failed
Hello World 5573728
Sending failed
Hello World 6605792
Sending failed
Hello World 7637864
Sending failed

很奇怪的是, 雖然資料實際上有傳送成功, 但 radio.write() 的傳回值卻都是 0, 導致印出 "Sending failed". 參考函式庫原始碼 RF24.cpp 810~844 行的 write() 函數, 傳送成功應該傳回 1, 失敗傳回 0, 但不知為何傳送沒問題卻傳回 0. 

接收板序列埠監控視窗輸出訊息 :

NRF24L01 receiving...
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x3230303030 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0x3130303030 
RX_PW_P0-6 = 0x20 0x00 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x4c
RF_SETUP = 0x07
CONFIG = 0x0f
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
Pipe num=0 Hello World 208899772
Pipe num=0 Hello World 209931904
Pipe num=0 Hello World 210964028
Pipe num=0 Hello World 211996152
Pipe num=0 Hello World 213028276
Pipe num=0 Hello World 214060400
Pipe num=0 Hello World 215092524
Pipe num=0 Hello World 216124648
Pipe num=0 Hello World 217156772
Pipe num=0 Hello World 218188896
Pipe num=0 Hello World 219221020
Pipe num=0 Hello World 220253144

上面兩個測試都是靠 Arduino Nano 上的 TX 燈閃爍與序列埠監控視窗觀察無線傳輸情況是否正常, 接下來要做個比較有感的遙控測試, 我在傳送板上加裝一個按鈕連接到 Arduino 的 D3 腳; 另外在接收板上加裝一個蜂鳴器, 同樣連接到 D3 腳, 當按下傳送板的按鈕時, 接收板的蜂鳴器會發出嗶聲. 我將測試 2 的二合一程式修改如下 :


測試 3 : 兩個 NRF24L01 一送一收成對傳送訊息 (按鈕遠端控制蜂鳴器)

#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","00002"}; //兩個節點位址,一個傳送,另一個接收
bool role=1; //1=sender (default), 0=receiver

void alarmBeep(int pin) {
  tone(pin, 1000, 1000);
  delay(2000);
  }

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  printf_begin();  //初始化 RF24 的列印輸出功能

  radio.setPALevel(RF24_PA_MAX);  //設為低功率, 預設為 RF24_PA_MAX
  pinMode(4, INPUT_PULLUP);  //D4=模式開關 (預設=1:傳送模式)
  role=digitalRead(4);  //讀取 D4 準位決定接收 or 傳送模式 (default)
  if (role==1) { //=1:傳送模式
    pinMode(3, INPUT_PULLUP);  //D2=按鈕開關
    radio.stopListening();  //傳送模式:停止傾聽
    radio.openWritingPipe(address[1]); //使用位址 '00002'
    radio.openReadingPipe(0,address[0]); //pipe 0:使用位址 '00001'
    Serial.println("NRF24L01 sending...");
    }
  else { //=0:接收模式
    pinMode(3, OUTPUT);  //D3=接蜂鳴器
    radio.startListening();  //接收端開始接收
    radio.openWritingPipe(address[0]);  //使用位址 '00001'
    radio.openReadingPipe(0,address[1]);  //pipe 0:使用位址 '00002'
    Serial.println("NRF24L01 receiving...");
    }
  radio.printDetails();
  }
void loop() {
  if (role == 1) { //傳送模式
    const char text[10];  //宣告用來儲存欲傳送之字串   
    if (digitalRead(3)==LOW) {sprintf(text, "beep");}
    else {sprintf(text, "none");}
    Serial.println(text);
    if (!radio.write(&text, sizeof(text))) {  //將字串寫入傳送緩衝器
      Serial.println("Sending failed");
      }
    delay(1000); 
    }
  if (role == 0) { //=0:接收模式
    uint8_t pipe_num;
    if (radio.available(&pipe_num)) {  //偵測接收緩衝器是否有資料
      char text[10]="";   //用來儲存接收字元之陣列
      radio.read(&text, sizeof(text));  //讀取接收字元
      Serial.print("Pipe num=");
      Serial.print(pipe_num);
      Serial.print(" ");
      Serial.println(text);
      if (strcmp(text, "beep")==0) {alarmBeep(3);}
      }
    }
  }

此程式中 D3 腳在傳送板是接按鈕後接地並開啟上拉電阻, 因此沒有按下時狀態是 HIGH 送出 "none" 字串; 按鈕按下時為 LOW 傳送出 "beep" 字串. 在接收板 D3 被設為輸出腳外接一個無源蜂鳴器後接地, 當收到傳送板送來的字串是 "beep" 時便呼叫自訂的 alarmBeep() 函數, 利用 Arduino 的 tone() 函數發出 PWM 脈波產生 "嗶" 聲.

注意, 這裡使用了 strcmp() 函數來比較字串是否為不標字串, 當字串相等時傳回 0, 否則傳回 1 (大於) 或 -1 (小於), 參考 :

# 字串比較函數範例 strcmp
# 字串的比較、尋找、代換、分解與結合

當然也可以使用 ==  或 equals() 去比對, 但是這兩個運算對象都是 String 類型資料, 必須先將 char 陣列用 String() 轉型才能通過編譯 :

if (String(text.equals("beep")) {alarmBeep(3);}
if (String(text)=="beep") {alarmBeep(3);}

# https://www.arduino.cc/en/Reference/StringEquals
# https://www.arduino.cc/en/Reference/StringComparison




傳送板序列埠輸出訊息如下 :

NRF24L01 sending...
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x3130303030 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0x3230303030
RX_PW_P0-6 = 0x20 0x00 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x4c
RF_SETUP = 0x07
CONFIG = 0x0e
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
none
Sending failed
none
Sending failed
none
Sending failed
none
Sending failed
none
Sending failed
none
Sending failed
none

接收板序列埠輸出訊息如下 :

NRF24L01 receiving...
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x3230303030 0xc2c2c2c2c2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0x3130303030
RX_PW_P0-6 = 0x20 0x00 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x4c
RF_SETUP = 0x07
CONFIG = 0x0f
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
Pipe num=0 none
Pipe num=0 none
Pipe num=0 none
Pipe num=0 beep     (蜂鳴器發出嗶聲)
Pipe num=0 beep     (蜂鳴器發出嗶聲)
Pipe num=0 none
Pipe num=0 none
Pipe num=0 beep     (蜂鳴器發出嗶聲)
Pipe num=0 none
Pipe num=0 beep     (蜂鳴器發出嗶聲)
Pipe num=0 none


接下來要測試 nRF24L01 最強的星狀網路功能, 稱為 Multi-ceiver, 可支援 1 對 6 的星狀網路通訊, 亦即一個節點當 Hub receiver (又稱為 PRX : primary receiver), 其他子節點當傳送器 (又稱PTX : transmitter nodes), 可應用在如水位, 噪音, 溫度, 濕度, 含氧量等環境資訊的蒐集上. 注意, 雖然是星狀網路架構, 但 Hub 其實還是依序一次與一個子節點通訊, 而且每個子節點位址必須不同.

Source : Instructable


這張圖清楚地說明了 nRF24L01 的 Multi-ceiver  架構, 這是 125 個頻段中的一個頻段 (寬度 1MHz ), 每個頻段至多支援 6 個通道 (Pipe), 每個通道須綁定獨一無二的地址以便識別至多 6 個子節點. 注意, 在此架構下 PRX Hub 雖然是當作接收板用, 但它也是可以隨時切換至傳送模式傳送資料給子節點 (一次一個節點), 因為 SPI 協定是全雙工的.

在下面的測試 4 中我準備了三組 Arduino Nano+nRF24L01 板, 其中一組當接收 Hub, 其他兩組當傳送子節點, 兩個傳送板具有不同的位址, 在接收板上這兩個位址被綁定到不同通道 (pipe) 上, 不過這三塊 nRF24L01 都是在同一個頻段上通訊 (共有 126 個頻段). 選擇頻段要用 RF24 物件的 setChannel() 方法 :

radio.setChannel(channel_number);

我將上面測試 1 的程式擴充為如下面測試 4 的三個程式 :


測試 4 : 三個 NRF24L01 兩送一收 (Multi-ceiver)

傳送板 1 程式 : 

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

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

int counter=0;  //Hello 計數器
void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  radio.setPALevel(RF24_PA_MAX);   //設為低功率, 預設為 RF24_PA_MAX
  radio.setChannel(108); //設定頻道=108 (0~125, 較高的頻道似乎比較 open)
  radio.openWritingPipe(address);  //開啟寫入管線
  radio.stopListening();  //傳送端不需接收, 停止傾聽
  }
void loop() {
  const char text[32];  //宣告用來儲存欲傳送之字串
  sprintf(text, "Board 1 sending : %d", counter);  //將整數嵌入字串中
  Serial.println(text);
  radio.write(&text, sizeof(text));   //將字串寫入傳送緩衝器
  ++counter;
  delay(1000);
  }


傳送板 2 程式 :

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

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

int counter=0;  //Hello 計數器
void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  radio.setPALevel(RF24_PA_MAX);   //設為低功率, 預設為 RF24_PA_MAX
  radio.setChannel(108);  //設定頻道=108 (0~125, 較高的頻道似乎比較 open)
  radio.openWritingPipe(address);  //開啟寫入管線
  radio.stopListening();  //傳送端不需接收, 停止傾聽
  }
void loop() {
  const char text[32];  //宣告用來儲存欲傳送之字串
  sprintf(text, "Board 2 sending : %d", counter);  //將整數嵌入字串中
  Serial.println(text);
  radio.write(&text, sizeof(text));   //將字串寫入傳送緩衝器
  ++counter;
  delay(1000);
  }

接收板程式 :

#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]={"1node","2node"};  //節點位址為 5 bytes + \0=6 bytes

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  printf_begin();  //初始化 RF24 的列印輸出功能
  radio.setPALevel(RF24_PA_MAX);  //設為低功率, 預設為 RF24_PA_MAX
  radio.setChannel(108);  //設定頻道=108 (0~125, 較高的頻道似乎比較 open)
  radio.openReadingPipe(0, address[0]);  //開啟 pipe 0 之讀取管線
  radio.openReadingPipe(1, address[1]);  //開啟 pipe 1 之讀取管線
  radio.startListening();  //接收端開始接收
  radio.printDetails();  //印出 nRF24L01 詳細狀態
  Serial.println("NRF24L01 receiver");
  Serial.println("waiting...");
  }
void loop() {
  uint8_t pipe_num;   //通道號碼
  if (radio.available(&pipe_num)) {  //偵測接收緩衝器是否有資料
    char text[32] = "";   //用來儲存接收字元之陣列
    radio.read(&text, sizeof(text));  //讀取接收字元
    Serial.print("Pipe num=");  //顯示從哪一通道接收
    Serial.print(pipe_num);
    Serial.print(" ");
    Serial.println(text);  //顯示接收資訊
    }
  }


注意, 上面的程式中, 兩個子節點的位址取名為 "1node" 與 "2node" (也可以用 "10000" 與 "20000"), 如果使用 "00001" 與 "00002", 或者 "node1" 與 "node2" 的話, 則 Hub 接收板將讀取不到兩個子板傳送過來的訊息. 這是因為 nRF24L01 在一個頻段 (1MHz 寬度) 的 6 個通道中定址時, 事實上只有 Pipe 0 與 Pipe 1 的位址 (5 個 bytes) 才會被完整儲存起來, 而 Pipe 2~5 只儲存第一個 byte, 其餘 4 個 byte 是跟 Pipe 1 借來補足的. 由於區別 6 個通道是靠獨一無二的位址, 因此對於位址的設定只要 6 個位址的第一個 byte 都不同就可以了, 這就是為何使用 "00001" 與 "00002" 不行, 而用 "10000" 與 "20000" 卻可以的原因了, 上面測試 1~3 因為是一對一位址都一樣, 所以不受影響.

參考 RF24.h 的第 268~277 行 :


   * @note Pipes 0 and 1 will store a full 5-byte address. Pipes 2-5 will technically
   * only store a single byte, borrowing up to 4 additional bytes from pipe #1 per the
   * assigned address width.
   * @warning Pipes 1-5 should share the same address, except the first byte.
   * Only the first byte in the array should be unique, e.g.
   * @code
   *   uint8_t addresses[][6] = {"1Node","2Node"};
   *   openReadingPipe(1,addresses[0]);
   *   openReadingPipe(2,addresses[1]);
   * @endcode


呼叫 startListening() 會列印出接收板詳細資料, 其中前四行中的 RX_ADDR 便顯示了 6 個通道的位址 :

STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x3030303031 0x3030303032
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xe7e7e7e7e7

可見只有 Pipe 0/1 具有完整位址, Pipe 2~5 都只有第一個 byte, 其餘 bytes 是從 Pipe 1 借用, 因此 Pipe 2 的真實位址是 0x30303030c3, 而 Pipe 5 則是 0x30303030c6. 

接收板 (Hub) 之序列埠監控視窗輸出訊息如下 : 

STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x65646f6e31 0x65646f6e32
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xe7e7e7e7e7
RX_PW_P0-6 = 0x20 0x20 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x6c
RF_SETUP = 0x07
CONFIG = 0x0f
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
NRF24L01 receiver
waiting...
Pipe num=0 Board 1 sending : 0
Pipe num=1 Board 2 sending : 0
Pipe num=0 Board 1 sending : 1
Pipe num=1 Board 2 sending : 1
Pipe num=0 Board 1 sending : 2
Pipe num=1 Board 2 sending : 2
Pipe num=0 Board 1 sending : 3
Pipe num=1 Board 2 sending : 3
Pipe num=0 Board 1 sending : 4
Pipe num=1 Board 2 sending : 4
Pipe num=0 Board 1 sending : 5
Pipe num=1 Board 2 sending : 5
Pipe num=0 Board 1 sending : 6
Pipe num=1 Board 2 sending : 6
Pipe num=0 Board 1 sending : 7
Pipe num=0 Board 1 sending : 8
Pipe num=0 Board 1 sending : 9
Pipe num=1 Board 2 sending : 9
Pipe num=0 Board 1 sending : 10
Pipe num=0 Board 1 sending : 11
Pipe num=1 Board 2 sending : 11
Pipe num=0 Board 1 sending : 12
Pipe num=0 Board 1 sending : 13
Pipe num=1 Board 2 sending : 13
Pipe num=0 Board 1 sending : 14
Pipe num=1 Board 2 sending : 14
Pipe num=0 Board 1 sending : 15
Pipe num=1 Board 2 sending : 15

可見 Hub 接收板上會依序收到兩個子節點傳送的資訊. 透過 Multi-ceiver 功能可以在 Hub 上先將蒐集之子節點資訊整理後再透過 WiFi 或乙太網傳送給雲端伺服器, 這樣可以減少路由器的負荷. 

接下來要測試一個更有趣的 Multiceiver 應用, 這是我在 Instructable 找到的範例, PTR Hub 先產生一個 0~10 的隨機數來給 PTX node 猜, 每個 node 在送出猜測數字給 Hub 後馬上切換為接收模式等候 Hub 傳送結果; 如果猜對的話, Hub 會切換成傳送模式將正確數字傳給猜對的那個 node, 若 node 在 200 ms 內收到 Hub 回應的正確數字, 表示可能答對了 (因傳輸也許會錯誤), node 將 Hub 回應之正確數字與自己送出的猜測數字比對, 符合的話就確認真的猜對了, 這時就會停止再送出新的猜測數字. 如果沒有再 200 ms 時限內收到回應, 那表示可能猜錯了, 就產生新的隨機猜測數字送出去. 參考 :

# NRF24L01+ Multiceiver Network

我將原始程式改編為如下測試 5 :

測試 5 : 猜數字 (Multi-ceiver)

傳送板 (PTX node) 1 程式 :

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); //指定 Arduino Nano 腳位對應 nRF24L01 之 (CE, CSN)
const byte address[6]="1node";  //節點位址為 5 bytes + \0=6 bytes
bool done=false;  //用來判斷是否要停止傳送猜測數字

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  radio.setPALevel(RF24_PA_MAX);   //設為低功率, 預設為 RF24_PA_MAX
  radio.setChannel(108); //設定頻道=108 (0~125, 較高的頻道似乎比較 open)
  radio.openWritingPipe(address);  //開啟寫入通道
  radio.openReadingPipe(0,address);   //開啟讀取通道 pipe 0 (接收 PRX Hub 答案)
  radio.stopListening();  //PTX node 暫時不需要接收, 停止傾聽
  randomSeed(analogRead(0));  //利用 A0 腳的隨機狀態設定偽隨機序列種子
  }
void loop() {
  if (!done) {  //還沒猜對就繼續猜
    byte gnumber=(byte)random(11);  //產生 0~10 之隨機猜測數字 
    Serial.print("Guess number=");
    Serial.print(gnumber);
    Serial.print("...");
    //傳送猜測數字 (1 byte) 給 PTR Hub
    if (!radio.write(&gnumber,1)) {Serial.println("Sending failed");}  //傳送失敗
    else { //傳送成功
      Serial.println("Sending OK");
      radio.startListening();  //切換到接收模式等待 PTR Hub 回應
      unsigned long startTimer=millis(); //開啟計時器等候 200ms
      bool timeout=false;  //計時器逾時旗標,預設未逾時
      while (!radio.available() && !timeout) { //尚未收到回應且旗標為未逾時
        if (millis()-startTimer > 200 ) {timeout=true;} //等候回應直到逾時或收到回應
        }
      //未收到回應,可能猜錯了
      if (timeout) {Serial.println("Last guess may be wrong, try again.");}
      else {  //未逾時且收到回應,可能答對了
        byte rnumber;  //儲存 PTR Hub 傳來的回應數字
        radio.read(&rnumber,1);  //讀取 PTR Hub 傳來的回應數字 (1 byte)
        if (gnumber==rnumber) {  //由 PTR 回應確認猜對了
          Serial.println("You guess right!");
          done=true;  //猜對了就結束猜測
          }
        else {Serial.println("Something went wrong, keep guessing.");}
        }
      radio.stopListening();  //切回傳送模式
      }
    }
  delay(1000);
  }

此程式節點位址為 "1node", 在 setup() 中同時開啟讀取與寫入通道, 但因為主要是作為傳送用途, 因此先將接收模式關閉, 同時利用 A0 腳上隨機的漂移電壓呼叫 randomSeed() 設定偽隨機序列種子.

進入迴圈後先判斷是否已猜中 (done==TRUE), 若未猜中就呼叫 random() 產生一個 0~10 的隨機數 gnumber, 因為 random() 傳回值是 long, 故要強制轉型為 byte.  關於 randomSeed() 與 random() 參考 :

# https://www.arduino.cc/en/Reference/RandomSeed
# https://www.arduino.cc/en/Reference/Random

接著將此猜測的數字經由 pipe 0 傳送出去, 若傳送成功就切換到接收模式, 然後起始一個 200 ms 的計時器, 等候接收板傳送正確答案過來. 若在時限內收到接收板的回應, 就將正確答案與自己猜測的 gnumber 比對, 若相同就是確認猜中了, 就將 done 改為 true, 停止猜測.

第二個節點傳送板 2 程式與上面幾乎相同, 只是位址與綁定的通道不同, 分別是 "2node" 與 pipe 1, 除此之外其他都與傳送板 1 完全一樣, 如下所示 :


傳送板 (PTX node) 2 程式 :

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(7, 8); //指定 Arduino Nano 腳位對應 nRF24L01 之 (CE, CSN)
const byte address[6]="2node";  //節點位址為 5 bytes + \0=6 bytes
bool done=false;  //用來判斷是否要停止傳送猜測數字

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  radio.setPALevel(RF24_PA_MAX);   //設為低功率, 預設為 RF24_PA_MAX
  radio.setChannel(108); //設定頻道=108 (0~125, 較高的頻道似乎比較 open)
  radio.openWritingPipe(address);  //開啟寫入通道
  radio.openReadingPipe(1,address);   //開啟讀取通道 pipe 1 (接收 PRX Hub 答案)
  radio.stopListening();  //PTX node 暫時不需要接收, 停止傾聽
  randomSeed(analogRead(0));  //利用 A0 腳的隨機狀態設定偽隨機序列種子
  }
void loop() {
  if (!done) {  //還沒猜對就繼續猜
    byte gnumber=(byte)random(11);  //產生 0~10 之隨機猜測數字 
    Serial.print("Guess number=");
    Serial.print(gnumber);
    Serial.print("...");
    //傳送猜測數字 (1 byte) 給 PTR Hub
    if (!radio.write(&gnumber,1)) {Serial.println("Sending failed");}  //傳送失敗
    else { //傳送成功
      Serial.println("Sending OK");
      radio.startListening();  //切換到接收模式等待 PTR Hub 回應
      unsigned long startTimer=millis(); //開啟計時器等候 200ms
      bool timeout=false;  //計時器逾時旗標,預設未逾時
      while (!radio.available() && !timeout) { //尚未收到回應且旗標為未逾時
        if (millis()-startTimer > 200 ) {timeout=true;} //等候回應直到逾時或收到回應
        }
      //未收到回應,可能猜錯了
      if (timeout) {Serial.println("Last guess may be wrong, try again.");}
      else {  //未逾時且收到回應,可能答對了
        byte rnumber;  //儲存 PTR Hub 傳來的回應數字
        radio.read(&rnumber,1);  //讀取 PTR Hub 傳來的回應數字 (1 byte)
        if (gnumber==rnumber) {  //由 PTR 回應確認猜對了
          Serial.println("You guess right!");
          done=true;  //猜對了就結束猜測
          }
        else {Serial.println("Something went wrong, keep guessing.");}
        }
      radio.stopListening();  //切回傳送模式
      }
    }
  delay(1000);
  }


接收板 (PTR Hub) 程式 :  

#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]={"1node","2node","3node","4node","5node","6node"};
byte rnumber;  //要讓 PTX node 猜的隨機數字

void setup() {
  Serial.begin(9600);
  radio.begin();  //初始化 nRF24L01 模組
  printf_begin();  //初始化 RF24 的列印輸出功能
  radio.setPALevel(RF24_PA_MAX);  //設為低功率, 預設為 RF24_PA_MAX
  radio.setChannel(108);  //設定頻道=108 (0~125, 較高的頻道似乎比較開放)
  radio.openReadingPipe(0, address[0]);  //開啟 pipe 0 之讀取通道
  radio.openReadingPipe(1, address[1]);  //開啟 pipe 1 之讀取通道
  radio.openReadingPipe(2, address[2]);  //開啟 pipe 2 之讀取通道
  radio.openReadingPipe(3, address[3]);  //開啟 pipe 3 之讀取通道
  radio.openReadingPipe(4, address[4]);  //開啟 pipe 4 之讀取通道
  radio.openReadingPipe(5, address[5]);  //開啟 pipe 5 之讀取通道
  radio.startListening();  //接收端開始接收
  radio.printDetails();  //印出 nRF24L01 詳細狀態
  Serial.println("NRF24L01 PRX Hub receiving ...");
  randomSeed(analogRead(0));  //利用 A0 腳的隨機狀態設定偽隨機序列
  rnumber=(byte)random(11);  //產生 0~10 的隨機數供 PTX node 猜測
  Serial.print("The number for PTX node to guess=");
  Serial.println(rnumber);  //輸出待猜測之數字
  Serial.println();
  }
void loop() {
  byte pipe_num;   //用來儲存 PTX node 傳送板之通道編號
  byte gnumber;  //用來儲存從 PTX node 傳來的猜測號碼
  if (radio.available(&pipe_num)) {  //偵測接收緩衝器是否有資料
    radio.read(&gnumber, 1);  //讀取接收的 1 個 byte 猜測數字
    Serial.print("Received from node=");  //顯示從哪一通道接收
    Serial.print(pipe_num);
    Serial.print(" guess number=");
    Serial.print(gnumber);  //顯示收到的猜測號碼
    Serial.print("...");  //顯示猜測結果
    if (gnumber==rnumber) {  //猜對了
      radio.stopListening();  //PRX Hub 暫時停止接收, 切換至傳送模式
      radio.openWritingPipe(address[pipe_num]);  //開啟 PTR Hub 之寫入通道
      //將待猜測之數字傳給答對者
      if (!radio.write(&rnumber, 1)) {Serial.println("Guess right!");}
      else {Serial.println("Sending failed!");}
      radio.startListening();  //重新開啟 PRX Hub 之接收功能
      }
    else {Serial.println("Guess wrong!");}  //猜錯了
    }
  }

此接收板程式中會開啟 108 頻段的 Pipe 0~5 (位址 "1node" ~ "6node") 全部 6 個通道, 然後利用 A0 腳上飄移的隨機位準呼叫 randomSeed() 設定隨機序列種子, 再呼叫 random() 得到一個 0~10 的隨機值做為被猜測的數字 rnumber, 接著進入迴圈等候傳送板傳遞猜測值過來進行比對. 一旦傳送板有傳資料過來, 就記下其通道號碼與所傳之猜測數字, 然後比對 rnumber 與 gnumber, 若相同表示猜對了, 這時就先停止接收, 切到傳送模式, 把正確答案 rnumber 傳送給猜對的節點. 注意, 這裡接收板切到傳送模式時傳入 openWritingPipe() 的是該通道所綁定之位址 address[pipe_num], 傳送成功後又再切回接收模式.

接收板 PTR Hub 之序列埠監控視窗輸出 :

STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x65646f6e31 0x65646f6e32
RX_ADDR_P2-5 = 0x33 0x34 0x35 0x36
TX_ADDR = 0xe7e7e7e7e7
RX_PW_P0-6 = 0x20 0x20 0x20 0x20 0x20 0x20
EN_AA = 0x3f
EN_RXADDR = 0x3f
RF_CH = 0x6c
RF_SETUP = 0x07
CONFIG = 0x0f
DYNPD/FEATURE = 0x00 0x00
Data Rate = 1MBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
NRF24L01 PRX Hub receiving ...
The number for PTX node to guess=5

Received from node=0 guess number=8...Guess wrong!
Received from node=1 guess number=0...Guess wrong!
Received from node=0 guess number=5...Guess right!
Received from node=1 guess number=9...Guess wrong!
Received from node=0 guess number=7...Guess wrong!
Received from node=1 guess number=8...Guess wrong!
Received from node=0 guess number=4...Guess wrong!
Received from node=1 guess number=8...Guess wrong!
Received from node=0 guess number=7...Guess wrong!
Received from node=1 guess number=1...Guess wrong!
Received from node=0 guess number=2...Guess wrong!
Received from node=1 guess number=5...Guess right!
Received from node=0 guess number=6...Guess wrong!
Received from node=1 guess number=8...Guess wrong!
Received from node=0 guess number=0...Guess wrong!
Received from node=1 guess number=2...Guess wrong!
Received from node=0 guess number=5...Guess right! 
Received from node=1 guess number=0...Guess wrong!
Received from node=0 guess number=8...Guess wrong!
Received from node=1 guess number=2...Guess wrong!

傳送板監控視窗輸出訊息如下 : 

Guess number=7...Sending OK
Last guess may be wrong, try again.
Guess number=1...Sending OK
Last guess may be wrong, try again.
Guess number=5...Sending failed
Last guess may be wrong, try again.
Guess number=2...Sending OK
Last guess may be wrong, try again.
Guess number=4...Sending OK
Guess number=3...Sending OK
Last guess may be wrong, try again.
Guess number=5...Sending failed
Guess number=5...Sending failed
Guess number=5...Sending failed
Guess number=5...Sending OK
You guess right!  (終於停了)

但奇怪的是, 明明接收板顯示 Sending OK 表示它有成功將正確答案傳給猜對的傳送板, 但似乎猜對的傳送板沒收到, 以至於 done 沒有被更新為 true, 所以即使猜對了, 傳送板還是繼續送出猜測數字不會停止, 照理講猜對了就該停止才對.

2017-10-12 補充 :

經測試, 是因為 timeout 的緣故或傳送板 Sending failed 之故. 為何每次都在猜對時 Sending failed? 真是奇怪.

參考 :

# http://playground.arduino.cc/InterfacingWithHardware/Nrf24L01#
# Optimized High Speed Driver for nRF24L01(+) 2.4GHz Wireless Transceiver
# Connecting the Radio
# [Arduino] 以 nRF24L01+ 和 RF24 library 製作無線電端點
# Arduino Wireless Communication – NRF24L01 Tutorial
# Arduino NRF24L01 文件
# NRF24L01 功能說明
# [Arduino]001 Arduino與NRF24L01 2.4G無線應用
# 邁入『物聯網』的第一步:如何使用無線傳輸:基本篇
# [How To Arduino] Arduino 簡易測試 NRF24L01 無線傳輸
# 糊涂塔克学习笔记01 Arduino+nRF24L01
# nRF24L01 Module Demo for Arduino
# NRF24L01 XN297L 無線網路 區域遠距傳輸
# nRF24L01 Module Demo for Arduino
# 4 Arduino 4 Nrf24L01 Wireless Communication
# nRF24L01 範例下載
# BuyIC Arduino nRF24L01 資料下載

2017年10月6日 星期五

免電池的無線門鈴

今天在露天拍賣找數位電視機上盒時, 看到賣家也賣下面這款免電池的無線門鈴, 感到非常好奇 :

# 超遠150米!加購【防水免電池按鈕】學習型高穿透性門鈴【可1對多+多對1】 $350
# 電池防水【1對1】150米超遠高穿透性!環保學習型無線門鈴組【可1對多+多對1】

免電池? 那發射器的電能哪裡來? 原來是門鈴發射器裡有一個微能量發電機, 靠著按壓門鈴的力道讓小磁鐵切割線圈, 產生的電能儲存在鉭質電容上, 用來驅動低功號無線發射晶片. 有效距離長達 150 米, 可穿透 3 層樓牆壁, 還有中繼功能.

# 不用電池的呼叫器 — Linbell G2 自發電無線門鈴評測和拆解

2017年10月5日 星期四

購買數位電視機上盒

鄉下家的第四台 9/30 到期須繳下半年費用 3300 元, 因上個月申裝了 MOD, 爸說只有在看財經台, 其他都沒在看, 乾脆拆機算了, 每個月單一費率 550 元買一堆不會看的頻道, 不能像 MOD 那樣選購頻道, 實在是因為區域壟斷經營的關係.

前天南國來剪線並取回數位機上盒後, 爸在客廳就看 MOD, 在飯廳就看以前高雄拆回去的舊款數位電視 (DVB-T, 只能收到 6 台). 但 MOD 基本款並沒有數位電視的台視財經台, 所以只好單訂, 每個月 5 元, 加上寰宇財經每個月多付 35 元.

為了讓飯廳能收到全部 22 台數位電視, 今天上露天欲購買下面這款 DVB-T2 機上盒加天線 :

# 現貨送HDMI線!2017年DVB-T2雙頻數位機上盒【強化版】台灣區22台免費HD節目 $680
# 38DB小鋼炮!超強DVB-T2天線【5米線+5V增益器】室內車用兩用高強度柱型地上波天線.完勝大通菱形天線 $450

合購是 680+450+70 運費共 1200 元, 但我發現賣家有自己的網站賣場, 加入會員有優惠 70 元, 比露天還便宜 100 元, 所以就直接在賣場購買 :

# 送HDMI線 22數位HD台【DVB-T2+小鋼炮增益5米天線】 房東最愛 EPG選單 取代第四台1年保 $1100

要等收到貨回鄉下安裝才能確認是否可完整收到 22 台訊號.

2017-10-22 補充 :

上週帶回鄉下安裝, 小鋼炮可以完整收到 22 台頻道, 品質 ok, 只有台視信號較弱, 有時會馬賽克.

2017年10月4日 星期三

曬衣繩更換記 (new)

今天中秋節早上在家解 APCS 題目, 下午把買來很久的新曬衣繩安裝上去, 兩年前在小北買的那條 80 元的尼龍繩不耐用, 才兩年就風化了, 拉繩時會出現白色屑屑, 最後整個斷裂. 數月前又去小北買了 120 元表皮是橡膠, 內芯是鋼絲的曬衣繩, 但一直沒時間更換, 下午趁著整理後陽台, 驗收防漏工程之便, 把曬衣繩換新.

舊曬衣繩

新曬衣繩

另, 後陽台驗收有瑕疵, 中間較低會積水, 已通知老闆來檢視.

參考 :

# 曬衣繩更換記 (2015-03-30)

APCS (大學程式先修檢測) 105 年 10 月觀念題解析

解完 105 年 3 月份 APCS 觀念題後感覺越來越上手了, 繼續來解 10 月份的觀念題.

1. 右側 F() 函式 執行 後,輸出 為何?

void F( ) {
  char t, item[] = {'2', '8', '3', '1','9'};
  int a, b, c, count = 5;

  for (a=0; a<count-1; a=a+1) {
    c = a;
    t = item[a];
    for (b=a+1; b<count; b=b+1) {
       if (item[b] < t) {
         c = b;
         t = item[b];
         }
       if ((a==2) && (b==3)) {
         printf ("%c %d\n", t, c);
         }
       }
    }
  }

(A) 1 2     (B) 1 3     (C) 3 2       (D) 3 3

解析 : 

此題乍看之下很複雜, 是雙重迴圈, 但實際上不需要從頭追蹤程式執行流程, 只要看它的輸出條件即可, 其唯一的輸出指令 printf() 會被執行的條件是外迴圈索引 a=2 且內迴圈索引 b=3 時, 只要追蹤此條件即可. 

外迴圈 a=2 時, c=2, t=item[2]=3
然後進入內迴圈, 只須看 b=3 時, 因 item[b=3]=1 小於外迴圈得到的 t=3, 因此 if 為真, 執行 c=b=3, 以及 t=item[b=3]=1, 接著判斷 a==2 且 b==3 都符合, 因此輸出 t,c 為 1 3, 答案為 (B).


2. 下列 switch 敘述程式碼可以如何以if-else  改寫?   

switch (x) {
  case 10: y = 'a'; break; 
  case 20:
  case 30: y = 'b'; break; 
  default: y = 'c';
  }

   (A) if (x==10) y = 'a' ;
       if (x==20 || x==30) y = 'b';
       y = 'c';

   (B) if (x==10) y = 'a' ; 
       else if (x==20 || x==30) y = 
       else y = 'c';

   (C) if (x==10) y = 'a' ; 
       if (x>=20 && x<=30) y = 'b';
       y = 'c';

   (D) if (x==10) y = 'a' ; 
       else if (x>=20 && x<=30) y = 'b';
       else y = 'c';

解析 : 

switch case 相當於 if else if else 架構, 不是 if if if 架構, 故 (A) 與 (C) 均排除. (B) 與 (D) 基本上相同, 差別在 else if 裡面兩個條件是 AND 還是 OR. 由於 case 20 是空的, 無 break 跳出, 若 x=20 會直接往下執行 case=30 選項, 因此 x=20 或 x=30 都是執行 x=30, 是 OR 關係, 答案是 (B).


3. 給定下列 G() , K() 兩函式,執行 G(3) 後所回傳的值為何 ?

int K(int a[], int n) {
  if (n >= 0)
    return (K(a, n-1) + a[n]); 
  else return 0;
  }
int G(int n){
  int a[] = {5,4,3,2,1}; 
  return K(a, n);
  }

(A) 5    (B) 12   (C) 14     (D) 15

解析 : 

此題是遞迴函數題, 追蹤如後 :

呼叫 G(3) : 把陣列 a 與 n=3 傳入呼叫 K(a,3)
呼叫 K(a,3) : 因 n=3 大於 0, 故回傳 K(a, n-1) + a[n]=K(a, 2) + a[3]=K(a,2)+2
呼叫 K(a,2) : 因 n=2 大於 0, 故回傳 K(a, n-1) + a[n]=K(a, 1) + a[2]=K(a,1)+3
呼叫 K(a,1) : 因 n=1 大於 0, 故回傳 K(a, n-1) + a[n]=K(a, 0) + a[1]=K(a,0)+4
呼叫 K(a,0) : 因 n=0 等於 0, 故回傳 K(a, n-1) + a[n]=K(a, -1) + a[0]=K(a,-1)+5
呼叫 K(a,-1) : 因 n=-1 小於 0, 故遞迴結束回傳 0, 一一傳回原呼叫者, 最後回傳的結果是 2+3+4+5=14, 答案是 (C)


4. 下列程式碼執行後輸出結果為何 ?

int a=2, b=3;
int c=4, d=5;
int val;
val = b/a + c/b + d/b;
printf ("%d\n", val);

(A) 3      (B) 4      (C) 5     (D) 6

解析 : 

C 語言的整數除法是無條件捨去 (只取整數), 因此 b/a + c/b + d/b=3/2+4/3+5/3=1+1+1=3, 答案是 (A).


5. 下列程式碼執行後輸出結果為何 ?

int a[9] = {l, 3, 5, 7, 9, 8, 6, 4, 2}; 
int n=9, tmp;

for (int i=0; i<n; i=i+1) {
  tmp = a[i];
  a[i] = a[n-i-1];
  a[n-i-1] = tmp;
  }                                        
for (int i=0; i<=n/2; i=i+1)
  printf ("%d %d ", a[i], a[n-i-1]);

(A) 2 4 6 8 9 7 5 3 1 9
(B) 1 3 5 7 9 2 4 6 8 9
(C) 1 2 3 4 5 6 7 8 9 9
(D) 2 4 6 8 5 1 3 7 9 9

解析 : 

此程式有兩個 for 迴圈, 第一個迴圈是以陣列元素 a[4]=9 為中心左右交換兩次, i=0~3 左右交換一次變成 a[]={2,4,6,8,9,7,5,3,1}, i=4 時 9 與 9 自己交換, i=5~8 時左右又交換一次還原, 變回原來的 a[]={1,3,5,7,8,9,8,6,4,2}, 等於是虛晃一招.

第二個迴圈拜訪陣列 i=0~9/2=0~4, 會印出 a[i] 與其以 a[4]=9 為軸, 從尾端算過來的對應元素, 例如 a[0] 對應 a[8], 印出 12, a[1] 對應 a[7] 印出 34, a[2] 對應 a[6] 印出 56, a[3] 對應 a[5] 印出 78, a[4] 對應 a[4] 印出 99, 整個輸出 1234567899, 故答案是 (C).


6. 右側函式以 F(7) 呼叫後回傳值 為 12,則<condition> 應為何 ?

int F(int a) {
  if ( <condition> )
    return 1;
  else
    return F(a-2) + F(a-3);

(A) a < 3
(B) a < 2
(C) a < 1
(D) a < 0

解析 : 

此為遞迴函數題, 求其終止條件為何才能讓呼叫 F(7) 得到回傳值 12.

若條件為 a < 3 :
F(7)=F(5)+F(4)=3+2=5 (非也)
F(5)=F(3)+F(2)=2+1=3
F(4)=F(2)+F(1)=1+1=2
F(3)=F(1)+F(0)=1+1=2

若條件為 a < 2 :
F(7)=F(5)+F(4)=4+3=7 (非也)
F(5)=F(3)+F(2)=2+2=4
F(4)=F(2)+F(1)=2+1=3
F(3)=F(1)+F(0)=1+1=2
F(2)=F(0)+F(-1)=1+1=2

若條件為 a < 1 :
F(7)=F(5)+F(4)=5+4=9 (非也)
F(5)=F(3)+F(2)=3+2=5
F(4)=F(2)+F(1)=2+2=4
F(3)=F(1)+F(0)=2+1=3
F(2)=F(0)+F(-1)=1+1=2
F(1)=F(-1)+F(-2)=1+1=2

若條件為 a < 0 :
F(7)=F(5)+F(4)=7+5=12 (正確)
F(5)=F(3)+F(2)=4+3=7
F(4)=F(2)+F(1)=3+2=5
F(3)=F(1)+F(0)=2+2=4
F(2)=F(0)+F(-1)=2+1=3
F(1)=F(-1)+F(-2)=1+1=2
F(0)=F(-2)+F(-3)=1+1=2

故答案為 (D)


7. 若 n為正整數,下列程式三個迴圈執行完畢後 a 值將為何 ?

for (int i=1; i<=n; i=i+1)
  for (int j=i; j<=n; j=j+1)
    for (int k=1; k<=n; k=k+1)
      a = a + 1;

(A) n(n+1)/2
(B) n3/2
(C) n(n-1)/2
(D) n2(n+1)/2

解析 : 





9. 下列是依據分數 s 評定等第的程式碼片段,正確的等第公式應為:

90~100 判為 A 等
80~89 判為 B 等
70~79 判為 C 等
60~69 判為 D 等
0~59 判為 F 等



這段程式碼 在處理 0~100 的分數時,有幾個分數的等第是錯?

(A) 20    (B) 11     (C) 2   (D) 10



~未完待續~

2017 年第 39 周記事

後陽台防漏與主臥室地磚更新工程上周四完工後約需一周時間讓其乾燥凝固, 房間的床在施工時被立起來, 所以這一周來我都在客廳睡活動躺椅, 還好最近沒蚊子, 躺椅也蠻舒服的, 只是不能翻身而已.

自從上周在我家菜園種植小番茄後, 小舅開始常住鄉下了, 每天都會來我家, 除了灌溉之外, 還屋前屋後到處整理可種菜的區域, 圍牆外水溝邊, 門口大馬路邊通通難逃魔爪. 這樣也好, 反正我這麼忙, 菜園就交給他打理好了, 小舅種菜十餘年, 據說鄰居都誇他把菜園弄得像花園, 我且拭目以待.

這個週日 10/1 突然訪客甚多, 先是小舅媽與婷婷表妹一道回鄉下, 帶來中秋禮物吳記月餅 (超好吃) 與柚子. 接著信宏也來了, 想找我去爬山運動, 我說我還在滷冬瓜沒辦法, 結果他一出發就馬上下起大雨. 然後是高樹阿姨也是帶著月餅柚子來了, 不久國中同學阿錦也是帶著月餅柚子來找, 天啊! 這下月餅柚子吃不完了! 上週日傍晚跑步到他家去找他聊天, 結果他也是去運動沒遇到. 今天他提到他已徒步走遍我們鄉下各村里, 終於搞清楚那些甚麼寮的舊地名到底是在哪裡, 問我有無興趣過年時跟他一起去走, 我想冬天走路不會熱到爆, 當即欣然應允.

母親仙遊後家裡就沒這麼熱鬧過了, 這個周日真好.