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。

0 件のコメント:

コメントを投稿