製作モジュール一覧

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

2022年10月27日木曜日

SSD1306 OLEDをSTM32で動かす。 afiskonライブラリ

afiskonライブラリafiskon/stm32-ssd1306はSSD1306は4iloライブラリからforkされたものの様で、SSD1306 OLEDのI2C版とSPI版に対応しています。

Nucleo-F446REとNucleo-G431KBで動作確認しました。

以下の記事を参考にさせていただきました。

「afiskon」さん(本家) https://github.com/afiskon/stm32-ssd1306
「hacksOnTable」さん 「STM32 OLED TUTORIAL(Youtube)」

HALを使ったプログラミングはSTM32CubeIDEの登場でかなり便利になりましたが、MPUのシリーズによって特徴があり、設定やプログラミングが方法がそれぞれ少しずつ異なります。冗長になりますが、Nucleo-F446REとNucleo-G431GBで、SSD1306のI2C版とSPI版の使い方を説明します。

STM32CubeIDEのバージョン: 1.10.1

I2C版 Nucleo-F446RE


Board SelecotorでNuckeo-F446REを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          Master Features
            I2C Speed Mode: Standard Mode
        GPIO Settings
          PB8 I2C1_SCL
          PB9 I2C1_SDA

afiskonライブラリではデフォルトでI2C1を使う設定になっています。I2C2やI2C3などを使う場合は「ssd1306_conf_template.h」を適宜書き換えます。

ハードウェアの準備


SSD1306 F446RE Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PB8 D15
SDA PB9 D14

ブレッドボード配線図

ライブラリの準備


GitHubのafiskon/stm32-ssd1306からZIPファイルをダウンロードします。
  • ssd1306.h
  • ssd1306_conf_templates.h
  • ssd1306_fonts.h
  • ssd1306_tests.h
を「<Projectフォルダ>\Core\Inc\」に
  • ssd1306.c
  • ssd1306_fonts.c
  • ssd1306_tests.c
を「<Projectフォルダ>\Core\Src\」にコピーします。IDEのProject ExplorerでProjectフォルダを右クリックし「Refresh」します。

「Core/Inc/ssd1306_conf_template.h」の内容を以下のように書き換えます。

// Choose a microcontroller family
//#define STM32F0
#define STM32F4

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<略>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

実行している様子

FPS表示値

Standard Mode 9
Fast Mode 34

F446REの駆動クロックはHSI/84MHz。

I2C信号波形

F446RE I2C Fast Mode 2.2kΩ Pullup


C1:SDA C2:SCL

I2C版 Nucleo-G431KB


Board SelecotorでNuckeo-G431KBを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          Master Features
            I2C Speed Mode: Standard Mode
        GPIO Settings
          PA15 I2C1_SCL
          PB7  I2C1_SDA

ブレッドボード配線図


SSD1306 G431KB Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PA15 D5
SDA PB7 D4

ライブラリの準備


必要なファイルをコピーします。
  • Core/Inc/ssd1306.h
  • Core/Inc/ssd1306_conf_templates.h
  • Core/Inc/ssd1306_fonts.h
  • Core/Inc/ssd1306_tests.h
  • Core/Src/ssd1306.c
  • Core/Src/ssd1306_fonts.c
  • Core/Src/ssd1306_tests.c
「Core/Inc/ssd1306_conf_template.h」の内容を以下のように書き換えます。

// Choose a microcontroller family
//#define STM32F0
#define STM32G4

「#define STM32G4」はコメントアウトで用意されていないので書き加えます。「ssd1306.h」では

#elif defined(STM32G4)
#include "stm32g4xx_hal.h"

と、STM32G4を使用する場合の定義があります。

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<略>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

main.cへのソースコードの追加はI2C版 Nucleo-F446REと同じです。

実行している様子

FPS表示値

Standard Mode 9
Fast Mode 35
Fast Mode Plus 76

※Fast Mode Plusで使用する場合は2.2kΩ程度で外部Pullupする。

G431KBの駆動クロックはHSI/170MHz。

