學 Go 玩創客,TinyGo 初體驗(4):使用 BBC micro:bit 與 TinyGo drivers 驅動周邊元件,Part II

Photo by Clark Tibbs on Unsplash

上篇我們講了如何使用 micro:bit 和 TinyGo 來控制一些周邊元件,並下載 TinyGo drivers。在第二篇裡,我們來繼續看更多幾個範例,甚至是如何使用 TinyFont 字庫在螢幕模組上印出文字。

BH1750 亮度計

BH1750 是 I2C 介面的亮度計,能傳回環境的照度(illuminance,單位為 lux 或 lx)。市面上最容易找的版本是 GY-30 和 GY-302 兩種,外型和腳位有點差異,不過用起來是一模一樣的。

之所以選擇這玩意來介紹,是因為 BH1750 的 I2C 通訊算是蠻簡單的。我們可以藉此示範一下不使用 drivers 套件的 I2C 通訊大概是什麼樣子。

  1. Vcc -> 3V
  2. Gnd -> G
  3. SCL -> P19
  4. SDA -> P20

不使用 TinyGo drivers

package mainimport (
"machine"
"time"
)
const ( // device address and some registers
ADDRESS = 0x23
POWER_ON = 0x01
CONTINUOUS_HIGH_RES_MODE = 0x10
)
func main() { i2c := machine.I2C0
i2c.Configure(machine.I2CConfig{})
i2c.WriteRegister(ADDRESS, POWER_ON, nil) // power on
buf := make([]byte, 2) // buffer to receive data (2 bytes) for {
// read raw data
i2c.ReadRegister(ADDRESS, CONTINUOUS_HIGH_RES_MODE, buf)
// calculate illuminance
lux := ((int32(buf[0]) << 24) | int32(buf[1])<<16) / 78642
println("Illuminance:", lux, "lx")
time.Sleep(time.Millisecond * 200)
}
}

你需要一個終端機軟體(比如 Tera Term)來讀 micro:bit 的 UART 或序列埠輸出,baud rate 為 115200。多虧 micro:bit 的雙晶片架構,你什麼時候讀 UART 都可以,那不會和你上傳程式的動作產生衝突。

以上是示範 I2C 裝置的操作過程:先啟動裝置,然後給它一個某模式的 register 註冊碼,裝置就會傳回 2 bytes 的資料代表亮度。將結果稍作計算後便能得到正確的照度值。

Photo by Kaitlin Duffey on Unsplash

使用 TinyGo drivers

package mainimport (
"machine"
"time"
"tinygo.org/x/drivers/bh1750"
)
func main() { machine.I2C0.Configure(machine.I2CConfig{})
sensor := bh1750.New(machine.I2C0)
sensor.Configure()
for { lux := sensor.Illuminance() / 1000
println("Illuminance:", lux, "lx")
time.Sleep(time.Millisecond * 200)
}}

看得出來,使用 TinyGo drivers 就讓程式碼簡潔許多了。

要注意的是此驅動程式傳回的照度單位是 milliLux,也就是千分之一 lux,型別為 int32。這樣設計的目的,是盡量避免使用 float32 浮點數來傳資料給開發板。有些開發者說浮點數出錯的機率會大一點,但這個人就無法確認了。

此外 BH1750 支援幾種不同的讀取模式,其精確度與所需的等待時間都不同。目前由於 TinyGo 還沒什麼說明文件,所以真的想進一步設定的話,你得自行參閱驅動程式的原始碼以及 BH1750 的 datasheet。

在 SSD1306 OLED 顯示文字

SSD1306 是個常見的小型 OLED 單色顯示模組,常見規格有 0.96 吋 128x64 或 0.91 吋 128x32 像素兩種。有些所謂的雙色模組是有上下兩塊不同顏色而已。此外記得買只有 4 根針腳的,多於這個數目就有可能是使用 SPI 通訊的版本。

