diff --git a/libraries/pico_unicorn/pico_unicorn.cpp b/libraries/pico_unicorn/pico_unicorn.cpp index 4548ee0db..71525abe9 100644 --- a/libraries/pico_unicorn/pico_unicorn.cpp +++ b/libraries/pico_unicorn/pico_unicorn.cpp @@ -44,16 +44,6 @@ enum pin { Y = 15, }; -constexpr uint32_t ROW_COUNT = 7; -constexpr uint32_t ROW_BYTES = 12; -constexpr uint32_t BCD_FRAMES = 15; // includes fet discharge frame -constexpr uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * BCD_FRAMES); - -// must be aligned for 32bit dma transfer -alignas(4) static uint8_t bitstream[BITSTREAM_LENGTH] = {0}; - -static uint32_t dma_channel; - static inline void unicorn_jetpack_program_init(PIO pio, uint sm, uint offset) { pio_gpio_init(pio, pin::LED_DATA); pio_gpio_init(pio, pin::LED_CLOCK); @@ -87,39 +77,55 @@ static inline void unicorn_jetpack_program_init(PIO pio, uint sm, uint offset) { pio_sm_set_enabled(pio, sm, true); } +static pimoroni::PicoUnicorn *unicorn = nullptr; + +void __isr picounicorn_dma_complete() { + if(unicorn) { + unicorn->dma_complete(); + } +} + namespace pimoroni { // once the dma transfer of the scanline is complete we move to the // next scanline (or quit if we're finished) - void __isr dma_complete() { - if (dma_hw->ints0 & (1u << dma_channel)) { - dma_hw->ints0 = (1u << dma_channel); // clear irq flag + void PicoUnicorn::dma_complete() { + if(dma_channel_get_irq0_status(dma_channel)) { + dma_channel_acknowledge_irq0(dma_channel); + dma_channel_set_trans_count(dma_channel, BITSTREAM_LENGTH / 4, false); - dma_channel_set_read_addr(dma_channel, bitstream, true); + dma_channel_set_read_addr(dma_channel, buffer, true); } } PicoUnicorn::~PicoUnicorn() { - // stop and release the dma channel irq_set_enabled(DMA_IRQ_0, false); - dma_channel_set_irq0_enabled(dma_channel, false); irq_set_enabled(pio_get_dreq(bitstream_pio, bitstream_sm, true), false); - irq_remove_handler(DMA_IRQ_0, dma_complete); - dma_channel_wait_for_finish_blocking(dma_channel); - dma_channel_unclaim(dma_channel); + if(dma_channel_is_claimed(dma_channel)) { + dma_channel_set_irq0_enabled(dma_channel, false); + irq_remove_handler(DMA_IRQ_0, picounicorn_dma_complete); + //dma_channel_wait_for_finish_blocking(dma_channel); + dma_channel_abort(dma_channel); + dma_channel_acknowledge_irq0(dma_channel); + dma_channel_unclaim(dma_channel); + } + + if(pio_sm_is_claimed(bitstream_pio, bitstream_sm)) { + pio_sm_set_enabled(bitstream_pio, bitstream_sm, false); + pio_sm_drain_tx_fifo(bitstream_pio, bitstream_sm); + pio_sm_unclaim(bitstream_pio, bitstream_sm); + } - // release the pio and sm - pio_sm_unclaim(bitstream_pio, bitstream_sm); pio_clear_instruction_memory(bitstream_pio); - pio_sm_restart(bitstream_pio, bitstream_sm); } + [[deprecated("Handled by constructor.")]] void PicoUnicorn::init() { - // todo: shouldn't need to do this if things were cleaned up properly but without - // this any attempt to run a micropython script twice will fail - static bool already_init = false; + return; + } + PicoUnicorn::PicoUnicorn() { // setup pins gpio_init(pin::LED_DATA); gpio_set_dir(pin::LED_DATA, GPIO_OUT); gpio_init(pin::LED_CLOCK); gpio_set_dir(pin::LED_CLOCK, GPIO_OUT); @@ -134,6 +140,8 @@ namespace pimoroni { gpio_init(pin::ROW_5); gpio_set_dir(pin::ROW_5, GPIO_OUT); gpio_init(pin::ROW_6); gpio_set_dir(pin::ROW_6, GPIO_OUT); + uint8_t *bitstream = (uint8_t *)buffer; + // initialise the bcd timing values and row selects in the bitstream for(uint8_t row = 0; row < HEIGHT; row++) { for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) { @@ -171,25 +179,6 @@ namespace pimoroni { gpio_set_function(pin::X, GPIO_FUNC_SIO); gpio_set_dir(pin::X, GPIO_IN); gpio_pull_up(pin::X); gpio_set_function(pin::Y, GPIO_FUNC_SIO); gpio_set_dir(pin::Y, GPIO_IN); gpio_pull_up(pin::Y); - if(already_init) { - // stop and release the dma channel - irq_set_enabled(DMA_IRQ_0, false); - dma_channel_abort(dma_channel); - dma_channel_wait_for_finish_blocking(dma_channel); - - dma_channel_set_irq0_enabled(dma_channel, false); - irq_set_enabled(pio_get_dreq(bitstream_pio, bitstream_sm, true), false); - irq_remove_handler(DMA_IRQ_0, dma_complete); - - dma_channel_unclaim(dma_channel); - - // release the pio and sm - pio_sm_unclaim(bitstream_pio, bitstream_sm); - pio_clear_instruction_memory(bitstream_pio); - pio_sm_restart(bitstream_pio, bitstream_sm); - //return; - } - // setup the pio bitstream_pio = pio0; bitstream_sm = pio_claim_unused_sm(pio0, true); @@ -203,15 +192,18 @@ namespace pimoroni { channel_config_set_bswap(&config, false); // byte swap to reverse little endian channel_config_set_dreq(&config, pio_get_dreq(bitstream_pio, bitstream_sm, true)); dma_channel_configure(dma_channel, &config, &bitstream_pio->txf[bitstream_sm], NULL, 0, false); - dma_channel_set_irq0_enabled(dma_channel, true); + + irq_set_exclusive_handler(DMA_IRQ_0, picounicorn_dma_complete); irq_set_enabled(pio_get_dreq(bitstream_pio, bitstream_sm, true), true); - irq_set_exclusive_handler(DMA_IRQ_0, dma_complete); + + dma_channel_set_irq0_enabled(dma_channel, true); + irq_set_enabled(DMA_IRQ_0, true); - dma_channel_set_trans_count(dma_channel, BITSTREAM_LENGTH / 4, false); - dma_channel_set_read_addr(dma_channel, bitstream, true); + unicorn = this; - already_init = true; + dma_channel_set_trans_count(dma_channel, BITSTREAM_LENGTH / 4, false); + dma_channel_set_read_addr(dma_channel, buffer, true); } void PicoUnicorn::clear() { @@ -225,6 +217,8 @@ namespace pimoroni { void PicoUnicorn::set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return; + uint8_t *bitstream = (uint8_t *)buffer; + // make those coordinates sane x = (WIDTH - 1) - x; diff --git a/libraries/pico_unicorn/pico_unicorn.hpp b/libraries/pico_unicorn/pico_unicorn.hpp index 9c7f9570a..ce2871c0f 100644 --- a/libraries/pico_unicorn/pico_unicorn.hpp +++ b/libraries/pico_unicorn/pico_unicorn.hpp @@ -4,7 +4,7 @@ namespace pimoroni { - class PicoUnicorn { + class alignas(4) PicoUnicorn { public: static const int WIDTH = 16; static const int HEIGHT = 7; @@ -12,13 +12,26 @@ namespace pimoroni { static const uint8_t B = 13; static const uint8_t X = 14; static const uint8_t Y = 15; + + static const uint32_t ROW_COUNT = 7; + static const uint32_t ROW_BYTES = 12; + static const uint32_t BCD_FRAMES = 15; // includes fet discharge frame + static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * BCD_FRAMES); + private: PIO bitstream_pio = pio0; uint bitstream_sm = 0; uint sm_offset = 0; + uint dma_channel; + // must be aligned for 32bit dma transfer + alignas(4) uint32_t buffer[BITSTREAM_LENGTH / 4] = {0}; + public: + PicoUnicorn(); ~PicoUnicorn(); + void dma_complete(); + void init(); void clear(); diff --git a/micropython/modules/pico_unicorn/pico_unicorn.c b/micropython/modules/pico_unicorn/pico_unicorn.c index 8fcc54ad7..cf0d3851c 100755 --- a/micropython/modules/pico_unicorn/pico_unicorn.c +++ b/micropython/modules/pico_unicorn/pico_unicorn.c @@ -16,26 +16,46 @@ enum buttons //////////////////////////////////////////////////////////////////////////////////////////////////// /***** Module Functions *****/ -STATIC MP_DEFINE_CONST_FUN_OBJ_0(picounicorn_init_obj, picounicorn_init); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picounicorn_set_pixel_obj, 6, 6, picounicorn_set_pixel); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picounicorn_set_pixel_value_obj, 4, 4, picounicorn_set_pixel_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(picounicorn_clear_obj, picounicorn_clear); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(picounicorn_is_pressed_obj, picounicorn_is_pressed); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(picounicorn__del__obj, picounicorn__del__); + +STATIC const mp_rom_map_elem_t picounicorn_locals_dict[] = { + { MP_ROM_QSTR(MP_QSTR_set_pixel), MP_ROM_PTR(&picounicorn_set_pixel_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_pixel_value), MP_ROM_PTR(&picounicorn_set_pixel_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&picounicorn_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&picounicorn_is_pressed_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&picounicorn__del__obj) }, +}; + +#ifdef MP_DEFINE_CONST_OBJ_TYPE +MP_DEFINE_CONST_OBJ_TYPE( + picounicorn_type, + MP_QSTR_PicoUnicorn, + MP_TYPE_FLAG_NONE, + make_new, picounicorn_make_new, + locals_dict, (mp_obj_dict_t*)&picounicorn_locals_dict +); +#else +const mp_obj_type_t picounicorn_type = { + { &mp_type_type }, + .name = MP_QSTR_PicoUnicorn, + .make_new = picounicorn_make_new, + .locals_dict = (mp_obj_dict_t*)&picounicorn_locals_dict, +}; +#endif + STATIC MP_DEFINE_CONST_FUN_OBJ_0(picounicorn_get_width_obj, picounicorn_get_width); STATIC MP_DEFINE_CONST_FUN_OBJ_0(picounicorn_get_height_obj, picounicorn_get_height); -//STATIC MP_DEFINE_CONST_FUN_OBJ_0(picounicorn_update_obj, picounicorn_update); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picounicorn_set_pixel_obj, 5, 5, picounicorn_set_pixel); -STATIC MP_DEFINE_CONST_FUN_OBJ_3(picounicorn_set_pixel_value_obj, picounicorn_set_pixel_value); -STATIC MP_DEFINE_CONST_FUN_OBJ_0(picounicorn_clear_obj, picounicorn_clear); -STATIC MP_DEFINE_CONST_FUN_OBJ_1(picounicorn_is_pressed_obj, picounicorn_is_pressed); /***** Globals Table *****/ STATIC const mp_rom_map_elem_t picounicorn_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_picounicorn) }, - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&picounicorn_init_obj) }, + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_picounicorn) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_PicoUnicorn), (mp_obj_t)&picounicorn_type }, { MP_ROM_QSTR(MP_QSTR_get_width), MP_ROM_PTR(&picounicorn_get_width_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_height), MP_ROM_PTR(&picounicorn_get_height_obj) }, - //{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&picounicorn_update_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_pixel), MP_ROM_PTR(&picounicorn_set_pixel_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_pixel_value), MP_ROM_PTR(&picounicorn_set_pixel_value_obj) }, - { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&picounicorn_clear_obj) }, - { MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&picounicorn_is_pressed_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_height), MP_ROM_PTR(&picounicorn_get_height_obj) }, { MP_ROM_QSTR(MP_QSTR_BUTTON_A), MP_ROM_INT(BUTTON_A) }, { MP_ROM_QSTR(MP_QSTR_BUTTON_B), MP_ROM_INT(BUTTON_B) }, { MP_ROM_QSTR(MP_QSTR_BUTTON_X), MP_ROM_INT(BUTTON_X) }, diff --git a/micropython/modules/pico_unicorn/pico_unicorn.cpp b/micropython/modules/pico_unicorn/pico_unicorn.cpp index 59e3e5258..50162b5e8 100644 --- a/micropython/modules/pico_unicorn/pico_unicorn.cpp +++ b/micropython/modules/pico_unicorn/pico_unicorn.cpp @@ -1,23 +1,27 @@ -#include "hardware/spi.h" -#include "hardware/sync.h" -#include "pico/binary_info.h" - #include "libraries/pico_unicorn/pico_unicorn.hpp" +#include "micropython/modules/util.hpp" using namespace pimoroni; -PicoUnicorn *unicorn = nullptr; - extern "C" { #include "pico_unicorn.h" -#define NOT_INITIALISED_MSG "Cannot call this function, as picounicorn is not initialised. Call picounicorn.init() first." +typedef struct _picounicorn_obj_t { + mp_obj_base_t base; + PicoUnicorn *unicorn; +} picounicorn_obj_t; + +mp_obj_t picounicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + picounicorn_obj_t *self = m_new_obj_with_finaliser(picounicorn_obj_t); + self->base.type = &picounicorn_type; + self->unicorn = m_new_class(PicoUnicorn); + return MP_OBJ_FROM_PTR(self); +} -mp_obj_t picounicorn_init() { - if(unicorn == nullptr) - unicorn = new PicoUnicorn(); - unicorn->init(); +mp_obj_t picounicorn__del__(mp_obj_t self_in) { + picounicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, picounicorn_obj_t); + self->unicorn->~PicoUnicorn(); return mp_const_none; } @@ -29,99 +33,84 @@ mp_obj_t picounicorn_get_height() { return mp_obj_new_int(PicoUnicorn::HEIGHT); } -// mp_obj_t picounicorn_update() { -// unicorn.update(); -// return mp_const_none; -// } - mp_obj_t picounicorn_set_pixel(mp_uint_t n_args, const mp_obj_t *args) { - (void)n_args; //Unused input parameter, we know it's 5 - - if(unicorn != nullptr) { - int x = mp_obj_get_int(args[0]); - int y = mp_obj_get_int(args[1]); - int r = mp_obj_get_int(args[2]); - int g = mp_obj_get_int(args[3]); - int b = mp_obj_get_int(args[4]); - - if(x < 0 || x >= PicoUnicorn::WIDTH || y < 0 || y >= PicoUnicorn::HEIGHT) - mp_raise_ValueError("x or y out of range."); - else - { - if(r < 0 || r > 255) - mp_raise_ValueError("r out of range. Expected 0 to 255"); - else if(g < 0 || g > 255) - mp_raise_ValueError("g out of range. Expected 0 to 255"); - else if(b < 0 || b > 255) - mp_raise_ValueError("b out of range. Expected 0 to 255"); - else - unicorn->set_pixel(x, y, r, g, b); - } + (void)n_args; // Unused input parameter, we know it's 5 + + enum { ARG_self, ARG_x, ARG_y, ARG_r, ARG_g, ARG_b }; + + picounicorn_obj_t *self = MP_OBJ_TO_PTR2(ARG_self, picounicorn_obj_t); + + int x = mp_obj_get_int(args[ARG_x]); + int y = mp_obj_get_int(args[ARG_y]); + int r = mp_obj_get_int(args[ARG_r]); + int g = mp_obj_get_int(args[ARG_g]); + int b = mp_obj_get_int(args[ARG_b]); + + if(x < 0 || x >= PicoUnicorn::WIDTH || y < 0 || y >= PicoUnicorn::HEIGHT) { + mp_raise_ValueError("x or y out of range."); } - else - mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + if(r < 0 || r > 255) mp_raise_ValueError("r out of range. Expected 0 to 255"); + if(g < 0 || g > 255) mp_raise_ValueError("g out of range. Expected 0 to 255"); + if(b < 0 || b > 255) mp_raise_ValueError("b out of range. Expected 0 to 255"); + self->unicorn->set_pixel(x, y, r, g, b); return mp_const_none; } -mp_obj_t picounicorn_set_pixel_value(mp_obj_t x_obj, mp_obj_t y_obj, mp_obj_t v_obj) { - if(unicorn != nullptr) { - int x = mp_obj_get_int(x_obj); - int y = mp_obj_get_int(y_obj); - int val = mp_obj_get_int(v_obj); - - if(x < 0 || x >= PicoUnicorn::WIDTH || y < 0 || y >= PicoUnicorn::HEIGHT) - mp_raise_ValueError("x or y out of range."); - else { - if(val < 0 || val > 255) - mp_raise_ValueError("val out of range. Expected 0 to 255"); - else - unicorn->set_pixel(x, y, val); - } +mp_obj_t picounicorn_set_pixel_value(size_t n_args, const mp_obj_t *args) { + enum { ARG_self, ARG_x, ARG_y, ARG_v }; + + picounicorn_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], picounicorn_obj_t); + + int x = mp_obj_get_int(args[ARG_x]); + int y = mp_obj_get_int(args[ARG_y]); + int val = mp_obj_get_int(args[ARG_v]); + + if(x < 0 || x >= PicoUnicorn::WIDTH || y < 0 || y >= PicoUnicorn::HEIGHT) { + mp_raise_ValueError("x or y out of range."); } - else - mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + if(val < 0 || val > 255) mp_raise_ValueError("val out of range. Expected 0 to 255"); + + self->unicorn->set_pixel(x, y, val); return mp_const_none; } -mp_obj_t picounicorn_clear() { - if(unicorn != nullptr) - unicorn->clear(); - else - mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); +mp_obj_t picounicorn_clear(mp_obj_t self_in) { + picounicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, picounicorn_obj_t); + self->unicorn->clear(); return mp_const_none; } -mp_obj_t picounicorn_is_pressed(mp_obj_t button_obj) { +mp_obj_t picounicorn_is_pressed(mp_obj_t self_in, mp_obj_t button_obj) { + picounicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, picounicorn_obj_t); + bool buttonPressed = false; - - if(unicorn != nullptr) { - int buttonID = mp_obj_get_int(button_obj); - switch(buttonID) { - case 0: - buttonPressed = unicorn->is_pressed(PicoUnicorn::A); - break; - - case 1: - buttonPressed = unicorn->is_pressed(PicoUnicorn::B); - break; - - case 2: - buttonPressed = unicorn->is_pressed(PicoUnicorn::X); - break; - - case 3: - buttonPressed = unicorn->is_pressed(PicoUnicorn::Y); - break; - - default: - mp_raise_ValueError("button not valid. Expected 0 to 3"); - break; - } + int buttonID = mp_obj_get_int(button_obj); + + switch(buttonID) { + case 0: + buttonPressed = self->unicorn->is_pressed(PicoUnicorn::A); + break; + + case 1: + buttonPressed = self->unicorn->is_pressed(PicoUnicorn::B); + break; + + case 2: + buttonPressed = self->unicorn->is_pressed(PicoUnicorn::X); + break; + + case 3: + buttonPressed = self->unicorn->is_pressed(PicoUnicorn::Y); + break; + + default: + mp_raise_ValueError("button not valid. Expected 0 to 3"); + break; } - else - mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); return buttonPressed ? mp_const_true : mp_const_false; } diff --git a/micropython/modules/pico_unicorn/pico_unicorn.h b/micropython/modules/pico_unicorn/pico_unicorn.h index cf3d43ccc..d0d20ef98 100644 --- a/micropython/modules/pico_unicorn/pico_unicorn.h +++ b/micropython/modules/pico_unicorn/pico_unicorn.h @@ -1,13 +1,13 @@ -// Include MicroPython API. -//#include "py/obj.h" #include "py/runtime.h" -// Declare the functions we'll make available in Python -extern mp_obj_t picounicorn_init(); +extern const mp_obj_type_t picounicorn_type; + extern mp_obj_t picounicorn_get_width(); extern mp_obj_t picounicorn_get_height(); -//extern mp_obj_t picounicorn_update(); + +extern mp_obj_t picounicorn__del__(mp_obj_t self_in); +extern mp_obj_t picounicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); extern mp_obj_t picounicorn_set_pixel(mp_uint_t n_args, const mp_obj_t *args); -extern mp_obj_t picounicorn_set_pixel_value(mp_obj_t x_obj, mp_obj_t y_obj, mp_obj_t v_obj); -extern mp_obj_t picounicorn_clear(); -extern mp_obj_t picounicorn_is_pressed(mp_obj_t button_obj); \ No newline at end of file +extern mp_obj_t picounicorn_set_pixel_value(mp_uint_t n_args, const mp_obj_t *args); +extern mp_obj_t picounicorn_clear(mp_obj_t self_in); +extern mp_obj_t picounicorn_is_pressed(mp_obj_t self_in, mp_obj_t button_obj); \ No newline at end of file