STM32でZephyrRTOS入門~SPIでOLED(SH1107)制御~
はじめに
これまでWindows上にZephyrRTOSの開発環境を構築し、NucleoF103RBボードを用いて学習を進めてきた。
今回はSPI接続のOLEDディスプレイ制御に挑戦する。使用したOLEDは以下の通りである。
Grove - OLED Display 1.12" SH1107 V3.0
このOLEDは購入時点ではI2C接続で動作する配線となっているが、パターンカットとはんだ付けを行うことでSPI接続に変更できる。
また、このOLEDに搭載されるSH1107コントローラはZephyrでは公式でサポートされていないため、今回もドライバの自作から取り組むことにした。
NucleoF103RBとOLEDの接続
| OLED | NucleoF103RB |
|---|---|
| CS | PB6 |
| D/C | PA9 |
| RES | PA8 |
| SI | MOSI(PA7) |
| SCL | SCK(PA5) |
| 5V | 5V |
| GND | GND |
Zephyrで自作ドライバを作成する手順
大まかな流れは以下の通り。
- 自作ドライバのコードを作成
- ビルドシステム用の設定ファイルを作成
- バインディングファイルを作成
- モジュール定義ファイルを作成
1. 自作ドライバのコードを作成
以前自作したカラーセンサ(TCS34725)ドライバ同様、以下ディレクトリ構成でSH1107ドライバを作成した。
modules/
sh1107/
drivers/
display/
sh1107/
sh1107.c
sh1107_reg.h
「sh1107.c」、「sh1107_reg.h」はZephyr公式のSSD1306ドライバをベースとし、SH1107特有の初期化シーケンスはArduino向けライブラリを参考に実装を進めた。
ソースコードは以下の通りである。
sh1107_reg.h
#ifndef __SH1107_REGS_H__
#define __SH1107_REGS_H__
/* All following bytes will contain commands */
#define SH1107_CONTROL_ALL_BYTES_CMD 0x00
/* All following bytes will contain data */
#define SH1107_CONTROL_ALL_BYTES_DATA 0x40
/* The next byte will contain a command */
#define SH1107_CONTROL_BYTE_CMD 0x80
/* The next byte will contain data */
#define SH1107_CONTROL_BYTE_DATA 0xc0
#define SH1107_READ_STATUS_MASK 0xc0
#define SH1107_READ_STATUS_BUSY 0x80
#define SH1107_READ_STATUS_ON 0x40
/*
* Fundamental Command Table
*/
#define SH1107_SET_CONTRAST_CTRL 0x81 /* double byte command */
#define SH1107_SET_ENTIRE_DISPLAY_OFF 0xa4
#define SH1107_SET_ENTIRE_DISPLAY_ON 0xa5
/* RAM data of 1 indicates an "ON" pixel */
#define SH1107_SET_NORMAL_DISPLAY 0xa6
/* RAM data of 0 indicates an "ON" pixel */
#define SH1107_SET_REVERSE_DISPLAY 0xa7
#define SH1107_DISPLAY_OFF 0xae
#define SH1107_DISPLAY_ON 0xaf
/*
* Addressing Setting Command Table
*/
#define SH1107_SET_LOWER_COL_ADDRESS 0x00
#define SH1107_SET_LOWER_COL_ADDRESS_MASK 0x0f
#define SH1107_SET_HIGHER_COL_ADDRESS 0x10
#define SH1107_SET_HIGHER_COL_ADDRESS_MASK 0x0f
#define SH1107_SET_MEM_ADDRESSING_MODE 0x20 /* double byte command */
#define SH1107_SET_MEM_ADDRESSING_HORIZONTAL 0x00
#define SH1107_SET_MEM_ADDRESSING_VERTICAL 0x01
#define SH1107_SET_MEM_ADDRESSING_PAGE 0x02
#define SH1107_SET_COLUMN_ADDRESS 0x21 /* triple byte command */
#define SH1107_SET_PAGE_ADDRESS 0x22 /* triple byte command */
#define SH1107_SET_PAGE_START_ADDRESS 0xb0
#define SH1107_SET_PAGE_START_ADDRESS_MASK 0x07
/*
* Hardware Configuration Command Table
*/
// #define SH1107_SET_START_LINE 0x40
#define SH1107_SET_START_LINE 0xdc
#define SH1107_SET_START_LINE_MASK 0x3f
#define SH1107_SET_SEGMENT_MAP_NORMAL 0xa0
#define SH1107_SET_SEGMENT_MAP_REMAPED 0xa1
#define SH1107_SET_MULTIPLEX_RATIO 0xa8 /* double byte command */
#define SH1107_SET_COM_OUTPUT_SCAN_NORMAL 0xc0
#define SH1107_SET_COM_OUTPUT_SCAN_FLIPPED 0xc8
#define SH1107_SET_DISPLAY_OFFSET 0xd3 /* double byte command */
#define SH1107_SET_PADS_HW_CONFIG 0xda /* double byte command */
#define SH1107_SET_PADS_HW_SEQUENTIAL 0x02
#define SH1107_SET_PADS_HW_ALTERNATIVE 0x12
#define SH1107_SET_IREF_MODE 0xad
#define SH1107_SET_IREF_MODE_INTERNAL 0x30
#define SH1107_SET_IREF_MODE_EXTERNAL 0x00
/*
* Timing and Driving Scheme Setting Command Table
*/
#define SH1107_SET_CLOCK_DIV_RATIO 0xd5 /* double byte command */
#define SH1107_SET_CHARGE_PERIOD 0xd9 /* double byte command */
#define SH1107_SET_VCOM_DESELECT_LEVEL 0xdb /* double byte command */
#define SH1107_NOP 0xe3
/*
* Charge Pump Command Table
*/
#define SH1107_SET_CHARGE_PUMP_ON 0x8d /* double byte command */
#define SH1107_SET_CHARGE_PUMP_ON_DISABLED 0x10
#define SH1107_SET_CHARGE_PUMP_ON_ENABLED 0x14
#define SH1106_SET_DCDC_MODE 0xad /* double byte command */
#define SH1106_SET_DCDC_DISABLED 0x8a
#define SH1106_SET_DCDC_ENABLED 0x8b
#define SH1107_SET_PUMP_VOLTAGE_64 0x30
#define SH1107_SET_PUMP_VOLTAGE_74 0x31
#define SH1107_SET_PUMP_VOLTAGE_80 0x32
#define SH1107_SET_PUMP_VOLTAGE_90 0x33
/*
* Read modify write
*/
#define SH1107_READ_MODIFY_WRITE_START 0xe0
#define SH1107_READ_MODIFY_WRITE_END 0xee
/* time constants in ms */
#define SH1107_RESET_DELAY 10
#define SH1107_SUPPLY_DELAY 20
#endif
sh1107.c
基本的にはsh1306.cをコピーしてsh1306をsh1107に置換したものである。大きく変更したのは以下の関数。
-
sh1107_init_device
SH1107の初期化シーケンスに基づき実装を変更。 -
sh1107_write
もともとconfig->sh1106_compatibleのフラグを見てssd1306_write_sh1106()を呼び出していたが、フラグによらず、ssd1306_write_sh1106()相当の処理を行うように変更。
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sh1107, CONFIG_DISPLAY_LOG_LEVEL);
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include "sh1107_regs.h"
#define SH1107_CLOCK_DIV_RATIO 0x0
#define SH1107_CLOCK_FREQUENCY 0x8
#define SH1107_PANEL_VCOM_DESEL_LEVEL 0x20
#define SH1107_PANEL_PUMP_VOLTAGE SH1107_SET_PUMP_VOLTAGE_90
#define SH1107_PANEL_VCOM_DESEL_LEVEL_SSD1309 0x34
#ifndef SH1107_ADDRESSING_MODE
#define SH1107_ADDRESSING_MODE (SH1107_SET_MEM_ADDRESSING_HORIZONTAL)
#endif
union sh1107_bus {
struct i2c_dt_spec i2c;
struct spi_dt_spec spi;
};
typedef bool (*sh1107_bus_ready_fn)(const struct device *dev);
typedef int (*sh1107_write_bus_fn)(const struct device *dev, uint8_t *buf, size_t len,
bool command);
typedef const char *(*sh1107_bus_name_fn)(const struct device *dev);
struct sh1107_config {
union sh1107_bus bus;
struct gpio_dt_spec data_cmd;
struct gpio_dt_spec reset;
struct gpio_dt_spec supply;
sh1107_bus_ready_fn bus_ready;
sh1107_write_bus_fn write_bus;
sh1107_bus_name_fn bus_name;
uint16_t height;
uint16_t width;
uint8_t segment_offset;
uint8_t page_offset;
uint8_t display_offset;
uint8_t multiplex_ratio;
uint8_t prechargep;
bool segment_remap;
bool com_invdir;
bool com_sequential;
bool color_inversion;
bool ssd1309_compatible;
bool sh1106_compatible;
int ready_time_ms;
bool use_internal_iref;
};
struct sh1107_data {
enum display_pixel_format pf;
};
#if DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(sinowealth_sh1107, i2c)
static bool sh1107_bus_ready_i2c(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
return i2c_is_ready_dt(&config->bus.i2c);
}
static int sh1107_write_bus_i2c(const struct device *dev, uint8_t *buf, size_t len, bool command)
{
const struct sh1107_config *config = dev->config;
return i2c_burst_write_dt(&config->bus.i2c,
command ? SH1107_CONTROL_ALL_BYTES_CMD :
SH1107_CONTROL_ALL_BYTES_DATA,
buf, len);
}
static const char *sh1107_bus_name_i2c(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
return config->bus.i2c.bus->name;
}
#endif
#if DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(sinowealth_sh1107, spi)
static bool sh1107_bus_ready_spi(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
if (gpio_pin_configure_dt(&config->data_cmd, GPIO_OUTPUT_INACTIVE) < 0) {
return false;
}
return spi_is_ready_dt(&config->bus.spi);
}
static int sh1107_write_bus_spi(const struct device *dev, uint8_t *buf, size_t len, bool command)
{
const struct sh1107_config *config = dev->config;
int ret;
gpio_pin_set_dt(&config->data_cmd, command ? 0 : 1);
struct spi_buf tx_buf = {
.buf = buf,
.len = len
};
struct spi_buf_set tx_bufs = {
.buffers = &tx_buf,
.count = 1
};
ret = spi_write_dt(&config->bus.spi, &tx_bufs);
return ret;
}
static const char *sh1107_bus_name_spi(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
return config->bus.spi.bus->name;
}
#endif
static inline bool sh1107_bus_ready(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
return config->bus_ready(dev);
}
static inline int sh1107_write_bus(const struct device *dev, uint8_t *buf, size_t len,
bool command)
{
const struct sh1107_config *config = dev->config;
return config->write_bus(dev, buf, len, command);
}
static inline int sh1107_set_panel_orientation(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {(config->segment_remap ? SH1107_SET_SEGMENT_MAP_REMAPED
: SH1107_SET_SEGMENT_MAP_NORMAL),
(config->com_invdir ? SH1107_SET_COM_OUTPUT_SCAN_FLIPPED
: SH1107_SET_COM_OUTPUT_SCAN_NORMAL)};
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static inline int sh1107_set_timing_setting(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {SH1107_SET_CLOCK_DIV_RATIO,
(SH1107_CLOCK_FREQUENCY << 4) | SH1107_CLOCK_DIV_RATIO,
SH1107_SET_CHARGE_PERIOD,
config->prechargep,
SH1107_SET_VCOM_DESELECT_LEVEL,
config->ssd1309_compatible ? SH1107_PANEL_VCOM_DESEL_LEVEL_SSD1309 :
SH1107_PANEL_VCOM_DESEL_LEVEL};
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static inline int sh1107_set_hardware_config(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {
SH1107_SET_START_LINE,
SH1107_SET_DISPLAY_OFFSET,
config->display_offset,
SH1107_SET_PADS_HW_CONFIG,
(config->com_sequential ? SH1107_SET_PADS_HW_SEQUENTIAL
: SH1107_SET_PADS_HW_ALTERNATIVE),
SH1107_SET_MULTIPLEX_RATIO,
config->multiplex_ratio,
};
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static inline int sh1107_set_charge_pump(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {
(config->sh1106_compatible ? SH1106_SET_DCDC_MODE : SH1107_SET_CHARGE_PUMP_ON),
(config->sh1106_compatible ? SH1106_SET_DCDC_ENABLED
: SH1107_SET_CHARGE_PUMP_ON_ENABLED),
SH1107_PANEL_PUMP_VOLTAGE,
};
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static inline int sh1107_set_iref_mode(const struct device *dev)
{
int ret = 0;
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {
SH1107_SET_IREF_MODE,
SH1107_SET_IREF_MODE_INTERNAL,
};
if (config->use_internal_iref) {
ret = sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
return ret;
}
static int sh1107_resume(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {
SH1107_DISPLAY_ON,
};
/* Turn on supply if pin connected */
if (config->supply.port) {
gpio_pin_set_dt(&config->supply, 1);
k_sleep(K_MSEC(SH1107_SUPPLY_DELAY));
}
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static int sh1107_suspend(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
uint8_t cmd_buf[] = {
SH1107_DISPLAY_OFF,
};
/* Turn off supply if pin connected */
if (config->supply.port) {
gpio_pin_set_dt(&config->supply, 0);
k_sleep(K_MSEC(SH1107_SUPPLY_DELAY));
}
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static int sh1107_write(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, const void *buf)
{
const struct sh1107_config *config = dev->config;
size_t buf_len;
if (desc->pitch < desc->width) {
LOG_ERR("Pitch is smaller than width");
return -1;
}
buf_len = MIN(desc->buf_size, desc->height * desc->width / 8);
if (buf == NULL || buf_len == 0U) {
LOG_ERR("Display buffer is not available");
return -1;
}
if (desc->pitch > desc->width) {
LOG_ERR("Unsupported mode");
return -1;
}
if ((y & 0x7) != 0U) {
LOG_ERR("Unsupported origin");
return -1;
}
LOG_DBG("x %u, y %u, pitch %u, width %u, height %u, buf_len %u", x, y, desc->pitch,
desc->width, desc->height, buf_len);
uint8_t x_offset = x + config->segment_offset;
uint8_t cmd_buf[] = {
SH1107_SET_LOWER_COL_ADDRESS |
(x_offset & SH1107_SET_LOWER_COL_ADDRESS_MASK),
SH1107_SET_HIGHER_COL_ADDRESS |
((x_offset >> 4) & SH1107_SET_LOWER_COL_ADDRESS_MASK),
SH1107_SET_PAGE_START_ADDRESS | (y / 8)
};
uint8_t *buf_ptr = (uint8_t *)buf;
for (uint8_t n = 0; n < desc->height / 8; n++) {
cmd_buf[sizeof(cmd_buf) - 1] =
SH1107_SET_PAGE_START_ADDRESS | (n + (y / 8));
LOG_HEXDUMP_DBG(cmd_buf, sizeof(cmd_buf), "cmd_buf");
if (sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true)) {
return -1;
}
if (sh1107_write_bus(dev, buf_ptr, desc->width, false)) {
return -1;
}
buf_ptr = buf_ptr + desc->width;
if (buf_ptr > ((uint8_t *)buf + buf_len)) {
LOG_ERR("Exceeded buffer length");
return -1;
}
}
return 0;
}
static int sh1107_set_contrast(const struct device *dev, const uint8_t contrast)
{
uint8_t cmd_buf[] = {
SH1107_SET_CONTRAST_CTRL,
contrast,
};
return sh1107_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
}
static void sh1107_get_capabilities(const struct device *dev,
struct display_capabilities *caps)
{
const struct sh1107_config *config = dev->config;
struct sh1107_data *data = dev->data;
caps->x_resolution = config->width;
caps->y_resolution = config->height;
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10 | PIXEL_FORMAT_MONO01;
caps->current_pixel_format = data->pf;
caps->screen_info = SCREEN_INFO_MONO_VTILED;
caps->current_orientation = DISPLAY_ORIENTATION_NORMAL;
}
static int sh1107_set_pixel_format(const struct device *dev,
const enum display_pixel_format pf)
{
struct sh1107_data *data = dev->data;
uint8_t cmd;
int ret;
if (pf == data->pf) {
return 0;
}
if (pf == PIXEL_FORMAT_MONO10) {
cmd = SH1107_SET_REVERSE_DISPLAY;
} else if (pf == PIXEL_FORMAT_MONO01) {
cmd = SH1107_SET_NORMAL_DISPLAY;
} else {
LOG_WRN("Unsupported pixel format");
return -ENOTSUP;
}
ret = sh1107_write_bus(dev, &cmd, 1, true);
if (ret) {
return ret;
}
data->pf = pf;
return 0;
}
static int sh1107_init_device(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
int ret;
/* RESET pulse */
if (config->reset.port) {
gpio_pin_set_dt(&config->reset, 0);
k_msleep(10);
gpio_pin_set_dt(&config->reset, 1);
k_msleep(10);
gpio_pin_set_dt(&config->reset, 0);
k_msleep(10);
}
/* === SH1107 Initial Command Sequence === */
uint8_t init_cmds[] = {
SH1107_DISPLAY_OFF,
SH1107_SET_CLOCK_DIV_RATIO, 0x51,
SH1107_SET_MEM_ADDRESSING_MODE,
SH1107_SET_CONTRAST_CTRL, 0x4F,
SH1106_SET_DCDC_MODE, 0x8A,
SH1107_SET_SEGMENT_MAP_NORMAL,
SH1107_SET_COM_OUTPUT_SCAN_NORMAL,
SH1107_SET_START_LINE, 0x00,
SH1107_SET_DISPLAY_OFFSET, 0x60,
SH1107_SET_CHARGE_PERIOD, 0x22,
SH1107_SET_VCOM_DESELECT_LEVEL, 0x35,
SH1107_SET_MULTIPLEX_RATIO, 0x3F,
SH1107_SET_ENTIRE_DISPLAY_OFF,
SH1107_SET_NORMAL_DISPLAY,
};
ret = sh1107_write_bus(dev, init_cmds, sizeof(init_cmds), true);
if (ret) return ret;
/* === Additional 128x128 setup === */
uint8_t init_128x128[] = {
SH1107_SET_DISPLAY_OFFSET, config->display_offset,
SH1107_SET_MULTIPLEX_RATIO, 0x7F,
};
ret = sh1107_write_bus(dev, init_128x128, sizeof(init_128x128), true);
if (ret) return ret;
k_msleep(100);
uint8_t disp_on = SH1107_DISPLAY_ON;
ret = sh1107_write_bus(dev, &disp_on, 1, true);
return ret;
}
static int sh1107_init(const struct device *dev)
{
const struct sh1107_config *config = dev->config;
int ret;
k_sleep(K_TIMEOUT_ABS_MS(config->ready_time_ms));
if (!sh1107_bus_ready(dev)) {
LOG_ERR("Bus device %s not ready!", config->bus_name(dev));
return -EINVAL;
}
if (config->supply.port) {
ret = gpio_pin_configure_dt(&config->supply,
GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
return ret;
}
if (!gpio_is_ready_dt(&config->supply)) {
LOG_ERR("Supply GPIO device not ready");
return -ENODEV;
}
}
if (config->reset.port) {
ret = gpio_pin_configure_dt(&config->reset,
GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
return ret;
}
if (!gpio_is_ready_dt(&config->reset)) {
LOG_ERR("Reset GPIO device not ready");
return -ENODEV;
}
}
if (sh1107_init_device(dev)) {
LOG_ERR("Failed to initialize device!");
return -EIO;
}
return 0;
}
static DEVICE_API(display, sh1107_driver_api) = {
.blanking_on = sh1107_suspend,
.blanking_off = sh1107_resume,
.write = sh1107_write,
.set_contrast = sh1107_set_contrast,
.get_capabilities = sh1107_get_capabilities,
.set_pixel_format = sh1107_set_pixel_format,
};
#define SH1107_CONFIG_SPI(node_id) \
.bus = {.spi = SPI_DT_SPEC_GET( \
node_id, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0)}, \
.bus_ready = sh1107_bus_ready_spi, \
.write_bus = sh1107_write_bus_spi, \
.bus_name = sh1107_bus_name_spi, \
.data_cmd = GPIO_DT_SPEC_GET(node_id, data_cmd_gpios),
#define SH1107_CONFIG_I2C(node_id) \
.bus = {.i2c = I2C_DT_SPEC_GET(node_id)}, \
.bus_ready = sh1107_bus_ready_i2c, \
.write_bus = sh1107_write_bus_i2c, \
.bus_name = sh1107_bus_name_i2c, \
.data_cmd = {0},
#define SH1107_DEFINE(node_id) \
static struct sh1107_data data##node_id; \
static const struct sh1107_config config##node_id = { \
.reset = GPIO_DT_SPEC_GET_OR(node_id, reset_gpios, {0}), \
.supply = GPIO_DT_SPEC_GET_OR(node_id, supply_gpios, {0}), \
.height = DT_PROP(node_id, height), \
.width = DT_PROP(node_id, width), \
.segment_offset = DT_PROP(node_id, segment_offset), \
.page_offset = DT_PROP(node_id, page_offset), \
.display_offset = DT_PROP(node_id, display_offset), \
.multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
.segment_remap = DT_PROP(node_id, segment_remap), \
.com_invdir = DT_PROP(node_id, com_invdir), \
.com_sequential = DT_PROP(node_id, com_sequential), \
.prechargep = DT_PROP(node_id, prechargep), \
.color_inversion = DT_PROP(node_id, inversion_on), \
.ssd1309_compatible = DT_NODE_HAS_COMPAT(node_id, solomon_ssd1309fb), \
.sh1106_compatible = DT_NODE_HAS_COMPAT(node_id, sinowealth_sh1106), \
.ready_time_ms = DT_PROP(node_id, ready_time_ms), \
.use_internal_iref = DT_PROP(node_id, use_internal_iref), \
COND_CODE_1(DT_ON_BUS(node_id, spi), (SH1107_CONFIG_SPI(node_id)), \
(SH1107_CONFIG_I2C(node_id))) \
}; \
\
DEVICE_DT_DEFINE(node_id, sh1107_init, NULL, &data##node_id, &config##node_id, \
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &sh1107_driver_api);
DT_FOREACH_STATUS_OKAY(sinowealth_sh1107, SH1107_DEFINE)
2. ビルドシステム用の設定ファイルを作成
Zephryのビルドシステムで自作ドライバを認識させるため、modules以下の各ディレクトリにCMakeLists.txt, Kconfigを作成する。
modules/sh1107/drivers/display/sh1107 以下
zephyr_library()
zephyr_library_sources(sh1107.c)
zephyr_library_include_directories(.)
config SH1107
bool "SH1107 display driver"
default n
depends on DT_HAS_SINOWEALTH_SH1107_ENABLED
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_SINOWEALTH_SH1107),i2c)
select SPI if $(dt_compat_on_bus,$(DT_COMPAT_SINOWEALTH_SH1107),spi)
help
Enable driver for SH1107 display driver.
if SH1107
config SH1107_DEFAULT_CONTRAST
int "SH1107 default contrast"
default 47
range 0 $(UINT8_MAX)
help
SH1107 default contrast.
endif # SH1107
modules/sh1107/drivers/display 以下
add_subdirectory_ifdef(CONFIG_SH1107 sh1107)
rsource "sh1107/Kconfig"
modules/sh1107/drivers 以下
add_subdirectory(display)
zephyr_include_directories(display)
rsource "display/Kconfig"
modules/sh1107 以下
add_subdirectory(drivers)
zephyr_include_directories(drivers)
rsource "drivers/Kconfig"
3. バインディングファイルを作成
続いて、デバイスツリーで自作ドライバを認識させるため、以下のディレクトリ構成でバインディングファイルを作成する。
modules/
sh1107/
bindings/
display/
sinowealth,sh1107-spi.yaml
sinowealth,sh1107-spi.yaml
description: SH1107 128x128 dot-matrix display controller on SPI bus
compatible: "sinowealth,sh1107"
include: ["solomon,ssd1306fb-common.yaml", "spi-device.yaml"]
properties:
data-cmd-gpios:
type: phandle-array
required: true
description: D/C# pin.
4. モジュール定義ファイルを作成
最後に、以下ディレクトリ構成でモジュール定義ファイルを作成する。
modules/
zephyr/
module.yml
module.yml
name: sh1107
build:
cmake: .
kconfig: Kconfig
settings:
dts_root: .
アプリケーションコード
自作ドライバを利用するための設定
zephyr/CMakeLists.txt
cmake_minimum_required(VERSION 3.13.1)
set(ZEPHYR_EXTRA_MODULES "${CMAKE_SOURCE_DIR}/../modules/sh1107")
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(nucleof103rb_zephyr)
FILE(GLOB app_sources ../src/*.c*)
target_sources(app PRIVATE ${app_sources})
zephyr/prj.conf
CONFIG_LOG=y
CONFIG_SPI=y
CONFIG_DISPLAY=y
CONFIG_SH1107=y
CONFIG_SH1107_DEFAULT_CONTRAST=47
CONFIG_CHARACTER_FRAMEBUFFER=y
CONFIG_NEWLIB_LIBC=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_HEAP_MEM_POOL_SIZE=4096
CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=4096
zephyr/boards/nucleo_f103rb.overlay
&spi1 {
status = "okay";
pinctrl-0 = <&spi1_sck_master_pa5 &spi1_miso_master_pa6 &spi1_mosi_master_pa7>;
pinctrl-names = "default";
cs-gpios = <&gpiob 6 (GPIO_ACTIVE_LOW)>;
oled: sh1107@0 {
compatible = "sinowealth,sh1107";
reg = <0>;
spi-max-frequency = <10000000>;
width = <128>;
height = <128>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0x00>;
multiplex-ratio = <0x7F>;
prechargep = <0x22>;
segment-remap;
com-invdir;
inversion-on;
reset-gpios = <&gpioa 8 GPIO_ACTIVE_LOW>;
data-cmd-gpios = <&gpioa 9 GPIO_ACTIVE_HIGH>;
ready-time-ms = <100>;
};
};
OLED表示のサンプルコード
動作確認にあたっては以下動画を参考に進めた。
src/logo_image.h
上記動画の概要欄から取得したものをベースとし、128x128の解像度に合わせて配列サイズを2048バイトに変更している。
#ifndef __LOGO_IMAGE_H__
#define __LOGO_IMAGE_H__
#include <zephyr/kernel.h>
uint8_t buf[2048] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00,
0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfc, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0x1f, 0x1f, 0x0f, 0x0f, 0x00,
0x00, 0x0f, 0x0f, 0x1f, 0x1f, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfc, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf8, 0xfe, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x86, 0x86, 0x86, 0x86, 0x86,
0x86, 0x86, 0x86, 0x86, 0x86, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfe, 0xf8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x21, 0x21, 0x21, 0x21, 0x21,
0x21, 0x21, 0x21, 0x21, 0x21, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1f, 0x7f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18,
0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x7f, 0x1f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x03, 0x07, 0x0f, 0x0f, 0x1f, 0x1f, 0x3f, 0x3f, 0x3f, 0x7e, 0x7e, 0x7c, 0x7c, 0x00,
0x00, 0x7c, 0x7c, 0x7e, 0x7e, 0x3f, 0x3f, 0x3f, 0x1f, 0x1f, 0x0f, 0x0f, 0x07, 0x03, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0xf0, 0x10,
0x10, 0x00, 0xf0, 0x80, 0x80, 0x80, 0xf0, 0x00, 0xf0, 0x90, 0x90, 0x90, 0x10, 0x00, 0x00, 0x00,
0xf0, 0x90, 0x90, 0x90, 0x60, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0,
0x00, 0xf0, 0x90, 0x90, 0x90, 0x60, 0x00, 0x00, 0x00, 0xf0, 0x90, 0x90, 0x90, 0x60, 0x00, 0xf0,
0x90, 0x90, 0x10, 0x10, 0x00, 0x60, 0x90, 0x90, 0x90, 0x10, 0x00, 0xf0, 0x00, 0x60, 0x90, 0x90,
0x90, 0x10, 0x00, 0x10, 0x10, 0xf0, 0x10, 0x10, 0x00, 0xe0, 0x10, 0x10, 0x10, 0xe0, 0x00, 0xf0,
0x90, 0x90, 0x90, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x04, 0x04, 0x03, 0x00, 0x07, 0x04, 0x04, 0x04,
0x04, 0x00, 0x07, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x04, 0x04, 0x03,
0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x01, 0x06, 0x00, 0x07,
0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x03, 0x00, 0x07, 0x00, 0x04, 0x04, 0x04,
0x04, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x04, 0x04, 0x04, 0x03, 0x00, 0x07,
0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
#endif /* __LOGO_IMAGE_H__ */
src/main.c
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/display.h>
#include <zephyr/display/cfb.h>
#include <zephyr/logging/log.h>
#include "logo_image.h"
#define DISPLAY_BUFFER_PITCH 128
LOG_MODULE_REGISTER(display);
static const struct device *display = DEVICE_DT_GET(DT_NODELABEL(oled));
int main(void)
{
printk("Debug start\n");
if (display == NULL)
{
LOG_ERR("device pointer is NULL");
return 0;
}
if (!device_is_ready(display))
{
LOG_ERR("display device is not ready");
return 0;
}
printk("Device is ready.\n");
#if 1
struct display_capabilities capabilities;
display_get_capabilities(display, &capabilities);
LOG_INF("Display resolution: %ux%u", capabilities.x_resolution, capabilities.y_resolution);
LOG_INF("Display scrreen info: %u", capabilities.screen_info);
LOG_INF("Display supported pixel formats: 0x%08x", capabilities.supported_pixel_formats);
LOG_INF("Display pixel format: %u", capabilities.current_pixel_format);
LOG_INF("Display orientation: %u", capabilities.current_orientation);
const struct display_buffer_descriptor buf_desc = {
.width = capabilities.x_resolution,
.height = capabilities.y_resolution,
.buf_size = capabilities.x_resolution * capabilities.y_resolution,
.pitch = DISPLAY_BUFFER_PITCH,
};
if (display_write(display, 0, 0, &buf_desc, buf) != 0)
{
LOG_ERR("Failed to write to display");
return 0;
};
#endif
#if 0
int ret;
ret = cfb_framebuffer_init(display);
if (ret != 0)
{
LOG_ERR("cfb_framebuffer_init failed: %d", ret);
return 0;
}
printk("Framebuffer initialized.\n");
ret = cfb_print(display, "Hello,world!", 0, 64);
if (ret != 0)
{
LOG_ERR("cfb_print failed: %d", ret);
return 0;
}
printk("Printed to framebuffer.\n");
ret = cfb_framebuffer_finalize(display);
if (ret != 0)
{
LOG_ERR("cfb_framebuffer_finalize failed: %d", ret);
return 0;
}
printk("Framebuffer finalized.\n");
#endif
while (1)
{
k_sleep(K_MSEC(200));
}
return 0;
}
おわりに
以上、SH1107コントローラ用のZephyrディスプレイドライバを自作し、Nucleo-F103RBボードで動作させる手順を紹介した。
今回もドライバを自作することとなり、なかなか骨の折れる作業であった。
ZephyrではLVGLなどのGUIライブラリも利用できるようなので、今後はそちらも試してみたいと思う。
Discussion