Add support for display rotation and raw commands

Display rotation is relative to the scan order of the display.
The scan order can be found by scrolling the display with command
0x37 `display_bus.send(0x37, struct.pack(">H", i % 128))`

Fixes #1504
This commit is contained in:
Scott Shawcroft 2019-02-01 00:32:03 -08:00
parent d72cd5b2d6
commit 448ae64d8e
No known key found for this signature in database
GPG Key ID: FD0EDC4B6C53CA59
7 changed files with 147 additions and 19 deletions

View File

@ -47,13 +47,16 @@
//| objects in CircuitPython, Display objects live until `displayio.release_displays()`
//| is called. This is done so that CircuitPython can use the display itself.
//|
//| Most people should not use this class directly. Use a specific display driver instead that will
//| contain the initialization sequence at minimum.
//|
//| .. warning:: This will be changed before 4.0.0. Consider it very experimental.
//|
//| .. class:: Display(display_bus, init_sequence, *, width, height, colstart=0, rowstart=0, color_depth=16, set_column_command=0x2a, set_row_command=0x2b, write_ram_command=0x2c)
//| .. class:: Display(display_bus, init_sequence, *, width, height, colstart=0, rowstart=0, rotation=0, color_depth=16, set_column_command=0x2a, set_row_command=0x2b, write_ram_command=0x2c, set_vertical_scroll=0, backlight_pin=None)
//|
//| Create a Display object on the given display bus (`displayio.FourWire` or `displayio.ParallelBus`).
//|
//| The ``init_sequence`` is bitbacked to minimize the ram impact. Every command begins with a
//| The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins with a
//| command byte followed by a byte to determine the parameter count and if a delay is need after.
//| When the top bit of the second byte is 1, the next byte will be the delay time in milliseconds.
//| The remaining 7 bits are the parameter count excluding any delay byte. The third through final
@ -73,21 +76,26 @@
//| (b"") are merged together on load. The parens are needed to allow byte literals on subsequent
//| lines.
//|
//| The initialization sequence should always leave the display memory access inline with the scan
//| of the display to minimize tearing artifacts.
//|
//| :param displayio.FourWire or displayio.ParallelBus display_bus: The bus that the display is connected to
//| :param buffer init_sequence: Byte-packed initialization sequence.
//| :param int width: Width in pixels
//| :param int height: Height in pixels
//| :param int colstart: The index if the first visible column
//| :param int rowstart: The index if the first visible row
//| :param int rotation: The rotation of the display in 90 degree increments
//| :param int color_depth: The number of bits of color per pixel transmitted. (Some displays
//| support 18 bit but 16 is easier to transmit. The last bit is extrapolated.)
//| :param int set_column_command: Command used to set the start and end columns to update
//| :param int set_row_command: Command used so set the start and end rows to update
//| :param int write_ram_command: Command used to write pixels values into the update region
//| :param int set_vertical_scroll: Command used to set the first row to show
//| :param microcontroller.Pin backlight_pin: Pin connected to the display's backlight
//|
STATIC mp_obj_t displayio_display_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_display_bus, ARG_init_sequence, ARG_width, ARG_height, ARG_colstart, ARG_rowstart, ARG_color_depth, ARG_set_column_command, ARG_set_row_command, ARG_write_ram_command, ARG_backlight_pin };
enum { ARG_display_bus, ARG_init_sequence, ARG_width, ARG_height, ARG_colstart, ARG_rowstart, ARG_rotation, ARG_color_depth, ARG_set_column_command, ARG_set_row_command, ARG_write_ram_command, ARG_set_vertical_scroll, ARG_backlight_pin };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_display_bus, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_init_sequence, MP_ARG_REQUIRED | MP_ARG_OBJ },
@ -95,10 +103,12 @@ STATIC mp_obj_t displayio_display_make_new(const mp_obj_type_t *type, size_t n_a
{ MP_QSTR_height, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED, },
{ MP_QSTR_colstart, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} },
{ MP_QSTR_rowstart, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} },
{ MP_QSTR_rotation, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} },
{ MP_QSTR_color_depth, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
{ MP_QSTR_set_column_command, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0x2a} },
{ MP_QSTR_set_row_command, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0x2b} },
{ MP_QSTR_write_ram_command, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0x2c} },
{ MP_QSTR_set_vertical_scroll, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0x0} },
{ MP_QSTR_backlight_pin, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
@ -116,6 +126,10 @@ STATIC mp_obj_t displayio_display_make_new(const mp_obj_type_t *type, size_t n_a
backlight_pin = MP_OBJ_TO_PTR(backlight_pin_obj);
assert_pin_free(backlight_pin);
}
mp_int_t rotation = args[ARG_rotation].u_int;
if (rotation % 90 != 0) {
mp_raise_ValueError(translate("Display rotation must be in 90 degree increments"));
}
displayio_display_obj_t *self = NULL;
for (uint8_t i = 0; i < CIRCUITPY_DISPLAY_LIMIT; i++) {
@ -130,9 +144,11 @@ STATIC mp_obj_t displayio_display_make_new(const mp_obj_type_t *type, size_t n_a
}
self->base.type = &displayio_display_type;
common_hal_displayio_display_construct(self,
display_bus, args[ARG_width].u_int, args[ARG_height].u_int, args[ARG_colstart].u_int, args[ARG_rowstart].u_int,
display_bus, args[ARG_width].u_int, args[ARG_height].u_int, args[ARG_colstart].u_int, args[ARG_rowstart].u_int, rotation,
args[ARG_color_depth].u_int, args[ARG_set_column_command].u_int, args[ARG_set_row_command].u_int,
args[ARG_write_ram_command].u_int, bufinfo.buf, bufinfo.len, MP_OBJ_TO_PTR(backlight_pin));
args[ARG_write_ram_command].u_int,
args[ARG_set_vertical_scroll].u_int,
bufinfo.buf, bufinfo.len, MP_OBJ_TO_PTR(backlight_pin));
return self;
}

