main: commit first working firmware

This commit is contained in:
King Kévin 2021-07-21 23:51:15 +02:00
parent 729e73e705
commit a710db6915
2 changed files with 275 additions and 3 deletions

243
main.c
View File

@ -1,5 +1,5 @@
/* firmware template for STM8S microcontroller
* Copyright (C) 2019-2020 King Kévin <kingkevin@cuvoodoo.info>
/* firmware for STM8S-microcontroller-based HDMI firewall programmer
* Copyright (C) 2019-2021 King Kévin <kingkevin@cuvoodoo.info>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdint.h>
@ -8,8 +8,47 @@
#include "stm8s.h"
#include "main.h"
#include "i2c_master.h"
#include "eeprom_blockprog.h"
// blink RUN LED to show error
static bool led_error = false;
// set when the button is pressed
static volatile bool rw_button_pressed = false;
// AWU tick count
static volatile uint8_t awu_tick = 0;
// to store the current E-EDID (EDID + extension)
static uint8_t edid[256];
// function RAM (code in RAM)
uint8_t f_ram[112 + 5]; // use RAM_SEG size in main.map (plus some margin)
// function for saving EDID + extension to EEPROM, to put in RAM
bool (*ram_eeprom_blockprog)(const uint8_t* data, uint16_t length);
// size of RAM segment
volatile uint8_t RAM_SEG_LEN;
// get the size of the RAM segment
inline void get_ram_section_length() {
__asm__("mov _RAM_SEG_LEN, #l_RAM_SEG");
}
// copy functions to RAM
bool ram_cpy() {
get_ram_section_length();
if (RAM_SEG_LEN > ARRAY_LENGTH(f_ram)) {
return false;
}
for (uint8_t i = 0; i < RAM_SEG_LEN; i++) {
f_ram[i] = ((uint8_t *)eeprom_blockprog)[i];
}
ram_eeprom_blockprog = (bool (*)(const uint8_t* data, uint16_t length)) &f_ram;
return true;
}
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
static void wait_10us(uint32_t us10)
@ -18,6 +57,110 @@ static void wait_10us(uint32_t us10)
for (volatile uint32_t t = 0; t < us10; t++); // burn energy
}
// verify (E-)EDID checksums
// the sum of the bytes (including checksum at the end) must be 0
static bool checksum_ok(const uint8_t* data, uint16_t length)
{
uint8_t checksum = 0;
for (uint16_t i = 0; i < length; i++) {
checksum += data[i];
}
return (0 == checksum);
}
// return if the current EDID is valid
/* from HDMI 1.3a specification
* All Sinks shall contain an CEA-861-D compliant E-EDID data structure.
* A Source shall read the EDID 1.3 and first CEA Extension to determine the capabilities supported
by the Sink.
* The first 128 bytes of the E-EDID shall contain an EDID 1.3 structure. The contents of this
structure shall also meet the requirements of CEA-861-D.
*
* HDMI 2.1 uses CTA-861-G
* this uses EDID 1.4 structure
* EDID 1.3/1.4 is 128 bytes long, and can point to 128 bytes extension
* EDID 2.0 with its 256 bytes does not seem to be used in HDMI at all
* DisplayID with its variable-length structure meant to replace E-EDID only seems to be used in DisplayPort
* I have no idea how more than 1 extension is supported since technically the ROM is limited to 256 bytes
*/
static uint16_t edid_length(void)
{
// check EDID 1.3/1.4 fixed pattern header
if (0x00 != edid[0] || 0xff != edid[1] || 0xff != edid[2] || 0xff != edid[3] || 0xff != edid[4] || 0xff != edid[5] || 0xff != edid[6] || 0x00 != edid[7]) {
return 0;
}
if (1 == edid[18]) { // EDID 1.3/1.4 128-byte structure
if (checksum_ok(&edid[0], 128)) {
if (0 == edid[126]) { // no extension
return 128;
} else { // extension available
// the usual extension is CEA EDID Timing Extension (with extension tag 02), but we allow others
// no idea how more than 1 extension is supported
if (checksum_ok(&edid[128], 128)) {
return 256;
} else {
return 0; // EDID is broken
}
}
} else {
return 0;
}
} else if (2 == edid[18]) { // EDID 2.0 256-byte structure
if (checksum_ok(&edid[0], 256)) {
return 256;
} else {
return 0;
}
}
return 0;
}
// load EDID + extension from EEPROM
static void load_edid(void)
{
for (uint16_t i = 0; i < ARRAY_LENGTH(edid); i++) {
edid[i] = *(uint8_t*)(EEPROM_ADDR + i);
}
}
// save EDID + extension in EEPROM
static bool save_edid(void)
{
return ram_eeprom_blockprog(edid, edid_length());
}
// read EDID from I²C memory
// return if succeeded
static bool read_edid(void)
{
const uint8_t address[] = {0};
if (!i2c_master_setup(false)) {
return false;
}
if (I2C_MASTER_RC_NONE != i2c_master_address_read(I2C_SLAVE, false, address, ARRAY_LENGTH(address), edid, ARRAY_LENGTH(edid))) {
return false;
}
i2c_master_release(); // release I²C again
return true;
}
// write EDID to I²C memory
// return if succeeded
static bool write_edid(void)
{
const uint8_t address[] = {0};
if (!i2c_master_setup(false)) {
return false;
}
if (I2C_MASTER_RC_NONE != i2c_master_address_write(I2C_SLAVE, false, address, ARRAY_LENGTH(address), edid, edid_length())) {
return false;
}
i2c_master_release(); // release I²C again
return true;
}
void main(void)
{
sim(); // disable interrupts (while we reconfigure them)
@ -26,6 +169,24 @@ void main(void)
CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency to 16 MHz
while (!CLK->ICKR.fields.HSIRDY); // wait for internal oscillator to be ready
// save power by disabling unused peripheral
CLK_PCKENR1 = CLK_PCKENR1_I2C; // keep I²C
CLK_PCKENR2 = CLK_PCKENR2_AWU; // keep AWU
// configure LEDs
EDID_LED_PORT->DDR.reg |= EDID_LED_PIN; // switch pin to output
EDID_LED_PORT->CR1.reg &= ~EDID_LED_PIN; // use in open-drain mode
edid_led_off(); // start with LED off
RUN_LED_PORT->DDR.reg |= RUN_LED_PIN; // switch pin to output
RUN_LED_PORT->CR1.reg &= ~RUN_LED_PIN; // use in open-drain mode
run_led_off(); // start with LED off
// configure read/write button
RW_BUTTON_PORT->DDR.reg &= ~RW_BUTTON_PIN; // switch pin to input
RW_BUTTON_PORT->CR1.reg |= RW_BUTTON_PIN; // pull up
RW_BUTTON_PORT->CR2.reg |= RW_BUTTON_PIN; // enable external interrupt
EXTI->CR1.fields.PDIS = EXTI_FALLING_EDGE; // interrupt only on falling edges (WARNING hard coded port)
// configure auto-wakeup (AWU) to be able to refresh the watchdog
// 128 kHz LSI used by default in option bytes CKAWUSEL
// we skip measuring the LS clock frequency since there is no need to be precise
@ -33,6 +194,9 @@ void main(void)
AWU->APR.fields.APR = 0x3e; // set time to 256 ms
AWU_CSR |= AWU_CSR_AWUEN; // enable AWU (start only when entering wait or active halt mode)
// load function in RAM
ram_cpy();
// configure independent watchdog (very loose, just it case the firmware hangs)
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
IWDG->KR.fields.KEY = IWDG_KR_KEY_ENABLE; // start watchdog
@ -40,14 +204,81 @@ void main(void)
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
// erase saved EDID when button is pressed on boot
if (0 == (RW_BUTTON_PORT->IDR.reg & RW_BUTTON_PIN)) { // button is pressed while booting
for (uint16_t i = 0; i < ARRAY_LENGTH(edid); i++) {
edid[i] = 0; // create empty EDID
}
ram_eeprom_blockprog(edid, ARRAY_LENGTH(edid)); // erase EDID
}
load_edid(); // load EDID from EEPROM
bool edid_valid = (0 != edid_length()); // verify if EDID is valid
rim(); // re-enable interrupts
bool action = false; // if an action has been performed
while (true) {
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
// handle button press
if (rw_button_pressed) {
wait_10us(100); // wait 1 ms for the noise to be gone
if (0 == (RW_BUTTON_PORT->IDR.reg & RW_BUTTON_PIN)) { // ensure the button is pressed (the pull-up is really weak)
wait_10us(10000); // debounce for 100 ms
uint8_t press_duration = 1; // start counting how long the button is pressed
while (0 == (RW_BUTTON_PORT->IDR.reg & RW_BUTTON_PIN)) { // wait until button is depressed
wait_10us(10000); // wait for 100 ms
press_duration++;
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
}
led_error = false; // reset error state
if (press_duration < 30) { // less than 3 sec
run_led_on(); // indicate we started
if (read_edid()) { // read EDID from I²C
edid_valid = (0 != edid_length()); // verify if EDID is valid
if (edid_valid) { // read EDID is valid
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
led_error = !save_edid(); // save to EEPROM
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
} else { // read EDID is not valid
led_error = true; // indicate read error
load_edid(); // re-load EDID from EEPROM
edid_valid = (0 != edid_length()); // verify if EDID is valid
}
} else { // read error
led_error = true; // indicate read error
load_edid(); // re-load EDID from EEPROM
edid_valid = (0 != edid_length()); // verify if EDID is valid
}
i2c_master_release(); // release I²C again
} else { // button pressed > 3s
run_led_on(); // indicate we started
if (edid_valid) {
led_error = !write_edid(); // write EDID to I²C EEPROM
} else {
led_error = true; // we can't program an invalid EDID
}
}
}
action = true; // remember we performed an action
rw_button_pressed = false; // clear flag
}
// update LED
if ((awu_tick & 0x7) < 4) { // on period of blink (every second)
edid_led_on(); // on period
if (led_error) {
run_led_on(); // start blinking
}
} else {
if (!edid_valid) { // EDID not valid
edid_led_off(); // blink to let user know
}
if (led_error) {
run_led_off(); // blink to indicate error
}
}
if (action) { // something has been performed, check if other flags have been set meanwhile
action = false; // clear flag
} else { // nothing down
wfi(); // go to sleep (wait for any interrupt, including periodic AWU)
wfi(); // go to sleep (wait for any interrupt, also starting AWU)
}
}
}
@ -55,5 +286,11 @@ void main(void)
void awu(void) __interrupt(IRQ_AWU) // auto wakeup
{
volatile uint8_t awuf = AWU_CSR; // clear interrupt flag by reading it (reading is required, and volatile prevents compiler optimization)
awu_tick++; // increment tick count
// let the main loop kick the dog
}
void rw_button_isr(void) __interrupt(IRQ_EXTI3) // button pressed
{
rw_button_pressed = true; // notify main loop
}

