LPC810メモ:Pin interrupts

GPIOの入力の変化で割り込みを発生させるpin interruptsの例。
ボタンを押すと、LEDの点灯/消灯が切り替わるだけ。

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

サンプルコード

#include "LPC8xx.h"

int main(void) {
    // UM10601 8.3 Basic configuration
    // に従った手順
    LPC_SYSCON->PINTSEL[0] = 0; // PIO0_0(8番pin)をpin interruptの0番に。
    // PINTSEL[7]まであって、それぞれ違う割り込みになる。
    // たとえば、
    // LPC_SYSCON->PINTSEL[3] = 2;
    // なら、PIO0_2(4番pin)をpin interruptの3番に、設定したことになる。

    // GPIOに対してclockを有効化(pin interruptと共通の6番)
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // wake-upの設定は省略

    //  Pin interrupt 0の割り込み有効化。
    //  UM10601  3.3.1 Interrupt sources
    NVIC->ISER[0] = (0x1 << 24); // 0番だからbit24。7番(bit31)まである。

    //  UM10601 8.3 Basic configuration
    //  の手順、ここまで。

    // LED出力用に、PIO0_4(2番pin)をoutputに設定
    LPC_GPIO_PORT->DIR0 |= (1<<4);

    // Pin interrupt 0の割り込み、falling edgeに対して有効化
    // UM10601  8.6.6 Pin interrupt active level or falling edge interrupt set register
    LPC_PIN_INT->SIENF = (1 << 0);

    return 0 ;
}

extern "C" {
    // pin interrupt 0の割り込みハンドラ。
    // cr_startup_lpc8xx.cpp で定義されている名称。
    void PININT0_IRQHandler(void)
    {
        // 割り込みをクリア
        // pin interrupt 0の割り込みをクリア
        // UM10601 8.6.10 Pin interrupt status register
        LPC_PIN_INT->IST = (1<<0);

        // PIO0_4(2番pin)の出力反転
        LPC_GPIO_PORT->NOT0 |= (1<<4);
    }
}

接続

  • ボタンは8番pin、LEDは2番pin
  • ボタンを押して、離した瞬間(8番の入力がHIGH->HIGH、つまりfalling edge)に、2番の出力が反転して、LEDの点灯/消灯が入れ替わる

補足/雑感

  • falling edgeじゃなくてrising edge検出ならUM10601「8.6.2 Pin interrupt level or rising edge interrupt enable register」で有効化
  • なにか難しいものと勘違いして今まで試してなった。これと対になってるpattern match engineが複雑なので、pin interrupts自体は非常に簡単な機能だといまさら気づく。

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

ボクのLPC810工作ノート


この例でコメントをもらったのをきっかけに検索して見つけた。LPC810だけの本が出てるとは。

いまのとこ初心者に厳しいユーザマニュアル(UM10601)等でがんばってるが、心が折れたら買ってしまうと思われるのでメモ。
みっかぼの無料Androidアプリはこちら。

LPC810メモ:I2C-bus ROM API、slave送信->master受信

前回同様、I2C-bus ROM APIを利用して、I2Cのslaveとして動作させる例。ただし今回は、master側がslave側からdataを受信する。LPC810を2個使って、それぞれmaster/slaveとして機能させる点は同じ。

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

前回コメントでいろいろ教わったので、今回は最初から固定長データとして割り切り(2byte送受信)、余計なコードがないのでバイナリサイズは1264byteでわりと小さい。master、slave兼用でこれなら、片方なら1Kbyteくらいにできそうな気がする(未検証)。

  • LPC810を2個使う。
    • 1つはmaster、もう片方はslave。挙動は下記のとおりで、おおむね前回と逆。
    • プログラムは共通。開始直後に8番pinの入力を読み込み、HIGHならmaster、LOWならslaveとして動作する仕組み。前回と逆。
  • slave側の8番にプッシュボタンをつけておいて、HIGH、LOWの状態に応じてmaster側にI2Cで送信。前回と逆。
    • HIGHなら{11,22}の2byteを送信しつつ、自分の2番pinをHIGH(LED点灯)
    • LOWなら{22,11}の2byteを送信しつつ、自分の2番pinをLOW(LED消灯)
  • master側も、受信データに応じて点滅
  • 結果として、slave側のプッシュボタンを押す/離すことにより、master,slave両方のLEDが点灯/消灯することになる。

サンプルコード

#include "LPC8xx.h"
#include<string.h>

#define I2C_BUFF_SZ         16

// 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)
}

// master側の記述ここから
//  APIを使う前提となる変数を用意
static I2C_HANDLE_T *i2cHandleMaster;
static uint32_t i2cMasterHandleMEM[0x20];

// num byteのデータを受信
static void masterRecvData(uint8_t addr, uint8_t data[], uint32_t num)
{
    uint8_t buf[I2C_BUFF_SZ]; // 受信用バッファ
    buf[0] = addr; // 0byte目はslaveのアドレスを入れる
    I2C_PARAM_T param;
    I2C_RESULT_T result;

    param.num_bytes_rec= num + 1; // addrの分を含めてnum + 1 byteを指定。
    param.buffer_ptr_rec = buf;
    param.stop_flag  = 1; // 受信終わったらstop
    result.n_bytes_recd = 0;

    // タイムアウト設定
    // UM10601 24.4.18 I2C time-out value
    LPC_I2CD_API->i2c_set_timeout(i2cHandleMaster, 100000);

    // 受信
    // UM10601 24.4.3 I2C Master Receive Polling
    LPC_I2CD_API->i2c_master_receive_poll(i2cHandleMaster, &param, &result);

    // 受信
    memcpy(data, &buf[1], num); // 引数のdataに書き戻す。
    // 0byte目はslaveのアドレスなので、[1]から先がデータ本体。
}