View File

@ -38,8 +38,8 @@ extern const mp_obj_type_t displayio_display_type;
void common_hal_displayio_display_construct(displayio_display_obj_t* self,
mp_obj_t bus, uint16_t width, uint16_t height,
int16_t colstart, int16_t rowstart, uint16_t color_depth,
uint8_t set_column_command, uint8_t set_row_command, uint8_t write_ram_command,
int16_t colstart, int16_t rowstart, uint16_t rotation, uint16_t color_depth,
uint8_t set_column_command, uint8_t set_row_command, uint8_t write_ram_command, uint8_t set_vertical_scroll,
uint8_t* init_sequence, uint16_t init_sequence_len, const mcu_pin_obj_t* backlight_pin);
int32_t common_hal_displayio_display_wait_for_frame(displayio_display_obj_t* self);

View File

@ -98,7 +98,31 @@ STATIC mp_obj_t displayio_fourwire_make_new(const mp_obj_type_t *type, size_t n_
return self;
}
//| .. method:: send(command, data)
//|
//| Sends the given command value followed by the full set of data. Display state, such as
//| vertical scroll, set via ``send`` may or may not be reset once the code is done.
//|
STATIC mp_obj_t displayio_fourwire_obj_send(mp_obj_t self, mp_obj_t command_obj, mp_obj_t data_obj) {
mp_int_t command_int = MP_OBJ_SMALL_INT_VALUE(command_obj);
if (!MP_OBJ_IS_SMALL_INT(command_obj) || command_int > 255 || command_int < 0) {
mp_raise_ValueError(translate("Command must be an int between 0 and 255"));
}
uint8_t command = command_int;
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(data_obj, &bufinfo, MP_BUFFER_READ);
common_hal_displayio_fourwire_begin_transaction(self);
common_hal_displayio_fourwire_send(self, true, &command, 1);
common_hal_displayio_fourwire_send(self, false, ((uint8_t*) bufinfo.buf), bufinfo.len);
common_hal_displayio_fourwire_end_transaction(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(displayio_fourwire_send_obj, displayio_fourwire_obj_send);
STATIC const mp_rom_map_elem_t displayio_fourwire_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&displayio_fourwire_send_obj) },
};
STATIC MP_DEFINE_CONST_DICT(displayio_fourwire_locals_dict, displayio_fourwire_locals_dict_table);

View File

@ -102,7 +102,31 @@ STATIC mp_obj_t displayio_parallelbus_make_new(const mp_obj_type_t *type, size_t
return self;
}
//| .. method:: send(command, data)
//|
//| Sends the given command value followed by the full set of data. Display state, such as
//| vertical scroll, set via ``send`` may or may not be reset once the code is done.
//|
STATIC mp_obj_t displayio_parallelbus_obj_send(mp_obj_t self, mp_obj_t command_obj, mp_obj_t data_obj) {
mp_int_t command_int = MP_OBJ_SMALL_INT_VALUE(command_obj);
if (!MP_OBJ_IS_SMALL_INT(command_obj) || command_int > 255 || command_int < 0) {
mp_raise_ValueError(translate("Command must be an int between 0 and 255"));
}
uint8_t command = command_int;
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(data_obj, &bufinfo, MP_BUFFER_READ);
common_hal_displayio_parallelbus_begin_transaction(self);
common_hal_displayio_parallelbus_send(self, true, &command, 1);
common_hal_displayio_parallelbus_send(self, false, ((uint8_t*) bufinfo.buf), bufinfo.len);
common_hal_displayio_parallelbus_end_transaction(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(displayio_parallelbus_send_obj, displayio_parallelbus_obj_send);
STATIC const mp_rom_map_elem_t displayio_parallelbus_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&displayio_parallelbus_send_obj) },
};
STATIC MP_DEFINE_CONST_DICT(displayio_parallelbus_locals_dict, displayio_parallelbus_locals_dict_table);

