激安マイコン基板で温度・湿度センサーの値を読み取ってみた

ストロベリーリナックスで購入できるMSP430 LaunchPadと中国製らしい温度・湿度センサーDHT22を使ってみた。


DHT22からセンサーの値を読み取るには1本の信号だけを使って行う。この1本線のプロトコルは、モールス信号みたいに信号のHigh期間の長さで0/1を表現するので、カウンタで長さを数えてやればいい。

開発環境のデバッガを走らせてみると、確かにデータを受信できている。(MSP430のスピードが遅いためか、for文で回すとうまく受信できなかったのでベタ書きするようにした)

一度に受信するデータは5バイトで、上から順に

  1. 湿度のHigh Byte
  2. 湿度のLow Byte
  3. 温度のHigh Byte
  4. 温度のLow Byte
  5. CheckSum(1〜4を足した値)

となっている。

ちなみに、このときの湿度と温度は、

  • 湿度=0x01CE=462=46.2%
  • 温度=0x00DE=222=22.2℃

というそれっぽい値だった。
もちろんCheckSumもOK。

//******************************************************************************
//  MSP430x2xx Demo - Software Toggle P1.0
//
//  Description; Toggle P1.0 by xor'ing P1.0 inside of a software loop.
//  ACLK = n/a, MCLK = SMCLK = default DCO
//
//                MSP430x2xx
//             -----------------
//         /|\|              XIN|-
//          | |                 |
//          --|RST          XOUT|-
//            |                 |
//            |             P1.0|-->LED
//
//  A. Dannenberg
//  Texas Instruments, Inc
//  January 2006
//  Built with IAR Embedded Workbench Version: 3.40A
//******************************************************************************

#include "msp430.h"

int main(void)
{
  volatile unsigned char data[5];
  volatile unsigned char sum;
  volatile unsigned int i;
  char cnt0, cnt1;
  
  WDTCTL = WDTPW + WDTHOLD;             // Stop watchdog timer
  P1DIR = 0x01;                        // Set P1.0 to output direction
                                        // 0: input, 1: output
  P1OUT = 0x00;
  
  for (;;)
  {
    // output start signal to P1.4
    P1DIR |= 0x10;
    
    // delay
    i = 1000;
    do { i--; } while (i != 0);
    
    P1DIR &= ~0x10;
    
    while ((P1IN & 0x10) != 0); // wait DHT response
    while ((P1IN & 0x10) == 0); // response
    while ((P1IN & 0x10) != 0); // sense
    
#define RECV(d)                                    \
        cnt0 = 0;                                  \
        do { cnt0++; } while ((P1IN & 0x10) == 0); \
        cnt1 = 0;                                  \
        do { cnt1++; } while ((P1IN & 0x10) != 0); \
        data[d] = data[d] << 1;                    \
        if (cnt0 < cnt1) {                         \
          data[d] += 1;                            \
        }

    // 1st byte
    RECV(0);
    RECV(0);
    RECV(0);
    RECV(0);
    RECV(0);
    RECV(0);
    RECV(0);
    RECV(0);
    
    // 2nd byte
    RECV(1);
    RECV(1);
    RECV(1);
    RECV(1);
    RECV(1);
    RECV(1);
    RECV(1);
    RECV(1);
    
    // 3rd byte
    RECV(2);
    RECV(2);
    RECV(2);
    RECV(2);
    RECV(2);
    RECV(2);
    RECV(2);
    RECV(2);
    
    // 4th byte
    RECV(3);
    RECV(3);
    RECV(3);
    RECV(3);
    RECV(3);
    RECV(3);
    RECV(3);
    RECV(3);
    
    // 5th byte
    RECV(4);
    RECV(4);
    RECV(4);
    RECV(4);
    RECV(4);
    RECV(4);
    RECV(4);
    RECV(4);
    
    // checksum
    sum = 0;
    sum += data[0];
    sum += data[1];
    sum += data[2];
    sum += data[3];
    
    // compare
    if (sum == data[4])
      P1OUT |=  0x01; // OK
    else
      P1OUT &= ~0x00; // NG
  }
}

