LPC810メモ:I2C-bus ROM API(テキストLCDを使う)

I2C-bus ROM API利用して(マスタによる送信のみだけど)、テキストLCDを使ってみる。

前提とする環境はこちら→LPC810メモ 共通の準備

利用したテキストLCDはAQM0802

サンプルコード

#include "LPC8xx.h"

// UM10601 24.4 API description
// に相当すると思われるプロトタイプ宣言を、
// LPCOpenのヘッダから持ってきた。
extern "C" {
typedef int ErrorCode_t; // 本当はenumだけど今回は値は見ない
typedef int CHIP_I2C_MODE_T; // これも本当はenum
typedef void *I2C_HANDLE_T;
typedef void (*I2C_CALLBK_T)(uint32_t err_code, uint32_t n);

typedef struct I2C_PARAM {
    uint32_t        num_bytes_send;
    uint32_t        num_bytes_rec;
    uint8_t         *buffer_ptr_send;
    uint8_t         *buffer_ptr_rec;
    I2C_CALLBK_T    func_pt;
    uint8_t         stop_flag;
    uint8_t         dummy[3];
} I2C_PARAM_T;

typedef struct I2C_RESULT {
    uint32_t n_bytes_sent;
    uint32_t n_bytes_recd;
} I2C_RESULT_T;

typedef struct  I2CD_API {
    void (*i2c_isr_handler)(I2C_HANDLE_T *handle);
    ErrorCode_t (*i2c_master_transmit_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_master_receive_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_master_tx_rx_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_master_transmit_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_master_receive_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_master_tx_rx_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_slave_receive_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_slave_transmit_poll)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_slave_receive_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_slave_transmit_intr)(I2C_HANDLE_T *handle, I2C_PARAM_T *param, I2C_RESULT_T *result);
    ErrorCode_t (*i2c_set_slave_addr)(I2C_HANDLE_T *handle, uint32_t slave_addr_0_3, uint32_t slave_mask_0_3);
    uint32_t        (*i2c_get_mem_size)(void);
    I2C_HANDLE_T *  (*i2c_setup)( uint32_t  i2c_base_addr, uint32_t * start_of_ram);
    ErrorCode_t     (*i2c_set_bitrate)(I2C_HANDLE_T *handle, uint32_t  p_clk_in_hz, uint32_t bitrate_in_bps);
    uint32_t        (*i2c_get_firmware_version)(void);
    CHIP_I2C_MODE_T (*i2c_get_status)(I2C_HANDLE_T *handle);
    ErrorCode_t     (*i2c_set_timeout)(I2C_HANDLE_T *handle, uint32_t timeout);
} I2CD_API_T;

// 「UM10601 21.5.2 ROM-based APIs」に相当するstruct
// 本当はI2C以外も定義されているが今回は省略
typedef struct ROM_API {
	const uint32_t    dummy1[5];
	const I2CD_API_T  *pI2CD;
	const uint32_t    dummy2[4];
} LPC_ROM_API_T;

// UM10601 24.3 General description
// より、アドレスの設定
#define LPC_I2CD_API ((*(LPC_ROM_API_T * *) 0x1FFF1FF8UL)->pI2CD)
}

// 前のサンプルで書いたwait_msを流用
volatile bool waiting = false;
void wait_ms(uint32_t ms) {
    waiting = true;
    LPC_MRT->Channel[0].INTVAL = (12000000 / 1000) * ms;
    LPC_MRT->Channel[0].INTVAL |= 0x1UL<<31;
    while(waiting) {};
}
extern "C" {
    void MRT_IRQHandler(void) {
        if ( LPC_MRT->Channel[0].STAT & 0x1) {
            LPC_MRT->Channel[0].STAT = 0x1;
            waiting = false;
        }
        return;
    }
}

//  APIを使う前提となる変数を用意
//  (2つめが0x20である理由は知らない。LPCOpenのサンプルの真似)
static I2C_HANDLE_T *i2cHandleMaster;
static uint32_t i2cMasterHandleMEM[0x20];

// I2Cのマスタとして、'0x7c'のスレーブに2byte送信
// (今回使うAQM0802に合わせて'0x7c'と2byteを決め打ち。汎用性は無い。)
static void send(uint8_t d1, uint8_t d2)
{
    uint8_t data[3] = {(uint8_t)0x7c, d1, d2};
    I2C_PARAM_T param;
    I2C_RESULT_T result;

    param.num_bytes_send  = 3; // 3byte送信
    param.buffer_ptr_send  = &data[0];
    param.num_bytes_rec  = 0; // 受信はしない(byte)
    param.stop_flag   = 1; // 送信終わったらstop

    // タイムアウト設定
    LPC_I2CD_API->i2c_set_timeout(i2cHandleMaster, 100000);

    // 送信
    LPC_I2CD_API->i2c_master_transmit_poll(i2cHandleMaster, &param, &result);
    // ここで戻り値が0(LP_OK)なら成功している。本例ではチェックしない。
}

