Skip to content

Commit 1bd7e69

Browse files
markdpgeorge
authored andcommitted
Don't use DAL to do PWM rendering of display.
Gives visible better results.
1 parent 57a4b19 commit 1bd7e69

File tree

8 files changed

+173
-65
lines changed

8 files changed

+173
-65
lines changed

inc/genhdr/qstrdefs.generated.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,6 @@ QDEF(MP_QSTR_height, (const byte*)"\xfa\x06" "height")
301301
QDEF(MP_QSTR_invert, (const byte*)"\xb7\x06" "invert")
302302
QDEF(MP_QSTR_set_pixel, (const byte*)"\xb0\x09" "set_pixel")
303303
QDEF(MP_QSTR_get_pixel, (const byte*)"\xa4\x09" "get_pixel")
304-
QDEF(MP_QSTR_set_pixel_raw, (const byte*)"\xcb\x0d" "set_pixel_raw")
305-
QDEF(MP_QSTR_get_pixel_raw, (const byte*)"\xdf\x0d" "get_pixel_raw")
306304
QDEF(MP_QSTR_shift_left, (const byte*)"\xa1\x0a" "shift_left")
307305
QDEF(MP_QSTR_shift_right, (const byte*)"\xba\x0b" "shift_right")
308306
QDEF(MP_QSTR_shift_up, (const byte*)"\xdf\x08" "shift_up")

inc/microbit/microbitimage.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,4 @@ mp_obj_t scrolling_string_image_iterable(mp_obj_t str);
7171
} \
7272
}
7373

74-
extern int BRIGHTNESS_SCALE[];
75-
7674
extern monochrome_5by5_t BLANK_IMAGE;

inc/microbit/modmicrobit.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ extern const mp_obj_type_t microbit_image_type;
104104
extern const mp_obj_type_t microbit_accelerometer_type;
105105
extern const struct _microbit_accelerometer_obj_t microbit_accelerometer_obj;
106106

107-
extern const struct _microbit_display_obj_t microbit_display_obj;
107+
extern struct _microbit_display_obj_t microbit_display_obj;
108108
extern const struct _microbit_button_obj_t microbit_button_a_obj;
109109
extern const struct _microbit_button_obj_t microbit_button_b_obj;
110110
extern const struct _microbit_compass_obj_t microbit_compass_obj;
@@ -133,9 +133,7 @@ MP_DECLARE_CONST_FUN_OBJ(microbit_display_scroll_obj);
133133
MP_DECLARE_CONST_FUN_OBJ(microbit_display_clear_obj);
134134
MP_DECLARE_CONST_FUN_OBJ(microbit_display_animate_obj);
135135
MP_DECLARE_CONST_FUN_OBJ(microbit_display_get_pixel_obj);
136-
MP_DECLARE_CONST_FUN_OBJ(microbit_display_get_pixel_raw_obj);
137136
MP_DECLARE_CONST_FUN_OBJ(microbit_display_set_pixel_obj);
138-
MP_DECLARE_CONST_FUN_OBJ(microbit_display_set_pixel_raw_obj);
139137
MP_DECLARE_CONST_FUN_OBJ(microbit_pin_read_digital_obj);
140138
MP_DECLARE_CONST_FUN_OBJ(microbit_pin_write_digital_obj);
141139
MP_DECLARE_CONST_FUN_OBJ(microbit_pin_read_analog_obj);

inc/microbit/qstrdefsport.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ Q(height)
5959
Q(invert)
6060
Q(set_pixel)
6161
Q(get_pixel)
62-
Q(set_pixel_raw)
63-
Q(get_pixel_raw)
6462
Q(shift_left)
6563
Q(shift_right)
6664
Q(shift_up)

