Tasmota/lib/libesp32_epdiy/src/epd_driver/render.c
2021-04-03 17:41:35 +02:00

383 lines
12 KiB
C
Executable File

#include "epd_temperature.h"
#include "display_ops.h"
#include "epd_driver.h"
#include "include/epd_driver.h"
#include "include/epd_internals.h"
#include "lut.h"
#include "driver/rtc_io.h"
#include "esp_types.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "xtensa/core-macros.h"
#include <string.h>
inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }
const int clear_cycle_time = 12;
const int DEFAULT_FRAME_TIME = 120;
#define RTOS_ERROR_CHECK(x) \
do { \
esp_err_t __err_rc = (x); \
if (__err_rc != pdPASS) { \
abort(); \
} \
} while (0)
#define CLEAR_BYTE 0B10101010
#define DARK_BYTE 0B01010101
// Queue of input data lines
static QueueHandle_t output_queue;
static OutputParams fetch_params;
static OutputParams feed_params;
// Space to use for the EPD output lookup table, which
// is calculated for each cycle.
static uint8_t* conversion_lut;
void epd_push_pixels(EpdRect area, short time, int color) {
uint8_t row[EPD_LINE_BYTES] = {0};
const uint8_t color_choice[4] = {DARK_BYTE, CLEAR_BYTE, 0x00, 0xFF};
for (uint32_t i = 0; i < area.width; i++) {
uint32_t position = i + area.x % 4;
uint8_t mask = color_choice[color] & (0b00000011 << (2 * (position % 4)));
row[area.x / 4 + position / 4] |= mask;
}
reorder_line_buffer((uint32_t *)row);
epd_start_frame();
for (int i = 0; i < EPD_HEIGHT; i++) {
// before are of interest: skip
if (i < area.y) {
skip_row(time);
// start area of interest: set row data
} else if (i == area.y) {
epd_switch_buffer();
memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES);
epd_switch_buffer();
memcpy(epd_get_current_buffer(), row, EPD_LINE_BYTES);
write_row(time * 10);
// load nop row if done with area
} else if (i >= area.y + area.height) {
skip_row(time);
// output the same as before
} else {
write_row(time * 10);
}
}
// Since we "pipeline" row output, we still have to latch out the last row.
write_row(time * 10);
epd_end_frame();
}
///////////////////////////// Coordination ///////////////////////////////
/**
* Find the waveform temperature range index for a given temperature in °C.
* If no range in the waveform data fits the given temperature, return the
* closest one.
* Returns -1 if the waveform does not contain any temperature range.
*/
int waveform_temp_range_index(const EpdWaveform* waveform, int temperature) {
int idx = 0;
if (waveform->num_temp_ranges == 0) {
return -1;
}
while (idx < waveform->num_temp_ranges - 1
&& waveform->temp_intervals[idx].min < temperature) {
idx++;
}
return idx;
}
//////////////////////////////// API Procedures //////////////////////////////////
static int get_waveform_index(const EpdWaveform* waveform, enum EpdDrawMode mode) {
for (int i=0; i < waveform->num_modes; i++) {
if (waveform->mode_data[i]->type == (mode & 0x3F)) {
return i;
}
}
return -1;
}
enum EpdDrawError IRAM_ATTR epd_draw_base(EpdRect area,
const uint8_t *data,
EpdRect crop_to,
enum EpdDrawMode mode,
int temperature,
const bool *drawn_lines,
const EpdWaveform *waveform) {
uint8_t line[EPD_WIDTH / 2];
memset(line, 255, EPD_WIDTH / 2);
int waveform_range = waveform_temp_range_index(waveform, temperature);
if (waveform_range < 0) {
return EPD_DRAW_NO_PHASES_AVAILABLE;
}
int waveform_index = 0;
uint8_t frame_count = 0;
const EpdWaveformPhases* waveform_phases = NULL;
// no waveform required for monochrome mode
if (!(mode & MODE_EPDIY_MONOCHROME)) {
waveform_index = get_waveform_index(waveform, mode);
if (waveform_index < 0) {
return EPD_DRAW_MODE_NOT_FOUND;
}
waveform_phases = waveform->mode_data[waveform_index]
->range_data[waveform_range];
// FIXME: error if not present
frame_count = waveform_phases->phases;
} else {
frame_count = 1;
}
if (crop_to.width < 0 || crop_to.height < 0) {
return EPD_DRAW_INVALID_CROP;
}
const bool crop = (crop_to.width > 0 && crop_to.height > 0);
if (crop && (crop_to.width > area.width
|| crop_to.height > area.height
|| crop_to.x > area.width
|| crop_to.y > area.height)) {
return EPD_DRAW_INVALID_CROP;
}
for (uint8_t k = 0; k < frame_count; k++) {
int frame_time = DEFAULT_FRAME_TIME;
if (waveform_phases != NULL && waveform_phases->phase_times != NULL) {
frame_time = waveform_phases->phase_times[k];
}
if (mode & MODE_EPDIY_MONOCHROME) {
frame_time = MONOCHROME_FRAME_TIME;
}
fetch_params.area = area;
// IMPORTANT: This must only ever read from PSRAM,
// Since the PSRAM workaround is disabled for lut.c
fetch_params.data_ptr = data;
fetch_params.crop_to = crop_to;
fetch_params.frame = k;
fetch_params.waveform_range = waveform_range;
fetch_params.waveform_index = waveform_index;
fetch_params.frame_time = frame_time;
fetch_params.mode = mode;
fetch_params.waveform = waveform;
fetch_params.error = EPD_DRAW_SUCCESS;
fetch_params.drawn_lines = drawn_lines;
fetch_params.output_queue = &output_queue;
feed_params.area = area;
feed_params.data_ptr = data;
feed_params.crop_to = crop_to;
feed_params.frame = k;
feed_params.frame_time = frame_time;
feed_params.waveform_range = waveform_range;
feed_params.waveform_index = waveform_index;
feed_params.mode = mode;
feed_params.waveform = waveform;
feed_params.error = EPD_DRAW_SUCCESS;
feed_params.drawn_lines = drawn_lines;
feed_params.output_queue = &output_queue;
xSemaphoreGive(fetch_params.start_smphr);
xSemaphoreGive(feed_params.start_smphr);
xSemaphoreTake(fetch_params.done_smphr, portMAX_DELAY);
xSemaphoreTake(feed_params.done_smphr, portMAX_DELAY);
enum EpdDrawError all_errors = fetch_params.error | feed_params.error;
if (all_errors != EPD_DRAW_SUCCESS) {
return all_errors;
}
}
return EPD_DRAW_SUCCESS;
}
void epd_clear_area(EpdRect area) {
epd_clear_area_cycles(area, 3, clear_cycle_time);
}
void epd_clear_area_cycles(EpdRect area, int cycles, int cycle_time) {
const short white_time = cycle_time;
const short dark_time = cycle_time;
for (int c = 0; c < cycles; c++) {
for (int i = 0; i < 10; i++) {
epd_push_pixels(area, dark_time, 0);
}
for (int i = 0; i < 10; i++) {
epd_push_pixels(area, white_time, 1);
}
}
}
void epd_init(enum EpdInitOptions options) {
epd_base_init(EPD_WIDTH);
epd_temperature_init();
size_t lut_size = 0;
if (options & EPD_LUT_1K) {
lut_size = 1 << 10;
} else if ((options & EPD_LUT_64K) || (options == EPD_OPTIONS_DEFAULT)) {
lut_size = 1 << 16;
} else {
ESP_LOGE("epd", "invalid init options: %d", options);
return;
}
conversion_lut = (uint8_t *)heap_caps_malloc(lut_size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
if (conversion_lut == NULL) {
ESP_LOGE("epd", "could not allocate LUT!");
}
assert(conversion_lut != NULL);
fetch_params.conversion_lut = conversion_lut;
fetch_params.conversion_lut_size = lut_size;
feed_params.conversion_lut = conversion_lut;
feed_params.conversion_lut_size = lut_size;
fetch_params.done_smphr = xSemaphoreCreateBinary();
fetch_params.start_smphr = xSemaphoreCreateBinary();
feed_params.done_smphr = xSemaphoreCreateBinary();
feed_params.start_smphr = xSemaphoreCreateBinary();
RTOS_ERROR_CHECK(xTaskCreatePinnedToCore((void (*)(void *))provide_out,
"epd_fetch", (1 << 12), &fetch_params, 5,
NULL, 0));
RTOS_ERROR_CHECK(xTaskCreatePinnedToCore((void (*)(void *))feed_display,
"epd_feed", 1 << 12, &feed_params,
5, NULL, 1));
//conversion_lut = (uint8_t *)heap_caps_malloc(1 << 16, MALLOC_CAP_8BIT);
//assert(conversion_lut != NULL);
int queue_len = 32;
if (options & EPD_FEED_QUEUE_32) {
queue_len = 32;
} else if (options & EPD_FEED_QUEUE_8) {
queue_len = 8;
}
output_queue = xQueueCreate(queue_len, EPD_WIDTH);
}
void epd_deinit() {
// FIXME: deinit processes
#if defined(CONFIG_EPD_BOARD_REVISION_V5)
gpio_reset_pin(CKH);
rtc_gpio_isolate(CKH);
#endif
epd_base_deinit();
}
EpdRect epd_difference_image_base(
const uint8_t* to,
const uint8_t* from,
EpdRect crop_to,
int fb_width,
int fb_height,
uint8_t* interlaced,
bool* dirty_lines,
uint8_t* from_or,
uint8_t* from_and
) {
assert(from_or != NULL);
assert(from_and != NULL);
// OR over all pixels of the "from"-image
*from_or = 0x00;
// AND over all pixels of the "from"-image
*from_and = 0xFF;
uint8_t dirty_cols[EPD_WIDTH] = {0};
int x_end = min(fb_width, crop_to.x + crop_to.width);
int y_end = min(fb_height, crop_to.y + crop_to.height);
for (int y=crop_to.y; y < y_end; y++) {
uint8_t dirty = 0;
for (int x = crop_to.x; x < x_end; x++) {
uint8_t t = *(to + y*fb_width / 2 + x / 2);
t = (x % 2) ? (t >> 4) : (t & 0x0f);
uint8_t f = *(from + y*fb_width / 2+ x / 2);
*from_or |= f;
*from_and &= f;
f = (x % 2) ? (f >> 4) : (f & 0x0f);
dirty |= (t ^ f);
dirty_cols[x] |= (t ^ f);
interlaced[y * fb_width + x] = (t << 4) | f;
}
dirty_lines[y] = dirty > 0;
}
int min_x, min_y, max_x, max_y;
for (min_x = crop_to.x; min_x < x_end; min_x++) {
if (dirty_cols[min_x] != 0) break;
}
for (max_x = x_end - 1; max_x >= crop_to.x; max_x--) {
if (dirty_cols[max_x] != 0) break;
}
for (min_y = crop_to.y; min_y < y_end; min_y++) {
if (dirty_lines[min_y] != 0) break;
}
for (max_y = y_end - 1; max_y >= crop_to.y; max_y--) {
if (dirty_lines[max_y] != 0) break;
}
EpdRect crop_rect = {
.x = min_x,
.y = min_y,
.width = max(max_x - min_x + 1, 0),
.height = max(max_y - min_y + 1, 0),
};
return crop_rect;
}
EpdRect epd_difference_image(
const uint8_t* to,
const uint8_t* from,
uint8_t* interlaced,
bool* dirty_lines
) {
uint8_t from_or = 0;
uint8_t from_and = 0;
return epd_difference_image_base(to, from, epd_full_screen(), EPD_WIDTH, EPD_HEIGHT, interlaced, dirty_lines, &from_or, &from_and);
}
EpdRect epd_difference_image_cropped(
const uint8_t* to,
const uint8_t* from,
EpdRect crop_to,
uint8_t* interlaced,
bool* dirty_lines,
bool* previously_white,
bool* previously_black
) {
uint8_t from_or, from_and;
EpdRect result = epd_difference_image_base(to, from, crop_to, EPD_WIDTH, EPD_HEIGHT, interlaced, dirty_lines, &from_or, &from_and);
if (previously_white != NULL) *previously_white = (from_and == 0xFF);
if (previously_black != NULL) *previously_black = (from_or == 0x00);
return result;
}