製作モジュール一覧

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

2019年4月14日日曜日

STM32: u8g2を使おうという試み(未完)

Arduinombedで、中華製のSSD1306のOLEDとu8g2ライブラリを組み合わせてよく使っています。STM32Cubeでも使えないか試してみました。

参考
「Elastic Notes」さんの「u8g2 library usage with STM32 MCU
「olikraus」さんの「Use U8g2 with STM32 #356
「olikraus」さんの「Template for the GPIO and Delay callback

「Elastic Notes」さんの記事がよくできているので、ほとんどそのまま試しました。

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

実行環境

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

配線図


SSD1306 Arduino Header Pin Name Function Lavel
D0 D13 PA5 SPI1 SPI1_SCK
D1 D11 PA7 SPI1 SPI1_MOSI
CS D10 PB6 GPIO SPI1_CS
DC D9 PC7 GPIO OLED_DC
RES D8 PA9 GPIO OLED_RES

一応100uFのOSコンで電源をデカップリングしています。

STM32CubeMXの設定


Clock Configuration
  PLL Source Mux: HSE
  HCLK (MHz): 180

GPIO-GPIO

GPIO-SPI1

SPI1

SW4STM32でコードを追加


GPIOと遅延と、SPI通信の2つのCallback関数を追加

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t u8x8_stm32_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8,
    U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int,
    U8X8_UNUSED void *arg_ptr)
{
  switch (msg)
  {
  case U8X8_MSG_GPIO_AND_DELAY_INIT:
    HAL_Delay(1);
    break;
  case U8X8_MSG_DELAY_MILLI:
    HAL_Delay(arg_int);
    break;
  case U8X8_MSG_DELAY_10MICRO:
    for (uint16_t n = 0; n < 320; n++)
    {
      __NOP();
    }
  break;
  case U8X8_MSG_DELAY_100NANO:
    __NOP();
  break;
  case U8X8_MSG_GPIO_CS:
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, arg_int);
    break;
  case U8X8_MSG_GPIO_DC:
    HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int);
    break;
  case U8X8_MSG_GPIO_RESET:
    HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, arg_int);
    break;
  }
  return 1;
}

uint8_t u8x8_byte_4wire_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
    void *arg_ptr)
{
  switch (msg)
  {
  case U8X8_MSG_BYTE_SEND:
    HAL_SPI_Transmit(&hspi1, (uint8_t *) arg_ptr, arg_int, 10000);
    break;
  case U8X8_MSG_BYTE_INIT:
    break;
  case U8X8_MSG_BYTE_SET_DC:
    HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int);
    break;
  case U8X8_MSG_BYTE_START_TRANSFER:
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
    __NOP();
    break;
  case U8X8_MSG_BYTE_END_TRANSFER:
    __NOP();
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
    break;
  default:
    return 0;
  }
  return 1;
}
/* USER CODE END 0 */

「Elastic Notes」さんは、CSピンがないタイプのSSD1306 OLEDを使われていますが、私の手持ちのものはCSピンがあるタイプなので、Callback関数にCSの処理を追加しています。

u8g2ライブラリの準備


u8g2/csrcをプロジェクトのDriverディレクトリに配置、名前をu8g2に変更。

このパスをインクルードに追加。[Project]->[Properties]->[C/C++ Build]->[Setting]->[Includes]で
../Drivers/u8g2
を追加。

main.cファイルの先頭にu8g2.hを追加。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "u8g2.h"
/* USER CODE END Includes */

グローバル変数u8g2を配置。

/* USER CODE BEGIN PV */
static u8g2_t u8g2;
/* USER CODE END PV */

u8g2をセットアップ。

  /* USER CODE BEGIN 2 */

  u8g2_Setup_ssd1306_128x64_noname_1(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8x8_stm32_gpio_and_delay);
  u8g2_InitDisplay(&u8g2);
  u8g2_SetPowerSave(&u8g2, 0);
  /* USER CODE END 2 */

メインループで描画処理

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    u8g2_FirstPage(&u8g2);
    do
    {
      u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
      u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");
      u8g2_DrawCircle(&u8g2, 64, 40, 10, U8G2_DRAW_ALL);
    } while (u8g2_NextPage(&u8g2));
  }
  /* USER CODE END 3 */

Buildすると実行可能ファイルが大きすぎてエラーになるので、未使用のコードとデータを除去するオプションを設定。


実行結果


