I2Cというシリアルコミュニケーションを実際に仕事で使用しているので理解したいと以前から思っておりました。
私が業務で使用しているマイコンはARMマイコンでは無いですが、I2Cの理解には特にマイコンの種別は関係ないので勉強をしていきます。
ちょうどこれは私がARMマイコンを勉強のきっかけになった雑誌の付録基板があったのでそちらで行います。
付録基板が無くても、回路自体はそれほど複雑ではないので、ブレッドボードで組むことも可能なはずです。
回路図を記しておきます。今回は、I2Cの概要を理解することが目的なので、ここで行う内容の部分のみの回路図を示しておきます。
今回から少々複雑になります。付録基板を持っていればほとんどジャンプワイヤにて配線する部分はありませんが ブレッドボードにフルで配線するとなると大変かもしれませんが、決して組めないレベルの規模ではありません。
ピンクの一点鎖線でくくった部分は付録基板愛配線です。私が参考にしている紹介した本では、SCLラインとSDAラインは10kΩにてプルアップしていないような回路図ですが、上記雑誌の回路図を参考にして書いた回路図では、10kΩにてプルアップされております。
I2Cインターフェイスは私が実際に仕事で相手にしている回路においも、まさに回路図のように、外部記憶OCとの通信に使用していたり、その他の専用ICとの通信に頻繁に使用しております。実際にSPIインターフェイスも同様、実際の製品回路ないで使用している通信インターフェイスです。
I2Cは、まさにこのマイコンのメーカでもあるNXPがが開発したシリアル通信インターフェイスです。電源を除いて、2本の線で通信します。
制御するマスタ側と、制御されるスレーブ側との間で通信をします。今回LPC810はI2Cマスタとして動作させ、24FC64はスレーブになります。各スレーブは固有のスレーブアドレスを持っています。I2Cマスタはスレーブアドレスを送信して、送信先を指定します。
スレーブアドレスは、通常7ビット構成のようで、7ビットということは、128種類の状態を識別で来ますから、接続できるスレーブの最大個数は128個接続できるということです。ただ、実際には予約アドレスがあり、128個を接続で切るわけでは無い。
| LCD AQM0802 | ||
|---|---|---|
| 番号 | 名称 | 機能 |
| 1 | V0 | テストピン |
| 2 | Vout | DC-DC電圧出力 |
| 3 | CAP1N | 外部コンデンサピン |
| 4 | CAP1P | 外部コンデンサピン |
| 5 | Vdd | 電源(3.1~3.5V) |
| 6 | Vss | グランド |
| 7 | SDA | シリアルデータ |
| 8 | SCL | シリアルクロック |
| 9 | XRESETB | リセット |
参考文献で紹介されいてる、romi2c.hというのは、ヘッダファイル名についてはこの文献特有のものですが、ヘッダファイルの内容自体はLPC810のマニュアル(UM10601)Boot Romの項目あたりから長々と記載されております。
ここで、大切なのは以下の図になります。
上図はマニュアルに記載されている、Boot Rom構造です。このROM領域にI2Cや電力管理、シリアルインターフェイスなどのいわゆる機能が使いやすいようパッケージングされたものが記憶されているということです。(Excelでいうところのオブジェクトが定義されているイメージでしょうか?)
この図より、ROMDriverの開始先頭アドレスは0x1FFF1FF8と分かります。したがって、このROMDriverの木庭kのアドレスにアクセスして、機能をうまく使用するには、構造体をうまく使用することが想像できます。
したがって、まずはこのROMDriverテーブル領域(以下テーブル領域)の大まかなアドレスを定義するために、
以下のようなことを定義します。
MCUXPresso IDEにて、「File」→「New」→「Other」を選択し、以下のウィンドウにて、
Header Fileを選択し「Next」をクリックすると、
となるので、Header file:の所に、ここでは、例えば参考文献と同様、ROM_I2C_Hという名前を指定してOKします。するとsrcフォルダにヘッダファイルが作られ、ヘッダファイル内には、条件コンパイル式が入力されたひな形が自動的に作成されております。後は基本的には、マニュアルに記載されているコードを転記していく感じです。
すべての構造体を記入しても、あまり中身に関して詳細には迫れないため、ポイント部分だけ記載しますと、
マニュアルで以下の通り、メモリ領域を確保する構造体を定義します。
参考書のP70、P71に記載されているコードを私なりに解説していきます。多分これをしっかり理解していないと後々困る予感がしたのでここにまとめておきます。
まず、先に示した方法でヘッダファイルを作成すると、ひな形として、自動的に
#ifndef ROMI2C_H_
#define ROMI2C_H_
#endif /* ROMI2C_H_ */
は生成されます。
#include "stdint.h"
これはとりあえずおまじないとして進みましょう。これは私の推測ですが、後々わかったのですがこれを記載していなくても、プログラム自体は問題なく 動作します。 uint8_tやuint32_tといった変数の定義をプログラム中で使用できるようにするために定義していると思われますが、これ以外のヘッダファイル内 すでにこのような宣言や変数の定義はされている(例えば、LPC810xx.h)のでここではあまり深い意味はなさそうです。
I2CROMドライバへのポインタの定義をしています。
typedef void* I2C_HANDLE_T //I2C ROmドライバに割り当てるポインタ
typedef void (*I2C_CALLBK_T) (uint32_t err_code, uint32_t n //I2Cの割り込み関数
これはマニュアルのどこに記載されているかというと以下の写真い示す通りに記載されていました。
![]()
typedef struct _ROM_API { //type struct ROM_CALL ROM;
const uint32_t unused[3];
const PWRD_API_T *pPWRD;
const uint32_t p_dev1;
const I2CD_API_T *pI2CD;
const uint32_t p_dev3;
const uint32_t p_dev4;
const uint32_t p_dev5;
const UARTD_API_T *pUARTD;
} ROM_API_T;
テーブル領域と同じように5個目に、*pI2CDというのが定義されております。後は同様な構造体の定義を先ほど作成したヘッダファイルにマニュアルで記載されているとおりに転記していえばOKです。私は以下の2行のコードを理解するのにかなり時間がかかりました。
#define ROM_DRIVER_BASE (0x1FFF1FF8UL)
#define LPC_I2CD_API ((I2CD_API_T * ) ((*(ROM_API_T * *) (ROM_DRIVER_BASE))->pI2CD))
1行目はもちろんなんてことはない、アドレスの定義ですが、2行目が難しい!理解するのに数時間かかりました。
この2行目の意味ですが、私の努力なりにそういう意味かまとめますと、
以下の部分です。
PARAM構造体
typedef struct i2c_A { //parameters passed to ROM function
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; // callback function pointer
uint8_t stop_flag;
uint8_t dummy[3] ; // required for word alignment
} I2C_PARAM ;
RESULT構造体
typedef struct i2c_R { // RESULTs struct--results are here when returned
uint32_t n_bytes_sent ;
uint32_t n_bytes_recd ;
} I2C_RESULT ;
I2CROMドライバの入力パラメータの定義のようです。PARAM構造体はI2Cドライバに渡されるパラメータがあるようです。またRESULT構造体はI2Cドライバが呼び出された後の結果が格納されるようです。
![]()
I2CROMドライバはエラーコードを決まった定数で返すようです。それがenum構造体として定義します。
typedef enum
{
LPC_OK=0, /**< enum value returned on Success */
ERROR,
ERR_I2C_BASE = 0x00060000,
/*0x00060001*/ ERR_I2C_NAK=ERR_I2C_BASE+1,
/*0x00060002*/ ERR_I2C_BUFFER_OVERFLOW,
/*0x00060003*/ ERR_I2C_BYTE_COUNT_ERR,
/*0x00060004*/ ERR_I2C_LOSS_OF_ARBRITRATION,
/*0x00060005*/ ERR_I2C_SLAVE_NOT_ADDRESSED,
/*0x00060006*/ ERR_I2C_LOSS_OF_ARBRITRATION_NAK_BIT,
/*0x00060007*/ ERR_I2C_GENERAL_FAILURE,
/*0x00060008*/ ERR_I2C_REGS_SET_TO_DEFAULT
} ErrorCode_t;
i2c_get_status() 関数というのがある?ようで、I2Cの現在のステータスのenum構造体として定義されております
typedef enum I2C_mode {
IDLE,
MASTER_SEND,
MASTER_RECEIVE,
SLAVE_SEND,
SLAVE_RECEIVE
} I2C_MODE_T ;
長いですが、ハードウェアマニュアルに乗っているAPICallの参考そのものです。 これはハードウェアマニュアルにそのまま掲載されております。
typedef struct I2CD_API { // index of all the i2c driver functions
void (*i2c_isr_handler) (I2C_HANDLE_T* h_i2c) ; // ISR interrupt service request
// MASTER functions ***
ErrorCode_t (*i2c_master_transmit_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr );
ErrorCode_t (*i2c_master_receive_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr );
ErrorCode_t (*i2c_master_tx_rx_poll)(I2C_HANDLE_T* h_i2c,I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_master_transmit_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_master_receive_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_master_tx_rx_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT*
ptr ) ;
// SLAVE functions ***
ErrorCode_t (*i2c_slave_receive_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT*
ptr ) ;
ErrorCode_t (*i2c_slave_transmit_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_slave_receive_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT*
ptr ) ;
ErrorCode_t (*i2c_slave_transmit_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_set_slave_addr)(I2C_HANDLE_T* h_i2c,
uint32_t slave_addr_0_3, uint32_t slave_mask_0_3);
// OTHER functions
uint32_t (*i2c_get_mem_size)(void) ; //ramsize_in_bytes memory needed by I2C drivers
I2C_HANDLE_T* (*i2c_setup)(uint32_t i2c_base_addr, uint32_t *start_of_ram ) ;
ErrorCode_t (*i2c_set_bitrate)(I2C_HANDLE_T* h_i2c, uint32_t P_clk_in_hz,
uint32_t bitrate_in_bps) ;
uint32_t (*i2c_get_firmware_version)() ;
I2C_MODE_T (*i2c_get_status)(I2C_HANDLE_T* h_i2c ) ;
} I2CD_API_T ;
以下は参考書の最後の定義をマニュアルに記載されている内容で定義したものです。
#define ROM_DRIVER_BASE (0x1FFF1FF8UL)
#define LPC_I2CD_API ((I2CD_API_T *) ((*(ROM_API_T * *) (ROM_DRIVER_BASE))->pI2CD))
参考書では以下のように定義されていますが、意味は同じになると思います。
#define ROM_DRIVERS_PTR((ROM *)(*((unsigned int *)0x1FFF1FF8)))
以上が長くなりましたが、LPC810のROMドライバの機能を利用してI2Cを利用するための定義です。
以下に、私が使用したヘッダファイルを示します。かなり時間が掛かりましたので、全体を記しておきます。
*
* RomI2C.h
*
* Created on: 2023/12/17
* Author: YukiyaInoue
*/
#ifndef ROMI2C_H_
#define ROMI2C_H_
#include "stdint.h"
typedef void *I2C_HANDLE_T; //I2C ROMドライバに割り当てるポインタ
typedef void (*I2C_CALLBK_T) (uint32_t err_code, uint32_t n);
#define ROM_DRIVER_BASE (0x1FFF1FF8UL)
#define LPC_I2CD_API ((I2CD_API_T *) ((*(ROM_API_T * *) (ROM_DRIVER_BASE))->pI2CD))
//I2CROMドライバ エラーコードEnum構造体
typedef enum
{
LPC_OK=0, /**< enum value returned on Success */
ERROR,
ERR_I2C_BASE = 0x00060000,
/*0x00060001*/ ERR_I2C_NAK=ERR_I2C_BASE+1,
/*0x00060002*/ ERR_I2C_BUFFER_OVERFLOW,
/*0x00060003*/ ERR_I2C_BYTE_COUNT_ERR,
/*0x00060004*/ ERR_I2C_LOSS_OF_ARBRITRATION,
/*0x00060005*/ ERR_I2C_SLAVE_NOT_ADDRESSED,
/*0x00060006*/ ERR_I2C_LOSS_OF_ARBRITRATION_NAK_BIT,
/*0x00060007*/ ERR_I2C_GENERAL_FAILURE,
/*0x00060008*/ ERR_I2C_REGS_SET_TO_DEFAULT
} ErrorCode_t;
//RESULT構造体
typedef struct i2c_R { // RESULTs struct--results are here when returned
uint32_t n_bytes_sent ;
uint32_t n_bytes_recd ;
} I2C_RESULT ;
//I2Cステータス構造体
typedef enum I2C_mode {
IDLE,
MASTER_SEND,
MASTER_RECEIVE,
SLAVE_SEND,
SLAVE_RECEIVE
} I2C_MODE_T ;
//PARAM構造体
typedef struct i2c_A { //parameters passed to ROM function
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; // callback function pointer
uint8_t stop_flag;
uint8_t dummy[3] ; // required for word alignment
} I2C_PARAM ;
typedef struct I2CD_API { // index of all the i2c driver functions
void (*i2c_isr_handler) (I2C_HANDLE_T* h_i2c) ; // ISR interrupt service request
// MASTER functions ***
ErrorCode_t (*i2c_master_transmit_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT* ptr );
ErrorCode_t (*i2c_master_receive_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr );
ErrorCode_t (*i2c_master_tx_rx_poll)(I2C_HANDLE_T* h_i2c,I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_master_transmit_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_master_receive_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_master_tx_rx_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT*
ptr ) ;
// SLAVE functions ***
ErrorCode_t (*i2c_slave_receive_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT*
ptr ) ;
ErrorCode_t (*i2c_slave_transmit_poll)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_slave_receive_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp, I2C_RESULT*
ptr ) ;
ErrorCode_t (*i2c_slave_transmit_intr)(I2C_HANDLE_T* h_i2c, I2C_PARAM* ptp,
I2C_RESULT* ptr ) ;
ErrorCode_t (*i2c_set_slave_addr)(I2C_HANDLE_T* h_i2c,
uint32_t slave_addr_0_3, uint32_t slave_mask_0_3);
// OTHER functions
uint32_t (*i2c_get_mem_size)(void) ; //ramsize_in_bytes memory needed by I2C drivers
I2C_HANDLE_T* (*i2c_setup)(uint32_t i2c_base_addr, uint32_t *start_of_ram ) ;
ErrorCode_t (*i2c_set_bitrate)(I2C_HANDLE_T* h_i2c, uint32_t P_clk_in_hz, uint32_t bitrate_in_bps) ;
uint32_t (*i2c_get_firmware_version)() ;
I2C_MODE_T (*i2c_get_status)(I2C_HANDLE_T* h_i2c ) ;
} I2CD_API_T ;
//The boot rom structure
typedef struct _ROM_API {
const uint32_t unused1[5];
//const PWRD_API_T *pPWRD;
//const uint32_t p_dev1;
const I2CD_API_T *pI2CD;
//const uint32_t *pI2CD;
//const uint32_t p_dev3;
//const uint32_t p_dev4;
//const uint32_t p_dev5;
const uint32_t unused2[3];
//const UARTD_API_T *pUARTD; //UART API
const uint32_t *pUARTD;
} ROM_API_T;
#endif /* ROMI2C_H_ */
上記について補足説明を致します。
まず、ROM_API_Tの構造体定義ですが、一番最後に持っていきました。これはテキストと記載順序が異なりデータシート通りとなります。
これはどうもI2CD_API_Tが定義されていないためにコンパイル時にエラーとなることを防ぐためです。
他同様に、コンパイル時にUnknown~とエラーが出るので、エラーが出ないようにいじっていったら上記のようになりました。
繰り返しになりますが、上記に記載したヘッダファイルの内容はすべてマニュアルからコピーしたものになります。
*
* Copyright 2022 NXP
* NXP confidential.
* This software is owned or controlled by NXP and may only be used strictly
* in accordance with the applicable license terms. By expressly accepting
* such terms or by downloading, installing, activating and/or otherwise using
* the software, you are agreeing that you have read, and that you agree to
* comply with and are bound by, such license terms. If you do not agree to
* be bound by the applicable license terms, then you may not retain, install,
* activate or otherwise use the software.
*/
#ifdef __USE_CMSIS
#include "LPC8xx.h"
#endif
#include <cr_section_macros.h>
#include "RomI2C.h"
// TODO: insert other include files here
//#include "stdint.h"
void SwitchMatrix_Init();
void I2CInt();
void GPIO0_1Init();
I2C_HANDLE_T *hI2C;
volatile uint8_t iHandle[96];
// TODO: insert other definitions and declarations here
#define I2Cclk 10000
I2C_PARAM i_param={0,0,(uint8_t *)0,(uint8_t *)0,(void *)0,1,{0,0,0}};
I2C_RESULT i_result={0,0};
int waiting=0;
void SysTick_Handler(){
waiting=0;
}
void wait_ms(uint32_t ms){
waiting=1;
//LPC_GPIO_PORT->NOT0=(1<<1);
SysTick_Config(SystemCoreClock/1000*ms);
while(waiting);
//LPC_GPIO_PORT->NOT0=(1<<1);
}
#define S1A (0x3E) //slave address LCD
#define S1W (S1A<<1)
//LCDの初期化
static uint8_t initSeq[11]=
{10,S1W,0x38,0x39,0x14,0x7F,0x56,0x6A,0x38,0x0C,0x01};
//1¥送信する文字列の定義
static uint8_t string1[4][10]=
{
{3,S1W,0x06,0x80},
{8,S1W,0x40,0xCA,0xDB,0xB0,0x20,0x20,0x20},
{3,S1W,0x06,0xC0},
{8,S1W,0x40,'W','o','r','l','d','.'}
};
static uint8_t string2[4][10] = {
{ 3, S1W, 0x06 , 0xC0 },
{ 8, S1W, 0x40, 'H', 'e' ,'l', 'l', 'o','.'},
{ 3, S1W, 0x06, 0x80 },
{ 8, S1W, 0x40, 'W' , 'o', 'r', 'l', 'd' , '.'}
};
int main(void) {
//SystemCoreClockUpdate();
SwitchMatrix_Init();
GPIO0_1Init();
//LPC_GPIO_PORT->NOT0=(1<<1);
I2CInt();
// uint32_t memsize;
// memsize=LPC_I2CD_API->i2c_get_mem_size();
uint32_t memsize;
memsize=LPC_I2CD_API->i2c_get_mem_size();
register int m;
while(1){
for(m=0; m<4; m++){
i_param.num_bytes_send=string1[m][0];
i_param.buffer_ptr_send=(uint8_t *)&string1[m][1];
LPC_I2CD_API->i2c_master_transmit_poll((I2C_HANDLE_T*)hI2C,&i_param,&i_result);
}
wait_ms(500);
for( m = 0; m < 4; m++ ) {
i_param.num_bytes_send = string2[m][0];
i_param.buffer_ptr_send = (uint8_t *) &string2[m][1];
LPC_I2CD_API->i2c_master_transmit_poll(
(I2C_HANDLE_T*) hI2C, &i_param, &i_result);
}
wait_ms(500);
LPC_GPIO_PORT->NOT0=(1<<1);
}
return 0;
}
void I2CInt(){
hI2C= LPC_I2CD_API->i2c_setup(LPC_I2C_BASE,(uint32_t *)&iHandle[0]);
LPC_I2CD_API->i2c_set_bitrate(hI2C,SystemCoreClock,I2Cclk);
wait_ms(500);
i_param.num_bytes_send=initSeq[0]; //10
i_param.buffer_ptr_send=(uint8_t *)&initSeq[1]; //S1W
LPC_I2CD_API->i2c_master_transmit_poll((I2C_HANDLE_T*)hI2C,&i_param,&i_result);
wait_ms(1);
}
void SwitchMatrix_Init() //2番ピンをSCL 8番ピンをSDAとして設定
{
SystemCoreClockUpdate();
/* Enable SWM clock */
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 18)|(1<<7) |(1 << 5);
//LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);
/* Pin Assign 8 bit Configuration */
/* I2C0_SDA */
LPC_SWM->PINASSIGN7 = 0x00ffffffUL; //PIO0_0 I2C SDA
/* I2C0_SCL */
LPC_SWM->PINASSIGN8 = 0xffffff04UL; //PIO_0_4 I2C SCL
//LPC_SWM->PINASSIGN0 = 0xffffffffUL; //RESET時、すべてFF
/* Pin Assign 1 bit Configuration */
/* SWCLK */
/* SWDIO */
/* RESET */
LPC_SWM->PINENABLE0 = 0xffffffb3UL;
// LPC_IOCON->PIO0_4=0x00000410UL;
// LPC_IOCON->PIO0_8=0x00000410UL;
}
void GPIO0_1Init(){
LPC_GPIO_PORT->DIR0 |=(1<<1); //PIO0_1 OUTPUT
}
上記について補足します。
volatile uint8_t iHandle[96];
の部分ですが、これはデバッガを使用しないとわかりませんでした。
一度MCU Xpresso IDEの上部メニューから「GUI Flash Tool」というコマンドがある(ICの絵のようなアイコン)これを使用して、デバッガ(LPC-Link2を使用。)プログラムを書き込み、プログラム中の変数(main関数内のmemsize。試しに返り値を見るために宣言)にマウスカーソルを合わせると、上に示すようにValueが0x60と表示されました。
これは10進数に変換して考えると96です。テキストで示されていた値と合致いたしました。
あとはまったのが、void SwitchMatrix_Init()関数内のSYSAHBCLKCTRLです。これは後々考えれば当たり前だったのですが、結論から言うと、テキストに示されている通りに設定しないと、LCDには何も表示されませんでした。なぜこれにはまったかというと、使用しているSwitch Matrix Toolにて設定すると、
となります。LPC_SYSCON->SYSAHBCLKCTRL |= (1«7)と設定されます。これを信じ切っていました。ところが、データシートで見ると、ビット5(Enable for I2C)と18(Enable for IOCON)を設定しないとやはり動きません。なぜSwitch Matrix Toolではこのような設定を吐き出すのかがわかりませんが。