😶🌫️
TinyGo + MH-Z19C(CO2 センサー) で、室内の二酸化炭素濃度を計測してみる
やりたいこと
タイトルの通り。データ通信には PWM と UART を使用する方法のうち、UART 側で実施する。
また、数値のデータを保存して、ある程度の妥当性は確認したい。
最終的にはディスプレイに表示したい。
結果
計測が成功し、保存した数値データから上手く動いてそうであることを確認することができた。
- 数値をグラフ化したもの
- 詳細:計測を開始し、屋内 → 屋外 → 屋内へ移動させた結果、屋外では 400ppm となっており、意図通りに数値が変動している(400ppm → 外気環境は一般的に400ppmとされている)
- 詳細:計測を開始し、屋内 → 屋外 → 屋内へ移動させた結果、屋外では 400ppm となっており、意図通りに数値が変動している(400ppm → 外気環境は一般的に400ppmとされている)
「ディスプレイに表示」については・・・、気が向いたら追記します。
背景
Arduino IDE でライブラリを使用した結果。動作することは確認した。
しかし、以下の問題が発生したため、先日セットアップしたロジックアナライザ で動作確認しつつ、TinyGo で勉強も兼ねて実装してみようと考えた。
後述しますが、発生した問題の原因は回路でした。 以下の写真に問題点が写ってます。
- 使用した回路
- 発生した問題点
- 計測値が安定しない
- 最初は問題なく表示されるが、時間経過と主に数値が下がり、最終的に室内にも関わらず、300ppm まで下がってしまった
- PWM では動作するが、UART を使用した場合はうまく動作しない
- UART 通信しようとするとエラーになってデータを受信することができない
- 計測値が安定しない
やってみる
使用するもの
- ソフト
- TinyGo
- tinygo version 0.33.0 windows/amd64 (using go version go1.22.6 and LLVM version 18.1.2)
- TinyGo
- ハード
- MH-Z19C(CO2センサーモジュール)
- Seeed XIAO RP2040
- https://akizukidenshi.com/catalog/g/g117044/
- Arduino UNO を使用してもいいのですが、ROM が 32KB しかなく、fmtパッケージをインポートしただけで、容量をオーバーするので・・・
実装する
機器の接続
こんな感じで接続しました。
Xiao RP2040 ピン | MH-Z19C ピン | |
---|---|---|
5V | → | Vin |
GND | → | GND |
D6 (UART0 TX) | → | Rx |
D7 (UART0 RX) | → | Tx |
ソースコード
説明はソースコード内のコメントに記載しているため、データシート を見つつ確認してもらえればと思います。
package main
import (
"fmt"
"machine"
"time"
)
func main() {
// UART の設定
uart := machine.UART0
err := uart.Configure(
machine.UARTConfig{
// センサーとの通信は 9600bps にする必要がある
BaudRate: 9600,
RX: machine.UART0_RX_PIN,
TX: machine.UART0_TX_PIN,
})
if err != nil {
fmt.Println(err)
}
// 待機処理:「Preheat time」に「1 min」と記載されているため、待つ
fmt.Println("waiting preheating")
for i := 0; i < 60; i++ {
fmt.Println(".")
time.Sleep(time.Second)
}
fmt.Println("setting end")
// センサーに対して以下のデータを送信することで、センサーが CO2 の計測データを送信する
sendData := []byte{
0xFF,
0x01,
0x86,
0x00,
0x00,
0x00,
0x00,
0x00,
0x79,
}
// uart.READ() 実行するにあたり、センサーが送信するデータ数分の length にする必要がある
recieveData := []byte{
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
}
fmt.Println("sending messages start:board -> MH-Z19C")
for {
uart.Write(sendData)
uart.Read(recieveData)
// センサーから受け取ったデータで、計測データを算出する
dataHIGH := int(recieveData[2])
dataLOW := int(recieveData[3])
ppm := (dataHIGH * 256) + dataLOW
fmt.Printf("%d\r\n", ppm)
time.Sleep(time.Second * 2)
}
}
余談
1. 回路の誤り
回路を変えている時に、回路内の誤りに気付いた。
これ ↓ 赤枠内全部のピンが短絡してるじゃん・・・。
幸いにも、こちら側のピンは電源はないため、壊れませんでした。
「V0」? 「SR」 という動きがよくわからないピンがありますが、恐らく、上手く動いていなかった原因は以下です。
- 計測値が安定しない
- → キャリブレーションを行うためのピンがずっと他のピンと繋ながっているため
- キャリブレーションを行うには 400ppm の環境で GND ピンに繋ぎ、そこを基準にしなければならないが、CO2 濃度が高い場所でキャリブレーションされ、そこを基準にするということが発生している(例えば 700ppmの環境を基準に計測すると 400ppm の環境になった時に数値が400を下回ってしまう)
- → キャリブレーションを行うためのピンがずっと他のピンと繋ながっているため
- PWM は動作するが、UART を使用した場合はうまく動作しない
- → UART を使用した場合の RX, TX のピンが繋がっているので通信に失敗している
2. ロジックアナライザの結果
ちょっと warning が出てますが、必要なデータのやり取りは出来ているので・・・。
問題になってそうであれば、解決に向けて頑張ったり、頑張らなかったりすると思います。
- (マイコンの)送信側
- (マイコンの)受信側
参考
- データシート・ピン配置等
- 参考記事
-
https://toccho.net/2021/05/17/co2mz19c-arduino
- ↑で使用しているような 5V出力昇圧DCDCコンバーターを使用しなくても、一応動きました
- データシートの 「Working voltage」を見ると、「5.0±0.1V DC」と記載されていますが、「Notes」 に「To ensure the normal work, the power supply must be among 4.5V~5.5V DC rang」と書かれているため、多分問題ないと思います
-
https://toccho.net/2021/05/17/co2mz19c-arduino
追記(2025/02/24)
ディスプレイを追加しつつ、リファクタリングをして、最終的には以下のような感じに落ち着きました。
- 画像
- ソースコード
package main
import (
"fmt"
"image/color"
"machine"
"strconv"
"time"
"tinygo.org/x/drivers/ssd1306"
"tinygo.org/x/tinyfont"
"tinygo.org/x/tinyfont/freemono"
)
var (
// センサーに対して以下のデータを送信することで、センサーが CO2 の計測データを送信する
// 0x86- Read CO2 concentration
SendData = []byte{
0xFF,
0x01,
0x86,
0x00,
0x00,
0x00,
0x00,
0x00,
0x79,
}
// ディスプレイ用
White = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
)
func main() {
// ディスプレイの設定
machine.I2C0.Configure(machine.I2CConfig{
Frequency: 2.8 * machine.MHz,
SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
display := ssd1306.NewI2C(machine.I2C0)
display.Configure(ssd1306.Config{
Address: 0x3C,
Width: 128,
Height: 64,
})
display.ClearBuffer()
display.ClearDisplay()
time.Sleep(time.Millisecond * 50)
// UART の設定
uart := getUART0()
// 待機処理:「Preheat time」に「1 min」と記載されているため、待つ
time.Sleep(time.Minute)
// uart.READ() 実行するにあたり、センサーが送信するデータ数分の length にする必要があるため、注意
recieveData := make([]byte, 9)
tinyfont.WriteLine(&display, &freemono.Regular9pt7b, 3, 10, "60sec\n:Preheating", White)
display.Display()
for {
uart.Write(SendData)
uart.Read(recieveData)
ppm := strconv.Itoa(calcPPM(int(recieveData[2]), int(recieveData[3])))
display.ClearDisplay()
tinyfont.WriteLine(&display, &freemono.Regular9pt7b, 3, 10, "PPM:", White)
tinyfont.WriteLine(&display, &freemono.Regular24pt7b, 3, 50, ppm, White)
display.Display()
time.Sleep(time.Second * 2)
}
}
func getUART0() machine.UART {
uart := machine.UART0
err := uart.Configure(
machine.UARTConfig{
// センサーとの通信は 9600bps にする必要がある
BaudRate: 9600,
RX: machine.UART0_RX_PIN,
TX: machine.UART0_TX_PIN,
})
if err != nil {
fmt.Println(err)
}
return *uart
}
func calcPPM(dataHIGH, dataLOW int) int {
return (dataHIGH * 256) + dataLOW
}
- 参考
Discussion