連番を挿入するEmacs Lispを書いてみた

矩形領域コマンドの要領で領域を指定して連番を挿入できるEmacs Lispを書いてみた。Emacs Lispを書くのは初めてだけどちゃんと動いているっぽい。初期値の設定時にフォーマットを指定できる(たぶん)。便利。

  • Start number: 0 を入力したとき → 初期値: 0, フォーマット: %01d
  • Start number: 03h を入力したとき → 初期値: 3, フォーマット: %02x
  • Start number: A0A0H を入力したとき → 初期値: A0A0, フォーマット: %04X
(defun insert-seq (pstart pend)
  "Insert sequential numbers to rectangle region."
  (interactive "r")
  (save-excursion
    (let ((cstart (progn (goto-char pstart) (current-column)))
          (cend   (progn (goto-char pend)   (current-column)))
          (lstart (progn (goto-char pstart) (count-lines (point-min) (point))))
          (lend   (progn (goto-char pend)   (count-lines (point-min) (point))))
          (fmt    (read-from-minibuffer "Start number: " "0")))
      (setq fmt_last (string (elt fmt (1- (length fmt)))))
      (setq radix (if (equal fmt_last "H") "X" (if (equal fmt_last "h") "x" "d")))
      (setq len (if (equal radix "d") (length fmt) (1- (length fmt))))
      (setq j (string-to-number fmt (if (equal radix "d") 10 16)))
      (setq i lstart)
      (while (<= i lend)
        (let ((s (progn (goto-line i) (move-to-column cstart) (point)))
              (e (progn (goto-line i) (move-to-column cend)   (point))))
          (kill-region s e)
          (goto-char s)
;;          (while (< (current-column) cstart)
;;            (insert " "))
;;          (insert (format (format "%s0%d%s" "%" len radix) j))
;;          (setq j (1+ j))
          (if (= (current-column) cstart)
              (progn (insert (format (format "%s0%d%s" "%" len radix) j)) (setq j (1+ j))))
          (setq i (1+ i))
          )))))

LAN内のマシンにホスト名でアクセスする

家にある複数台のUbuntuマシンが互いにホスト名で名前解決できなくて、毎回IPアドレスを打つのが面倒だった。試行錯誤した結果、以下のようにすればよいことが分かった。sambaを入れないといけないのはなんでか分からない。

  1. 下記のページにあるように、/etc/nsswitch.confの変更とwinbindのインストールを行う
  2. sambaをインストールする(?)

JapaninoでのADCについて

前の記事でJapaninoと温度センサを使って温度計を作ったが、温度がやけに高めになってしまう問題があったのでちょっと調べてみた。

デジタル温度計を作ってみる - andooのメモとか

Arduinoでアナログ信号をADC(アナログデジタル変換)する際に使用できる参照電圧には以下の3つがある。

  • DEFAULT: 電源電圧(5V、正確に言うとAVCCピン電圧)
  • INTERNAL: 内部で作られる1.1V
  • EXTERNAL: AREFピン電圧

ADCの出力はこれらの参照電圧を1024としたときの10bitの値で得られる。USBから供給される5V電源はそれなりの精度で安定しているだろうと考え、上記温度計ではDEFAULTの5Vを用いてADCを行うように設定していた。ところが、Arduinoに供給されている電源の電圧を測ってみると4.75Vしかないことがわかった。つまり、参照電圧が期待していた値よりも低かったため、ADCの結果が高めに出ていたということだ。Japanino基板上の電圧を調べてみたところ、どうやら犯人は電流の逆流防止用と思われるダイオードのようだ。このダイオードの入力と出力で電圧が5.05Vから4.75Vに降下していることがわかった。

対策として、参照電圧が4.75Vであるとして温度を計算するように式を修正した。これにより実際の温度にかなり近い値が表示されるようになった。


void loop() {
  int num = 20;
  long sum = 0;
  for (int i = 0; i < num; i++) {
    sum += analogRead(analogPin0);
    delay(5);
  }
  int temp = (int) (sum * 4750 / num / 1024 - 600);
  
  lcd.clear();
  lcd.print("Temp:");
  lcd.print(temp/10, DEC);
  lcd.print(".");
  lcd.print(temp%10, DEC);
    
  if (serial_gets(buffer, 16) > 0) {
    if (strcmp(buffer, "temp?") == 0) {
      Serial.println(sum, DEC);
    }
  }
}

