Add KRONE 8200 PST driver

This commit is contained in:
Julian Metzler 2024-02-04 14:49:01 +01:00
parent 72d349401b
commit 818ad88915
15 changed files with 1764 additions and 3 deletions

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS sel_k8200_pst.c
INCLUDE_DIRS include
REQUIRES util)

View File

@ -0,0 +1,53 @@
menu "KRONE 8200 PST Split-Flap Configuration (Selection Mode)"
config K8200_PST_SEL_TX_IO
int "Tx GPIO"
default 0
help
GPIO pin for data transmitted to split-flap modules
config K8200_PST_SEL_RX_IO
int "Rx GPIO"
default 0
help
GPIO pin for data received from split-flap modules
config K8200_PST_SEL_NMI_IO
int "NMI GPIO"
default 0
help
GPIO pin for interrupt signal to split-flap modules
config K8200_PST_SEL_NMI_ACT_HIGH
bool "NMI active-high"
default false
help
NMI GPIO active-low or active-high
choice K8200_PST_SEL_UART
bool "UART to use"
default K8200_PST_SEL_UART_1
config K8200_PST_SEL_UART_0
bool "UART0"
config K8200_PST_SEL_UART_1
bool "UART1"
config K8200_PST_SEL_UART_2
bool "UART2"
endchoice
config K8200_PST_SEL_RX_BUF_SIZE
int "Rx buffer size in bytes"
default 64
help
UART receive buffer size. Must be greater that UART_FIFO_LEN.
config K8200_PST_SEL_TX_BUF_SIZE
int "Tx buffer size in bytes"
default 256
help
UART transmit buffer size. Set to 0 to disable buffer and block while sending data. Otherwise, must be greater that UART_FIFO_LEN.
endmenu

View File

@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
#include "nvs.h"
esp_err_t display_init(nvs_handle_t* nvsHandle, uint8_t* display_framebuf_mask, uint16_t* display_num_units);
void display_render_frame(uint8_t* frame, uint8_t* prevFrame, uint16_t frameBufSize, uint8_t* display_framebuf_mask, uint16_t display_num_units);

View File

