common-lisp
電子工作
RaspberryPi

CommonLispでRaspberryPi電子工作 ~I2C 温度センサ~

More than 1 year has passed since last update.

はじめに

今回はI2CでADT7410を使用した温度センサーモジュールを制御してみたいと思います。

回路図

今回はまず最初にRaspberryPiとADT7410を先に繋いでしまいます。

デバイスアドレスの確認

次に、MI2CLCD-01のデバイスアドレスを確認します。
デバイスアドレスの確認にはi2cdetectコマンドを使用します。

pi@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

パッケージ作成

cffiをQuicklispでロードし、パッケージ定義。
いつものです。

packages.lisp
;; cffiをQuicklispでロード
(ql:quickload "cffi")

;; cl-cffiパッケージを定義
(defpackage :cl-cffi
    (:use :common-lisp :cffi))

APIラッパー作成

今回追加する関数は以下の1つだけです。

・wiringPiI2CReadReg16
示されたデバイス・レジスタから16ビットの値を読み出します。

libwiringPi.lisp
(define-foreign-library libwiringPi
  (:unix "libwiringPi.so"))

(use-foreign-library libwiringPi)

;; Initialization of the wiringPi
(defcfun "wiringPiSetupGpio" :int)

;; Set the mode of the GPIO pin
(defcfun "pinMode" :void (pin :int) (mode :int))

;; GPIO pin output control
(defcfun "digitalWrite" :void (pin :int) (value :int))

;; Waiting process
(defcfun "delay" :void (howlong :uint))

;; Set the state when nothing is connected to the terminal
(defcfun "pullUpDnControl" :void (pin :int) (pud :int))

;; Read the status of the GPIO pin
(defcfun "digitalRead" :int (pin :int))

;; Initialization of the I2C systems.
(defcfun "wiringPiI2CSetup" :int (fd :int))

;; Writes 8-bit data to the instructed device register.
(defcfun "wiringPiI2CWriteReg8" :int (fd :int) (reg :int) (data :int))

;; It reads the 16-bit value from the indicated device register.
(defcfun "wiringPiI2CReadReg16" :int (fd :int) (reg :int))

I2C ADT7410プログラム本体

ADT7410から温度データを取得し、コンソールに表示するだけのプログラムです。
解像度16ビットの高精度で温度を取得するため、レジスタ0x03に「0x80」を書き込んでいます。
また、温度データを取得するとビッグエンディアンになってしまっているので、バイトスワップしてリトルエンディアンにする必要があります。

【温度計算について】
・解像度13ビットの場合
4~16ビット目までが有効なデータなので、取得データを8で割って下位3ビットを捨ててから、温度分解能値である0.0625をかけます。
計算式:(取得データ / 8) × 0.0625

・解像度16ビットの場合
全てのデータが使えるので、そのまま温度分解能値である0.0078とかけます。
計算式:取得データ × 0.0078

adt7410.lisp
;; Load packages
(load "packages.lisp" :external-format :utf-8)

(in-package :cl-cffi)

;; Load wrapper API
(load "libwiringPi.lisp" :external-format :utf-8)

;; I2C device address (0x48)
(defconstant +i2c-addr+ #X48)

;; バイトスワップ関数
(defun byte-swap (num-value)
  (let (str-value temp-msb temp-lsb)
    ;; 数値を文字列へ変換
    (setq str-value (write-to-string num-value :base 16))
    ;; 上位2桁(MSB)を取得
    (setq temp-msb (subseq str-value 0 2))
    ;; 下位2桁(LSB)を取得
    (setq temp-lsb (subseq str-value 2))
    ;; スワップして結合
    (setq str-value (concatenate 'string temp-lsb temp-msb))
    ;; 文字列を数値へ変換
    (parse-integer str-value :radix 16)))

;; メイン関数
(defun main ()
  (let (fd base-data actual-data result)
    ;; i2cの初期設定
    (setq fd (wiringPiI2CSetup +i2c-addr+))
    ;; 温度を16ビットのデータで取得するようレジスタ「0x03」に設定
    (wiringPiI2CWriteReg8 fd #X03 #X80)
    ;; ADT7410からデータを取得
    (setq base-data (wiringPiI2CReadReg16 fd #X00))
    ;; バイトスワップ
    (setq actual-data (byte-swap base-data))
    ;; 温度計算
    (setq result (* actual-data 0.0078))
    ;; 結果を標準出力
    (format t "~d~%" result)))

;; 実行!
(main)

実行

以下のコマンドで実行します。

sbcl --load adt7410.lisp

以下実行中の様子

測定値:28.421875℃
目覚ましの温度計:28.7℃

かなり近い結果が出ていますね。

最後に

ついに、センサからデータを取得するところまで来ました。
やっぱり、センサは難しいですね。

とりあえず、今回一番大変だったのはバイトスワップ処理です・・・。