// master側の初期設定
static void masterMain() {
    //  I2C-bus ROM APIを使って初期設定
    // UM10601 24.4.14 I2C Setup
    i2cHandleMaster = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cMasterHandleMEM);

    // UM10601 24.4.15 I2C Set Bit Rate
    LPC_I2CD_API->i2c_set_bitrate(i2cHandleMaster, 12000000, 100000); // 速度100KHz

    while(1) {
        uint8_t data[I2C_BUFF_SZ];
        // 定期的にslave側(アドレス0xc0)からデータを受信
        masterRecvData(0xc0, data, 2);

        // 受信した結果が・・
        if(data[0] == 11 && data[1] == 22) {
            // {11, 22}なら2番pinをHIGHに(master側のledを点灯)
            LPC_GPIO_PORT->SET0 |= (1 << 4);
        } else   if(data[0] == 22 && data[1] == 11) {
            // {22, 11}なら2番pinをLOWに(master側のledを消灯)
            LPC_GPIO_PORT->CLR0 |= (1 << 4);
        }

        // 適当に待つ。
        for(volatile int i = 0; i < 500000; i++) {}
    }
}
// master側の記述ここまで

// slave側の記述ここから
//  APIを使う前提となる変数を用意
static I2C_HANDLE_T *i2cHandleSlave;
static uint32_t i2cSlaveHandleMEM[0x20];
static I2C_PARAM_T slaveParam;
static I2C_RESULT_T slaveResult;

// 送信の準備
static void slaveSetupSend(uint8_t addr, uint8_t data[], uint32_t num, I2C_CALLBK_T  func) {
    // slaveのアドレスを設定
    // UM10601 24.4.12 I2C Set Slave Address
    LPC_I2CD_API->i2c_set_slave_addr(i2cHandleSlave, addr, 0x00);

    // コールバック関数を設定
    slaveParam.func_pt = func;

    // 受信用のバッファを設定
    slaveParam.buffer_ptr_send = data;

    // 送信バイト数
    slaveParam.num_bytes_send = num;

    // 読み込んだ文字数をリセット
    slaveResult.n_bytes_sent = 0;

    // i2c slaveの送信、割り込みモード
    // 完了したらコールバックが呼ばれる仕組み
    // UM10601 24.4.11 I2C Slave Transmit Interrupt
    LPC_I2CD_API->i2c_slave_transmit_intr(i2cHandleSlave, &slaveParam, &slaveResult);
}

// 受信完了時に呼ばれるコールバック
static void slaveCompleteRecv(uint32_t err_code, uint32_t n) {
    // 特になにもしない
}

// I2C割り込みハンドラ
extern "C" {
    void I2C_IRQHandler(void)
    {
        // 処理をrom apiに委譲
        LPC_I2CD_API->i2c_isr_handler(i2cHandleSlave);
    }
}

static void slaveMain() {
    //  I2C-bus ROM APIを使って初期設定
    // UM10601 24.4.14 I2C Setup
    i2cHandleSlave =LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cSlaveHandleMEM);

    // タイムアウト設定
    // UM10601 24.4.18 I2C time-out value
    LPC_I2CD_API->i2c_set_timeout(i2cHandleSlave, 100000);

    //  Enable/disable the I2C interrupt in interrupt slots #8 in the NVIC.
    NVIC->ISER[0] = (0x1 << 8);

    while(1) {
        if(LPC_GPIO_PORT->PIN0 & (1<<0)) {
            // 8番pinがHIGH
            // {11, 22}の2byteを送信
            uint8_t data[] = {11, 22};
            slaveSetupSend(0xc0, data, 2, slaveCompleteRecv);

            //  2番pinをHIGHに(slave側のledを点灯)
            LPC_GPIO_PORT->SET0 |= (1 << 4);
        } else {
            // 8番pinがLOW
            // {22, 11}の2byteを送信
            uint8_t data[] = {22, 11};
            slaveSetupSend(0xc0, data, 2, slaveCompleteRecv);

            //  2番pinをLOWに(slave側のledを消灯)
            LPC_GPIO_PORT->CLR0 |= (1 << 4);

        }
        // 適当に待つ
        for(volatile int i = 0; i < 200000; i++) {}
    }
}
// slave側の記述ここまで

int main(void) {
    // 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);

    // GPIO clockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // 2番pinを出力に設定
    LPC_GPIO_PORT->DIR0 |= (1<<4);
    LPC_GPIO_PORT->CLR0 |= (1<<4);

    if(LPC_GPIO_PORT->PIN0 & (1<<0)) {
        // 開始時点で8番がHIGH -> masterとして動作
        masterMain();
    } else {
        // 開始時点で8番がLOW -> slaveとして動作
        slaveMain();
    }
    return 0 ;
}

接続

  • 上のLPC810がmaster、下がslave。
    • 上は8番pinが常にHIGHになっているので、前述のプログラムより、masterとして動作する。
  • I2Cは、3番pinがSCL、4番pinがSDA。pull up抵抗は適当に3KΩ。
  • LEDは前回同様に写真ではわかりにくいが、2番pinについてる。
  • slaveの8番についてるボタンを押すと、master,slaveの両方のLEDが点灯、離すと消灯。
    • 前回と違って、タイムラグあり。masterからslaveに通信する間隔を長めにしているため。

