diff --git a/boards/peripheral/board.mk b/boards/peripheral/board.mk index f24ee43..9a0246d 100644 --- a/boards/peripheral/board.mk +++ b/boards/peripheral/board.mk @@ -40,3 +40,10 @@ CFLAGS += -DPERIPHERAL_BMI270 BOARD_SOURCE += $(_PERIPHERAL_DIR)/sensor/imu/bmi270.c BOARD_SOURCE += $(WHAL_DIR)/src/sensor/imu/bmi270_sensor.c endif + +ifneq ($(filter sharp_ls013b7dh03,$(PERIPHERALS)),) +CFLAGS += -DPERIPHERAL_SHARP_LS013B7DH03 +BOARD_SOURCE += $(_PERIPHERAL_DIR)/display/sharp_ls013b7dh03.c +BOARD_SOURCE += $(WHAL_DIR)/src/display/sharp_memory_display.c +BOARD_SOURCE += $(WHAL_DIR)/src/display/display.c +endif diff --git a/boards/peripheral/display/sharp_ls013b7dh03.c b/boards/peripheral/display/sharp_ls013b7dh03.c new file mode 100644 index 0000000..aad51f8 --- /dev/null +++ b/boards/peripheral/display/sharp_ls013b7dh03.c @@ -0,0 +1,70 @@ +/* sharp_ls013b7dh03.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include "sharp_ls013b7dh03.h" +#include +#include "board.h" + +/* + * Sharp LS013B7DH03 - 128x128 monochrome memory LCD + * + * - 3-wire SPI (SCLK, SI, SCS), mode 0, up to 1.1 MHz + * - SCS is active high and driven as a plain GPIO (board chip-select pin) + * - COM inversion (VCOM) is supplied externally on EXTCOMIN with EXTMODE tied + * high; here it is driven from the board PWM at 60 Hz, 50% duty + * + * Wired to the board SPI bus and chip-select pin used for SPI peripherals. + */ + +static whal_Spi_ComCfg g_ls013ComCfg = { + .freq = 1000000, /* 1 MHz (panel max 1.1 MHz) */ + .mode = WHAL_SPI_MODE_0, + .wordSz = 8, + .dataLines = 1, +}; + +/* + * EXTCOMIN waveform: 60 Hz, 50% duty. The counts are in the board PWM's + * (prescaled) tick units. On the STM32WB55 Nucleo, LPTIM1 runs at /128 (2 us + * per tick), so 8333 ticks is ~60 Hz. A board whose PWM ticks differently must + * scale these to keep EXTCOMIN within the panel's 54-65 Hz range. + */ +static whal_Pwm_ChannelCfg g_ls013VcomCfg = { + .periodCycles = 8333, + .pulseCycles = 4167, + .pulseCount = WHAL_PWM_PULSE_COUNT_CONTINUOUS, + .polarity = WHAL_PWM_POLARITY_NORMAL, +}; + +whal_Display g_whalSharpLs013b7dh03 = { + .driver = &whal_SharpMemory_Display_Driver, + .cfg = &(whal_SharpMemory_Display_Cfg) { + .spiDev = BOARD_SPI_DEV, + .spiComCfg = &g_ls013ComCfg, + .gpioDev = BOARD_GPIO_DEV, + .csPin = SPI_CS_PIN, + .pwm = BOARD_PWM_DEV, + .pwmChannel = 0, + .vcomCfg = &g_ls013VcomCfg, + .width = 128, + .height = 128, + }, +}; diff --git a/boards/peripheral/display/sharp_ls013b7dh03.h b/boards/peripheral/display/sharp_ls013b7dh03.h new file mode 100644 index 0000000..708ff38 --- /dev/null +++ b/boards/peripheral/display/sharp_ls013b7dh03.h @@ -0,0 +1,30 @@ +/* sharp_ls013b7dh03.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WHAL_PERIPHERAL_SHARP_LS013B7DH03_H +#define WHAL_PERIPHERAL_SHARP_LS013B7DH03_H + +#include + +/* Sharp LS013B7DH03 128x128 memory LCD wired to the board's SPI bus. */ +extern whal_Display g_whalSharpLs013b7dh03; + +#endif /* WHAL_PERIPHERAL_SHARP_LS013B7DH03_H */ diff --git a/boards/stm32wb55xx_nucleo/board.c b/boards/stm32wb55xx_nucleo/board.c index a4ace7e..b149b07 100644 --- a/boards/stm32wb55xx_nucleo/board.c +++ b/boards/stm32wb55xx_nucleo/board.c @@ -55,6 +55,7 @@ static const whal_Stm32wb_Rcc_PeriphClk g_periphClks[] = { {WHAL_STM32WB55_AES1_GATE}, {WHAL_STM32WB55_PKA_GATE}, {WHAL_STM32WB55_I2C1_GATE}, + {WHAL_STM32WB55_LPTIM1_GATE}, #ifdef BOARD_WATCHDOG_WWDG {WHAL_STM32WB55_WWDG_GATE}, #endif @@ -72,6 +73,17 @@ whal_I2c g_whalI2c = { }, }; +/* PWM (LPTIM1): vtable dispatch; /128 prescaler makes one PWM tick = 2 us. */ +whal_Pwm g_whalPwm = { + .base = WHAL_STM32WB55_LPTIM1_BASE, + .driver = WHAL_STM32WB55_LPTIM1_PWM_DRIVER, + .cfg = &(whal_Stm32wb_Lptim_Pwm_Cfg) { + .prescaler = 7, /* /128 */ + .clkSel = WHAL_STM32WB_LPTIM_PWM_CLKSEL_INTERNAL, + .timeout = &g_whalTimeout, + }, +}; + /* SPI */ whal_Spi g_whalSpi = { .base = WHAL_STM32WB55_SPI1_BASE, @@ -242,6 +254,11 @@ whal_Error Board_Init(void) return err; } + err = whal_Pwm_Init(&g_whalPwm); + if (err) { + return err; + } + err = whal_Flash_Init(BOARD_FLASH_DEV); if (err) { return err; @@ -322,6 +339,11 @@ whal_Error Board_Deinit(void) return err; } + err = whal_Pwm_Deinit(&g_whalPwm); + if (err) { + return err; + } + err = whal_I2c_Deinit(&g_whalI2c); if (err) { return err; diff --git a/boards/stm32wb55xx_nucleo/board.h b/boards/stm32wb55xx_nucleo/board.h index a571936..3a01646 100644 --- a/boards/stm32wb55xx_nucleo/board.h +++ b/boards/stm32wb55xx_nucleo/board.h @@ -31,6 +31,7 @@ extern whal_Uart g_whalUart; extern whal_Spi g_whalSpi; extern whal_Crypto g_whalCrypto; extern whal_I2c g_whalI2c; +extern whal_Pwm g_whalPwm; extern whal_Timeout g_whalTimeout; extern volatile uint32_t g_tick; @@ -45,6 +46,7 @@ enum { SPI_CS_PIN, I2C_SCL_PIN, I2C_SDA_PIN, + LPTIM1_OUT_PIN, PIN_COUNT, }; @@ -61,6 +63,7 @@ enum { #define BOARD_UART_DEV (&g_whalUart) #define BOARD_SPI_DEV (&g_whalSpi) #define BOARD_I2C_DEV (&g_whalI2c) +#define BOARD_PWM_DEV (&g_whalPwm) #define BOARD_FLASH_DEV ((whal_Flash *)&whal_Stm32wb_Flash_Dev) #define BOARD_WATCHDOG_DEV WHAL_INTERNAL_DEV #define BOARD_RNG_DEV WHAL_INTERNAL_DEV @@ -112,6 +115,10 @@ enum { WHAL_STM32WB_GPIO_PORT_B, 9, WHAL_STM32WB_GPIO_MODE_ALTFN, \ WHAL_STM32WB_GPIO_OUTTYPE_OPENDRAIN, WHAL_STM32WB_GPIO_SPEED_FAST, \ WHAL_STM32WB_GPIO_PULL_UP, 4), \ + [LPTIM1_OUT_PIN] = WHAL_STM32WB_GPIO_PIN( \ + WHAL_STM32WB_GPIO_PORT_B, 2, WHAL_STM32WB_GPIO_MODE_ALTFN, \ + WHAL_STM32WB_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32WB_GPIO_SPEED_LOW, \ + WHAL_STM32WB_GPIO_PULL_NONE, 1), \ }, \ .pinCount = PIN_COUNT, \ }, \ diff --git a/boards/stm32wb55xx_nucleo/board.mk b/boards/stm32wb55xx_nucleo/board.mk index 18f67fd..e28138a 100644 --- a/boards/stm32wb55xx_nucleo/board.mk +++ b/boards/stm32wb55xx_nucleo/board.mk @@ -22,7 +22,7 @@ _BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))) PLATFORM = stm32wb -TESTS ?= clock gpio flash timer rng aes_ecb aes_cbc aes_ctr aes_gcm aes_gmac aes_ccm pka block uart i2c +TESTS ?= clock gpio flash timer rng aes_ecb aes_cbc aes_ctr aes_gcm aes_gmac aes_ccm pka block uart i2c pwm GCC = $(GCC_PATH)arm-none-eabi-gcc LD = $(GCC_PATH)arm-none-eabi-ld @@ -63,6 +63,7 @@ BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/sensor.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/block.c) +BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/pwm.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/irq/cortex_m4_nvic.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/stm32wb_*.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c) diff --git a/docs/writing_a_driver.md b/docs/writing_a_driver.md index 095f317..de503f2 100644 --- a/docs/writing_a_driver.md +++ b/docs/writing_a_driver.md @@ -983,6 +983,80 @@ Reset the timer counter back to its initialized state. --- +## PWM + +Header: `wolfHAL/pwm/pwm.h` + +The PWM driver generates a pulse-width-modulated output. Each channel's waveform +(frequency, duty, polarity) is described by a `whal_Pwm_ChannelCfg` passed to +Start, which applies it and begins output; Stop halts the channel. Channels are +addressed by a zero-based index, and a driver returns `WHAL_ENOTSUP` for any +channel its hardware does not implement (a single-output peripheral accepts only +channel 0). + +The waveform's `periodCycles` and `pulseCycles` are counted in ticks of the +driver's (prescaled) timer clock, not seconds; the board translates real time +into ticks. On hardware that shares one counter across channels, every channel +of an instance must use the same `periodCycles`. + +### Init + +Configure the instance-static PWM settings (clock source, prescaler, waveform +mode) with the output disabled. The board must enable the peripheral clock +before calling Init. + +### Deinit + +Disable the PWM output and release resources. + +### Start + +Apply a `whal_Pwm_ChannelCfg` to a channel and begin output. The generic +dispatcher rejects a null waveform, a zero period, and +`pulseCycles > periodCycles` with `WHAL_EINVAL`; the driver returns +`WHAL_ENOTSUP` for a channel or waveform its hardware cannot represent. Start is +a repeatable toggle with Stop and does not require a re-Init. + +### Stop + +Halt output on a channel, parking the pin. A subsequent Start reprograms and +resumes the waveform. + +--- + +## Display + +Header: `wolfHAL/display/display.h` + +The display driver provides a bus-agnostic API for pixel-addressable panels. +Each driver implements the vtable and talks to its panel over the appropriate +bus (SPI, parallel, etc.) internally. The pixel format, the byte layout of the +data buffer, and the meaning of a region are defined by each driver rather than +by the generic API. + +### Init + +Bring the panel up: configure the bus session, start any refresh / COM-inversion +source the panel needs, and clear pixel memory to a known state. A driver that +acquires resources here (e.g. a VCOM PWM) should release them on any Init error +path so a failed Init leaves nothing running. + +### Deinit + +Shut the panel down, mirroring Init's bring-up in reverse (e.g. blank the panel +before halting COM inversion, then release the bus). + +### Update + +Push pixel data to the `w` by `h` region whose top-left corner is (`x`, `y`). +The generic dispatcher rejects structural violations (null pointers, a zero-area +region, a null or zero-length data buffer) with `WHAL_EINVAL`; the driver +returns `WHAL_ENOTSUP` for a region its hardware cannot address (e.g. a panel +that can only write whole lines requires a full-width region) and validates +`dataSz` against its own pixel format. + +--- + ## RNG Header: `wolfHAL/rng/rng.h` diff --git a/src/display/display.c b/src/display/display.c new file mode 100644 index 0000000..980b42f --- /dev/null +++ b/src/display/display.c @@ -0,0 +1,55 @@ +/* display.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include + +inline whal_Error whal_Display_Init(whal_Display *dev) +{ + if (!dev) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Init) + return WHAL_ENOTSUP; + + return dev->driver->Init(dev); +} + +inline whal_Error whal_Display_Deinit(whal_Display *dev) +{ + if (!dev) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Deinit) + return WHAL_ENOTSUP; + + return dev->driver->Deinit(dev); +} + +inline whal_Error whal_Display_Update(whal_Display *dev, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, + const void *data, size_t dataSz) +{ + if (!dev || !data || dataSz == 0 || w == 0 || h == 0) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Update) + return WHAL_ENOTSUP; + + return dev->driver->Update(dev, x, y, w, h, data, dataSz); +} diff --git a/src/display/sharp_memory_display.c b/src/display/sharp_memory_display.c new file mode 100644 index 0000000..ae6f493 --- /dev/null +++ b/src/display/sharp_memory_display.c @@ -0,0 +1,276 @@ +/* sharp_memory_display.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_SINGLE_INSTANCE +#include "board.h" /* provides WHAL_CFG_SHARPMEMORY_DISPLAY_DEV initializer */ +#endif +#include +#include +#include +#include +#include +#include + +/* + * Sharp Memory LCD serial command bits (mode-selection byte). + * + * On the wire the panel clocks LSB-first: M0 is the first bit, then M1, then + * M2. The wolfHAL SPI bus transmits MSB-first, so the first wire bit must sit + * in bit 7 of the transmitted byte. These constants are therefore already + * expressed in that MSB-first orientation (M0 -> bit 7, M1 -> bit 6, + * M2 -> bit 5). + */ +#define MODE_WRITE 0x80 /* M0=H: data update mode (write line memory) */ +#define MODE_CLEAR 0x20 /* M2=H: clear all pixel memory to white */ + +#define DUMMY 0x00 /* trailing / don't-care byte ("L" recommended) */ + +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_DIRECT_API_MAPPING +#define whal_SharpMemory_Display_Init whal_Display_Init +#define whal_SharpMemory_Display_Deinit whal_Display_Deinit +#define whal_SharpMemory_Display_Update whal_Display_Update +#endif /* WHAL_CFG_SHARPMEMORY_DISPLAY_DIRECT_API_MAPPING */ + +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_SINGLE_INSTANCE +const whal_Display whal_SharpMemory_Display_Dev = WHAL_CFG_SHARPMEMORY_DISPLAY_DEV; +#endif + +/* SCS is active high on Sharp memory LCDs. */ +static whal_Error SharpMem_CsAssert(whal_SharpMemory_Display_Cfg *cfg) +{ + return whal_Gpio_Set(cfg->gpioDev, cfg->csPin, 1); +} + +static whal_Error SharpMem_CsDeassert(whal_SharpMemory_Display_Cfg *cfg) +{ + return whal_Gpio_Set(cfg->gpioDev, cfg->csPin, 0); +} + +/* + * Reverse the bit order of a byte. The gate-line address is sent LSB-first + * (AG0 = LSB of the line number) over the MSB-first bus, so the line number + * must be bit-reversed before transmission. + */ +static uint8_t SharpMem_Reverse8(uint8_t b) +{ + b = (uint8_t)(((b & 0xF0) >> 4) | ((b & 0x0F) << 4)); + b = (uint8_t)(((b & 0xCC) >> 2) | ((b & 0x33) << 2)); + b = (uint8_t)(((b & 0xAA) >> 1) | ((b & 0x55) << 1)); + return b; +} + +/* Clear all pixel memory to white (all-clear frame, M2=H). */ +static whal_Error SharpMem_Clear(whal_SharpMemory_Display_Cfg *cfg) +{ + uint8_t frame[2]; + whal_Error err; + + frame[0] = MODE_CLEAR; + frame[1] = DUMMY; + + err = SharpMem_CsAssert(cfg); + if (err) + return err; + err = whal_Spi_SendRecv(cfg->spiDev, frame, sizeof(frame), NULL, 0); + SharpMem_CsDeassert(cfg); + return err; +} + +whal_Error whal_SharpMemory_Display_Init(whal_Display *dev) +{ + whal_SharpMemory_Display_Cfg *cfg; + whal_Error err; + int pwmStarted = 0; + +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_SINGLE_INSTANCE + cfg = (whal_SharpMemory_Display_Cfg *)whal_SharpMemory_Display_Dev.cfg; + (void)dev; +#else + if (!dev || !dev->cfg) + return WHAL_EINVAL; + cfg = (whal_SharpMemory_Display_Cfg *)dev->cfg; +#endif + + /* + * gpioDev may legitimately be WHAL_INTERNAL_DEV (NULL) on platforms that + * direct-map the GPIO API, so it is not null-checked here; whal_Gpio_Set + * validates it on vtable-dispatched platforms. + */ + if (!cfg || !cfg->spiComCfg) + return WHAL_EINVAL; + if (cfg->width == 0 || cfg->height == 0 || (cfg->width & 0x7)) + return WHAL_EINVAL; + if (cfg->height > 255) + return WHAL_ENOTSUP; + + /* Ensure SCS starts deasserted (low) before configuring the bus. */ + err = SharpMem_CsDeassert(cfg); + if (err) + return err; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + /* + * Start the EXTCOMIN (VCOM) source. COM polarity must be inverted + * periodically to avoid a DC bias building up on the panel; the board + * ties EXTMODE high and feeds EXTCOMIN from this signal. + */ + if (cfg->vcomCfg) { + err = whal_Pwm_Start(cfg->pwm, cfg->pwmChannel, cfg->vcomCfg); + if (err) + goto cleanup; + pwmStarted = 1; + } + + /* Power-on requires initializing pixel memory at least once. */ + err = SharpMem_Clear(cfg); + +cleanup: + if (err && pwmStarted) + whal_Pwm_Stop(cfg->pwm, cfg->pwmChannel); + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +whal_Error whal_SharpMemory_Display_Deinit(whal_Display *dev) +{ + whal_SharpMemory_Display_Cfg *cfg; + whal_Error err = WHAL_SUCCESS; + +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_SINGLE_INSTANCE + cfg = (whal_SharpMemory_Display_Cfg *)whal_SharpMemory_Display_Dev.cfg; + (void)dev; +#else + if (!dev || !dev->cfg) + return WHAL_EINVAL; + cfg = (whal_SharpMemory_Display_Cfg *)dev->cfg; +#endif + + if (!cfg) + return WHAL_EINVAL; + + /* Blank the panel before halting COM inversion so no static image is left + * latched without EXTCOMIN toggling (DC bias). */ + if (cfg->spiComCfg) { + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (!err) { + err = SharpMem_Clear(cfg); + whal_Spi_EndCom(cfg->spiDev); + } + } + + if (cfg->vcomCfg) + whal_Pwm_Stop(cfg->pwm, cfg->pwmChannel); + + if (err == WHAL_SUCCESS) + return SharpMem_CsDeassert(cfg); + + SharpMem_CsDeassert(cfg); + return err; +} + +whal_Error whal_SharpMemory_Display_Update(whal_Display *dev, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, + const void *data, size_t dataSz) +{ + const uint8_t *pixels = (const uint8_t *)data; + whal_SharpMemory_Display_Cfg *cfg; + size_t bytesPerLine; + uint8_t cmd = MODE_WRITE; + uint8_t trailer = DUMMY; + uint16_t line; + whal_Error err; + +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_SINGLE_INSTANCE + cfg = (whal_SharpMemory_Display_Cfg *)whal_SharpMemory_Display_Dev.cfg; + (void)dev; +#else + if (!dev || !dev->cfg) + return WHAL_EINVAL; + cfg = (whal_SharpMemory_Display_Cfg *)dev->cfg; +#endif + + if (!cfg || !cfg->spiComCfg) + return WHAL_EINVAL; + if (!data || h == 0) + return WHAL_EINVAL; + + /* The panel writes whole lines: a region must span the full width. */ + if (x != 0 || w != cfg->width) + return WHAL_ENOTSUP; + if ((uint32_t)y + h > cfg->height) + return WHAL_EINVAL; + + bytesPerLine = cfg->width / 8; + if (dataSz != (size_t)h * bytesPerLine) + return WHAL_EINVAL; + + err = whal_Spi_StartCom(cfg->spiDev, cfg->spiComCfg); + if (err) + return err; + + err = SharpMem_CsAssert(cfg); + if (err) + goto cleanup; + + /* + * Frame layout (data update mode, M0=H): + * [mode] { [gate addr][line data][8ck dummy] }... [8ck dummy] + * The trailing dummy of each line plus the final dummy form the 16ck + * data-transfer period the panel needs to latch the last line. + */ + err = whal_Spi_SendRecv(cfg->spiDev, &cmd, 1, NULL, 0); + + for (line = 0; !err && line < h; line++) { + /* Gate lines are 1-based; address is sent LSB-first (bit-reversed). */ + uint8_t addr = SharpMem_Reverse8((uint8_t)(y + line + 1)); + + err = whal_Spi_SendRecv(cfg->spiDev, &addr, 1, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, + pixels + (size_t)line * bytesPerLine, + bytesPerLine, NULL, 0); + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, &trailer, 1, NULL, 0); + } + + /* Final dummy byte to complete the last line's transfer period. */ + if (!err) + err = whal_Spi_SendRecv(cfg->spiDev, &trailer, 1, NULL, 0); + + SharpMem_CsDeassert(cfg); + +cleanup: + whal_Spi_EndCom(cfg->spiDev); + return err; +} + +#ifndef WHAL_CFG_SHARPMEMORY_DISPLAY_DIRECT_API_MAPPING +const whal_DisplayDriver whal_SharpMemory_Display_Driver = { + .Init = whal_SharpMemory_Display_Init, + .Deinit = whal_SharpMemory_Display_Deinit, + .Update = whal_SharpMemory_Display_Update, +}; +#endif /* !WHAL_CFG_SHARPMEMORY_DISPLAY_DIRECT_API_MAPPING */ diff --git a/src/pwm/pwm.c b/src/pwm/pwm.c new file mode 100644 index 0000000..b03221f --- /dev/null +++ b/src/pwm/pwm.c @@ -0,0 +1,65 @@ +/* pwm.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include + +inline whal_Error whal_Pwm_Init(whal_Pwm *dev) +{ + if (!dev) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Init) + return WHAL_ENOTSUP; + + return dev->driver->Init(dev); +} + +inline whal_Error whal_Pwm_Deinit(whal_Pwm *dev) +{ + if (!dev) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Deinit) + return WHAL_ENOTSUP; + + return dev->driver->Deinit(dev); +} + +inline whal_Error whal_Pwm_Start(whal_Pwm *dev, uint8_t channel, + const whal_Pwm_ChannelCfg *cfg) +{ + if (!dev || !cfg || cfg->periodCycles == 0 || + cfg->pulseCycles > cfg->periodCycles) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Start) + return WHAL_ENOTSUP; + + return dev->driver->Start(dev, channel, cfg); +} + +inline whal_Error whal_Pwm_Stop(whal_Pwm *dev, uint8_t channel) +{ + if (!dev) + return WHAL_EINVAL; + if (!dev->driver || !dev->driver->Stop) + return WHAL_ENOTSUP; + + return dev->driver->Stop(dev, channel); +} diff --git a/src/pwm/stm32wb_lptim_pwm.c b/src/pwm/stm32wb_lptim_pwm.c new file mode 100644 index 0000000..9d589c7 --- /dev/null +++ b/src/pwm/stm32wb_lptim_pwm.c @@ -0,0 +1,187 @@ +/* stm32wb_lptim_pwm.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include +#include +#include + +/* STM32WB LPTIM registers (RM0434 Rev 14, Section 27.7). */ +#define LPTIM_ISR_REG 0x00 +#define LPTIM_ISR_CMPOK_Pos 3 /* Compare register update OK */ +#define LPTIM_ISR_CMPOK_Msk (1UL << LPTIM_ISR_CMPOK_Pos) +#define LPTIM_ISR_ARROK_Pos 4 /* Autoreload register update OK */ +#define LPTIM_ISR_ARROK_Msk (1UL << LPTIM_ISR_ARROK_Pos) + +#define LPTIM_ICR_REG 0x04 +#define LPTIM_ICR_CMPOKCF_Msk (1UL << 3) /* Clear CMPOK */ +#define LPTIM_ICR_ARROKCF_Msk (1UL << 4) /* Clear ARROK */ + +#define LPTIM_CFGR_REG 0x0C +#define LPTIM_CFGR_CKSEL_Pos 0 /* Clock source (0 = internal) */ +#define LPTIM_CFGR_CKSEL_Msk (1UL << LPTIM_CFGR_CKSEL_Pos) +#define LPTIM_CFGR_PRESC_Pos 9 /* Clock prescaler */ +#define LPTIM_CFGR_PRESC_Msk (WHAL_BITMASK(3) << LPTIM_CFGR_PRESC_Pos) +#define LPTIM_CFGR_WAVPOL_Msk (1UL << 21) /* Waveform polarity */ + +#define LPTIM_CR_REG 0x10 +#define LPTIM_CR_ENABLE_Msk (1UL << 0) /* LPTIM enable */ +#define LPTIM_CR_CNTSTRT_Msk (1UL << 2) /* Continuous-mode start */ + +#define LPTIM_CMP_REG 0x14 /* Compare register (16-bit) */ +#define LPTIM_ARR_REG 0x18 /* Autoreload register (16-bit) */ + +/* ARR/CMP are 16-bit, so the period (ARR + 1) tops out at 65536 ticks. */ +#define LPTIM_PERIOD_MAX 0x10000UL + +whal_Error whal_Stm32wb_Lptim_Pwm_Init(whal_Pwm *dev) +{ + size_t base; + whal_Stm32wb_Lptim_Pwm_Cfg *cfg; + uint32_t cfgr; + + if (!dev || !dev->cfg) { + return WHAL_EINVAL; + } + + base = dev->base; + cfg = (whal_Stm32wb_Lptim_Pwm_Cfg *)dev->cfg; + + if (cfg->prescaler > 7 || cfg->clkSel > 1) { + return WHAL_ENOTSUP; + } + + /* CFGR is writable only while disabled: set clock source, prescaler, and PWM waveform here. */ + whal_Reg_Write(base, LPTIM_CR_REG, 0); + cfgr = whal_SetBits(LPTIM_CFGR_PRESC_Msk, LPTIM_CFGR_PRESC_Pos, + cfg->prescaler) | + whal_SetBits(LPTIM_CFGR_CKSEL_Msk, LPTIM_CFGR_CKSEL_Pos, + cfg->clkSel); + whal_Reg_Write(base, LPTIM_CFGR_REG, cfgr); + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wb_Lptim_Pwm_Deinit(whal_Pwm *dev) +{ + if (!dev) { + return WHAL_EINVAL; + } + + /* Disable the timer and clear its configuration; the board owns clock gating. */ + whal_Reg_Write(dev->base, LPTIM_CR_REG, 0); + whal_Reg_Write(dev->base, LPTIM_CFGR_REG, 0); + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wb_Lptim_Pwm_Start(whal_Pwm *dev, uint8_t channel, + const whal_Pwm_ChannelCfg *channelCfg) +{ + size_t base; + whal_Stm32wb_Lptim_Pwm_Cfg *cfg; + uint32_t arr, cmp; + whal_Error err; + + if (!dev || !dev->cfg || !channelCfg || + channelCfg->periodCycles == 0 || + channelCfg->pulseCycles > channelCfg->periodCycles) { + return WHAL_EINVAL; + } + if (channel != WHAL_STM32WB_LPTIM_PWM_CHANNEL) { + return WHAL_ENOTSUP; + } + + base = dev->base; + cfg = (whal_Stm32wb_Lptim_Pwm_Cfg *)dev->cfg; + + /* No pulse counter: only continuous output, and the 16-bit ARR bounds the period. */ + if (channelCfg->periodCycles > LPTIM_PERIOD_MAX || + channelCfg->pulseCount != WHAL_PWM_PULSE_COUNT_CONTINUOUS) { + return WHAL_ENOTSUP; + } + + /* ARR = period - 1; output is high while CNT > CMP, so CMP = ARR - pulse (0 when full-on). */ + arr = channelCfg->periodCycles - 1; + if (channelCfg->pulseCycles >= channelCfg->periodCycles) { + cmp = 0; + } else { + cmp = arr - channelCfg->pulseCycles; + } + + /* Disable so CFGR/ARR/CMP are writable, then set this call's polarity via WAVPOL. */ + whal_Reg_Write(base, LPTIM_CR_REG, 0); + whal_Reg_Update(base, LPTIM_CFGR_REG, LPTIM_CFGR_WAVPOL_Msk, + channelCfg->polarity != WHAL_PWM_POLARITY_NORMAL ? + LPTIM_CFGR_WAVPOL_Msk : 0); + + /* ARR/CMP are writable only once enabled; clear stale ARROK/CMPOK before writing. */ + whal_Reg_Update(base, LPTIM_CR_REG, LPTIM_CR_ENABLE_Msk, + LPTIM_CR_ENABLE_Msk); + whal_Reg_Write(base, LPTIM_ICR_REG, + LPTIM_ICR_ARROKCF_Msk | LPTIM_ICR_CMPOKCF_Msk); + + whal_Reg_Write(base, LPTIM_ARR_REG, arr); + err = whal_Reg_ReadPoll(base, LPTIM_ISR_REG, LPTIM_ISR_ARROK_Msk, + LPTIM_ISR_ARROK_Msk, cfg->timeout); + if (err != WHAL_SUCCESS) { + whal_Reg_Write(base, LPTIM_CR_REG, 0); + return err; + } + + whal_Reg_Write(base, LPTIM_CMP_REG, cmp); + err = whal_Reg_ReadPoll(base, LPTIM_ISR_REG, LPTIM_ISR_CMPOK_Msk, + LPTIM_ISR_CMPOK_Msk, cfg->timeout); + if (err != WHAL_SUCCESS) { + whal_Reg_Write(base, LPTIM_CR_REG, 0); + return err; + } + + /* Waveform programmed; begin continuous counting. */ + whal_Reg_Update(base, LPTIM_CR_REG, LPTIM_CR_CNTSTRT_Msk, + LPTIM_CR_CNTSTRT_Msk); + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wb_Lptim_Pwm_Stop(whal_Pwm *dev, uint8_t channel) +{ + if (!dev) { + return WHAL_EINVAL; + } + if (channel != WHAL_STM32WB_LPTIM_PWM_CHANNEL) { + return WHAL_ENOTSUP; + } + + /* Clearing ENABLE stops the counter and parks the output; Start resumes. */ + whal_Reg_Update(dev->base, LPTIM_CR_REG, LPTIM_CR_ENABLE_Msk, 0); + + return WHAL_SUCCESS; +} + +const whal_PwmDriver whal_Stm32wb_Lptim_Pwm_Driver = { + .Init = whal_Stm32wb_Lptim_Pwm_Init, + .Deinit = whal_Stm32wb_Lptim_Pwm_Deinit, + .Start = whal_Stm32wb_Lptim_Pwm_Start, + .Stop = whal_Stm32wb_Lptim_Pwm_Stop, +}; diff --git a/tests/core/Makefile b/tests/core/Makefile index 0fe1c81..dd23488 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -32,7 +32,9 @@ WHAL_SRC = $(WHAL_DIR)/src/gpio/gpio.c \ $(WHAL_DIR)/src/timer/timer.c \ $(WHAL_DIR)/src/spi/spi.c \ $(WHAL_DIR)/src/rng/rng.c \ - $(WHAL_DIR)/src/block/block.c + $(WHAL_DIR)/src/block/block.c \ + $(WHAL_DIR)/src/pwm/pwm.c \ + $(WHAL_DIR)/src/display/display.c SOURCE = $(TEST_SRC) $(WHAL_SRC) OBJECTS = $(patsubst %.c,%.o,$(SOURCE)) diff --git a/tests/core/test_dispatch.c b/tests/core/test_dispatch.c index 72805dd..957edce 100644 --- a/tests/core/test_dispatch.c +++ b/tests/core/test_dispatch.c @@ -346,6 +346,100 @@ static void Test_Block_ValidDispatch(void) WHAL_ASSERT_EQ(whal_Block_Deinit(&dev), WHAL_SUCCESS); } +/* --- PWM dispatch tests --- */ + +static whal_Error MockPwmInit(whal_Pwm *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockPwmDeinit(whal_Pwm *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockPwmStart(whal_Pwm *d, uint8_t ch, const whal_Pwm_ChannelCfg *c) { (void)d; (void)ch; (void)c; return WHAL_SUCCESS; } +static whal_Error MockPwmStop(whal_Pwm *d, uint8_t ch) { (void)d; (void)ch; return WHAL_SUCCESS; } + +static const whal_PwmDriver mockPwmDriver = { + .Init = MockPwmInit, + .Deinit = MockPwmDeinit, + .Start = MockPwmStart, + .Stop = MockPwmStop, +}; + +static void Test_Pwm_NullDev(void) +{ + whal_Pwm_ChannelCfg cfg = { .periodCycles = 100, .pulseCycles = 50 }; + WHAL_ASSERT_EQ(whal_Pwm_Init(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Pwm_Deinit(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Pwm_Start(NULL, 0, &cfg), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Pwm_Stop(NULL, 0), WHAL_EINVAL); +} + +static void Test_Pwm_NullDriver(void) +{ + whal_Pwm dev = { .driver = NULL }; + WHAL_ASSERT_EQ(whal_Pwm_Init(&dev), WHAL_ENOTSUP); +} + +static void Test_Pwm_BadWave(void) +{ + whal_Pwm dev = { .driver = &mockPwmDriver }; + whal_Pwm_ChannelCfg cfg = { .periodCycles = 100, .pulseCycles = 101 }; + whal_Pwm_ChannelCfg zero = { .periodCycles = 0, .pulseCycles = 0 }; + WHAL_ASSERT_EQ(whal_Pwm_Start(&dev, 0, NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Pwm_Start(&dev, 0, &cfg), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Pwm_Start(&dev, 0, &zero), WHAL_EINVAL); +} + +static void Test_Pwm_ValidDispatch(void) +{ + whal_Pwm dev = { .driver = &mockPwmDriver }; + whal_Pwm_ChannelCfg cfg = { .periodCycles = 100, .pulseCycles = 50 }; + WHAL_ASSERT_EQ(whal_Pwm_Init(&dev), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Pwm_Start(&dev, 0, &cfg), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Pwm_Stop(&dev, 0), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Pwm_Deinit(&dev), WHAL_SUCCESS); +} + +/* --- Display dispatch tests --- */ + +static whal_Error MockDisplayInit(whal_Display *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockDisplayDeinit(whal_Display *d) { (void)d; return WHAL_SUCCESS; } +static whal_Error MockDisplayUpdate(whal_Display *d, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const void *data, size_t dataSz) { (void)d; (void)x; (void)y; (void)w; (void)h; (void)data; (void)dataSz; return WHAL_SUCCESS; } + +static const whal_DisplayDriver mockDisplayDriver = { + .Init = MockDisplayInit, + .Deinit = MockDisplayDeinit, + .Update = MockDisplayUpdate, +}; + +static void Test_Display_NullDev(void) +{ + uint8_t buf[1] = {0}; + WHAL_ASSERT_EQ(whal_Display_Init(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Display_Deinit(NULL), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Display_Update(NULL, 0, 0, 1, 1, buf, sizeof(buf)), WHAL_EINVAL); +} + +static void Test_Display_NullDriver(void) +{ + whal_Display dev = { .driver = NULL }; + WHAL_ASSERT_EQ(whal_Display_Init(&dev), WHAL_ENOTSUP); +} + +static void Test_Display_BadArgs(void) +{ + whal_Display dev = { .driver = &mockDisplayDriver }; + uint8_t buf[1] = {0}; + WHAL_ASSERT_EQ(whal_Display_Update(&dev, 0, 0, 1, 1, NULL, 1), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Display_Update(&dev, 0, 0, 1, 1, buf, 0), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Display_Update(&dev, 0, 0, 0, 1, buf, sizeof(buf)), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Display_Update(&dev, 0, 0, 1, 0, buf, sizeof(buf)), WHAL_EINVAL); +} + +static void Test_Display_ValidDispatch(void) +{ + whal_Display dev = { .driver = &mockDisplayDriver }; + uint8_t buf[4] = {0}; + WHAL_ASSERT_EQ(whal_Display_Init(&dev), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Display_Update(&dev, 0, 0, 2, 2, buf, sizeof(buf)), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Display_Deinit(&dev), WHAL_SUCCESS); +} + void whal_Test_Dispatch(void) { WHAL_TEST_SUITE_START("dispatch"); @@ -371,5 +465,13 @@ void whal_Test_Dispatch(void) WHAL_TEST(Test_Block_NullDev); WHAL_TEST(Test_Block_NullDriver); WHAL_TEST(Test_Block_ValidDispatch); + WHAL_TEST(Test_Pwm_NullDev); + WHAL_TEST(Test_Pwm_NullDriver); + WHAL_TEST(Test_Pwm_BadWave); + WHAL_TEST(Test_Pwm_ValidDispatch); + WHAL_TEST(Test_Display_NullDev); + WHAL_TEST(Test_Display_NullDriver); + WHAL_TEST(Test_Display_BadArgs); + WHAL_TEST(Test_Display_ValidDispatch); WHAL_TEST_SUITE_END(); } diff --git a/tests/display/test_sharp_memory_display.c b/tests/display/test_sharp_memory_display.c new file mode 100644 index 0000000..886911b --- /dev/null +++ b/tests/display/test_sharp_memory_display.c @@ -0,0 +1,311 @@ +/* test_sharp_memory_display.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* + * Sharp Memory LCD (LS013B7DH03) test. + * + * Builds a 1bpp framebuffer in RAM, draws a few primitive shapes into it, + * and pushes the result to the panel with the driver's Update path. On real + * hardware the shapes are visible; in all cases the test asserts that each + * transfer (and each argument-validation case) returns the expected status. + * + * The framebuffer uses the driver's documented pixel layout: 1 bit per + * pixel, packed MSB-first (bit 7 of a byte is the left-most pixel), where a + * 1 bit is white and a 0 bit is black. + * + * The panel is an optional external peripheral. Wire it up with + * make TESTS="... sharp_memory_display" PERIPHERALS="sharp_ls013b7dh03" + * When the peripheral is not present the suite skips. + */ + +#include +#include +#include +#include "test.h" + +#ifdef PERIPHERAL_SHARP_LS013B7DH03 +#include "display/sharp_ls013b7dh03.h" + +/* Largest panel this test allocates a framebuffer for (LS013B7DH03 is 128x128). */ +#define SHARP_MAX_W 128 +#define SHARP_MAX_H 128 +#define SHARP_STRIDE(w) ((w) / 8) +#define SHARP_FB_SZ (SHARP_STRIDE(SHARP_MAX_W) * SHARP_MAX_H) + +#define PIXEL_WHITE 1 +#define PIXEL_BLACK 0 + +static uint8_t g_fb[SHARP_FB_SZ]; + +static whal_Display *g_dev; +static uint16_t g_w; +static uint16_t g_h; +static size_t g_stride; + +/* --- tiny 1bpp drawing primitives (MSB-first packing, 1 = white) --- */ + +static void Fb_Fill(uint8_t color) +{ + size_t i; + uint8_t byte = color ? 0xFF : 0x00; + for (i = 0; i < g_stride * g_h; i++) + g_fb[i] = byte; +} + +static void Fb_SetPixel(int x, int y, uint8_t color) +{ + uint8_t mask; + + if (x < 0 || y < 0 || x >= (int)g_w || y >= (int)g_h) + return; + + mask = (uint8_t)(0x80 >> (x & 7)); + if (color) + g_fb[y * g_stride + (x >> 3)] |= mask; + else + g_fb[y * g_stride + (x >> 3)] &= (uint8_t)~mask; +} + +static void Fb_HLine(int x, int y, int len, uint8_t color) +{ + int i; + for (i = 0; i < len; i++) + Fb_SetPixel(x + i, y, color); +} + +static void Fb_VLine(int x, int y, int len, uint8_t color) +{ + int i; + for (i = 0; i < len; i++) + Fb_SetPixel(x, y + i, color); +} + +/* Outlined rectangle. */ +static void Fb_Rect(int x, int y, int w, int h, uint8_t color) +{ + Fb_HLine(x, y, w, color); + Fb_HLine(x, y + h - 1, w, color); + Fb_VLine(x, y, h, color); + Fb_VLine(x + w - 1, y, h, color); +} + +/* Solid rectangle. */ +static void Fb_FillRect(int x, int y, int w, int h, uint8_t color) +{ + int row; + for (row = 0; row < h; row++) + Fb_HLine(x, y + row, w, color); +} + +/* Line via Bresenham's algorithm. */ +static void Fb_Line(int x0, int y0, int x1, int y1, uint8_t color) +{ + int dx = x1 - x0; + int dy = y1 - y0; + int sx = dx < 0 ? -1 : 1; + int sy = dy < 0 ? -1 : 1; + int err, e2; + + if (dx < 0) dx = -dx; + if (dy < 0) dy = -dy; + err = dx - dy; + + for (;;) { + Fb_SetPixel(x0, y0, color); + if (x0 == x1 && y0 == y1) + break; + e2 = 2 * err; + if (e2 > -dy) { err -= dy; x0 += sx; } + if (e2 < dx) { err += dx; y0 += sy; } + } +} + +/* Circle outline via the midpoint algorithm. */ +static void Fb_Circle(int cx, int cy, int r, uint8_t color) +{ + int x = r; + int y = 0; + int err = 1 - r; + + while (x >= y) { + Fb_SetPixel(cx + x, cy + y, color); + Fb_SetPixel(cx + y, cy + x, color); + Fb_SetPixel(cx - y, cy + x, color); + Fb_SetPixel(cx - x, cy + y, color); + Fb_SetPixel(cx - x, cy - y, color); + Fb_SetPixel(cx - y, cy - x, color); + Fb_SetPixel(cx + y, cy - x, color); + Fb_SetPixel(cx + x, cy - y, color); + y++; + if (err < 0) { + err += 2 * y + 1; + } + else { + x--; + err += 2 * (y - x) + 1; + } + } +} + +/* Push the whole framebuffer to the panel. */ +static whal_Error Fb_Flush(void) +{ + return whal_SharpMemory_Display_Update(g_dev, 0, 0, g_w, g_h, + g_fb, g_stride * g_h); +} + +/* --- tests --- */ + +static void Test_SharpDisplay_Init(void) +{ + whal_Display *dev = &g_whalSharpLs013b7dh03; + whal_SharpMemory_Display_Cfg *cfg; + + cfg = (whal_SharpMemory_Display_Cfg *)dev->cfg; + WHAL_ASSERT_NEQ(cfg, NULL); + + g_w = cfg->width; + g_h = cfg->height; + g_stride = SHARP_STRIDE(g_w); + + /* This test's static framebuffer only covers up to 128x128. */ + if (g_w == 0 || g_h == 0 || g_w > SHARP_MAX_W || g_h > SHARP_MAX_H || + (g_w & 7)) + WHAL_SKIP(); + + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Init(dev), WHAL_SUCCESS); + + /* Only now is the panel geometry known to fit g_fb; the tests below run + * off g_dev being set. */ + g_dev = dev; +} + +static void Test_SharpDisplay_DrawShapes(void) +{ + int cx, cy, r; + int gx, gy; + + if (!g_dev) + WHAL_SKIP(); + + cx = g_w / 2; + cy = g_h / 2; + r = (g_w < g_h ? g_w : g_h) / 4; + + /* Start from a white background (1 = white). */ + Fb_Fill(PIXEL_WHITE); + + /* Black border one pixel inside the edges. */ + Fb_Rect(0, 0, g_w, g_h, PIXEL_BLACK); + + /* Crossed diagonals. */ + Fb_Line(0, 0, g_w - 1, g_h - 1, PIXEL_BLACK); + Fb_Line(g_w - 1, 0, 0, g_h - 1, PIXEL_BLACK); + + /* Solid square in the top-left quadrant. */ + Fb_FillRect(g_w / 8, g_h / 8, g_w / 4, g_h / 4, PIXEL_BLACK); + + /* Circle centered on the panel. */ + Fb_Circle(cx, cy, r, PIXEL_BLACK); + + /* Checkerboard of 8x8 cells in the bottom-right quadrant. */ + for (gy = g_h / 2; gy < (int)g_h; gy += 8) { + for (gx = g_w / 2; gx < (int)g_w; gx += 8) { + if (((gx / 8) ^ (gy / 8)) & 1) + Fb_FillRect(gx, gy, 8, 8, PIXEL_BLACK); + } + } + + WHAL_ASSERT_EQ(Fb_Flush(), WHAL_SUCCESS); +} + +static void Test_SharpDisplay_PartialUpdate(void) +{ + uint16_t bandY, bandH; + + if (!g_dev) + WHAL_SKIP(); + + bandY = g_h / 4; + bandH = g_h / 2; + + /* Repaint just the middle band of rows as solid black, full width. */ + Fb_FillRect(0, bandY, g_w, bandH, PIXEL_BLACK); + + WHAL_ASSERT_EQ( + whal_SharpMemory_Display_Update(g_dev, 0, bandY, g_w, bandH, + g_fb + (size_t)bandY * g_stride, + (size_t)bandH * g_stride), + WHAL_SUCCESS); +} + +static void Test_SharpDisplay_ApiValidation(void) +{ + if (!g_dev) + WHAL_SKIP(); + + /* NULL data. */ + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Update(g_dev, 0, 0, g_w, g_h, + NULL, g_stride * g_h), + WHAL_EINVAL); + + /* Partial-width region is unsupported by the panel. */ + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Update(g_dev, 0, 0, g_w / 2, g_h, + g_fb, g_stride * g_h), + WHAL_ENOTSUP); + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Update(g_dev, 1, 0, g_w, g_h, + g_fb, g_stride * g_h), + WHAL_ENOTSUP); + + /* Region runs past the bottom edge. */ + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Update(g_dev, 0, 1, g_w, g_h, + g_fb, g_stride * g_h), + WHAL_EINVAL); + + /* dataSz does not match the requested region. */ + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Update(g_dev, 0, 0, g_w, g_h, + g_fb, g_stride * g_h - 1), + WHAL_EINVAL); +} + +static void Test_SharpDisplay_Deinit(void) +{ + if (!g_dev) + WHAL_SKIP(); + + WHAL_ASSERT_EQ(whal_SharpMemory_Display_Deinit(g_dev), WHAL_SUCCESS); +} +#endif /* PERIPHERAL_SHARP_LS013B7DH03 */ + +void whal_Test_SharpMemoryDisplay(void) +{ + WHAL_TEST_SUITE_START("sharp_memory_display"); +#ifdef PERIPHERAL_SHARP_LS013B7DH03 + WHAL_TEST(Test_SharpDisplay_Init); + WHAL_TEST(Test_SharpDisplay_DrawShapes); + WHAL_TEST(Test_SharpDisplay_PartialUpdate); + WHAL_TEST(Test_SharpDisplay_ApiValidation); + WHAL_TEST(Test_SharpDisplay_Deinit); +#else + whal_Test_Printf(" no sharp_ls013b7dh03 peripheral wired; skipping\n"); +#endif + WHAL_TEST_SUITE_END(); +} diff --git a/tests/main.c b/tests/main.c index 13854ae..a5ad460 100644 --- a/tests/main.c +++ b/tests/main.c @@ -141,6 +141,10 @@ void whal_Test_Eth_Platform(void); void whal_Test_Bmi270_Sensor(void); #endif +#ifdef WHAL_TEST_ENABLE_SHARP_MEMORY_DISPLAY +void whal_Test_SharpMemoryDisplay(void); +#endif + #ifdef WHAL_TEST_ENABLE_WATCHDOG void whal_Test_Watchdog(void); #ifdef WHAL_TEST_ENABLE_WATCHDOG_PLATFORM @@ -162,6 +166,17 @@ void whal_Test_I2c_Platform(void); #endif #endif +#ifdef WHAL_TEST_ENABLE_PWM +void whal_Test_Pwm(void); +#ifdef WHAL_TEST_ENABLE_PWM_PLATFORM +void whal_Test_Pwm_Platform(void); +#endif +#endif + +#ifdef WHAL_TEST_ENABLE_PWM_PULSE +void whal_Test_Pwm_Pulse(void); +#endif + #ifdef WHAL_TEST_ENABLE_DMA_PLATFORM void whal_Test_Dma_Platform(void); #endif @@ -322,6 +337,10 @@ void main(void) whal_Test_Bmi270_Sensor(); #endif +#ifdef WHAL_TEST_ENABLE_SHARP_MEMORY_DISPLAY + whal_Test_SharpMemoryDisplay(); +#endif + #ifdef WHAL_TEST_ENABLE_WATCHDOG whal_Test_Watchdog(); #ifdef WHAL_TEST_ENABLE_WATCHDOG_PLATFORM @@ -343,6 +362,17 @@ void main(void) #endif #endif +#ifdef WHAL_TEST_ENABLE_PWM + whal_Test_Pwm(); +#ifdef WHAL_TEST_ENABLE_PWM_PLATFORM + whal_Test_Pwm_Platform(); +#endif +#endif + +#ifdef WHAL_TEST_ENABLE_PWM_PULSE + whal_Test_Pwm_Pulse(); +#endif + #ifdef WHAL_TEST_ENABLE_DMA_PLATFORM whal_Test_Dma_Platform(); #endif diff --git a/tests/pwm/test_pwm.c b/tests/pwm/test_pwm.c new file mode 100644 index 0000000..9af5888 --- /dev/null +++ b/tests/pwm/test_pwm.c @@ -0,0 +1,56 @@ +/* test_pwm.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include "board.h" +#include "test.h" + +static void Test_Pwm_Api(void) +{ + /* A waveform every driver can represent: channel 0, continuous, 25% duty. */ + whal_Pwm_ChannelCfg wave = { + .periodCycles = 1000, + .pulseCycles = 250, + .pulseCount = WHAL_PWM_PULSE_COUNT_CONTINUOUS, + .polarity = WHAL_PWM_POLARITY_NORMAL, + }; + + /* Init ran in Board_Init; Start/Stop is a repeatable toggle, so run two cycles. */ + WHAL_ASSERT_EQ(whal_Pwm_Start(BOARD_PWM_DEV, 0, &wave), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Pwm_Stop(BOARD_PWM_DEV, 0), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Pwm_Start(BOARD_PWM_DEV, 0, &wave), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Pwm_Stop(BOARD_PWM_DEV, 0), WHAL_SUCCESS); + + /* Structural violations the generic dispatch rejects: pulse > period, and null pointers. */ + wave.pulseCycles = wave.periodCycles + 1; + WHAL_ASSERT_EQ(whal_Pwm_Start(BOARD_PWM_DEV, 0, &wave), WHAL_EINVAL); + wave.pulseCycles = 250; + + WHAL_ASSERT_EQ(whal_Pwm_Start(NULL, 0, &wave), WHAL_EINVAL); + WHAL_ASSERT_EQ(whal_Pwm_Start(BOARD_PWM_DEV, 0, NULL), WHAL_EINVAL); +} + +void whal_Test_Pwm(void) +{ + WHAL_TEST_SUITE_START("pwm"); + WHAL_TEST(Test_Pwm_Api); + WHAL_TEST_SUITE_END(); +} diff --git a/tests/pwm/test_pwm_pulse.c b/tests/pwm/test_pwm_pulse.c new file mode 100644 index 0000000..2860f3f --- /dev/null +++ b/tests/pwm/test_pwm_pulse.c @@ -0,0 +1,59 @@ +/* test_pwm_pulse.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include "board.h" +#include "test.h" + +/* Period in PWM ticks; the frequency depends on the board's PWM clock and prescaler. */ +#define TEST_PWM_PULSE_PERIOD 64000 + +/* Hold each duty long enough to capture on a logic analyzer. */ +#define TEST_PWM_PULSE_HOLD_MS 1000 + +/* Drive 25/50/75% duty on the wired output so the steps are visible on a logic analyzer. */ +static void Test_Pwm_Pulse(void) +{ + const uint8_t duty[] = {25, 50, 75}; + whal_Pwm_ChannelCfg wave = { + .periodCycles = TEST_PWM_PULSE_PERIOD, + .pulseCount = WHAL_PWM_PULSE_COUNT_CONTINUOUS, + .polarity = WHAL_PWM_POLARITY_NORMAL, + }; + size_t i; + + for (i = 0; i < sizeof(duty) / sizeof(duty[0]); i++) { + wave.pulseCycles = TEST_PWM_PULSE_PERIOD * duty[i] / 100; + WHAL_ASSERT_EQ(whal_Pwm_Start(BOARD_PWM_DEV, 0, &wave), WHAL_SUCCESS); + Board_WaitMs(TEST_PWM_PULSE_HOLD_MS); + } + + WHAL_ASSERT_EQ(whal_Pwm_Stop(BOARD_PWM_DEV, 0), WHAL_SUCCESS); +} + +void whal_Test_Pwm_Pulse(void) +{ + WHAL_TEST_SUITE_START("pwm_pulse"); + WHAL_TEST(Test_Pwm_Pulse); + WHAL_TEST_SUITE_END(); +} diff --git a/tests/pwm/test_stm32wb_pwm.c b/tests/pwm/test_stm32wb_pwm.c new file mode 100644 index 0000000..72c69f2 --- /dev/null +++ b/tests/pwm/test_stm32wb_pwm.c @@ -0,0 +1,96 @@ +/* test_stm32wb_pwm.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include "board.h" +#include "test.h" + +/* The LPTIM driver must enforce the same waveform contract when called + * directly as it does through whal_Pwm_Start. Every case here returns before + * any LPTIM register access, so no hardware state is touched. */ +static void Test_Stm32wb_Lptim_Pwm_Reject(void) +{ + whal_Pwm_ChannelCfg wave = { + .periodCycles = 1000, + .pulseCycles = 500, + .pulseCount = WHAL_PWM_PULSE_COUNT_CONTINUOUS, + .polarity = WHAL_PWM_POLARITY_NORMAL, + }; + + /* Structural waveform violations are EINVAL, matching whal_Pwm_Start. */ + wave.pulseCycles = wave.periodCycles + 1; + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL, &wave), WHAL_EINVAL); + wave.pulseCycles = 500; + + wave.periodCycles = 0; + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL, &wave), WHAL_EINVAL); + wave.periodCycles = 1000; + + /* Limits the LPTIM cannot represent are ENOTSUP. */ + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, 1, &wave), + WHAL_ENOTSUP); + + wave.periodCycles = 0x10001; /* beyond the 16-bit ARR range */ + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL, &wave), WHAL_ENOTSUP); + wave.periodCycles = 1000; + + wave.pulseCount = 3; /* LPTIM has no pulse counter */ + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL, &wave), WHAL_ENOTSUP); +} + +/* Accepted boundaries: the largest representable period and 100% duty. Each + * programs the LPTIM, so the channel is stopped again afterwards. */ +static void Test_Stm32wb_Lptim_Pwm_Accept(void) +{ + whal_Pwm_ChannelCfg wave = { + .pulseCount = WHAL_PWM_PULSE_COUNT_CONTINUOUS, + .polarity = WHAL_PWM_POLARITY_NORMAL, + }; + + /* Largest representable period (ARR = 0xFFFF). */ + wave.periodCycles = 0x10000; + wave.pulseCycles = 0x8000; + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL, &wave), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Stop(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL), WHAL_SUCCESS); + + /* Maximum duty: pulse == period exercises the CMP = 0 branch. */ + wave.periodCycles = 1000; + wave.pulseCycles = 1000; + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Start(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL, &wave), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Stm32wb_Lptim_Pwm_Stop(BOARD_PWM_DEV, + WHAL_STM32WB_LPTIM_PWM_CHANNEL), WHAL_SUCCESS); +} + +void whal_Test_Pwm_Platform(void) +{ + WHAL_TEST_SUITE_START("pwm_platform"); + WHAL_TEST(Test_Stm32wb_Lptim_Pwm_Reject); + WHAL_TEST(Test_Stm32wb_Lptim_Pwm_Accept); + WHAL_TEST_SUITE_END(); +} diff --git a/wolfHAL/display/display.h b/wolfHAL/display/display.h new file mode 100644 index 0000000..4ab41f4 --- /dev/null +++ b/wolfHAL/display/display.h @@ -0,0 +1,109 @@ +/* display.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WHAL_DISPLAY_H +#define WHAL_DISPLAY_H + +#include +#include +#include + +/* + * @file display.h + * @brief Generic display abstraction and driver interface. + * + * Provides a bus-agnostic API for driving pixel-addressable displays. + * Each display driver implements the vtable and talks to its panel over + * the appropriate bus (SPI, parallel, etc.) internally. The Update + * function pushes a rectangular region of pixel data to the panel; the + * pixel format and the byte layout of the data buffer are defined by + * each driver. + */ + +typedef struct whal_Display whal_Display; + +/* + * @brief Driver vtable for display devices. + */ +typedef struct { + /* Initialize the display hardware. */ + whal_Error (*Init)(whal_Display *dev); + /* Deinitialize the display hardware. */ + whal_Error (*Deinit)(whal_Display *dev); + /* Push pixel data to the w-by-h region whose top-left corner is (x,y). */ + whal_Error (*Update)(whal_Display *dev, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, + const void *data, size_t dataSz); +} whal_DisplayDriver; + +/* + * @brief Display device instance tying a driver and configuration. + */ +struct whal_Display { + const whal_DisplayDriver *driver; + void *cfg; +}; + +/* + * @brief Initialize a display device and its driver. + * + * @param dev Pointer to the display instance to initialize. + * + * @retval WHAL_SUCCESS Driver-specific init completed. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_ENOTSUP No driver bound or operation unsupported. + */ +whal_Error whal_Display_Init(whal_Display *dev); +/* + * @brief Deinitialize a display device and release resources. + * + * @param dev Pointer to the display instance to deinitialize. + * + * @retval WHAL_SUCCESS Driver-specific deinit completed. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_ENOTSUP No driver bound or operation unsupported. + */ +whal_Error whal_Display_Deinit(whal_Display *dev); +/* + * @brief Push pixel data to a rectangular region of the display. + * + * Updates the @p w by @p h rectangle whose top-left corner is (@p x, + * @p y) with the pixel data in @p data. The pixel format and the + * expected size of @p data for a given region are defined by each + * driver. + * + * @param dev Pointer to the display instance. + * @param x Left column of the region. + * @param y Top row of the region. + * @param w Width of the region in pixels. + * @param h Height of the region in pixels. + * @param data Pointer to the driver-defined pixel data to push. + * @param dataSz Size of @p data in bytes. + * + * @retval WHAL_SUCCESS Region updated successfully. + * @retval WHAL_EINVAL Null pointer or malformed region request. + * @retval WHAL_ENOTSUP No driver bound, or region/data size unsupported. + */ +whal_Error whal_Display_Update(whal_Display *dev, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, + const void *data, size_t dataSz); + +#endif /* WHAL_DISPLAY_H */ diff --git a/wolfHAL/display/sharp_memory_display.h b/wolfHAL/display/sharp_memory_display.h new file mode 100644 index 0000000..31551d7 --- /dev/null +++ b/wolfHAL/display/sharp_memory_display.h @@ -0,0 +1,151 @@ +/* sharp_memory_display.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WHAL_SHARPMEMORY_DISPLAY_H +#define WHAL_SHARPMEMORY_DISPLAY_H + +#include +#include +#include +#include +#include +#include + +/** + * @file sharp_memory_display.h + * @brief Sharp Memory LCD driver (LS013B7DH03 and compatible). + * + * Drives a Sharp memory-in-pixel monochrome LCD over a 3-wire SPI bus + * (SCLK, SI, SCS). The panel has no read path and no register file: each + * transaction is a one-shot serial frame that either updates whole display + * lines, clears the panel, or holds the current image. + * + * Wire format notes (handled by the driver): + * - SCS (chip select) is *active high*, unlike a conventional SPI slave, + * and is driven as a plain GPIO (cfg->gpioDev / cfg->csPin). + * - The bus is mode 0 and runs at up to 1.1 MHz (set via cfg->spiComCfg). + * - COM inversion (VCOM) is performed in hardware: the board ties EXTMODE + * high and feeds EXTCOMIN from a ~1-60 Hz, 50%-duty signal. When a vcomCfg + * waveform is supplied the driver starts/stops cfg->pwm as that source. + * + * Pixel data format for whal_SharpMemory_Display_Update(): + * - 1 bit per pixel, packed MSB-first: bit 7 of the first byte is the + * left-most pixel of a line, bit 6 the next, and so on. + * - A bit value of 1 is white, 0 is black (matching the panel's "H=white"). + * - The panel can only write whole lines, so an update must span the full + * panel width (x == 0, w == width); any contiguous range of rows may be + * updated. @p data is therefore h * (width / 8) bytes, row-major. + */ + +/** + * @brief Display device configuration. + */ +typedef struct whal_SharpMemory_Display_Cfg { + whal_Spi *spiDev; /**< SPI bus device */ + whal_Spi_ComCfg *spiComCfg; /**< SPI session config for StartCom */ + whal_Gpio *gpioDev; /**< GPIO device for chip select (SCS); + * may be WHAL_INTERNAL_DEV on platforms + * that direct-map the GPIO API */ + size_t csPin; /**< GPIO pin index for SCS (active high) */ + whal_Pwm *pwm; /**< PWM driving EXTCOMIN (used only when + * vcomCfg is set) */ + uint8_t pwmChannel; /**< PWM channel driving EXTCOMIN */ + whal_Pwm_ChannelCfg *vcomCfg;/**< EXTCOMIN waveform, and the presence flag + * for the PWM VCOM source: NULL means the + * board generates VCOM elsewhere and the + * driver starts no PWM (the device handles + * may be WHAL_INTERNAL_DEV/NULL, so they + * cannot serve as the discriminator) */ + uint16_t width; /**< Panel width in pixels (128) */ + uint16_t height; /**< Panel height in pixels (128); at most + * 255, the range of the gate address */ +} whal_SharpMemory_Display_Cfg; + +#ifdef WHAL_CFG_SHARPMEMORY_DISPLAY_SINGLE_INSTANCE +/** + * @brief Fixed device instance for boards with a single on-board panel. + * Defined in the driver TU from the WHAL_CFG_SHARPMEMORY_DISPLAY_DEV + * initializer in board.h. When this macro is not defined, the driver instead + * operates on the whal_Display passed to each call (e.g. an external panel + * wired up under boards/peripheral/), like the SPI-NOR flash driver. + */ +extern const whal_Display whal_SharpMemory_Display_Dev; +#endif + +#ifndef WHAL_CFG_SHARPMEMORY_DISPLAY_DIRECT_API_MAPPING +/** + * @brief Driver instance for SharpMemory Display peripheral. + */ +extern const whal_DisplayDriver whal_SharpMemory_Display_Driver; + +/** + * @brief Initialize the SharpMemory display hardware. + * + * Deasserts SCS, opens the SPI session parameters, starts the EXTCOMIN + * (VCOM) source when a PWM is configured, and clears the panel to white. + * + * @param dev Pointer to the display instance to initialize. + * + * @retval WHAL_SUCCESS Initialization completed. + * @retval WHAL_EINVAL Null pointer or invalid configuration. + * @retval WHAL_ENOTSUP Panel height exceeds 255 lines; the update frame + * carries a single-byte gate address, so taller panels + * need a wire format this driver does not implement. + */ +whal_Error whal_SharpMemory_Display_Init(whal_Display *dev); +/** + * @brief Deinitialize the SharpMemory display hardware. + * + * Blanks the panel (clears pixel memory to white) and then stops the EXTCOMIN + * source, so no static image is left latched without COM inversion. + * + * @param dev Pointer to the display instance to deinitialize. + * + * @retval WHAL_SUCCESS Deinitialization completed. + * @retval WHAL_EINVAL Null pointer or invalid configuration. + */ +whal_Error whal_SharpMemory_Display_Deinit(whal_Display *dev); +/** + * @brief Push pixel data to full-width display lines. + * + * The panel updates whole lines only, so the region must span the full + * panel width (@p x == 0, @p w == width). Rows @p y .. @p y + @p h - 1 are + * rewritten from @p data, which is @p h * (width / 8) bytes of 1bpp, + * MSB-first pixel data (see the file header for the bit layout). + * + * @param dev Pointer to the display instance. + * @param x Left column of the region (must be 0). + * @param y Top row of the region. + * @param w Width of the region in pixels (must equal the panel width). + * @param h Height of the region in pixels (number of lines). + * @param data Pointer to the pixel data to push. + * @param dataSz Size of @p data in bytes; must be h * (width / 8). + * + * @retval WHAL_SUCCESS Region updated successfully. + * @retval WHAL_EINVAL Null pointer or malformed region request. + * @retval WHAL_ENOTSUP Region not a full-width line range. + */ +whal_Error whal_SharpMemory_Display_Update(whal_Display *dev, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, + const void *data, size_t dataSz); +#endif /* !WHAL_CFG_SHARPMEMORY_DISPLAY_DIRECT_API_MAPPING */ + +#endif /* WHAL_SHARPMEMORY_DISPLAY_H */ diff --git a/wolfHAL/platform/st/stm32wb55xx.h b/wolfHAL/platform/st/stm32wb55xx.h index 7f024c2..ffd87f4 100644 --- a/wolfHAL/platform/st/stm32wb55xx.h +++ b/wolfHAL/platform/st/stm32wb55xx.h @@ -37,6 +37,7 @@ #include #include #include +#include /* * @file stm32wb55xx.h @@ -70,6 +71,9 @@ #define WHAL_STM32WB55_I2C3_BASE 0x40005C00 #define WHAL_STM32WB55_I2C3_DRIVER &whal_Stm32wb_I2c_Driver +#define WHAL_STM32WB55_LPTIM1_BASE 0x40007C00 +#define WHAL_STM32WB55_LPTIM1_PWM_DRIVER &whal_Stm32wb_Lptim_Pwm_Driver + #define WHAL_STM32WB55_IWDG_BASE 0x40003000 #define WHAL_STM32WB55_IWDG_DRIVER &whal_Stm32wb_Iwdg_Driver @@ -140,6 +144,11 @@ .enableMask = (1UL << 23), \ .enablePos = 23 +#define WHAL_STM32WB55_LPTIM1_GATE \ + .regOffset = 0x58, \ + .enableMask = (1UL << 31), \ + .enablePos = 31 + #define WHAL_STM32WB55_LPUART1_GATE \ .regOffset = 0x5C, \ .enableMask = (1UL << 0), \ diff --git a/wolfHAL/pwm/pwm.h b/wolfHAL/pwm/pwm.h new file mode 100644 index 0000000..c64301d --- /dev/null +++ b/wolfHAL/pwm/pwm.h @@ -0,0 +1,144 @@ +/* pwm.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WHAL_PWM_H +#define WHAL_PWM_H + +#include +#include +#include + +/* + * @file pwm.h + * @brief Generic PWM abstraction and driver interface. + * + * Provides a hardware-agnostic API for generating pulse-width-modulated + * output. The waveform of a channel (frequency, duty, polarity) is described + * by a whal_Pwm_ChannelCfg passed to Start, which applies it and begins + * output; Stop halts the channel. + * + * Channels are addressed by a zero-based index. A driver returns + * WHAL_ENOTSUP for any channel its hardware does not implement; a + * single-output peripheral accepts only channel 0. + */ + +/* Output polarity for a PWM channel. */ +#define WHAL_PWM_POLARITY_NORMAL 0 /* pulse drives the active-high level */ +#define WHAL_PWM_POLARITY_INVERTED 1 /* pulse drives the active-low level */ + +/* Run the channel continuously with no automatic stop; value for + * whal_Pwm_ChannelCfg.pulseCount. Any nonzero value requests exactly that + * many pulses; drivers that cannot count pulses reject it with WHAL_ENOTSUP. */ +#define WHAL_PWM_PULSE_COUNT_CONTINUOUS 0 + +/* + * @brief Generic description of a single PWM channel's waveform. + * + * periodCycles sets the frequency, pulseCycles the duty, polarity the active + * level, and pulseCount whether the output runs continuously or stops after a + * fixed number of pulses. The cycle counts are in ticks of the driver's + * (prescaled) timer clock. On hardware that shares one counter across + * channels, every channel of an instance must use the same periodCycles. + */ +typedef struct whal_Pwm_ChannelCfg { +#ifdef WHAL_CFG_64BIT_TICK + uint64_t periodCycles; /* full cycle length in timer ticks (1..) */ + uint64_t pulseCycles; /* asserted width in ticks; must be <= periodCycles */ +#else + uint32_t periodCycles; /* full cycle length in timer ticks (1..) */ + uint32_t pulseCycles; /* asserted width in ticks; must be <= periodCycles */ +#endif + uint32_t pulseCount; /* WHAL_PWM_PULSE_COUNT_CONTINUOUS or number of pulses */ + uint8_t polarity; /* WHAL_PWM_POLARITY_NORMAL or _INVERTED */ +} whal_Pwm_ChannelCfg; + +typedef struct whal_Pwm whal_Pwm; + +/* + * @brief Driver vtable for PWM devices. + */ +typedef struct { + /* Initialize the PWM hardware. */ + whal_Error (*Init)(whal_Pwm *dev); + /* Deinitialize the PWM hardware. */ + whal_Error (*Deinit)(whal_Pwm *dev); + /* Apply the given waveform to a channel and start its output. */ + whal_Error (*Start)(whal_Pwm *dev, uint8_t channel, + const whal_Pwm_ChannelCfg *cfg); + /* Stop output on a channel. */ + whal_Error (*Stop)(whal_Pwm *dev, uint8_t channel); +} whal_PwmDriver; + +/* + * @brief PWM device instance tying a driver and configuration. + */ +struct whal_Pwm { + const size_t base; + const whal_PwmDriver *driver; + void *cfg; +}; + +/* + * @brief Initialize a PWM device. + * + * @param dev Pointer to the PWM instance to initialize. + * + * @retval WHAL_SUCCESS Driver-specific init completed. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_ENOTSUP No driver bound or operation unsupported. + */ +whal_Error whal_Pwm_Init(whal_Pwm *dev); +/* + * @brief Deinitialize a PWM device and release resources. + * + * @param dev Pointer to the PWM instance to deinitialize. + * + * @retval WHAL_SUCCESS Driver-specific deinit completed. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_ENOTSUP No driver bound or operation unsupported. + */ +whal_Error whal_Pwm_Deinit(whal_Pwm *dev); +/* + * @brief Apply a waveform to a channel and start its output. + * + * @param dev Pointer to the PWM instance. + * @param channel Zero-based channel to start. + * @param cfg Waveform (period, pulse, polarity) to apply. + * + * @retval WHAL_SUCCESS Output started. + * @retval WHAL_EINVAL Null pointer, zero period, or pulse exceeds period. + * @retval WHAL_ENOTSUP No driver bound, or channel/waveform unsupported. + */ +whal_Error whal_Pwm_Start(whal_Pwm *dev, uint8_t channel, + const whal_Pwm_ChannelCfg *cfg); +/* + * @brief Stop PWM output on a channel. + * + * @param dev Pointer to the PWM instance. + * @param channel Zero-based channel to stop. + * + * @retval WHAL_SUCCESS Output stopped. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_ENOTSUP No driver bound, or channel unsupported. + */ +whal_Error whal_Pwm_Stop(whal_Pwm *dev, uint8_t channel); + +#endif /* WHAL_PWM_H */ diff --git a/wolfHAL/pwm/stm32wb_lptim_pwm.h b/wolfHAL/pwm/stm32wb_lptim_pwm.h new file mode 100644 index 0000000..d770ef7 --- /dev/null +++ b/wolfHAL/pwm/stm32wb_lptim_pwm.h @@ -0,0 +1,146 @@ +/* stm32wb_lptim_pwm.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHAL. + * + * wolfHAL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHAL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WHAL_STM32WB_LPTIM_PWM_H +#define WHAL_STM32WB_LPTIM_PWM_H + +#include +#include +#include +#include + +/* + * @file stm32wb_lptim_pwm.h + * @brief STM32WB LPTIM-based PWM driver configuration. + * + * Implements the generic whal_Pwm interface on top of an STM32WB + * low-power timer (LPTIM). The LPTIM has a single PWM output, so the only + * valid channel is WHAL_STM32WB_LPTIM_PWM_CHANNEL (0). The waveform is + * passed to Start as a whal_Pwm_ChannelCfg, which programs it and begins + * output; Stop halts it. The driver configuration holds the prescaler, clock + * source, and polling timeout. + * + * The LPTIM has no pulse counter, so only continuous output is supported: + * the waveform's pulseCount must be WHAL_PWM_PULSE_COUNT_CONTINUOUS, and any + * finite count is rejected with WHAL_ENOTSUP. + * + * The board is responsible for enabling the LPTIM kernel clock and + * selecting its source (LSE/LSI/HSI16/PCLK) via RCC before whal_Pwm_Init + * is called; this driver only touches LPTIM registers. The period and + * pulse are counted in ticks of that kernel clock divided by the + * configured prescaler. + */ + +/* The LPTIM exposes a single PWM output. */ +#define WHAL_STM32WB_LPTIM_PWM_CHANNEL 0 + +/* Clock source for LPTIM_CFGR.CKSEL. */ +#define WHAL_STM32WB_LPTIM_PWM_CLKSEL_INTERNAL 0 /* RCC-supplied kernel clock */ +#define WHAL_STM32WB_LPTIM_PWM_CLKSEL_EXTERNAL 1 /* external clock on LPTIM input */ + +/* + * @brief STM32WB LPTIM PWM device configuration. + */ +typedef struct whal_Stm32wb_Lptim_Pwm_Cfg { + /* Prescaler field value (LPTIM_CFGR.PRESC[2:0]): 0 = /1, 1 = /2, 2 = /4, + * ... 7 = /128. Divides the LPTIM kernel clock to form the PWM tick that + * the periodCycles / pulseCycles passed to Start are measured in. */ + uint8_t prescaler; + /* Clock source (LPTIM_CFGR.CKSEL): WHAL_STM32WB_LPTIM_PWM_CLKSEL_INTERNAL + * counts the RCC-supplied kernel clock; _EXTERNAL counts a clock applied + * on the LPTIM input. */ + uint8_t clkSel; + /* Timeout used while polling the ARROK / CMPOK update-synchronization + * flags after writing the autoreload and compare registers. */ + whal_Timeout *timeout; +} whal_Stm32wb_Lptim_Pwm_Cfg; + +/* + * @brief Driver instance for the STM32WB LPTIM PWM peripheral. + */ +extern const whal_PwmDriver whal_Stm32wb_Lptim_Pwm_Driver; + +/* + * @brief Initialize the LPTIM and apply the instance configuration. + * + * Configures the instance-static LPTIM settings (clock source, prescaler, + * PWM waveform) with the timer disabled. The per-call polarity + * and the ARR/CMP waveform are programmed by whal_Pwm_Start. The board must + * have enabled the LPTIM kernel clock beforehand. + * + * @param dev Pointer to the PWM instance to initialize. + * + * @retval WHAL_SUCCESS Initialization completed. + * @retval WHAL_EINVAL Null pointer or missing configuration. + * @retval WHAL_ENOTSUP Prescaler or clock source out of range. + */ +whal_Error whal_Stm32wb_Lptim_Pwm_Init(whal_Pwm *dev); +/* + * @brief Deinitialize the LPTIM PWM output. + * + * @param dev Pointer to the PWM instance to deinitialize. + * + * @retval WHAL_SUCCESS Deinitialization completed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_Stm32wb_Lptim_Pwm_Deinit(whal_Pwm *dev); +/* + * @brief Program the given waveform and start PWM output. + * + * Sets the output polarity and programs LPTIM_ARR / LPTIM_CMP from @p cfg's + * period and pulse (waiting for the ARROK / CMPOK synchronization flags), + * then sets LPTIM_CR.CNTSTRT to begin continuous counting. The clock source, + * prescaler, and PWM waveform mode are configured by Init. + * + * Because the LPTIM asserts its output while LPTIM_CNT exceeds LPTIM_CMP + * (and clears it on the ARR match), the compare value is derived as + * (ARR - pulseCycles). + * + * @param dev Pointer to the PWM instance. + * @param channel Must be WHAL_STM32WB_LPTIM_PWM_CHANNEL (0). + * @param cfg Waveform (period, pulse, polarity) to apply. + * + * @retval WHAL_SUCCESS Output started. + * @retval WHAL_EINVAL Null pointer, missing configuration, zero period, or + * pulse exceeding period. + * @retval WHAL_ENOTSUP Unsupported channel, period out of the 16-bit range, + * or a non-continuous pulse count. + * @retval WHAL_ETIMEOUT ARR/CMP update did not synchronize before timeout. + */ +whal_Error whal_Stm32wb_Lptim_Pwm_Start(whal_Pwm *dev, uint8_t channel, + const whal_Pwm_ChannelCfg *cfg); +/* + * @brief Stop the PWM output. + * + * Clears LPTIM_CR.ENABLE, which halts the counter and parks the output. + * whal_Pwm_Start resumes output without a re-Init; the Init-configured + * clock source, prescaler, and waveform mode are retained. + * + * @param dev Pointer to the PWM instance. + * @param channel Must be WHAL_STM32WB_LPTIM_PWM_CHANNEL (0). + * + * @retval WHAL_SUCCESS Output stopped. + * @retval WHAL_EINVAL Null pointer. + * @retval WHAL_ENOTSUP Unsupported channel. + */ +whal_Error whal_Stm32wb_Lptim_Pwm_Stop(whal_Pwm *dev, uint8_t channel); + +#endif /* WHAL_STM32WB_LPTIM_PWM_H */ diff --git a/wolfHAL/wolfHAL.h b/wolfHAL/wolfHAL.h index 3a9bceb..ccb9e45 100644 --- a/wolfHAL/wolfHAL.h +++ b/wolfHAL/wolfHAL.h @@ -60,5 +60,7 @@ #include #include #include +#include +#include #endif /* WOLFHAL_H */