以上で冒頭の画像の通りOLEDに描画されるのですが、なぜだかSCKにオシロのプローブを当てていないと描画されません。SCKにプローブを当てていれば必ずOKというわけでもなく、たまに描画に失敗します。オシロのプローブもx1モードではだめで、x10モードのときにだけ描画されます。どういうこと?

オシロのプローブの仕様から、入力インピーダンス10MΩ・20pFぐらいでGNDにプルダウンすれば動作するんでしょうか?とりあえず1MΩ以上の抵抗は持ち合わせていないので検証できません。

それにしても、あまりにも厳しい条件です。

Arduinoでは似たような条件で特別なことをしなくても問題なく動作します。

参考「温度・湿度・気圧センサーのBME280をArduino Pro Miniで使う。

問題はおそらく違うところにあると思うので、しばらく放置して、気が向いたらまたテストしたいと思います。

メモ:


SPI1のSCKに割り当てているPA5(D13)はNucleo Board上でLEDに接続されている。ただし、Arduinoも同様で、Elastic Notesさんの検証でもSCKはPA5。

SPI通信波形


MOSI

ch1:MOSI(D1) ch2:SCK(D0)

RES

ch1:RES ch2:SCK(D0)

RESはReset時にLow。通常はHigh


DC

ch1:DC ch2:SCK(D0)

DCはWrite Command時にLow。Write Data時にHigh。

CS

ch1:CS ch2:SCK(D0)

CSはSlave選択時にLow。

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年10月7日日曜日

Nucleo DCO Thread対応

ダンボール箱に入れてテスト中

ADC読み取りをなめらかにするために(読み取りのサンプリングレートをあげる)、ADC読み取りと、OLED表示をそれぞれ別Threadにしました。

最初は「mbed OS 5のThread」でやったように

0 波形生成  Ticker
1 ADC読み取り Thread(priority:高)
2 メインループ Thread(priority:普通) デジタル入出力
3 OLED表示 Thread(priority:低)

としたのですが、実行時に「HardFault」というメッセージがUARTに出力されて、プログラムが停止しました。そこで、Thread化する対象を切り分けてようすをみました。

「ADC読み取り」、「OLED表示」ともにThread化

Nucleo_DCO_Test20 https://github.com/ryood/Nucleo_DCO/tree/master/mbed/Nucleo_DCO_Test20

エラー出力
728  DCO Test20  0  4  288.28 0.000 8007 0.424: 0  4  314.53 0.091 30961 0.101: 0  4  224.00 -0.442 37380 0.576: MasterAmplitude:0.413 DisplayMode:1 21.6 fps 12.08 V SuppressADC:0 
729  DCO Test20  0  4  287.10 0.000 7889 0.424: 0  4  313.23 0.091 31095 0.098: 0  4  227.34 -0.423 37212 0.574: MasterAmplitude:0.・ム DisplayMode:1 21.6 fps 12.07 V SuppressA
++ MbedOS Fault Handler ++

FaultType: HardFault

Context:
R0   : BDD13786
R1   : 00000000
R2   : 00000001
R3   : 20007B78
R4   : 00000000
R5   : 20000460
R6   : 00000002
R7   : 00000000
R8   : 20007C00
R9   : 00000017
R10  : 20000000
R11  : 3FF51367
R12  : FFFFFFFF
SP   : 20009980
LR   : 08011B73
PC   : 080122B8
xPSR : 81070000
PSP  : 20009918
MSP  : 2007FFD8
CPUID: 411FC270
HFSR : 40000000
MMFSR: 00000000
BFSR : 00000000
UFSR : 00000100
DFSR : 0000000B
AFSR : 00000000
Mode : Thread
Priv : Privileged
Stack: PSP

-- MbedOS Fault Handler --



++ MbedOS Error Info ++
Error Status: 0x80FF013D Code: 317 Module: 255
Error Message: Fault exception
Location: 0x8003577
Error Value: 0x80122B8
Current Thread: Id: 0x20005EBC Entry: 0x8005B85 StackSize: 0x1000 StackMem: 0x20008C48 SP: 0x2007FF70 
-- MbedOS Error Info --

UARTに実行時間(秒)を含めたログを出力していて、Fault時は「729」となっているので12分9秒でFaultです。

「FaultType: HardFault」とあり、「Error Status: 0x80FF013D Code: 317 Module: 255」ともあるので原因は「HardFault」のようです。

参考「Docs › Reference › Platform › Error handling

HardFaultは「APS」さんの「初心者講座 → Cortex-M編 → 例外のタイプ」によると、
優先順位の関係、または他のハンドラが無効にされていて、実行できないときのデフォルト(すべて)のフォールト
となっています。