@ -0,0 +1,121 @@
/*
* Functions for KRONE 8200 PST split-flap displays
*/
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "driver/uart.h"
#include "macros.h"
#include "util_generic.h"
#include "util_gpio.h"
#include "util_disp_selection.h"
#include "sel_k8200_pst.h"
#if defined(CONFIG_DISPLAY_DRIVER_SEL_KRONE_8200_PST)
#define LOG_TAG "SEL-K8200-PST"
// TODO: Do something with Rx pin?
esp_err_t display_init(nvs_handle_t* nvsHandle, uint8_t* display_framebuf_mask, uint16_t* display_num_units) {
/*
* Set up all needed peripherals
*/
esp_err_t ret;
ret = display_selection_loadAndParseConfiguration(nvsHandle, display_framebuf_mask, display_num_units, LOG_TAG);
if (ret != ESP_OK) return ret;
uart_config_t uart_config = {
.baud_rate = 2400,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_2,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_REF_TICK,
};
if (CONFIG_K8200_PST_SEL_TX_IO >= 0) gpio_reset_pin(CONFIG_K8200_PST_SEL_TX_IO);
if (CONFIG_K8200_PST_SEL_RX_IO >= 0) gpio_reset_pin(CONFIG_K8200_PST_SEL_RX_IO);
if (CONFIG_K8200_PST_SEL_NMI_IO >= 0) gpio_reset_pin(CONFIG_K8200_PST_SEL_NMI_IO);
if (CONFIG_K8200_PST_SEL_TX_IO >= 0) gpio_set_direction(CONFIG_K8200_PST_SEL_TX_IO, GPIO_MODE_OUTPUT);
if (CONFIG_K8200_PST_SEL_RX_IO >= 0) gpio_set_direction(CONFIG_K8200_PST_SEL_RX_IO, GPIO_MODE_INPUT);
if (CONFIG_K8200_PST_SEL_NMI_IO >= 0) gpio_set_direction(CONFIG_K8200_PST_SEL_NMI_IO, GPIO_MODE_OUTPUT);
ret = uart_driver_install(K8200_PST_SEL_UART, CONFIG_K8200_PST_SEL_RX_BUF_SIZE, CONFIG_K8200_PST_SEL_TX_BUF_SIZE, 0, NULL, 0);
if (ret != ESP_OK) return ret;
ret = uart_param_config(K8200_PST_SEL_UART, &uart_config);
if (ret != ESP_OK) return ret;
ret = uart_set_pin(K8200_PST_SEL_UART, CONFIG_K8200_PST_SEL_TX_IO, CONFIG_K8200_PST_SEL_RX_IO, -1, -1);
if (ret != ESP_OK) return ret;
ret = uart_set_line_inverse(K8200_PST_SEL_UART, UART_SIGNAL_TXD_INV);
if (ret != ESP_OK) return ret;
k8200_pst_set_nmi(0);
vTaskDelay(100 / portTICK_PERIOD_MS);
k8200_pst_set_nmi(1);
vTaskDelay(100 / portTICK_PERIOD_MS);
k8200_pst_set_nmi(0);
vTaskDelay(100 / portTICK_PERIOD_MS);
k8200_pst_reset();
return ESP_OK;
}
void k8200_pst_set_nmi(uint8_t state) {
// state: 1 to assert NMI (stop units), 0 to deassert
if (CONFIG_K8200_PST_SEL_NMI_IO >= 0) gpio_set_level(CONFIG_K8200_PST_SEL_NMI_IO, CONFIG_K8200_PST_SEL_NMI_ACT_HIGH ? !!state : !state);
}
void k8200_pst_reset(void) {
char command = 0x1A;
uart_write_bytes(K8200_PST_SEL_UART, &command, 1);
}
void k8200_pst_home(void) {
char command = 0x1B;
uart_write_bytes(K8200_PST_SEL_UART, &command, 1);
}
void getCommandBytes_SetCode(uint8_t address, uint8_t code, uint8_t* outBuf) {
outBuf[0] = 0x3A;
outBuf[1] = address;
outBuf[2] = uint8_to_bcd(code);
}
void display_render_frame(uint8_t* frame, uint8_t* prevFrame, uint16_t frameBufSize, uint8_t* display_framebuf_mask, uint16_t display_num_units) {
// Nothing to do if frame hasn't changed
if (prevFrame != NULL && memcmp(frame, prevFrame, frameBufSize) == 0) return;
esp_err_t ret = uart_wait_tx_done(K8200_PST_SEL_UART, 10 / portTICK_PERIOD_MS);
if (ret != ESP_OK) return; // If this is ESP_ERR_TIMEOUT, Tx is still ongoing
size_t bufSize = display_num_units * 3 + 1; // + 1 for CMD_SET_ALL at the end
uint8_t* buf = malloc(bufSize);
uint16_t bufIdx = 0;
for (uint16_t addr = 0; addr < frameBufSize; addr++) {
// Skip addresses that aren't present
if (!GET_MASK(display_framebuf_mask, addr)) continue;
getCommandBytes_SetCode(addr, frame[addr], &buf[bufIdx*3]);
bufIdx++;
}
buf[bufSize-1] = 0x1C; // Start all units
ESP_LOG_BUFFER_HEX(LOG_TAG, buf, bufSize);
for (uint16_t i = 0; i < bufSize; i++) {
uart_write_bytes(K8200_PST_SEL_UART, &buf[i], 1);
ets_delay_us(7500);
}
free(buf);
if (prevFrame != NULL) memcpy(prevFrame, frame, frameBufSize);
}
#endif

View File

@ -0,0 +1,24 @@
#pragma once
#include "esp_system.h"
#include "nvs.h"
#if defined(CONFIG_K8200_PST_SEL_UART_0)
#define K8200_PST_SEL_UART 0
#elif defined(CONFIG_K8200_PST_SEL_UART_1)
#define K8200_PST_SEL_UART 1
#elif defined(CONFIG_K8200_PST_SEL_UART_2)
#define K8200_PST_SEL_UART 2
#endif
#ifndef CONFIG_K8200_PST_SEL_NMI_ACT_HIGH
#define CONFIG_K8200_PST_SEL_NMI_ACT_HIGH 0
#endif
esp_err_t display_init(nvs_handle_t* nvsHandle, uint8_t* display_framebuf_mask, uint16_t* display_num_units);
void k8200_pst_set_nmi(uint8_t state);
void k8200_pst_reset(void);
void k8200_pst_home(void);
void getCommandBytes_SetCode(uint8_t address, uint8_t code, uint8_t* outBuf);
void display_render_frame(uint8_t* frame, uint8_t* prevFrame, uint16_t frameBufSize, uint8_t* display_framebuf_mask, uint16_t display_num_units);

View File