I2C信号波形

G431RB I2C Fast Mode Plus 2.2kΩ Pullup


C1:SDA C2:SCL

SPI版 Nucleo-F446RE


Board SelecotorでNuckeo-F446REを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  System Core
    Configuration
      GPIO
        PA10 : Output Push Pull : Very High : OLED_Res
        PB4  : Output Push Pull : Very High : OLED_CS
        PB5  : Output Push Pull : Very High : OLED_DC
  Connectivity
    SPI2
      Mode
        Mode: Half Duplex Master
      Configuration
        Parameter Settings
          Clock Parameters
            Prescaler(for Baud Rate): 64
            Clock Polarity(CPOL): High
            Clock Phase(CPHA): 2 Edge
        GPIO Settings
          PB10 SPI2_SCK
          PC1  SPI2_MOSI

Nucleo-F446REでは、SPI1はデフォルトでは使用不可になっているのでSPI2を使用します。MISOは使わないのでModeはHalf Duplex Masterにします。

動作確認のためClock Prescalerは大きめにしてクロック周波数を低く設定します。

重要:Clock Polarity(CPOL)、Clock Phase(CPHA)は上記の通り設定すると動作するようです。

またGPIOで制御信号線(CS、RES、DC)を割り当てます。右側のPinout図でPinをクリックしてGPIO_Outputを選択し、User Labelを設定します。User Labelはssd1306_conf_template.hで定義されているものを入力します。

ハードウェアの準備


SSD1306 F446RE Arduino Header 機能
GND GND GND GND
VDD 3V3 3V3 3.3V Power
D0 PB10 D6 SPI/SCK
D1 PC1 A4 SPI/MOSI
RES PA10 D2 Reset
DC PB5 D4 Data/Command
CS PB4 D5 SPI/CS

ブレッドボード配線図

ライブラリの準備


必要なファイルをコピーします。
  • Core/Inc/ssd1306.h
  • Core/Inc/ssd1306_conf_templates.h
  • Core/Inc/ssd1306_fonts.h
  • Core/Inc/ssd1306_tests.h
  • Core/Src/ssd1306.c
  • Core/Src/ssd1306_fonts.c
  • Core/Src/ssd1306_tests.c
Core\Inc\ssd1306_conf_template.hを以下のように変更します。

// Choose a microcontroller family
//#define STM32F0
//#define STM32F1
#define STM32F4
//#define STM32L0
//#define STM32L1
//#define STM32L4
//#define STM32F3
//#define STM32H7
//#define STM32F7
//#define STM32G0

// Choose a bus
//#define SSD1306_USE_I2C
#define SSD1306_USE_SPI

// I2C Configuration
//#define SSD1306_I2C_PORT        hi2c1
//#define SSD1306_I2C_ADDR        (0x3C << 1)

// SPI Configuration
#include "main.h"
#define SSD1306_SPI_PORT        hspi2
#define SSD1306_CS_Port         OLED_CS_GPIO_Port
#define SSD1306_CS_Pin          OLED_CS_Pin
#define SSD1306_DC_Port         OLED_DC_GPIO_Port
#define SSD1306_DC_Pin          OLED_DC_Pin
#define SSD1306_Reset_Port      OLED_Res_GPIO_Port
#define SSD1306_Reset_Pin       OLED_Res_Pin

SPI2を使うのでSSD_1306_SPI_PORTの定義をhspi2に変更します。

あまり感心できるものではありませんが、OLED_CS_GPIO_Portなどが定義されていないというエラーが出るので、直前に
#include "main.h"
という一行を追加して、定義されているヘッダーファイルをインポートします。

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<略>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

main.cのコード追加はI2C版と同じです。

実行している様子

FPS表示値

SPI Prescaler:64 (655.25KBits/s) 60
SPI Prescaler:16 (2.625MBits/s) 144
SPI Prescaler: 2 (21.0MBits/s) 217

F446REの駆動クロックはHSI/84MHz。

SPI信号波形

F446RE SPI Prescaler=16