int main(void) {
    // MRTの設定(上のwait_msを使うため。I2Cとは直接関係なし)
    LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<10);
    LPC_SYSCON->PRESETCTRL &= ~(0x1<<7);
    LPC_SYSCON->PRESETCTRL |= (0x1<<7);
    NVIC->ISER[0] = (0x1 << 10);
    LPC_MRT->Channel[0].CTRL = (0x01 << 1) | (0x1);

    // I2Cを使うために、SWMでpinを割りあてる
    // SWMのclockを有効化。
    // UM10601 4.6.13 System clock control register
    // → SWMは7bit目
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

    // SDAを4番pinに
    // UM10601  9.5.8 Pin assign register 7
    LPC_SWM->PINASSIGN7 = 0x02ffffffUL;

    // SCLを3番pinに
    // UM10601  9.5.9 Pin assign register 8
    LPC_SWM->PINASSIGN8 = 0xffffff03UL;

    // fixed pin functionはreset以外全部disable
    // UM10601  9.5.10 Pin enable register 0
    LPC_SWM->PINENABLE0 = 0xffffffbfUL;

    // UM10601  16.3 Basic configuration
    // にしたがってI2Cの準備
    // In the SYSAHBCLKCTRL register, set bit 5 (Table 30) to enable the clock to theregister interface.
    LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<5);

    // Clear the I2C peripheral reset using the PRESETCTRL register (Table 19).
    LPC_SYSCON->PRESETCTRL &= ~(0x1<<6);
    LPC_SYSCON->PRESETCTRL |= (0x1<<6);

    //  Enable/disable the I2C interrupt in interrupt slots #8 in the NVIC.
    // -> defaultはdisableっぽいし、今回は割り込みいらないから略。

    //  Configure the I2C pin functions through the switch matrix. See Section 16.4.
    // -> 上の方ですでにやった。

    //  The peripheral clock for the I2C is the system clock (see Figure 32).
    //  -> これは単なる説明だよね?
    
    //  I2C-bus ROM APIを使って初期設定
    i2cHandleMaster = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cMasterHandleMEM);
    LPC_I2CD_API->i2c_set_bitrate(i2cHandleMaster, 12000000, 100000); // 速度100KHz

    // 以下、LCDにコマンドを送る
    // (送信するコマンドの内容は、mbedにあったAQM0802ライブラリを真似した)
   wait_ms(100);
   // Function set = 0x38
   send(0x00, 0x38); wait_ms(1);
   // Function set = 0x39
   send(0x00, 0x39); wait_ms(1);
   // Internal OSC frequency = 0x14
   send(0x00, 0x14); wait_ms(1);
   // Contrast set = 0x78
   send(0x00, 0x78); wait_ms(1);
   // Power/ICON/Contrast control = 0x55
   send(0x00, 0x55);  wait_ms(1);
   // Follower control = 0x6C
   send(0x00, 0x6C); wait_ms(200);
   // Function set = 0x38
   send(0x00, 0x38); wait_ms(1);
   // Display ON/OFF control = 0x0C
   send(0x00, 0x0C); wait_ms(1);
   // Clear Display = 0x01
   send(0x00, 0x01); wait_ms(2);

   // 'Hello'と表示
   send(0x40, 'H');  send(0x40, 'e');  send(0x40, 'l');
   send(0x40, 'l');  send(0x40, 'o');  send(0x40, '!');

   return 0 ;
}

接続

  • LCDのpinは、上からVDD,RESET(未使用),SCL,SDA,GND
  • LPC810の方は、3番がSCL、4番がSDA
  • SCL、SDAのプルアップ抵抗は適当だけど3KΩ

補足/雑感

  • SWMのクロックは、pinの設定が終わったら節電のために無効にして良いらしい。この例ではしてない。
  • SWMの設定の時にリセットも無効にしてしまうと、次にFlashMagicで書き込もうとした時に失敗した。その場合、5番pinをlowにした状態で電源を入れると、強制的にISPモードになって書き込めるようになるらしい。
  • それにしても長くなってしまった。ROM APIとかチップの機能なのだから、関数プロトタイプとかLPC8xx.hに入っててほしかった。そういうものじゃないのだろうか。
  • ユーザマニュアル(UM10601)は全体的に、自分のような初心者にはキツすぎる。わかる人にはわかるんだろうか。
  • 特にROM APIは、説明する気がないんじゃないかと思うくらい。
  • これだけで1084byteか。Hello!って表示しただけなのに1kb超えるとは、この先が不安。
  • 何にせよ、文字を出せるようになってよかった。もうLEDの点滅回数とかでデバッグしなくて良くなる。達成感あり。

みっかぼの無料Androidアプリはこちら。