source/microbit/display_readme.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Notes on the Display
2+
3+
Rendering of images to the display is now done entirely within MicroPython
4+
without using the DAL's rendering logic.
5+
6+
This achieves the following:
7+
8+
1. It gives more obviously distinct brightness levels on the scale of 1 to 9
9+
2. Most obviously, the dimmest level is dimmer. Level 1 is clearly dimmer than level 2
10+
3. It is possible to support sophisticated animations asynchronously.
11+
12+
## How rendering works
13+
14+
Rendering on the microbit display works by using pulse width modulation implemented
15+
in software. A render cycle consists of:
16+
17+
* Render each display row (which does not correspond to the image row)
18+
* Turn off all LEDs in the previous row.
19+
* Turn on all LEDs that are maximum brightness
20+
* Do any computation required to update the image
21+
* Turn on all LEDs with non-zero brightness
22+
* In exponentially increasing time steps:
23+
* Turn off LEDs in increasing order of brightness.
24+
25+
This means that each LEDs is turned on for a period of time approximately proportional
26+
to 2**brightness.
27+
By turning on maximum brightness LEDs before updating the image, and performing the
28+
increasing time steps after the update, image is rendering is smooth even with complex
29+
image iterators.
30+
31+
Provided that the display update step takes no more that about 2.2ms then
32+
there will no effect on the rendering of the image.
33+
Even if it takes up to 4ms (which a lot of computation to yield just a single image)
34+
then only effect is that level 8 brightness will be dimmed toward the level 7 brightness.
35+
36+
## How this differs from the DAL.
37+
The DAL updates the image before turning on any pixels.
38+
DAL rendering timings assume that the full 6ms cycle duration can be divided
39+
up evenly. This does not reflect the underlying clock speed and may be the cause
40+
of the unevenness in brightness levels.
41+
The DAL supports 255 brightness levels as well as rotation in the rendering ticker function,
42+
which adds quite a lot of overhead to a function that is called more than 1000 times a second.

