| @@ -37,8 +37,19 @@ void Pattern::step(Color pixels[], int len, int64_t &last_us, Fixed &offset) con | |||||
| last_us = now_us; | last_us = now_us; | ||||
| //ESP_LOGI(TAG, "duration %d", duration_us); | //ESP_LOGI(TAG, "duration %d", duration_us); | ||||
| /* | |||||
| int period_us = 1000000 / 60; | |||||
| int n_skipped = (duration_us - period_us / 2) / period_us; | |||||
| if (n_skipped) { | |||||
| ESP_LOGW(TAG, "Skipped %d frames", n_skipped); | |||||
| } | |||||
| */ | |||||
| // Don't make a major animation jump if it's been terribly long | |||||
| if (duration_us > 100000) duration_us = 100000; | |||||
| int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); | int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); | ||||
| if (reverse) offset -= offset_delta; else offset += offset_delta; | |||||
| if (reverse) offset += offset_delta; else offset -= offset_delta; | |||||
| Fixed off = march ? | Fixed off = march ? | ||||
| (((int)offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : | (((int)offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : | ||||
| offset; | offset; | ||||
| @@ -73,7 +84,7 @@ static std::string termcolor(Color c) { | |||||
| std::to_string((int)c.b) + "m"; | std::to_string((int)c.b) + "m"; | ||||
| } | } | ||||
| void TerminalLEDs::show() const { | |||||
| void TerminalLEDs::show() { | |||||
| std::string line; | std::string line; | ||||
| for (int i = 0; i < length; i++) { | for (int i = 0; i < length; i++) { | ||||
| line += termcolor(pixels[i]) + " "; | line += termcolor(pixels[i]) + " "; | ||||
| @@ -36,7 +36,7 @@ public: | |||||
| void step(); | void step(); | ||||
| virtual void show() const = 0; | |||||
| virtual void show() = 0; | |||||
| virtual void length_changing(int) {}; | virtual void length_changing(int) {}; | ||||
| void setPattern(const Pattern *_pattern) { pattern = _pattern; } | void setPattern(const Pattern *_pattern) { pattern = _pattern; } | ||||
| @@ -58,5 +58,5 @@ private: | |||||
| class TerminalLEDs : public LEDStrip { | class TerminalLEDs : public LEDStrip { | ||||
| public: | public: | ||||
| TerminalLEDs(int length = 0) : LEDStrip(length) {} | TerminalLEDs(int length = 0) : LEDStrip(length) {} | ||||
| void show() const; | |||||
| void show(); | |||||
| }; | }; | ||||
| @@ -131,6 +131,7 @@ std::string gen_config(bool is_on, const std::string &effect) { | |||||
| constexpr char config_path[] = "/spiffs/blinky.json"; | constexpr char config_path[] = "/spiffs/blinky.json"; | ||||
| //#define PROFILE | |||||
| // Entry Point | // Entry Point | ||||
| @@ -214,16 +215,16 @@ extern "C" void app_main(void) { | |||||
| int period_us = 1000000 / 60; | int period_us = 1000000 / 60; | ||||
| int64_t start = time_us(); | int64_t start = time_us(); | ||||
| int64_t target_us = start; | int64_t target_us = start; | ||||
| /* | |||||
| #ifdef PROFILE | |||||
| int64_t total_delay_us = 0; | int64_t total_delay_us = 0; | ||||
| int n_delays = 0; | int n_delays = 0; | ||||
| int64_t after_sleep_us = 0; | int64_t after_sleep_us = 0; | ||||
| int n_processes = 0; | int n_processes = 0; | ||||
| int64_t total_process_us = 0; | int64_t total_process_us = 0; | ||||
| */ | |||||
| #endif // PROFILE | |||||
| while (true) { | while (true) { | ||||
| int64_t now = time_us(); | int64_t now = time_us(); | ||||
| /* | |||||
| #ifdef PROFILE | |||||
| if (after_sleep_us > 0) { | if (after_sleep_us > 0) { | ||||
| total_process_us += now - after_sleep_us; | total_process_us += now - after_sleep_us; | ||||
| ++n_processes; | ++n_processes; | ||||
| @@ -240,23 +241,23 @@ extern "C" void app_main(void) { | |||||
| start = now; | start = now; | ||||
| } | } | ||||
| */ | |||||
| #endif // PROFILE | |||||
| int64_t delay_us = target_us - 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; | ||||
| } | } | ||||
| /* | |||||
| target_us = now + delay_us + period_us; | |||||
| #ifdef PROFILE | |||||
| after_sleep_us = time_us(); | after_sleep_us = time_us(); | ||||
| total_delay_us += (after_sleep_us - now); | total_delay_us += (after_sleep_us - now); | ||||
| ++n_delays; | ++n_delays; | ||||
| */ | |||||
| #endif // PROFILE | |||||
| LEDs->step(); | LEDs->step(); | ||||
| LEDs->show(); | LEDs->show(); | ||||
| target_us += period_us; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -16,7 +16,8 @@ static const char *TAG = "spi_leds"; | |||||
| SPI_LEDs::SPI_LEDs(int _gpio, int length) : | SPI_LEDs::SPI_LEDs(int _gpio, int length) : | ||||
| LEDStrip(length), | LEDStrip(length), | ||||
| host(SPI3_HOST), gpio(_gpio), device(NULL), | host(SPI3_HOST), gpio(_gpio), device(NULL), | ||||
| out_buf(NULL) { | |||||
| out_buf(NULL) | |||||
| { | |||||
| int ret; | int ret; | ||||
| spi_bus_config_t buscfg = { | spi_bus_config_t buscfg = { | ||||
| @@ -53,7 +54,7 @@ SPI_LEDs::SPI_LEDs(int _gpio, int length) : | |||||
| .cs_ena_posttrans = 0, // moot | .cs_ena_posttrans = 0, // moot | ||||
| // Datasheet implies 3.2 MHz => 1.25 us / 4 | // Datasheet implies 3.2 MHz => 1.25 us / 4 | ||||
| // We can get away with 4.4_ MHz => 900 ns / 4 | // We can get away with 4.4_ MHz => 900 ns / 4 | ||||
| .clock_speed_hz = 4444444, | |||||
| .clock_speed_hz = 3200000, | |||||
| .input_delay_ns = 0, // moot | .input_delay_ns = 0, // moot | ||||
| .spics_io_num = -1, | .spics_io_num = -1, | ||||
| .flags = 0, // Keep it MSB first | .flags = 0, // Keep it MSB first | ||||
| @@ -77,6 +78,19 @@ void SPI_LEDs::configure(int len) { | |||||
| if (len <= 0) return; | if (len <= 0) return; | ||||
| out_buf = (uint8_t*)heap_caps_malloc(tx_len(len), MALLOC_CAP_DMA); | out_buf = (uint8_t*)heap_caps_malloc(tx_len(len), MALLOC_CAP_DMA); | ||||
| memset(out_buf + tx_len(len) - reset_bytes, 0, reset_bytes); | |||||
| transaction = { | |||||
| .flags = 0, | |||||
| .cmd = 0, | |||||
| .addr = 0, | |||||
| .length = tx_len(len) * 8, | |||||
| .rxlength = 0, | |||||
| .user = NULL, | |||||
| .tx_buffer = out_buf, | |||||
| .rx_buffer = NULL, | |||||
| }; | |||||
| } | } | ||||
| // Inverted | // Inverted | ||||
| @@ -92,7 +106,8 @@ static uint8_t* encode_byte(uint8_t *out, uint8_t in) { | |||||
| return out; | return out; | ||||
| } | } | ||||
| void SPI_LEDs::show() const { | |||||
| void SPI_LEDs::show() { | |||||
| static int index = 0; | |||||
| int ret; | int ret; | ||||
| const Color *pixel = pixels; | const Color *pixel = pixels; | ||||
| uint8_t *out = out_buf; | uint8_t *out = out_buf; | ||||
| @@ -104,24 +119,24 @@ void SPI_LEDs::show() const { | |||||
| ++pixel; | ++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, | |||||
| }; | |||||
| ++index; | |||||
| ret = spi_device_transmit(device, &transaction_ext.base); | |||||
| RET_ON_ERR(spi_device_transmit, ret); | |||||
| ret = spi_device_queue_trans(device, &transaction, portMAX_DELAY); | |||||
| RET_ON_ERR(spi_device_queue_trans, ret); | |||||
| /* | |||||
| while ((ret = spi_device_queue_trans(device, &transaction, 0)) != ESP_OK) { | |||||
| if (ret == ESP_ERR_TIMEOUT) { | |||||
| // Wait for the previous transaction to finish | |||||
| ESP_LOGI(TAG, "SPI transaction collision (# %d)", index); | |||||
| spi_transaction_t *ret_trans; | |||||
| ret = spi_device_get_trans_result(device, &ret_trans, portMAX_DELAY); | |||||
| RET_ON_ERR(spi_device_get_trans_result, ret); | |||||
| ESP_LOGI(TAG, "Transaction complete (# %d)", index); | |||||
| } else { | |||||
| RET_ON_ERR(spi_device_queue_trans, ret); | |||||
| } | |||||
| } | |||||
| */ | |||||
| } | } | ||||
| @@ -8,7 +8,7 @@ | |||||
| class SPI_LEDs : public LEDStrip { | class SPI_LEDs : public LEDStrip { | ||||
| public: | public: | ||||
| SPI_LEDs(int gpio, int length = 0); | SPI_LEDs(int gpio, int length = 0); | ||||
| void show() const; | |||||
| void show(); | |||||
| void length_changing(int len) { configure(len); } | void length_changing(int len) { configure(len); } | ||||
| private: | private: | ||||
| @@ -16,8 +16,10 @@ private: | |||||
| int gpio; | int gpio; | ||||
| spi_device_handle_t device; | spi_device_handle_t device; | ||||
| uint8_t *out_buf; | uint8_t *out_buf; | ||||
| spi_transaction_t transaction; | |||||
| static size_t tx_len(int len) { return (len * 3) * 4; } | |||||
| static constexpr int reset_bytes = 30; | |||||
| static size_t tx_len(int len) { return (len * 3) * 4 + reset_bytes; } | |||||
| void configure(int length); | void configure(int length); | ||||
| }; | }; | ||||
| @@ -4,7 +4,10 @@ | |||||
| int64_t time_us() { | int64_t time_us() { | ||||
| struct timeval tv_now; | |||||
| /*struct timeval tv_now; | |||||
| gettimeofday(&tv_now, NULL); | gettimeofday(&tv_now, NULL); | ||||
| return (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec; | |||||
| return (int64_t)tv_now.tv_sec * 1000000L + (int64_t)tv_now.tv_usec;*/ | |||||
| struct timespec ts; | |||||
| clock_gettime(CLOCK_MONOTONIC, &ts); | |||||
| return (int64_t)ts.tv_sec * 1000000L + (int64_t)ts.tv_nsec / 1000L; | |||||
| } | } | ||||