製作モジュール一覧

ラベル I2C の投稿を表示しています。 すべての投稿を表示
ラベル I2C の投稿を表示しています。 すべての投稿を表示

2019年4月8日月曜日

STM32: I2C接続のLCD:AQM1602を使う(Nucleo-F446RE)

秋月のI2C接続のキャラクタLCDのAQM1602を動作させました。

I2Cについては以下の記事を参考にしました。


プロジェクト
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_I2C_AQM1602_Test1

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

配線

SCLとSDAは4.7kΩでPullUpしています。

STM32Cubeの設定


Connectivity
  I2C1
    Mode: I2C
  Prameter Settings
    Master Features
      I2C Speed Mode: Standard Mode
      I2C Clock Speed(Hz): 100000
   
Pinout ViewでPinを割り当て
  PB8: I2C1_SCL
  PB9: I2C1_SDA

SW4STM32でコードを追加(一部抜粋)


/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define AQM1602_ADDRESS (0x3e << 1)
/* USER CODE END PD */

AQM1602のI2Cアドレスを指定します。Arduinoは7bit右詰めですが、STM32Cubeは7bitで左詰めなので左シフトしています。

参考「I2C addressing on STM32F4-Discovery

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

// I2C LCD : Akizuki AQM1602

void lcd_cmd(uint8_t x)
{
  uint8_t aTxBuffer[2] = { 0x00, x };
  while (HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)AQM1602_ADDRESS, (uint8_t*)aTxBuffer, 2, 1000) != HAL_OK) {
    if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
    {
      Error_Handler();
    }
  }
}

void lcd_data(uint8_t x)
{
  uint8_t aTxBuffer[2] = { 0x40, x };
  while (HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)AQM1602_ADDRESS, (uint8_t*)aTxBuffer, 2, 1000) != HAL_OK) {
    if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
    {
      Error_Handler();
    }
  }
}

void lcd_puts(const char *s)
{
  while(*s) lcd_data(*s++);
}

// param:
//  contrast: 0 ~ 63 (最初は大きくして調整)
void lcd_init(uint8_t contrast)
{
  // LCD initialize
  lcd_cmd(0x38); // function set
  lcd_cmd(0x39); // function set
  lcd_cmd(0x14); // interval osc
  lcd_cmd(0x70 | (contrast & 15)); // contrast low
  lcd_cmd(0x5c | (contrast >> 4 & 3)); // contrast high / icon / power
  lcd_cmd(0x6c); // follower control
  HAL_Delay(300);
  lcd_cmd(0x38); // function set
  lcd_cmd(0x0c); // display on
  lcd_cmd(0x01); // clear display
  HAL_Delay(2);
}

void lcd_move(uint8_t pos){
  lcd_cmd(0x80 | pos);
}

void lcd_pos(uint8_t raw, uint8_t col) {
  lcd_cmd(0x80 | ((raw & 0x01) << 6) | col);
}

void lcd_clear() {
  lcd_cmd(0x01);
}

/* USER CODE END 0 */

AQM1602の制御関数です。

  /* USER CODE BEGIN 2 */
  lcd_init(32);
  lcd_puts("Hello STM32");
  lcd_pos(1, 0);
  lcd_puts("AQM1602");
  /* USER CODE END 2 */

AQM1602を初期化。


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    char strBuffer[17] = {0};
    sprintf(strBuffer, "CNT=%05d", cnt);
    lcd_pos(1, 0);
    lcd_puts(strBuffer);
    cnt++;
    HAL_Delay(100);
  }
  /* USER CODE END 3 */

メインループではインクリメントした数値の表示とLチカを行っています。

I2C通信のようす

ch1:SDA ch2:SCL

2018年11月12日月曜日

Nucleo(mbed)からI2Cで通信してArduinoでOLEDに表示させてみる(その2)

配線は前回と同じです。主にファームウェアを検討しました。


Nucleo DCOはNucleoF767ZIをコアとして開発していますが、(貧乏で何枚もF767ZIを買う余裕がないため)テストはNucleo F446REで行いました。

IC間通信 データーフォーマット

https://github.com/ryood/Nucleo_DCO/blob/master/Nucleo-Arduino_I2C_Test/Arduino/Nucleo_DCO_I2C_Slave_OLED_print_FullBuffer_Test/DataComFormat.h

I2Cではバイト単位の送受信を行うので、送受信するデータをまとめるために構造体を定義しました。
#pragma pack(1)
とすると構造体のアラインメントが1Byte単位になります。

I2Cマスター Nucleo F446RE(mbed)


https://github.com/ryood/Nucleo_DCO/tree/master/Nucleo-Arduino_I2C_Test/mbed/I2C_Master_Arduino_OLED_Test01