補足/雑感

  • i2c_set_slave_addrの第3引数は、前回教わった0xffではなく、とりあえず0で続行。
  • master側の受信用のバッファ(サンプルではuint8_t buf[I2C_BUFF_SZ])、0byte目は受信用じゃなくてslaveのアドレスを入れておく必要がある。実際に読み込まれるデータは、[1]以降になる。
    • 受信byte数を指定するはずのnum_bytes_recも、そのぶんの1を足さなくてはならない。2byte受信したければ3を指定する。
    • APIと名乗るわりに、I2Cの仕組みを隠蔽する気がなさそう。使いやすくはないし汚い気がするが、こういうところが面白い気がする。
    • とはいっても、受信だからといって最下位bitを1にしておく必要はない模様。
  • 一応、master,slaveで指定する送受信byte数が異なった場合の実験結果(厳密な検証ではない)
    • masterのbyte数が大きい場合
      • masterはタイムアウト発生するが、そこまでのデータはbuffer_ptr_recに読み込まれる模様。
      • slaveは、正常に送信した、とみなされる模様。
    • slaveのbyte数が大きい場合
      • masterは、正常に受信した、とみなされる模様。
      • slaveは、どうなってるのかよくわからないが、例によってコールバックはいつまでも呼ばれない。
  • この際、ROM APIソースコードを見れたら良いのにと思う。

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

LPC810メモ:I2C-bus ROM API、I2C slaveとして動作させる(LPC810 2個使ってmaster-slave通信)

I2C-bus ROM APIを利用して、I2Cのslaveとして動作させる例。

LPC810を2個使って、それぞれmaster/slaveとして機能させ、masterからslaveに文字列を送信する。LPC810が2個必要だが、他の種類のマイコンを持ってない人でも試せるので、自分のように「LPC810から始めました」な人にとっては良い例を作れたのではないかと思う。そんな人が他に居るか知らないが。

プログラム自体はmaster側としてもslave側としても機能するもので、バイナリサイズは1608byte、まあまあ小さいのではないかと思われる。なお、master送信、slave受信のみ。逆は次に試したい。

前提とする環境はこちら→LPC810メモ 共通の準備
masterとして送信するだけのサンプルはこちら(今思えばこれは簡単だった)→http://d.hatena.ne.jp/mikkabo/20150401/1427899147

1byte受信や、固定長の受信まではわりと楽勝だった。でも「stopコンディションになるまで読む」を実現するのにかなり苦労したというか、I2C-bus ROM API、i2c_slave_receive_intr関数の挙動がバグじゃないかと疑うくらい不思議。最後の「補足/雑感」のところで疑問をぶちまけるので、詳しかったりベテランの親切な方が偶然ここを読んでくれていたら、ぜひご教示をいただきたいですm(_ _)m

  • LPC810を2個使う。
    • 1つはmaster、もう片方はslave。
    • プログラムは共通。開始直後に8番pinの入力を読み込み、LOWならmaster、HIGHならslaveとして動作する仕組み。
  • master側の8番にプッシュボタンをつけておいて、HIGH、LOWの状態に応じてslave側にI2Cで送信
    • HIGHなら"led on"という文字列を送信しつつ、自分の2番pinをHIGH(LED点灯)
    • LOWなら"led off"という文字列を送信しつつ、自分の2番pinをLOW(LED消灯)
    • 文字列である必然性は全くないが、複数byte通信しないと面白くないので、あえて文字列。
  • slave側は、"led on"を受信したら2番pinをHIGH(LED点灯)、"led off"を受信したら2番pinをLOW(LED消灯)
  • 結果として、master側のプッシュボタンを押す/離すことにより、master,slave両方のLEDが点灯/消灯することになる。

サンプルコード

  • コメントで「★」マークがついている部分は、I2C-bus ROM APIの不思議な挙動を回避しようと書いたコード。
  • 何が不思議と思っているかは、最後の「補足/雑感」のところに記載。
#include "LPC8xx.h"
#include<string.h>

#define I2C_BUFF_SZ         16

// 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)
}

// master側の記述ここから
//  APIを使う前提となる変数を用意
static I2C_HANDLE_T *i2cHandleMaster;
static uint32_t i2cMasterHandleMEM[0x20];

// num byteのデータを送信
static void masterSendData(uint8_t addr, uint8_t data[], uint32_t num)
{
    uint8_t d[I2C_BUFF_SZ];
    d[0] = addr; // 0byte目はslaveのアドレスを入れる
    memcpy(&d[1], data, num); // [1]から先がデータ本体
    I2C_PARAM_T param;
    I2C_RESULT_T result;

    param.num_bytes_send  = num + 1; // addrの分を含めてnum + 1 byte送信
    param.buffer_ptr_send  = &d[0];
    param.num_bytes_rec  = 0; // 受信はしない(byte)
    param.stop_flag   = 1; // 送信終わったらstop

    // タイムアウト設定
    // UM10601 24.4.18 I2C time-out value
    LPC_I2CD_API->i2c_set_timeout(i2cHandleMaster, 100000);

    // 送信
    // UM10601 24.4.2 I2C Master Transmit Polling
    LPC_I2CD_API->i2c_master_transmit_poll(i2cHandleMaster, &param, &result);
}

