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/_common/common.mk b/bricks/_common/common.mk index dd23d9063..eb6a86aeb 100644 --- a/bricks/_common/common.mk +++ b/bricks/_common/common.mk @@ -483,9 +483,7 @@ endif NXOS_SRC_C = $(addprefix lib/pbio/platform/nxt/nxos/,\ _abort.c \ 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/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/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/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/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 new file mode 100644 index 000000000..264c45e09 --- /dev/null +++ b/lib/pbio/drv/display/display_nxt.c @@ -0,0 +1,430 @@ +// 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 +// +// 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 + +#if PBDRV_CONFIG_DISPLAY_NXT + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "nxos/lock.h" +#include "nxos/interrupts.h" +#include "nxos/drivers/systick.h" +#include "nxos/drivers/aic.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 mode, command or data. + */ +typedef enum { + /* Command. */ + SPI_MODE_COMMAND, + /* Data. */ + SPI_MODE_DATA, +} spi_mode_t; + +/* + * 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 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_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_mode = mode; + + if (mode == SPI_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(SPI_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, called when Tx is done. + */ +static void spi_isr(void) { + // 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); + + // 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 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 pbdrv_display_nxt_sync_refresh(void) { + int i, j; + + // Start the data transfer. + 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(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)) { + ; + } + + // Send the data. + *AT91C_SPI_TDR = pbdrv_display_send_buffer[j]; + } + } +} + +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[] = { + // 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), + }; + + 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(); + 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()); + 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. + 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++) { + // 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); + } + } + + // 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. +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 &pbdrv_display_image; +} + +uint8_t pbdrv_display_get_max_value(void) { + return 1; +} + +uint8_t pbdrv_display_get_value_from_hsv(uint16_t h, uint8_t s, uint8_t v) { + 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) { + // 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 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/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/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 5b660cfee..000000000 --- a/lib/pbio/platform/nxt/nxos/display.c +++ /dev/null @@ -1,207 +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) { - if (display.auto_refresh) - nx__lcd_dirty_display(); -} - - -/* 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) { - nx__lcd_dirty_display(); -} - - -/* - * 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; - nx__lcd_set_display(&display.buffer[0][0]); - 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.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 - -/** @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. */ - -/** 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); - -/** 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") - * 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/pbdrvconfig.h b/lib/pbio/platform/nxt/pbdrvconfig.h index 4d91cfb96..5cbda7327 100644 --- a/lib/pbio/platform/nxt/pbdrvconfig.h +++ b/lib/pbio/platform/nxt/pbdrvconfig.h @@ -25,7 +25,8 @@ #define PBDRV_CONFIG_COUNTER_NUM_DEV (3) #define PBDRV_CONFIG_COUNTER_NXT (1) -#define PBDRV_CONFIG_DISPLAY (0) +#define PBDRV_CONFIG_DISPLAY (1) +#define PBDRV_CONFIG_DISPLAY_NXT (1) #define PBDRV_CONFIG_DISPLAY_NUM_COLS (100) #define PBDRV_CONFIG_DISPLAY_NUM_ROWS (64) 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) diff --git a/lib/pbio/platform/nxt/platform.c b/lib/pbio/platform/nxt/platform.c index ee070d602..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 @@ -22,10 +24,8 @@ #include -#include #include #include -#include #include #include #include @@ -159,8 +159,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(); @@ -177,6 +175,9 @@ 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"); + + // 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 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. 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 (;;) { 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