mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
04c3facd8e
For the parallel interface support the following changes were made: 1. Additional `lcd_*` pseudomodules are defined to control whether SPI serial or MCU 8080 8-/16-bit parallel interface is used. 2. The low level function implementation was extended for parallel interfaces so that the now can use either the SPI serial interface or the MCU 8080 8-/16-bit parallel interface. Using the `lcc_*` modules, either the SPI serial interface or the MCU 8080 8-/16-bit interface or even both for multiple display can be used simultaneously.
493 lines
14 KiB
C
493 lines
14 KiB
C
/*
|
|
* Copyright (C) 2018 Koen Zandberg
|
|
* 2021 Francisco Molina
|
|
* 2023 Gunar Schorcht
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_lcd
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver implementation for the lcd display controller
|
|
*
|
|
* @author Koen Zandberg <koen@bergzand.net>
|
|
* @author Francisco Molina <francois-xavier.molina@inria.fr>
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include "byteorder.h"
|
|
#include "kernel_defines.h"
|
|
#include "ztimer.h"
|
|
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
#include "periph/spi.h"
|
|
#endif
|
|
|
|
#include "lcd.h"
|
|
#include "lcd_internal.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
|
|
#if MODULE_LCD_PARALLEL_16BIT
|
|
#error "MCU 8080 16-bit parallel interface is not supported yet"
|
|
#endif
|
|
|
|
static void lcd_ll_par_write_byte(lcd_t *dev, bool cont, uint8_t out)
|
|
{
|
|
if (gpio_is_valid(dev->params->cs_pin)) {
|
|
gpio_clear(dev->params->cs_pin);
|
|
}
|
|
|
|
gpio_clear(dev->params->wrx_pin);
|
|
|
|
gpio_write(dev->params->d0_pin, out & 0x01);
|
|
gpio_write(dev->params->d1_pin, out & 0x02);
|
|
gpio_write(dev->params->d2_pin, out & 0x04);
|
|
gpio_write(dev->params->d3_pin, out & 0x08);
|
|
gpio_write(dev->params->d4_pin, out & 0x10);
|
|
gpio_write(dev->params->d5_pin, out & 0x20);
|
|
gpio_write(dev->params->d6_pin, out & 0x40);
|
|
gpio_write(dev->params->d7_pin, out & 0x80);
|
|
|
|
gpio_set(dev->params->wrx_pin);
|
|
|
|
if (gpio_is_valid(dev->params->cs_pin) && !cont) {
|
|
gpio_set(dev->params->cs_pin);
|
|
};
|
|
}
|
|
|
|
static uint8_t lcd_ll_par_read_byte(lcd_t *dev, bool cont)
|
|
{
|
|
uint8_t in = 0;
|
|
|
|
if (gpio_is_valid(dev->params->cs_pin)) {
|
|
gpio_clear(dev->params->cs_pin);
|
|
}
|
|
|
|
gpio_clear(dev->params->rdx_pin);
|
|
|
|
in |= gpio_read(dev->params->d0_pin) ? 0x01 : 0;
|
|
in |= gpio_read(dev->params->d1_pin) ? 0x02 : 0;
|
|
in |= gpio_read(dev->params->d2_pin) ? 0x04 : 0;
|
|
in |= gpio_read(dev->params->d3_pin) ? 0x08 : 0;
|
|
in |= gpio_read(dev->params->d4_pin) ? 0x10 : 0;
|
|
in |= gpio_read(dev->params->d5_pin) ? 0x20 : 0;
|
|
in |= gpio_read(dev->params->d6_pin) ? 0x40 : 0;
|
|
in |= gpio_read(dev->params->d7_pin) ? 0x80 : 0;
|
|
|
|
gpio_set(dev->params->rdx_pin);
|
|
|
|
if (gpio_is_valid(dev->params->cs_pin) && !cont) {
|
|
gpio_set(dev->params->cs_pin);
|
|
};
|
|
|
|
return in;
|
|
}
|
|
|
|
static void lcd_ll_par_read_bytes(lcd_t *dev, bool cont,
|
|
void *data, size_t len)
|
|
{
|
|
assert(len);
|
|
|
|
uint8_t *data_in = data;
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
data_in[i] = lcd_ll_par_read_byte(dev, i == (len - 1) ? cont : true);
|
|
}
|
|
}
|
|
|
|
static void lcd_ll_par_write_bytes(lcd_t *dev, bool cont,
|
|
const void *data, size_t len)
|
|
{
|
|
assert(len);
|
|
|
|
const uint8_t *data_out = data;
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
lcd_ll_par_write_byte(dev, i == (len - 1) ? cont : true, data_out[i]);
|
|
}
|
|
}
|
|
|
|
#endif /* IS_USED(MODULE_LCD_PARALLEL) */
|
|
|
|
static inline void lcd_ll_write_byte(lcd_t *dev, bool cont, uint8_t data)
|
|
{
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
if (dev->params->spi != SPI_UNDEF) {
|
|
/* SPI serial interface is used */
|
|
spi_transfer_byte(dev->params->spi, dev->params->cs_pin, cont, data);
|
|
}
|
|
else {
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
/* MCU 8080 8-/16-bit parallel interface is used */
|
|
lcd_ll_par_write_byte(dev, cont, data);
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static inline void lcd_ll_write_bytes(lcd_t *dev, bool cont,
|
|
const void *data, size_t len)
|
|
{
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
if (dev->params->spi != SPI_UNDEF) {
|
|
/* SPI serial interface is used */
|
|
spi_transfer_bytes(dev->params->spi,
|
|
dev->params->cs_pin, cont, data, NULL, len);
|
|
}
|
|
else {
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
/* MCU 8080 8-/16-bit parallel interface is used */
|
|
lcd_ll_par_write_bytes(dev, cont, data, len);
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static inline void lcd_ll_read_bytes(lcd_t *dev, bool cont,
|
|
void *data, size_t len)
|
|
{
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
if (dev->params->spi != SPI_UNDEF) {
|
|
/* SPI serial interface is used */
|
|
/* Dummy read */
|
|
spi_transfer_byte(dev->params->spi,
|
|
dev->params->cs_pin, true, 0x00);
|
|
spi_transfer_bytes(dev->params->spi,
|
|
dev->params->cs_pin, cont, NULL, data, len);
|
|
}
|
|
else {
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
/* MCU 8080 8-/16-bit parallel interface is used */
|
|
|
|
/* switch GPIO mode to input */
|
|
gpio_init(dev->params->d0_pin, GPIO_IN);
|
|
gpio_init(dev->params->d1_pin, GPIO_IN);
|
|
gpio_init(dev->params->d2_pin, GPIO_IN);
|
|
gpio_init(dev->params->d3_pin, GPIO_IN);
|
|
gpio_init(dev->params->d4_pin, GPIO_IN);
|
|
gpio_init(dev->params->d5_pin, GPIO_IN);
|
|
gpio_init(dev->params->d6_pin, GPIO_IN);
|
|
gpio_init(dev->params->d7_pin, GPIO_IN);
|
|
|
|
/* Dummy read */
|
|
lcd_ll_par_read_byte(dev, true);
|
|
lcd_ll_par_read_bytes(dev, cont, data, len);
|
|
|
|
/* switch GPIO mode back to output */
|
|
gpio_init(dev->params->d0_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d1_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d2_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d3_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d4_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d5_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d6_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d7_pin, GPIO_OUT);
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void lcd_ll_cmd_start(lcd_t *dev, uint8_t cmd, bool cont)
|
|
{
|
|
gpio_clear(dev->params->dcx_pin);
|
|
lcd_ll_write_byte(dev, cont, cmd);
|
|
gpio_set(dev->params->dcx_pin);
|
|
}
|
|
|
|
static void lcd_ll_set_area_default(lcd_t *dev, uint16_t x1, uint16_t x2,
|
|
uint16_t y1, uint16_t y2)
|
|
{
|
|
be_uint16_t params[2];
|
|
|
|
x1 += dev->params->offset_x;
|
|
x2 += dev->params->offset_x;
|
|
y1 += dev->params->offset_y;
|
|
y2 += dev->params->offset_y;
|
|
|
|
/* Function is called by a high level function of the LCD driver where
|
|
* the device is already acquired. So we don't must acquire it here.
|
|
* Therefore the low level write command function is called. */
|
|
|
|
params[0] = byteorder_htons(x1);
|
|
params[1] = byteorder_htons(x2);
|
|
lcd_ll_write_cmd(dev, LCD_CMD_CASET, (uint8_t *)params,
|
|
sizeof(params));
|
|
params[0] = byteorder_htons(y1);
|
|
params[1] = byteorder_htons(y2);
|
|
lcd_ll_write_cmd(dev, LCD_CMD_PASET, (uint8_t *)params,
|
|
sizeof(params));
|
|
}
|
|
|
|
static void lcd_ll_set_area(lcd_t *dev, uint16_t x1, uint16_t x2,
|
|
uint16_t y1, uint16_t y2)
|
|
{
|
|
if (dev->driver->set_area) {
|
|
dev->driver->set_area(dev, x1, x2, y1, y2);
|
|
}
|
|
else {
|
|
lcd_ll_set_area_default(dev, x1, x2, y1, y2);
|
|
}
|
|
}
|
|
|
|
void lcd_ll_acquire(lcd_t *dev)
|
|
{
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
if (dev->params->spi != SPI_UNDEF) {
|
|
/* SPI serial interface is used */
|
|
spi_acquire(dev->params->spi, dev->params->cs_pin,
|
|
dev->params->spi_mode, dev->params->spi_clk);
|
|
}
|
|
else {
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
/* MCU 8080 8-/16-bit parallel interface is used */
|
|
mutex_lock(&dev->lock);
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void lcd_ll_release(lcd_t *dev)
|
|
{
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
if (dev->params->spi != SPI_UNDEF) {
|
|
/* SPI serial interface is used */
|
|
spi_release(dev->params->spi);
|
|
}
|
|
else {
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
/* MCU 8080 8-/16-bit parallel interface is used */
|
|
mutex_unlock(&dev->lock);
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void lcd_ll_write_cmd(lcd_t *dev, uint8_t cmd, const uint8_t *data,
|
|
size_t len)
|
|
{
|
|
lcd_ll_cmd_start(dev, cmd, len ? true : false);
|
|
if (len) {
|
|
lcd_ll_write_bytes(dev, false, data, len);
|
|
}
|
|
}
|
|
|
|
void lcd_ll_read_cmd(lcd_t *dev, uint8_t cmd, uint8_t *data, size_t len)
|
|
{
|
|
assert(len);
|
|
lcd_ll_cmd_start(dev, cmd, len ? true : false);
|
|
lcd_ll_read_bytes(dev, false, data, len);
|
|
}
|
|
|
|
int lcd_init(lcd_t *dev, const lcd_params_t *params)
|
|
{
|
|
dev->params = params;
|
|
|
|
assert(gpio_is_valid(dev->params->dcx_pin));
|
|
gpio_init(dev->params->dcx_pin, GPIO_OUT);
|
|
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
if (dev->params->spi != SPI_UNDEF) {
|
|
/* SPI serial interface is used */
|
|
int res = spi_init_cs(dev->params->spi, dev->params->cs_pin);
|
|
|
|
if (res != SPI_OK) {
|
|
DEBUG("[st7735] init: error initializing the CS pin [%i]\n", res);
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
/* MCU 8080 8-/16-bit parallel interface is used */
|
|
if (gpio_is_valid(dev->params->cs_pin)) {
|
|
gpio_init(dev->params->cs_pin, GPIO_OUT);
|
|
gpio_set(dev->params->cs_pin);
|
|
}
|
|
|
|
assert(gpio_is_valid(dev->params->wrx_pin));
|
|
gpio_init(dev->params->wrx_pin, GPIO_OUT);
|
|
gpio_set(dev->params->wrx_pin);
|
|
|
|
if (gpio_is_valid(dev->params->wrx_pin)) {
|
|
gpio_init(dev->params->wrx_pin, GPIO_OUT);
|
|
gpio_set(dev->params->wrx_pin);
|
|
}
|
|
|
|
if (gpio_is_valid(dev->params->rdx_pin)) {
|
|
gpio_init(dev->params->rdx_pin, GPIO_OUT);
|
|
gpio_set(dev->params->rdx_pin);
|
|
}
|
|
|
|
assert(gpio_is_valid(dev->params->d0_pin));
|
|
assert(gpio_is_valid(dev->params->d1_pin));
|
|
assert(gpio_is_valid(dev->params->d2_pin));
|
|
assert(gpio_is_valid(dev->params->d3_pin));
|
|
assert(gpio_is_valid(dev->params->d4_pin));
|
|
assert(gpio_is_valid(dev->params->d5_pin));
|
|
assert(gpio_is_valid(dev->params->d6_pin));
|
|
assert(gpio_is_valid(dev->params->d7_pin));
|
|
gpio_init(dev->params->d0_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d1_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d2_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d3_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d4_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d5_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d6_pin, GPIO_OUT);
|
|
gpio_init(dev->params->d7_pin, GPIO_OUT);
|
|
#else
|
|
assert(false);
|
|
#endif
|
|
#if IS_USED(MODULE_LCD_SPI)
|
|
}
|
|
#endif
|
|
|
|
if (gpio_is_valid(dev->params->rst_pin)) {
|
|
gpio_init(dev->params->rst_pin, GPIO_OUT);
|
|
gpio_clear(dev->params->rst_pin);
|
|
ztimer_sleep(ZTIMER_MSEC, 120);
|
|
gpio_set(dev->params->rst_pin);
|
|
}
|
|
ztimer_sleep(ZTIMER_MSEC, 120);
|
|
|
|
#if IS_USED(MODULE_LCD_PARALLEL)
|
|
mutex_init(&dev->lock);
|
|
#endif
|
|
|
|
/* controller-specific init function has to be defined */
|
|
assert(dev->driver->init);
|
|
return dev->driver->init(dev, params);
|
|
}
|
|
|
|
void lcd_write_cmd(lcd_t *dev, uint8_t cmd, const uint8_t *data,
|
|
size_t len)
|
|
{
|
|
lcd_ll_acquire(dev);
|
|
lcd_ll_write_cmd(dev, cmd, data, len);
|
|
lcd_ll_release(dev);
|
|
}
|
|
|
|
void lcd_read_cmd(lcd_t *dev, uint8_t cmd, uint8_t *data, size_t len)
|
|
{
|
|
lcd_ll_acquire(dev);
|
|
lcd_ll_read_cmd(dev, cmd, data, len);
|
|
lcd_ll_release(dev);
|
|
}
|
|
|
|
void lcd_fill(lcd_t *dev, uint16_t x1, uint16_t x2, uint16_t y1,
|
|
uint16_t y2, uint16_t color)
|
|
{
|
|
/* Send fill area to the display */
|
|
|
|
/* Calculate number of pixels */
|
|
int32_t num_pix = (x2 - x1 + 1) * (y2 - y1 + 1);
|
|
|
|
DEBUG("[lcd]: Write x1: %" PRIu16 ", x2: %" PRIu16 ", "
|
|
"y1: %" PRIu16 ", y2: %" PRIu16 ". Num pixels: %lu\n",
|
|
x1, x2, y1, y2, (unsigned long)num_pix);
|
|
|
|
/* Send fill area to the display */
|
|
lcd_ll_acquire(dev);
|
|
|
|
lcd_ll_set_area(dev, x1, x2, y1, y2);
|
|
/* Memory access command */
|
|
lcd_ll_cmd_start(dev, LCD_CMD_RAMWR, true);
|
|
|
|
if (IS_ACTIVE(CONFIG_LCD_LE_MODE)) {
|
|
color = htons(color);
|
|
}
|
|
|
|
for (int i = 0; i < (num_pix - 1); i++) {
|
|
lcd_ll_write_bytes(dev, true, (uint8_t *)&color, sizeof(color));
|
|
}
|
|
lcd_ll_write_bytes(dev, false, (uint8_t *)&color, sizeof(color));
|
|
lcd_ll_release(dev);
|
|
}
|
|
|
|
void lcd_pixmap(lcd_t *dev, uint16_t x1, uint16_t x2,
|
|
uint16_t y1, uint16_t y2, const uint16_t *color)
|
|
{
|
|
size_t num_pix = (x2 - x1 + 1) * (y2 - y1 + 1);
|
|
|
|
DEBUG("[lcd]: Write x1: %" PRIu16 ", x2: %" PRIu16 ", "
|
|
"y1: %" PRIu16 ", y2: %" PRIu16 ". Num pixels: %lu\n",
|
|
x1, x2, y1, y2, (unsigned long)num_pix);
|
|
|
|
lcd_ll_acquire(dev);
|
|
|
|
/* Send fill area to the display */
|
|
lcd_ll_set_area(dev, x1, x2, y1, y2);
|
|
|
|
/* Memory access command */
|
|
lcd_ll_cmd_start(dev, LCD_CMD_RAMWR, true);
|
|
|
|
if (IS_ACTIVE(CONFIG_LCD_LE_MODE)) {
|
|
for (size_t i = 0; i < num_pix - 1; i++) {
|
|
uint16_t ncolor = htons(*(color + i));
|
|
lcd_ll_write_bytes(dev, true, &ncolor, sizeof(uint16_t));
|
|
}
|
|
uint16_t ncolor = htons(*(color + num_pix - 1));
|
|
lcd_ll_write_bytes(dev, false, &ncolor, sizeof(uint16_t));
|
|
}
|
|
else {
|
|
lcd_ll_write_bytes(dev, false, (const uint8_t *)color, num_pix * 2);
|
|
}
|
|
|
|
lcd_ll_release(dev);
|
|
}
|
|
|
|
void lcd_invert_on(lcd_t *dev)
|
|
{
|
|
uint8_t command = (dev->params->inverted) ? LCD_CMD_DINVOFF
|
|
: LCD_CMD_DINVON;
|
|
|
|
lcd_write_cmd(dev, command, NULL, 0);
|
|
}
|
|
|
|
void lcd_invert_off(lcd_t *dev)
|
|
{
|
|
uint8_t command = (dev->params->inverted) ? LCD_CMD_DINVON
|
|
: LCD_CMD_DINVOFF;
|
|
|
|
lcd_write_cmd(dev, command, NULL, 0);
|
|
}
|
|
|
|
void lcd_set_brightness(lcd_t *dev, uint8_t brightness)
|
|
{
|
|
lcd_write_cmd(dev, LCD_CMD_WRDISBV, &brightness, 1);
|
|
uint8_t param = 0x26;
|
|
lcd_write_cmd(dev, LCD_CMD_WRCTRLD, ¶m, 1);
|
|
}
|