auxdisplay: Add SerLCD auxdisplay driver
SerLCD is an interface for several lcd character display sold by sparkfun. Signed-off-by: Jan Henke <Jan.Henke@taujhe.de>
This commit is contained in:
parent
1fac5ed2a6
commit
7ca296c016
|
@ -8,3 +8,4 @@ zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_HD44780 auxdisplay_hd44780.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_ITRON auxdisplay_itron.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_JHD1313 auxdisplay_jhd1313.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_PT6314 auxdisplay_pt6314.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_SERLCD auxdisplay_serlcd.c)
|
||||
|
|
|
@ -24,5 +24,6 @@ source "drivers/auxdisplay/Kconfig.hd44780"
|
|||
source "drivers/auxdisplay/Kconfig.itron"
|
||||
source "drivers/auxdisplay/Kconfig.jhd1313"
|
||||
source "drivers/auxdisplay/Kconfig.pt6314"
|
||||
source "drivers/auxdisplay/Kconfig.serlcd"
|
||||
|
||||
endif # AUXDISPLAY
|
||||
|
|
10
drivers/auxdisplay/Kconfig.serlcd
Normal file
10
drivers/auxdisplay/Kconfig.serlcd
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright (c) 2023 Jan Henke <Jan.Henke@taujhe.de>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config AUXDISPLAY_SERLCD
|
||||
bool "SparkFun SerLCD dot character LCD driver"
|
||||
default y
|
||||
select I2C
|
||||
depends on DT_HAS_SPARKFUN_SERLCD_ENABLED
|
||||
help
|
||||
Enable driver for SparkFun SerLCD.
|
436
drivers/auxdisplay/auxdisplay_serlcd.c
Normal file
436
drivers/auxdisplay/auxdisplay_serlcd.c
Normal file
|
@ -0,0 +1,436 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Jan Henke <Jan.Henke@taujhe.de>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT sparkfun_serlcd
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/auxdisplay.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(auxdisplay_serlcd, CONFIG_AUXDISPLAY_LOG_LEVEL);
|
||||
|
||||
/*
|
||||
* | in ASCII, used to begin a display command
|
||||
*/
|
||||
#define SERLCD_BEGIN_COMMAND 0x7C
|
||||
|
||||
/*
|
||||
* special command for the underlying display controller
|
||||
*/
|
||||
#define SERLCD_BEGIN_SPECIAL_COMMAND 0xFE
|
||||
|
||||
/*
|
||||
* delay in milliseconds after a normal command was sent
|
||||
*/
|
||||
#define SERLCD_COMMAND_DELAY_MS 10
|
||||
|
||||
/*
|
||||
* delay in milliseconds after a special command was sent
|
||||
*/
|
||||
#define SERLCD_SPECIAL_COMMAND_DELAY_MS 50
|
||||
|
||||
/*
|
||||
* maximum amount of custom chars the display supports
|
||||
*/
|
||||
#define SERLCD_CUSTOM_CHAR_MAX_COUNT 8
|
||||
|
||||
/*
|
||||
* height of a custom char in bits
|
||||
*/
|
||||
#define SERLCD_CUSTOM_CHAR_HEIGHT 8
|
||||
|
||||
/*
|
||||
* width of a custom char in bits
|
||||
*/
|
||||
#define SERLCD_CUSTOM_CHAR_WIDTH 5
|
||||
|
||||
/*
|
||||
* char code for the first custom char
|
||||
*/
|
||||
#define SERLCD_CUSTOM_CHAR_INDEX_BASE 0x08
|
||||
|
||||
/*
|
||||
* bitmask for custom character detection
|
||||
*/
|
||||
#define SERLCD_CUSTOM_CHAR_BITMASK 0xf8
|
||||
|
||||
/*
|
||||
* bit to set in the display control special command to indicate the display should be powered on
|
||||
*/
|
||||
#define SERLCD_DISPLAY_CONTROL_POWER_BIT BIT(2)
|
||||
|
||||
/*
|
||||
* bit to set in the display control special command to indicate the cursor should be displayed
|
||||
*/
|
||||
#define SERLCD_DISPLAY_CONTROL_CURSOR_BIT BIT(1)
|
||||
|
||||
/*
|
||||
* bit to set in the display control special command to indicate the cursor should be blinking
|
||||
*/
|
||||
#define SERLCD_DISPLAY_CONTROL_BLINKING_BIT BIT(0)
|
||||
|
||||
struct auxdisplay_serlcd_data {
|
||||
bool power;
|
||||
bool cursor;
|
||||
bool blinking;
|
||||
uint16_t cursor_x;
|
||||
uint16_t cursor_y;
|
||||
};
|
||||
|
||||
struct auxdisplay_serlcd_config {
|
||||
struct auxdisplay_capabilities capabilities;
|
||||
struct i2c_dt_spec bus;
|
||||
};
|
||||
|
||||
enum auxdisplay_serlcd_command {
|
||||
SERLCD_COMMAND_SET_CUSTOM_CHAR = 0x1B,
|
||||
SERLCD_COMMAND_WRITE_CUSTOM_CHAR = 0x23,
|
||||
SERLCD_COMMAND_CLEAR = 0x2D,
|
||||
};
|
||||
|
||||
enum auxdisplay_serlcd_special_command {
|
||||
SERLCD_SPECIAL_RETURN_HOME = 0x02,
|
||||
SERLCD_SPECIAL_DISPLAY_CONTROL = 0x08,
|
||||
SERLCD_SPECIAL_SET_DD_RAM_ADDRESS = 0x80,
|
||||
};
|
||||
|
||||
static int auxdisplay_serlcd_send_command(const struct device *dev,
|
||||
const enum auxdisplay_serlcd_command command)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
const uint8_t buffer[2] = {SERLCD_BEGIN_COMMAND, command};
|
||||
|
||||
int rc = i2c_write_dt(&config->bus, buffer, sizeof(buffer));
|
||||
|
||||
k_sleep(K_MSEC(SERLCD_COMMAND_DELAY_MS));
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
auxdisplay_serlcd_send_special_command(const struct device *dev,
|
||||
const enum auxdisplay_serlcd_special_command command)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
const uint8_t buffer[2] = {SERLCD_BEGIN_SPECIAL_COMMAND, command};
|
||||
|
||||
int rc = i2c_write_dt(&config->bus, buffer, sizeof(buffer));
|
||||
|
||||
k_sleep(K_MSEC(SERLCD_SPECIAL_COMMAND_DELAY_MS));
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_send_display_state(const struct device *dev,
|
||||
const struct auxdisplay_serlcd_data *data)
|
||||
{
|
||||
uint8_t command = SERLCD_SPECIAL_DISPLAY_CONTROL;
|
||||
|
||||
if (data->power) {
|
||||
command |= SERLCD_DISPLAY_CONTROL_POWER_BIT;
|
||||
}
|
||||
if (data->cursor) {
|
||||
command |= SERLCD_DISPLAY_CONTROL_CURSOR_BIT;
|
||||
}
|
||||
if (data->blinking) {
|
||||
command |= SERLCD_DISPLAY_CONTROL_BLINKING_BIT;
|
||||
}
|
||||
|
||||
return auxdisplay_serlcd_send_special_command(dev, command);
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_display_on(const struct device *dev)
|
||||
{
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
data->power = true;
|
||||
|
||||
return auxdisplay_serlcd_send_display_state(dev, data);
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_display_off(const struct device *dev)
|
||||
{
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
data->power = false;
|
||||
|
||||
return auxdisplay_serlcd_send_display_state(dev, data);
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_cursor_set_enabled(const struct device *dev, bool enable)
|
||||
{
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
data->cursor = enable;
|
||||
|
||||
return auxdisplay_serlcd_send_display_state(dev, data);
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_position_blinking_set_enabled(const struct device *dev, bool enable)
|
||||
{
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
data->blinking = enable;
|
||||
|
||||
return auxdisplay_serlcd_send_display_state(dev, data);
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_cursor_position_set(const struct device *dev,
|
||||
enum auxdisplay_position type, int16_t x,
|
||||
int16_t y)
|
||||
{
|
||||
static const uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
|
||||
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
const struct auxdisplay_capabilities capabilities = config->capabilities;
|
||||
const uint16_t columns = capabilities.columns;
|
||||
const uint16_t rows = capabilities.rows;
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
if (type == AUXDISPLAY_POSITION_ABSOLUTE) {
|
||||
/*
|
||||
* shortcut for (0,0) position
|
||||
*/
|
||||
if (x == 0 && y == 0) {
|
||||
data->cursor_x = x;
|
||||
data->cursor_y = y;
|
||||
return auxdisplay_serlcd_send_special_command(dev,
|
||||
SERLCD_SPECIAL_RETURN_HOME);
|
||||
}
|
||||
|
||||
/*
|
||||
* bounds checking
|
||||
*/
|
||||
if (x < 0 || x >= columns) {
|
||||
return -EINVAL;
|
||||
}
|
||||
if (y < 0 || y >= rows) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->cursor_x = x;
|
||||
data->cursor_y = y;
|
||||
|
||||
const uint8_t cursor_address = x + row_offsets[y];
|
||||
|
||||
return auxdisplay_serlcd_send_special_command(
|
||||
dev, SERLCD_SPECIAL_SET_DD_RAM_ADDRESS | cursor_address);
|
||||
|
||||
} else if (type == AUXDISPLAY_POSITION_RELATIVE) {
|
||||
/*
|
||||
* clip relative move to display dimensions
|
||||
*/
|
||||
const int new_x = (data->cursor_x + x) % columns;
|
||||
const int new_y = (data->cursor_y + y + x / columns) % rows;
|
||||
const uint16_t column = new_x < 0 ? new_x + columns : new_x;
|
||||
const uint16_t row = new_y < 0 ? new_y + rows : new_y;
|
||||
|
||||
data->cursor_x = column;
|
||||
data->cursor_y = row;
|
||||
|
||||
const uint8_t cursor_address = column + row_offsets[row];
|
||||
|
||||
return auxdisplay_serlcd_send_special_command(
|
||||
dev, SERLCD_SPECIAL_SET_DD_RAM_ADDRESS | cursor_address);
|
||||
}
|
||||
|
||||
/*
|
||||
* other types of movement are not implemented/supported
|
||||
*/
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_cursor_position_get(const struct device *dev, int16_t *x, int16_t *y)
|
||||
{
|
||||
const struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
*x = (int16_t)data->cursor_x;
|
||||
*y = (int16_t)data->cursor_y;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_capabilities_get(const struct device *dev,
|
||||
struct auxdisplay_capabilities *capabilities)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
|
||||
memcpy(capabilities, &config->capabilities, sizeof(struct auxdisplay_capabilities));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_clear(const struct device *dev)
|
||||
{
|
||||
int rc = auxdisplay_serlcd_send_command(dev, SERLCD_COMMAND_CLEAR);
|
||||
|
||||
k_sleep(K_MSEC(SERLCD_COMMAND_DELAY_MS));
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_custom_character_set(const struct device *dev,
|
||||
struct auxdisplay_character *character)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* only indexes 0..7 are supported
|
||||
*/
|
||||
const uint8_t char_index = character->index;
|
||||
|
||||
if (char_index > (SERLCD_CUSTOM_CHAR_MAX_COUNT - 1)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* custom characters are accessible via char codes 0x08..0x0f
|
||||
*/
|
||||
character->character_code = SERLCD_CUSTOM_CHAR_INDEX_BASE | char_index;
|
||||
|
||||
rc = auxdisplay_serlcd_send_command(dev, SERLCD_COMMAND_SET_CUSTOM_CHAR + char_index);
|
||||
if (!rc) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* the display expects the custom character as 8 lines of 5 bit each, shades are not
|
||||
* supported
|
||||
*/
|
||||
for (int l = 0; l < SERLCD_CUSTOM_CHAR_HEIGHT; ++l) {
|
||||
uint8_t buffer = 0;
|
||||
|
||||
for (int i = 0; i < SERLCD_CUSTOM_CHAR_WIDTH; ++i) {
|
||||
if (character->data[(l * 5) + i]) {
|
||||
buffer |= BIT(4 - i);
|
||||
}
|
||||
}
|
||||
rc = i2c_write_dt(&config->bus, &buffer, sizeof(buffer));
|
||||
if (!rc) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void auxdisplay_serlcd_advance_current_position(const struct device *dev)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
++(data->cursor_x);
|
||||
if (data->cursor_x >= config->capabilities.columns) {
|
||||
data->cursor_x = 0;
|
||||
++(data->cursor_y);
|
||||
}
|
||||
if (data->cursor_y >= config->capabilities.rows) {
|
||||
data->cursor_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_write(const struct device *dev, const uint8_t *text, uint16_t len)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
|
||||
int rc = 0;
|
||||
|
||||
/*
|
||||
* the display wraps around by itself, just write the text and update the position data
|
||||
*/
|
||||
for (int i = 0; i < len; ++i) {
|
||||
uint8_t character = text[i];
|
||||
|
||||
/*
|
||||
* customer characters require a special command, so check for custom char
|
||||
*/
|
||||
if ((character & SERLCD_CUSTOM_CHAR_BITMASK) == SERLCD_CUSTOM_CHAR_INDEX_BASE) {
|
||||
const uint8_t command = SERLCD_COMMAND_WRITE_CUSTOM_CHAR +
|
||||
(character & ~SERLCD_CUSTOM_CHAR_BITMASK);
|
||||
|
||||
rc = auxdisplay_serlcd_send_command(dev, command);
|
||||
if (!rc) {
|
||||
return rc;
|
||||
}
|
||||
auxdisplay_serlcd_advance_current_position(dev);
|
||||
} else if (character == SERLCD_BEGIN_COMMAND ||
|
||||
character == SERLCD_BEGIN_SPECIAL_COMMAND) {
|
||||
/*
|
||||
* skip these characters in text, as they have a special meaning, if
|
||||
* required a custom character can be used as replacement
|
||||
*/
|
||||
continue;
|
||||
} else {
|
||||
rc = i2c_write_dt(&config->bus, text, len);
|
||||
if (!rc) {
|
||||
return rc;
|
||||
}
|
||||
auxdisplay_serlcd_advance_current_position(dev);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int auxdisplay_serlcd_init(const struct device *dev)
|
||||
{
|
||||
const struct auxdisplay_serlcd_config *config = dev->config;
|
||||
struct auxdisplay_serlcd_data *data = dev->data;
|
||||
|
||||
/*
|
||||
* Initialize our data structure
|
||||
*/
|
||||
data->power = true;
|
||||
|
||||
if (!device_is_ready(config->bus.bus)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
auxdisplay_serlcd_clear(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct auxdisplay_driver_api auxdisplay_serlcd_auxdisplay_api = {
|
||||
.display_on = auxdisplay_serlcd_display_on,
|
||||
.display_off = auxdisplay_serlcd_display_off,
|
||||
.cursor_set_enabled = auxdisplay_serlcd_cursor_set_enabled,
|
||||
.position_blinking_set_enabled = auxdisplay_serlcd_position_blinking_set_enabled,
|
||||
.cursor_position_set = auxdisplay_serlcd_cursor_position_set,
|
||||
.cursor_position_get = auxdisplay_serlcd_cursor_position_get,
|
||||
.capabilities_get = auxdisplay_serlcd_capabilities_get,
|
||||
.clear = auxdisplay_serlcd_clear,
|
||||
.custom_character_set = auxdisplay_serlcd_custom_character_set,
|
||||
.write = auxdisplay_serlcd_write,
|
||||
};
|
||||
|
||||
#define AUXDISPLAY_SERLCD_INST(inst) \
|
||||
static const struct auxdisplay_serlcd_config auxdisplay_serlcd_config_##inst = { \
|
||||
.capabilities = { \
|
||||
.columns = DT_INST_PROP(inst, columns), \
|
||||
.rows = DT_INST_PROP(inst, rows), \
|
||||
.mode = 0, \
|
||||
.brightness.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
|
||||
.brightness.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
|
||||
.backlight.minimum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
|
||||
.backlight.maximum = AUXDISPLAY_LIGHT_NOT_SUPPORTED, \
|
||||
.custom_characters = SERLCD_CUSTOM_CHAR_MAX_COUNT, \
|
||||
.custom_character_width = SERLCD_CUSTOM_CHAR_WIDTH, \
|
||||
.custom_character_height = SERLCD_CUSTOM_CHAR_HEIGHT, \
|
||||
}, \
|
||||
.bus = I2C_DT_SPEC_INST_GET(inst)}; \
|
||||
\
|
||||
static struct auxdisplay_serlcd_data auxdisplay_serlcd_data_##inst; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(inst, &auxdisplay_serlcd_init, NULL, &auxdisplay_serlcd_data_##inst, \
|
||||
&auxdisplay_serlcd_config_##inst, POST_KERNEL, \
|
||||
CONFIG_AUXDISPLAY_INIT_PRIORITY, &auxdisplay_serlcd_auxdisplay_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_SERLCD_INST)
|
34
dts/bindings/auxdisplay/sparkfun,serlcd.yaml
Normal file
34
dts/bindings/auxdisplay/sparkfun,serlcd.yaml
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Copyright (c) 2023 Jan Henke <Jan.Henke@taujhe.de>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
SparkFun SerLCD Dot Character VFD Controller/Driver IC
|
||||
|
||||
Example:
|
||||
&i2c1 {
|
||||
serlcd@72 {
|
||||
compatible = "sparkfun,serlcd";
|
||||
reg = <0x72>;
|
||||
columns = <16>;
|
||||
rows = <2>;
|
||||
};
|
||||
};
|
||||
|
||||
compatible: "sparkfun,serlcd"
|
||||
|
||||
include: [auxdisplay-device.yaml, i2c-device.yaml]
|
||||
|
||||
properties:
|
||||
columns:
|
||||
type: int
|
||||
default: 16
|
||||
enum:
|
||||
- 16
|
||||
- 20
|
||||
|
||||
rows:
|
||||
type: int
|
||||
default: 2
|
||||
enum:
|
||||
- 2
|
||||
- 4
|
Loading…
Reference in a new issue