STM32F767ZIのReference manual(RM0410)の「10.1.2  Interrupt and exception vectors」の「Table 46. STM32F76xxx and STM32F77xxx vector table」には
All class of fault
とあります。「なんかしらんけど例外が起こった。レジスタを見て調べてちょーだい」ということらしいです。

「OLED表示」のみThread化(ADC読み取りはメイン・ループ内で)

Nucleo_DCO_Test21 https://github.com/ryood/Nucleo_DCO/tree/master/mbed/Nucleo_DCO_Test21

エラー出力
<...>
2946  DCO Test21  3  4  473.76 0.000 7973 0.425: 4  3  232.66 -0.036 31011 0.464: 1  2  176.05 0.486 31835 0.161: MasterAmplitude:0.505 DisplayMode:1 31.9 fps 12.08 V SuppressADC:0 
2946  DCO Test21  3  4  474.95 0.000 7939 0.425: 4  3
++ MbedOS Fault Handler ++

FaultType: HardFault

Context:
R0   : 00363234
R1   : 00000000
R2   : FFFFFFFF
R3   : 00000005
R4   : 20000460
R5   : 00000001
R6   : 00000005
R7   : 00000000
R8   : 20007B40
R9   : 0000002B
R10  : 8F6D0AD0
R11  : 406D29AA
R12  : 08004955
SP   : 200072A8
LR   : 08011B73
PC   : 08011B7E
xPSR : 81070000
PSP  : 20007240
MSP  : 2007FFD8
CPUID: 411FC270
HFSR : 40000000
MMFSR: 00000000
BFSR : 00000004
UFSR : 00000000
DFSR : 0000000B
AFSR : 00000000
Mode : Thread
Priv : Privileged
Stack: PSP

-- MbedOS Fault Handler --



++ MbedOS Error Info ++
Error Status: 0x80FF013D Code: 317 Module: 255
Error Message: Fault exception
Location: 0x8003577
Error Value: 0x8011B7E
Current Thread: Id: 0x2000649C Entry: 0x80035CD StackSize: 0x1000 StackMem: 0x200064E8 SP: 0x2007FF70 
-- MbedOS Error Info --

OLEDのみThread化した場合も「HardFault」しました。こちらは「2946秒(約50分)」です。

「ADC読み取り」のみThread化(OLED表示はメイン・ループ内で)


Nucleo_DCO_Test21 https://github.com/ryood/Nucleo_DCO/tree/master/mbed/Nucleo_DCO_Test22

ログ
<...>
90650  DCO Test22  0  3  152.01 0.000 30356 0.201: 0  3  231.93 0.525 20543 0.234: 0  3  276.62 0.819 61141 0.125: MasterAmplitude:0.791 DisplayMode:1  2.0 fps 12.11 V SuppressADC:0 
90651  DCO Test22  0  3  151.54 0.000 30485 0.201: 0  3  230.71 0.523 20576 0.227: 0  3  276.08 0.816 61141 0.124: MasterAmplitude:0.790 DisplayMode:1  2.0 fps 12.42 V SuppressADC:0 
90651  DCO Test22  0  3  151.67 0.000 30457 0.202: 0  3  232.01 0.525 20543 0.225: 0  3  278.65 0.832 61309 0.124: MasterAmplitude:0.789 DisplayMode:1  2.0 fps 12.12 V SuppressADC:0 

「ADC読み取り」のみThread化した場合は、「90651秒(24時間以上)」動作させましたが、Faultは起こりませんでした。

メイン・ループの処理時間は
OLED表示中 2.0fps程度
OLED非表示 32fps程度
でした。ADC読み取りのThreadはメイン・ループより優先度が高く、OLED表示中でもADCの読み取りは高速に行われPOTを回すと出音はなめらかに変化します。代わりにプッシュスイッチやロータリーエンコーダーのレスポンスは悪くなってしまいました。

HardFaultが起こる原因はどうやら、u8g2-mbedライブラリをThreadで使ったときに起こるようです。元のu8g2ライブラリは規模が大きくチョイチョイとはいじれなく、mbedに対応させた部分(u8g2_mbed.cpp)もちょっとどうかなという感じなので原因追求はやめておきます。

通信速度や表示も遅いし、SPI版のSSD1306では動作不良だったAdafluit版をmbedに移植されたもの(参考「Nucleo DCO 表示器を考える(メモ)」)もいずれ試してみるつもりです。

他にやったこと