C1:MOSI C2:SCK


SPI版 Nucleo-G431KB


Board SelecotorでNuckeo-G431KBを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  System Core
    Configuration
      GPIO
        PA11 : Output Push Pull : Very High : OLED_Res
        PB4  : Output Push Pull : Very High : OLED_CS
        PB5  : Output Push Pull : Very High : OLED_DC
  Connectivity
    SPI1
      Mode
        Mode: Half Duplex Master
      Configuration
        Parameter Settings
          Basic Parameters
            Data Size: 8bit
          Clock Parameters
            Prescaler(for Baud Rate): 256
            Clock Polarity(CPOL): High
            Clock Phase(CPHA): 2 Edge
        GPIO Settings
          PA5 SPI2_SCK
          PA7 SPI2_MOSI

Nucleo-G431KBではデフォルトでSPI1が使用できるのでSPI1を使います。MISOは使わないのでModeはHalf Duplex Masterにします。重要:Data Sizeがデフォルトでは4bitになっているので、8bitに変更します。

動作確認のためClock Prescalerは大きめにしてクロック周波数を低く設定します。

Clock Polarity(CPOL)、Clock Phase(CPHA)は上記の通り設定すると動作するようです。

またGPIOで制御信号線(CS、RES、DC)を割り当てます。右側のPinout図でPinをクリックしてGPIO_Outputを選択し、User Labelを設定します。User Labelはssd1306_conf_template.hで定義されているものを入力します。

ハードウェアの準備


SSD1306 G431KB Arduino Header 機能
GND GND GND GND
VDD 3V3 3V3 3.3V Power
D0 PA5 A4 SPI/SCK
D1 PA7 A6 SPI/MOSI
RES PA11 D10 Reset
DC PB5 D11 Data/Command
CS PB4 D12 SPI/CS

ブレッドボード配線図


ライブラリの準備


必要なファイルをコピーします。
  • Core/Inc/ssd1306.h
  • Core/Inc/ssd1306_conf_templates.h
  • Core/Inc/ssd1306_fonts.h
  • Core/Inc/ssd1306_tests.h
  • Core/Src/ssd1306.c
  • Core/Src/ssd1306_fonts.c
  • Core/Src/ssd1306_tests.c
Core\Inc\ssd1306_conf_template.hを以下のように変更します。

// Choose a microcontroller family
//#define STM32F0
//#define STM32F1
//#define STM32F4
//#define STM32L0
//#define STM32L1
//#define STM32L4
//#define STM32F3
//#define STM32H7
//#define STM32F7
//#define STM32G0
#define STM32G4

// Choose a bus
//#define SSD1306_USE_I2C
#define SSD1306_USE_SPI

// I2C Configuration
//#define SSD1306_I2C_PORT        hi2c1
//#define SSD1306_I2C_ADDR        (0x3C << 1)

// SPI Configuration
#include "main.h"
#define SSD1306_SPI_PORT        hspi1
#define SSD1306_CS_Port         OLED_CS_GPIO_Port
#define SSD1306_CS_Pin          OLED_CS_Pin
#define SSD1306_DC_Port         OLED_DC_GPIO_Port
#define SSD1306_DC_Pin          OLED_DC_Pin
#define SSD1306_Reset_Port      OLED_Res_GPIO_Port
#define SSD1306_Reset_Pin       OLED_Res_Pin

あまり感心できるものではありませんが、OLED_CS_GPIO_Portなどが定義されていないというエラーが出るので、直前に
#include "main.h"
という一行を追加して、定義されているヘッダーファイルをインポートします。

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。


ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<略>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

main.cはいずれも同じコードです。

実行している様子

FPS表示値

SPI Prescaler:256 (654.062KBits/s) 69
SPI Prescaler: 64 (2.65625MBits/s) 199
SPI Prescaler: 8 (21.25MBits/s) 437
SPI Prescaler: 4 (42.5MBits/s) 動作せず

G431REの駆動クロックはHSI/170MHz。

SPI信号波形

G431KB SPI Prescaler=64