35
main.h
View File

@ -1,2 +1,37 @@
// get length of array
#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
// LED to indicate valid EDID
// sink to witch on
// off = firmware not running
// on = valid EDID
// blink = no valid EDID
#define EDID_LED_PORT GPIO_PD
#define EDID_LED_PIN PD4
#define edid_led_on() {EDID_LED_PORT->ODR.reg &= ~EDID_LED_PIN;}
#define edid_led_off() {EDID_LED_PORT->ODR.reg |= EDID_LED_PIN;}
#define edid_led_toggle() {EDID_LED_PORT->ODR.reg ^= EDID_LED_PIN;}
// LED to indicate operation read/write result
// sink to switch on
// off = no operation started
// on = operation succeeded
// blink = operation failed
#define RUN_LED_PORT GPIO_PD
#define RUN_LED_PIN PD6
#define run_led_on() {RUN_LED_PORT->ODR.reg &= ~RUN_LED_PIN;}
#define run_led_off() {RUN_LED_PORT->ODR.reg |= RUN_LED_PIN;}
#define run_led_toggle() {RUN_LED_PORT->ODR.reg ^= RUN_LED_PIN;
// button to start read/write operation
// press shorts it to ground
// short press = read EDID
// long press (> 3s) = write EDID
#define RW_BUTTON_PORT GPIO_PD
#define RW_BUTTON_PIN PD3
// start address of EEPROM
#define EEPROM_ADDR 0x4000
// address of I²C EEPROM slave device containing EDID information
#define I2C_SLAVE 0x50