ADC読み取り抑止用フラグを以下のように宣言していましたが、
// Suppress ADC
bool isSuppressAdc = false;
ADC読み取りをThread化した場合に反映されなくなったので
// Suppress ADC
volatile bool isSuppressAdc = false;
とvolatile指定すると正常動作しました。「isSuppressAdc」は外部割り込み(InterruptIn)で値を変更しているので、volatile指定しないとThread内で読み取る時には最適化されて無視されてしまうようです。

RotaryEncoderライブラリをcallback()を使うように変更しました。

電源電圧を15Vまで測定できるように抵抗の分圧値を変更しました。精度は悪くなりますが使える電源電圧を融通するため。

2018年8月30日木曜日

Nucleo DCO OLED表示(u8g2)と波形出力のテスト

u8g2-mbedを使ってSSD1306 OLED(I2C)に表示しつつ、内蔵DACから波形出力しました。Nucleoボードは、F446REだとサンプリング・レート100kHzではだめで50kHzぐらいまで下げないと追いつかなかったので、F767ZIを使用しました。


Nucleo_DCO_Test04 ピンアサイン


Arduino Nucleo Description
D15 PB_8 OLED/I2C/SCL
D14 PB_9 OLED/I2C/SDA
GND GND OLED/GND
D5 PE_11 CheckPin
D4 PF_14 CheckPin
- PA_4 WaveOut

テストプログラム(mbed-cli)
https://github.com/ryood/Nucleo_DCO/tree/master/mbed/Nucleo_DCO_Test04

u8g2-mbedを使ったプログラム作成の手順


プロジェクト作成

PS D:\Users\gizmo\Documents\mbed> mbed new Nucleo_DCO_Test04
[mbed] Creating new program "Nucleo_DCO_Test04" (git)
[mbed] Adding library "mbed-os" from "https://github.com/ARMmbed/mbed-os" at branch/tag "latest"
[mbed] Updating reference "mbed-os" -> "https://github.com/ARMmbed/mbed-os/#f8b140f8d7cb226e41486c5df66ac4f3ce699219"
The following paths are ignored by one of your .gitignore files:
mbed-os.lib
Use -f if you really want to add them.