source/microbit/help.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,7 @@ STATIC const mp_doc_t help_table_instances[] = {
108108
{&microbit_display_clear_obj, "clear() -- clear the display.\n"},
109109
{&microbit_display_animate_obj, "animate(img, delay, stride, start=0, async=False, repeat=False) -- animate\n image 'img' with 'delay' milliseconds and 'stride' pixels offset between\n frames. Optional: 'start' offset from left hand side, 'async' to run in the\n background, 'repeat' to loop the animation.\n"},
110110
{&microbit_display_get_pixel_obj, "get_brightness(x, y) -- gets the brightness of the display pixel (x,y).\n"},
111-
{&microbit_display_get_pixel_raw_obj, "get_brightness(x, y) -- gets the raw value (a value between 0 and 255) of the display pixel (x,y).\n"},
112111
{&microbit_display_set_pixel_obj, "set_brightness(x, y, b) -- sets the brightness 'b' of the display pixel (x,y).\n"},
113-
{&microbit_display_set_pixel_raw_obj, "set_brightness_raw(x, y, r) -- sets the raw value 'r' (a value between 0 and 255) of the display pixel (x,y).\n"},
114112
{&microbit_p0_obj, "Represents pin 0 on the gold edge connector.\n"},
115113
{&microbit_p1_obj, "Represents pin 1 on the gold edge connector.\n"},
116114
{&microbit_p2_obj, "Represents pin 2 on the gold edge connector.\n"},

source/microbit/microbitdisplay.cpp

Lines changed: 130 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,38 @@
2626

2727
#include "MicroBit.h"
2828
#include "microbitobj.h"
29+
#include "nrf_gpio.h"
2930

3031
extern "C" {
3132

3233
#include "py/runtime.h"
3334
#include "modmicrobit.h"
3435
#include "microbitimage.h"
3536

37+
38+
3639
typedef struct _microbit_display_obj_t {
3740
mp_obj_base_t base;
38-
MicroBitDisplay *display;
41+
mp_int_t image_buffer[5][5];
3942
} microbit_display_obj_t;
4043

41-
STATIC void display_print(const microbit_display_obj_t *display, microbit_image_obj_t *image) {
42-
MicroBitImage *display_image = &display->display->image;
44+
45+
STATIC void display_print(microbit_display_obj_t *display, microbit_image_obj_t *image) {
4346
mp_int_t w = min(image->width(), 5);
4447
mp_int_t h = min(image->height(), 5);
4548
mp_int_t x = 0;
4649
for (; x < w; ++x) {
4750
mp_int_t y = 0;
4851
for (; y < h; ++y) {
49-
display_image->setPixelValue(x, y, BRIGHTNESS_SCALE[image->getPixelValue(x, y)]);
52+
display->image_buffer[x][y] = image->getPixelValue(x, y);
5053
}
5154
for (; y < 5; ++y) {
52-
display_image->setPixelValue(x, y, 0);
55+
display->image_buffer[x][y] = 0;
5356
}
5457
}
5558
for (; x < 5; ++x) {
5659
for (mp_int_t y = 0; y < 5; ++y) {
57-
display_image->setPixelValue(x, y, 0);
60+
display->image_buffer[x][y] = 0;
5861
}
5962
}
6063
}
@@ -111,6 +114,11 @@ static bool async_error = false;
111114
static uint16_t async_nonce = 0;
112115
static int async_delay = 1000;
113116
static int async_tick = 0;
117+
static int strobe_row = 0;
118+
static int strobe_mask = 0x20;
119+
static int minimum_brightness = 0;
120+
static Ticker renderTimer;
121+
114122

115123
STATIC void wakeup_event() {
116124
// Wake up any fibers that were blocked on the animation (if any).
@@ -134,7 +142,103 @@ STATIC void async_stop(void) {
134142
wakeup_event();
135143
}
136144

137-
void microbit_display_tick(void) {
145+
struct DisplayPoint {
146+
uint8_t x;
147+
uint8_t y;
148+
};
149+
150+
#define NO_CONN 0
151+
152+
DisplayPoint display_map[MICROBIT_DISPLAY_COLUMN_COUNT][MICROBIT_DISPLAY_ROW_COUNT] = {
153+
{{0,0}, {4,2}, {2,4}},
154+
{{2,0}, {0,2}, {4,4}},
155+
{{4,0}, {2,2}, {0,4}},
156+
{{4,3}, {1,0}, {0,1}},
157+
{{3,3}, {3,0}, {1,1}},
158+
{{2,3}, {3,4}, {2,1}},
159+
{{1,3}, {1,4}, {3,1}},
160+
{{0,3}, {NO_CONN,NO_CONN}, {4,1}},
161+
{{1,2}, {NO_CONN,NO_CONN}, {3,2}}
162+
};
163+
164+
static void set_pins_for_pixels(int brightness) {
165+
166+
int column_strobe = 0;
167+
168+
// Calculate the bitpattern to write.
169+
for (int i = 0; i<MICROBIT_DISPLAY_COLUMN_COUNT; i++)
170+
{
171+
int x = display_map[i][strobe_row].x;
172+
int y = display_map[i][strobe_row].y;
173+
174+
if (microbit_display_obj.image_buffer[x][y] >= brightness)
175+
column_strobe |= (1 << i);
176+
}
177+
178+
//write the new bit pattern
179+
//set port 0 4-7 and retain lower 4 bits
180+
nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT0, ~column_strobe<<4 & 0xF0 | nrf_gpio_port_read(NRF_GPIO_PORT_SELECT_PORT0) & 0x0F);
181+
182+
//set port 1 8-12 for the current row
183+
nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobe_mask | (~column_strobe>>4 & 0x1F));
184+
185+
}
186+
187+
static void clear_row()
188+
{
189+
//clear the old bit pattern for this row.
190+
//clear port 0 4-7 and retain lower 4 bits
191+
nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT0, 0xF0 | nrf_gpio_port_read(NRF_GPIO_PORT_SELECT_PORT0) & 0x0F);
192+
193+
// clear port 1 8-12 for the current row
194+
nrf_gpio_port_write(NRF_GPIO_PORT_SELECT_PORT1, strobe_mask | 0x1F);
195+
}
196+
197+
static void microbit_display_advance_row(void) {
198+
199+
/* Clear the old row */
200+
clear_row();
201+
202+
// Move on to the next row.
203+
strobe_mask <<= 1;
204+
strobe_row++;
205+
206+
//reset the row counts and bit mask when we have hit the max.
207+
if(strobe_row == MICROBIT_DISPLAY_ROW_COUNT) {
208+
strobe_row = 0;
209+
strobe_mask = 0x20;
210+
}
211+
/* Turn on any pixels that are at max */
212+
set_pins_for_pixels(MAX_BRIGHTNESS);
213+
214+
}
215+
216+
static const int render_timings[] =
217+
{ 0, /* Brightness, Duration */
218+
32, /* 1, 32 */
219+
32, /* 2, 64 */
220+
64, /* 3, 128 */
221+
128, /* 4, 256 */
222+
256, /* 5, 512 */
223+
480, /* 6, 992 */
224+
924, /* 7, 1916 */
225+
1784, /* 8, 3700 */
226+
/* Always on 9, 6000 */
227+
};
228+
229+
static void render_row() {
230+
231+
// Attach to timer
232+
if (minimum_brightness < MAX_BRIGHTNESS) {
233+
renderTimer.attach_us(render_row, render_timings[minimum_brightness]);
234+
}
235+
set_pins_for_pixels(minimum_brightness);
236+
++minimum_brightness;
237+
238+
}
239+
240+
241+
static void microbit_display_update(void) {
138242
async_tick += FIBER_TICK_PERIOD_MS;
139243
if(async_tick < async_delay)
140244
return;
@@ -183,6 +287,17 @@ void microbit_display_tick(void) {
183287
}
184288
}
185289

290+
void microbit_display_tick(void) {
291+
292+
microbit_display_advance_row();
293+
294+
microbit_display_update();
295+
296+
minimum_brightness = 1;
297+
render_row();
298+
299+
}
300+
186301
STATIC void do_synchronous_animation_once(microbit_display_obj_t *display, mp_obj_t iterator, mp_int_t delay) {
187302
do {
188303
async_tick = 0;
@@ -301,51 +416,27 @@ mp_obj_t microbit_display_clear(void) {
301416
}
302417
MP_DEFINE_CONST_FUN_OBJ_1(microbit_display_clear_obj, microbit_display_clear);
303418

304-
mp_obj_t microbit_display_set_pixel_raw(mp_uint_t n_args, const mp_obj_t *args) {
305-
(void)n_args;
306-
microbit_display_obj_t *self = (microbit_display_obj_t*)args[0];
307-
self->display->image.setPixelValue(mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3]));
308-
return mp_const_none;
309-
}
310-
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(microbit_display_set_pixel_raw_obj, 4, 4, microbit_display_set_pixel_raw);
311-
312419
mp_obj_t microbit_display_set_pixel(mp_uint_t n_args, const mp_obj_t *args) {
313420
(void)n_args;
314421
microbit_display_obj_t *self = (microbit_display_obj_t*)args[0];
315422
mp_int_t bright = mp_obj_get_int(args[3]);
316423
if (bright < 0 || bright > MAX_BRIGHTNESS)
317424
nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "brightness out of bounds."));
318-
self->display->image.setPixelValue(mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), BRIGHTNESS_SCALE[bright]);
425+
self->image_buffer[mp_obj_get_int(args[1])][mp_obj_get_int(args[2])] = bright;
319426
return mp_const_none;
320427
}
321428
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(microbit_display_set_pixel_obj, 4, 4, microbit_display_set_pixel);
322429

