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, ¶m, &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, ¶m, &result); // 受信 memcpy(data, &buf[1], num); // 引数のdataに書き戻す。 // 0byte目はslaveのアドレスなので、[1]から先がデータ本体。 } #endif /* I2CBASE_H_ */
接続
- 電源はモーターとマイコンで分けたほうが良いとどこかで読んだので、GNDだけつないで電源は別個にした
- 左上の緑と赤の2本はモーターにつなぐ
- 水色の3つはコンデンサ、多少なりとも安定させたく大き目の1μF(多分)のやつ
- 真ん中あたりの紫と緑はI2Cの線。それぞれ3kΩの抵抗でプルアップ。
- 紫はSDA、LPC810の4番と、DRV8830の9番をつなぐ。
- 緑はSCL、LPC810の3番と、DRV8830の10番をつなぐ。
- 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番に。