u8g2-mbedから以下のファイルをプロジェクトディレクトリにコピー
u8g2.lib
.mbedignore
src/*
src/main.cppはu8g2-mbedを使ったプログラムの雛形として使えます。

.mbedignoreの中身は

PS D:\Users\gizmo\Documents\Develop\Nucleo_DCO\mbed\Nucleo_DCO_Test04> cat .mbedignore
u8g2/cppsrc/*
u8g2/doc/*
u8g2/sys/*
u8g2/tools/*

となっていて、mbedのBUILD対象外とするファイルを指定するようです。

参考「Ignoring files from mbed build

続いてプロジェクトディレクトリに移動して、デプロイ

> mbed deploy

コンパイルです。

> mbed compile -t GCC_ARM -m NUCLEO_F767ZI

Nucleo_DCO_Test04ではOLEDに
fps | 経過時間
表示更新回数
を表示しています。fpsはおおよそ10.9になりました。OLEDの表示はメイン・ループ内で行っていて1秒間に10.9回のループです。操作子からの入力もメイン・ループ内で行うと思うので、fpsはもっと下がると思います。

出力波形

ch1:PA_4

Ticker割り込みの処理時間

ch2:D4

Hになっている時間(PW)が波形出力の割り込み処理に要した時間で、1.2マイクロ秒です。

mbedのオンラインコンパイラ(mbed OS 2)でやったときは(参考「Nucleo DCOの構想 ~ いわゆるプロダクト・アウト」)720ナノ秒だったので、処理時間が増えました。

これはOLED表示のためというよりは、OS 2 → OS 5に移行したためなんじゃなかな~と思います。

F767でもいろいろやろうと思うと48kHzのサンプリング・レートがキープできるかどうかちょっと心配です。

ToDo:

・3オシレーターの波形合成(ミキシング)
・入力系の処理のテスト

2018年8月28日火曜日

NucleoF446RE(mbed-cli)でu8g2を使う。


Arduinoの汎用モノクロディスプレイ用ライブラリのu8g2をmbedにPortしたライブラリが公開されています。

u8g2-mbed https://github.com/domantascibas/u8g2-mbed

mbed-cliで使うようになっています。mbed-cliのインストール、使い方は前回の「NucleoF446REでmbed-cliを使う。(メモ)」にあります。

Github:u8g2-mbed」からZipファイルを入手して作業ディレクトリに展開します。

作業ディレクトリ
D:\Users\gizmo\Documents\mbed\u8g2-mbed-master
展開したディレクトリに移動してmbedプロジェクトを作成。

PS D:\Users\gizmo\Documents\mbed\u8g2-mbed-master> mbed new .
[mbed] WARNING: Directory "D:\Users\gizmo\Documents\mbed\u8g2-mbed-master" is not empty.
---
[mbed] Creating new program "u8g2-mbed-master" (git)
[mbed] Adding library "mbed-os" from "https://github.com/ARMmbed/mbed-os" at branch/tag "latest"
[mbed] Updating reference "mbed-os" -> "https://github.com/ARMmbed/mbed-os/#f8b140f8d7cb226e41486c5df66ac4f3ce699219"
The following paths are ignored by one of your .gitignore files:
mbed-os.lib
Use -f if you really want to add them..

続いてデプロイします。

PS D:\Users\gizmo\Documents\mbed\u8g2-mbed-master> mbed deploy
[mbed] Updating library "mbed-os" to rev #f8b140f8d7cb (tags: latest, mbed-os-5.9.5)
[mbed] Adding library "u8g2" from "https://github.com/olikraus/u8g2" at rev #eb1b12fde776

ディレクトリの確認

PS D:\Users\gizmo\Documents\mbed\u8g2-mbed-master> ls


    ディレクトリ: D:\Users\gizmo\Documents\mbed\u8g2-mbed-master


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2018/08/26     17:11                .vscode
d-----       2018/08/26     17:11                fonts
d-----       2018/08/26     17:21                mbed-os
d-----       2018/08/26     17:11                src
d-----       2018/08/26     17:23                u8g2
-a----       2017/11/27      5:42             71 .gitignore
-a----       2018/08/26     17:23              8 .mbed
-a----       2017/11/27      5:42             48 .mbedignore
-a----       2017/11/27      5:42           1067 LICENSE
-a----       2018/08/26     17:21             78 mbed-os.lib
-a----       2018/08/26     17:21           1469 mbed_settings.py
-a----       2017/11/27      5:42           1508 README.md
-a----       2017/11/27      5:42             75 u8g2.lib

手持ちのI2C接続のSSD1306 OLED 128x32でテストしました。

ブレッドボード配線図

src/main.cppを以下のように書き換えました。

#include "mbed.h"
#include "u8g2.h"

DigitalOut led(D13);

uint8_t u8x8_gpio_and_delay_mbed(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
u8g2_t myScreen;

extern const uint8_t XLargeFont[] U8G2_FONT_SECTION("XLargeFont");
extern const uint8_t LargeFont[] U8G2_FONT_SECTION("LargeFont");

void setup() {
  //u8g2_Setup_ssd1306_i2c_128x64_noname_f(&myScreen, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_mbed);
  u8g2_Setup_ssd1306_i2c_128x32_univision_f(&myScreen, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_mbed);
  u8g2_InitDisplay(&myScreen);
  u8g2_SetPowerSave(&myScreen, 0);
  u8g2_ClearBuffer(&myScreen);

  // u8g2_SetFont(&myScreen, XLargeFont);
  // u8g2_DrawUTF8(&myScreen, 0, 32, "53");
  // u8g2_SetFont(&myScreen, LargeFont);
  // u8g2_DrawUTF8(&myScreen, 0, 32, "Hello world");
  u8g2_SetFont(&myScreen, u8g2_font_f16_t_japanese1);
  u8g2_DrawUTF8(&myScreen, 0, 20, "こんにちは世界");
  // u8g2_SetFont(&myScreen, u8g2_font_unifont_t_chinese2);
  // u8g2_DrawUTF8(&myScreen, 0, 62, "你好世界");

  u8g2_SendBuffer(&myScreen);
}

int main() {
  led = 0;
  // printf("Hello world");
  setup();
  
  while(1) {
    led = !led;
    wait(1.0);
  }
  return 0;
}

mbed-cliにMakefileみたいなものがあるのかわかりませんが、mbed compileするとfonts/以下の*.cファイル(フォント・データ)はコンパイルされるようです。

PS D:\Users\gizmo\Documents\mbed\u8g2-mbed-master> ls .\BUILD\NUCLEO_F446RE\GCC_ARM\fonts


    ディレクトリ: D:\Users\gizmo\Documents\mbed\u8g2-mbed-master\BUILD\NUCLEO_F446RE\GCC_ARM\fonts


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2018/08/28     12:29            150 LargeFont.d
-a----       2018/08/28     12:29           9880 LargeFont.o
-a----       2018/08/28     12:29            152 MediumFont.d
-a----       2018/08/28     12:29           8588 MediumFont.o
-a----       2018/08/28     12:29            150 SmallFont.d
-a----       2018/08/28     12:29           6084 SmallFont.o
-a----       2018/08/28     12:29            152 XLargeFont.d
-a----       2018/08/28     12:29           2464 XLargeFont.o

オブジェクトファイルは生成されますが、「XLargeFont」や「LargeFont」はmain.cppで宣言されていなくてコンパイル・エラーになるのでexternで宣言しました。

extern const uint8_t XLargeFont[] U8G2_FONT_SECTION("XLargeFont");
extern const uint8_t LargeFont[] U8G2_FONT_SECTION("LargeFont");

変数名のあとに付いている「U8G2_FONT_SECTION()」はメモリ空間を指定するマクロのようです。

I2C接続のSSD1306 128x64を使うようになっているのでI2C接続のSSD1306 128x32のものを使うように初期化関数を変更。

  //u8g2_Setup_ssd1306_i2c_128x64_noname_f(&myScreen, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_mbed);
  u8g2_Setup_ssd1306_i2c_128x32_univision_f(&myScreen, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_mbed);

「u8g2_font_f16_t_japanese1」というフォントはu8g2.hで宣言されています。一覧は「olikraus/u8g2」の「fntlistall」にあります。

コンパイル

PS D:\Users\gizmo\Documents\mbed\u8g2-mbed-master> mbed compile -t GCC_ARM -m NUCLEO_F446RE
Building project u8g2-mbed-master (NUCLEO_F446RE, GCC_ARM)
Scan: .
Scan: env
Scan: mbed
Compile [100.0%]: main.cpp
Link: u8g2-mbed-master
Elf2Bin: u8g2-mbed-master
+------------------+--------+-------+------+
| Module           |  .text | .data | .bss |
+------------------+--------+-------+------+
| [fill]           |    115 |     4 |   14 |
| [lib]\c.a        |  24641 |  2472 |   56 |
| [lib]\gcc.a      |   3168 |     0 |    0 |
| [lib]\misc       |    252 |    16 |   28 |
| mbed-os\drivers  |    192 |     4 |  100 |
| mbed-os\hal      |   1697 |     4 |   68 |
| mbed-os\platform |   2764 |   260 |  133 |
| mbed-os\rtos     |   8498 |   168 | 6073 |
| mbed-os\targets  |   8583 |     4 |  552 |
| src\delay.o      |      4 |     0 |    0 |
| src\main.o       |    194 |     4 |  156 |
| src\u8g2_mbed.o  |    204 |     4 |   56 |
| u8g2\csrc        |  50114 |     0 |  512 |
| Subtotals        | 100426 |  2940 | 7748 |
+------------------+--------+-------+------+
Total Static RAM memory (data + bss): 10688 bytes
Total Flash memory (text + data): 103366 bytes

Image: .\BUILD\NUCLEO_F446RE\GCC_ARM\u8g2-mbed-master.bin

ピンをアサイン


ピン名はPinNames.hで定義されています。Nucleo F446REの場合は

https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F446xE/TARGET_NUCLEO_F446RE/PinNames.h

I2C_SCL     = PB_8,
I2C_SDA     = PB_9,

と定義されています。

u8g2のピンアサインはu8g2-mbed-master\u8g2\cppsrc\U8g2lib.hで定義されています。


class U8G2_SSD1306_128X32_UNIVISION_1_SW_I2C : public U8G2 {
  public: U8G2_SSD1306_128X32_UNIVISION_1_SW_I2C(const u8g2_cb_t *rotation, uint8_t clock, uint8_t data, uint8_t reset = U8X8_PIN_NONE) : U8G2() {
    u8g2_Setup_ssd1306_i2c_128x32_univision_1(&u8g2, rotation, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_arduino);
    u8x8_SetPin_SW_I2C(getU8x8(), clock,  data,  reset);
  }
};

「u8g2-mbed-master\u8g2\cppsrc\」下はArduino用のラッパーで、そのままではmbedで使えません。

I2Cで信号線がSCL/SDAの2本だけのものならピン固定で使えそうですが、SPIだとRESETやDS線が必要なのでピンアサインできるようにしないとダメですがちょっとしんどそう(@@;;;

<追記:2018.09.01>

ピンアサインについてですが、u8g2-mbedのsrc/u8g2_mbed.cppを見てみると

DigitalInOut i2c_scl(PB_8);
DigitalInOut i2c_sda(PB_9);

とI2Cに使うピン名がハードコーディングされていました。(しかもSTM32のピン名)

u8g2-mbedは、u8g2の全体を移植したものではなく、STM32でI2CのSSD1306を使うためのものと考えたほうが良さそうです。

</追記>

2018年8月16日木曜日

ESP WROOM 02(ESP8266)でBME280とSSD1306 OLED(I2C)を使う。

ESP WROOM 02(ESP8266)で、気温、湿度、気圧センサーのBME280とSSD1306 OLEDを同時に使用しました。

SSD1306はI2C接続のものとSPI接続のものがありますが、ESP8266のSPIは使ったことがないので、とりあえずI2C接続のものを使用しました。

I2Cは信号線をプルアップして使うので、I2CとSPIで信号線を共有するのは難しそうです。ESP8266はIOで使えるピンが少ないので、SPIとI2Cを同時使用するにはちゃんと検証する必要がありそうです。

ESP8266のI2Cの使い方もよくわかっていないので、ライブラリ等ありものを使わせていただいてとりあえず動作させるのを優先しました。

ブレッドボード配線図


運用時(USB-UART変換モジュールなし)

I2Cの信号線はBME280モジュールでもSSD1306モジュールでもオンボードでプルアップされているので、理屈としてはそれぞれの抵抗を並列した値になります。
10kΩ // 4.7kΩ ≒ 3.2kΩ
Lの時に (3.3V / 3.2kΩ) ≒ 1mA 程度流れます。SCLとSDAがあるので無視できない消費量ですね。

Arduinoのスケッチ


BME280のライブラリは「SparkFun BME280 Arduino Library」を使用していましたが、問題がありました。

I2CのピンがデフォルトでハードウェアI2Cのものを使うようになっていて、自由にピンを割り当てられるソフトウェアI2Cを使うのが少しめんどうです。

スケッチ例に「Example9_SoftwareI2C.ino」があって、Arduino IDEのライブラリマネージャから「SoftwareWire by Testato」をインストールして使うようになってます。

他にI2CのピンをアサインできるBME280のライブラリを探してみると「Arduino-ESP8266-BME280」がありました。(Ambientさんでした)

exampleもAmbientに送るようになっているのでそのまま使わせていただくことにしました。

SSD1306のライブラリはu8g2を使いました。

参考
HiLetogo 0.9" I2C OLED 128X32をu8g2で使ってみる。
u8g2のFull BufferとPage Buffer

BME280、SSD1306のどちらのライブラリもArduinoのI2C制御クラスのWireオブジェクトをクラス内で初期化(begin())していて同時に使用するのは怖いんですが、ArduinoのI2C制御クラスのWireはSingletonになっているとおもうので(←確証なしです)、SCLとSDAに使うPINを合わせておけばだいじょうぶかな~と半分やけくそで使いました。

結果としては動作しているように見えます。

WiFiでAmbientにデータを送信するようにして、DeepSleepをかけました。

<Ambient_ESP8266_BME280_u8g2.ino>

#include <ESP8266WiFi.h>
#include <Wire.h>
#include "BME280.h"
#include "Ambient.h"
#include "U8g2lib.h"
#include "etc/config.h"

extern "C" {
#include "user_interface.h"
}

#define UART_TRACE (1)
#define PIN_CHECK  (1)
#define USE_OLED   (1)

#define TITLE_STR1  ("ESP8266 BME280 Thermo")
#define TITLE_STR2  ("2018.08.13")

#define LED 4
#define SDA 14
#define SCL 13

#define PERIOD 60

/*
  const char* ssid = "ssid";
  const char* password = "password";
  unsigned int channelId = 100;
  const char* writeKey = "...writeKey...";
*/