// master側の初期設定
static void masterMain() {
    //  I2C-bus ROM APIを使って初期設定
    // UM10601 24.4.14 I2C Setup
    i2cHandleMaster = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cMasterHandleMEM);

    // UM10601 24.4.15 I2C Set Bit Rate
    LPC_I2CD_API->i2c_set_bitrate(i2cHandleMaster, 12000000, 100000); // 速度100KHz

    while(1) {

        if(LPC_GPIO_PORT->PIN0 & (1<<0)) { 
            // 8番pinがHIGH
            // address 0xc0の slaveに文字列"led on"を送信
            uint8_t data[] = "led on";
            masterSendData(0xc0, data, sizeof(data));

            //  2番pinをHIGHに(master側のledを点灯)
            LPC_GPIO_PORT->SET0 |= (1 << 4);
        } else {
            // 8番pinがHIGH
            // address 0xc0 のslaveに文字列"led off"を送信
            uint8_t data[] = "led off";
            masterSendData(0xc0, data, sizeof(data));

            //  2番pinをLOWに(master側のledを消灯)
            LPC_GPIO_PORT->CLR0 |= (1 << 4);
        }

        //  適当にwait
        for(volatile int i = 0; i < 200000; i++) {}
    }
}
// master側の記述ここまで

// slave側の記述ここから
//  APIを使う前提となる変数を用意
static I2C_HANDLE_T *i2cHandleSlave;
static uint32_t i2cSlaveHandleMEM[0x20];

// 受信用のバッファ
static uint8_t rxBuff[I2C_BUFF_SZ];

// 割り込みを使う都合上、APIで使うパラメータをファイルスコープで用意
static I2C_PARAM_T slaveParam;
static I2C_RESULT_T slaveResult;

// ★何文字読んだか、rom apiの外側で記録するカウンタ
static uint32_t slaveRecvCounter = 0;

// 受信の準備
// 最初の1回だけは、slaveのアドレスとコールバックを設定して呼び出すようにした。
static void slaveSetupRecv(uint8_t addr = 0, I2C_CALLBK_T  func = 0) {
    if(addr != 0) {
        // slaveのアドレスを設定
        // UM10601 24.4.12 I2C Set Slave Address
        LPC_I2CD_API->i2c_set_slave_addr(i2cHandleSlave, addr, 0);
    }

    // コールバック関数を設定
    if(func != 0) { slaveParam.func_pt = func;   }

    // 受信用のバッファを設定
    slaveParam.buffer_ptr_rec = rxBuff;

    // 最大受信バイト数
    slaveParam.num_bytes_rec = sizeof(rxBuff);

    // 読み込んだ文字数をリセット
    slaveResult.n_bytes_recd = 0;
    slaveRecvCounter = 0; // ★読み込んだ文字数を、rom apiの外側でも記録

    // ★なぜかrom apiが使わない、Slave Deselectの割り込みを有効化
    // 16.6.3 Interrupt Enable Set and read register
    LPC_I2C->INTENSET |= (1 << 15); 

    // i2c slaveの受信、割り込みモード
    // 完了したらコールバックが呼ばれる仕組み
    // UM10601 24.4.10 I2C Slave Receive Interrupt
    LPC_I2CD_API->i2c_slave_receive_intr(i2cHandleSlave, &slaveParam, &slaveResult);
}

// 受信完了時に呼ばれるコールバック
static void slaveCompleteRecv(uint32_t err_code, uint32_t n) {
    static char buffer[I2C_BUFF_SZ];

    if (slaveResult.n_bytes_recd > 1) {
        // 1byteより多く読み込まれた場合
        // なお、0byte目はslave addressが入るので、1byteジャストだと
        // 実質読み込まれてない。

        // slave addressを除いたrxBuff[1] 以降を取り出す
        strncpy(buffer, (char *)(&rxBuff[1]), sizeof(buffer)); 
        buffer[sizeof(buffer) - 1] = 0; // null終端の保証

        // masterから送信された文字列が・・・
        if(strcmp(buffer, "led on") == 0) {
            // "led on"なら、2番pinをHIGHに(slave側のledを点灯)
            LPC_GPIO_PORT->SET0 |= (1 << 4);
        } else if(strcmp(buffer, "led off") == 0) {
            // "led on"なら、2番pinをLOWに(slave側のledを点灯)
            LPC_GPIO_PORT->CLR0 |= (1 << 4);
        }
    }

    // 次回の受信準備
    slaveSetupRecv();
}

// I2C割り込みハンドラ
extern "C" {
    void I2C_IRQHandler(void)
    {
        // ★slave pending状態で、slave receive.の場合、読み込み文字数を1増やす
        // UM10601 16.6.7 I2C Interrupt Status register
        // UM10601 16.6.2 I2C Status register
        if(
           (LPC_I2C->INTSTAT & (1 << 8)) && 
           ((LPC_I2C->STAT & (0x3 << 9)) == (0x1 << 9))) {
            slaveRecvCounter++;
        }

        // 処理をrom apiに委譲
        LPC_I2CD_API->i2c_isr_handler(i2cHandleSlave);

        // ★Slave Deselectedの割り込みを処理
        // UM10601 16.6.7 I2C Interrupt Status register
        if(LPC_I2C->INTSTAT & (1 << 15)) {
            if(slaveRecvCounter > 0) {
                // ★1byte以上読み込んでいたら、rom apiに代わってコールバック関数を呼ぶ
                slaveResult.n_bytes_recd = slaveRecvCounter;
                slaveCompleteRecv(0,0);
            }
            LPC_I2C->STAT |= (1 << 15);
        }
    }
}

