2015年7月17日 星期五

關於 PL2303HX 在 win8 無法驅動的問題

為了給 Arduino Pro mini 灌程式, 今年 4 月在露天買了一條蠻好用的 PL2303HX USB-TTL 程式上傳線 (價格便宜 & 板子有封裝), 在 XP 或 Win7 安裝官網所下載的 1.8.0 版驅動程式都可以抓到 com port, 上傳都 OK, 參考 :

# 安裝 PL2303HX USB 轉 TTL 驅動程式
# PL2303HX 下載線 USB轉TTL 長1米 UART Arduino Pro mini 可

但是這條 PL2303HX 線一直無法被家裡的聯想桌電 Win8.1 與我的 Inhon Win8.1 筆電抓到 (錯誤代碼 Code 10 : 無法啟動此裝置), 所以我向賣家詢問是否無法在 Win8.1 系統使用, 其回答如下 :

"可以在win8使用,但要注意、Win8系統自帶的驅動程式均不支援PL2303HX, 必須要關閉Win8的驅動程式自動更新,請自行下載2009年以前的驅動程式才可正常使用哦!"

我也在谷歌搜尋到好幾個解答, 但其實都沒有啥用 :

# https://drive.google.com/file/d/0BzdGbZCSZTdsdTh4dTFmUEF1bzQ/view
# Prolific PL2303 USB-to-Serial port adapter driver fails to install
# win8 PL2303驱动的问题(已经解决掉了),有问题的同学可以参考
# PL2303 Prolific USB to TTL Driver for Windows 8.1

最後找到這個外國人的解答, 這才了解為何露天賣家說要找 2009 年以前的驅動程式安裝才行, 原來這條上傳/下載線裡面的晶片可能是仿製的 (原文用 Fake, 假冒的), 所以原廠旺玖科技 Prolific 已經在新版的驅動程式中封殺此類晶片, 參見 :

# FAKE PL2303 – HOW TO INSTALL ON WINDOWS 8.1

"A quick investigation gave an unpleasant anwser – PL2303 chips are fake!
In the newest drivers Prolific blocked all fake chips and the driver in Windows 8.1 is also blocking my fake pl2303. But there is a solution – you have to install old drivers – version 3.3.2.105."

我照文中建議方法, 去下載 2009 年以前的驅動程式 :

# IO-Cable_PL-2303_Drivers-Generic_Windows_PL2303_Prolific

解開後執行其中的 PL2303_Prolific_GPS_1013_20090319.exe, 但這只是把驅動程式複製到系統裡, 還必須做更新驅動程式的動作, 才能把黃色驚嘆號去掉, 讓系統抓到 com port :


選下面那個 "瀏覽電腦上的驅動程式軟體" :


還是一樣選下方的 "讓我從電腦上的裝置驅動程式清單中挑選"


挑選剛剛安裝的那個 3.3.2.105 版驅動程式 (2008/10/27) :


再按下一步, 跑完 "正在安裝驅動程式軟體" 後, 即可抓到 com port 了 :


驚嘆號三角形消失, 大功告成.

關於 USB-TTL/UART 轉接線問題, 高手葉難寫過一篇很完整的文章, 詳見 :

# Raspberry Pi:透過序列埠登入系統

20150722 補充 :

我量了 TXRX 線輸出為 3.3V (VCC 輸出為 5V), 似乎可用在 ESP8266 (但電源必須用 AMS1117 電源模組輸出的 3.3V 才夠力), 參考 PL2303HX 的 PDF 規格書,

# http://www.prolific.com.tw/UserFiles/files/ds_pl2303HXD_v1_4_4.pdf

在 "3.0 Introduction" 中有提到可連接 3.3v 設備 :

" Flexible signal level requirement on the RS232-like serial port side also allows PL-2303HXD to connect directly to any 3.3V~1.8V range devices."

20150622 補充 :

我在露天找到這款可支援 Win10 的 PL2303TA 晶片的下載線, 要 88 元, 我當初買 PL2303HA 的才 34 元! 其實有網友照上面程序在 Win10 安裝 2008 年驅動程式 OK, 我今天也來試試看.

# ►474◄ PL2303TA USB轉TTL/RS232 4Pin 支持WIN7 WIN8 WIN10 $88


2015年7月14日 星期二

Lua 學習筆記 (一) : 基本語法

最近花了一點時間來學 Lua, 作為學習 NodeMCU 的準備. Lua 目前在 TIOBE 排行第 34, 雖然屬於後段班, 但由於核心以 C 語言寫成, 編譯後僅 100KB 左右, 啟動速度快, 執行效率高, 可利用 C/C++ 擴展, 不僅可嵌入到其他程式語言裡, 還能嵌入到硬體晶片中, 非常適合用來處理 C 不擅長的部分 (例如陣列與字串), 在過去早已廣泛被各類線上遊戲採用為腳本語言, 我認為低價的 NodeMCU 平台的出現, 會使 Lua 在物聯網的應用發展中漸漸受到重視, 因為它跟 C/C++ 太麻吉啦!

Lua 語言是巴西里約熱內盧 Pontifical Catholic University 大學的科技軟體開發學院 (Tecgraf) 教授 Roberto Ierusalimschy , Luiz Henrique de Figueiredo, 以及 Waldemar Celes 所設計的. Tecgraf 學院與巴西石油公司有合作開發關係, Tecgraf 協助石油公司開發圖形顯示與地質探勘專案軟體, 先後創造了 DEL 與 SOL 兩種資料驅動語言. Roberto 教授以這兩種語言為基礎, 研發出一個更完整, 具備可嵌入與可攜性的簡易語言. 由於 SOL 在葡萄牙語意思為太陽 (Sun), 所以在朋友建議下將這新語言命名為 Lua, 葡語為月亮 (Moon) 之意. 自 1993 年 Lua 1.0 發布以來, 歷經多次改版, 納入 prototype-based 的物件導向功能與事件驅動機制 (與 Javascript 類似) 等. 1996 年 Roberto 教授在美國 Dr. Dobb's Journal 期刊 (以微電腦軟體為主軸) 發表了一篇標題為 "Lua: an Extensible Embedded Language" 的文章, Lua 才開始踏出巴西走向世界, 許多遊戲軟體如 LucasArts 與 World of Warcraft (魔獸世界) 紛紛改用 Lua 作為 Scripting 語言, 知名手機遊戲開發工具 Corona 與 Moai 也是以 Lua 作為腳本語言, 甚至連 TCP/IP 軟體 Wireshark 與網路入侵檢測軟體 Snort 也內嵌了 Lua, 顯示 Lua 的簡單易學與強大功能正吸引全世界開發者的注意, 詳參 :

# A brief history of Lua
# The Evolution of Lua (PDF)
# Lua as a Configuration And Data Exchange Language (PDF)
# Wiki : Category:Lua-scripted video games
# Wiki : Lua (programming language)

在 Windows 上操作 Lua 請參考 :

# Lua 解譯器的用法

Lua 語言特色摘要如下 :
  1. Lua 是動態語言 (Dynamic) :
    亦即它是在執行時期 (Runtime) 才做型別檢查, 所以宣告變數時不需要宣告其資料型別, 而且執行中可任意變換其型別. 其他知名的語言如 Javascript, PHP, Python, Ruby 等都是動態語言. 而 Java 與 C 等靜態語言則是在編譯時期就做型別檢查, 宣告變數時必須同時指定其資料型別, 而且宣告後不能再更換為其他型別, 若賦予之資料與宣告型別不符就會產生編譯錯誤.
    參考 :
    # 靜態語言 vs. 動態語言的比較
  2. Lua 是弱型別語言 (Weak typing) :
    弱型別語言允許一個變數在相對應的記憶體區域中可以存放不同類型的資料, 而強型別語言則不可以. 弱型別語言的型別推斷功能可以進行較大程度的隱性 (自動) 型別轉換, 反之, 強型別語言的自動型別轉換則較少, 強弱是相對的, 並非絕對. 注意, 動態語言不一定是弱型別語言, 例如動態語言的 Python 與 Ruby 就屬於強型別語言; 而靜態語言的 Java 也有自動型別轉換, 但是屬於強型別語言.
    參考 :
    # Wiki 類型系統
    # JavaScript 語言核心(4)弱型別的代價 
  3. Lua 擴展能力強 (Extensible) :
    Lua 的核心非常精簡小巧, 語法簡潔優美, 類似簡化的英文, 其強大的功能來自於易擴展性, 主要是透過以 Lua 本身內建或用 C/C++ 編寫的外部函式庫來達成, 也具有 Java, Fortran, Smalltalk, Ada 的 API 介面來擴展函式庫. 
  4. Lua 可嵌入其他語言中 (Embedable) :
    Lua 可以嵌入到 C/C++, Java, C#, Python, Ruby, Perl 等其他語言中, 做為其函式庫使用. 而且 Lua 可以雙向呼叫 (bi-directional), 亦即可由 Lua 呼叫宿主語言函式, 也可以由宿主語言呼叫 Lua 函式, 一般腳本語言通常只專注在呼叫外部函式庫. 
  5. Lua 是跨平台語言 (Portable) :
    Lua 以 ANSI C/C++ 語言編寫而成, 可在多數作業系統上執行, 甚至在嵌入式系統如手機 Android 與 iPhone, 遊戲機 PSP, PS3, 甚至以 WiFi 晶片 ESP8266 為基礎的 NodeMCU 上也可以執行, 可移植性極高.   