WiFiClient client;
Ambient ambient;
BME280 bme280;
#if (USE_OLED)
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
#endif

float calcDiscomfort(float temperature, float humidity) {
  return 0.81 * temperature + 0.01 * humidity * (0.99 * temperature - 14.3) + 46.3;
}

void setup()
{
  int t = millis();
  //wifi_set_sleep_type(LIGHT_SLEEP_T);

  struct rst_info* p_reset_info = ESP.getResetInfoPtr();

#if (UART_TRACE)
  Serial.begin(115200);
  delay(10);
  Serial.println("");
  Serial.println("");
  Serial.println(TITLE_STR1);
  Serial.println(TITLE_STR2);
  Serial.print(ESP.getResetReason());
  Serial.print("\t");
  Serial.println(p_reset_info->reason);
  Serial.print("Start");
#endif
#if (PIN_CHECK)
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);
#endif

  // BME280
  bme280.begin(SDA, SCL);
  // 電源投入直後の値は不安定なので、読み捨てる
  bme280.readTemperature();
  bme280.readHumidity();
  bme280.readPressure();

  double temperature = 0.0;
  double humidity = 0.0;
  double pressure = 0.0;
  float discomfort = 0.0;

  temperature = bme280.readTemperature();
  humidity = bme280.readHumidity();
  pressure = bme280.readPressure();
  discomfort = calcDiscomfort(temperature, humidity);

