From f18805a5bdd3549b4545b8f0125ea2c5b8042dfb Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 16:55:04 +0100 Subject: [PATCH 1/9] pbio/drv/display_nxt: Migrate nxos driver. Bring LCD driver into a dedicated pbio driver. No feature change yet, only apply code formatting. Refs: https://github.com/pybricks/support/issues/2425 --- bricks/_common/common.mk | 1 - bricks/_common/sources.mk | 1 + lib/pbio/drv/display/display_nxt.c | 357 ++++++++++++++++++++++ lib/pbio/platform/nxt/nxos/drivers/_lcd.c | 341 --------------------- lib/pbio/platform/nxt/pbdrvconfig.h | 3 +- lib/pbio/platform/nxt/platform.c | 2 - 6 files changed, 360 insertions(+), 345 deletions(-) create mode 100644 lib/pbio/drv/display/display_nxt.c delete mode 100644 lib/pbio/platform/nxt/nxos/drivers/_lcd.c diff --git a/bricks/_common/common.mk b/bricks/_common/common.mk index dd23d9063..ee33ac61f 100644 --- a/bricks/_common/common.mk +++ b/bricks/_common/common.mk @@ -485,7 +485,6 @@ NXOS_SRC_C = $(addprefix lib/pbio/platform/nxt/nxos/,\ assert.c \ display.c \ drivers/_efc.c \ - drivers/_lcd.c \ drivers/_twi.c \ drivers/_uart.c \ drivers/aic.c \ diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index c13c99f74..3cd93409d 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -143,6 +143,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ drv/counter/counter_nxt.c \ drv/counter/counter_stm32f0_gpio_quad_enc.c \ drv/display/display_ev3.c \ + drv/display/display_nxt.c \ drv/display/display_virtual.c \ drv/gpio/gpio_ev3.c \ drv/gpio/gpio_nxt.c \ diff --git a/lib/pbio/drv/display/display_nxt.c b/lib/pbio/drv/display/display_nxt.c new file mode 100644 index 000000000..789b385bf --- /dev/null +++ b/lib/pbio/drv/display/display_nxt.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2007 the NxOS developers +// See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. +// Copyright (c) 2025 The Pybricks Authors + +#include + +#if PBDRV_CONFIG_DISPLAY_NXT + +#include +#include +#include + +#include + +#include + +#include "nxos/lock.h" +#include "nxos/interrupts.h" +#include "nxos/drivers/systick.h" +#include "nxos/drivers/aic.h" + +#include "nxos/drivers/_lcd.h" + +/* + * Internal command bytes implementing part of the basic command set of + * the UC1601. + */ +#define SET_COLUMN_ADDR0(addr) (0x00 | (addr & 0xF)) +#define SET_COLUMN_ADDR1(addr) (0x10 | ((addr >> 4) & 0xF)) +#define SET_MULTIPLEX_RATE(rate) (0x20 | (rate & 3)) +#define SET_SCROLL_LINE(sl) (0x40 | (sl & 0x3F)) +#define SET_PAGE_ADDR(page) (0xB0 | (page & 0xF)) +#define SET_BIAS_POT0() (0x81) +#define SET_BIAS_POT1(pot) (pot & 0xFF) +#define SET_RAM_ADDR_CONTROL(auto_wrap, page_first, neg_inc, write_only_inc) \ + (0x88 | (auto_wrap << 0) | (page_first << 1) | \ + (neg_inc << 2) | (write_only_inc << 3)) +#define SET_ALL_PIXELS_ON(on) (0xA4 | (on & 1)) +#define SET_INVERSE_DISPLAY(on) (0xA6 | (on & 1)) +#define ENABLE(on) (0xAE | (on & 1)) +#define SET_MAP_CONTROL(mx, my) (0xC0 | (mx << 1) | (my << 2)) +#define RESET() (0xE2) +#define SET_BIAS_RATIO(bias) (0xE8 | (bias & 3)) + +/* + * SPI controller driver. + */ +typedef enum spi_mode { + COMMAND, + DATA +} spi_mode; + +/* + * The SPI device state. Contains some of the actual state of the bus, + * and transitory state for interrupt-driven DMA transfers. + */ +static volatile struct { + /* + * true if the SPI driver is configured for sending commands, false + * if it's configured for sending video data. + */ + spi_mode mode; + + /* + * A pointer to the in-memory screen framebuffer to mirror to + * screen, and a flag stating whether the in-memory buffer is dirty + * (new content needs mirroring to the LCD device. + */ + uint8_t *screen; + bool screen_dirty; + + /* + * State used by the display update code to manage the DMA + * transfer. + */ + uint8_t *data; + uint8_t page; + bool send_padding; +} spi_state = { + COMMAND, // We're initialized in command tx mode + NULL, // No screen buffer + false, // ... So obviously not dirty + NULL, // No current refresh data pointer + 0, // Current state: 1st data page... + false // And about to send display data +}; + +/* + * Set the data transmission mode. + */ +static void spi_set_tx_mode(spi_mode mode) { + if (spi_state.mode == mode) { + // Mode hasn't changed, no-op. + return; + } else { + // If there is a mode switch, we need to let the SPI controller + // drain all data first, to avoid spurious writes of the wrong + // type. + while (!(*AT91C_SPI_SR & AT91C_SPI_TXEMPTY)) { + ; + } + } + + spi_state.mode = mode; + + if (mode == COMMAND) { + *AT91C_PIOA_CODR = AT91C_PA12_MISO; + } else { + *AT91C_PIOA_SODR = AT91C_PA12_MISO; + } +} + +/* + * Send a command byte to the LCD controller. + */ +static void spi_write_command_byte(uint8_t command) { + spi_set_tx_mode(COMMAND); + + // Wait for the transmit register to empty. + while (!(*AT91C_SPI_SR & AT91C_SPI_TDRE)) { + ; + } + + // Send the command byte and wait for a reply. + *AT91C_SPI_TDR = command; +} + +/* + * Interrupt routine for handling DMA screen refreshing. + */ +static void spi_isr(void) { + // If we are in the initial state, determine whether we need to do a + // refresh cycle. + if (spi_state.page == 0 && !spi_state.send_padding) { + // Atomically retrieve the dirty flag and set it to false. This is + // to avoid race conditions where a set of the dirty flag could + // get squashed by the interrupt handler resetting it. + bool dirty = nx_atomic_cas8((uint8_t *)&(spi_state.screen_dirty), false); + spi_state.data = dirty ? spi_state.screen: NULL; + + // If the screen is not dirty, or if there is no screen pointer to + // source data from, then shut down the DMA refresh interrupt + // routine. It'll get reenabled by the screen dirtying function or + // the 1kHz interrupt update if the screen becomes dirty. + if (!spi_state.data) { + *AT91C_SPI_IDR = AT91C_SPI_ENDTX; + return; + } + } + + // Make sure that we are in data TX mode. This is a no-op if we + // already are, so it costs next to nothing to make sure of it at + // every interrupt. + spi_set_tx_mode(DATA); + + + if (!spi_state.send_padding) { + // We are at the start of a page, so we need to send 100 bytes of + // pixel data to display. We also set the state for the next + // interrupt, which is to send end-of-page padding. + spi_state.send_padding = true; + *AT91C_SPI_TNPR = (uint32_t)spi_state.data; + *AT91C_SPI_TNCR = 100; + } else { + // 100 bytes of displayable data have been transferred. We now + // have to send 32 more bytes to get to the end of the page and + // wrap around. We also set up the state for the next interrupt, + // which is to send the visible part of the next page of data. + // + // Given that this data is off-screen, we just resend the last 32 + // bytes of the 100 we just transferred. + spi_state.page = (spi_state.page + 1) % 8; + spi_state.data += 100; + spi_state.send_padding = false; + *AT91C_SPI_TNPR = (uint32_t)(spi_state.data - 32); + *AT91C_SPI_TNCR = 32; + } +} + +static void spi_init(void) { + uint32_t state = nx_interrupts_disable(); + + // Enable power to the SPI and PIO controllers. + *AT91C_PMC_PCER = (1 << AT91C_ID_SPI) | (1 << AT91C_ID_PIOA); + + // Configure the PIO controller: Hand the MOSI (Master Out, Slave + // In) and SPI clock pins over to the SPI controller, but keep MISO + // (Master In, Slave Out) and PA10 (Chip Select in this case) and + // configure them for manually driven output. + // + // The initial configuration is command mode (sending LCD commands) + // and the LCD controller chip not selected. + *AT91C_PIOA_PDR = AT91C_PA13_MOSI | AT91C_PA14_SPCK; + *AT91C_PIOA_ASR = AT91C_PA13_MOSI | AT91C_PA14_SPCK; + + *AT91C_PIOA_PER = AT91C_PA12_MISO | AT91C_PA10_NPCS2; + *AT91C_PIOA_OER = AT91C_PA12_MISO | AT91C_PA10_NPCS2; + *AT91C_PIOA_CODR = AT91C_PA12_MISO; + *AT91C_PIOA_SODR = AT91C_PA10_NPCS2; + + // Disable all SPI interrupts, then configure the SPI controller in + // master mode, with the chip select locked to chip 0 (UC1601 LCD + // controller), communication at 2MHz, 8 bits per transfer and an + // inactive-high clock signal. + *AT91C_SPI_CR = AT91C_SPI_SWRST; + *AT91C_SPI_CR = AT91C_SPI_SPIEN; + *AT91C_SPI_IDR = ~0; + *AT91C_SPI_MR = (6 << 24) | AT91C_SPI_MSTR; + AT91C_SPI_CSR[0] = ((0x18 << 24) | (0x18 << 16) | (0x18 << 8) | + AT91C_SPI_BITS_8 | AT91C_SPI_CPOL); + + // Now that the SPI bus is initialized, pull the Chip Select line + // low, to select the uc1601. For some reason, letting the SPI + // controller do this fails. Therefore, we force it once now. + *AT91C_PIOA_CODR = AT91C_PA10_NPCS2; + + // Install an interrupt handler for the SPI controller, and enable + // DMA transfers for SPI data. All SPI-related interrupt sources are + // inhibited, so it won't bother us until we're ready. + nx_aic_install_isr(AT91C_ID_SPI, AIC_PRIO_DRIVER, AIC_TRIG_LEVEL, spi_isr); + *AT91C_SPI_PTCR = AT91C_PDC_TXTEN; + + nx_interrupts_enable(state); +} + +void nx__lcd_fast_update(void) { + if (spi_state.screen_dirty) { + *AT91C_SPI_IER = AT91C_SPI_ENDTX; + } +} + +void nx__lcd_set_display(uint8_t *display) { + spi_state.screen = display; + *AT91C_SPI_IER = AT91C_SPI_ENDTX; +} + +void nx__lcd_dirty_display(void) { + spi_state.screen_dirty = true; +} + +void nx__lcd_shutdown(void) { + // When power to the controller goes out, there is the risk that + // some capacitors mounted around the controller might damage it + // when discharging in an uncontrolled fashion. To avoid this, the + // spec recommends putting the controller into reset mode before + // shutdown, which activates a drain circuit to empty the board + // capacitors gracefully. + *AT91C_SPI_IDR = ~0; + *AT91C_SPI_PTCR = AT91C_PDC_TXTDIS; + spi_write_command_byte(RESET()); + nx_systick_wait_ms(20); +} + +void nx__lcd_sync_refresh(void) { + int i, j; + + // Start the data transfer. + for (i = 0; i < 8; i++) { + spi_set_tx_mode(COMMAND); + spi_write_command_byte(SET_COLUMN_ADDR0(0)); + spi_write_command_byte(SET_COLUMN_ADDR1(0)); + spi_write_command_byte(SET_PAGE_ADDR(i)); + spi_set_tx_mode(DATA); + + for (j = 0; j < 100; j++) { + // Wait for the transmit register to empty. + while (!(*AT91C_SPI_SR & AT91C_SPI_TDRE)) { + ; + } + + // Send the command byte and wait for a reply. + *AT91C_SPI_TDR = spi_state.screen[i * 100 + j]; + } + } +} + +void pbdrv_display_init(void) { + uint32_t i; + // This is the command byte sequence that should be sent to the LCD + // after a reset. + const uint8_t lcd_init_sequence[] = { + // LCD power configuration. + // + // The LEGO Hardware Developer Kit documentation specifies that the + // display should be configured with a multiplex rate (MR) of 1/65, + // and a bias ratio (BR) of 1/9, and a display voltage V(LCD) of 9V. + // + // The specified MR and BR both map to simple command writes. V(LCD) + // however is determined by an equation that takes into account both + // the BR and the values of the PM (Potentiometer) and TC + // (Temperature Compensation) configuration parameters. + // + // The equation and calculations required are a little too complex + // to inline here, but the net result is that we should set a PM + // value of 92. This will result in a smooth voltage gradient, from + // 9.01V at -20 degrees Celsius to 8.66V at 60 degrees Celsius + // (close to the maximum operational range of the LCD display). + SET_MULTIPLEX_RATE(3), + SET_BIAS_RATIO(3), + SET_BIAS_POT0(), + SET_BIAS_POT1(92), + + // Set the RAM address control, which defines how the data we send + // to the LCD controller are placed in its internal video RAM. + // + // We want the bytes we send to be written in row-major order (line + // by line), with no automatic wrapping. + SET_RAM_ADDR_CONTROL(1, 0, 0, 0), + + // Set the LCD mapping mode, which defines how the data in video + // RAM is driven to the display. The display on the NXT is mounted + // upside down, so we want just Y mirroring. + SET_MAP_CONTROL(0, 1), + + // Set the initial position of the video memory cursor. We + // initialize it to point to the start of the screen. + SET_COLUMN_ADDR0(0), + SET_COLUMN_ADDR1(0), + SET_PAGE_ADDR(0), + + // Turn the display on. + ENABLE(1), + }; + + // Initialize the SPI controller to enable communication, then wait + // a little bit for the UC1601 to register the new SPI bus state. + spi_init(); + nx_systick_wait_ms(20); + + // Issue a reset command, and wait. Normally here we'd check the + // UC1601 status register, but as noted at the start of the file, we + // can't read from the LCD controller due to the board setup. + spi_write_command_byte(RESET()); + nx_systick_wait_ms(20); + + for (i = 0; i < sizeof(lcd_init_sequence); i++) { + spi_write_command_byte(lcd_init_sequence[i]); + } +} + +pbio_image_t *pbdrv_display_get_image(void) { + return NULL; +} + +uint8_t pbdrv_display_get_max_value(void) { + return 0; +} + +uint8_t pbdrv_display_get_value_from_hsv(uint16_t h, uint8_t s, uint8_t v) { + return 0; +} + +void pbdrv_display_update(void) { +} + +#endif // PBDRV_CONFIG_DISPLAY_NXT diff --git a/lib/pbio/platform/nxt/nxos/drivers/_lcd.c b/lib/pbio/platform/nxt/nxos/drivers/_lcd.c deleted file mode 100644 index 380c6b876..000000000 --- a/lib/pbio/platform/nxt/nxos/drivers/_lcd.c +++ /dev/null @@ -1,341 +0,0 @@ -/* Copyright (C) 2007 the NxOS developers - * - * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#include -#include -#include - -#include - -#include "nxos/lock.h" -#include "nxos/interrupts.h" -#include "nxos/drivers/systick.h" -#include "nxos/drivers/aic.h" - -#include "nxos/drivers/_lcd.h" - -/* Internal command bytes implementing part of the basic commandset of - * the UC1601. - */ -#define SET_COLUMN_ADDR0(addr) (0x00 | (addr & 0xF)) -#define SET_COLUMN_ADDR1(addr) (0x10 | ((addr >> 4) & 0xF)) -#define SET_MULTIPLEX_RATE(rate) (0x20 | (rate & 3)) -#define SET_SCROLL_LINE(sl) (0x40 | (sl & 0x3F)) -#define SET_PAGE_ADDR(page) (0xB0 | (page & 0xF)) -#define SET_BIAS_POT0() (0x81) -#define SET_BIAS_POT1(pot) (pot & 0xFF) -#define SET_RAM_ADDR_CONTROL(auto_wrap, page_first, neg_inc, write_only_inc) \ - (0x88 | (auto_wrap << 0) | (page_first << 1) | \ - (neg_inc << 2) | (write_only_inc << 3)) -#define SET_ALL_PIXELS_ON(on) (0xA4 | (on & 1)) -#define SET_INVERSE_DISPLAY(on) (0xA6 | (on & 1)) -#define ENABLE(on) (0xAE | (on & 1)) -#define SET_MAP_CONTROL(mx, my) (0xC0 | (mx << 1) | (my << 2)) -#define RESET() (0xE2) -#define SET_BIAS_RATIO(bias) (0xE8 | (bias & 3)) - -/* - * SPI controller driver. - */ -typedef enum spi_mode { - COMMAND, - DATA -} spi_mode; - -/* - * The SPI device state. Contains some of the actual state of the bus, - * and transitory state for interrupt-driven DMA transfers. - */ -static volatile struct { - /* true if the SPI driver is configured for sending commands, false - * if it's configured for sending video data. */ - spi_mode mode; - - /* A pointer to the in-memory screen framebuffer to mirror to - * screen, and a flag stating whether the in-memory buffer is dirty - * (new content needs mirroring to the LCD device. - */ - uint8_t *screen; - bool screen_dirty; - - /* State used by the display update code to manage the DMA - * transfer. */ - uint8_t *data; - uint8_t page; - bool send_padding; -} spi_state = { - COMMAND, /* We're initialized in command tx mode */ - NULL, /* No screen buffer */ - false, /* ... So obviously not dirty */ - NULL, /* No current refresh data pointer */ - 0, /* Current state: 1st data page... */ - false /* And about to send display data */ -}; - -/* - * Set the data transmission mode. - */ -static void spi_set_tx_mode(spi_mode mode) { - if (spi_state.mode == mode) - /* Mode hasn't changed, no-op. */ - return; - else - /* If there is a mode switch, we need to let the SPI controller - * drain all data first, to avoid spurious writes of the wrong - * type. - */ - while(!(*AT91C_SPI_SR & AT91C_SPI_TXEMPTY)); - - spi_state.mode = mode; - - if (mode == COMMAND) { - *AT91C_PIOA_CODR = AT91C_PA12_MISO; - } else { - *AT91C_PIOA_SODR = AT91C_PA12_MISO; - } -} - -/* - * Send a command byte to the LCD controller. - */ -static void spi_write_command_byte(uint8_t command) { - spi_set_tx_mode(COMMAND); - - /* Wait for the transmit register to empty. */ - while (!(*AT91C_SPI_SR & AT91C_SPI_TDRE)); - - /* Send the command byte and wait for a reply. */ - *AT91C_SPI_TDR = command; -} - -/* Interrupt routine for handling DMA screen refreshing. */ -static void spi_isr(void) { - /* If we are in the initial state, determine whether we need to do a - * refresh cycle. - */ - if (spi_state.page == 0 && !spi_state.send_padding) { - /* Atomically retrieve the dirty flag and set it to false. This is - * to avoid race conditions where a set of the dirty flag could - * get squashed by the interrupt handler resetting it. - */ - bool dirty = nx_atomic_cas8((uint8_t*)&(spi_state.screen_dirty), false); - spi_state.data = dirty ? spi_state.screen: NULL; - - /* If the screen is not dirty, or if there is no screen pointer to - * source data from, then shut down the DMA refresh interrupt - * routine. It'll get reenabled by the screen dirtying function or - * the 1kHz interrupt update if the screen becomes dirty. - */ - if (!spi_state.data) { - *AT91C_SPI_IDR = AT91C_SPI_ENDTX; - return; - } - } - - /* Make sure that we are in data TX mode. This is a no-op if we - * already are, so it costs next to nothing to make sure of it at - * every interrupt. - */ - spi_set_tx_mode(DATA); - - - if (!spi_state.send_padding) { - /* We are at the start of a page, so we need to send 100 bytes of - * pixel data to display. We also set the state for the next - * interrupt, which is to send end-of-page padding. - */ - spi_state.send_padding = true; - *AT91C_SPI_TNPR = (uint32_t)spi_state.data; - *AT91C_SPI_TNCR = 100; - } else { - /* 100 bytes of displayable data have been transferred. We now - * have to send 32 more bytes to get to the end of the page and - * wrap around. We also set up the state for the next interrupt, - * which is to send the visible part of the next page of data. - * - * Given that this data is off-screen, we just resend the last 32 - * bytes of the 100 we just transferred. - */ - spi_state.page = (spi_state.page + 1) % 8; - spi_state.data += 100; - spi_state.send_padding = false; - *AT91C_SPI_TNPR = (uint32_t)(spi_state.data - 32); - *AT91C_SPI_TNCR = 32; - } -} - -static void spi_init(void) { - uint32_t state = nx_interrupts_disable(); - - /* Enable power to the SPI and PIO controllers. */ - *AT91C_PMC_PCER = (1 << AT91C_ID_SPI) | (1 << AT91C_ID_PIOA); - - /* Configure the PIO controller: Hand the MOSI (Master Out, Slave - * In) and SPI clock pins over to the SPI controller, but keep MISO - * (Master In, Slave Out) and PA10 (Chip Select in this case) and - * configure them for manually driven output. - * - * The initial configuration is command mode (sending LCD commands) - * and the LCD controller chip not selected. - */ - *AT91C_PIOA_PDR = AT91C_PA13_MOSI | AT91C_PA14_SPCK; - *AT91C_PIOA_ASR = AT91C_PA13_MOSI | AT91C_PA14_SPCK; - - *AT91C_PIOA_PER = AT91C_PA12_MISO | AT91C_PA10_NPCS2; - *AT91C_PIOA_OER = AT91C_PA12_MISO | AT91C_PA10_NPCS2; - *AT91C_PIOA_CODR = AT91C_PA12_MISO; - *AT91C_PIOA_SODR = AT91C_PA10_NPCS2; - - /* Disable all SPI interrupts, then configure the SPI controller in - * master mode, with the chip select locked to chip 0 (UC1601 LCD - * controller), communication at 2MHz, 8 bits per transfer and an - * inactive-high clock signal. - */ - *AT91C_SPI_CR = AT91C_SPI_SWRST; - *AT91C_SPI_CR = AT91C_SPI_SPIEN; - *AT91C_SPI_IDR = ~0; - *AT91C_SPI_MR = (6 << 24) | AT91C_SPI_MSTR; - AT91C_SPI_CSR[0] = ((0x18 << 24) | (0x18 << 16) | (0x18 << 8) | - AT91C_SPI_BITS_8 | AT91C_SPI_CPOL); - - /* Now that the SPI bus is initialized, pull the Chip Select line - * low, to select the uc1601. For some reason, letting the SPI - * controller do this fails. Therefore, we force it once now. - */ - *AT91C_PIOA_CODR = AT91C_PA10_NPCS2; - - /* Install an interrupt handler for the SPI controller, and enable - * DMA transfers for SPI data. All SPI-related interrupt sources are - * inhibited, so it won't bother us until we're ready. - */ - nx_aic_install_isr(AT91C_ID_SPI, AIC_PRIO_DRIVER, AIC_TRIG_LEVEL, spi_isr); - *AT91C_SPI_PTCR = AT91C_PDC_TXTEN; - - nx_interrupts_enable(state); -} - -/* Initialize the LCD controller. */ -void nx__lcd_init(void) { - uint32_t i; - /* This is the command byte sequence that should be sent to the LCD - * after a reset. - */ - const uint8_t lcd_init_sequence[] = { - /* LCD power configuration. - * - * The LEGO Hardware Developer Kit documentation specifies that the - * display should be configured with a multiplex rate (MR) of 1/65, - * and a bias ratio (BR) of 1/9, and a display voltage V(LCD) of 9V. - * - * The specified MR and BR both map to simple command writes. V(LCD) - * however is determined by an equation that takes into account both - * the BR and the values of the PM (Potentiometer) and TC - * (Temperature Compensation) configuration parameters. - * - * The equation and calculations required are a little too complex - * to inline here, but the net result is that we should set a PM - * value of 92. This will result in a smooth voltage gradient, from - * 9.01V at -20 degrees Celsius to 8.66V at 60 degrees Celsius - * (close to the maximum operational range of the LCD display). - */ - SET_MULTIPLEX_RATE(3), - SET_BIAS_RATIO(3), - SET_BIAS_POT0(), - SET_BIAS_POT1(92), - - /* Set the RAM address control, which defines how the data we send - * to the LCD controller are placed in its internal video RAM. - * - * We want the bytes we send to be written in row-major order (line - * by line), with no automatic wrapping. - */ - SET_RAM_ADDR_CONTROL(1, 0, 0, 0), - - /* Set the LCD mapping mode, which defines how the data in video - * RAM is driven to the display. The display on the NXT is mounted - * upside down, so we want just Y mirroring. - */ - SET_MAP_CONTROL(0, 1), - - /* Set the initial position of the video memory cursor. We - * initialize it to point to the start of the screen. - */ - SET_COLUMN_ADDR0(0), - SET_COLUMN_ADDR1(0), - SET_PAGE_ADDR(0), - - /* Turn the display on. */ - ENABLE(1), - }; - - /* Initialize the SPI controller to enable communication, then wait - * a little bit for the UC1601 to register the new SPI bus state. - */ - spi_init(); - nx_systick_wait_ms(20); - - /* Issue a reset command, and wait. Normally here we'd check the - * UC1601 status register, but as noted at the start of the file, we - * can't read from the LCD controller due to the board setup. - */ - spi_write_command_byte(RESET()); - nx_systick_wait_ms(20); - - for (i=0; i #include #include -#include #include #include #include @@ -159,7 +158,6 @@ void SystemInit(void) { // TODO: we should be able to convert these to generic pbio drivers and use // pbio_busy_count_busy instead of busy waiting for 100ms. nx__motors_init(); - nx__lcd_init(); nx__display_init(); // nx__sensors_init(); // nx_i2c_init(); From f954c7a98f17eaeb09b356ee5e9766c30e853ad2 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 19:36:47 +0100 Subject: [PATCH 2/9] pbio/drv/display: Add pbdrv_display_deinit to shutdown the display. This is needed on NXT to avoid damaging the display. Call it in pbdrv_deinit. Refs: https://github.com/pybricks/support/issues/2425 --- lib/pbio/drv/core.c | 1 + lib/pbio/drv/display/display.h | 4 ++++ lib/pbio/drv/display/display_ev3.c | 3 +++ lib/pbio/drv/display/display_nxt.c | 26 +++++++++++------------ lib/pbio/drv/display/display_virtual.c | 3 +++ lib/pbio/platform/nxt/nxos/drivers/_lcd.h | 9 -------- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/pbio/drv/core.c b/lib/pbio/drv/core.c index b733da317..e5ebb1fb1 100644 --- a/lib/pbio/drv/core.c +++ b/lib/pbio/drv/core.c @@ -84,6 +84,7 @@ void pbdrv_init(void) { void pbdrv_deinit(void) { pbdrv_imu_deinit(); + pbdrv_display_deinit(); pbdrv_bluetooth_deinit(); pbdrv_usb_deinit(); diff --git a/lib/pbio/drv/display/display.h b/lib/pbio/drv/display/display.h index e0c5bd9ca..fa9f0ab8c 100644 --- a/lib/pbio/drv/display/display.h +++ b/lib/pbio/drv/display/display.h @@ -12,12 +12,16 @@ #if PBDRV_CONFIG_DISPLAY void pbdrv_display_init(void); +void pbdrv_display_deinit(void); #else // PBDRV_CONFIG_DISPLAY static inline void pbdrv_display_init(void) { } +static inline void pbdrv_display_deinit(void) { +} + #endif // PBDRV_CONFIG_DISPLAY #endif // _INTERNAL_PBDRV_DISPLAY_H_ diff --git a/lib/pbio/drv/display/display_ev3.c b/lib/pbio/drv/display/display_ev3.c index cdc5eb885..61a3905e7 100644 --- a/lib/pbio/drv/display/display_ev3.c +++ b/lib/pbio/drv/display/display_ev3.c @@ -519,4 +519,7 @@ void pbdrv_display_update(void) { pbio_os_request_poll(); } +void pbdrv_display_deinit(void) { +} + #endif // PBDRV_CONFIG_DISPLAY_EV3 diff --git a/lib/pbio/drv/display/display_nxt.c b/lib/pbio/drv/display/display_nxt.c index 789b385bf..415704eca 100644 --- a/lib/pbio/drv/display/display_nxt.c +++ b/lib/pbio/drv/display/display_nxt.c @@ -239,19 +239,6 @@ void nx__lcd_dirty_display(void) { spi_state.screen_dirty = true; } -void nx__lcd_shutdown(void) { - // When power to the controller goes out, there is the risk that - // some capacitors mounted around the controller might damage it - // when discharging in an uncontrolled fashion. To avoid this, the - // spec recommends putting the controller into reset mode before - // shutdown, which activates a drain circuit to empty the board - // capacitors gracefully. - *AT91C_SPI_IDR = ~0; - *AT91C_SPI_PTCR = AT91C_PDC_TXTDIS; - spi_write_command_byte(RESET()); - nx_systick_wait_ms(20); -} - void nx__lcd_sync_refresh(void) { int i, j; @@ -354,4 +341,17 @@ uint8_t pbdrv_display_get_value_from_hsv(uint16_t h, uint8_t s, uint8_t v) { void pbdrv_display_update(void) { } +void pbdrv_display_deinit(void) { + // When power to the controller goes out, there is the risk that + // some capacitors mounted around the controller might damage it + // when discharging in an uncontrolled fashion. To avoid this, the + // spec recommends putting the controller into reset mode before + // shutdown, which activates a drain circuit to empty the board + // capacitors gracefully. + *AT91C_SPI_IDR = ~0; + *AT91C_SPI_PTCR = AT91C_PDC_TXTDIS; + spi_write_command_byte(RESET()); + nx_systick_wait_ms(20); +} + #endif // PBDRV_CONFIG_DISPLAY_NXT diff --git a/lib/pbio/drv/display/display_virtual.c b/lib/pbio/drv/display/display_virtual.c index b0c1047e4..7364fd78b 100644 --- a/lib/pbio/drv/display/display_virtual.c +++ b/lib/pbio/drv/display/display_virtual.c @@ -111,4 +111,7 @@ void pbdrv_display_update(void) { pbio_os_request_poll(); } +void pbdrv_display_deinit(void) { +} + #endif // PBDRV_CONFIG_DISPLAY_VIRTUAL diff --git a/lib/pbio/platform/nxt/nxos/drivers/_lcd.h b/lib/pbio/platform/nxt/nxos/drivers/_lcd.h index f70e83401..45162a24e 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/_lcd.h +++ b/lib/pbio/platform/nxt/nxos/drivers/_lcd.h @@ -61,15 +61,6 @@ void nx__lcd_set_display(uint8_t *display_buffer); /** Mark the display as requiring a refresh cycle. */ void nx__lcd_dirty_display(void); -/** Safely power off the LCD controller. - * - * The LCD controller must be powered off this way in order to drain - * several capacitors connected to the display. Failure to do so may - * damage the LCD display (although in practice the screen seems to - * take hard poweroffs fairly happily). - */ -void nx__lcd_shutdown(void); - /** Display an abort message. * * This will take the kernel offline (the technical term for "crash") From 57adc3edbf4cddf2c1d3fef8ffb5bcd5eeaa486f Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 22:02:47 +0100 Subject: [PATCH 3/9] pbio/drv/display_nxt: Comments, constants and enum cleanup. Refs: https://github.com/pybricks/support/issues/2425 --- lib/pbio/drv/display/display_nxt.c | 39 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/pbio/drv/display/display_nxt.c b/lib/pbio/drv/display/display_nxt.c index 415704eca..ba781c609 100644 --- a/lib/pbio/drv/display/display_nxt.c +++ b/lib/pbio/drv/display/display_nxt.c @@ -44,12 +44,14 @@ #define SET_BIAS_RATIO(bias) (0xE8 | (bias & 3)) /* - * SPI controller driver. + * SPI mode, command or data. */ -typedef enum spi_mode { - COMMAND, - DATA -} spi_mode; +typedef enum { + /* Command. */ + SPI_MODE_COMMAND, + /* Data. */ + SPI_MODE_DATA, +} spi_mode_t; /* * The SPI device state. Contains some of the actual state of the bus, @@ -60,7 +62,7 @@ static volatile struct { * true if the SPI driver is configured for sending commands, false * if it's configured for sending video data. */ - spi_mode mode; + spi_mode_t mode; /* * A pointer to the in-memory screen framebuffer to mirror to @@ -78,7 +80,7 @@ static volatile struct { uint8_t page; bool send_padding; } spi_state = { - COMMAND, // We're initialized in command tx mode + SPI_MODE_COMMAND, // We're initialized in command tx mode NULL, // No screen buffer false, // ... So obviously not dirty NULL, // No current refresh data pointer @@ -89,7 +91,7 @@ static volatile struct { /* * Set the data transmission mode. */ -static void spi_set_tx_mode(spi_mode mode) { +static void spi_set_tx_mode(spi_mode_t mode) { if (spi_state.mode == mode) { // Mode hasn't changed, no-op. return; @@ -104,7 +106,7 @@ static void spi_set_tx_mode(spi_mode mode) { spi_state.mode = mode; - if (mode == COMMAND) { + if (mode == SPI_MODE_COMMAND) { *AT91C_PIOA_CODR = AT91C_PA12_MISO; } else { *AT91C_PIOA_SODR = AT91C_PA12_MISO; @@ -115,7 +117,7 @@ static void spi_set_tx_mode(spi_mode mode) { * Send a command byte to the LCD controller. */ static void spi_write_command_byte(uint8_t command) { - spi_set_tx_mode(COMMAND); + spi_set_tx_mode(SPI_MODE_COMMAND); // Wait for the transmit register to empty. while (!(*AT91C_SPI_SR & AT91C_SPI_TDRE)) { @@ -152,7 +154,7 @@ static void spi_isr(void) { // Make sure that we are in data TX mode. This is a no-op if we // already are, so it costs next to nothing to make sure of it at // every interrupt. - spi_set_tx_mode(DATA); + spi_set_tx_mode(SPI_MODE_DATA); if (!spi_state.send_padding) { @@ -243,21 +245,21 @@ void nx__lcd_sync_refresh(void) { int i, j; // Start the data transfer. - for (i = 0; i < 8; i++) { - spi_set_tx_mode(COMMAND); + for (i = 0; i < PBDRV_CONFIG_DISPLAY_NUM_ROWS / 8; i++) { + spi_set_tx_mode(SPI_MODE_COMMAND); spi_write_command_byte(SET_COLUMN_ADDR0(0)); spi_write_command_byte(SET_COLUMN_ADDR1(0)); spi_write_command_byte(SET_PAGE_ADDR(i)); - spi_set_tx_mode(DATA); + spi_set_tx_mode(SPI_MODE_DATA); - for (j = 0; j < 100; j++) { + for (j = 0; j < PBDRV_CONFIG_DISPLAY_NUM_COLS; j++) { // Wait for the transmit register to empty. while (!(*AT91C_SPI_SR & AT91C_SPI_TDRE)) { ; } - // Send the command byte and wait for a reply. - *AT91C_SPI_TDR = spi_state.screen[i * 100 + j]; + // Send the data. + *AT91C_SPI_TDR = spi_state.screen[i * PBDRV_CONFIG_DISPLAY_NUM_COLS + j]; } } } @@ -266,7 +268,7 @@ void pbdrv_display_init(void) { uint32_t i; // This is the command byte sequence that should be sent to the LCD // after a reset. - const uint8_t lcd_init_sequence[] = { + static const uint8_t lcd_init_sequence[] = { // LCD power configuration. // // The LEGO Hardware Developer Kit documentation specifies that the @@ -321,6 +323,7 @@ void pbdrv_display_init(void) { spi_write_command_byte(RESET()); nx_systick_wait_ms(20); + // Send every command of the init sequence. for (i = 0; i < sizeof(lcd_init_sequence); i++) { spi_write_command_byte(lcd_init_sequence[i]); } From fdb8150f7ccee56dc09dd47d8a37fb6e3181317c Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 22:15:19 +0100 Subject: [PATCH 4/9] pbio/drv/display_nxt: Convert to pbio model. Use a PBIO thread to handle the screen refresh instead of doing it in ISR. The driver provides the frame buffer which is used by the user. On refresh, format is converted to match the one used by the LCD driver. In the future, the user frame buffer may use the LCD driver format in order to improve memory usage. This will require pbio/image to handle this format. Refs: https://github.com/pybricks/support/issues/2425 --- lib/pbio/drv/clock/clock_nxt.c | 6 - lib/pbio/drv/display/display_nxt.c | 266 ++++++++++++++-------- lib/pbio/platform/nxt/nxos/display.c | 4 - lib/pbio/platform/nxt/nxos/drivers/_lcd.h | 19 -- lib/pbio/platform/nxt/pbioconfig.h | 1 + 5 files changed, 166 insertions(+), 130 deletions(-) diff --git a/lib/pbio/drv/clock/clock_nxt.c b/lib/pbio/drv/clock/clock_nxt.c index 51b631396..cd990d61d 100644 --- a/lib/pbio/drv/clock/clock_nxt.c +++ b/lib/pbio/drv/clock/clock_nxt.c @@ -15,7 +15,6 @@ #include #include #include -#include /* The main clock is at 48MHz, and the PIT divides that by 16 to get * its base timer frequency. @@ -41,11 +40,6 @@ static void systick_isr(void) { /* Do the system timekeeping. */ pbdrv_clock_ticks++; pbio_os_request_poll(); - - /* The LCD dirty display routine can be done here too, since it is - * very short. - */ - nx__lcd_fast_update(); } void pbdrv_clock_init(void) { diff --git a/lib/pbio/drv/display/display_nxt.c b/lib/pbio/drv/display/display_nxt.c index ba781c609..7e276e2eb 100644 --- a/lib/pbio/drv/display/display_nxt.c +++ b/lib/pbio/drv/display/display_nxt.c @@ -2,6 +2,21 @@ // Copyright (C) 2007 the NxOS developers // See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. // Copyright (c) 2025 The Pybricks Authors +// +// This driver contains a basic SPI driver to talk to the UltraChip +// 1601 LCD controller, as well as a higher level API implementing the +// UC1601's commandset. +// +// Note that the SPI driver is not suitable as a general-purpose SPI +// driver: the MISO pin (Master-In Slave-Out) is instead wired to the +// UC1601's CD input (used to select whether the transferred data is +// control commands or display data). Thus, the SPI driver here takes +// manual control of the MISO pin, and drives it depending on the type +// of data being transferred. +// +// This also means that you can only write to the UC1601, not read +// back from it. This is not too much of a problem, as we can just +// introduce a little delay in the places where we really need it. #include @@ -10,9 +25,13 @@ #include #include #include +#include #include +#include +#include + #include #include "nxos/lock.h" @@ -54,45 +73,54 @@ typedef enum { } spi_mode_t; /* - * The SPI device state. Contains some of the actual state of the bus, - * and transitory state for interrupt-driven DMA transfers. + * SPI state. + */ +typedef enum { + /* Operation started, bus is busy. */ + SPI_STATE_WAIT, + /* Operation complete, bus is idle. */ + SPI_STATE_COMPLETE, +} spi_state_t; + +/* + * Current SPI mode, set with spi_set_tx_mode(). + */ +static spi_mode_t spi_mode; + +/* + * Current SPI state. + */ +static volatile spi_state_t spi_state; + +/* + * User frame buffer. Each value is one pixel with value: + * + * 0: Empty / White + * 1: Black + */ +static uint8_t pbdrv_display_user_frame[PBDRV_CONFIG_DISPLAY_NUM_ROWS][PBDRV_CONFIG_DISPLAY_NUM_COLS] +__attribute__((section(".noinit"))); + +/* + * Flag to indicate that the user frame has been updated and needs to be + * encoded and sent to the display driver. */ -static volatile struct { - /* - * true if the SPI driver is configured for sending commands, false - * if it's configured for sending video data. - */ - spi_mode_t mode; - - /* - * A pointer to the in-memory screen framebuffer to mirror to - * screen, and a flag stating whether the in-memory buffer is dirty - * (new content needs mirroring to the LCD device. - */ - uint8_t *screen; - bool screen_dirty; - - /* - * State used by the display update code to manage the DMA - * transfer. - */ - uint8_t *data; - uint8_t page; - bool send_padding; -} spi_state = { - SPI_MODE_COMMAND, // We're initialized in command tx mode - NULL, // No screen buffer - false, // ... So obviously not dirty - NULL, // No current refresh data pointer - 0, // Current state: 1st data page... - false // And about to send display data -}; +static bool pbdrv_display_user_frame_update_requested; + +/* + * Buffer to send one page of pixels to the display, using its internal + * format. Every byte describes a column of 8 pixels, where the least + * significant bit is used for the top pixel and the most significant bit is + * used for the bottom pixel. If 1, the pixel is lit, or black. If 0, the + * pixel is not lit, or white. + */ +static uint8_t pbdrv_display_send_buffer[PBDRV_CONFIG_DISPLAY_NUM_COLS]; /* * Set the data transmission mode. */ static void spi_set_tx_mode(spi_mode_t mode) { - if (spi_state.mode == mode) { + if (spi_mode == mode) { // Mode hasn't changed, no-op. return; } else { @@ -104,7 +132,7 @@ static void spi_set_tx_mode(spi_mode_t mode) { } } - spi_state.mode = mode; + spi_mode = mode; if (mode == SPI_MODE_COMMAND) { *AT91C_PIOA_CODR = AT91C_PA12_MISO; @@ -129,60 +157,22 @@ static void spi_write_command_byte(uint8_t command) { } /* - * Interrupt routine for handling DMA screen refreshing. + * Interrupt routine, called when Tx is done. */ static void spi_isr(void) { - // If we are in the initial state, determine whether we need to do a - // refresh cycle. - if (spi_state.page == 0 && !spi_state.send_padding) { - // Atomically retrieve the dirty flag and set it to false. This is - // to avoid race conditions where a set of the dirty flag could - // get squashed by the interrupt handler resetting it. - bool dirty = nx_atomic_cas8((uint8_t *)&(spi_state.screen_dirty), false); - spi_state.data = dirty ? spi_state.screen: NULL; - - // If the screen is not dirty, or if there is no screen pointer to - // source data from, then shut down the DMA refresh interrupt - // routine. It'll get reenabled by the screen dirtying function or - // the 1kHz interrupt update if the screen becomes dirty. - if (!spi_state.data) { - *AT91C_SPI_IDR = AT91C_SPI_ENDTX; - return; - } - } - - // Make sure that we are in data TX mode. This is a no-op if we - // already are, so it costs next to nothing to make sure of it at - // every interrupt. - spi_set_tx_mode(SPI_MODE_DATA); - - - if (!spi_state.send_padding) { - // We are at the start of a page, so we need to send 100 bytes of - // pixel data to display. We also set the state for the next - // interrupt, which is to send end-of-page padding. - spi_state.send_padding = true; - *AT91C_SPI_TNPR = (uint32_t)spi_state.data; - *AT91C_SPI_TNCR = 100; - } else { - // 100 bytes of displayable data have been transferred. We now - // have to send 32 more bytes to get to the end of the page and - // wrap around. We also set up the state for the next interrupt, - // which is to send the visible part of the next page of data. - // - // Given that this data is off-screen, we just resend the last 32 - // bytes of the 100 we just transferred. - spi_state.page = (spi_state.page + 1) % 8; - spi_state.data += 100; - spi_state.send_padding = false; - *AT91C_SPI_TNPR = (uint32_t)(spi_state.data - 32); - *AT91C_SPI_TNCR = 32; - } + // Disable interrupts, this event is handled in thread context. + *AT91C_SPI_IDR = AT91C_SPI_ENDTX; + // Signal the thread context. + spi_state = SPI_STATE_COMPLETE; + pbio_os_request_poll(); } static void spi_init(void) { uint32_t state = nx_interrupts_disable(); + spi_mode = SPI_MODE_COMMAND; + spi_state = SPI_STATE_COMPLETE; + // Enable power to the SPI and PIO controllers. *AT91C_PMC_PCER = (1 << AT91C_ID_SPI) | (1 << AT91C_ID_PIOA); @@ -226,21 +216,19 @@ static void spi_init(void) { nx_interrupts_enable(state); } -void nx__lcd_fast_update(void) { - if (spi_state.screen_dirty) { - *AT91C_SPI_IER = AT91C_SPI_ENDTX; +void pbdrv_display_nxt_convert_page(int page) { + int x, y; + for (x = 0; x < PBDRV_CONFIG_DISPLAY_NUM_COLS; x++) { + uint8_t b = 0; + for (y = 0; y < 8; y++) { + if (pbdrv_display_user_frame[page * 8 + y][x]) { + b |= 1 << y; + } + } + pbdrv_display_send_buffer[x] = b; } } -void nx__lcd_set_display(uint8_t *display) { - spi_state.screen = display; - *AT91C_SPI_IER = AT91C_SPI_ENDTX; -} - -void nx__lcd_dirty_display(void) { - spi_state.screen_dirty = true; -} - void nx__lcd_sync_refresh(void) { int i, j; @@ -252,6 +240,8 @@ void nx__lcd_sync_refresh(void) { spi_write_command_byte(SET_PAGE_ADDR(i)); spi_set_tx_mode(SPI_MODE_DATA); + pbdrv_display_nxt_convert_page(i); + for (j = 0; j < PBDRV_CONFIG_DISPLAY_NUM_COLS; j++) { // Wait for the transmit register to empty. while (!(*AT91C_SPI_SR & AT91C_SPI_TDRE)) { @@ -259,13 +249,22 @@ void nx__lcd_sync_refresh(void) { } // Send the data. - *AT91C_SPI_TDR = spi_state.screen[i * PBDRV_CONFIG_DISPLAY_NUM_COLS + j]; + *AT91C_SPI_TDR = pbdrv_display_send_buffer[j]; } } } -void pbdrv_display_init(void) { - uint32_t i; +static pbio_os_process_t pbdrv_display_nxt_process; + +/* + * Display driver process. Initialize the display and updates the display with + * the user frame buffer if updated. + */ +static pbio_error_t pbdrv_display_nxt_process_thread(pbio_os_state_t *state, void *context) { + static pbio_os_timer_t timer; + static size_t i; + static int page; + // This is the command byte sequence that should be sent to the LCD // after a reset. static const uint8_t lcd_init_sequence[] = { @@ -312,36 +311,101 @@ void pbdrv_display_init(void) { ENABLE(1), }; + PBIO_OS_ASYNC_BEGIN(state); + // Initialize the SPI controller to enable communication, then wait // a little bit for the UC1601 to register the new SPI bus state. spi_init(); - nx_systick_wait_ms(20); + PBIO_OS_AWAIT_MS(state, &timer, 20); // Issue a reset command, and wait. Normally here we'd check the // UC1601 status register, but as noted at the start of the file, we // can't read from the LCD controller due to the board setup. spi_write_command_byte(RESET()); - nx_systick_wait_ms(20); + PBIO_OS_AWAIT_MS(state, &timer, 20); // Send every command of the init sequence. for (i = 0; i < sizeof(lcd_init_sequence); i++) { spi_write_command_byte(lcd_init_sequence[i]); } + + // Clear display to start with. + memset(&pbdrv_display_user_frame, 0, sizeof(pbdrv_display_user_frame)); + pbdrv_display_user_frame_update_requested = true; + + // Make sure that we are in data TX mode. + spi_set_tx_mode(SPI_MODE_DATA); + + // Done initializing. + pbio_busy_count_down(); + + // Update the display with the user frame buffer, if changed. + for (;;) { + PBIO_OS_AWAIT_UNTIL(state, pbdrv_display_user_frame_update_requested); + pbdrv_display_user_frame_update_requested = false; + for (page = 0; page < PBDRV_CONFIG_DISPLAY_NUM_ROWS / 8; page++) { + // Convert pixel format. + pbdrv_display_nxt_convert_page(page); + // We are at the start of a page, so we need to send 100 bytes of + // pixel data to display. + spi_state = SPI_STATE_WAIT; + *AT91C_SPI_TNPR = (uint32_t)pbdrv_display_send_buffer; + *AT91C_SPI_TNCR = PBDRV_CONFIG_DISPLAY_NUM_COLS; + *AT91C_SPI_IER = AT91C_SPI_ENDTX; + PBIO_OS_AWAIT_UNTIL(state, spi_state == SPI_STATE_COMPLETE); + // 100 bytes of displayable data have been transferred. We now + // have to send 32 more bytes to get to the end of the page and + // wrap around. + // + // Given that this data is off-screen, we just resend the first 32 + // bytes of the 100 we just transferred. + spi_state = SPI_STATE_WAIT; + *AT91C_SPI_TNPR = (uint32_t)pbdrv_display_send_buffer; + *AT91C_SPI_TNCR = 132 - PBDRV_CONFIG_DISPLAY_NUM_COLS; + *AT91C_SPI_IER = AT91C_SPI_ENDTX; + PBIO_OS_AWAIT_UNTIL(state, spi_state == SPI_STATE_COMPLETE); + } + } + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + +// Image corresponding to the display. +static pbio_image_t pbdrv_display_image; + +void pbdrv_display_init(void) { + // Initialize image. + pbio_image_init(&pbdrv_display_image, (uint8_t *)pbdrv_display_user_frame, + PBDRV_CONFIG_DISPLAY_NUM_COLS, PBDRV_CONFIG_DISPLAY_NUM_ROWS, + PBDRV_CONFIG_DISPLAY_NUM_COLS); + pbdrv_display_image.print_font = &pbio_font_mono_8x5_8; + pbdrv_display_image.print_value = 1; + + // Start display process and ask pbdrv to wait until it is initialized. + pbio_busy_count_up(); + pbio_os_process_start(&pbdrv_display_nxt_process, pbdrv_display_nxt_process_thread, NULL); } pbio_image_t *pbdrv_display_get_image(void) { - return NULL; + return &pbdrv_display_image; } uint8_t pbdrv_display_get_max_value(void) { - return 0; + return 1; } uint8_t pbdrv_display_get_value_from_hsv(uint16_t h, uint8_t s, uint8_t v) { - return 0; + uint16_t l_x100 = v * (100 - s / 2); + if (l_x100 < 50 * 100) { + return 1; + } else { + return 0; + } } void pbdrv_display_update(void) { + pbdrv_display_user_frame_update_requested = true; + pbio_os_request_poll(); } void pbdrv_display_deinit(void) { diff --git a/lib/pbio/platform/nxt/nxos/display.c b/lib/pbio/platform/nxt/nxos/display.c index 5b660cfee..aaa85f053 100644 --- a/lib/pbio/platform/nxt/nxos/display.c +++ b/lib/pbio/platform/nxt/nxos/display.c @@ -46,8 +46,6 @@ static struct { static inline void dirty_display(void) { - if (display.auto_refresh) - nx__lcd_dirty_display(); } @@ -70,7 +68,6 @@ void nx_display_auto_refresh(bool auto_refresh) { * auto-refresh is disabled. */ inline void nx_display_refresh(void) { - nx__lcd_dirty_display(); } @@ -201,7 +198,6 @@ void nx__display_init(void) { display.cursor.x = 0; display.cursor.y = 0; display.cursor.ignore_lf = false; - nx__lcd_set_display(&display.buffer[0][0]); display.auto_refresh = true; dirty_display(); } diff --git a/lib/pbio/platform/nxt/nxos/drivers/_lcd.h b/lib/pbio/platform/nxt/nxos/drivers/_lcd.h index 45162a24e..c1ccb0e36 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/_lcd.h +++ b/lib/pbio/platform/nxt/nxos/drivers/_lcd.h @@ -42,25 +42,6 @@ /** Height of the LCD display, in bytes. */ #define LCD_HEIGHT 8 /* == 64 pixels. */ -/** Initialize the LCD driver. */ -void nx__lcd_init(void); - -/** Periodic update function, called once every millisecond. - * - * @warning This is called by the systick driver, and shouldn't be - * invoked directly unless you really know what you are doing. - */ -void nx__lcd_fast_update(void); - -/** Set the virtual display to mirror to the screen. - * - * @param display_buffer The screen buffer to mirror. - */ -void nx__lcd_set_display(uint8_t *display_buffer); - -/** Mark the display as requiring a refresh cycle. */ -void nx__lcd_dirty_display(void); - /** Display an abort message. * * This will take the kernel offline (the technical term for "crash") diff --git a/lib/pbio/platform/nxt/pbioconfig.h b/lib/pbio/platform/nxt/pbioconfig.h index e83e98c60..919478320 100644 --- a/lib/pbio/platform/nxt/pbioconfig.h +++ b/lib/pbio/platform/nxt/pbioconfig.h @@ -5,6 +5,7 @@ #define PBIO_CONFIG_DCMOTOR (1) #define PBIO_CONFIG_DCMOTOR_NUM_DEV (3) #define PBIO_CONFIG_DRIVEBASE_SPIKE (0) +#define PBIO_CONFIG_IMAGE (1) #define PBIO_CONFIG_IMU (0) #define PBIO_CONFIG_LIGHT (0) #define PBIO_CONFIG_LOGGER (1) From f45cc8efea2ec0f3954fc304841a7d7f4d44a2d4 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 23:37:40 +0100 Subject: [PATCH 5/9] pbio/platform/nxt: Remove nxos display functions. Remove unused debug display code. Use pbio image and driver instead of nxos display functions when appropriate. Refs: https://github.com/pybricks/support/issues/2425 --- bricks/_common/common.mk | 1 - lib/pbio/drv/display/display_nxt.c | 4 +- lib/pbio/drv/display/display_nxt.h | 11 + lib/pbio/drv/reset/reset_nxt.c | 1 - lib/pbio/include/pbio/image.h | 4 + lib/pbio/platform/nxt/nxos/_abort.c | 26 ++- lib/pbio/platform/nxt/nxos/_display.h | 33 --- lib/pbio/platform/nxt/nxos/_font.h | 163 -------------- lib/pbio/platform/nxt/nxos/assert.c | 26 ++- lib/pbio/platform/nxt/nxos/display.c | 203 ------------------ lib/pbio/platform/nxt/nxos/display.h | 105 --------- lib/pbio/platform/nxt/nxos/drivers/_lcd.h | 55 ----- lib/pbio/platform/nxt/nxos/drivers/bt.c | 11 - lib/pbio/platform/nxt/nxos/drivers/bt.h | 4 - lib/pbio/platform/nxt/nxos/drivers/i2c.c | 19 +- .../platform/nxt/nxos/drivers/i2c_memory.c | 1 - lib/pbio/platform/nxt/nxos/drivers/radar.c | 36 ---- lib/pbio/platform/nxt/nxos/drivers/radar.h | 10 - lib/pbio/platform/nxt/platform.c | 4 - lib/pbio/src/image/image.c | 14 ++ 20 files changed, 71 insertions(+), 660 deletions(-) create mode 100644 lib/pbio/drv/display/display_nxt.h delete mode 100644 lib/pbio/platform/nxt/nxos/_display.h delete mode 100644 lib/pbio/platform/nxt/nxos/_font.h delete mode 100644 lib/pbio/platform/nxt/nxos/display.c delete mode 100644 lib/pbio/platform/nxt/nxos/display.h delete mode 100644 lib/pbio/platform/nxt/nxos/drivers/_lcd.h diff --git a/bricks/_common/common.mk b/bricks/_common/common.mk index ee33ac61f..eb6a86aeb 100644 --- a/bricks/_common/common.mk +++ b/bricks/_common/common.mk @@ -483,7 +483,6 @@ endif NXOS_SRC_C = $(addprefix lib/pbio/platform/nxt/nxos/,\ _abort.c \ assert.c \ - display.c \ drivers/_efc.c \ drivers/_twi.c \ drivers/_uart.c \ diff --git a/lib/pbio/drv/display/display_nxt.c b/lib/pbio/drv/display/display_nxt.c index 7e276e2eb..45aa3d497 100644 --- a/lib/pbio/drv/display/display_nxt.c +++ b/lib/pbio/drv/display/display_nxt.c @@ -39,8 +39,6 @@ #include "nxos/drivers/systick.h" #include "nxos/drivers/aic.h" -#include "nxos/drivers/_lcd.h" - /* * Internal command bytes implementing part of the basic command set of * the UC1601. @@ -229,7 +227,7 @@ void pbdrv_display_nxt_convert_page(int page) { } } -void nx__lcd_sync_refresh(void) { +void pbdrv_display_nxt_sync_refresh(void) { int i, j; // Start the data transfer. diff --git a/lib/pbio/drv/display/display_nxt.h b/lib/pbio/drv/display/display_nxt.h new file mode 100644 index 000000000..c1b293a20 --- /dev/null +++ b/lib/pbio/drv/display/display_nxt.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#ifndef _INTERNAL_PBDRV_DISPLAY_NXT_H_ +#define _INTERNAL_PBDRV_DISPLAY_NXT_H_ + +#include + +void pbdrv_display_nxt_sync_refresh(void); + +#endif // _INTERNAL_PBDRV_DISPLAY_NXT_H_ diff --git a/lib/pbio/drv/reset/reset_nxt.c b/lib/pbio/drv/reset/reset_nxt.c index 80ba554c2..15bdfc7dc 100644 --- a/lib/pbio/drv/reset/reset_nxt.c +++ b/lib/pbio/drv/reset/reset_nxt.c @@ -8,7 +8,6 @@ #if PBDRV_CONFIG_RESET_NXT #include -#include #include #include diff --git a/lib/pbio/include/pbio/image.h b/lib/pbio/include/pbio/image.h index 893aa594d..fd7b31b4c 100644 --- a/lib/pbio/include/pbio/image.h +++ b/lib/pbio/include/pbio/image.h @@ -147,6 +147,8 @@ void pbio_image_bbox_text(const pbio_font_t *font, const char *text, void pbio_image_print(pbio_image_t *image, const char *text, size_t text_len); +void pbio_image_print0(pbio_image_t *image, const char *text); + void pbio_image_print_uint(pbio_image_t *image, uint32_t number); void pbio_image_print_int(pbio_image_t *image, int32_t number); @@ -195,6 +197,8 @@ static inline void pbio_image_bbox_text(const pbio_font_t *font, const char *tex } static inline void pbio_image_print(pbio_image_t *image, const char *text, size_t text_len) { } +static inline void pbio_image_print0(pbio_image_t *image, const char *text) { +} static inline void pbio_image_print_uint(pbio_image_t *image, uint32_t number) { } static inline void pbio_image_print_int(pbio_image_t *image, int32_t number) { diff --git a/lib/pbio/platform/nxt/nxos/_abort.c b/lib/pbio/platform/nxt/nxos/_abort.c index 571cf0bde..9078daf04 100644 --- a/lib/pbio/platform/nxt/nxos/_abort.c +++ b/lib/pbio/platform/nxt/nxos/_abort.c @@ -9,25 +9,29 @@ #include #include -#include "nxos/display.h" +#include + +#include + #include "nxos/interrupts.h" -#include "nxos/drivers/_lcd.h" #include "nxos/_abort.h" +#include "../../../drv/display/display_nxt.h" + void nx__abort(bool data, uint32_t pc, uint32_t cpsr) { nx_interrupts_disable(); - nx_display_auto_refresh(false); - nx_display_clear(); + pbio_image_t *display = pbdrv_display_get_image(); + pbio_image_fill(display, 0); if (data) { - nx_display_string("Data"); + pbio_image_print0(display, "Data"); } else { - nx_display_string("Prefetch"); + pbio_image_print0(display, "Prefetch"); } - nx_display_string(" abort\nPC: "); - nx_display_hex(pc); - nx_display_string("\nCPSR: "); - nx_display_hex(cpsr); - nx__lcd_sync_refresh(); + pbio_image_print0(display, " abort\nPC: "); + pbio_image_print_hex(display, pc); + pbio_image_print0(display, "\nCPSR: "); + pbio_image_print_hex(display, cpsr); + pbdrv_display_nxt_sync_refresh(); while(1); } diff --git a/lib/pbio/platform/nxt/nxos/_display.h b/lib/pbio/platform/nxt/nxos/_display.h deleted file mode 100644 index 55a7b931e..000000000 --- a/lib/pbio/platform/nxt/nxos/_display.h +++ /dev/null @@ -1,33 +0,0 @@ -/** @file _display.h - * @brief Internal display APIs. - */ - -/* Copyright (c) 2007,2008 the NxOS developers - * - * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ -#ifndef __NXOS_BASE__DISPLAY_H__ -#define __NXOS_BASE__DISPLAY_H__ - -#include "nxos/display.h" - -/** @addtogroup kernelinternal */ -/*@{*/ - -/** @defgroup displayinternal Display internals - * - * The display's initialization function is private, since the baseplate - * is the only one that should be able to initialize it. - */ -/*@{*/ - -/** Initialize the display driver. */ -void nx__display_init(void); - -/*@}*/ -/*@}*/ - -#endif /* __NXOS_BASE__DISPLAY_H__ */ diff --git a/lib/pbio/platform/nxt/nxos/_font.h b/lib/pbio/platform/nxt/nxos/_font.h deleted file mode 100644 index d969d2024..000000000 --- a/lib/pbio/platform/nxt/nxos/_font.h +++ /dev/null @@ -1,163 +0,0 @@ -/** @file _font.h - * @brief Embedded font data. - */ - -/* Copyright (c) 2008 the NxOS developers - * - * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#ifndef __NXOS_BASE__FONT_H__ -#define __NXOS_BASE__FONT_H__ - -#include - -#include "nxos/drivers/_lcd.h" - -/** @addtogroup kernelinternal */ -/*@{*/ - -/** @defgroup fontinternal Font data - * - * A basic font is embedded into the baseplate, for use by the display - * driver. - * - * @note This data is automatically generated from a "font grid" image - * at compile time. This makes it easy to tweak the looks of the font by - * editing an image, and have it nicely embedded in the Baseplate. - */ -/*@{*/ - -/** The ASCII offset of the first character in the font table. - * - * All characters before that one are unprintable. - */ -#define NX__FONT_START 0x20 - -/** The ASCII offset of the last character in the font table. - * - * All characters after that one are unprintable. - */ -#define NX__FONT_END 0x80 - -/** The width of a font character in pixels. */ -#define NX__FONT_WIDTH 5 -/** The width of a font cell, which includes a 1-pixel spacer. */ -#define NX__CELL_WIDTH (NX__FONT_WIDTH + 1) -/** The number of font cells per line. */ -#define NX__DISPLAY_WIDTH_CELLS (LCD_WIDTH / NX__CELL_WIDTH) -/** The number of fond lines on the display. */ -#define NX__DISPLAY_HEIGHT_CELLS LCD_HEIGHT - -/** The font character data. - * - * Each entry in the font array is a subarray of length @b - * FONT_WIDTH. Each element in that array gives the bitmask for one - * vertical slice of the character in question. - */ -static const uint8_t nx__font_data[][NX__FONT_WIDTH] = { - { 0x00, 0x00, 0x00, 0x00, 0x00 }, - { 0x00, 0x00, 0x5F, 0x00, 0x00 }, - { 0x00, 0x07, 0x00, 0x07, 0x00 }, - { 0x14, 0x3E, 0x14, 0x3E, 0x14 }, - { 0x55, 0xAA, 0x55, 0xAA, 0x55 }, - { 0x26, 0x16, 0x08, 0x34, 0x32 }, - { 0xAA, 0x55, 0xAA, 0x55, 0xAA }, - { 0x00, 0x00, 0x07, 0x00, 0x00 }, - { 0x00, 0x1C, 0x22, 0x41, 0x00 }, - { 0x00, 0x41, 0x22, 0x1C, 0x00 }, - { 0x2A, 0x1C, 0x7F, 0x1C, 0x2A }, - { 0x08, 0x08, 0x3E, 0x08, 0x08 }, - { 0x00, 0x50, 0x30, 0x00, 0x00 }, - { 0x08, 0x08, 0x08, 0x08, 0x08 }, - { 0x00, 0x60, 0x60, 0x00, 0x00 }, - { 0x20, 0x10, 0x08, 0x04, 0x02 }, - { 0x3E, 0x51, 0x49, 0x45, 0x3E }, - { 0x00, 0x42, 0x7F, 0x40, 0x00 }, - { 0x42, 0x61, 0x51, 0x49, 0x46 }, - { 0x21, 0x41, 0x45, 0x4B, 0x31 }, - { 0x18, 0x14, 0x12, 0x7F, 0x10 }, - { 0x27, 0x45, 0x45, 0x45, 0x39 }, - { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, - { 0x01, 0x01, 0x79, 0x05, 0x03 }, - { 0x36, 0x49, 0x49, 0x49, 0x36 }, - { 0x06, 0x49, 0x49, 0x29, 0x1E }, - { 0x00, 0x36, 0x36, 0x00, 0x00 }, - { 0x00, 0x56, 0x36, 0x00, 0x00 }, - { 0x08, 0x14, 0x22, 0x41, 0x00 }, - { 0x14, 0x14, 0x14, 0x14, 0x14 }, - { 0x41, 0x22, 0x14, 0x08, 0x00 }, - { 0x02, 0x01, 0x59, 0x05, 0x02 }, - { 0x3E, 0x41, 0x49, 0x55, 0x1E }, - { 0x7E, 0x09, 0x09, 0x09, 0x7E }, - { 0x7F, 0x49, 0x49, 0x49, 0x3E }, - { 0x3E, 0x41, 0x41, 0x41, 0x22 }, - { 0x7F, 0x41, 0x41, 0x22, 0x1C }, - { 0x7F, 0x49, 0x49, 0x49, 0x41 }, - { 0x7F, 0x09, 0x09, 0x09, 0x01 }, - { 0x3E, 0x41, 0x41, 0x49, 0x3A }, - { 0x7F, 0x08, 0x08, 0x08, 0x7F }, - { 0x00, 0x41, 0x7F, 0x41, 0x00 }, - { 0x20, 0x40, 0x41, 0x3F, 0x01 }, - { 0x7F, 0x08, 0x14, 0x22, 0x41 }, - { 0x7F, 0x40, 0x40, 0x40, 0x40 }, - { 0x7F, 0x02, 0x04, 0x02, 0x7F }, - { 0x7F, 0x04, 0x08, 0x10, 0x7F }, - { 0x3E, 0x41, 0x41, 0x41, 0x3E }, - { 0x7F, 0x09, 0x09, 0x09, 0x06 }, - { 0x3E, 0x41, 0x51, 0x21, 0x5E }, - { 0x7F, 0x09, 0x19, 0x29, 0x46 }, - { 0x26, 0x49, 0x49, 0x49, 0x32 }, - { 0x01, 0x01, 0x7F, 0x01, 0x01 }, - { 0x3F, 0x40, 0x40, 0x40, 0x3F }, - { 0x1F, 0x20, 0x40, 0x20, 0x1F }, - { 0x7F, 0x20, 0x18, 0x20, 0x7F }, - { 0x63, 0x14, 0x08, 0x14, 0x63 }, - { 0x03, 0x04, 0x78, 0x04, 0x03 }, - { 0x61, 0x51, 0x49, 0x45, 0x43 }, - { 0x00, 0x7F, 0x41, 0x41, 0x00 }, - { 0x02, 0x04, 0x08, 0x10, 0x20 }, - { 0x00, 0x41, 0x41, 0x7F, 0x00 }, - { 0x04, 0x02, 0x01, 0x02, 0x04 }, - { 0x40, 0x40, 0x40, 0x40, 0x40 }, - { 0x00, 0x01, 0x02, 0x04, 0x00 }, - { 0x20, 0x54, 0x54, 0x54, 0x78 }, - { 0x7F, 0x48, 0x44, 0x44, 0x38 }, - { 0x30, 0x48, 0x48, 0x48, 0x20 }, - { 0x38, 0x44, 0x44, 0x48, 0x7F }, - { 0x38, 0x54, 0x54, 0x54, 0x18 }, - { 0x08, 0x7E, 0x09, 0x09, 0x02 }, - { 0x0C, 0x52, 0x52, 0x52, 0x3E }, - { 0x7F, 0x08, 0x04, 0x04, 0x78 }, - { 0x00, 0x44, 0x7D, 0x40, 0x00 }, - { 0x20, 0x40, 0x40, 0x3D, 0x00 }, - { 0x7F, 0x10, 0x28, 0x44, 0x00 }, - { 0x00, 0x41, 0x7F, 0x40, 0x00 }, - { 0x7C, 0x04, 0x18, 0x04, 0x78 }, - { 0x7C, 0x08, 0x04, 0x04, 0x78 }, - { 0x38, 0x44, 0x44, 0x44, 0x38 }, - { 0xFC, 0x14, 0x14, 0x14, 0x08 }, - { 0x08, 0x14, 0x14, 0x18, 0x7C }, - { 0x7C, 0x08, 0x04, 0x04, 0x08 }, - { 0x48, 0x54, 0x54, 0x54, 0x20 }, - { 0x04, 0x3F, 0x44, 0x40, 0x20 }, - { 0x3C, 0x40, 0x40, 0x20, 0x7C }, - { 0x1C, 0x20, 0x40, 0x20, 0x1C }, - { 0x3C, 0x40, 0x38, 0x40, 0x3C }, - { 0x44, 0x28, 0x10, 0x28, 0x44 }, - { 0x0C, 0x50, 0x50, 0x50, 0x3C }, - { 0x44, 0x64, 0x54, 0x4C, 0x44 }, - { 0x00, 0x08, 0x36, 0x41, 0x00 }, - { 0x00, 0x00, 0x7F, 0x00, 0x00 }, - { 0x00, 0x41, 0x36, 0x08, 0x00 }, - { 0x00, 0x07, 0x00, 0x07, 0x00 }, - { 0x08, 0x1C, 0x2A, 0x08, 0x08 }, -}; - -/*@}*/ -/*@}*/ - -#endif /* __NXOS_BASE__FONT_H__ */ diff --git a/lib/pbio/platform/nxt/nxos/assert.c b/lib/pbio/platform/nxt/nxos/assert.c index c2ece8dda..ca1d1a0bc 100644 --- a/lib/pbio/platform/nxt/nxos/assert.c +++ b/lib/pbio/platform/nxt/nxos/assert.c @@ -9,30 +9,28 @@ #include #include +#include + #include +#include -#include "nxos/display.h" #include "nxos/util.h" #include "nxos/drivers/systick.h" #include "nxos/assert.h" +#include "../../../drv/display/display_nxt.h" + void nx_assert_error(const char *file, const int line, const char *expr, const char *msg) { const char *basename = strrchr(file, '/'); basename = basename ? basename+1 : file; - nx_display_clear(); - nx_display_string("** Assertion **\n"); - - nx_display_string(basename); - nx_display_string(":"); - nx_display_uint(line); - nx_display_end_line(); - - nx_display_string(expr); - nx_display_end_line(); - - nx_display_string(msg); - nx_display_end_line(); + pbio_image_t *display = pbdrv_display_get_image(); + pbio_image_fill(display, 0); + pbio_image_print0(display, "** Assertion **\n"); + pbio_image_printf(display, "%s:%d\n", basename, line); + pbio_image_printf(display, "%s\n", expr); + pbio_image_printf(display, "%s\n", msg); + pbdrv_display_nxt_sync_refresh(); } diff --git a/lib/pbio/platform/nxt/nxos/display.c b/lib/pbio/platform/nxt/nxos/display.c deleted file mode 100644 index aaa85f053..000000000 --- a/lib/pbio/platform/nxt/nxos/display.c +++ /dev/null @@ -1,203 +0,0 @@ -/* Copyright (c) 2008 the NxOS developers - * - * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#include -#include -#include - -#include "nxos/interrupts.h" -#include "nxos/util.h" -#include "nxos/assert.h" -#include "nxos/drivers/systick.h" -#include "nxos/drivers/aic.h" -#include "nxos/drivers/_lcd.h" - -#include "nxos/_display.h" - -/* A simple 8x5 font. This is in a separate file because the embedded - * font is converted from a .png at compile time. - */ -#include "_font.h" - -static struct { - /* The display buffer, which is mirrored to the LCD controller's RAM. */ - uint8_t buffer[LCD_HEIGHT][LCD_WIDTH]; - - /* Whether the display is automatically refreshed after every call - * to display functions. */ - bool auto_refresh; - - /* The position of the text cursor. This is used for easy displaying - * of text in a console-like manner. - */ - struct { - uint8_t x; - uint8_t y; - bool ignore_lf; /* If the display just wrapped from the right side - of the screen, ignore an LF immediately - after. */ - } cursor; -} display; - - -static inline void dirty_display(void) { -} - - -/* Clear the display. */ -void nx_display_clear(void) { - memset(&display.buffer[0][0], 0, sizeof(display.buffer)); - nx_display_cursor_set_pos(0, 0); - dirty_display(); -} - - -/* Enable or disable auto-refresh. */ -void nx_display_auto_refresh(bool auto_refresh) { - display.auto_refresh = auto_refresh; - dirty_display(); -} - - -/* Explicitely refresh the display. You only need to use this when - * auto-refresh is disabled. - */ -inline void nx_display_refresh(void) { -} - - -/* - * Text display functions. - */ -static inline bool is_on_screen(uint8_t x, uint8_t y) { - if (x < NX__DISPLAY_WIDTH_CELLS && - y < NX__DISPLAY_HEIGHT_CELLS) - return true; - else - return false; -} - -static inline const uint8_t *char_to_font(const char c) { - if (c >= NX__FONT_START) - return nx__font_data[c - NX__FONT_START]; - else - return nx__font_data[0]; /* Unprintable characters become spaces. */ -} - -static inline void update_cursor(bool inc_y) { - if (!inc_y) { - display.cursor.x++; - - if (display.cursor.x >= LCD_WIDTH / NX__CELL_WIDTH) { - display.cursor.x = 0; - display.cursor.y++; - display.cursor.ignore_lf = true; - } else { - display.cursor.ignore_lf = false; - } - } else if (display.cursor.ignore_lf) { - display.cursor.ignore_lf = false; - } else { - display.cursor.x = 0; - display.cursor.y++; - } - - if (display.cursor.y >= LCD_HEIGHT) - display.cursor.y = 0; -} - -void nx_display_cursor_set_pos(uint8_t x, uint8_t y) { - NX_ASSERT(is_on_screen(x, y)); - display.cursor.x = x; - display.cursor.y = y; -} - -inline void nx_display_end_line(void) { - update_cursor(true); -} - -void nx_display_string(const char *str) { - while (*str != '\0') { - if (*str == '\n') - update_cursor(true); - else { - int x_offset = display.cursor.x * NX__CELL_WIDTH; - memcpy(&display.buffer[display.cursor.y][x_offset], - char_to_font(*str), NX__FONT_WIDTH); - update_cursor(false); - } - str++; - } - dirty_display(); -} - -void nx_display_hex(uint32_t val) { - const char hex[16] = "0123456789ABCDEF"; - char buf[9]; - char *ptr = &buf[8]; - - if (val == 0) { - ptr--; - *ptr = hex[0]; - } else { - while (val != 0) { - ptr--; - *ptr = hex[val & 0xF]; - val >>= 4; - } - } - - buf[8] = '\0'; - - nx_display_string(ptr); - dirty_display(); -} - -void nx_display_uint(uint32_t val) { - char buf[11]; - char *ptr = &buf[10]; - - if (val == 0) { - ptr--; - *ptr = '0'; - } else { - - while (val > 0) { - ptr--; - *ptr = val % 10 + '0'; - val /= 10; - } - - } - - buf[10] = '\0'; - - nx_display_string(ptr); - dirty_display(); -} - -void nx_display_int(int32_t val) { - if( val < 0 ) { - nx_display_string("-"); - val = -val; - } - nx_display_uint(val); -} - -/* - * Display initialization. - */ -void nx__display_init(void) { - display.auto_refresh = false; - nx_display_clear(); - display.cursor.x = 0; - display.cursor.y = 0; - display.cursor.ignore_lf = false; - display.auto_refresh = true; - dirty_display(); -} diff --git a/lib/pbio/platform/nxt/nxos/display.h b/lib/pbio/platform/nxt/nxos/display.h deleted file mode 100644 index 6d8b7e9f7..000000000 --- a/lib/pbio/platform/nxt/nxos/display.h +++ /dev/null @@ -1,105 +0,0 @@ -/** @file display.h - * @brief Display handling interface. - */ - -/* Copyright (C) 2007 the NxOS developers - * - * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#ifndef __NXOS_BASE_DISPLAY_H__ -#define __NXOS_BASE_DISPLAY_H__ - -#include -#include - -/** @addtogroup kernel */ -/*@{*/ - -/** @defgroup display Display - * - * The shenanigans of handling refreshes of the LCD display are neatly - * abstracted away by NxOS. The display is an abstract sheet of memory, - * which can be poked using various functions to display things. - * - * One important part of the abstraction is the cursor. It is similar to - * ncurse's notion of a cursor: it is where the next character will be - * printed. The cursor is initially in the top-left corner of the - * screen, will move automatically as you write things to the screen, - * and automatically wraps when you hit either the right or bottom edge - * of the screen. - * - * The screen is initially in auto-refresh mode: any time you write - * anything to the screen, the physical LCD is automatically refreshed. - * Auto-refresh can be disabled, in which case the physical screen will - * only refresh itself on an explicit call to nx_display_refresh(). - */ -/*@{*/ - -/** Clear the display and reset the cursor to (0,0). */ -void nx_display_clear(void); - -/** Set the refreshing mode of the display. - * - * @param auto_refresh true if the display should be refreshed - * implicitely when written to, false if you want to give explicit - * refresh commands. - */ -void nx_display_auto_refresh(bool auto_refresh); - -/** Start a display refresh cycle. - * - * @note This call has very little effect if the display is in - * auto-refresh mode: a refresh cycle will be triggered, but since the - * display is being refreshed anyway, you probably won't notice a - * difference. - */ -void nx_display_refresh(void); - -/** Move the cursor to line @a x and column @a y. - * - * @param x The cursor's new line position. - * @param y The cursor's new column position. - * - * @note Both @a x and @a y are zero-indexed: (0,0) is the top-left - * corner of the display. - */ -void nx_display_cursor_set_pos(uint8_t x, uint8_t y); - -/** Print a single line feed. */ -void nx_display_end_line(void); - -/** Display @a str at the current cursor position. - * - * @param str The string to display. - * - * @note Any unprintable characters in the string are rendered as - * spaces. - */ -void nx_display_string(const char *str); - -/** Display @a val as a hexadecimal number. - * - * @param val The number to display in hex. - */ -void nx_display_hex(uint32_t val); - -/** Display @a val as a (unsigned) decimal number. - * - * @param val The number to display. - */ -void nx_display_uint(uint32_t val); - -/** Display @a val as a (signed) decimal number. - * - * @param val The number to display. - */ -void nx_display_int(int32_t val); - -/*@}*/ -/*@}*/ - -#endif /* __NXOS_BASE_DISPLAY_H__ */ diff --git a/lib/pbio/platform/nxt/nxos/drivers/_lcd.h b/lib/pbio/platform/nxt/nxos/drivers/_lcd.h deleted file mode 100644 index c1ccb0e36..000000000 --- a/lib/pbio/platform/nxt/nxos/drivers/_lcd.h +++ /dev/null @@ -1,55 +0,0 @@ -/** @file _lcd.h - * @brief LCD controller interface. - */ - -/* Copyright (C) 2007 the NxOS developers - * - * See lib/pbio/platform/nxt/nxos/AUTHORS for a full list of the developers. - * - * Redistribution of this file is permitted under - * the terms of the GNU Public License (GPL) version 2. - */ - -#ifndef __NXOS_BASE_DRIVERS__LCD_H__ -#define __NXOS_BASE_DRIVERS__LCD_H__ - -#include - -/** @addtogroup driverinternal */ -/*@{*/ - -/** @defgroup lcdinternal LCD controller - * - * This driver contains a basic SPI driver to talk to the UltraChip - * 1601 LCD controller, as well as a higher level API implementing the - * UC1601's commandset. - * - * Note that the SPI driver is not suitable as a general-purpose SPI - * driver: the MISO pin (Master-In Slave-Out) is instead wired to the - * UC1601's CD input (used to select whether the transferred data is - * control commands or display data). Thus, the SPI driver here takes - * manual control of the MISO pin, and drives it depending on the type - * of data being transferred. - * - * This also means that you can only write to the UC1601, not read - * back from it. This is not too much of a problem, as we can just - * introduce a little delay in the places where we really need it. - */ -/*@{*/ - -/** Width of the LCD display, in pixels. */ -#define LCD_WIDTH 100 -/** Height of the LCD display, in bytes. */ -#define LCD_HEIGHT 8 /* == 64 pixels. */ - -/** Display an abort message. - * - * This will take the kernel offline (the technical term for "crash") - * after displaying an abort message. - */ -void nx__lcd_sync_refresh(void); - -/*@}*/ -/*@}*/ - -#endif /* __NXOS_BASE_DRIVERS__LCD_H__ */ diff --git a/lib/pbio/platform/nxt/nxos/drivers/bt.c b/lib/pbio/platform/nxt/nxos/drivers/bt.c index 0f5791ea2..8546a7fad 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/bt.c +++ b/lib/pbio/platform/nxt/nxos/drivers/bt.c @@ -14,7 +14,6 @@ #include #include "nxos/util.h" -#include "nxos/display.h" #include "nxos/drivers/systick.h" #include "nxos/drivers/_uart.h" @@ -971,13 +970,3 @@ void nx_bt_stream_close(void) } - -/* to remove: */ -void nx_bt_debug(void) -{ - //nx_display_uint(bt_state.last_heartbeat); - //nx_display_end_line(); - USB_SEND((char *)bt_state.cmds); -} - - diff --git a/lib/pbio/platform/nxt/nxos/drivers/bt.h b/lib/pbio/platform/nxt/nxos/drivers/bt.h index 56c987b73..0f8526afb 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/bt.h +++ b/lib/pbio/platform/nxt/nxos/drivers/bt.h @@ -237,8 +237,4 @@ uint32_t nx_bt_stream_data_read(void); void nx_bt_stream_close(void); -/* to remove */ -void nx_bt_debug(void); - - #endif diff --git a/lib/pbio/platform/nxt/nxos/drivers/i2c.c b/lib/pbio/platform/nxt/nxos/drivers/i2c.c index 784fea472..ebe4f067c 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/i2c.c +++ b/lib/pbio/platform/nxt/nxos/drivers/i2c.c @@ -7,6 +7,10 @@ #include #include +#include + +#include + #include #define I2C_LOG false @@ -14,7 +18,6 @@ #include "nxos/nxt.h" #include "nxos/interrupts.h" #include "nxos/util.h" -#include "nxos/display.h" #include "nxos/drivers/aic.h" #include "nxos/drivers/_sensors.h" #include "nxos/drivers/i2c.h" @@ -170,14 +173,20 @@ void nx_i2c_init(void) { static void i2c_log(const char *s) { - if (I2C_LOG) - nx_display_string(s); + if (I2C_LOG) { + pbio_image_t *display = pbdrv_display_get_image(); + pbio_image_print0(display, s); + pbdrv_display_update(); + } } static void i2c_log_uint(uint32_t val) { - if (I2C_LOG) - nx_display_uint(val); + if (I2C_LOG) { + pbio_image_t *display = pbdrv_display_get_image(); + pbio_image_print_uint(display, val); + pbdrv_display_update(); + } } /** Register a remote device (by its address) on the given sensor. */ diff --git a/lib/pbio/platform/nxt/nxos/drivers/i2c_memory.c b/lib/pbio/platform/nxt/nxos/drivers/i2c_memory.c index 7d02f2fc1..f20599384 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/i2c_memory.c +++ b/lib/pbio/platform/nxt/nxos/drivers/i2c_memory.c @@ -14,7 +14,6 @@ #include "nxos/nxt.h" #include "nxos/util.h" -#include "nxos/display.h" #include "nxos/drivers/sensors.h" #include "nxos/drivers/systick.h" #include "nxos/drivers/i2c.h" diff --git a/lib/pbio/platform/nxt/nxos/drivers/radar.c b/lib/pbio/platform/nxt/nxos/drivers/radar.c index 84799d075..b92bed96f 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/radar.c +++ b/lib/pbio/platform/nxt/nxos/drivers/radar.c @@ -16,7 +16,6 @@ #include "nxos/nxt.h" #include "nxos/interrupts.h" -#include "nxos/display.h" #include "nxos/util.h" #include "nxos/drivers/i2c_memory.h" #include "nxos/drivers/radar.h" @@ -172,38 +171,3 @@ bool nx_radar_set_op_mode(uint32_t sensor, radar_op_mode mode) { uint8_t val = mode; return nx_radar_write(sensor, RADAR_OP_MODE, &val); } - -/** Display connected radar's information. */ -void nx_radar_info(uint32_t sensor) { - uint8_t buf[8]; - - // Product ID (LEGO) - memset(buf, 0, 8); - nx_radar_read(sensor, RADAR_PRODUCT_ID, buf); - nx_display_string((char *)buf); - nx_display_string(" "); - - // Sensor Type (Sonar) - memset(buf, 0, 8); - nx_radar_read(sensor, RADAR_SENSOR_TYPE, buf); - nx_display_string((char *)buf); - nx_display_string(" "); - - // Version (V1.0) - memset(buf, 0, 8); - nx_radar_read(sensor, RADAR_VERSION, buf); - nx_display_string((char *)buf); - nx_display_end_line(); - - // Measurement units - nx_display_string("Units: "); - memset(buf, 0, 8); - nx_radar_read(sensor, RADAR_MEASUREMENT_UNITS, buf); - nx_display_string((char *)buf); - nx_display_end_line(); - - // Measurement interval - nx_display_string("Interval: "); - nx_display_uint(nx_radar_read_value(sensor, RADAR_INTERVAL)); - nx_display_string(" ms?\n"); -} diff --git a/lib/pbio/platform/nxt/nxos/drivers/radar.h b/lib/pbio/platform/nxt/nxos/drivers/radar.h index 47bd45c0e..396d57c6e 100644 --- a/lib/pbio/platform/nxt/nxos/drivers/radar.h +++ b/lib/pbio/platform/nxt/nxos/drivers/radar.h @@ -116,16 +116,6 @@ bool nx_radar_detect(uint32_t sensor); */ void nx_radar_reset(uint32_t sensor); -/** Display the radar's information. - * - * Displays on the NXT screen the radar's information, including the - * product ID, sensor type, device version, measurement units and - * the measurements interval. - * - * @param sensor The sensor port number. - */ -void nx_radar_info(uint32_t sensor); - /** Read from the radar's memory. * * @param sensor The sensor port number. diff --git a/lib/pbio/platform/nxt/platform.c b/lib/pbio/platform/nxt/platform.c index 225143847..9fe343c97 100644 --- a/lib/pbio/platform/nxt/platform.c +++ b/lib/pbio/platform/nxt/platform.c @@ -22,7 +22,6 @@ #include -#include #include #include #include @@ -158,7 +157,6 @@ void SystemInit(void) { // TODO: we should be able to convert these to generic pbio drivers and use // pbio_busy_count_busy instead of busy waiting for 100ms. nx__motors_init(); - nx__display_init(); // nx__sensors_init(); // nx_i2c_init(); @@ -175,6 +173,4 @@ void SystemInit(void) { local_addr[0], local_addr[1], local_addr[2], local_addr[3], local_addr[4], local_addr[5]); } - nx_display_string(bluetooth_address_string); - nx_display_string("\n"); } diff --git a/lib/pbio/src/image/image.c b/lib/pbio/src/image/image.c index e3e014f72..81d24d350 100644 --- a/lib/pbio/src/image/image.c +++ b/lib/pbio/src/image/image.c @@ -1007,6 +1007,20 @@ void pbio_image_print(pbio_image_t *image, const char *text, size_t text_len) { image->print_y_top = y_top; } +/** + * Print null terminated text inside image, like in a terminal, scroll when + * bottom is reached. + * @param [in] image Image to draw into. + * @param [in] text Null terminated text string. + * + * Clipping: drawing is clipped to image dimensions. + * + * Text uses ASCII encoding. Newlines are handled, text is flush left. + */ +void pbio_image_print0(pbio_image_t *image, const char *text) { + pbio_image_print(image, text, strlen(text)); +} + /** * Print unsigned number inside image, like in a terminal, scroll when bottom is * reached. From c35dfe1347bf730492ea1a4ebbafeab55b5d696f Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sun, 21 Dec 2025 00:02:11 +0100 Subject: [PATCH 6/9] pbio/hmi_lcd: Change logo scale to work on both EV3 and NXT screen. Refs: https://github.com/pybricks/support/issues/2425 --- lib/pbio/sys/hmi_lcd.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/pbio/sys/hmi_lcd.c b/lib/pbio/sys/hmi_lcd.c index 49f134ac8..1329804f4 100644 --- a/lib/pbio/sys/hmi_lcd.c +++ b/lib/pbio/sys/hmi_lcd.c @@ -39,20 +39,20 @@ #endif // Scaling factors and functions to simplify drawing the logo. -static float _scale; +static uint32_t _scale_mul, _scale_div; static uint32_t _offset_x; static uint32_t _offset_y; static uint32_t sx(uint32_t x) { - return x * _scale + _offset_x + 0.5f; + return (x * _scale_mul + _scale_div / 2) / _scale_div + _offset_x; } static uint32_t sy(uint32_t y) { - return y * _scale + _offset_y + 0.5f; + return (y * _scale_mul + _scale_div / 2) / _scale_div + _offset_y; } static uint32_t sr(uint32_t r) { - return r * _scale + 0.5f; + return (r * _scale_mul + _scale_div / 2) / _scale_div; } /** @@ -60,12 +60,13 @@ static uint32_t sr(uint32_t r) { * * @param x [in] Horizontal offset from the left. * @param y [in] Vertical offset from the top. - * @param scale [in] Scale (1.0 is 154 x 84). + * @param width [in] Width (natural size is 154 x 84). */ -static void draw_pybricks_logo(uint32_t x, uint32_t y, float scale) { +static void draw_pybricks_logo(uint32_t x, uint32_t y, uint32_t width) { _offset_x = x; _offset_y = y; - _scale = scale; + _scale_mul = width; + _scale_div = 154; pbio_image_t *display = pbdrv_display_get_image(); pbio_image_fill(display, 0); @@ -118,7 +119,9 @@ static pbio_error_t run_ui(pbio_os_state_t *state, pbio_os_timer_t *timer) { PBIO_OS_ASYNC_BEGIN(state); // Centered above 5 code slot indicators. - draw_pybricks_logo(27, 12, 0.8); + const uint32_t width = PBDRV_CONFIG_DISPLAY_NUM_COLS * 7 / 10; + draw_pybricks_logo((PBDRV_CONFIG_DISPLAY_NUM_COLS - width) / 2, + PBDRV_CONFIG_DISPLAY_NUM_ROWS / 10, width); for (;;) { From 6ef96a2a7d4be59b96c2b27e29e29392e46a67d5 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 19:40:52 +0100 Subject: [PATCH 7/9] pbio: Build NXT platform with umm_malloc. This is needed to allocate memory for images, without using the micropython allocator. Refs: https://github.com/pybricks/support/issues/2425 --- .github/build-each-commit.py | 2 +- .vscode/c_cpp_properties.json | 3 ++- bricks/nxt/Makefile | 2 ++ bricks/nxt/dbglog/dbglog.h | 11 +++++++++++ lib/pbio/platform/nxt/platform.c | 7 +++++++ lib/pbio/platform/nxt/platform.ld | 8 ++++++++ lib/pbio/platform/nxt/umm_malloc_cfgport.h | 12 ++++++++++++ 7 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 bricks/nxt/dbglog/dbglog.h create mode 100644 lib/pbio/platform/nxt/umm_malloc_cfgport.h diff --git a/.github/build-each-commit.py b/.github/build-each-commit.py index 286044876..0dda3474c 100755 --- a/.github/build-each-commit.py +++ b/.github/build-each-commit.py @@ -78,7 +78,7 @@ pybricks.git.submodule( "update", "--init", "--checkout", "lib/STM32_USB_Device_Library" ) - if args.hub == "ev3": + if args.hub == "ev3" or args.hub == "nxt": pybricks.git.submodule("update", "--init", "--checkout", "lib/umm_malloc") if args.hub == "buildhat": diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5ddcf64c4..fc4a86f49 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -281,7 +281,8 @@ "${workspaceFolder}/bricks/nxt/build", "${workspaceFolder}/lib/pbio/include", "${workspaceFolder}", - "${workspaceFolder}/micropython" + "${workspaceFolder}/micropython", + "${workspaceFolder}/lib/umm_malloc/src" ], "defines": [ "MICROPY_MODULE_FROZEN_MPY", diff --git a/bricks/nxt/Makefile b/bricks/nxt/Makefile index d5113b754..8c3fbfca4 100644 --- a/bricks/nxt/Makefile +++ b/bricks/nxt/Makefile @@ -4,4 +4,6 @@ PBIO_PLATFORM = nxt PB_MCU_FAMILY = AT91SAM7 +PB_LIB_UMM_MALLOC = 1 + include ../_common/common.mk diff --git a/bricks/nxt/dbglog/dbglog.h b/bricks/nxt/dbglog/dbglog.h new file mode 100644 index 000000000..c3437e7dd --- /dev/null +++ b/bricks/nxt/dbglog/dbglog.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors +// +// Hack to allow umm_info() to print in MicroPython. + +#include "py/mpprint.h" + +#undef DBGLOG_FORCE +#define DBGLOG_FORCE(force, fmt, ...) mp_printf(&mp_plat_print, fmt,##__VA_ARGS__) + +#define DBGLOG_32_BIT_PTR(p) (p) diff --git a/lib/pbio/platform/nxt/platform.c b/lib/pbio/platform/nxt/platform.c index 9fe343c97..6dd7fcfff 100644 --- a/lib/pbio/platform/nxt/platform.c +++ b/lib/pbio/platform/nxt/platform.c @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -173,4 +175,9 @@ void SystemInit(void) { local_addr[0], local_addr[1], local_addr[2], local_addr[3], local_addr[4], local_addr[5]); } + + // Separate heap for large allocations - defined in linker script. + extern char pb_umm_heap_start; + extern char pb_umm_heap_end; + umm_init_heap(&pb_umm_heap_start, &pb_umm_heap_end - &pb_umm_heap_start); } diff --git a/lib/pbio/platform/nxt/platform.ld b/lib/pbio/platform/nxt/platform.ld index 06eff64bf..cd93bac5e 100644 --- a/lib/pbio/platform/nxt/platform.ld +++ b/lib/pbio/platform/nxt/platform.ld @@ -29,6 +29,9 @@ RAM_BASE = 2M; RAM_SIZE = 64k; SAMBA_RESERVED_SIZE = 8k; +/* Extra heap for large allocations (images, etc). */ +pb_umm_heap_size = 8k; + SECTIONS { /* * Interrupt vectors. These are loaded to the bottom of memory at @@ -90,6 +93,11 @@ SECTIONS { { . = ALIGN(4); *(.noinit) + + . = ALIGN(4); + pb_umm_heap_start = .; + . = . + pb_umm_heap_size; + pb_umm_heap_end = .; } > ram /* diff --git a/lib/pbio/platform/nxt/umm_malloc_cfgport.h b/lib/pbio/platform/nxt/umm_malloc_cfgport.h new file mode 100644 index 000000000..643097f66 --- /dev/null +++ b/lib/pbio/platform/nxt/umm_malloc_cfgport.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors +// +// Configuration for umm_malloc memory allocator for NXT platform. +// See lib/umm_malloc/src/umm_malloc_cfg.h for details. + +// We only use this for large allocations, like images, so we can use a larger +// block size to reduce overhead. +#define UMM_BLOCK_BODY_SIZE 128 + +#define UMM_INFO +#define DBGLOG_ENABLE From 38992476f1cc162abfe7863c00cba0a3f48801aa Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sat, 20 Dec 2025 22:31:04 +0100 Subject: [PATCH 8/9] pybricks.NXTBrick: Add screen access. Refs: https://github.com/pybricks/support/issues/2425 --- bricks/nxt/mpconfigport.h | 2 +- pybricks/hubs/pb_type_nxtbrick.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bricks/nxt/mpconfigport.h b/bricks/nxt/mpconfigport.h index e1ed46f47..9e54fcfcf 100644 --- a/bricks/nxt/mpconfigport.h +++ b/bricks/nxt/mpconfigport.h @@ -44,7 +44,7 @@ #define PYBRICKS_PY_PARAMETERS (1) #define PYBRICKS_PY_PARAMETERS_BUTTON (1) #define PYBRICKS_PY_PARAMETERS_ICON (0) -#define PYBRICKS_PY_PARAMETERS_IMAGE (0) +#define PYBRICKS_PY_PARAMETERS_IMAGE (1) #define PYBRICKS_PY_DEVICES (1) #define PYBRICKS_PY_ROBOTICS (1) #define PYBRICKS_PY_ROBOTICS_DRIVEBASE_GYRO (0) diff --git a/pybricks/hubs/pb_type_nxtbrick.c b/pybricks/hubs/pb_type_nxtbrick.c index df5bb5278..de8c27e90 100644 --- a/pybricks/hubs/pb_type_nxtbrick.c +++ b/pybricks/hubs/pb_type_nxtbrick.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -21,6 +22,7 @@ typedef struct _hubs_NXTBrick_obj_t { mp_obj_base_t base; mp_obj_t battery; mp_obj_t buttons; + mp_obj_t screen; mp_obj_t speaker; mp_obj_t system; } hubs_NXTBrick_obj_t; @@ -48,6 +50,7 @@ static mp_obj_t hubs_NXTBrick_make_new(const mp_obj_type_t *type, size_t n_args, hubs_NXTBrick_obj_t *self = mp_obj_malloc(hubs_NXTBrick_obj_t, type); self->battery = MP_OBJ_FROM_PTR(&pb_module_battery); self->buttons = pb_type_Keypad_obj_new(MP_OBJ_FROM_PTR(self), pb_type_nxtbrick_button_pressed); + self->screen = pb_type_Image_display_obj_new(); self->speaker = mp_call_function_0(MP_OBJ_FROM_PTR(&pb_type_Speaker)); self->system = MP_OBJ_FROM_PTR(&pb_type_System); return MP_OBJ_FROM_PTR(self); @@ -56,6 +59,7 @@ static mp_obj_t hubs_NXTBrick_make_new(const mp_obj_type_t *type, size_t n_args, static const pb_attr_dict_entry_t hubs_NXTBrick_attr_dict[] = { PB_DEFINE_CONST_ATTR_RO(MP_QSTR_battery, hubs_NXTBrick_obj_t, battery), PB_DEFINE_CONST_ATTR_RO(MP_QSTR_buttons, hubs_NXTBrick_obj_t, buttons), + PB_DEFINE_CONST_ATTR_RO(MP_QSTR_screen, hubs_NXTBrick_obj_t, screen), PB_DEFINE_CONST_ATTR_RO(MP_QSTR_speaker, hubs_NXTBrick_obj_t, speaker), PB_DEFINE_CONST_ATTR_RO(MP_QSTR_system, hubs_NXTBrick_obj_t, system), PB_ATTR_DICT_SENTINEL From b955b07d9d846423889d107255679fe69cc42258 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Sun, 21 Dec 2025 14:09:51 +0100 Subject: [PATCH 9/9] pbio/drv/display_nxt: Deinit from the display thread. Use asynchronous code to avoid blocking. Thanks: Laurens for the code to do it. Refs: https://github.com/pybricks/support/issues/2425 --- lib/pbio/drv/display/display_nxt.c | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/pbio/drv/display/display_nxt.c b/lib/pbio/drv/display/display_nxt.c index 45aa3d497..264c45e09 100644 --- a/lib/pbio/drv/display/display_nxt.c +++ b/lib/pbio/drv/display/display_nxt.c @@ -338,7 +338,7 @@ static pbio_error_t pbdrv_display_nxt_process_thread(pbio_os_state_t *state, voi pbio_busy_count_down(); // Update the display with the user frame buffer, if changed. - for (;;) { + while (pbdrv_display_nxt_process.request != PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL) { PBIO_OS_AWAIT_UNTIL(state, pbdrv_display_user_frame_update_requested); pbdrv_display_user_frame_update_requested = false; for (page = 0; page < PBDRV_CONFIG_DISPLAY_NUM_ROWS / 8; page++) { @@ -365,7 +365,20 @@ static pbio_error_t pbdrv_display_nxt_process_thread(pbio_os_state_t *state, voi } } - PBIO_OS_ASYNC_END(PBIO_SUCCESS); + // When power to the controller goes out, there is the risk that + // some capacitors mounted around the controller might damage it + // when discharging in an uncontrolled fashion. To avoid this, the + // spec recommends putting the controller into reset mode before + // shutdown, which activates a drain circuit to empty the board + // capacitors gracefully. + *AT91C_SPI_IDR = ~0; + *AT91C_SPI_PTCR = AT91C_PDC_TXTDIS; + spi_write_command_byte(RESET()); + PBIO_OS_AWAIT_MS(state, &timer, 20); + + pbio_busy_count_down(); + + PBIO_OS_ASYNC_END(PBIO_ERROR_CANCELED); } // Image corresponding to the display. @@ -407,16 +420,11 @@ void pbdrv_display_update(void) { } void pbdrv_display_deinit(void) { - // When power to the controller goes out, there is the risk that - // some capacitors mounted around the controller might damage it - // when discharging in an uncontrolled fashion. To avoid this, the - // spec recommends putting the controller into reset mode before - // shutdown, which activates a drain circuit to empty the board - // capacitors gracefully. - *AT91C_SPI_IDR = ~0; - *AT91C_SPI_PTCR = AT91C_PDC_TXTDIS; - spi_write_command_byte(RESET()); - nx_systick_wait_ms(20); + // This doesn't do deinit right away, but it ask the display process to + // gracefully exit at a useful spot, and then do deinit. The busy count is + // used so all processes can await deinit before actual power off. + pbio_busy_count_up(); + pbio_os_process_make_request(&pbdrv_display_nxt_process, PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL); } #endif // PBDRV_CONFIG_DISPLAY_NXT