static void slaveMain() {
    //  I2C-bus ROM APIを使って初期設定
    // UM10601 24.4.14 I2C Setup
    i2cHandleSlave =LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cSlaveHandleMEM);

    // タイムアウト設定
    // UM10601 24.4.18 I2C time-out value
    LPC_I2CD_API->i2c_set_timeout(i2cHandleSlave, 100000);

    //  Enable/disable the I2C interrupt in interrupt slots #8 in the NVIC.
    NVIC->ISER[0] = (0x1 << 8);

    // 受信の準備
    slaveSetupRecv(0xc0, slaveCompleteRecv);
}
// slave側の記述ここまで

int main(void) {
    // 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);

    // GPIO clockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // 2番pinを出力に設定
    LPC_GPIO_PORT->DIR0 |= (1<<4);

    if(LPC_GPIO_PORT->PIN0 & (1<<0)) {
        // 開始時点で8番がHIGH -> slaveとして動作
        slaveMain();
    } else {
        // 開始時点で8番がLOW -> masterとして動作
        masterMain();
    }
    return 0 ;
}

接続

  • 上のLPC810がmaster、下がslave。
    • 下は8番pinが常にHIGHになっているので、前述のプログラムより、slaveとして動作する。
  • I2Cは、3番pinがSCL、4番pinがSDA。pull up抵抗は適当に3KΩ。
  • LEDは写真ではわかりにくいが、2番pinについてる。
  • masterの8番についてるボタンを押すと、master,slaveの両方のLEDが点灯、離すと消灯。

補足/雑感

実は、I2C-bus ROM APIがコールバックを呼んでくれず、結構苦労した。疑問に思うことを書くので、親切な方が偶然ここを読んでくれていたら、ぜひご教示をいただきたい。

1byte受信や、固定長の受信は簡単だった。サンプルコードのslaveParam.num_bytes_recのところに、たとえば3を代入しておけば、3byte受信した時点でコールバック(上記例だとslaveCompleteRecv)が呼ばれる。ところがその場合、masterから2byteだけ送信してstopコンディションで打ち切った場合、コールバックが呼ばれない。

まず、i2c_slave_receive_intrが固定長の受信にしか対応しないのではないかと思った。しかし、UM10601「24.5.5 I2C Slave Mode Transmit/Receive」を見ると、「STOP or repeated START is received」まで受信する、って書いてあるのでおかしい。

それでも「i2c_slave_receive_intrはSTOP conditionを検知しない」というのが現時点での自分の見解であり、なぜそうなのかが非常に疑問。そう思う理由は下記なので、考え方が間違っていたら教えてほしい。

  • stop conditionが検知されたら、Slave Deselectの割り込みが発生するはず。
    • 根拠。UM10601「16.6.2 I2C Status register」→SLVDESEL
  • Slave Deselectの割り込みを使うつもりなら、INTENSETレジスタで対応するbitが立ってるはずだ。
    • 根拠。UM10601「16.6.3 Interrupt Enable Set and read register」→SLVDESELEN
  • i2c_slave_receive_intrをコール後に、INTENSETレジスタでどのbitが立ってるか確かめてみた。結果は以下のとおり。
    • 8:SLVPENDINGEN
    • 24:EVENTTIMEOUTEN
    • 25:SCLTIMEOUTEN
  • すなわち、timeoutを除くと、slave pendingの割り込み以外は処理する気がないようだ。15:SLVDESELENは無視される。
  • 上記より、i2c_slave_receive_intrはSTOP conditionを検知しない

このように思ったので、サンプルではSLVDESELENを立てて、検知したら勝手にcallbackを呼ぶようにしてみた。一応ちゃんと動いている模様。

さらに補足。

  • 上での述べたが、固定長であればslaveParam.num_bytes_recを受信するbyte数(正確にはaddressの1byteをプラスするので、受信するbyte数+1)にすれば良いので、サンプルコードの「★」マークのような余計なコードは省略できる。
  • stopコンディション検知前でも、受信したデータはslaveParam.buffer_ptr_recに順次格納される。
  • しかし、読み込んだbyte数であるslaveResult.n_bytes_recdは、最後に設定されるらしい。1byte毎にインクリメントされたりはしない。従って、サンプルのようにcallbackを無理やり呼び出す仕組みだと何文字読まれたかはわからない。
    • サンプルで、ROM APIの外で、slaveRecvCounterなどという変数を用意してカウントしているのはそのため。
  • 苦労した分楽しかった。LPC810をたくさん並べて動かすとか、夢が広がる。2個しか買ってないのが残念、でも逆に考えると、2個買っておいてよかった。
  • この後、master受信/slave送信も成功させたら、クラス化したいところ。

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

LPC810メモ:アナログコンパレータ(電圧ラダーの例)

アナログコンパレータで、電圧ラダーを使う例。

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

  • 電圧ラダーを用いると、外部からの入力電圧2本を比較するのではなく、入力電圧1本と、内部で設定する電圧と比較することができる。
    • GNDからVddの間で、5bit(32段階)で設定可能
  • この例では、32段階に変化させながら、比較結果によってLED点灯、消灯することで、「電圧が高いほど点灯時間が長いLチカ」をやってみる

