diff --git a/drivers/st7789/st7789.cpp b/drivers/st7789/st7789.cpp index 0c0e2969..0c49a89b 100644 --- a/drivers/st7789/st7789.cpp +++ b/drivers/st7789/st7789.cpp @@ -173,26 +173,47 @@ namespace pimoroni { } uint ST7789::get_sck() const { - return sck; + return wr_sck; } uint ST7789::get_mosi() const { - return mosi; + return d0; } uint ST7789::get_bl() const { return bl; } - void ST7789::command(uint8_t command, size_t len, const char *data) { - gpio_put(cs, 0); + void ST7789::write_blocking_parallel(const uint8_t *src, size_t len) { + uint32_t mask = 0xff << d0; + while(len--) { + gpio_put(wr_sck, false); + uint8_t v = *src++; + gpio_put_masked(mask, v << d0); + asm("nop;"); + gpio_put(wr_sck, true); + asm("nop;"); + } + } + void ST7789::command(uint8_t command, size_t len, const char *data) { gpio_put(dc, 0); // command mode - spi_write_blocking(spi, &command, 1); + + gpio_put(cs, 0); + + if(spi) { + spi_write_blocking(spi, &command, 1); + } else { + write_blocking_parallel(&command, 1); + } if(data) { gpio_put(dc, 1); // data mode - spi_write_blocking(spi, (const uint8_t*)data, len); + if(spi) { + spi_write_blocking(spi, (const uint8_t*)data, len); + } else { + write_blocking_parallel((const uint8_t*)data, len); + } } gpio_put(cs, 1); diff --git a/drivers/st7789/st7789.hpp b/drivers/st7789/st7789.hpp index e2bc7cc7..522924f1 100644 --- a/drivers/st7789/st7789.hpp +++ b/drivers/st7789/st7789.hpp @@ -23,8 +23,9 @@ namespace pimoroni { // interface pins with our standard defaults where appropriate uint cs; uint dc; - uint sck; - uint mosi; + uint wr_sck; + uint rd_sck = PIN_UNUSED; + uint d0; uint bl; uint vsync = PIN_UNUSED; // only available on some products @@ -37,46 +38,58 @@ namespace pimoroni { // frame buffer where pixel data is stored uint16_t *frame_buffer; + // Parallel init + ST7789(uint16_t width, uint16_t height, uint16_t *frame_buffer, + uint cs, uint dc, uint wr_sck, uint rd_sck, uint d0, uint bl = PIN_UNUSED) : + spi(nullptr), + width(width), height(height), round(false), + cs(cs), dc(dc), wr_sck(wr_sck), rd_sck(rd_sck), d0(d0), bl(bl), frame_buffer(frame_buffer) { + + gpio_set_function(cs, GPIO_FUNC_SIO); + gpio_set_dir(cs, GPIO_OUT); + + gpio_set_function(dc, GPIO_FUNC_SIO); + gpio_set_dir(dc, GPIO_OUT); + + gpio_set_function(wr_sck, GPIO_FUNC_SIO); + gpio_set_dir(wr_sck, GPIO_OUT); + + gpio_set_function(rd_sck, GPIO_FUNC_SIO); + gpio_set_dir(rd_sck, GPIO_OUT); + + for(auto i = 0u; i < 8; i++) { + gpio_set_function(d0 + i, GPIO_FUNC_SIO); + gpio_set_dir(d0 + i, GPIO_OUT); + } + + gpio_put(rd_sck, 1); + + common_init(); + } + + // Serial init ST7789(uint16_t width, uint16_t height, bool round, uint16_t *frame_buffer, spi_inst_t *spi, uint cs, uint dc, uint sck, uint mosi, uint bl = PIN_UNUSED) : spi(spi), width(width), height(height), round(round), - cs(cs), dc(dc), sck(sck), mosi(mosi), bl(bl), frame_buffer(frame_buffer) { + cs(cs), dc(dc), wr_sck(sck), d0(mosi), bl(bl), frame_buffer(frame_buffer) { - if(!this->frame_buffer) { - this->frame_buffer = new uint16_t[width * height]; - } + // configure spi interface and pins + spi_init(spi, SPI_BAUD); - // configure spi interface and pins - spi_init(spi, SPI_BAUD); + gpio_set_function(dc, GPIO_FUNC_SIO); + gpio_set_dir(dc, GPIO_OUT); - gpio_set_function(dc, GPIO_FUNC_SIO); - gpio_set_dir(dc, GPIO_OUT); + gpio_set_function(cs, GPIO_FUNC_SIO); + gpio_set_dir(cs, GPIO_OUT); - gpio_set_function(cs, GPIO_FUNC_SIO); - gpio_set_dir(cs, GPIO_OUT); + gpio_set_function(wr_sck, GPIO_FUNC_SPI); + gpio_set_function(d0, GPIO_FUNC_SPI); - gpio_set_function(sck, GPIO_FUNC_SPI); - gpio_set_function(mosi, GPIO_FUNC_SPI); + common_init(); + } - // if a backlight pin is provided then set it up for - // pwm control - if(bl != PIN_UNUSED) { - pwm_config cfg = pwm_get_default_config(); - pwm_set_wrap(pwm_gpio_to_slice_num(bl), 65535); - pwm_init(pwm_gpio_to_slice_num(bl), &cfg, true); - gpio_set_function(bl, GPIO_FUNC_PWM); - set_backlight(0); // Turn backlight off initially to avoid nasty surprises - } - - } - - - //-------------------------------------------------- - // Methods - //-------------------------------------------------- - public: void init(); void configure_display(bool rotate180); @@ -115,6 +128,24 @@ namespace pimoroni { } return PIN_UNUSED; }; + + private: + void write_blocking_parallel(const uint8_t *src, size_t len); + void common_init() { + if(!this->frame_buffer) { + this->frame_buffer = new uint16_t[width * height]; + } + + // if a backlight pin is provided then set it up for + // pwm control + if(bl != PIN_UNUSED) { + pwm_config cfg = pwm_get_default_config(); + pwm_set_wrap(pwm_gpio_to_slice_num(bl), 65535); + pwm_init(pwm_gpio_to_slice_num(bl), &cfg, true); + gpio_set_function(bl, GPIO_FUNC_PWM); + set_backlight(0); // Turn backlight off initially to avoid nasty surprises + } + } }; } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8e45b4d8..f9675d27 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -44,6 +44,7 @@ add_subdirectory(pico_wireless) add_subdirectory(plasma2040) add_subdirectory(badger2040) +add_subdirectory(tufty2040) add_subdirectory(interstate75) add_subdirectory(servo2040) add_subdirectory(motor2040) diff --git a/examples/tufty2040/CMakeLists.txt b/examples/tufty2040/CMakeLists.txt new file mode 100644 index 00000000..282ad558 --- /dev/null +++ b/examples/tufty2040/CMakeLists.txt @@ -0,0 +1 @@ +include(tufty2040_drawing.cmake) diff --git a/examples/tufty2040/tufty2040_drawing.cmake b/examples/tufty2040/tufty2040_drawing.cmake new file mode 100644 index 00000000..f918ea3f --- /dev/null +++ b/examples/tufty2040/tufty2040_drawing.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME tufty2040_drawing) +add_executable(${OUTPUT_NAME} tufty2040_drawing.cpp) + +target_link_libraries(${OUTPUT_NAME} + tufty2040 + hardware_spi + generic_st7789 + button +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/tufty2040/tufty2040_drawing.cpp b/examples/tufty2040/tufty2040_drawing.cpp new file mode 100644 index 00000000..687df518 --- /dev/null +++ b/examples/tufty2040/tufty2040_drawing.cpp @@ -0,0 +1,137 @@ +#include "pico/stdlib.h" +#include +#include +#include +#include +#include "pico/time.h" +#include "pico/platform.h" + +#include "common/pimoroni_common.hpp" +#include "generic_st7789.hpp" +#include "tufty2040.hpp" +#include "button.hpp" + +using namespace pimoroni; + +Tufty2040 tufty; + +uint16_t buffer[Tufty2040::WIDTH * Tufty2040::HEIGHT]; + + static const uint8_t LCD_CS = 10; + static const uint8_t LCD_DC = 11; + static const uint8_t LCD_WR = 12; + static const uint8_t LCD_RD = 13; + static const uint8_t LCD_D0 = 14; + + + +// Swap WIDTH and HEIGHT to rotate 90 degrees +ST7789Generic pico_display( + Tufty2040::WIDTH, Tufty2040::HEIGHT, + buffer, + Tufty2040::LCD_CS, Tufty2040::LCD_DC, Tufty2040::LCD_WR, Tufty2040::LCD_RD, Tufty2040::LCD_D0, + Tufty2040::BACKLIGHT +); + +Button button_a(Tufty2040::A); +Button button_b(Tufty2040::B); +Button button_c(Tufty2040::C); +Button button_up(Tufty2040::UP); +Button button_down(Tufty2040::DOWN); + +uint32_t time() { + absolute_time_t t = get_absolute_time(); + return to_ms_since_boot(t); +} + +// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel +// Outputs are rgb in the range 0-255 for each channel +void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) { + float i = floor(h * 6.0f); + float f = h * 6.0f - i; + v *= 255.0f; + uint8_t p = v * (1.0f - s); + uint8_t q = v * (1.0f - f * s); + uint8_t t = v * (1.0f - (1.0f - f) * s); + + switch (int(i) % 6) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } +} + +int main() { + pico_display.set_backlight(255); + pico_display.configure_display(true); // Rotate 180 + + struct pt { + float x; + float y; + uint8_t r; + float dx; + float dy; + uint16_t pen; + }; + + std::vector shapes; + for(int i = 0; i < 100; i++) { + pt shape; + shape.x = rand() % pico_display.bounds.w; + shape.y = rand() % pico_display.bounds.h; + shape.r = (rand() % 10) + 3; + shape.dx = float(rand() % 255) / 64.0f; + shape.dy = float(rand() % 255) / 64.0f; + shape.pen = pico_display.create_pen(rand() % 255, rand() % 255, rand() % 255); + shapes.push_back(shape); + } + + Point text_location(0, 0); + uint8_t i = 0; + + while(true) { + + pico_display.set_pen(120, 40, 60); + pico_display.clear(); + + for(auto &shape : shapes) { + shape.x += shape.dx; + shape.y += shape.dy; + if((shape.x - shape.r) < 0) { + shape.dx *= -1; + shape.x = shape.r; + } + if((shape.x + shape.r) >= pico_display.bounds.w) { + shape.dx *= -1; + shape.x = pico_display.bounds.w - shape.r; + } + if((shape.y - shape.r) < 0) { + shape.dy *= -1; + shape.y = shape.r; + } + if((shape.y + shape.r) >= pico_display.bounds.h) { + shape.dy *= -1; + shape.y = pico_display.bounds.h - shape.r; + } + + pico_display.set_pen(shape.pen); + pico_display.circle(Point(shape.x, shape.y), shape.r); + + } + + + pico_display.set_pen(255, 255, 255); + pico_display.text("Hello World", text_location, 320); + + // update screen + pico_display.update(); + + i+=10; + tufty.led(i); + } + + return 0; +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 0304d153..47f1031c 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -29,6 +29,7 @@ add_subdirectory(pico_rgb_keypad) add_subdirectory(pico_wireless) add_subdirectory(plasma2040) add_subdirectory(badger2040) +add_subdirectory(tufty2040) add_subdirectory(servo2040) add_subdirectory(motor2040) add_subdirectory(adcfft) diff --git a/libraries/generic_st7789/generic_st7789.hpp b/libraries/generic_st7789/generic_st7789.hpp index fd7e61d0..f68aaa9c 100644 --- a/libraries/generic_st7789/generic_st7789.hpp +++ b/libraries/generic_st7789/generic_st7789.hpp @@ -33,6 +33,14 @@ namespace pimoroni { this->st7789.init(); }; + ST7789Generic(uint16_t width, uint16_t height, uint16_t *frame_buffer, + uint cs, uint dc, uint wr_sck, uint rd_sck, uint d0, uint bl = PIN_UNUSED) : + PicoGraphics(width, height, frame_buffer), + st7789(width, height, frame_buffer, cs, dc, wr_sck, rd_sck, d0, bl) { + this->frame_buffer = st7789.frame_buffer; + this->st7789.init(); + }; + spi_inst_t* get_spi() const; int get_cs() const; int get_dc() const; diff --git a/libraries/tufty2040/CMakeLists.txt b/libraries/tufty2040/CMakeLists.txt new file mode 100644 index 00000000..8f727a06 --- /dev/null +++ b/libraries/tufty2040/CMakeLists.txt @@ -0,0 +1 @@ +include(tufty2040.cmake) \ No newline at end of file diff --git a/libraries/tufty2040/tufty2040.cmake b/libraries/tufty2040/tufty2040.cmake new file mode 100644 index 00000000..ecc4dccb --- /dev/null +++ b/libraries/tufty2040/tufty2040.cmake @@ -0,0 +1,11 @@ +set(LIB_NAME tufty2040) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp +) + +target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hardware_pwm) diff --git a/libraries/tufty2040/tufty2040.cpp b/libraries/tufty2040/tufty2040.cpp new file mode 100644 index 00000000..c32350c6 --- /dev/null +++ b/libraries/tufty2040/tufty2040.cpp @@ -0,0 +1,7 @@ +#include +#include + +#include "tufty2040.hpp" + +namespace pimoroni { +} \ No newline at end of file diff --git a/libraries/tufty2040/tufty2040.hpp b/libraries/tufty2040/tufty2040.hpp new file mode 100644 index 00000000..dca2283f --- /dev/null +++ b/libraries/tufty2040/tufty2040.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include "hardware/gpio.h" // Workaround SDK bug - https://github.com/raspberrypi/pico-sdk/issues/3 +#include "hardware/pwm.h" + +namespace pimoroni { + + class Tufty2040 { + public: + static const int WIDTH = 320; + static const int HEIGHT = 240; + static const uint8_t A = 7; + static const uint8_t B = 8; + static const uint8_t C = 9; + static const uint8_t Y = 15; + static const uint8_t UP = 22; + static const uint8_t DOWN = 6; + static const uint8_t LED = 25; + static const uint8_t BACKLIGHT = 2; + + + static const uint8_t LCD_CS = 10; + static const uint8_t LCD_DC = 11; + static const uint8_t LCD_WR = 12; + static const uint8_t LCD_RD = 13; + static const uint8_t LCD_D0 = 14; + + public: + Tufty2040() { + + gpio_set_function(LCD_D0 + 0, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 0, true); + gpio_set_function(LCD_D0 + 1, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 1, true); + gpio_set_function(LCD_D0 + 2, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 2, true); + gpio_set_function(LCD_D0 + 3, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 3, true); + gpio_set_function(LCD_D0 + 4, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 4, true); + gpio_set_function(LCD_D0 + 5, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 5, true); + gpio_set_function(LCD_D0 + 6, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 6, true); + gpio_set_function(LCD_D0 + 7, GPIO_FUNC_SIO); gpio_set_dir(LCD_D0 + 7, true); + + // led control pin + pwm_config cfg = pwm_get_default_config(); + pwm_set_wrap(pwm_gpio_to_slice_num(LED), 65535); + pwm_init(pwm_gpio_to_slice_num(LED), &cfg, true); + gpio_set_function(LED, GPIO_FUNC_PWM); + led(0); + } + + void led(uint8_t brightness) { + // set the led brightness from 1 to 256 with gamma correction + float gamma = 2.8; + uint16_t v = (uint16_t)(pow((float)(brightness) / 256.0f, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(LED, v); + } + + }; + +}