LPC810メモ:I2C送受信クラス(マスターのみ)とモータードライバ

かなり以前実験したI2CのROM APIを使った送受信(マスターのみ)をクラス化。
サンプルでは、I2Cのモータードライバによりモーターを順方向、逆方向に回す。


前提とする環境はこちら→LPC810メモ 共通の準備
基本的なサンプルについてはこちら→LPC810みっかぼ自作サンプル集

モータードライバはDRV8830、秋月で以前買ったこれ→DRV8830モータードライバモジュール

DRV8830を動作させるために主に参考にしたのは、秋月のページからダウンロードできる日本語の説明書と、mbedのサンプル


このサンプルでは、まずモーターが順方向に徐々にスピードを上げて回り、次に逆方向に回り、を繰り返す。


I2CBaseクラスのメンバ関数は以下。具体的な使い方はサンプル参照。

  • static void init()
    • 初期化。最初に必ず呼ぶ。
  • static void masterSendData(uint8_t addr, uint8_t data[], uint32_t num)
    • アドレスaddrのslaveに、dataの内容をnumバイト送信
  • static void masterSendData(uint8_t addr, std::array data, uint32_t num)
    • 同上だが、dataがstd::arrayとなっているので、送信内容を即値で渡せる。送信用の配列を別途定義したくない場合に使う。
    • 例) masterSendData(0xc0, {1,0b10000000}, 2);
  • static void masterRecvData(uint8_t addr, uint8_t data[], uint32_t num)
    • アドレスaddrのslaveから、dataにnumバイト受信

サンプルコード

sample.cpp(クラスを使う側)

#include "LPC8xx.h"
#include "I2CBase.h"

// 別途定義したI2CBaseクラス。SDA,SCLピンを指定。
using I2C = I2CBase<2,3>; // 4番(GPIO2)、3番(GPIO3)

// DRV8830のI2Cアドレス
constexpr uint8_t addr = 0xc0;

int main(void) {
    I2C::init();

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

    for(;;) {
        // FAULTレジスタを確認
        uint8_t r[1];
        I2C::masterSendData(addr, {1}, 1); //レジスタ1(FAULT)を指定
        I2C::masterRecvData(addr, r, sizeof(r));
        // ここでr[0]にFAULTレジスタの値が読み込まれたはず。
        // でもこの例では特になんにも使わない

        // 順方向で徐々に出力を上げる
        // v=63まであるけど、大きくすると不安定になるので半分で止めておく
        for(uint8_t v = 0; v < 32; v++) {
            // FAULTレジスタをリセット(低電圧とかで何かとFAULTになるので、ここでは常に気にせずリセット)
            I2C::masterSendData(addr, {
                    1,           // レジスタ1(FALUT)に設定
                        0b10000000   // 最上位ビットに書き込むとリセット
			}, 2); // 2byte書き込み

            // 出力の大きさを設定
            I2C::masterSendData(addr, {
                    0,
                        (uint8_t)(
                                  v << 2 // 上位6bit → 出力の大きさ。
                                  | 0b01 // 下位2bit → 方向。00が停止、01が順方向、10が逆方向(多分)
                                  )
			}, 2); // 2byte書き込み
            for(volatile int i = 0; i < 100000; i++); // ちょっと待つ
        }
        for(volatile int i = 0; i < 1000000; i++); // ちょっと待つ

        // 逆方向で徐々に出力を上げる
        for(uint8_t v = 0; v < 32; v++) {
            I2C::masterSendData(addr, {1,0b10000000}, 2); // リセット
            I2C::masterSendData(addr, {0,(uint8_t)(v << 2 | 0b10)}, 2);
            for(volatile int i = 0; i < 100000; i++); // ちょっと待つ
        }
        for(volatile int i = 0; i < 1000000; i++); // ちょっと待つ
    }

    return 0;
}

I2CBase.h(I2C送受信クラス、今後使いまわす予定)

#ifndef I2CBASE_H_
#define I2CBASE_H_

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

// UM10601 24.4 API description
// に相当すると思われるプロトタイプ宣言をlpcopenから抜いてきて一部加工
typedef int ErrorCode_t; // 本当はenumだけどint扱い
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
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
// より、アドレスの設定
const I2CD_API_T *LPC_I2CD_API = ((*(LPC_ROM_API_T * *) 0x1FFF1FF8UL)->pI2CD);