@ -309,6 +309,13 @@
input = $("<select>");
input.change(updateDisplay);
let map = config["maps"][unit["map"]];
// Add home position
let option = $("<option>");
option.attr("value", unit["home_pos"]);
option.html("&lt;Home&gt;");
input.append(option);
for (const [key, value] of Object.entries(map)) {
let option = $("<option>");
option.attr("value", key);

View File

@ -16,4 +16,6 @@ typedef enum {
void buffer_8to1(uint8_t* buf8, uint8_t* buf1, uint16_t width, uint16_t height, buf_merge_t mergeType);
void buffer_utf8_to_iso88591(char* dst, char* src);
void buffer_iso88591_to_utf8(char* dst, char* src);
void buffer_textbuf_to_charbuf(uint8_t* display_text_buffer, uint8_t* display_char_buffer, uint16_t* display_quirk_flags_buffer, uint16_t textBufSize, uint16_t charBufSize);
#if defined(CONFIG_DISPLAY_TYPE_CHARACTER)
void buffer_textbuf_to_charbuf(uint8_t* display_text_buffer, uint8_t* display_char_buffer, uint16_t* display_quirk_flags_buffer, uint16_t textBufSize, uint16_t charBufSize);
#endif

View File

@ -39,6 +39,7 @@ typedef struct {
uint8_t count_set_bits(uint8_t byte);
uint8_t int_num_digits(int64_t n, uint8_t includeNegSign);
uint8_t uint_num_digits(uint64_t n);
uint8_t uint8_to_bcd(uint8_t n);
void str_toUpper(char* str);
void str_filterAllowed(char* out, char* in, char* allowedChars, bool allowLineBreaks);
void str_filterDisallowed(char* out, char* in, char* disallowedChars, bool allowLineBreaks);

View File

@ -60,6 +60,7 @@ void buffer_iso88591_to_utf8(char* dst, char* src) {
}
}
#if defined(CONFIG_DISPLAY_TYPE_CHARACTER)
void buffer_textbuf_to_charbuf(uint8_t* display_text_buffer, uint8_t* display_char_buffer, uint16_t* display_quirk_flags_buffer, uint16_t textBufSize, uint16_t charBufSize) {
/*
Convert a text buffer to a character buffer and a quirk flag buffer.
@ -135,4 +136,5 @@ void buffer_textbuf_to_charbuf(uint8_t* display_text_buffer, uint8_t* display_ch
}
}
}
}
}
#endif

View File

@ -35,6 +35,16 @@ uint8_t uint_num_digits(uint64_t n) {
return result;
}
uint8_t uint8_to_bcd(uint8_t n) {
// Turn a positive integer between in the range [0, 99]
// into its hexadecimal BCD representation.
// e.g. 37 => 0x37
uint8_t tens = n / 10;
if (tens > 9) return 0;
uint8_t ones = n % 10;
return tens * 16 + ones;
}
void str_toUpper(char* str) {
while (*str) {
*str = toupper((unsigned char) *str);

View File

@ -122,6 +122,8 @@ DISPLAY_TEXTBUF_SIZE: Number of characters in the user-facing text buffe
#define DISPLAY_DRIVER "char_16seg_led_ws281x"
#elif defined(CONFIG_DISPLAY_DRIVER_SEL_KRONE_9000)
#define DISPLAY_DRIVER "sel_krone_9000"
#elif defined(CONFIG_DISPLAY_DRIVER_SEL_KRONE_8200_PST)
#define DISPLAY_DRIVER "sel_krone_8200_pst"
#endif
#if defined(CONFIG_DISPLAY_HAS_SHADERS)

View File

@ -38,4 +38,6 @@ build_flags =
[env:16segrgb]
[env:splitflap_k9000]
[env:splitflap_k9000]
[env:splitflap_k8200_pst]

File diff suppressed because it is too large Load Diff

View File

@ -167,6 +167,10 @@ choice DISPLAY_DRIVER
config DISPLAY_DRIVER_SEL_KRONE_9000
depends on DISPLAY_TYPE_SELECTION
bool "KRONE 9000 Split-Flap (using UART peripheral)"
config DISPLAY_DRIVER_SEL_KRONE_8200_PST
depends on DISPLAY_TYPE_SELECTION
bool "KRONE 8200 Split-Flap, PST (using UART peripheral)"
endchoice
config DISPLAY_UNIT_BUF_SIZE

View File

@ -54,6 +54,8 @@
#include "driver_display_char_16seg_led_ws281x.h"
#elif defined(CONFIG_DISPLAY_DRIVER_SEL_KRONE_9000)
#include "driver_display_sel_krone_9000.h"
#elif defined(CONFIG_DISPLAY_DRIVER_SEL_KRONE_8200_PST)
#include "driver_display_sel_krone_8200_pst.h"
#endif
#if defined(CONFIG_DISPLAY_TYPE_PIXEL)