| @@ -9,6 +9,7 @@ idf_component_register( | |||||
| "utils.cpp" | "utils.cpp" | ||||
| "leds.cpp" | "leds.cpp" | ||||
| "presets.cpp" | "presets.cpp" | ||||
| "spi_leds.cpp" | |||||
| INCLUDE_DIRS "." | INCLUDE_DIRS "." | ||||
| ) | ) | ||||
| @@ -81,7 +81,3 @@ void TerminalLEDs::show() const { | |||||
| line += "\x1b[0m"; | line += "\x1b[0m"; | ||||
| ESP_LOGI(TAG, "%s", line.c_str()); | ESP_LOGI(TAG, "%s", line.c_str()); | ||||
| } | } | ||||
| void TerminalLEDs::length_changing(int new_len) { | |||||
| ESP_LOGI(TAG, "Length changing: %d->%d", length, new_len); | |||||
| } | |||||
| @@ -6,7 +6,7 @@ | |||||
| struct Color { | struct Color { | ||||
| uint8_t r, g, b; | uint8_t r, g, b; | ||||
| }; | |||||
| } __attribute__ ((packed)); | |||||
| typedef uint16_t Fixed; | typedef uint16_t Fixed; | ||||
| constexpr int factor = 65536; | constexpr int factor = 65536; | ||||
| @@ -59,5 +59,4 @@ class TerminalLEDs : public LEDStrip { | |||||
| public: | public: | ||||
| TerminalLEDs(int length = 0) : LEDStrip(length) {} | TerminalLEDs(int length = 0) : LEDStrip(length) {} | ||||
| void show() const; | void show() const; | ||||
| void length_changing(int); | |||||
| }; | }; | ||||
| @@ -15,6 +15,7 @@ static const char *TAG = "blinky"; | |||||
| #include "leds.hpp" | #include "leds.hpp" | ||||
| #include "presets.hpp" | #include "presets.hpp" | ||||
| #include "utils.hpp" | #include "utils.hpp" | ||||
| #include "spi_leds.hpp" | |||||
| // mDNS / NetBIOS | // mDNS / NetBIOS | ||||
| @@ -167,7 +168,7 @@ extern "C" void app_main(void) { | |||||
| cJSON_Delete(json); | cJSON_Delete(json); | ||||
| } | } | ||||
| LEDStrip *LEDs = new TerminalLEDs(); | |||||
| LEDStrip *LEDs = new SPI_LEDs(39); //new TerminalLEDs(); | |||||
| while (true) { | while (true) { | ||||
| ESP_LOGI(TAG, "Configuring LEDs"); | ESP_LOGI(TAG, "Configuring LEDs"); | ||||
| @@ -209,16 +210,46 @@ extern "C" void app_main(void) { | |||||
| } else { | } else { | ||||
| ESP_LOGI(TAG, "Starting animation"); | ESP_LOGI(TAG, "Starting animation"); | ||||
| int period_us = 1000000 / 4; | |||||
| int64_t target_us = time_us(); | |||||
| int period_us = 1000000 / 60; | |||||
| int64_t start = time_us(); | |||||
| int64_t target_us = start; | |||||
| int64_t total_delay_us = 0; | |||||
| int n_delays = 0; | |||||
| int64_t after_sleep_us = 0; | |||||
| int n_processes = 0; | |||||
| int64_t total_process_us = 0; | |||||
| while (true) { | while (true) { | ||||
| int64_t delay_us = target_us - time_us(); | |||||
| int64_t now = time_us(); | |||||
| if (after_sleep_us > 0) { | |||||
| total_process_us += now - after_sleep_us; | |||||
| ++n_processes; | |||||
| } | |||||
| if (now - start >= 5000000) { | |||||
| ESP_LOGI(TAG, "Average delay: %f ms", (double)total_delay_us / (n_delays * 1000)); | |||||
| total_delay_us = 0; | |||||
| n_delays = 0; | |||||
| ESP_LOGI(TAG, "Average processing: %f us", (double)total_process_us / n_processes); | |||||
| total_process_us = 0; | |||||
| n_processes = 0; | |||||
| start = now; | |||||
| } | |||||
| int64_t delay_us = target_us - now; | |||||
| if (delay_us < 0) delay_us = 0; | if (delay_us < 0) delay_us = 0; | ||||
| if (device.wait(delay_us / 1000)) { | if (device.wait(delay_us / 1000)) { | ||||
| // If the semaphore is set, go back to top of outer loop | // If the semaphore is set, go back to top of outer loop | ||||
| break; | break; | ||||
| } | } | ||||
| after_sleep_us = time_us(); | |||||
| total_delay_us += (after_sleep_us - now); | |||||
| ++n_delays; | |||||
| LEDs->step(); | LEDs->step(); | ||||
| LEDs->show(); | LEDs->show(); | ||||
| @@ -0,0 +1,127 @@ | |||||
| static const char *TAG = "spi_leds"; | |||||
| #include <esp_log.h> | |||||
| #include <string.h> | |||||
| #include <driver/gpio.h> | |||||
| #include <soc/io_mux_reg.h> | |||||
| #include "spi_leds.hpp" | |||||
| #define RET_ON_ERR(func, err) { if (err) { ESP_LOGE(TAG, #func ": %d", err); return; } } | |||||
| // LCD is using bus 2 in quad SPI mode | |||||
| SPI_LEDs::SPI_LEDs(int _gpio, int length) : | |||||
| LEDStrip(length), | |||||
| host(SPI3_HOST), gpio(_gpio), device(NULL), | |||||
| out_buf(NULL) { | |||||
| int ret; | |||||
| spi_bus_config_t buscfg = { | |||||
| .mosi_io_num = gpio, | |||||
| .miso_io_num = -1, | |||||
| .sclk_io_num = -1, | |||||
| .data2_io_num = -1, | |||||
| .data3_io_num = -1, | |||||
| .data4_io_num = -1, | |||||
| .data5_io_num = -1, | |||||
| .data6_io_num = -1, | |||||
| .data7_io_num = -1, | |||||
| .max_transfer_sz = 0, // 4092 (?) | |||||
| .flags = SPICOMMON_BUSFLAG_MASTER, | |||||
| .intr_flags = 0, | |||||
| }; | |||||
| // Initialize the SPI bus | |||||
| ret = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); | |||||
| RET_ON_ERR(spi_bus_initialize, ret); | |||||
| // Invert the signal! | |||||
| // TODO: Clean way thru API? | |||||
| uint32_t *GPIO39_REG = (uint32_t*)(void*)(0x3f404000 + 0x0554 + (4 * 39)); | |||||
| *GPIO39_REG |= 0x200; | |||||
| spi_device_interface_config_t devcfg = { | |||||
| .command_bits = 0, | |||||
| .address_bits = 0, | |||||
| .dummy_bits = 0, | |||||
| .mode = 0, // moot | |||||
| .duty_cycle_pos = 0, // moot | |||||
| .cs_ena_pretrans = 0, // moot | |||||
| .cs_ena_posttrans = 0, // moot | |||||
| // Datasheet implies 3.2 MHz => 1.25 us / 4 | |||||
| // We can get away with 4.4_ MHz => 900 ns / 4 | |||||
| .clock_speed_hz = 4444444, | |||||
| .input_delay_ns = 0, // moot | |||||
| .spics_io_num = -1, | |||||
| .flags = 0, // Keep it MSB first | |||||
| .queue_size = 1, | |||||
| .pre_cb = NULL, | |||||
| .post_cb = NULL, | |||||
| }; | |||||
| ret = spi_bus_add_device(host, &devcfg, &device); | |||||
| RET_ON_ERR(spi_bus_add_device, ret); | |||||
| configure(length); | |||||
| } | |||||
| void SPI_LEDs::configure(int len) { | |||||
| if (out_buf) { | |||||
| free(out_buf); | |||||
| out_buf = NULL; | |||||
| } | |||||
| if (len <= 0) return; | |||||
| out_buf = (uint8_t*)heap_caps_malloc(tx_len(len), MALLOC_CAP_DMA); | |||||
| } | |||||
| // Inverted | |||||
| constexpr uint8_t nibble_0 = 0b0111; | |||||
| constexpr uint8_t nibble_1 = 0b0011; | |||||
| static uint8_t* encode_byte(uint8_t *out, uint8_t in) { | |||||
| for (int i = 8; i > 0; i -= 2) { | |||||
| *out++ = (((in & 0x80) ? nibble_1 : nibble_0) << 4) | | |||||
| (((in & 0x40) ? nibble_1 : nibble_0) << 0); | |||||
| in <<= 2; | |||||
| } | |||||
| return out; | |||||
| } | |||||
| void SPI_LEDs::show() const { | |||||
| int ret; | |||||
| const Color *pixel = pixels; | |||||
| uint8_t *out = out_buf; | |||||
| for (int i = length; i > 0; --i) { | |||||
| out = encode_byte(out, pixel->r); | |||||
| out = encode_byte(out, pixel->g); | |||||
| out = encode_byte(out, pixel->b); | |||||
| ++pixel; | |||||
| } | |||||
| // TODO: Reset pad? Or should we check clocks? | |||||
| spi_transaction_ext_t transaction_ext = { | |||||
| .base = { | |||||
| .flags = 0, | |||||
| .cmd = 0, | |||||
| .addr = 0, | |||||
| .length = tx_len(length) * 8, | |||||
| .rxlength = 0, | |||||
| .user = NULL, | |||||
| .tx_buffer = out_buf, | |||||
| .rx_buffer = NULL, | |||||
| }, | |||||
| .command_bits = 160, | |||||
| .address_bits = 0, | |||||
| .dummy_bits = 160, | |||||
| }; | |||||
| ret = spi_device_transmit(device, &transaction_ext.base); | |||||
| RET_ON_ERR(spi_device_transmit, ret); | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| #pragma once | |||||
| #include <driver/spi_master.h> | |||||
| #include "leds.hpp" | |||||
| class SPI_LEDs : public LEDStrip { | |||||
| public: | |||||
| SPI_LEDs(int gpio, int length = 0); | |||||
| void show() const; | |||||
| void length_changing(int len) { configure(len); } | |||||
| private: | |||||
| spi_host_device_t host; | |||||
| int gpio; | |||||
| spi_device_handle_t device; | |||||
| uint8_t *out_buf; | |||||
| static size_t tx_len(int len) { return (len * 3) * 4; } | |||||
| void configure(int length); | |||||
| }; | |||||