マスター側のNucleo(mbed)のプログラムは、ダミーデータを送信するものです。

ボードに付いている「User Button」を押すと、「display mode」が切り替わり、スレーブに送るデータが切り替わります。

I2C通信の仕方は、1Byte目に1Byteの「display mode」を送信し、それ以降のデータのフォーマットを識別できるようにしました。

困った点。


「void displayTitle()」は「プログラム名」、「コンパイル日付」、「コンパイル時刻」の3つの固定長文字列(32byte)を送信します。ここは、I2Cの流儀に従って、

[I2C Address][display mode(DM_TITLE)][プログラム名(32Byte)][コンパイル日付(32Byte)][コンパイル時刻(32Byte)][STOP]
と、まとめて送信したかったのですが、スレーブのArduinoから冒頭の「I2C Address」の時点でACKが返ってきません。

[I2C Address][display mode(DM_TITLE)][プログラム名(32Byte)][コンパイル日付(32Byte)][STOP]

と、文字列2個なら正常に通信できました。原因はわかりません。

しかたがないので、

[I2C Address][display mode(DM_TITLE_STR1)][プログラム名(32Byte)][STOP]
[I2C Address][display mode(DM_TITLE_STR2)][コンパイル日付(32Byte)][STOP]
[I2C Address][display mode(DM_TITLE_STR3)][コンパイル時刻(32Byte)][STOP]
wait_ms(1);
[I2C Address][display mode(DM_TITLE)][STOP]

と分離して送信するようにしました。また、途中に「wait_ms(1)」を入れないとかえって通信速度が遅くなります。Arduino側のI2Cのバッファサイズかなにかが影響しているのかもしれません。

I2Cスレーブ Arduino Pro mini 8MHz/3.3V 中華製

https://github.com/ryood/Nucleo_DCO/tree/master/Nucleo-Arduino_I2C_Test/Arduino/Nucleo_DCO_I2C_Slave_OLED_print_FullBuffer_Test

最終的にu8g2のフルバッファで表示できました。u8g2のページバッファは消費RAM量が少なくて済むのですが、OLEDの表示がダラダラした感じになるので見栄えのためにはフルバッファがいいと思います。

u8g2のフルバッファを使うとRAMを1024バイト消費するようです。ATMega328pのArduinoボードは2048バイトしか使えるRAMがなく、デバッグ用に使っているSerialでPCにUART通信する文字列の容量が大きく、「UART_TRACE」を有効にするとメモリーオーバーしてコンパイルできません。

Arduino IDEのメッセージは以下の通りです。