C1:MOSI C2:SCK


2022年10月24日月曜日

SSD1306 OLEDをSTM32で動かす。 4iloライブラリ

Nucleo-F446REとNucleo-G431KBで4lioライブラリを使ってみました。4iloライブラリはI2C版のみの対応です。SPI版のSSD1306 OLEDを使用するには派生ライブラリのafiskonライブラリafiskon/stm32-ssd1306を使います。(次回予定)

以下の記事を参考にさせていただきました。


使用したSTM32CubeIDEのバージョン: 1.10.1

なお、久しぶりにSTM32CubeIDEを立ち上げると延々とUpdateがかかるので事前に一度起動してUpdateを完了させると良いと思います。ST-LinkのファームウェアのアップデートもあるのでLチカなども走らせましょう。

Nucleo-F446RE


Board SelecotorでNuckeo-F446REを指定してDefult Modeで初期化。

MXの設定


STM32CubeIDEでは「Device Configuration Tool」と呼ばれるもので、「.ioc」ファイルを開くとIDE内で起動します。STM32CubeMXと機能的にはほぼ同等なので、このBlogではMXと呼んでいます。STM32CubeMXを別途立ち上げる必要はありません。

Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          Master Features
            I2C Speed Mode: Standard Mode
        GPIO Settings
          PB8 I2C1_SCL
          PB9 I2C1_SDA

Parameter Settingsはデフォルトのまま、Standard Mode (100kHz)でOKです。Fast Mode(400kHz)でも動作します。

GPIO Settingsは右側のPinout図で設定すると左下のConfigurationペインに反映されます。ピン割当はArduinoヘッダのD14(PB8)、D15(PB9)に変更しました。

ハードウェアの準備


ブレッドボード配線図
SSD1306 F446RE Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PB8 D15
SDA PB9 D14

ライブラリの準備


4ilo/ssd1306-stm32HALからZIPファイルをダウンロード(右上の「Code」-「Download ZIP」をクリック)して展開しておきます。
  • ssd1306.h
  • fonts.h
を「<Projectフォルダ>\Core\Inc\」に
  • ssd1306.c
  • fonts.c
を「<Projectフォルダ>\Core\Src\」にコピーします。IDEのProject ExplorerでProjectフォルダを右クリックし「Refresh」します。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

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

<略>

  /* USER CODE BEGIN 2 */
  // Init lcd using one of the stm32HAL i2c typedefs
  ssd1306_Init(&hi2c1);

  // Write data to local screenbuffer
  ssd1306_SetCursor(0, 36);
  ssd1306_WriteString("4ilo", Font_11x18, White);

  // Copy all data from local screenbuffer to the screen
  ssd1306_UpdateScreen(&hi2c1);
  /* USER CODE END 2 */

実行している様子

I2C信号波形


外部Pullupなし Fast Mode(400kHz)

C1:SDA C2:SCL

I2CのSCLとSDAは、SSD1306モジュール上でPullupされているので、外部Pullupしなくても動作します。

外部Pullup 10kΩ  Fast Mode(400kHz)

C1:SDA C2:SCL

外部Pullupすると信号波形がきれいになります。

外部Pullup 2.2kΩ  Fast Mode(400kHz)

C1:SDA C2:SCL

外部Pullupの抵抗値を小さくしても400kHzクロックではあまり変わりませんね。

Nucleo-G431KB


Board SelecotorでNuckeo-G431KBを指定して「Defult Mode」で初期化。

MXの設定


Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          I2C Speed Mode: Standard Mode
        GPIO Settings
          PA15: I2C1_SCL
          PB7:  I2C1_SDA

I2C Speed ModeはまずStandard Modeで動作確認し、Fast Mode、Fast Plus Modeに変更して動作確認しました。

ハードウェアの準備


ブレッドボード配線図
SSD1306 G431KB Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PA15 D4
SDA PB9 D5

ライブラリの準備


Nucleo-F446REと同様にファイルをコピーします。