#if (UART_TRACE)
  Serial.print("temp: ");
  Serial.print(temperature);
  Serial.print(" DegC,  humid: ");
  Serial.print(humidity);
  Serial.print(" %, pressure: ");
  Serial.print(pressure);
  Serial.print(" hPa ");
  Serial.print(discomfort);
  Serial.println("");
#endif

#if (USE_OLED)
  u8g2.begin();
  if (p_reset_info->reason != REASON_DEEP_SLEEP_AWAKE) {
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_7x14_tf);
    u8g2.drawStr(0, 16, TITLE_STR1);
    u8g2.drawStr(0, 32, TITLE_STR2);
    u8g2.sendBuffer();
    delay(3000);
  }

  char strBuffer1[16];
  char strBuffer2[16];
  
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_logisoso20_tf);
  sprintf(strBuffer2, "%s%cC", dtostrf(temperature, 2, 1, strBuffer1), 0xb0);
  u8g2.drawStr(0, 32, strBuffer2);
  u8g2.setFont( u8g2_font_helvR14_tr);
  sprintf(strBuffer2, "%s%%", dtostrf(humidity, 2, 1, strBuffer1));
  u8g2.drawStr(78, 16, strBuffer2);
  u8g2.drawStr(78, 32, dtostrf(discomfort, 2, 1, strBuffer1));
  u8g2.sendBuffer();