#define UART_TRACE (0)
最大30720バイトのフラッシュメモリのうち、スケッチが17392バイト(56%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が1866バイト(91%)を使っていて、ローカル変数で182バイト使うことができます。
スケッチが使用できるメモリが少なくなっています。動作が不安定になる可能性があります。
#define UART_TRACE (1)
最大30720バイトのフラッシュメモリのうち、スケッチが21210バイト(69%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が2689バイト(131%)を使っていて、ローカル変数で-641バイト使うことができます。
スケッチが使用するメモリが足りません。メモリを節約する方法については、以下のURLのページを参照してください。http://www.arduino.cc/en/Guide/Troubleshooting#size
ボードArduino Pro or Pro Miniに対するコンパイル時にエラーが発生しました。
スケッチで、
Serial.print("xxxx");
としているところを
Serial.print(F("xxxx"));
と「F()」マクロを使えば「"xxxx"」の文字列がRAMではなく、Flash Memoryに置かれてRAMの消費量が節約できるそうですが、試していません。

困った点というか助かった点。


Arduinoの「sprintf()」は浮動小数点が使えません。u8g2には「print()」メンバ関数が用意されていて、これを使えば「Serial.print()」と同じ様に
u8g2.print(<浮動小数点数>, <小数点以下の桁数>)
として浮動小数点数が表示できました。

タイミングの測定


ch1:Arduino(D2) ch2:I2C_SCL

ch1はArduinoのスケッチで、I2C割り込みがかかったとき(receiveEvent()が呼ばれる)、処理の最初と最後でH/Lしています。

これを見ると、I2C通信が終わったあとにI2C割り込みがかかっているようです。

やはりI2Cは難解です(T_T;

2018年11月9日金曜日

Nucleo(mbed)からI2Cで通信してArduinoでOLEDに表示させてみる。

mbedのSSD1306のライブラリはいくつかありますが、フォントが貧弱で、きれいな文字を表示しようと思うとやはりUoogやu8g2を使いたくなります。

U8g2はArduinoでは使いやすいですが、mbedでは少々やっかいです。

そこで、U8g2を使うためだけにArduinoを一個用意するという贅沢なことを思いつきました。

Nucleo本体は波形生成に専念し、パラメーターに変更があった場合にOLED表示用のAruduinoにI2C経由でデーターを投げるというイメージです。

I2Cは仕様が複雑でほんとは使いたくないんですが、AVRにもSTM32にも専用のハードウェアが用意されているのでパフォーマンスを考えると使わざるを得ないと思います。なにせ信号線2本で双方向通信できますから。

SSD1306 OLEDも、やりくりすればI2Cタイプでもかまわないのですが(Nucleo - Arduino間の通信と信号線を共用)、速度と手数を減らすためにSPIタイプのものを使いました。

配線図

I2Cマスター側 Nucleo(mbed)のプログラムは前回と同じです。

https://os.mbed.com/users/ryood/code/Nucleo_i2c_master_writer/

I2Cスレーブ側 Arduino Pro mini (3.3V/8MHz駆動)


I2C経由で、マスター側が生成したデータをスレーブ側のOLEDで表示することができました。

I2Cクロック=400kHzの場合

ch1:Nucleo(D2) ch2:SCL

ch1はNucleo(マスター)側でI2C通信の開始時、終了時でH/Lしています。

I2C通信に要した時間: 220u秒~230u秒程度。

I2Cクロック=100kHzの場合

ch1:Nucleo(D2) ch2:SCL

I2C通信に要した時間: 675u秒

I2Cクロックは、Nucleo(マスター)側で変更すれば、Arduino(スレーブ)側は400kHzの設定のままで大丈夫でした。

2018年11月4日日曜日

ArduinoとNucleo(mbed)でI2C通信してみる。

Nucleo(mbed)をマスター、ArduinoをスレーブとしてI2C通信してみました。

配線図


スレーブ側は中華製のArduino Pro Mini(3.3V/8MHz版)でPCとUART通信するために、秋月の「FT232RL USBシリアル変換モジュール」をつないでいます

Nucleo(マスター側)
https://os.mbed.com/users/ryood/code/Nucleo_i2c_master_writer/

Arduino(スレーブ側)はArduino-ArduinoのI2C通信の場合と同じです。
https://github.com/ryood/Arduino_I2C/tree/master/Arduino/Wire_Slave_Resiver_NoPullup

Arduinoのスケッチでは、

  pinMode(A4, INPUT);           // disable pullup
  pinMode(A5, INPUT);           // disable pullup

として、内部プルアップを無効化しています。配線図のように外付けの2.2kΩの抵抗でプルアップしています。

ArduinoのI2Cアドレスは7bitですが、mbedでは8bitです。mbedではアドレスを1bit左シフトします。

mbedの「I2C::write()」の引数の最後に「bool repeated=false」があります。このrpeatedをtureにすると、I2C通信で「STOPコンディション」が送信されません。

repeated=trueの場合

ch1:SDA ch2:SCL

repeated=falseの場合

ch1:SDA ch2:SCL

「repeated=false」の場合は最後の1バイトを送出する前に「STOPコンディション」が入っています。

「repeated=false」の場合は受信側のArduinoのスケッチでは正常に受信できず、コンソールには「x is32」と表示され続けました。

「repeated=true」の場合は前回のArduino同士のI2C通信のように、8bitカウンタがインクリメントされる様子が表示されます。

「STOPコンディション」は、SCLがHの間に、SDAがL→Hに変化するものです。


ch1:SDA ch2:SCL

2018年11月1日木曜日

Arduino同士でI2C通信をしてみる。

ArduinoのI2Cはあまり使ったことがなかったので、スケッチ例を試してみました。

Master Writer/Slave Receiver (https://www.arduino.cc/en/Tutorial/MasterWriter)

2つのArduinoの一方をI2C Master、もう一方をI2C Slaveとして互いに通信するものです。

回路図

この回路図を見るまで知らなかったんですが、プルアップ抵抗が入っていません。

I2Cはオープンドレインでプルアップ抵抗を入れないと普通は動作しません。

オープンドレインの概略図

左側の「Open Drain」がI2Cの(大雑把な)出力です。「OUT」端子はMOS-FET Q1のドレインそのままで、これでは信号を取り出せません。

右側の「Open Drain Pullup」は、MOS-FET Q2のドレインをR1でVddにプルアップしてOUTから信号を取り出します。Q2のGateがHになったときQ2がONし、R1に電流が流れて電圧降下し(「OUT」のポイントがGNDに短絡される)、「OUT」はLレベルになります。Q2のGateがLのときはQ2がOffし、VddからR1を通してOUTに電流が流れ、「OUT」はHレベルになります。

R1の抵抗値が高すぎるとOUTにつながった容量成分に流れ込む電流が少なくなり、Hレベルになるまでに時間がかかり信号波形がなまることになります。

ArduinoのWire(I2C)の内部プルアップ


今まで知らなかったんですが、ArduinoのWireクラスはデフォルトで内部プルアップが行われているそうです。

参考「ArduinoでI2Cする際のプルアップ抵抗について

「<Arduinoのインストール先>\hardware\arduino\avr\libraries\Wire\src\utility\twi.c」の「void twi_init(void)」関数を見ると、

  // activate internal pullups for twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

とWireクラスの中で内部プルアップが行われています。

Arduinoではデフォルトで内部プルアップされているとすると、このBlogのI2Cタグの記事を見直してみても、I2Cのプルアップがらみでいろいろ不思議に思っていた現象に説明が付きます。

内部プルアップを無効にするには、Wire.begin()したあとに

  pinMode(A4, INPUT);  // disable pullup
  pinMode(A5, INPUT);  // disable pullup

とすればピンの状態がHi-Zになりオープンドレインになります。Master/Slave両方でArduinoを使う場合、内部プルアップを完全に無効にするためには、Master/Slave両方で行って無効化する必要があります。

波形の測定


最近よく使っている中華製のArduino Pro Mini 8MHz/3.3V版で実験してみました。

Arduinoのスケッチ
https://github.com/ryood/Arduino_I2C/tree/master/Arduino

ブレッドボード配線図

ブレッドボード配線図の、下の「Direct」は内部プルアップを使う場合の配線、上の「Pullup」は外付けのプルアップ抵抗を使う場合の配線です。

MasterのI2Cのピン(A4、A5)に(Slaveにつながず)直接オシロのプローブを当てて測定しました。

電源電圧:3.29V


ch1:A4(SDA) ch2:A5(SCL)

内部プルアップが効いているので信号が出力されています。スレーブに接続していないので、SDAの9ビット目がLに引っ張られていません。

前述のように内部プルアップを無効化すると


ch1:A4(SDA) ch2:A5(SCL)

信号が現れません。Slaveに接続せず、信号線をVDD(3.3V電源)に2.2kΩの抵抗でプルアップすると


ch1:A4(SDA) ch2:A5(SCL)

信号が出力されているのを測定できました。しかも、内部プルアップの場合より波形がきれい(立ち上がり、立ち下がりが速い)です。これは2.2kΩとプルアップ抵抗の抵抗値が低いためです。

スレーブに接続して、プルアップ抵抗の抵抗値を変えてみる。


プルアップ抵抗値を変えてI2Cの信号波形を測定しました。

プルアップ抵抗:2.2kΩ

ch1:A4(SDA) ch2:A5(SCL)

Slaveに接続したので、SDAの9ビット目がLに引っ張られて(ACK)、以降の通信が成功しています。

プルアップ抵抗:10kΩ

ch1:A4(SDA) ch2:A5(SCL)

プルアップ抵抗:47kΩ

ch1:A4(SDA) ch2:A5(SCL)

プルアップ抵抗:100kΩ

ch1:A4(SDA) ch2:A5(SCL)

プルアップ抵抗値を上げていくてと、立ち上がりが遅くなっています。このスケッチでは、ざっと見た感じ、いずれもプログラム通りSlaveのSerialから文字列が出力されていますが、100kΩの場合はSCL/SDAの電圧やタイミングがかなり怪しくなっています。信号路の状態によっては通信エラーが発生する可能性が高いと思います。

10kΩあたりまでが無難でしょうか。プルアップ抵抗値を下げるとLレベルになった場合、電流が多く流れるので(特にバッテリー駆動の場合)なんでもかんでも抵抗値を下げればいいというわけでもありません。

I2Cクロックを上げてみる。


ArduinoのデフォルトではI2Cクロックは100kHz(Standard Mode)ですが、ATMega328Pを8MHz駆動すると400kHz(Fast Mode)まで動作させられます。

スケッチで

  Wire.setClock(400000);

と指定すれば400kHzのFast Modeになります。Fast Modeの場合もプルアップ抵抗値を変えて信号波形を測定しました。

プルアップ抵抗:10kΩ

ch1:A4(SDA) ch2:A5(SCL)

プルアップ抵抗:4.7kΩ

ch1:A4(SDA) ch2:A5(SCL)

プルアップ抵抗:2.2kΩ

ch1:A4(SDA) ch2:A5(SCL)

I2Cクロックを上げると波形の崩れにさらに注意する必要がありそうです。

メモ:


ArduinoのWireクラスは難解なI2Cの仕様をうまくラッピングしていて、AVRを直に使う場合と比べてとても楽だと思います。

ただ、うまく動作している場合はいいんですが、そうでない場合は・・・

2018年8月11日土曜日

ESP32-DevKitCでWiFiとDeepSleep

ESP32-DevKitCでWiFiとDeep Sleepを試しました。

セットアップ


今回はArduino core for the ESP32(arduino-esp32)を使用しました。

以下のサイトを参考にしました。←だいぶ前に環境を作ったので詳細は覚えていません(^q^;

ESP-WROOM-32」の「セットアップ-ソフトウェア
Arduino core for the ESP32」の「Installation Instructions

Arduino IDEで「BLEがない云々」という警告が出るので、ESP-BLE-Arduinoを入手し、解凍、<Arduinoのインストール先>\hardware\espressif\esp32\libraries\BLEにコピーしました(Windows10)。

配線


ESP32-DevKitCも幅が広いのでブレッドボードを連結して使用しました。

動作確認用のLEDは1kΩのRを介してIO4に接続しています。

電源はUSBから取るとWiFi接続に失敗するなど動作が不安定なので5Vの安定化電源から取っています。(こちらもエネループを使っていて残量が少ないと動作が怪しくなります)

念の為100uFのOSコンでデカップリングしました。

ESP32-DevKitCの回路図(USB/電源周り)

USBからの5V電源「VBUS」と外部5V電源「EXT_5V」の間にはショットキーバリアダイオードのD3(1N5819)が入っていますが、ショットキーなので漏れ電流(やなんやかや)が心配です。EXT_5VとUSBのVBUSの同時使用を常用するのは避けたほうが無難だと思います。

スケッチ


int型のカウンターをインクリメントしてAmbientに送るスケッチをDeep Sleepあり/なしで書いて電源電流を比較しました。

Sleepなし <Ambient_ESP32_CountUp.ino>


#include "Ambient.h"

#define LED 4
#define PERIOD 10

const char* ssid = "WiFi APのssid";
const char* password = "WiFi APのpassword";

unsigned int channelId = <AmbientChannelId>;
const char* writeKey = "Ambientのpassword";

WiFiClient client;
Ambient ambient;

void setup()
{
  Serial.begin(115200);
  delay(20);

  Serial.println("Start");
  pinMode(LED, OUTPUT);

  WiFi.begin(ssid, password);

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);

    digitalWrite(LED, i++ % 2);
    Serial.print(".");
  }

  digitalWrite(LED, LOW);
  Serial.print("WiFi connected\r\n");
  Serial.print("IP address: ");
  Serial.print(WiFi.localIP());
  Serial.print("\r\n");

  ambient.begin(channelId, writeKey, &client);
}

int cnt = 0;

void loop()
{
  Serial.print("cnt: ");
  Serial.print(cnt);
  Serial.print("\r\n");

  ambient.set(1, cnt);

  digitalWrite(LED, HIGH);

  ambient.send();

  digitalWrite(LED, LOW);

  delay(PERIOD * 1000);

  cnt++;
}

DeepSleepあり <Ambient_ESP32_DeepSleep_CountUp.ino>


/*
 * Ambient ESP32 DeepSleep CountUp
 *
 * 2018.08.11
 *
 */
#include "Ambient.h"

#define TITLE_STR1  ("Ambient ESP32 DeepSleep CountUp")
#define TITLE_STR2  ("2018.08.11")


#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  10        /* Time ESP32 will go to sleep (in seconds) */

#define LED (4)

const char* ssid = "WiFi APのssid";
const char* password = "WiFi APのpassword";

unsigned int channelId = <AmbientchannelId>;
const char* writeKey = "AmbientのwriteKey";

WiFiClient client;
Ambient ambient;

RTC_DATA_ATTR int bootCount = 0;

void setup()
{
  Serial.begin(115200);
  delay(20);

  Serial.println();
  Serial.println();
  Serial.println(TITLE_STR1);
  Serial.println(TITLE_STR2);

  Serial.println("Start");
  pinMode(LED, OUTPUT);
  
  bootCount++;
  Serial.println("Boot number: " + String(bootCount));

  WiFi.begin(ssid, password);

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);

    digitalWrite(LED, i++ % 2);
    Serial.print(".");
  }

  Serial.print("WiFi connected\r\n");
  Serial.print("IP address: ");
  Serial.print(WiFi.localIP());
  Serial.print("\r\n");

  ambient.begin(channelId, writeKey, &client);
  
  ambient.set(1, bootCount);
  ambient.send();

  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");

  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
  delay(1000);
}

void loop()
{
}

電源電流


OWON B35でロギングしました。サンプリング周期1s

Sleepなし

Avg 13.98116667 mV
Min 12.81 mV
Max 20.06 mV

DeepSleep

Avg 5.052566667 mV
Min 0.34 mV
Max 18.35 mV

Sleepなしだと常時140mA程度です。DeepSleepをかけるとWakeUp時は130mA~180mA、Sleep時は3.4mA程度で推移しています。Sleep時の電流はUSB-UART変換チップやLEDが結構消費してるんじゃないかな~と思います。

1秒周期のサンプリングなので、細かく見るともっとトゲトゲしく電源電流が変化している可能性が高いと思います。

ESP8266と比べると消費電流が多く、WakeUp時の概算で70mA vs 140mAで2倍程度です。

メモ:


ESP32のSleepモード(Modem Sleep、Light Sleep)については、「ESP-IDE Programming Guide」の「Sleep Mode」と「Power Management」にありますが、ネイティブ環境の「ESP-IDF」用なのでそのうち試したいと思います。

ESP-IDFはI2Sも使えるようで(参考「じわじわ進む」さんの「ESP32でI2Sから音を出す」)、ESP-IDFを少し触った感じではNucleoのネイティブ環境の「STM32Cube/CubeMX」よりとっつきやすそうです。

WiFi APのssidやpasswordを生書きしているのでミスってBlogにあげてしまわないかヒヤヒヤです。ソース管理もつらい(@@;

2016年11月25日金曜日

I2C EEPROM(AT24C1024B)をArduino UnoとNucleo F401RE(mbed)で使う。

1MbitのI2C EEPROMのAT24C1024Bの手持ちがあったのでテストしてみた。

仕様としては、I2C接続で電源電圧は2.5V~5.5VなのでArduinoでもNucleoでも気軽に使える。

書き込みはバイト単位、ページ単位、読み込みはランダムリードとシーケンシャルリードができる。

メモリの構成は、512Page × 256Byteになっていてアドレス指定は17bitで行う。

バイト単位の書き込み


1byte目 デバイス・アドレス ( 0x1010 | A2 | A1 | P0 | W )
2byte目 上位アドレス
3byte目 下位アドレス
4byte目 書き込みデータ

となっていて、1byte目のデバイスアドレスは、0x1010は固定、A2/A1はピンの接続と合わせて指定する(1個だけ使う場合はGNDに落として0を指定すると楽)。アドレス17bitのうち最上位1bitをP0で指定する。

バイト単位の読み込み


読み込みの場合は書き込みの1~3byte目と同じものをWモードで送信してアドレスを指定。4byte目のデータは送らずに、再びSTARTコンディションにしてRモードでデバイスアドレスを送信するとデータが返ってくる仕様。

Arduino


配線図


デバイスアドレスのA1/A2はGNDに落とし、I2CのSCL、SDAは1kΩでプルアップした。

ライブラリはArduinoのPlaygroundの「Arduino AT24C1024 I2C EEPROM Library」を使わせてもらった。(バイト単位の読み書きのみに対応)

http://playground.arduino.cc/Code/I2CEEPROM24C1024
https://github.com/jwhiddon/AT24C1024

ExampleのAT24C1024_EEPROM_Benchmarkをそのまま動作させることができた。全アドレスを読み書きするのでシリアル・コンソールにしばらく何も表示されないが、少し待てば「.」が表示されていく。

シリアル出力結果

AT24C1024 EEPROM Library Benchmark Sketch
--------------------------------
Write By Byte Test:
Writing data:...........................DONE
Total Time (seconds): 722
Write operations per second: 181
--------------------------------
--------------------------------
Read By Byte Test:
Reading data:..
Address: 9265 Should be: 25 Read val: 255
.........................DONE
Total Test Time (secs): 82
Read operations per second: 1598
Total errors: 1
--------------------------------

書き込みは181Byte/s、読み込みは1,598Byte/sで、1バイト読み書きに失敗したようだ。

I2C通信のようす

書き込み

ch1:SDA ch2:SCL

読み込み

Nucleo F401RE(mbed)

配線図


こちらも1kΩでプルアップ。

ライブラリはKenji Araiさんの「AT24C1024」ライブラリ(https://developer.mbed.org/users/kenjiArai/code/AT24C1024/)を使わせてもらった。こちらはページ単位の読み書きにも対応している。

テスト・プログラム main.cpp

#include "mbed.h"
#include "AT24C1024.h"

I2C         i2c(D14, D15);      // SDA, SCL
AT24C1024   at24c1024(i2c);     // Atmel 1Mbit EE-PROM

DigitalOut led1(LED1);

int main()
{
    printf("\r\n*** AT24C1024 Test ***\r\n");

    // Byte Read/Write
    // 
    uint8_t dt = 0x55;
    printf("Byte Write: %02x\r\n", dt);
    at24c1024.write(0, dt);     // write addr=0 data=dt
    wait_ms(5);
    dt = at24c1024.read(0);     // read addr=0
    printf("Byte Read: %02x\r\n", dt);

    // Page Read/Write
    // 
    uint8_t eep_buf[258];
    for (int i = 0; i < 256; i++) {
        eep_buf[i] = i;
    }
    printf("Page Write: 0..255\r\n");
    AT24C_STATUS status = at24c1024.write_page(0x1ff00, eep_buf, sizeof(eep_buf));
    wait_ms(5);
    printf("Status: %d\r\n", status);
    printf("Page Read:\r\n");
    status = at24c1024.read_page(0x1ff00, eep_buf, sizeof(eep_buf));
    printf("Status: %d\r\n", status);
    for (int i = 0; i < sizeof(eep_buf); i++) {
        printf("%d ", eep_buf[i]);
    }
    printf("\r\n");

    while(1) {
        led1 = !led1;
        wait(0.2);
    }
}

AT24C1024のWriteはDATASHEETを見ると5msかかるようなのでwait_ms(5)を入れている。waitをかけないと読み込みに失敗する。また、ページ単位の読み書きのバッファ、はメンバ関数内部でI2C通信用に2バイト使うので258バイト確保する必要がある。データはインデックス0から埋める。

<追記:2016.11.25>

write_page()とread_page()はエラーステータス(AT24C_STATUS)を返しますが、I2C通信がうまくいったかどうかは見ない仕様のようです。AT24C_OK = 0が返ってきても通信が失敗している場合もあります。

</追記>

I2C通信のようす


ch1:SDA ch2:SCL

メモ:


SDカードに比べるとR/Wの速度は遅いが、実装面積が節約できるし値段も安い。1Mbitの品種を使っているが、ベースマシンのデータ保存用ならそんなには必要ない。

2016年8月21日日曜日

ATTiny85のUSIでI2C通信を試してみる。

ATTiny85はI2Cのハードウェアは実装されていないが、USI(Universal Serial Interface)が備わっているので半分ハードウェア制御でI2Cも使えるようだ。(AVRではTWIと称している)

Tiny85への書き込み


配線図


AVRライタには、SparkfunのAVR Pocket Programmerというのを使っている。Tiny85を使うのははじめてなのでとりあえずLチカしてみた。

Fuse Bit
hHuse DF
lFuse 62
eFuse FF
LED_Blink.cpp
#define F_CPU   10000000L

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB = 0x08;
    
    while(1)
    { 
        PORTB ^= 0x08;
        _delay_ms(100);
    }
}

I2C出力




I2Cはオープンドレインなので、1kΩのRでVCC(+5V)にPullUpしている。

ライブラリは「https://github.com/adafruit/TinyWireM」を使わせてもらった。Aruduino用のライブラリだが、

USI_TWI_Master.cpp
USI_TWI_Master.h

は素のAVR用なのでこれを使ってみた。

#define F_CPU   8000000UL

#include <avr/io.h>
#include "USI_TWI_Master.h"

const unsigned char I2C_Address = 0xAA; // 0b10101010
const unsigned char I2C_Write   = 0x01;

unsigned char twiMsg[] = { I2C_Address | I2C_Write, 0x55 };

int main(void)
{
    USI_TWI_Master_Initialise();
    
    while(1)
    {
        USI_TWI_Start_Read_Write(twiMsg, 2);
        USI_TWI_Master_Stop();
    }
}

スレーブのアドレスは適当に0xAA(7bit)にして、最下位はI2CのWriteの1なのでSDAは0b10101011になる。

出力波形


I2C信号らしきものは出力されてるようだ。オシロのカーソルで測定するとI2CのSCLは約400KHzだった。

SCLの9bit目はスレーブがSDAをLに引っ張らないといけないのでマスター側としての最初の動作はうまく行っていると思う。(たぶん)

メモ:


ATTiny85のADCは差動入力+Gain20倍に対応しているようなので(http://dad8893.blogspot.jp/2016/07/blog-post_22.html)I2C LCDを制御できるようにしてみたい。

2016年5月7日土曜日

秋月のI2Cバス用双方向電圧レベル変換モジュール(PCA9306)を使ってみる。

PSoC 4 Pioneer KitとArduino UnoのI2S通信秋月のI2Cバス用双方向電圧レベル変換モジュール(PCA9306)を使って、ちゃんと5V系と3.3V系のレベルシフトをしてみた。

ブレッドボード図


変換前後のI2C信号


SCL

ch1:Arduino側 ch2:PSoC側

SDA

メモ:


LがGNDまでちゃんとおちてないかな?I2Cにぶら下がるデバイスが増えて通信がうまくいかない場合は使ってみるかも?

PSoC 4 Pioneer KitをスレーブにしてArduino UnoとI2C通信をしてみる

ブレッドボード図


PSoC 4 Pioneer KitのI2C SlaveのプログラムはExampleにある「SCB_EzI2cCommSlave」を使った。

これはI2Cのスレーブとして動作し、マスターからコマンドを受け取って、オンボードのRGB LEDの色を切り替えるというもの。

ExampleではPSoC 4 Pioneer Kit上のPSoC 5LP(ちょっとややこしいですが、PSoC 4 Pioneer KitではPCとの通信等はボード上のPSoC 5LPが受け持っている)がI2CマスターとなってUSB接続されたPCの「Bridge Control Panel」で・・・

説明するとめんどくさいのでドキュメントを見ながら実際にExampleをやってみるのが早いと思います(^q^;

PSoC 4 Pionner Kitの方は、ボード上のジャンパを3.3Vに設定してこのSlaveを走らせて、Arduino Unoは普通に5V駆動でマスタープログラムを書いて動作させた。

Arduinoのスケッチ

// Wire Master Writer
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Writes data to an I2C/TWI slave device
// Refer to the "Wire Slave Receiver" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>

#define SLAVE_ADDRESS  0x10

/* Start and end of packet markers */
#define PACKET_SOP      0x01
#define PACKET_EOP      0x17

/* Commands set */
#define CMD_SET_OFF     0
#define CMD_SET_RED     1
#define CMD_SET_GREEN   2
#define CMD_SET_BLUE    3

/* Base address */
#define BASE_ADDRESS_COMMAND  0x00
#define BASE_ADDRESS_STATUS   0x03

byte cmdOff[]   = { BASE_ADDRESS_COMMAND, PACKET_SOP, CMD_SET_OFF,   PACKET_EOP };
byte cmdRed[]   = { BASE_ADDRESS_COMMAND, PACKET_SOP, CMD_SET_RED,   PACKET_EOP };
byte cmdGreen[] = { BASE_ADDRESS_COMMAND, PACKET_SOP, CMD_SET_GREEN, PACKET_EOP };
byte cmdBlue[]  = { BASE_ADDRESS_COMMAND, PACKET_SOP, CMD_SET_BLUE,  PACKET_EOP };

void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{

  Wire.beginTransmission(SLAVE_ADDRESS);
  Wire.write(cmdRed, sizeof(cmdRed));
  Wire.endTransmission();
  delay(500);

  Wire.beginTransmission(SLAVE_ADDRESS);
  Wire.write(cmdGreen, sizeof(cmdGreen));
  Wire.endTransmission();
  delay(500);

  Wire.beginTransmission(SLAVE_ADDRESS);
  Wire.write(cmdBlue, sizeof(cmdBlue));
  Wire.endTransmission();
  delay(500);
}


ArduinoのExampleのWire/master_writerを改変してPSoCの「SCB_EzI2cCommSlave」のコマンドを送るようにした。

PSoCのプログラムは

  • master->slaveでLEDの色を切り替える
  • slave->masterでStatusを返す

ようになっているが、今回はslave->masterの返答の処理は端折った。

I2Cの通信状態


I2C通信にはプルアップが必要だが、プルアップしないでも動作した。


ch1:SDA ch2:SCL

波形はかなりなまっているが、通信はできている(SCLの9bit目でSDAがLになっている)し、LEDの表示色も切り替わる。

ExampleのDocumentを読むとPSoC 4 Pioneer Kit側でプルアップされてる可能性があるかも(?)

あとで調べるかも?

<追記:2016.05.10>

よく見ると無信号時にも4V付近まで電圧があがっている。↓のように3.3VでPull Upすると無信号時も3.3V~3.6Vあたりに落ち着く。

ということはPull Upというより、3.3Vに電圧を逃しているということになる?

どういう向きで電流が流れているかわかればはっきりするかな。

導通している部分に小さい値の抵抗を入れて電圧差を計れば電流はわかるか。

う~ん(^q^;;;

</追記>

3.3Vに1kΩでプルアップ

ブレッドボード図のようにPSoC側から供給した3.3Vに1kΩの抵抗でプルアップしてみると波形はかなり綺麗になる。

メモ:

前にも書いた気がしますが。

Arduino Unoは5V駆動で、ATMega328PのDATASHEETの「29.7 Two-wire Serial Interface Characteristics」を見ると、
VIH | Input High-voltage | 0.7 VCC
となっているので、仕様上ではHには最低、5V × 0.7 = 3.5V必要。

だが、1対1のI2C通信の場合は3.3VのPullUpでも殆どの場合動作する。

オープンドレインなので外部でPullUp必要があり、無信号時には信号線がプルアップする電圧になるため、高い方の電圧でプルアップすると電圧が低い方の素子を破壊する可能性がある。