此外注意,有些中國製的 SSD1306 因為接線關係,雖運作正常但不會回應 I2C 通訊,所以一定得用特別的驅動程式來跑。

Photo by Victória Kubiaki on Unsplash

下載 TinyFont 字庫

TinyFont 是 TinyGo 團隊開發的另一個東西,也就是多種顯示器元件可用的字庫。其下載方式和 TinyGo drivers 一樣:

go get tinygo.org/x/tinyfonts

TinyGo drivers 提供的驅動程式有很基本的像素控制,不過下面我們來看結合 TinyFont 的範例。畢竟,使用小型的實體顯示幕,最大的用處當然就是用來顯示其他感測器的資料了。

我們在本範例使用的 OLED 為 128x64x64 版本。既然它也是 I2C 裝置,接線就跟前面的 BH1750 一模一樣。

package mainimport (
"image/color"
"machine"
"tinygo.org/x/drivers/ssd1306"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freesans"
)
func main() { machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32,
Width: 128,
Height: 64,
})
c := color.RGBA{0xff, 0xff, 0xff, 0xff} display.ClearDisplay()
tinyfont.WriteLine(&display, &freesans.Regular12pt7b, 20, 40, []byte("TinyGo"), c)
display.Display()
}

這裡開始複雜一點了,而且唯一做的事就是在 SSD1306 上顯示一行字而已。很有趣的是驅動程式內的常數 ssd1306.Address_128_32 正是大多 SSD1306 的 I2C 位址 0x3C;下面記得把高度設成 64。

變數 c 是 OLED 要顯示的顏色。既然只有單色,RGBA 的四個值就全部設為 0xff(255)。

你可以看到後面會把 display 物件傳給 tinyfont.WriteLine() 並指定要寫入的字型跟座標。座標似乎是代表字的左下角,所以要設多一點才不會切掉字體的頭。

Photo by Calum MacAulay on Unsplash

有個比較難理解的部分是字型部分要怎麼寫?其實是這樣的:

  1. TinyFont 的資料夾底下找你要的字型。資料夾的名稱(freemono,freesans,freeserif,gophers)代表開頭要匯入的套件 tinygo.org/x/tinyfont/資料夾名稱。
  2. 寫入的字型名稱範例:

freesans12pt7b.go:&freesans.Regular12pt7b(正常)

freemonobold18pt7b.go:&freemono.Bold18pt7b(粗體)

freeserifitalic24pt7b.go:&freeserif.Italic24pt7b(斜體)

gophers22pt.go:&gophers.Regular22pt(Gopher 字體)

Gopher(地鼠)是 Go 語言的吉祥物,而 Gopher 字體還蠻可愛的,只是必須用英文大寫,不然會變成亂碼。

此外還有一些小型字體,只要匯入 tinygo.org/x/tinyfont 即可使用:

&tinyfont.Tiny3x3a2pt7b

&tinyfont.Picopixel

&tinyfont.TomThumb

&tinyfont.Org01

TinyFont 在編譯時只會留下你實際使用的字體,不過這樣的缺點是編譯起來明顯得等一陣子。開發者指出你可以複製你需要的字體檔到自己的程式目錄(並重新命名),不過嘛……想換字體時就有點麻煩了。

迷你專題:溫度與氣壓顯示器

Photo by yue su on Unsplash

現在,我們要結合另一個感測器 — — BMP180 溫度與氣壓模組,將其讀數顯示在 OLED 螢幕上。BMP180 是另一個 I2C 裝置,至此接線應該難不倒你了。