#endif

  // WiFi
  WiFi.begin(ssid, password);

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

#if (PIN_CHECK)
    digitalWrite(LED, i++ % 2);
#endif
#if (UART_TRACE)
    Serial.print(".");
#endif
  }
#if (PIN_CHECK)
  digitalWrite(LED, LOW);
#endif

#if (UART_TRACE)
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.print(WiFi.localIP());
  Serial.println("");
#endif

  ambient.begin(channelId, writeKey, &client);
  ambient.set(1, temperature);
  ambient.set(2, humidity);
  ambient.set(3, pressure);
  ambient.set(4, discomfort);
  ambient.send();

  t = millis() - t;
  t = (t < PERIOD * 1000) ? (PERIOD * 1000 - t) : 1;

  ESP.deepSleep(t * 1000, RF_DEFAULT);
  delay(1000);
}

void loop()
{
}

スケッチ冒頭の

struct rst_info* p_reset_info = ESP.getResetInfoPtr();

でResetの理由を保持するreset_infoを取得して、起動時にタイトル画面をOLEDに表示する処理を、Sleep復帰時には

if (p_reset_info->reason != REASON_DEEP_SLEEP_AWAKE) {
...
}

としてバイパスするようにしています。

ESP.getResetReason()

で、整数値ではなくString型でResetの理由も取得できます。

Sleep中もOLEDに電源を供給しておけば表示され続けますが、Sleepからの復帰時にOLEDが初期化されるので一瞬OLEDの表示が消えます。

電池ももったいないし、OLEDは動作確認用として、長期運用する場合は外しておくつもりです。

もっと細かい周期で環境を測定するためには、例えばArduino Nanoを使ってUSB経由でロギングするものを作ればいい?

Ambientのグラフ


電源電流


電源に0.1ΩのRを直列に入れOWON B35(DC/mV)で測定(1mV→10mA換算)。1秒周期

OLED、LEDあり

Avg 1.2414 mV
Min 0.59 mV
Max 12.83 mV

WakeUp時に80mA~130mA程度、Sleep時に6mA程度

OLED、LEDをブレッドボードから抜いた

Avg 0.614133333 mV
Min 0 mV
Max 12.35 mV

WakeUp時に70mA~125mA程度、Sleep時に0mA程度

OLED、LEDがあると常時6mA程度消費します。

I2C信号


ch1:SDA ch2:SCL

IC2クロックは多少揺れがあり、100kHzより少し遅いぐらいです。←Standard Modeぐらい。

と、なんとか不快指数計をWiFi対応させたところで、猛暑のピークは過ぎたようです(@@;