デジタル温度計を作ってみる

温度センサとLCDArduinoにつないで温度計を作ってみた。
こんなに簡単にLCDを扱えるとは本当に驚きだ。

温度センサにはLM61CIZというICを使った。
ちょっと温度高めになってしまうけどこんなもの?

#include <LiquidCrystal.h>

LiquidCrystal lcd = LiquidCrystal(11, 12, 2, 3, 4, 5); // RS,EN,D4,D5,D6,D7

int ledPin =  13;    // LED connected to digital pin 13
int analogPin0 = 0;

void setup() {                
  analogReference(DEFAULT);
  pinMode(ledPin, OUTPUT);     
  Serial.begin(9600);
}

int serial_gets(char *buffer, int size) {
  if (!Serial.available()) return 0;
  int i = 0;
  while (i < size-1) {
    if (Serial.available()) {
      buffer[i++] = Serial.read();
      if (buffer[i-1] == '\r') { i--; }
      if (buffer[i-1] == '\n') { i--; break; }
    } else {
      delay(1);
    }
  }
  buffer[i] = '\0';
  return i;
}

void loop() {
  int sum = 0;
  for (int i = 0; i < 50; i++) {
    sum += analogRead(analogPin0);
    delay(4);
  }
  int temp = (sum - 6144) * 5 / 32;
  temp = temp * 20 / 32;
  
  lcd.clear();
  lcd.print("Temp:");
  lcd.print(temp/10, DEC);
  lcd.print(".");
  lcd.print(temp%10, DEC);
    
  char buffer[16];
  if (serial_gets(buffer, 16) > 0) {
    if (strcmp(buffer, "temp?") == 0) {
      Serial.println(sum, DEC);
    }
  }
}

Japaninoとシリアル通信してみる

PCからシリアル経由でJapaninoのLEDを制御してみた。

int ledPin =  13;    // LED connected to digital pin 13

void setup() {                
  pinMode(ledPin, OUTPUT);     
  
  Serial.begin(9600);
}

int serial_gets(char *buffer, int size) {
  if (!Serial.available()) return 0;
  int i = 0;
  while (i < size-1) {
    if (Serial.available()) {
      buffer[i++] = Serial.read();
      if (buffer[i-1] == '\r') { i--; }
      if (buffer[i-1] == '\n') { i--; break; }
    } else {
      delay(1);
    }
  }
  buffer[i] = '\0';
  return i;
}

void loop() {      
  char buffer[16];
  if (serial_gets(buffer, 16) > 0) {
    if (strcmp(buffer, "led:on") == 0) {
      digitalWrite(ledPin, HIGH);
    } else if (strcmp(buffer, "led:off") == 0) {
      digitalWrite(ledPin, LOW);
    } else if (strcmp(buffer, "led?") == 0) {
      Serial.println(digitalRead(ledPin), DEC);
    }
  }
}

serial_getsでPCから送られてくる文字列を取得している。取得した文字列をstrcmpで比較して、"led:on"ならばLEDを点灯、"led:off"ならばLEDを消灯、"led?"ならばLEDの出力状態を取得して送信するようにしている。

PC側のプログラムはrubyで書いてみた。シリアル通信にはruby-serialportを使うのでインストールしておく。

sudo gem install ruby-serialport
#!/usr/bin/env ruby
require 'rubygems'
require 'serialport'

sp = SerialPort.new('/dev/ttyUSB0', 9600, 8, 1, SerialPort::NONE)
sleep 2

def serial_gets(sp)
  str = ""
  while true do
    c = sp.getc
    unless c.nil?
      str << c
      break if c.chr == "\n"
    end
  end
  str
end

begin
  while true
    sp.puts "led:on"
    sleep 1
    sp.puts "led:off"
    sleep 1
    sp.puts "led?"
    puts serial_gets(sp)
  end
rescue Interrupt
  sp.close
  puts
end