サンプルコード

#include "LPC8xx.h"

int main( void )
{
    //  SWMのclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 7);

    // UM10601 9.5.10 Pin enable register 0
    // ACMP_I1_ENを立てて、
    // Analog comparatorを有効化。
    // pinは8番に固定されている。
    LPC_SWM->PINENABLE0 = 0xffffffbeUL;

    // UM10601 18.3 Basic configuration の手順に従った設定
    // Analog comparatorのclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 19);

    // Analog comparatorのPower downを無効化
    // UM10601 4.6.32 Power configuration register
    LPC_SYSCON->PDRUNCFG &= ~(1 << 15);

    // Clear the analog comparator peripheral reset using the PRESETCTRL register
    // UM10601 4.6.2 Peripheral reset control register
    LPC_SYSCON->PRESETCTRL &= ~(1<<12);
    LPC_SYSCON->PRESETCTRL |= (1<<12);

    // The analog comparator interrupt is connected to interrupt #11 in the NVIC.
    // 本例は割り込みは使わない

    //  UM10601 18.3 Basic configuration の手順に従った設定ここまで

    // UM10601 .6.1 Comparator control register
    LPC_CMP->CTRL =
        (1 << 8)        // +inputはACMP_I1(8番pin)
        |  (0 << 11)       // -inputはVoltage ladder
        |  (1 << 25)      // hysteresis = 5 mV
        ;

    // UM10601 18.6.2 Voltage ladder register
    LPC_CMP->LAD |= (1 << 0);// ladder有効化

    // I/O configurationのclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 18);

    // Pull-up/Pull-downともに無効化
    // UM10601 6.5.17 PIO0_0 register
    // UM10601  6.5.12 PIO0_1 register
    LPC_IOCON->PIO0_0 &= ~(0x3 <<  3);
    LPC_IOCON->PIO0_1 &= ~(0x3 <<  3);

    // GPIOに対してclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // PIO0_2(すなわち4番pin)をoutputに設定
    // UM10601 27.1 Packages より、PIO0_0は8番
    // UM10601 7.6.3 GPIO port direction registers
    LPC_GPIO_PORT->DIR0 |= (1<<2);

    while(1) {
        for(uint32_t i = 0; i < 32; i++) {
            // UM10601 18.6.2 Voltage ladder register
            // 比較対象であるVoltage ladderを、 i * (Vdd / 31) に設定
            uint32_t lad = LPC_CMP->LAD;
            lad &= ~(31 << 1);
            lad |= i << 1;
            LPC_CMP->LAD = lad;

            // UM10601 18.6.1 Comparator control register
            // COMPSTATのbitを見て、
            if(LPC_CMP->CTRL & (1 << 21)) {
                // 立ってたら4番点灯
                LPC_GPIO_PORT->SET0 |= (1<<2);
            } else {
                // でなければ4番消灯
                LPC_GPIO_PORT->CLR0 |= (1<<2);
            }
            // 適当にループして間隔をあける
            for(volatile int i = 0; i < 10000; i++) {}
        }
    }
}

接続

  • 半固定抵抗を適当にいじると、LED点滅における点灯/消灯時間の割合が変わる。

補足/雑感

  • 前の例と大差ないとはいえ、何のトラブルもなく、書いたとたんに成功した。そういうの初めてでうれしい。

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

LPC810メモ:アナログコンパレータ(比較結果をそのまま出力)

単純にアナログコンパレータを試してみた例。

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

  • 8番と5番の電圧を比較して、8番の方が高ければ4番がHIGHになる(4番につないだLEDが光る)

サンプルコード

#include "LPC8xx.h"

int main( void )
{
    //  SWMのclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 7);

    // UM10601 9.5.10 Pin enable register 0
    // ACMP_I1_EN,ACMP_I2_ENを立てて、
    // Analog comparatorを有効化。
    // pinは8番と5番に固定されている。
    LPC_SWM->PINENABLE0 = 0xffffffbcUL;

    // ACMP_O_O を4番pinに設定
    // UM10601 9.5.9 Pin assign register 8
    LPC_SWM->PINASSIGN8 = 0xffff02ffUL;

    // UM10601 18.3 Basic configuration の手順に従った設定
    // Analog comparatorのclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 19);

    // Analog comparatorのPower downを無効化
    // UM10601 4.6.32 Power configuration register
    LPC_SYSCON->PDRUNCFG &= ~(1 << 15);

    // Clear the analog comparator peripheral reset using the PRESETCTRL register
    // UM10601 4.6.2 Peripheral reset control register
    LPC_SYSCON->PRESETCTRL &= ~(1<<12);
    LPC_SYSCON->PRESETCTRL |= (1<<12);

    // The analog comparator interrupt is connected to interrupt #11 in the NVIC.
    // 本例は割り込みは使わない

    //  UM10601 18.3 Basic configuration の手順に従った設定ここまで

    // UM19691 8.6.1 Comparator control register
    LPC_CMP->CTRL =
          (1 << 8)        // +inputはACMP_I1(8番pin)
        |  (2 << 11)       // -inputはACMP_I2(5番pin)
        |  (1 << 25)      // hysteresis = 5 mV
        ;

    // PIO0_0(8番)とPIO0_1(5番)は、デフォルトでpull upされているらしい。
    // I/O configurationのclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 18);

    // pull-up/pull-downともに無効化
    // UM10601 6.5.17 PIO0_0 register
    // UM10601  6.5.12 PIO0_1 register
    LPC_IOCON->PIO0_0 &= ~(0x3 <<  3);
    LPC_IOCON->PIO0_1 &= ~(0x3 <<  3);
}