package mainimport (
"image/color"
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/bmp180"
"tinygo.org/x/drivers/ssd1306"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freeserif"
)
func main() { machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
sensor := bmp180.New(machine.I2C0)
sensor.Configure()
connected := sensor.Connected()
if !connected {
println("BMP180 not detected")
return
}
println("BMP180 detected")
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: ssd1306.Address_128_32,
Width: 128,
Height: 64,
})
c := color.RGBA{0xff, 0xff, 0xff, 0xff}
display.ClearDisplay()
for { temp, _ := sensor.ReadTemperature()
pressure, _ := sensor.ReadPressure()
println("Temperature:", temp, " / Pressure:", pressure)
temp_str := strconv.FormatFloat(float64(temp)/1000, 'f', 1, 64) + " *C"
pressure_str := strconv.FormatFloat(float64(pressure)/100000, 'f', 2, 64) + " hPa"
display.ClearBuffer()
tinyfont.WriteLine(&display, &freeserif.Bold12pt7b, 0, 24, []byte(temp_str), c)
tinyfont.WriteLine(&display, &freeserif.Bold12pt7b, 0, 48, []byte(pressure_str), c)
display.Display()
time.Sleep(time.Millisecond * 100)
}
}

這裡特別得講的地方,是使用 Go 語言的 strconv 套件來把浮點數轉成字串(’f’ 代表一般浮點數,不使用科學記號),而且順便將小數點截短(溫度 1 位,氣壓 2 位)。

專題2:在 LCD1602 顯示 micro:bit 的旋轉角度、羅盤方位與溫度

Photo by Denise Jans on Unsplash

第二個範例是直接使用 micro:bit 本身的 LSM303AGR 三軸加速計/羅盤,利用內建功能得出俯仰、轉動角度以及羅盤方位,甚至是該感測器內建的溫度計,並顯示在便宜好用的 I2C 版 LCD1602(正確來說是裝有 I2C 轉接板的 HD44780 螢幕模組):

package mainimport (
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/hd44780i2c"
"tinygo.org/x/drivers/lsm303agr"
)
func main() { machine.I2C0.Configure(machine.I2CConfig{
Frequency: machine.TWI_FREQ_400KHZ,
})
accel_mag := lsm303agr.New(machine.I2C0)
accel_mag.Configure(lsm303agr.Configuration{})
lcd := hd44780i2c.New(machine.I2C0, 0x27)
lcd.Configure(hd44780i2c.Config{Width: 16, Height: 2})
for { lcd.ClearDisplay() pitch, roll := accel_mag.ReadPitchRoll()
heading := accel_mag.ReadCompass()
temp, _ := accel_mag.ReadTemperature()
lcd.SetCursor(0, 0)
lcd.Print([]byte("Pit:"))
lcd.SetCursor(4, 0)
lcd.Print([]byte(toStr(float64(pitch/1000000), 0)))
lcd.SetCursor(8, 0)
lcd.Print([]byte("Rol:"))
lcd.SetCursor(12, 0)
lcd.Print([]byte(toStr(float64(roll/1000000), 0)))
lcd.SetCursor(0, 1)
lcd.Print([]byte("Dir:"))
lcd.SetCursor(4, 1)
lcd.Print([]byte(toStr(float64(heading/1000000), 0)))
lcd.SetCursor(8, 1)
lcd.Print([]byte("Tmp:"))
lcd.SetCursor(12, 1)
lcd.Print([]byte(toStr(float64(temp/1000), 0)))
time.Sleep(time.Millisecond * 200)
}
}func toStr(f float64, d int) string {
return strconv.FormatFloat(f, 'f', d, 64)
}

結語

這兩篇我們看了如何使用 TinyGo drivers 的幾種驅動程式及 TinyFont 字庫,並搭配 BBC micro:bit 來實作。(其實不只是上面這兩個套件存在,還有一個叫做 TinyDraw 的簡單繪圖庫,不過筆者在此沒有著墨就是。)

TinyGo 的開發者的願景似乎是希望 TinyGo 將來能發展成像 Arduino 那樣的生態系,擁有豐富的開發板及驅動程式支援。這或許需要有更多人參與,才能更快成長到可實用化的階段吧。不過如各位所見,事情已經有個開端了。

Photo by Jared Erondu on Unsplash

Former translator, after-hours Maker, sunny-day analog film shooter. Currently a junior tech-book editor based in Taiwan. https://krantasblog.blogspot.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store