View File

@ -41,12 +41,10 @@
#define DELAY 0x80
void common_hal_displayio_display_construct(displayio_display_obj_t* self,
mp_obj_t bus, uint16_t width, uint16_t height, int16_t colstart, int16_t rowstart,
mp_obj_t bus, uint16_t width, uint16_t height, int16_t colstart, int16_t rowstart, uint16_t rotation,
uint16_t color_depth, uint8_t set_column_command, uint8_t set_row_command,
uint8_t write_ram_command, uint8_t* init_sequence, uint16_t init_sequence_len,
uint8_t write_ram_command, uint8_t set_vertical_scroll, uint8_t* init_sequence, uint16_t init_sequence_len,
const mcu_pin_obj_t* backlight_pin) {
self->width = width;
self->height = height;
self->color_depth = color_depth;
self->set_column_command = set_column_command;
self->set_row_command = set_row_command;
@ -100,6 +98,26 @@ void common_hal_displayio_display_construct(displayio_display_obj_t* self,
self->refresh = true;
self->current_group = &circuitpython_splash;
self->width = width;
self->height = height;
rotation = rotation % 360;
self->mirror_x = false;
self->mirror_y = false;
self->transpose_xy = false;
if (rotation == 0 || rotation == 180) {
if (rotation == 180) {
self->mirror_x = true;
self->mirror_y = true;
}
} else {
self->transpose_xy = true;
if (rotation == 90) {
self->mirror_y = true;
} else {
self->mirror_x = true;
}
}
// Always set the backlight type in case we're reusing memory.
self->backlight_inout.base.type = &mp_type_NoneType;
if (backlight_pin != NULL && common_hal_mcu_pin_is_free(backlight_pin)) {

View File

@ -59,6 +59,9 @@ typedef struct {
uint64_t last_backlight_refresh;
bool auto_brightness:1;
bool updating_backlight:1;
bool mirror_x;
bool mirror_y;
bool transpose_xy;
} displayio_display_obj_t;
void displayio_display_update_backlight(displayio_display_obj_t* self);

View File

@ -12,6 +12,12 @@
primary_display_t displays[CIRCUITPY_DISPLAY_LIMIT];
static inline void swap(uint16_t* a, uint16_t* b) {
uint16_t temp = *a;
*a = *b;
*b = temp;
}
void displayio_refresh_displays(void) {
for (uint8_t i = 0; i < CIRCUITPY_DISPLAY_LIMIT; i++) {
if (displays[i].display.base.type == NULL || displays[i].display.base.type == &mp_type_NoneType) {
@ -24,23 +30,60 @@ void displayio_refresh_displays(void) {
return;
}
if (displayio_display_refresh_queued(display)) {
// We compute the pixels
// We compute the pixels. r and c are row and column to match the display memory
// structure. x and y match location within the groups.
uint16_t c0 = 0;
uint16_t r0 = 0;
uint16_t c1 = display->width;
uint16_t r1 = display->height;
if (display->transpose_xy) {
swap(&c1, &r1);
}
displayio_display_start_region_update(display, c0, r0, c1, r1);
uint16_t x0 = 0;
uint16_t x1 = display->width - 1;
uint16_t startx = 0;
int8_t dx = 1;
if (display->mirror_x) {
dx = -1;
startx = x1;
}
uint16_t y0 = 0;
uint16_t x1 = display->width;
uint16_t y1 = display->height;
uint16_t y1 = display->height - 1;
uint16_t starty = 0;
int8_t dy = 1;
if (display->mirror_y) {
dy = -1;
starty = y1;
}
bool transpose = false;
if (display->transpose_xy) {
transpose = true;
int8_t temp_dx = dx;
dx = dy;
dy = temp_dx;
swap(&starty, &startx);
swap(&x0, &y0);
swap(&x1, &y1);
}
size_t index = 0;
//size_t row_size = (x1 - x0);
uint16_t buffer_size = 256;
uint32_t buffer[buffer_size / 2];
displayio_display_start_region_update(display, x0, y0, x1, y1);
for (uint16_t y = y0; y < y1; ++y) {
for (uint16_t x = x0; x < x1; ++x) {
for (uint16_t y = starty; y0 <= y && y <= y1; y += dy) {
for (uint16_t x = startx; x0 <= x && x <= x1; x += dx) {
uint16_t* pixel = &(((uint16_t*)buffer)[index]);
*pixel = 0;
if (display->current_group != NULL) {
displayio_group_get_pixel(display->current_group, x, y, pixel);
if (transpose) {
displayio_group_get_pixel(display->current_group, y, x, pixel);
} else {
displayio_group_get_pixel(display->current_group, x, y, pixel);
}
}
index += 1;