323-
mp_obj_t microbit_display_get_pixel_raw(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) {
324-
microbit_display_obj_t *self = (microbit_display_obj_t*)self_in;
325-
mp_int_t raw = self->display->image.getPixelValue(mp_obj_get_int(x_in), mp_obj_get_int(y_in));
326-
return MP_OBJ_NEW_SMALL_INT(raw);
327-
}
328-
MP_DEFINE_CONST_FUN_OBJ_3(microbit_display_get_pixel_raw_obj, microbit_display_get_pixel_raw);
329-
330430
mp_obj_t microbit_display_get_pixel(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) {
331431
microbit_display_obj_t *self = (microbit_display_obj_t*)self_in;
332-
mp_int_t raw = self->display->image.getPixelValue(mp_obj_get_int(x_in), mp_obj_get_int(y_in));
333-
for (int i = 0; i < MAX_BRIGHTNESS; ++i) {
334-
mp_int_t bright = BRIGHTNESS_SCALE[i];
335-
mp_int_t next = BRIGHTNESS_SCALE[i+1];
336-
if (raw < (bright + next)/2)
337-
return MP_OBJ_NEW_SMALL_INT(i);
338-
}
339-
return MP_OBJ_NEW_SMALL_INT(MAX_BRIGHTNESS);
432+
return MP_OBJ_NEW_SMALL_INT(self->image_buffer[mp_obj_get_int(x_in)][mp_obj_get_int(y_in)]);
340433
}
341434
MP_DEFINE_CONST_FUN_OBJ_3(microbit_display_get_pixel_obj, microbit_display_get_pixel);
342435