Lua 的優勢是執行速度快, 語法簡單學習曲線不陡峭. 以下摘要整理 Lua 程式的基本語法, 主要參考下列資料 :

# Programming in Lua (first edition) (線上電子書)
# Learn Lua in X minutes (PDF)
# Lua Tutorial (Lua Users)
# Lua Tutorial (TutorialPoints)
# Lua Tutorial (Youtube)
# [Lua] 語法筆記
# [Lua] Table使用手冊
# 看影片學 Lua 程式設計
# Scripting系統概論與Lua簡介
  1. 區塊 (chunk 或 block) :
    Lua 程式的一段陳述 (statement) 稱為 chunk, 它可以是指一條指令, 也可以是一群指令. 例如 :

    a=100
    print(a)

    或者

    a=100 print(a)

    每條指令間不須像 Javascript 或 PHP 那樣用分號結尾, 直接跳行即可, 但用分號結尾也是可以的 (行尾的分號對 Lua 而言是可有可無的, optional), 例如 :

    a=100;
    print(a);

    但如果兩條敘述要寫在同一行就必須用分號隔開 (不是用逗號), 例如 :

    a=100; print(a)

    最常見的區塊是函數 (function end), 迴圈 (for end, while end, repeat end), 判斷 (if end), 以及匿名區塊 (do end), 在這四種區塊內的變數都是自成一個單元, 享有相同的作用域或環境 (scope, context) :

    function function1()
      --do something
    end

    for a=1, 100, 5 do
      --do something
    end

    if a<=10 do
      --do something
    end

    do
      --do something
    end

    注意, 在 Lua 解譯器的交談模式中, 每一行完整的指令就是一個 chunk, 所以這一行定義的區域變數跳到下一行時是新的一個 chunk, 已經超出其作用域 (scope) 而無法存取, 執行的結果將不受上一行區域變數之影響. 此問題可用 do end 區塊解決, 包含在此區塊內的陳述都屬於同一 chunk. 
  2. 程式註解 :
    Lua 使用開頭的兩個 dash (--) 作為單行註解符號 (一直到行尾), 例如 :

    --這是單行註解行

    多行註解則是使用兩個 dash 與雙中括號 --[[ 開頭, 一直到下一個雙中括號為止, 所包含的區域解譯器會略過不予處理 :

    --[[這是
    多行
    註解]]

    例如下列範例 test.lua :

    --[[
    print ("Hello World!")
    ]]
    print ("Hello World!")

    執行此程式只會印出一行 "Hello World!", 因為第一個 print 指令被註解掉了不會執行.
    在程式開發過程中, 為了除錯方便常會使用多行註解來控制某一段程式是否要執行, 測試完畢後又要把 --[[ 與 ]] 分別去掉, 有個取巧的辦法可以解決此麻煩, 就是在結尾中括號前也加上雙 dash :

    --[[
    print ("Hello World!")
    --]]
    print ("Hello World!")

    這樣當要取消註解, 讓原先被註解掉的程式碼恢復執行時, 只要在註解開頭處再加一個 dash 即可 (因為 ---[[ 中前面的兩個 dash 把這行變成單行註解, 迫使結尾的 --] 單飛為單行註解, 多行註解破功) :

    ---[[
    print ("Hello World!")
    --]]
    print ("Hello World!")

    這樣註解頭失去多行註解功能, 而註解尾變成單行註解, 不用刻意去除. 這個程式會印出兩行 "Hello World!".
    但中括號有時令人覺得眼花撩亂, 一律使用 -- 其實會比較整齊與單純. 
  3. 識別字 (Identifier) 與保留字 :
    識別字用來指派變數或函式名稱, Lua 識別字的限制與 Javascript 一樣, 只能使用英文字母, 數字, 以及底線之組合, 但是第一個字元不可以是數字, 所以 2au 不是合法的識別字. 此外雖然可以用底線開頭, 但是因為 Lua 解譯器內部使用以底線開頭的變數, 特別是用底線開頭, 後面是大寫字母組成的識別字, 例如 _VERSION (這是 Lua 特殊用途的保留字), 所以最好不要用底線開頭來命名變數, 以免與系統衝突.

    另外, Lua 有 22 個保留字, 這些都不能拿來當識別字 :

    local nil not
    or and
    if then else elseif
    for end in do while until repeat break
    true false
    function return
    goto

    其中 nil 即 Javascript 或 PHP 的 null 或 NULL, 即無值之意. 被設為 nil 的變數會在記憶體管理下一次掃描時被垃圾回收而消失. 相較於 Javascript 有 29 個保留字, Python 的 33 個, Java 的 50 個, PHP 的 53 個, 而 C# 高達 87 個, Lua 卻只有 22 個, 可見 Lua 有多精簡.

    Lua 是有分大小寫的 (case-sensitive), 所以 and 是保留字不能用, 但 AND, And 卻是合法的識別字.
  4. 全域變數與區域變數 :
    只要不是用 local 宣告的變數都是全域變數, 即使它位於函數或迴圈等區塊中, 例如 :

    a=100

    全域變數一經賦值後就會一直存在記憶體中, 直到被設為 nil 才會被記憶體管理回收. 存取未賦值的全域變數會得到 nil. 全域變數實際上會放在名為 _G 的特殊表格中 (Table, 即關聯式陣列, 是 Lua 唯一的資料結構, 所有的變數, 函式都是以 table 來儲存), 安全性較高.

    區域變數是以 local 宣告的變數, 其作用域 (scope) 僅限於所在之區塊 (block) 內, 例如在函式, 分岐控制, 迴圈或 do end 結構中使用的變數應該宣告用 local 宣告為區域變數. 區塊中的區域變數會掩蓋同名的全域變數 (即不會存取到同名之全域變數), 意即更改區域變數之值不會影響全域變數的值. 當程式區塊執行結束時, 區塊內的區域變數就會被記憶體管理自動回收 (因此不需要刻意在區塊末尾將其設為 nil). 唯一的例外是, 如果全域變數有參考到區域變數, 則此區域變數不會隨著區塊結束而被回收, 例如 :

    a=1
    function myfunc()
      local b=2
      a=b      --全域變數 a 參考到區域變數 b
    end
    myfunc()
    print(b)   --輸出 nil (無法存取到區域變數)
    print(a)   --輸出 2

    由於全域變數 a 在函數內參照了區域變數 b, 使得 b 被全域化, 因此在呼叫函數 myfunc() 後, 原本是區域變數的 b 無法正常地被垃圾回收而留存在記憶體中 (但在上層全域卻看不到), 成為垃圾回收的漏網之魚. 這種參照方式要盡可能避免, 否則可能因 memory leak 造成程式崩潰.

    除非必要不要使用全域變數, 要盡量使用區域變數, 因為它的執行速度比較快, 而且在記憶體運用上也較有效率, 當它到達作用域盡頭時就會失去作用而被記憶體管理自動回收. 因為當 Lua 解譯一個變數時, 如果它前面有 local 標示為區域變數, 則解譯器會在目前的區域中尋找此變數, 找不到就是 nil; 如果沒有 local, 就會往上一層去找, 直到最上層為止, 找不到才是 nil, 因此全域變數要花較多時間尋找, 會影響執行效率, 所以除非是必要的全域變數, 盡量使用區域變數. 例如 :

    local a=1
    local b=2
    if a print(a)      --> 輸出 1
      local a=10      -- then 區塊內的區域變數, 不影響外面的區域變數 a
      print(a)           --> 輸出 10
    end
    print(a)             --> 輸出 1
    print(b)             --> 輸出 2

  5. 多重指派 (multiple assignment) :
    Lua 跟 Python 一樣具有多重指派語法, 可以在一個陳述中同時為多個變數賦值, 各變數以逗號隔開, 例如在交談模式中 :

    > do
    >> local a,b,c=1,2,3
    >> print(a,b,c)
    >> end
    1  2  3

    但如果賦值不足, 則未賦值之變數預設為 nil, 例如 :

    > do
    >> local a,b,c=1,2
    >> print(a,b,c)
    >> end
    1  2  nil

    若賦值超過變數之數目, 多出來的值會被視而不見略過. 多重指派讓資料交換變得很簡潔, 只需要一條指令即可 :

    local a,b=1,2
    a,b=b,a   --交換後 a=2, b=1
  6. 資料型態 (data types) 與運算子 :
    Lua 是動態弱型別語言, 其變數是無型態的 (typeless), 宣告時不需要指定資料型態, 但變數的值當然有型態, 因為解譯器是靠值的資料型態來動態地配置記憶體.
    Lua 有八種資料型態 :

    (1). nil  (無值)
    (2). boolean (布林)
    (3). number (數值)
    (4). string (字串)
    (5). table (表格, 關聯式陣列)
    (6). function (函式)
    (7). thread (執行緒)
    (8). userdata (使用者資料)

    前面六種都是其他語言中常見的型態, 而 thread 與 userdata 則較特殊. 事實上這八種資料都是使用 table 來實作的, 因為 table 是 Lua 唯一的資料結構. 使用者定義資料 userdata 是記憶體中的一個區塊, 用來給 C 函式透過 API 存取資料, Lua 無法建立或操控此資料. 而 thread 是用來指派一個例外的獨立執行緒, 但這與一般作業系統的 thread 不同, Lua 不支援真正的多執行緒 (先佔式記憶體分享, 因為 ANSI C 不提供), 不過 Lua 透過 coroutine 提供非先佔式或非記憶體分享多執行緒. 而 thread 就是 coroutine 與低層 API 的組合.

    Lua 有一個內建函數 type() 可用來檢測變數的資料類型, 例如 :

    print(type(1))          --輸出 number
    print(type("Tony"))  --輸出 string
    t={}        
    print(type(t))           --輸出 nil (因為是個空的表格)
    t={name="tony"}
    print(type(t))           --輸出 table

    以下整理最常用的字串, 數值, 布林值, 與無值 nil.

    (1) nil

    nil 相當於 Javascript 的 null, 表示一個變數無值, 即尚未初始化. 一個變數只宣告而未賦值, 預設就是 nil. 全域變數一經宣告就會存在於記憶體中, 除非我們把它設為 nil 才會被垃圾收集機制刪除.

    (2). boolean

    在 Javascript 中 true 與 1, false 與 0 是同義的, 但在 Lua 卻不同, Lua 的布林值只有 true 與 false 兩種, 1/0 並非 true/false 的別名, 配合 not, and 與 or 運算用在條件控制的邏輯判斷上. 但 Lua 的條件控制並非只能依賴這兩個布林值, boolean 以外的型態都可以用在邏輯判斷, 但除了 false 與 nil 在邏輯上為假外, 其他均為真, 例如 :

    b=0.22
    if (b) then print("true")   --> 印出 true
    else print("false")        
    end

    但是要注意, Lua 的 and 與 or 運算結果有點特別, 採用所謂 shortcut evaluation (捷徑估值法), 其傳回值不一定是 true 或 false, 而是取決於第一個運算元. 如果 and 運算的第一個運算元是 false 或 nil, 就直接傳回第一個運算元 (即 false 或 nil); 否則傳回第二運算元, 例如 :

    print(nil and 5)               --> 印出 nil  (第一個是 nil/false 就傳回 nil/false)
    print(false and "OK")     --> 印出 false  (第一個是 nil/false 就傳回 nil/false)
    print(2.2 and "OK")        --> 印出 OK (第一個不是 nil/false 就傳回第二個)

    其次, 如果 or 運算的第一個運算元是 nil 或 false, 就直接傳回第二個運算元, 否則就傳回第一個運算元, 例如 :

    print(nil or 5)                  --> 印出 5  (第一個是 nil/false 就傳回第二個)
    print(false or "OK")        --> 印出 OK  (第一個是 nil/false 就傳回第二個)
    print(2.2 or "OK")           --> 印出 2.2  (第一個不是 nil/false 就傳回自己)

    運算元最好只使用 true, false/nil 來進行運算, 以免邏輯判斷出現非預期結果.

    與布林值有關的除了邏輯運算外還有關係運算子 :

    > (大於) < (小於) >= (大於等於)  <= (小於等於) == (等於) ~= (不等於)

    與其他語言比較不同的是 Lua 用 ~= 表示不等於 (C/Java/PHP 等使用 !=), 例如 :

    if (nil ~= false) then print("不相等") end   --> 印出 不相等
    if (2.2>0) then print("正數") end                --> 印出 正數

    注意, 資料型態不同的變數不能使用關係運算子加以比較, 特別是比大小運算子 ( >, <, >=, <=) 會出現錯誤訊息. 例如 :

    > if ("OK">0) then print("正數") end
    stdin:1: attempt to compare number with string
    stack traceback:
             stdin:1: in main chunk
             [C]: in ?

    必須是相同資料型態才能進行含有大小的比較, 例如 :

    if ("smart">"rich") then print("smart is better")    --> 印出 smart is better
    else print("rich is better")
    end

    而 == 與 ~= 這兩個與大小無關的運算即使資料型態不同也不會出現錯誤, 例如 :

    if ("knowledge" == 0) then print("idiot")
    else print("expert") end    --輸出 "expert"

    事實上, == 與 ~= 運算會先去看兩個運算元資料型態是否相同, 然後再去比較其內容. 前者 == 會先看資料型態是否相同, 如果不同就直接傳回 false (不用比內容啦!), 型態相同才去比較內容, 相同傳回 true, 不同傳回 false, 例如 :

    if 1==true then  --資料類型不同傳回 false
      print("same")
    else
      print("different")   --輸出 different
    end

    if ("power" == "power") then print("power is power")
    else print("power is not power") end   --輸出 "power is power"

    if ("knowledge" == "power") then print("knowledge is power")
    else print("knowledge is not power") end   --輸出 "knowledge is not power"

    而 ~= 運算只要型態不同, 就直接傳回 true 了; 若型態相同才去比內容, 相同傳回 false, 不同傳回 true, 例如 :

    if ("money" ~= 0) then print("have money")
    else print("have no money") end   --輸出 "have money"

    if ("knowledge" ~= "power") then print("knowledge is not power")
    else print("knowledge is power") end   --輸出 "knowledge is not power"

    對於 table, function 等物件, 變數儲存的是參考 (指向實際的資料儲存位置), == 與 ~= 運算比較的是其參考, 例如 :

    t1={1,2,3}
    t2={1,2,3}
    if t1==t2 then print("same")
    else print("different")  --輸出 different (t1 與 t2 是不同參考)
    end
    t3=t1   --複製參考, t3 與 t1 指向同一個 table
    if t1==t3 then print("same")  --輸出 same
    else print("different")
    end

    上例之 t1 與 t2 雖然內容相同, 但參考不同, 故 t1==t2 運算傳回 false, 而 t3=t1 是複製參考, 兩者指向同一個 table, 故 t1==t3 傳回 true.

    (3). number

    數值方面, 可以使用 10/16 進位整數, 小數, 科學表示法來表示, 但 Lua 並沒有整數型態, Lua 的數值一律使用雙準度浮點數儲存 (IEEE 754). 例如 :

    print(2000)          --> 印出 2000
    print(3.14159)     --> 印出 3.14159
    print(2e3)            --> 印出 2000.0
    print(2E-2)          --> 印出 0.02

    雖然使用浮點數來表示整數, 但在 Lua 不會產生進為誤差 (rounding errors).

    科學表示法的指數用 e 或 E 都可以. 如果要表示 16 進位數, 數值就要以 0x 或 0X 開頭, 例如 :

    print(0xff)          --> 印出 255

    數值的運算子有如下七個 :

    + (加) - (減) * (乘) / (除) % (餘數) ^ (次方) - (單元運算, 負數), 例如 :

    print(1/2)       --> 印出 0.5
    print(5%3)     --> 印出 2
    print(2^10)    --> 印出 1024.0
    print(-2)         --> 印出 -2

    當字串與數字混合運算時, Lua 會進行自動轉型, 將數字字串轉成數字再運算, 例如 :

    print(1+"100")     --輸出 101.0
    print(1+".1")        --輸出 1.1

    (4). string

    Lua 的字串與 Java 一樣是不可變的 (immutable), 對字串的操作事實上是產生另外一個新字串. 字串的用法跟 Javacript 或 PHP 類似, 可用雙引號或單引號括起來, 除此之外, 也可以用兩個中括號對 (主要用在跨行的長字串), 例如 :

    a="Hello"
    b='World'
    print(a,b)                          --> 印出 Hello   World
    print([[Hello World!]])    --> 印出 Hello   World!

    如果字串中有引號時, 只要秉持成對 (matching) 與替換 (alternate) 原則就不會弄錯, 例如 :

    print("This is Tony's book.")   --> 印出 This is Tony's book.

    這裡所有格必須用單引號, 因此整個字串要用雙引號括起來. 如果用單引號就會出錯 :

    print('This is Tony's book.')   --> 錯誤! stdin:1: ')' expected near 's'

    如果堅持用單引號, 則所有格必須用倒斜線跳脫 :

    print('This is Tony\'s book.')   --> 印出 This is Tony's book.

    或者乾脆用雙重中括號免得麻煩 :

    print([[This is Tony's book.]])   --> 印出 This is Tony's book.

    常用的跳脫字元如下 :

    \" : 雙引號
    \' : 單引號
    \n : 換行
    \r : 回車
    \\ : 倒斜線

    字串也可以用 ASCII 編碼來表示 (如果覺得這樣很有趣, 又不會太麻煩的話), 例如 ABC 的 ASCII 編碼以十進位表示分別為 65, 66, 67, 所以要印出 "ABC" 可以用倒斜線跳脫 ASCII 碼來表示 :

    print("\65\66\67")     --> 印出 ABC

    也可以用十六進位表示, 但要加一個小寫 x (不能用大寫 X) :

    print("\x41\x42\x43")     --> 印出 ABC

    參考 : Wiki : ASCII

    對於可能會超過一行長字串, 為了編輯的方便需要拆成數行時, Lua 提供兩個方式來輸入長字串, 第一個是用 [[ 與 ]] 符號來包圍一塊長字串區域, 例如 :

    a=[[This is a very
                        very
                        very ...
                                long story.]]
    print(a)

    印出的結果與編輯時的樣子相同, 會保留所有的空格.
    第二個方法是用跳脫字元 \z 來串接長字串, 例如 :

    a="This is a very \z
                       very \z
                       very ... \z
                              long story."
    print(a)    --> 印出 This is a very very very ... long story.

    可見使用 \z 不會保留空格, \z 會直接串接到下一行的非空白字元.

    Lua 取得字串長度是在字串前面加個 "#", 例如 :

    print(#"ABC")          --> 印出 3
    a="Hello World!"
    print(#a)                  --> 印出 12

    也可以使用內建的 string.len() 函式, 例如 :

    print(string.len("Hello World!"))    --> 印出 12

    至於串接字串, 有別於 Javacript 用加號, PHP 用點號, Lua 是用連續兩個點號來串接字串, 例如 :

    print("Hello".." ".."World!")     --> 印出 Hello World!

    由於 Lua 是弱型態語言, 當數值字串進行數值運算時會自動進行型態轉換, 例如 :

    print("12" + 3)            --> 印出 15, 會先將 "12" 轉成 12 再加 3.
    print("4.5e25" * "3")    --> 印出 1.35e+026

    但如果不是數值字串, 就會無法轉成數值而發生錯誤, 例如 :

    > print("1 hundred" + 900)   --> 1 hundred 不是數值
    stdin:1: attempt to perform arithmetic on a string value
    stack traceback:
            stdin:1: in main chunk
            [C]: in ?

    反之, 當數值進行字串運算時, Lua 也會自動將數值先轉成字串再運算, 例如 :

    print(20 .. 15)   --> 印出 2015

    注意, 這裡數值與字串串接運算符 .. 之間必須有一個空格, 否則第一個點會被認為是小數點而產生 malformed number 錯誤.

    以上是數值與字串的隱性型別轉換, 顯性的轉換則是利用 tonumber() 與 tostring() 函式, 例如 :

    if (tonumber("2e2") == 200) then print("match") end     --> 印出 match
    if (tonumber("10.5") == 10.5) then print("match") end    --> 印出 match

    (5). Table (表格)

    Lua 的 table 其實就是關聯式陣列 (associative array), 這是 Lua 唯一的資料結構, 所有的資料型態都是用 table 來實作的, 例如全域變數就是儲存在名為 _G 的特殊表格中, 宣告一個全域變數 a="123" 其實跟 _G["a"]="123" 是一樣的. 在其他語言中常見的陣列 (array), 串列 (list), 紀錄 (record), 佇列 (queue), 或集合 (set) 等, Lua 都用 table 來實現.
    Lua 的 table 是用大括號來宣告的 :

    t={}    --> 宣告一個空的表格

    表格是一種關聯式陣列, 所以可以用數字當索引, 也可以用字串當索引, 事實上, 在 Lua 中除了 nil 外, 任何值都可以當 Table 的索引, 例如 :

    t={}
    t[0]="peter"
    t[2]=23
    t["2"]=45                        -- 這跟 t[2] 無關
    t["gender"]="female"
    print(t[2])     --輸出 23
    print(t["2"])    --輸出 45
    print(t["age"])   --輸出 nil (無此元素不會出現錯誤, 而是認為新元素賦值 nil)

    當然也可以在宣告時同時賦值, 例如 :

    t={[0]="peter", [2]=23, ["gender"]="female"}     -- 注意喲, 索引必須用中括號

    如果索引名稱符合識別字限制 (英數字與底線組合, 不以數字開頭), 則中括號與引號可以省略, 例如 :

    t={name="peter", age=23, gender="female", _class=0}

    所以, 索引 a 與 ["a"] 是同樣的意思, 下列宣告前後都是同一元素, 後者的會蓋掉前者 :

    t={["a"]=123, a=456}
    print(t["a"])    --> 印出 456 (被覆蓋了)     

    Table 的最後一個元素若跟著逗號是允許的, 例如 :

    t={1,2,3,}

    這對於以迴圈自動產生 Table 元素非常方便, 因為不需要花功夫去除最後一個逗號.        

    雖然都是關聯式陣列, 但 Lua 的 table 跟 PHP 的關聯式陣列在索引的使用上是不一樣的, 以數值當索引時, PHP 只能用 0 與正整數當索引, 而 table 的索引則不限於整數, 可以是任何數值, 可用實數, 也可以是布林值, 例如 :

    t={}
    t[2.2]="too small"
    t[2.2e10]="too big"
    t[-199.99]="negative"
    t[true]=true
    t[false]=false

    對於字串索引, Lua 也提供點符號來存取表格, 這跟物件或紀錄 (record) 的用法是一樣的, 例如 :

    t={}
    t.name="peter"                  -- 相當於 t["name"]
    t.age=23                            -- 相當於 t["age"]
    t.gender="female"            -- 相當於 t["gender"]
    t._ID1="S1122334455"    -- 相當於 t["_ID1"]

    注意, 這裡點後面的欄位名稱受識別字命名法規範, 亦即只能用英文字母, 底線與數字之組合, 而且不能用數字開頭, 例如 t.2xy 是不合法的.
    這種點符號最容易讓人誤解之處是, 點後面的欄位名稱是個字串, t.a 意思是 t["a"], 而不是t[a], t.a 的 a 就是字串 "a", 而 t[a] 的 a 是指一個變數 a, 所以 t[a] 的索引決定於 a 變數的值, 例如 :

    t={}
    a="x"
    t[a]=1             -- 即 t["x"]=1
    print(t.a)         --> 印出 nil (因為 t.a 就是 t["a"], 並未定義故為 nil)
    print(t[a])       --> 印出 1 (因為此 a 為變數, t[a] 即 t["x"], 已設為 1)

    如果要用 table 來實作一般的陣列, 而非關聯式陣列, 那就略過索引的指定, 直接把元素的值放在中括號內即可, 例如 :

    t={1, 2, 3}
    print(t[0])         --> 印出 nil
    print(t[1])         --> 印出 1
    print(t[2])         --> 印出 2
    print(t[3])         --> 印出 3

    如果要知道陣列長度 (即有幾個元素), 跟取得字串長度一樣, 使用 # 運算子即可 :

    t={"a", "b", "c", 1, 2, 1.7e-34}
    print(#t)     --> 印出 6

    但要注意的是, 存取元素時, 索引是從 1 開始算的, 而不是像 Javascript 或 PHP 那樣是從 0 開始, 因為在不指定索引的情況下, Lua 的整數索引預設是從 1 開始數的, 這在 Lua 是普遍的法則. 要不, 你就得自行指定索引 (愛從哪開始就從哪開始) :

    t={[0]=1, [1]=2, [2]=3}
    print(t[0])         --> 印出 1
    print(t[1])         --> 印出 2
    print(t[2])         --> 印出 3
    print(t[3])         --> 印出 nil (未定義)

    但這樣就失去懶人的目的了.

    Lua 允許使用負數做為陣列索引, 但是 # 運算子卻只能計算索引為 1 起始的整數索引元素, 例如 :

    t={}
    for i=-5,5 do
      t[i]=i
    end
    print(#t)   --輸出 5

    表格的元素也是可以是另一個表格, 謂之巢狀表格, 而且可以要多深就有多深, 例如 :

    t={name="peter", im={qq="1234567890", skype="aaa"}}
    print(t.im.qq)                  --> 印出 1234567890
    print(t["im"]["skype"])   --> 印出 aaa

    可以用 key 或 index 兩種方式來存取巢狀表格的元素.

    表格的元素也可以由函數的傳回值來產生, 例如 :

    function odd()
      return 2,4,6,8,10   --傳回多值
    end
    t={odd()}     --表格的元素由 odd() 函數產生
    print(table.unpack(t))   --輸出 2      4      6      8      10

    上例中先呼叫自訂的 odd() 函數, 用其傳回值來當元素, 再呼叫 table.unpack() 函數來拆解表格.

    (6) function (函式)

    Lua 的函數跟 table 一樣是個物件, 它其實是用 table 建構的. Lua 提供豐富的內建函數, 也可以利用 function 保留字來自定函式以便將演算邏輯抽象化 (abstract). 內建函數中最常用的是 print(), 用來在螢幕上輸出資料. 上面範例都只用了一個參數, 實際上 print() 可以接受多個參數, 各參數用逗號隔開, 輸出時各參數會以 tab 隔開 (8 個空格), 例如 :

    name="tony"
    print("Name=", name)   --輸出 "Name=    tony"
    print(1,2,3,"abc")    --輸出 "1       2       3     abc"

    Lua 的函式屬於第一級函式 (first-class function), 亦即, 函式可以像數值或字串等一般的值一樣被指派給一個變數儲存在資料結構中, 例如 :

    a=print    --將系統函數 print() 指派給變數 a
    a("ok")    --輸出 ok

    函數也可以當作引數傳遞給另外一個函式, 或者被當成回傳值傳回給呼叫者, 當然也就具有閉包 (closure) 性質了. 除了 Lua 外, 其他具有 first-class function 功能的語言有 Python, PHP, Javascript, 以及 Ruby 等.

    # Wiki : first-class function

    函式的宣告包括 scope(=local, 可有可無), function name, argument(可有可無), body, return value (可有可無), 與 Javascript/PHP 不同的是, Lua 語法用 end 標誌函式終點, 不用大括號 (已被用在 table) :

    scope name(arguments)
        body
        return values
    end


    當傳入比宣告之參數還要多的引數時, 多餘的引數會被丟棄; 反之, 若傳入之引數比參數少, 那些沒有傳入引數之參數值預設為 nil. 例如 :

    function f(x,y)
       print(x,y)
    end

    則 :

    f(1,2,3,4)         --輸出 1   2   
    f(1)                  --輸出 1   nil

    函數即使沒有參數, 呼叫它時也需要小括弧, 例如 func1(), 但有一個例外, 就是當函數只有一個參數而且此參數是字串或表格時, 可以不用小括弧, 例如 :

    print "I love you"         --輸出 I love you
    print {name="Tony"}    --輸出 table: 0075e350

    Lua 函數不能像 PHP 那樣在參數列中賦予參數預設值, 不過可以用 or 運算子來模擬預設值, 例如 :

    function inv(a)
       local a=a or 0   --用 or 模擬預設值=0
       return -a
    end
    print(inv())      --輸出 0
    print(inv(2))    --輸出 -2

    此例中的自訂函數 inv() 會回傳相反數, 當沒有傳入參數時, 區域變數 a 為 nil, 照 or 運算規則, 若第一運算元為 false/nil, 就傳回第二運算元 0, 達到模擬預設值目的.

    Lua 的函數還有一個其他語言所沒有的特性, 就是 Lua 函數可以傳回多個回傳值. 例如內建函數 string.find() 就會傳回兩個整數值, 此函數用來在字串中尋找子字串, 其兩個傳回值分別代表所找到之第一個子字串之開始與結束索引, 例如 :

    str="LuaLua"
    print(string.find(str, "Lua"))     --輸出 1       3
    s, e=string.find(str, "Lua")   --利用多重指派取得兩個傳回值
    print(s, e)   --輸出 1      3

    在自訂函數中傳回多值時, 各值以逗號隔開即可, 呼叫時用多重指派取得各傳回值, 例如 :

    function myfunc() {
      local a, b, c, d
      --do somthing
      return a,b,c,d
    end
    w,x,y,z=myfunc()

    Lua 函數也提供不定長度參數功能 (variadic), 參數列使用 ... 來表示一個不確定長度的串列, 它相當於是一個會傳回多值得函數, 可以用 print(...) 顯示其傳回值, 例如 :

    function sum(...)
      local s=0
      for i,v in ipairs{...} do   --呼叫內建函數 ipairs() 拆解陣列 {...}
         s=s+v
      end
      return s
    end
    print(sum(1,2,3,4,5))     --輸出 15

    Lua 沒有直接支援名稱引數 (named argument), 但是可以用表格來模擬, 例如 :

    function profile(args)
      print(args.name)
      print(args.gender)
      print(args.age)
      print(args.phone)
    end
    profile({name="Tony",gender="male",age=50,phone="0911092288"})

    因參數為表格或字串時, 函數呼叫可以略去小括弧, 故下面這樣也可以 :
    profile{name="Tony",gender="male",age=50,phone="0911092288"}
  7. 運算子的優先順序 :
    在不用小括弧限制之下, Lua 運算子的優先順序如下 :

     順序 運算子
     1 (最低) or
     2 and
     3 < > <= >= ~= ==
     4 ..
     5 + - (減)
     6 * / %
     7 not # -(負)
     8 (最高) ^
  8. 流程控制 :
    (1). 判斷 :
    Lua 的判斷式語法如下 :

     判斷式 語法 範例
     單向
     (單條件)
     if 條件式 then
        敘述
     end
     if score >= 60 then
       print ("及格")
     end
     雙向
     (單條件)
     if 條件式 then
        敘述
     else
        敘述
     end
     if score >= 60 then
       print ("及格")
     else
       print ("不及格")
     end
     巢狀
     (多條件)
     if 條件式 then
        敘述 
     elseif 條件式 then
        敘述
     else
        敘述
     end
     if score >= 90 then
       print ("優等") elseif score >= 80 and score < 90 then
       print ("甲等")
     elseif score >= 70 and score < 80 then

       print ("乙等")
     else
       print ("丙等")
     end

    (2). 迴圈 :
    Lua 只有 for 與 while 兩種迴圈, 沒有 do while 迴圈. 注意 while 迴圈必須控制條件式設定終止條件, 以免成為無窮迴圈. 如果要拜訪表格元素, 可以使用內建函數 pairs() 來取得每一組鍵-值對.

     迴圈 語法 範例
     for 迴圈
     (確定次數)
     for 起始值, 終止值 do
        敘述
     end
     local sum=0
     for i=1,10 do
       sum=sum + i
     end
     for 迴圈
     (遍歷表格)
     for key,value in pairs(表格) do
        敘述
     end
     local t={name="Tony",age=50}
     for key,value in pairs(t) do
       print(key, value)
     end
     while 迴圈 (不確定次數) while 條件式 do
        敘述
        條件運算式
     end
     local sum=0 local i=1
     while i <= 10 do
       sum=sum + i   i=i + 1
     end
     print(sum)

以上 Lua 語法摘要也參考了下列幾本書籍 :
  1. Learn Lua for iOS Game Development (Apress, 2012) 
  2. Learn Corona SDK Game Development (Apress, 2013)
  3. Corona SDK Mobile Game Development, 2nd Edition (Packt, 2015)
其他參考資料 :

# App開發新選擇—Corona SDK
# https://coronalabs.com/
# http://getmoai.com/
# http://www.cocos2d-x.org/
# Creating standalone Lua executables


2015年7月13日 星期一

第 29 週記事

本周重要記事如下 :

週六 7/11 下午二哥考全民英檢初級, 考場在我的母校, 上一次踏入母校校園是好久以前的事了, 很多景觀都變了, 但以前的班級教室, 第二, 三棟舊校舍還是存在, 而且當天工人還在施工整修拉皮呢! 離開學校快三十年了, 老師們大概都退休了吧! 望著以前常窩的圖書館, 突然發現左側的學生活動中心不見了, 蓋起了另一棟校舍, 記得以前劍道社都是在活動中心前空地活動, 令我不免有滄海桑田之慨. 不過二三棟之間的木樨花都還在 :


這一周只有我跟菁菁回鄉下, 不到五點就出發, 所以回到家時太陽都還沒下山, 真是難得. 芒果收成已經到尾聲了, 爸摘下的只剩一桶, 而樹上的大概只剩水圳邊的兩三顆而已了.

週日去全聯買了一管雀巢煉乳, 從市場一回來菁菁就裝了四杯水放在冷凍庫, 第二天中午吃完午飯迫不及待就拿出 Hello Kitty 刨冰機, 準備做芒果剉冰,  :



週六 7/11 吃過晚飯後, 把早上剛收到的 3W LED 燈拿出來, 連接到風機輸出, 再把下午去面交取貨拿到的電壓電流表也接上去, 然後開動電風扇來模擬, 發現空載時弱風可輸出約 20.4 V 電壓, 強風可達 30V :


但 3W LED 負載加上去, 電壓馬上降到 9.1V :


然而電流表卻幾乎沒動, 電流應該是十幾毫安而已, 而我這類比電流表為 0~5A, 難怪都沒動多少, 還是要買個 VA 合組的電壓電流表才行.


Lua 解譯器的用法

因為 NodeMCU 使用 Lua 語言編寫, 因此對 Lua 解譯器用法做了些測試, 整理如下, 首先下載目前最新版 Lua 5.3 :

# Lua source code download
# Lua binary for Windows
# Download lua-5.3_Win32_bin.zip (293.9 kB)

解開後會有四個檔案 :


其中 luac53.exe 是 Lua 的編譯器 (Compiler), 用來編譯原始碼; 而 lua53.exe 與 wlua53.exe 則是解譯器, 我們常用的是 lua53.exe 這個程式, 它是 console 輸出版, 點擊執行時會進入 Lua 交談模式, 可以直接執行 Lua 命令 :


在交談模式下, 也可以用 = 號代表 print 指令, 例如 :

E:\Lua>lua
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> =("Hello World!")
Hello World!
> =a
nil
> a=100
> =a
100
>

當然我們可以把程式寫成 test1.lua 檔案如下 :

print ("Hello World!")
print (a)
a=100
print (a)

然後開啟命令提示字元視窗用 lua53.exe 執行 :

D:\lua-5.3_Win32_bin>lua53.exe test1.lua
Hello World!
nil
100

但如果用 wlua53.exe 去執行的話就不會顯示結果, 因為 wlua53.exe 不會輸出結果到 console, 適用於不需顯示輸出之用. 例如參考下列網站 :

# Lua - File I/O

寫一個 test2.lua 如下 :

file=io.open("result.txt", "a")
io.output(file)
io.write("Hello World!")
io.close(file)
print ("Hello World!")

如果用 wlua53.exe 執行, 螢幕不會顯示任何結果 :

D:\lua-5.3_Win32_bin>wlua53.exe test2.lua

D:\lua-5.3_Win32_bin>

但是檢查 result.txt 檔案, 只有一筆 Hello World! :

Hello World!

如果使用 lua53.exe 來執行, 螢幕會輸出最後那個 print 指令 :

D:\lua-5.3_Win32_bin>lua53.exe test2.lua
Hello World!

D:\lua-5.3_Win32_bin>

檢查 result.txt 檔案, 仍然只有一筆 Hello World!, 因為 print 是輸出到 console 的.

當然我們可以在控制台的環境變數 path 中指定 Lua 執行檔位置, 這樣在任何資料夾下面都能執行 Lua 程式了 :


如果嫌 lua53.exe 與 wlua53.exe 太長, 也可以把版本號 53 去掉 (但 lua53.dll 的 53 不可以去掉, 因為其它三個執行檔內部必須呼叫此動態連結檔), 變成 lua.exe 與 wlua.exe, 這樣以後只要敲 lua 就可以了 :

D:\lua-5.3_Win32_bin>lua test2.lua
Hello World!

經過設定 path 後, 就能在任何資料夾下面執行 lua, 如果不指定要執行的程式, 就會顯示版本資訊後出現大於提示號 >, 進入 Lua 的交談模式 :

C:\Users\yhhuang>lua
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> print ("Hello World!")
Hello World!

在直譯器交談模式下, 如果輸入的指令不完整就按 Enter, 則提示號會變成兩個大於, 等輸入完整後才會執行, 如下所示 :

D:\lua>LUA
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> print(
>> "Hello World!"
>> )
Hello World!
>

按 Ctrl+C 或輸入 os.exit() 就可以跳出交談模式, 回到 DOS 命令模式了 (在 Linux/Unix 是 Ctrl+D).

其實不用進 Lua 交談模式, 直接在 DOS 命令提示字元視窗就可以下 Lua 指令, 只要加一個 -e 參數即可 :

D:\>lua -e "print('ok')"

Lua 語言之父 Roberto Ierusalimschy 為巴西 Pontifical Catholic University of Rio de Janeiro 教授, 版本提示號後面出現的 PUC-Rio 就是該大學的簡稱 (Rio 表示位於里約熱內盧), 詳參 :

# Lua 的創造者 -- 巴西的 Tecgraf 組織與 Roberto Ierusalimschy 教授

至於 Lua 的程式編輯器, 用記事本就可以了, 但因多年來習慣使用 EditPlus, 所以我也下載了 Lua 的語法, 雖然是 5.1 版的, 但 Lua 到五版後就很穩定了, 所以沒啥差異 :

# 下載 EditPlus 的 Lua 語法檔


2015年7月10日 星期五

NodeMCU 物聯網開發平台

之前在露天找 Arduino 模組時, 有注意到 NodeMCU 這塊主控板, 它是以 ESP8266(ESP12) 這顆 WiFi SoC 晶片為基礎, 集成 WiFi, GPIO, PWM, ADC, I2C, 1-Wire 等功能的主控板, 採用簡潔的 Lua 語言, Lua 標榜可以很容易內嵌到其它語言中, 內核非常小, 執行效率高, 非常適合物聯網應用開發, 因為它內建了 WiFi 功能!


上圖中, 右半部突起來的小 PCB 就是 ESP8266 模組, 左半部是 USB 晶片與插槽, RESET 按鈕等元件, 亦即這個板子組合了 USB 晶片 (CP2102) 與 ESP8266, 並燒錄了 NodeMCU 韌體, 接上電腦 USB 即可開始用 Lua 寫程式了. 當然也可以單買 ESP8266 模組加上 TTL 轉 USB 板連接電腦, 自行燒錄 NodeMCU韌體, 詳參高手 Proteus 的大作與所販賣之套件組 :

# ESP8266 ( ESP-01 ) 無線模組燒錄 NodeMCU 韌體 ( Lua 語法支援 )
# 萬物皆聯網-ESP8266 IoT(Internet of Things)入門學習套件
# NodeMCU(DevKit v1.0) Lua WiFi 物聯網開發板 ( 4M Bytes flash )

一般 WiFi 主要的缺點是耗電大, 而 NodeMCU 具有電源管理, 進入睡眠時功耗很低, 據下列資料顯示, 峰值時約 0.6W, 深度睡眠狀態僅消耗 79 uA, 2600mAH 的鋰電池可用三年 :

# http://bbs.minikm.com/forum.php?mod=viewthread&tid=793
# http://tim.jagenberg.info/2015/01/18/low-power-esp8266/

NodeMCU 的官網, 檔案管理網站, 以及 Lua 相關連結如下 :

# NodeMCU 物聯網開發平台
# NodeMCU GitHub
# Wiki : NodeMCU
# Wiki : Lua
# Lua Reference
# Programming in Lua
# Lua programming gem
# 下載 Lua
# Scripting系統概論與Lua簡介

此板體積小功能強大, 而且有把 ESP8266 的全部 10 個 GPIO 接腳接出 (一般 ESP8266 模組只接出 GPIO0 與 GPIO1), 因此可以連接感測器, 本身就能取代 Arduino 作為物聯網的主控元件. 最重要的是 ~~~ 價格便宜! 露天可找到約 $260 的板子 (第一版) :

# IOT 開發板 ESP8266 NodeMCU ESP-12 學習套件 $250
# IOT ESP-12 ESP8266串口 工業級 穩定版 測試板全套 !全IO引出 $200
# 新款 NodeMcu Lua WIFI 物聯網 開發板 ESP8266串口wifi模塊 
# NodeMcu Lua 物聯網 開發板 基於ESP8266 ★264121-039 ★ $260
# App HTML5 開發板 WF8266R ESP8266 物聯網 IoT 智慧家電 WIFI Arduino $250
# NodeMcu Lua WIFI 物聯網 開發板 基於ESP8266 $280
# ESP-12 ESP8266串口 工業級 穩定版 測試板全套 全IO腳引出 $195

不過目前最新的 NodeMCU 已經是第二版, 價格稍貴 :

# NodeMCU v2 - 基於 Lua 之 ESP8266 WiFi 開發板
# NodeMCU v2 Lua腳本 第二代 超簡單的物聯網開發平臺 物聯網開發板 ESP8266 wifi $340
# MCUapps™第二代 NodeMCU v2(DevKit v1.0)Lua ESP8266 WiFi 開發送 USB線 $355

淘寶網也有很多, 最便宜 RMB $33 就可買到, 折合台幣約 160 元, 似乎較便宜, 但加上郵資 (一般約 RMB $24), 其實跟露天價格也差不多.

# 淘寶網 NodeMCU

Youtube 上有許多 ESP8266/NodeMCU 的實作與教學影片 :

# Nodemcu Esp8266 development board
# ESP8266 NodeMCU RC car control over WiFi using RoboRemo app
# Cheap and Easy WiFi (IoT) Tutorial Part 1 - ESP8266 Setup/Intro
# Cheap and Easy WiFi (IoT) Tutorial Part 2 - ESP8266 Arduino Code
# Wireless Module on Breadboard - NRF24L01+ and ESP8266
# How to work with the NRF24L01+ Best Wireless Communication for Arduino
# Overview for WebEditor for ESP8266 with NodeMCU
# $5 Arduino WiFi Module!? ESP8266 mini Tutorial/Review
# $3 Wifi module part 1: FTDI Test
# $3 Wifi module part 2: ThingSpeak Demo
# $3 Wifi module part 3: NodeMCU LUA Interpreter
# $3 Wifi module part 4: WebServers
# $3 Wifi module part 5: Socket.io
# $3 Wifi module part 6: Unlimited I/O pins?!
# $3 Wifi module part 7: WiFi I/O
# $3 Wifi module part 8A: WiFi I/O Schematic Diagram
# $3 Wifi module part 8B: WiFi I/O NodeMCU LUA Code
# $3 Wifi module part 9: X-10 Hacking
# $3 Wifi module part 10: A World Record?!
# The ESP-8266 module ported to Arduino - An Introduction
# ESP8266 Arduino IDE HowTo
# ESP8266 - Getting Started & Connected.
# ESP8266 Part1 WiFi Basics, AT Commands, Wiring & 'TRAPS'
# ESP8266 wifi Part2 - Getting & Sending data via Internet
# Lua on the ESP8266 - Part 1
# Lua on the ESP8266 - Part 2 "The Burglar Alarm"
# First Impression on the ESP8266 Serial-to-WiFi Module
# ESP8266 Hack #1: Web Enabled LED - WiFi Internet-of-Things IoT
# ESP8266 Hack #2: Web Enabled LED - WiFi Internet-of-Things IoT
# ESP8266 Hack #3: Web Enabled LED - WiFi Internet-of-Things IoT
# ESP8266 Hack #4: Web Enabled LED - WiFi Internet-of-Things IoT
# 【技術教學】打造Wifi遙控車(之一):使用 ESP8266進行 Wifi無線通訊
# 【技術教學】打造Wifi遙控車(之二):整合 ESP8266 與 Arduino
# 【作品】Wifi 遙控車!
# ESP8266 How To Flash NodeMcu Firmware
# ESP8266 規格 (Nurdspace)
# ESP8266 的 AT 指令集
# ESP8266 Lua Loader
# 如何燒錄 ESP8266 無線模組的韌體

至於 ESP8266 的 WiFi 傳輸距離, 有人在馬里蘭州用 TP-LINK 無線路由器測試, 只用 ESP8266 板上天線時可達 366 公尺遠; 加裝天線後可達 479 公尺; 若基地台使用碟形天線發射, 則 ESP8266 裸板可達 3.71 公里, 接天線則高達 4.28 公里, 真是太驚人了! 參見 :

# ESP8266 Wifi Range/Distance Tests (Wi07C)


關於小型風力發電機的負載與控制器

最近完成小型風力發電機安裝後, 接下來要考慮發出來的電要做甚麼用? 此部發電機額定 120V 直流, 但沒標明額定電流, 從外線粗細判斷, 我想可能是 0.5A, 這樣額定功率是 30W (真有這麼大嗎?), 這樣可以點亮 3W LED 燈 10 盞. 其機身銘牌如下 :


這款型號為 SS3425D120 的發電機我在淘寶網有找到, 標價人民幣 25.8 元, 至台灣運費 40 元, 總計 65.8 元折合台幣約  330 元, 跟我當初向台中賣家買的價格差不多 :


其規格如下  :

RPM 1000 : 50V
RPM 2000 : 100V
RPM 3000 : 150V

還特別標註 : 此發電機發電電壓高, 電流不大. 我手上這顆發電機確實可發出 80V 的電.

其實淘寶還有很多這樣的發電機, 例如下面這顆是有 115 度過熱保護的, 功率 30W, 附一顆橋式整流器 (作電動機時用), 人民幣 16.63 元, 運費到左營 24 元, 合計 40.63 元, 折合台幣約 200 元, 更便宜 :


等目前這個風機證實運轉 OK, 再購買這一顆 (感覺線較粗電流會較大). 我計算了一下資費 : 

買一顆 : 郵資 24  (中10台14), 商品 16.63, 共 40.63(NT$206), 商品占總價 41%, 平均 NT$206
買三顆 : 郵資 40  (中14台16), 商品 49.88, 共 49.88(NT$455), 商品占總價 55.5%, 平均 NT$151
買五顆 : 郵資 54  (中16台38), 商品 83.15, 共 137.13(NT$695), 商品占總價 60.6%, 平均 NT$139
買十顆 : 郵資 84  (中22台62), 商品 166.25, 共 250.25(NT$1266), 商品占總價 66%, 平均 NT$126

看來買越多越划算, 不過我只要買五顆就夠了.

也可參考另一款電流較大的 (1.8A, 帶散熱風扇, 軸 5mm, 但只有前滾珠軸承) :

至於葉片法蘭, 可向下列賣家購買 (內徑 8mm), 一組 $150 :


目前想到的用途有二, 首先是想在側門水圳的蓮霧樹上安裝一個 LED 燈, 以便晚上要洗東西時照明之用. 利用 Arduino 或 ATTiny85 配合紅外線感測器, 當有人在偵測範圍活動時就點亮, 否則就關閉.  或者簡單些使用光度感測器, 到了晚上就點亮, 白天就關閉. 

我在露天找 LED 燈, 發現大部分是交流 110V/220V 用的, 這樣還要將 12V 蓄電池經過逆變器轉成 110V 後供電, 除了麻煩之外也會有電力轉換損失. 最後找到這款 12V 直流的 MR16 LED 燈具, 直接使用 12V 電瓶輸出效率較高 :


第二個負載是用來驅動之前買的 20W 小水泵 (ㄅㄥˋ), 將水圳的水抽到菜園去, 雖然抽水量很小, 但積少成多一整天下來應該可以存滿菜園的大水桶 :


這兩個負載加起來沒有超過 30W, 應該 OK. 

至於電力控制器, 露天這款 $259 元的根據標題, 似乎可以拿來給風力發電用 :


而綠市集這個據老闆說是太陽能專用, 風機可能不行 (?) :

# 【綠市集】太陽能控制器 10A/12V/24V通用 自動識別 太陽能路燈控制器A0151 $350

因為我這顆發電機屬於高電壓低電流, 強風時可達 80V 以上, 上面兩款控制器無法得知其輸入電壓規格, 我想通常都在 40V 左右, 所以若直接把風機輸出接到上面兩款控制器, 恐有燒毀之虞. 所以考慮先用一個穩壓電路板把電壓限制在 15V 後再灌到充電控制器, 通常使用 LM317 或 LM2596 穩壓 IC 來做, 特別是 LM2596HVS 可耐壓 60V, 參考下列兩款 :

# LM2596HVS-ADJ 輸入4.5V~60V DC-DC 降壓模組 直流可調降壓模組 $37
# 小翔電子 DC-DC 可調降壓模組輸入電壓5~60V,輸出可在1.25V~30V之間任意調整 $100

關於穩壓電路, 可參考 :

# LM317電路圖參數資料
# LM317/LM337 穩壓電路 - 喬治查爾斯電子電路網
# 好用的 LM317
# LM317是什麼?
# LM317穩壓電路板
# 電解電容 100V以上
# DC-DC 降壓器LM317代LM7805 超低紋波 /線性可調 電壓穩壓電路 RMB $7.5

至於鉛酸蓄電池, 考慮 WP1.2 或 WP7.2, 價差約 40~70 元, 但容量卻差了 6 倍 ... 所以似乎 WP7.2 較划算, 但小電流發電機要充飽 7.2Ah 可能需時較久, 還要考慮負載多少.

# 三多電池+附發票+LONG廣隆WP1.2-12 12V,1.2AH 鉛酸密閉式蓄電池 $294
#【三多電池】LONG廣隆電池WP7.2-12 12V,7.2AH UPS不斷電系統鉛酸電池 $336
# 全電行電池快遞☆LONG WP 1.2-12 12V 1.2A $260
# 全電行電池專賣☆(1箱4顆免運) YUASA湯淺REC14-12 12V,14AH 電動車 $750 (深循環)

參考學術論文 :

# 結合風力發電機與太陽能電池模組之混合式充電系統之研製
# 太陽能與風力發電複合系統之研製

其他 :

# 屋頂風力發電機工作四年多了
# 電工達人 改造節能屋 晴天電力自給自足
# 大灣高中-廢棄吊扇改直立風力發電機(完整版) 
# 簡單型-DIY垂直式風力發電機
# 臥式 連座軸承 帶座軸承 軸徑25mm FL005 滾珠軸承座
# 臥式 連座軸承 帶座軸承 軸徑30mm FL006 滾珠軸承座
# 加厚加長法蘭座不鏽鋼管座衣杆架座圓管座衣杆固定配件座衣櫃托架
# 304不鏽鋼毛巾座 掛衣杆法蘭底座 圓管固定座 支撐座 鋼管托


2015年7月9日 星期四

甘厝社區的水圳發電

同事聽說我要架設一個小型風力發電機, 就介紹我看這個 Youtube 影片, 不過這是水力的 :

# 100年度減碳環境競賽- 水車發電站 照亮阮公園 (雲林縣莿桐鄉甘厝社區)


長年流動的水圳也可以發電, 這是我去年以來每周日沿著水圳旁的產業道路到市集買菜時的一個發想. 沒想到這群熱心的社區人士已經實現了. 這不是甚麼創見, 綠能減碳缺的只是行動力而已. 這部影片是個絕佳的典範.

我另外在 Youtube 找到下面這個日本人利用田間灌溉水道發電的應用, 實在很佩服他們的巧思, 利用腳踏車車輪與花鼓發電機製作的小型水力發電機, 可以點亮夜間的路燈 :

# 農業用・用水路-ハブダイナモ発電のポテンシャルは!

田間水圳花鼓發電機

下面這個也是用自行車花鼓發電機改裝的水力發電, 花鼓是裝在中間的軸上 :

# ハブダイナモ水力発電_充電テスト

這個也是用腳踏車車輪改裝的 :

# 水力発電機の試験

還有一個更有創意, 把發電機做成船一樣放在水圳裡, 用繩子拉住以免漂走. 它的電池與控制器是放在保鮮盒裡防水, 蠻有創意的 :

# 中島洋治郎氏の小水力発電機

日本人真是厲害, 連小水溝都不放過, 都能拿來發電, 即使只是點亮路燈都好.

我們台灣是怎樣? 政府根本沒心去推綠能, 只想要搞核四. 原因我想可能是綠能若成功了, 台電幫就沒搞頭了, 龐大標案就 .... 還是不要知道太多比較好.

其他 :

# 全新shimano DH-3N70 6V-3W發電花鼓
# 0921315178 花鼓 發電機 發電花鼓 測試
# 0921315178發電機花鼓安裝應用
# 客家新聞雜誌 第330集 綠能,能不能? 3.水圳發電
# 客家新聞雜誌 第331集 綠能,能不能?6.自力發電
# 微型水力發電 建構低碳社區
# 【日本新能源】被浪費的能源 日研發「小水力發電」
# 螺旋式ピコ水力発電装置「ピコピカ」
# Homemade waterwheel
# Homemade waterwheel 2
# Water wheel power
# Wasserrad mit Generator
# water wheel generator
# Micro hydro project video


小型風力發電機架設 : part 4

上週日 7/5 因水某有回鄉下, 不用我下廚, 所以就有些時間繼續進行我的小型風力發電機架設工作. 其實下一步該怎麼做在腦海中已經有藍圖, 只是沒時間準備材料, 因為週日早上上市集的時間都很趕, 鎮上的五金行雖然順路, 卻無法停留太久找零件或裁切.

週日早上從市集回來後開始動手組裝, 首先在底板中央鑽一個洞, 以便引出風機的電線, 然後把風機用半圓鐵箍鎖在底板上 :


然後在 PVC 頂蓋下緣鑽四個洞, 用螺絲將頂蓋鎖在底板上. 因為 PVC 管有弧度, 所以鑽的時候請爸幫我固定住, 以免鑽歪了螺絲無法正對底板的中央 :



這樣就可以將滾輪軸承鎖在底板下方 :


接著在尾翼上鑽兩個洞, 鎖在尾翼鐵片上, 這尾翼鐵片取自電腦後面的 PCI 介面卡插槽擋片, 左右各一片夾住尾翼, 也是要鑽兩個洞, 位置要對好才能順利將小螺絲穿過去. 鐵片前端各鑽一個洞, 鎖在底板後端. 然後將滾輪軸承鎖在角鐵支架上 (我是將兩支角鐵重疊成 ㄇ 字形, 以便鎖定滾輪軸承的兩隻腳) :



然後前蓋板也是左右鑽兩個洞, 中間鑽一個較大的洞給風機的軸穿過去, 鎖在底板上, 並塗上白漆, 前蓋板與 PVC 之間的縫隙會被油漆填滿, 所以也用不到熱熔膠了 :


下面這張是後視圖, 可見這種 PCI 介面卡擋片很好用, 前面折彎 90 度部分剛好可以鎖在底板後端, 這種擋片在露天就有賣, 一片約 5 元, 我是回收利用 :


最後尾翼貼上日月星電四種圖案剪紙, 塗上油漆後撕掉就 OK 了 :


雖然還可以, 但是不管怎麼細心還是塗不均勻, 我覺得下次還是買噴漆好了, 雖然比較貴一些, 這罐油漆留作以後補漆時用.

因為蓮花颱風即將來襲, 所以本周回鄉下時再安裝到支架上. 同時此安裝法因為電力輸出線不是在軸心, 因此不能讓風機 360 度轉向, 否則電線可能會纏繞在支架上, 所以必須裝上左右擋板限制風機轉向角度. 我當初會從固定式安裝改成旋轉式, 就是因為風向主要集中在西南與西北這 90 度範圍內.

下次設計可 360 度旋轉風機時必須改軸心裝置. 我找到下面這種內徑 30mm 的臥式連座軸承應該可以用 :

# 臥式 連座軸承 帶座軸承 軸徑30mm FL006 滾珠軸承座 $280

只要找一根外徑 30mm 的圓形鐵管或銅管當支柱即可, 然後鎖在底板下方, 這樣電線就可以從鐵管中引出, 不會有纏繞問題了.


2015年7月7日 星期二

第 28 周記事

本年第 28 周已過去, 現在星期天回來都很累, 所以周記都拖到周二才寫, 實在是懶. 要知道現在記憶的時間都很短, 上兩周的事情早已忘記, 不記下生活點滴, 以後老了真的會沒啥好回顧的.

最近社會發生很多事情, 塵爆, 希臘倒債, 小辣椒, .... 這世界變化太快, 就像走馬燈一樣, 新聞太多了, 快到令人應接不暇, 但我們很快就會忘記. 猶記得去年媽住院時, 一連串發生高雄氣爆, 復興空難等災難, 現在還有多少人關心呢. 只有受難者家屬還記得那痛楚吧. 資訊讓我覺得這世界變化得太快, 如果沒有新聞, 沒有手機, 沒有臉書, 沒有 Line, 我們的世界應該會慢很多, 煩惱也會少很多.

周六 7/3 二哥的暑期補習要開始了, 週日整天也是, 所以就留他一個人在高雄. 7/3 聽說日子不錯, 小舅的小木屋正式入厝, 但早上我們都還在高雄, 沒法參加. 下午小舅媽打電話來邀我們晚上去她家吃晚飯, 哈, 總算大功告成, 小舅回老家蓋新屋的夢終於圓了.

現在菜園我都任冬瓜到處橫行, 可說已到寸步難行的地步. 結果的狀況還不錯, 最大的一顆少說也有十公斤了, 希望今夏就能豐收 :



芒果上兩周開始採收, 除了帶些到公司給同事品嘗海頓品種的滋味外, 還送了一箱給左營阿姨, 給榮總陳主任 (感謝他在母親住院期間的關懷), 本周再帶一箱給二姨媽. 我估計應該產出 400 顆以上才對, 但感覺卻沒這麼多.

芒果結束後, 就等待七月底的龍眼了, 家中的三株龍眼以院子中的長得最好 :


比起荔枝的過甜, 我比較喜歡龍眼的味道, 雖然仔大肉薄, 但夏日的午後拿一串到後院, 邊吃邊吐仔到田裡, 也是很愜意不是嗎?

這一周的閒暇時間都花在整理兩本財經書籍的讀書筆記, 老樣子, 有人預約了, 不得不還. 一本是股素人與卡小孜父女寫的 "股票收租法,我4年賺100%", 屬於價值投資法, 以三個 15 原則選股, 搭配基本面條件篩選出定存股, 每年只操作兩次, 以持股五年賺一倍為目標. 特別是書中說明台灣五十不適合當定存股, 因為殖利率太低了, 我覺得很有道理. 但若遇到股災, 但又不知買進那些股票時, 就用力買進 0050 吧.

另外一本是方天龍寫的 "籌碼細節", 屬於籌碼面投資法. 書中提到外資與投信屬於中長期投資, 因此其持股與進出值得追蹤與參考. 不過籌碼的計算不像基本面那麼容易, 要費一些腦筋.



2015年7月3日 星期五

挖井的方法

今天在 Youtube 看到這個有趣的影片, 原來挖井的方法是這樣, 先用鏟子挖一個洞, 然後用鑽土機往下鑽出約一米深的洞後, 放下鋼管, 套入特製的掘土長筒, 一筒一筒往下挖, 這時鋼管也會下陷, 再放下第二段接管旋緊後, 當然特製的掘土長筒也要接長再繼續用往下挖, 到 6 米深時, 放入接水管, 再一段段將鋼管抽回, 真厲害呀 !

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


不過這是因為地下都是砂質土才會這麼順利, 如果是黏土層就難搞了, 還需要長長的鑽管才行 :

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


如果遇到石頭或岩盤, 我看就得放棄, 另外找尋地點重新再挖了.

用機器挖井參考 :

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