template <uint8_t SDA_PIN, uint8_t SCL_PIN, uint32_t SPEED = 100000, uint32_t TIMEOUT = 100000>
    class I2CBase {
    public:
    static constexpr int I2C_BUFF_SZ = 16;

    // 初期化、最初に一回呼ぶ
    static void init();

    // 送信
    static void masterSendData(uint8_t addr, uint8_t data[], uint32_t size);

    // 送信、std::arrayで引数を渡したい場合用。
    static void masterSendData(uint8_t addr, std::array<uint8_t, I2C_BUFF_SZ> data, uint32_t size) {
        masterSendData(addr, data.data(), size);
    }

    // 受信
    static void masterRecvData(uint8_t addr, uint8_t data[], uint32_t size);

    // ※スレーブは未実装
    private:
    //  APIを使う前提となる変数
    static I2C_HANDLE_T *i2cHandleMaster;
    static uint32_t i2cMasterHandleMEM[0x20]; //  0x20である理由は知らない。LPCOpenのサンプルの真似。
};

template <uint8_t SDA_PIN, uint8_t SCL_PIN, uint32_t SPEED, uint32_t TIMEOUT>
    I2C_HANDLE_T *I2CBase<SDA_PIN,SCL_PIN,SPEED,TIMEOUT>::i2cHandleMaster;
template <uint8_t SDA_PIN, uint8_t SCL_PIN, uint32_t SPEED, uint32_t TIMEOUT>
    uint32_t I2CBase<SDA_PIN,SCL_PIN,SPEED,TIMEOUT>::i2cMasterHandleMEM[0x20];

// 初期化
template <uint8_t SDA_PIN, uint8_t SCL_PIN, uint32_t SPEED, uint32_t TIMEOUT>
    void I2CBase<SDA_PIN,SCL_PIN,SPEED,TIMEOUT>::init() {
    // I2Cを使うために、SWMでpinを割りあてる
    // SWMのclockを有効化。
    // UM10601 4.6.13 System clock control register
    // → SWMは7bit目
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

    // SDAのpinを設定
    // UM10601  9.5.8 Pin assign register 7
    LPC_SWM->PINASSIGN7 = 0x00ffffffUL | SDA_PIN << 24;

    // SCLのpinを設定
    // UM10601  9.5.9 Pin assign register 8
    LPC_SWM->PINASSIGN8 = 0xffffff00UL | SCL_PIN;

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

    //  I2C-bus ROM APIを使って初期設定
    i2cHandleMaster = LPC_I2CD_API->i2c_setup(LPC_I2C_BASE, i2cMasterHandleMEM);
    LPC_I2CD_API->i2c_set_bitrate(i2cHandleMaster, 12000000, SPEED); // 速度100KHz
}

// num byteのデータを送信
template <uint8_t SDA_PIN, uint8_t SCL_PIN, uint32_t SPEED, uint32_t TIMEOUT>
    void I2CBase<SDA_PIN,SCL_PIN,SPEED,TIMEOUT>::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, TIMEOUT);

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

// num byteのデータを受信
template <uint8_t SDA_PIN, uint8_t SCL_PIN, uint32_t SPEED, uint32_t TIMEOUT>
    void I2CBase<SDA_PIN,SCL_PIN,SPEED,TIMEOUT>::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, TIMEOUT);

    // 受信
    // 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]から先がデータ本体。
}

#endif /* I2CBASE_H_ */

接続

全体

  • 電源はモーターとマイコンで分けたほうが良いとどこかで読んだので、GNDだけつないで電源は別個にした
    • マイコン用、の方にはボタン電池(CR3032)をつないでいる。
    • モーター用、の方には単三の充電池(ニッケル水素)を3本。電圧違うけど動いている模様。
  • 左上の緑と赤の2本はモーターにつなぐ
  • 水色の3つはコンデンサ、多少なりとも安定させたく大き目の1μF(多分)のやつ
  • 真ん中あたりの紫と緑はI2Cの線。それぞれ3kΩの抵抗でプルアップ。
    • 紫はSDA、LPC810の4番と、DRV8830の9番をつなぐ。
    • 緑はSCL、LPC810の3番と、DRV8830の10番をつなぐ。

DRV8830周辺

  • 1,3(OUT2,OUT1)はモーターにつなぐ。
  • 2(ISENSE)はGND。
  • 4,5(VCC,GND)は普通にVCC,GND。
  • 6(FAULTn)は使わない。
  • 7,8(A0,A1)は両方GND、これによりI2Cアドレスは0xC0hとなる。
  • 9,10(SDA,SCL)は、それぞれLPC810の4番、3番に。

補足/雑感

  • 電圧降下なのか、出力を上げるとリセットかかる。電源わけてもだめなんだろうか。回路の知識がないので、そのあたりはいつか勉強したい。

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