Core/Inc/fonts.h
Core/Inc/ssd1306.h
Core/Src/fonts.c
Core/Src/fonts.h

重要: STMG431KBを使うので、Core/Inc/ssd1306.hを以下のように変更します。

//#include "stm32f4xx_hal.h"
#include "stm32g4xx_hal.h"

main.cへのソースコードの追加はNucleo-F446REの場合と同じです。

実行している様子

I2C Speed Mode


10kΩの外部Pullupした場合、Standard Mode、Fast Modeでは動作しましたが、Fast Mode Plusでは動作しませんでした。外部Pullupを2.2kΩに変更するとFast Mode Plusでも動作しました。

Standard Mode(100kHz) 外部Pullup 10kΩ 

C1:SDA C2:SCL

Fast Mode(400kHz) 外部Pullup 10kΩ 

C1:SDA C2:SCL

Fast Mode Plus(1000kHz) 外部Pullup 10kΩ 

C1:SDA C2:SCL

9bit目でACKされておらず(HIGHに引っ張りきれていない)、以降のI2C通信が失敗しています。

Fast Mode Plus(1000kHz) 外部Pullup 2.2kΩ 

C1:SDA C2:SCL

Pullup抵抗の抵抗値を小さくすると、ACKが認識されて正常に通信が行われています。

注)オシロ波形の下に周波数を表示させていますが、C1:SDAの値です。クロック周波数を知るためにはC2:SCLの値を表示させるべきでした。クロック周波数はC2(青色)の波形数と目盛りから概算できます。

2022年10月17日月曜日

SSD1306 OLEDをArduinoで動かす。 Adafruitのライブラリ

AliExpressで購入したSSD1306 OLEDモジュールをArduinoで動作確認しました。使用したライブラリはAdafruit SSD1306です。

ライブラリのインストール


Arduino IDE (Ver.1.8.13)で、「スケッチ」-「ライブラリをインクルード」-「ライブラリを管理...」を開く。検索窓に「Adafruit SSD1306」などと入力して、
Adafruit SSD1306 (by Adafruit)
を探し「インストール」します。同様に
Adafruit GFX Library (by Adafruit)
を探し「インストール」します。

SPI


ブレッドボード配線

Arduino Pro mini 3.3V/8MHz(互換品)を使用しました。

接続表


SSD1306 Arduino 機能
GND GND GND
VCC VCC VCC(3.3V)
D0 10 SPI/SCK
D1 9 SPI/MOSI
RES 13 RESET
DC 11 Data/Command
CS 12 SPI/CS

動作確認用スケッチ


「ファイル」-「スケッチ例」-「カスタムライブラリのスケッチ例」-「Adafruit SSD1306」-「ssd1306_128x64_spi」

SPI通信波形


C1:D1(SPI/MOSI) C2:D0(SPI/SCK)

I2C


ブレッドボード配線


Arduino Pro mini 3.3V/8MHz(互換品)を使用しました。

I2Cの信号線(SCK/SDA)はプルアップが必要ですが、OLEDモジュールでプルアップされており、Arduino側でも内部プルアップされているため、外部プルアップは不要です。ただし信号波形がなまる場合は必要に応じて追加で外部プルアップします。

接続表


SSD1306 Arduino 機能
GND GND GND
VCC VCC VCC(3.3V)
SCK A5 I2C/SCL
SDA A4 I2C/SDA

動作確認用スケッチ


「ファイル」-「スケッチ例」-「カスタムライブラリのスケッチ例」-「Adafruit SSD1306」-「ssd1306_128x64_i2c」

重要:SSD1306 OLEDのI2Cアドレスを変更する必要がある場合があります。私が使用したモジュールは変更が必要でした。

// #define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32

I2C波形


外部プルアップしない場合

C1:SDA C2:SCK


2.2kΩで外部プルアップした場合

C1:SDA C2:SCK

2019年4月14日日曜日

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

Arduinoやmbedで、中華製の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まで測定できるように抵抗の分圧値を変更しました。精度は悪くなりますが使える電源電圧を融通するため。