2012年8月29日水曜日

Raspberry Pi で I2C その2 ストリナI2C LCD

 前回、GR-SAKURAでストロベリー・リナックスのI2C LCDが動かせたので、この前の Raspberry Piで秋月のI2C RTCを動かしたのとあわせて、当然、Raspberry Pi でこのI2C LCDは動くだろうと考えたのですが、甘かったです。単純に差し替えて動いてはくれませんでした。
 今回はRaspduinoアダプタは使わずに、Raspberry PiのGPIO端子から直接結線して試したのですが、文字が表示されず、というか、初期化のコマンドのところでI2Cへ出力する関数がエラーを返してきます。ロジアナで覗くと一発目のアドレス送信でNACKが返って(ACKにならない)いたり、ACKが返ってもタイミングがずれているように見えます。GR-SAKURAでは動いて、Raspberry PiではダメなんだからRaspberry Pi側の問題?切り分けのために、パソコンからI2C信号を送れるアダプタで試したところ同じ結果になりました。原因がわからないまま、悶々としつつ、このアダプタのマニュアルとかを読んでいたら、「プルアップ抵抗が内蔵されています。」の記述が。そういえば、Raspberry Piも(設定で切り替えられると思うが)デフォルトで内部プルアップだったはず。外部(ブレッドボード)上にもプルアップ抵抗(今回は1kΩ)をつけてあるのが逆効果なのか?でも、GR-SAKURAの時は普通に動いていたけど・・・。(GR-SAKURAはデフォルト内部プルアップだけど、さらに外付けしたほうが安定ってライブラリの説明に書いてあった。)
 まあ、ものは試しなので、プルアップ抵抗を外して試してみよう・・・。って、動いたよ。


 ふ~ん。そういうもんなの。Raspberry Pi 側の内蔵プルアップがあるのに外部に(しかも1kΩだと低すぎ?本来2.2kΩとか?)プルアップを余計につけてしまうと並列になった抵抗をLCD側がACK状態にドライブできない(あるいはタイミングが遅れる)ということでしょうか。
 と、いうわけで。以下、ソース。例によってdelay関数でwiringPiを使用。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include <wiringPi.h>


#define LCD_ADDRESS (0b0111110)
#define LCD_CONTRAST 100

#define LCD_RS_CMD (0x00)
#define LCD_RS_DATA (0x40)
#define LCD_CMD_CLEAR (0x01)
#define LCD_CMD_HOME (0x03)

// 関数プロトタイプ宣言。
void LCD_init(int fd);
void LCD_write(unsigned char rs, unsigned char data, int fd);
void LCD_clear(int fd);
void LCD_setCursor(unsigned char col, unsigned char row, int fd);
void LCD_putc(unsigned char c, int fd);
void LCD_puts(char *str, int fd);

int main(int argc, char **argv)
{
 int lcd;       // ファイルディスクリプタ。
 char *i2cFileName = "/dev/i2c-0"; // I2Cドライバファイル名。
 int lcdAddress = LCD_ADDRESS;  // I2C LCD のアドレス。
 
 printf("***** start i2c lcd test program *****\n");
 
 // I2CポートをRead/Write属性でオープン。
 if ((lcd = open(i2cFileName, O_RDWR)) < 0)
 {
  printf("Faild to open i2c port\n");
  exit(1);
 }
 
 // 通信先アドレスの設定。
 if (ioctl(lcd, I2C_SLAVE, lcdAddress) < 0)
 {
  printf("Unable to get bus access to talk to slave\n");
  exit(1);
 }
 
 delay(100);

 // LCD初期化。
 LCD_init(lcd);
 
 // メッセージ表示。
    LCD_setCursor(0, 0, lcd);
    LCD_puts("Hello!!", lcd);
    LCD_setCursor(5, 1, lcd);
    LCD_puts("I2C LCD!!", lcd);
 
 return 0;
}

void LCD_init(int fd)
{
 delay(40);
 // Function Set。8bit bus mode, 2-line mode,normal font,normal instruction mode。
    LCD_write(LCD_RS_CMD, 0b00111000, fd);
    // Function Set。extension instruction modeへ。
    LCD_write(LCD_RS_CMD, 0b00111001, fd);
    // Internal OSC frequency(extension instruction mode)設定。
    LCD_write(LCD_RS_CMD, 0b00010100, fd);
    // Contrast set(extension instruction mode)。コントラスト値下位4bit設定。
    LCD_write(LCD_RS_CMD, 0b01110000 | (LCD_CONTRAST & 0xF), fd);
    // Power/ICON/Contrast set(extension instruction mode)。
    // アイコン On,booster On,コントラスト値上位2bit設定。
    LCD_write(LCD_RS_CMD, 0b01011100 | ((LCD_CONTRAST >> 4) & 0x3), fd);
    // Follower control。internal follower on, 
    LCD_write(LCD_RS_CMD, 0b01101100, fd);
    // 時間待ち。
    delay(300);
    
    // Function Set。normal instruction mode。
    LCD_write(LCD_RS_CMD, 0b00111000, fd);
    // Display On/Off。Display Onに設定。
    LCD_write(LCD_RS_CMD, 0b00001100, fd);
    // Clear Display。
    LCD_write(LCD_RS_CMD, 0b00001100, fd);
    // 時間待ち。
    delay(2);
}

void LCD_write(unsigned char rs, unsigned char data, int fd)
{
    unsigned char buf[2];
 
    if (rs == LCD_RS_CMD || rs == LCD_RS_DATA)
    {
        // LCD_RS_CMD ならコマンドモード。LCD_RS_DATA ならデータモード。
        
  buf[0] = rs;
  buf[1] = data;
  if (write(fd, buf, 2) != 2)
  {
   printf("Error writeing to i2c slave1\n");
  }        
    }
    else
    {
        // rsの指定がLCD_RS_CMD,LCD_RS_DATA以外ならなにもしない。
    }
}

void LCD_clear(int fd)
{
    LCD_write(LCD_RS_CMD, LCD_CMD_CLEAR, fd);
    delay(2);
    LCD_write(LCD_RS_CMD, LCD_CMD_HOME, fd);
    delay(2);
}

void LCD_setCursor(unsigned char col, unsigned char row, int fd)
{
    unsigned char offset[] = {0x00, 0x40};
    
    if (row > 1)    row = 1;
    if (col > 16)    col = 16;
    
    LCD_write(LCD_RS_CMD, 0x80 | (col + offset[row]), fd);
}

void LCD_putc(unsigned char c, int fd)
{
    LCD_write(LCD_RS_DATA, c, fd);
}

void LCD_puts(char *str, int fd)
{
    int i;
    for (i = 0; i < 16; i++)
    {
        if (str[i] == 0x00)
        {
            break;
        }
        else
        {
            LCD_putc((unsigned int)str[i], fd);
        }
    }
}

 前日まで悩んでたのはなんだったんだ。ま、結果オーライということで。昨夜、飲んだら眠くなって早く寝過ぎて、今朝、2時前に目が覚めて、これに手を付けたんだけど、早起きは三文の得、ということで・・・。