接続

  • 2つある半固定抵抗を適当にいじると、8番5番の電圧が変わり、8番が高ければLEDが光る仕組み。

補足/雑感

  • 入力は、ACMP_I1が8番、ACMP_I2が5番に固定されている。ACMP_I2が5番だと、開始時点でLOWだとISPモードになっちゃわない?
  • 最後のpull-upの無効化。そもそもdefaultだとpull-upされているというのも知らなかったけど、本当に無効化必要なのかよくわかってない。この例だと、あってもなくても見た目に挙動変わらなかった。本当にこれで無効化できてるかもわからない。

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

LPC810メモ:SCTの例その3(超音波距離センサ)

SCTの、event発生時点のタイマの値を記録するcapture機能を試してみたくて考えてみた例。

前提とする環境はこちら→LPC810メモ 共通の準備
SCT全般のメモはこちら→LPC810メモ:SCTimer/PWM (SCT)について

  • amazonで500円くらいで買ったこのHC-SR04というセンサを使う。
  • このセンサが5vなので、この昇圧コンバータも使う。
  • このセンサは、超音波を発して、音が反射して戻ってくるまでの経過時間を返してくれるので、音速で除算すれば距離がわかる、という仕組み。
    • trigger端子を10μsHIGHにしてやると超音波を発する。
      • → SCTの出力で、triggerを10μsだけHIGHにしてやればいい。
    • 経過時間は、「echo端子がHIGHになっている時間」という形で表現される。
      • → SCTの入力にechoをつなぎ、立ち上がり/下がりの時間をcaptureで記録して、引き算すればよい。
  • より具体的にはこうしてみた。
    • 1ms経過後にtriggerへの出力(2番pin)をHIGHに(event0)
    • その10μs後にtriggerへの出力(2番pin)をLOWに(event1)
    • echo(3番pin)が立ち上がったらその時間を記録(event3)
    • echo(3番pin)が立ち下がったらその時間を記録(event4)
    • 検知できずに30ms経過したら諦める(event2)
    • event4の値からevent3の値 を引いて距離測定。
  • 連続的に距離を測定して、「近いほど速くLチカ」してみる。

サンプルコード

#include "LPC8xx.h"