343436
STATIC const mp_map_elem_t microbit_display_locals_dict_table[] = {
344437

345438
{ MP_OBJ_NEW_QSTR(MP_QSTR_get_pixel), (mp_obj_t)&microbit_display_get_pixel_obj },
346-
{ MP_OBJ_NEW_QSTR(MP_QSTR_get_pixel_raw), (mp_obj_t)&microbit_display_get_pixel_raw_obj },
347439
{ MP_OBJ_NEW_QSTR(MP_QSTR_set_pixel), (mp_obj_t)&microbit_display_set_pixel_obj },
348-
{ MP_OBJ_NEW_QSTR(MP_QSTR_set_pixel_raw), (mp_obj_t)&microbit_display_set_pixel_raw_obj },
349440
{ MP_OBJ_NEW_QSTR(MP_QSTR_print), (mp_obj_t)&microbit_display_print_obj },
350441
{ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), (mp_obj_t)&microbit_display_scroll_obj },
351442
{ MP_OBJ_NEW_QSTR(MP_QSTR_animate), (mp_obj_t)&microbit_display_animate_obj },
@@ -372,14 +463,16 @@ STATIC const mp_obj_type_t microbit_display_type = {
372463
/* .locals_dict = */ (mp_obj_t)&microbit_display_locals_dict,
373464
};
374465

375-
const microbit_display_obj_t microbit_display_obj = {
466+
microbit_display_obj_t microbit_display_obj = {
376467
{&microbit_display_type},
377-
.display = &uBit.display
468+
{ 0 }
378469
};
379470

380471
void microbit_display_init(void) {
381-
microbit_display_obj.display->setBrightness(255);
382-
microbit_display_obj.display->setDisplayMode(DISPLAY_MODE_GREYSCALE);
472+
//set pins as output
473+
nrf_gpio_range_cfg_output(MICROBIT_DISPLAY_COLUMN_START,MICROBIT_DISPLAY_COLUMN_START + MICROBIT_DISPLAY_COLUMN_COUNT + MICROBIT_DISPLAY_ROW_COUNT);
474+
475+
uBit.display.disable();
383476
}
384477

385478
}

source/microbit/microbitimage.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,6 @@ extern "C" {
3434
#include "microbitimage.h"
3535
#include "py/runtime0.h"
3636

37-
/* This scale has been determined experimentally (and subjectively)
38-
It is not as even as I (Mark Shannon) would like.
39-
*/
40-
int BRIGHTNESS_SCALE[] = {
41-
/* 0 */ 0,
42-
/* 1 */ 2,
43-
/* 2 */ 5,
44-
/* 3 */ 10,
45-
/* 4 */ 14,
46-
/* 5 */ 24,
47-
/* 6 */ 43,
48-
/* 7 */ 78,
49-
/* 8 */ 141,
50-
/* 9 */ 255
51-
};
52-
53-
5437
monochrome_5by5_t BLANK_IMAGE = {
5538
{ &microbit_image_type },
5639
1, 0, 0, 0,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy