Tonino 咖啡烘焙色度檢測計 DIY – 硬體篇
上週某日看到 AppWorks 的咖啡師小丹在把玩新買的玩具: Tonino 咖啡烘焙色度檢測計,這是一個用來測量咖啡粉顏色的小裝置。把它放到磨好壓平的咖啡粉上,經過一兩秒後它就會給你一個 0 到 200 的讀數,數字越小,代表咖啡粉的顏色就越深。
大家一時興起,拿它來測膚色,沒想到 Tonino 認為小丹的膚色比我深… 但明明看起來我的皮膚就比較黑啊,這激起了我的好奇心,想知道它是用什麼方法測量咖啡粉的顏色。
上官網查了 Tonino 的規格,它說 sensor 是 “64-channel photo detector”,而測量原理則是 “polychromatic reflection”,看起來很厲害,但說穿了就是個 color sensor 而已。
首頁再往下捲,發現這居然是一個 open-source 的專案,於是 maker 的老祖宗也就是我本人又手癢了,立馬決定依樣畫葫蘆來做一個。
(小丹花了一萬八買了這一顆 Tonino,我要花多少材料可以把它做出來呢 ?)
Tiny 與 Classic
網站上開源的專案其實是上一代的 Classic Tonino,與目前正在販賣的 Tiny Tonino,也就是小丹買到的這一顆,略有不同。主要的差異是 Classic Tonino 用四位數的 LED 七段顯示器來顯示資訊,而且體型龐大,裝在一個應該是罐頭的金屬罐裡。而 Tiny Tonino 則用了一片 0.96 吋的彩色 OLED 顯示器來顯示資訊,並增加了無線充電的功能,整台的體型也縮小到可以放在掌心裡。
Classic Tonino 的硬體其實超簡單的,根據作者在 GitHub 上 Classic Tonino Hardware 專案,它其實是一個建在 Arduino 上的裝置,用了一片 3rd-party 的 Arduino Nano,一個 TCS3200 color sensor 模組,以及一片基於 Holtek HT16K33 I2C LED driver 的顯示模組。
前兩樣我手上都有,但 HT16K33 的 7-segment 顯示器模組因為比較少人用,不太容易取得。既然這是個開源的 Arduino 專案,幫它換個顯示裝置有什麼難的 ? 我手上還有一大把用 SSD1306 驅動的 I2C OLED 顯示模組,用這個來做的話能顯示的資訊比 7-segment LED 要多太多了,於是我就決定幫它換上小片的 OLED 顯示器。
我把原作者的 Classic Tonino Firmware 專案打包抓下來,很快地瀏覽了一下,發現它用到的軟體元件還蠻多的。除了常用的 I2C library “Wire” 和 EEPROM library 外,還用到了 AVR 的 low power library,用以控制處理器的耗電和睡眠等狀態。換句話說,這組程式碼應該綁 Arduino Nano 的 ATmega328 綁得很緊,不太容易 port 到其它的平台上。我本來還想把它轉移到 ESP8266 上做個無線的版本,看來是沒辦法簡單達成。
色彩之眼
Tonino 用來辨認顏色的 color sensor 是 TAOS (Texas Advanced Optoelectronic Solutions) 的 TCS3200。TAOS 是一家位在德州 Plano 的小型半導體公司,在 optical sensor 這一行非常有名。2011 年奧地利微電子 (Austriamicrosystems,現在更名叫 AMS) 以 $320M 收購了 TAOS,此後 AMS 的 ALS (ambient light sensor) 和 color sensor 等產品線都來自原來的 TAOS。
幾乎所有的 Apple 產品上都用了 ALS 來感應環境亮度以自動調節螢幕背光,而 AMS 就是 Apple 的 ALS 最主要的供應商。
TCS3200 裡面有 64 顆 110um x 110um 大小的 photodiode array,以 8×8 的形式排在一個 1mm x 1mm 的區域中。其中 16 顆 photodiodes 刷了紅色的的 color filter、16 顆刷了綠色的、16 顆刷了藍色的,而剩下 16 顆 photodiodes 則沒有刷任何 filter。
Datasheet 中列出來的頻率響應是光電二極體和 color filter 的綜合結果,是很典型的 Bayer filter 光譜,而且從 response 看來它沒有加 IR-cut filter,所以三個顏色到了 800nm 以上的近紅外光波段感度都很高,這是很典型的矽基光電二極體響應。
Photodiode array 中同樣顏色的二極體全部併聯在一起,因此等效上來說就變成一個 pixel size 16 倍大的大型光電二極體。
光電二極體的輸出是正比於照度的電流,TCS3200 裡面另外有個 current-to-frequency 轉換電路,把光電二極體的輸出轉換成頻率。因此 TCS3200 的輸出是一個 duty cycle 50%、頻率會隨照度變化的方波。
S2/S3 這兩隻腳是用來選擇要讀取 R/G/B/W 其中哪一組 photodiode 的,而 S0/S1 可以決定輸出頻率要不要過除頻器。如果不過除頻器的話,photodiode 在不飽合的狀況下最大輸出頻率是 600KHz,對某些速度比較慢的系統來說,要數這樣的頻率可能蠻辛苦的,在這種情況下可以用 S0/S1 設定把輸出頻率除以 5 或 50,犧牲一點解析度來換取較低的系統複雜度。
由於 TCS3200 的輸出是頻率,Tonino 的程式中用了一個叫 FreqCount 的 library 來計算 TCS3200 送過來的頻率。FreqCount 利用 ATmega 的硬體計數器來計算一定時間內的脈波數,藉以換算出頻率,因此輸入接腳固定在 Arduino 的 D5,也就是 ATmega328 的 T1 輸入。
Firmware 編譯
我本來以為只要把 Arduino 專案抓回來,該掛的 library 掛上,target board 設對,應該不用修改就可以直接編譯。但我按下 Arduino IDE 的 verify 後,收到一大堆錯誤訊息,大部份都是 library 找不到的問題。
我仔細看了一下,不知道為什麼作者在用 #include 引用跟 source code 包在一起的 header files 時,也用 < 和 > 去框檔名,照講這些跟 source code 在同一個目錄下的 header files 應該要用引號去框,只有放在 Arduino 系統共用 library 中的 header files 才用 < 和 > 去框。因此我又花了一些工夫,把 #include 的問題修正。
除此之外,我看到 source code 裡有一段這個:
// external lib that calls method according to serial input // slightly adapted from // https://github.com/kroimon/Arduino-SerialCommand, Version 20120522 // (increased command buffer size and readSerial // return true if command encountered) #include "SerialCommand.h"
因為我之前用過 SerialCommand 這個 library 來處理 serial console 的 command parsing,就不疑有它,直接去 GitHub 上抓了回來裝。
但裝好之後,怎麼 compile 都會卡在 readSerial 這個 function。在 Tonino 的程式碼中定義的 readSerial 是一個有 bool 型傳回值的 function,但 SerialCommand library 中的 readSerial 明明就是 void 型別,沒有任何傳回值,而且我還在 GitHub 上往前看了好幾版的 SerialCommand,readSerial 這個 function 從一開始就是 void 啊,哪來的 bool ?
原來我眼殘,沒注意到作者有寫 “slightly adapted from…”,其實他已經修改過這個 library,而且改過的程式碼就跟其它 source code 包在一起。救贖就在眼前,我卻大老遠跑去 GitHub 上抓不正確的 library,難怪又多浪費了半小時。
把以上的問題都解決後,firmware 就可以 compile 了。
我翻箱倒櫃找出一片 Arduino Nano 和 TCS3200 的 module,很快地焊了六條線把兩片板子接起來。
雖然手上沒有 HT16K33 的顯示模組,但根據經驗這些用 Wire library 驅動的顯示 library 都不做 I2C 的 error checking 和 handling,因此就算對應的 I2C slave 不存在,也不會讓程式卡住。(反過來說,如果你的 I2C 裝置出問題,你也不會知道…)
果然,程式燒進去後,我用手蓋住 color sensor,LED 就會亮起來一兩秒再熄掉,跟 Tiny Tonino 測量時的行為一樣。
(Tonino 沒有特別的開關用來觸發測量。它以固定的時間間隔去讀取 color sensor 的值,如果 color sensor 的輸出超過某個 threshold,就代表 Tonino 還看得到環境光,使用者還沒準備好要測量。如果它偵測到 color sensor 的輸出變成一個很低的值,就代表使用者已經將 Tonino 放到咖啡粉上,它就打開 LED 照亮目標物並讀取 color sensor 的輸出。)
現在我有一個沒有顯示輸出的啞巴 Tonino 了 。
OLED 顯示器
確定程式可以跑之後,我就可以進行下一步: 幫它裝上 OLED 顯示器。
我手上有很多用 SSD1306 驅動的 OLED 顯示器,主要有兩種規格: 0.91″ 128×32 的,和 0.96″ 128×64 的。
SSD1306 是一顆非常好用的 OLED driver,它接受 I2C 或三線/四線式 SPI 的控制,也有內建 charge pump 電路可以產生 OLED 面板所需的高壓 (其實也沒有很高啦,7.5V 而已)。我第一次在淘寶上發現它的時候,就覺得非常感嘆: 用了二三十年 HD44780 驅動的 LCD module 之後,終於有個像樣的 I2C/SPI 顯示裝置出來改變這一切。
這種顯示驅動晶片的腳位都非常多,以 SSD1306 來說它至少就有 128 隻陽極、64 隻陰極,再加上周邊電路需要的腳位,隨便都超過 200 隻腳。為了處理這種腳很多的顯示驅動晶片,多年來面板業發展了各種 IC 封裝和 bonding 的技術,專門用來對付這些如蜈蚣般的驅動晶片。這些驅動晶片在工廠中就已經藉 COG/TAB/COF 等製程跟顯示器封裝在一起了,因此一般使用者是不太容易看到單獨存在的顯示驅動晶片
這是我手上的一些 OLED panel,SSD1306 都已經跟面板封裝在一起了。
我找了一塊洞洞板,把 Arduino Nano 跟 OLED 面板裝在正面,TCS3200 模組裝在反面。總共需要連接的線也才 10 條: Nano 到 TCS3200 之間 6 條,前面講過,OLED 面板到 Nano 之間四條: VDD、SCL、SDA、GND。
我先隨便拿之前幫這片 OLED 面板寫的一隻小程式修改一下,燒進去,確定它會亮。
接下來我就可以開始把原來吐給 HT16K33 顯示器的資料,改寫給 SSD1306 了。
但我馬上遇到一個麻煩。
我們最常用來驅動 SSD1306 的一組 library 是 Adafruit 提供的 SSD1306 oled driver library,以及它底層的 GFX graphics core library。這組 library 功能強大又有彈性,可以畫圖、寫字、還可以做各種動畫特效,但它最大的缺點就是肥。我之前都在 ESP8266 上用它們,不管是 RAM 還是 flash 都大得好像不用管一樣,因此還沒遇過記憶體爆掉的事。但 Arduino Nano 上的 ATmega328 是一顆 flash 只有 32KB、RAM 只有 2KB 的小處理器,再看一下現在的 sketch 編譯後的結果:
Sketch uses 19064 bytes (59%) of program storage space. Maximum is 32256 bytes. Global variables use 1000 bytes (48%) of dynamic memory, leaving 1048 bytes for local variables. Maximum is 2048 bytes.
Adafruit 的 SSD1306 driver library 會為顯示器宣告一個 display buffer:128×64 的顯示器就需要 128 * 64 / 8 = 1024 bytes 的 buffer,ATmega328 僅有的 2KB RAM 就這麼被吃掉一半。現在的程式碼 compile 完後 RAM 已經只剩下 1048 bytes,再扣掉 1024 bytes 就只剩下 20 bytes,以我的經驗就算 compile 時會過,程式也會變得非常不穩定,stack 或 heap 都很容易在 runtime 時爆掉。
怎麼辦 ? Adafruit 這組 library 看來是不能用了,我得想辦法找個 non-buffered 的 SSD1306 驅動程式。
顯然我不會是第一個遇到這問題的人,網路上大家都在抱怨 Adafruit 的顯示驅動程式太佔 ATmega328 的記憶體,小顆一點的 ATmega168 只有 1KB 的 RAM,甚至連用的機會都沒有。
尋尋覓覓,所有的線索都指向一個叫 SSD1306ASCII 的 library。這是一個只顯示文字不畫圖的 SSD1306 驅動程式,作者設計的理念就是要降低記憶體的用量,因此連 buffer 也沒用。它的 “Hello World” 範例在 ATmega328 上只用了 2K 多一點的 flash 和 53 bytes 的 RAM,跟 Adafruit 的 library 相比真是一個天一個地。
於是我就把這個 library 掛上去,取代 Adafruit 的 library,開始改寫 Classic Tonino 的程式了。
原作者的 Arduino 程式碼還蠻有結構的,顯示的部分都放在 toino_lcd.cpp 裡,而且他充分運用了 Arduino C++ 語言的優勢,把顯示的程式碼包成一個物件。但原作者不知道為什麼把它取名叫 LCD,source code 裡的註解也叫 LCD 什麼的,但 HT16K33 驅動的明明就是一片 LED 啊…
修改的部分就不多說了,反正我花了幾個小時把原來要在 LED 上顯示的資訊內容都改到 OLED panel 上。原作者蠻用心的為四位數的 LED 模組設計了很多種動畫效果,用在不同的情境,但由於我的偷懶,我並沒有在 OLED 面板上重現這些效果。
總之,經過修改後,我現在有一顆功能跟原來的 Classic Tonino 一樣的裝置,而且它的測量信度蠻高的,只要遮光良好,對同樣顏色的表面,重複讀值的誤差在 2% 以內。
校正的問題
Tiny Tonino 出貨時,它的包裝裡面附了兩塊標準色盤,用來做裝置的校正。TCS3200 不同樣品間的誤差雖然不大,但用來照明的白光 LED 其實個別差異還蠻大的,因此每個裝置都要用標準色盤校正後,不同裝置間讀出來的值才能互相參考。
我本來以為我可以用小丹手上的色盤來校正做好的 Classic Tonino,但看了程式碼以後,發現 Tiny Tonino 跟 Classic Tonino 用的校正色盤不一樣。Classic Tonino 用咖啡色和紅色來校正,但 Tiny Tonino 卻用紅色和綠色來校正,兩者無法互換。
因此現在我手上雖然有一個讀值很準而且重複性很高的 Tonino,但它讀出來的值卻無法跟小丹的 Tiny Tonino 比較。
為了研究它的 calibration process,我想到 Tonino 還有一個 desktop application,可以透過 serial port 控制 Tonino 進行校正和參數讀取及設定。這個 application 是用 Python 寫的,GUI 則是用 Qt 畫的,因此可以輕易地做到跨平台。作者提供了 Windows、macOS、和 Ubuntu 的 binary,GitHub 上也有完整的 Tonino App 專案 source code 可以下載。
我只用 Python 寫過一些簡單的 script,這麼大又用到 GUI 的 application 完全不在我的管區範圍內,如果不是為了要研究它的 calibration 過程,我實在一點都不想碰它。
此時的我還不知道,為了搞定這個 application,後面還有一場硬仗在等著我。但這又是另一個故事了。
梁子凌先生:
您好!
我們正在設計第三代咖啡烘豆機, 會用到咖啡烘焙色度檢測計類似零件.
希望能有機會與您會談, 或合作.
王秋順
台中市和平區東崎路一段42號
0937-540102
所以花了多少$🤔感覺好強