I2Cを動かしてOLEDを表示させることができましたので、制御方法などについてを纏めます。
なお、マスター制御のライトのみですので、リードとスレーブ制御については何も情報ありません・・
(リードはそのうち・・)
I2C_CR1のPE(Bit[0])を1にする前に
I2C_CR2 I2C_CCR I2C_TRISE
からSCLの周波数を設定します。
I2C_CR2.FREQ[5:0]にはAPB1のクロック周波数を設定し、I2C_CCR.CCR[11:0]と乗算した値がSCL周波数となります。
計算式はリファレンスマニュアルのCCRに記載されています。
こちらのクロック構成よりSPLを使った環境では、APB1クロックは45MHzなので、各モードの設定値は下記のようになります。
Sm mode(100KHz)
SCL = CCR × FREQ × 2(Duty 1:1)
CCR = 100,000nsec(100KHz) / 2 / 22.222nsec(45MHz) ≠ 226
I2C1->CR2 = 45; I2C1->CCR = 226;
Fm mode(400KHz) / DUTY bit=0
CCR × FREQ × 3(Duty 1:2)
CCR = 2,500nsec(400KHz) / 3 / 22.222nsec(45MHz) ≠ 38
I2C1->CR2 = 45; I2C1->CCR = ((1 << 15) | (0 << 14) | 38);
Fm mode(400KHz) / DUTY bit=1
CCR × FREQ × 25(Duty 9:16)
CCR = 2,500nsec(400KHz) / 25 / 22.222nsec(45MHz) ≠ 5
I2C1->CR2 = 45; I2C1->CCR = ((1 << 15) | (1 << 14) | 5);
また、I2C_TRISEはSCLのフィードバックループの最大期間を指定するそうです。
全く理解できていませんが(笑)リファレンスマニュアルによればSm modeでは、
1,000nsec / APB1クロック周波数 + 1
を設定すれば良いようです。
1,000nsec / 22.222nsec(45MHz) + 1 = 46
となりました。
Fm modeでは、最大期間を300nsecで計算すれば良いと海外のフォーラムに書いてありましたので、
300nsec / 22.222nsec(45MHz) + 1 = 14
に設定しました。
PIC + OLEDで使った馬の画像を表示するプログラムを組んでみました。
#include "stm32f4xx_conf.h" extern unsigned char oled_data[8][1024]; #define FM_MODE int main(void){ // Clock Enable RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; // PB8 - I2C1_SCL // PB9 - I2C1_SDA GPIOB->MODER |= ((0x2 << 18) | (0x2 << 16)); // Alternate function mode GPIOB->OTYPER |= ((0x1 << 9) | (0x1 << 8)); // Output open-drain GPIOB->PUPDR |= ((0x1 << 18) | (0x1 << 16)); // Pull-up GPIOB->AFR[1] |= ((0x4 << 4) | (0x4 << 0)); // AF4 // I2C1 Reset I2C1->CR1 = (0x1 << 15); for(int i=0; i<1000; i++){ asm("NOP"); }; I2C1->CR1 = (0x0 << 15); #ifdef FM_MODE I2C1->CR2 = 45; I2C1->CCR = ((1 << 15) | (0 << 14) | 38); I2C1->TRISE = 14; #else I2C1->CR2 = 45; I2C1->CCR = ((0 << 15) | (0 << 14) | 226); I2C1->TRISE = 46; #endif I2C1->CR1 |= 1; I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_SendData(I2C1, 0x78); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // アドレス 0x3c << 1 I2C_SendData(I2C1, 0x00); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // コマンドライト I2C_SendData(I2C1, 0xa0); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 表示モード設定 左右 - 通常:0xa0 / 逆転:0xa1 I2C_SendData(I2C1, 0xc0); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // 表示モード設定 上下 - 通常:0xc0 / 逆転:0xc8 I2C_SendData(I2C1, 0x20); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // アドレッシングモード指定 I2C_SendData(I2C1, 0x00); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // → Horizontalモード I2C_SendData(I2C1, 0x21); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // カラム指定 I2C_SendData(I2C1, 0x00); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // → 開始位置(0) I2C_SendData(I2C1, 0x7f); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // → 終了位置(127) I2C_SendData(I2C1, 0x22); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // ページ指定 I2C_SendData(I2C1, 0x00); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // → 開始ページ(0) I2C_SendData(I2C1, 0x07); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // → 終了ページ(7) I2C_SendData(I2C1, 0x8d); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // チャージポンプ設定 I2C_SendData(I2C1, 0x14); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); // → On I2C_SendData(I2C1, 0xaf); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 画面表示 On I2C_GenerateSTOP(I2C1, ENABLE); for(int h=0; h<100; h++){ for(int j=0; j<8; j++){ I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_SendData(I2C1, 0x78); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // アドレス 0x3c << 1 I2C_SendData(I2C1, 0x40); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // ライトデータ for(int i=0; i<1023; i++){ I2C_SendData(I2C1, oled_data[j][i]); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)); } I2C_SendData(I2C1, oled_data[j][1023]); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); } } return(0); }
SPLに用意されている関数を使えば簡単にI2C通信を行うことができました。
送信に使っている関数は
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState); void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data); void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
の3つです。
スタート、アドレス、データの送信完了の確認として
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
をコールしています。
引数に指定しているは
I2C_EVENT_MASTER_MODE_SELECT I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED I2C_EVENT_MASTER_BYTE_TRANSMITTING I2C_EVENT_MASTER_BYTE_TRANSMITTED
の4つでI2C_SR2とI2C_SR1を組み合わせた32bit値で
StdPeriph_Driver\inc\stm32f4xx_i2c.h
で定義されています。
stm32f4xx_i2c.hのコメントを見ると、それぞれEV5、EV6、EV8、EV8_2と書かれており
これはリファレンスマニュアルの下記のタイミング図に対応しています。
100KHzと400KHzでそれぞれ動画に撮ってみました。
400KHzは驚くほど速かったです。
[参考記事]