// SCTの設定
void setSct() {
    //  SWMのclockを有効化
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

    //  3番pin(PIO_3)にSCT入力の0番(CTIN_0) を設定
    //  UM10601 9.5.6 Pin assign register 5
    LPC_SWM->PINASSIGN5 = 0x03ffffffUL;

    //  2番pin(PIO_4)にSCT出力の0番(CTOUT_0) を設定
    //  UM10601 9.5.7 Pin assign register 6
    LPC_SWM->PINASSIGN6 = 0x04ffffffUL;

    // RESET 以外の無効化
    LPC_SWM->PINENABLE0 = 0xffffffbfUL;

    // UM10601 10.3 Basic configuration
    // に従った準備
    //  SCTのclockを有効化(UM10601 4.6.13 System clock control register)
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8);

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

    // 今回は割り込みは使わない
    // UM10601 3.4.1 Interrupt Set Enable Register 0 register
    // NVIC->ISER[0] = (0x1 << 9);

    // 以下、SCTの動作の設定

    // UM10601 10.6.1 SCT configuration register
    // 今回は16bit一本(L)のみを使う。
    // UNIFY = 0でAUTOLIMITも使わないので、 LPC_SCT->CONFIG の設定は不要

    // UM10601 10.6.2 SCT control register
    LPC_SCT->CTRL_L  |= (12 - 1 ) << 5; // PRE_L = 12 -> 12clock(1μ秒)で1カウント

    // match/captureレジスタの3,4番は、capture用に設定。
    // センサからの入力を受け取る。
    // UM10601 10.6.10 SCT match/capture registers mode register
    LPC_SCT->REGMODE_L = 1 << 3  |  1 << 4;

    //  カウンタにmatchする値を定義
    // UM10601 10.6.20 SCT match reload registers 0 to 4 (REGMODEn bit = 0)
    LPC_SCT->MATCH[0].L = 1000; // 開始1ms後(ここで出力をHIGHにする)
    LPC_SCT->MATCH[1].L = 1010; // 開始1ms + 10μs後(ここで出力をLOWにする)
    LPC_SCT->MATCH[2].L = 30000; // 開始30ms(測定のタイムアウト)
    // limitは使わないので、MATCHRELでなく、MATCHそのものに設定している。

    // event0番(match 0、開始1ms後に発生)
    // 10.6.22 SCT event state mask registers 0 to 5
    // 10.6.23 SCT event control registers 0 to 5
    LPC_SCT->EVENT[0].STATE = 0x01; // state0で発生
    LPC_SCT->EVENT[0].CTRL =
        (0 << 0)    // MATCHSEL = 0 -> match0に対応
        |  (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (1 << 12);  // COMBMODE = 1 -> match だけに対応

    // event1番(match 1、開始1ms + 10μs後に発生、state1に移行)
    // 10.6.22 SCT event state mask registers 0 to 5
    // 10.6.23 SCT event control registers 0 to 5
    LPC_SCT->EVENT[1].STATE = 0x01; // state0で発生
    LPC_SCT->EVENT[1].CTRL =
        (1 << 0)    // MATCHSEL = 0 -> match0に対応
        |  (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (1 << 12)  // COMBMODE = 1 -> match だけに対応
        |  (1 << 14)   // STATELD = 1 -> 状態をSTATEVに設定
        |  (1 << 15);  // STATEV = 1

    // event2番(match 2、タイムアウト。state 0に戻る。)
    // 10.6.22 SCT event state mask registers 0 to 5
    // 10.6.23 SCT event control registers 0 to 5
    LPC_SCT->EVENT[2].STATE = 0x02; // state1で発生
    LPC_SCT->EVENT[2].CTRL =
        (2 << 0)    // MATCHSEL = 2 -> match2に対応
        |  (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (1 << 12)  // COMBMODE = 1 -> match だけに対応
        |  (1 << 14)   // STATELD = 1 -> 状態をSTATEVに設定
        |  (0 << 15);   // STATEV = 0

    // event3番(state1で、入力1の立下りで発生)
    LPC_SCT->EVENT[3].STATE = 0x02; // state1で発生
    LPC_SCT->EVENT[3].CTRL =
        (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (0 << 6)    // IOSEL 入力1に対応
        |  (1 << 10)   // IOCOND riseに対応
        |  (2 << 12);  // COMBMODE = 2 -> I/O condition だけに対応


    // event2番(state1で、入力1の立下りで発生。state0に戻る。)
    LPC_SCT->EVENT[4].STATE = 0x02; // state1で発生
    LPC_SCT->EVENT[4].CTRL =
        (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (0 << 6)    // IOSEL 入力1に対応h
        |  (2 << 10)   // IOCOND fallに対応
        |  (2 << 12)  // COMBMODE = 2 -> I/O condition だけに対応
        |  (1 << 14)   // STATELD = 1 -> 状態をSTATEVに設定
        |  (0 << 15);   // STATEV = 0

    // event3,4番が発生したら、それぞれcaptureの3,4番にタイマの値を記録
    // センサからの入力。
    // UM10601 10.6.21 SCT capture control registers 0 to 4 (REGMODEn bit = 1)
    LPC_SCT->CAPCTRL[3].L = 1 << 3;
    LPC_SCT->CAPCTRL[4].L = 1 << 4;

    // 出力の定義
    // event0で出力0をHIGHにし、1でLOWにする。
    // センサに対するトリガ。
    // UM10601 10.6.24 SCT output set registers 0 to 3
    // UM10601 10.6.25 SCT output clear registers 0 to 3
    LPC_SCT->OUT[0].SET = (1 << 0);
    LPC_SCT->OUT[0].CLR = (1 << 1);

    // event2,4番でhalt
    LPC_SCT->HALT_L = 1 << 4 | 1 << 2;
}

// SCTを使った距離測定
int16_t getDistance() {
    // カウンタをゼロに戻す
    // UM10601 10.6.7 SCT counter register
    LPC_SCT->COUNT_L = 0;

    // capture3,4をゼロに戻す
    // UM10601 10.6.19 SCT capture registers 0 to 4 (REGMODEn bit = 1)
    LPC_SCT->CAP[3].L = 0;
    LPC_SCT->CAP[4].L = 0;

    // haltを解除してstart
    /// UM10601 10.6.2 SCT control register
    LPC_SCT->CTRL_L &= ~(1 << 2);  // HALT_L = 0

    // haltになるまで待つ
    while((LPC_SCT->CTRL_L & (1 << 2)) == 0) {}

    // capture地を読んで距離測定
    uint16_t rise = LPC_SCT->CAP[3].L; // 入力の立ち上がり時間
    uint16_t fall = LPC_SCT->CAP[4].L;  // 入力の立ち下がり時間
    int16_t ret;
    if(fall == 0 || rise == 0) {
        // どちらかゼロなら測定失敗
        ret = -1;
    } else {
        // HIGHだった時間を求める
        ret = fall - rise;
        if(ret < 0) {
            ret = 0; // 何かの間違いで0未満になったら測定失敗扱い
        }  else {
            ret = ret / 58;  // cm単位に変換。58で割ればよいらしい。
        }
    }
    return ret;
}

int main(void) {
    // SCTの設定
    setSct();

    // GPIOに対してclockを有効化
    // UM10601 4.6.13 System clock control register
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // PIO0_0(8番pin)をoutputに設定
    // UM10601 27.1 Packages より、PIO0_0は8番
    // UM10601 7.6.3 GPIO port direction registers
    LPC_GPIO_PORT->DIR0 |= (1<<0);

    while(1)  {
        // SCTで距離測定
        int16_t distance = getDistance();
        if(distance == -1 || distance > 20) { distance = 20; }  // 測定エラーまたは20cm以上は、20cm扱い。

        // 距離が長いほどwaitを長くしてLEDを点滅2回。
        for(int i = 0; i < 4; i++) {
            // PIO0_0の出力をtoggle
            // UM10601 7.6.9 GPIO port toggle registers
            LPC_GPIO_PORT->NOT0 |= (1<<0);
            for(volatile int i = 0; i < 10000 * (distance + 1); i++) {}
        }
    }
    return 0 ;
}

接続

補足/雑感

  • LPC_SCT->REGMODE_Lの設定、'1 << 3 | 1 << 4;'でなくなぜか'1 << 3 | | 1 << 4;'と書いていて気づけず、ものすごくハマった。

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