Compare commits
34 Commits
master
...
hdmi_firew
Author | SHA1 | Date |
---|---|---|
King Kévin | 0b87be5fd7 | |
King Kévin | 20372fb78c | |
King Kévin | f94f1bd065 | |
King Kévin | b895c75621 | |
King Kévin | eb4e30f79a | |
King Kévin | 73a55e2a3f | |
King Kévin | 3c1b0ca7ea | |
King Kévin | e988151465 | |
King Kévin | 7cc0901d33 | |
King Kévin | e34fec4221 | |
King Kévin | ae158d3098 | |
King Kévin | 523153fcf0 | |
King Kévin | 03d4874605 | |
King Kévin | c68bf6a324 | |
King Kévin | 1d78baf925 | |
King Kévin | 07e2df9fde | |
King Kévin | 3e9a10ac08 | |
King Kévin | a66a60fbad | |
King Kévin | aa5828e22b | |
King Kévin | 28cf5160d8 | |
King Kévin | a8f80dc3fd | |
King Kévin | 098d439471 | |
King Kévin | d7bc187e37 | |
King Kévin | 64c97740e1 | |
King Kévin | 2bd3c4c0b3 | |
King Kévin | 55b9e429fa | |
King Kévin | a710db6915 | |
King Kévin | 729e73e705 | |
King Kévin | 6eca312035 | |
King Kévin | 8a7b6d3cc3 | |
King Kévin | 19f3e27cd6 | |
King Kévin | 05a9cb09f4 | |
King Kévin | 45322fd017 | |
King Kévin | 257b29c908 |
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@ CC := sdcc
|
|||
CFLAGS := -mstm8 --std-c99 --opt-code-size --Werror
|
||||
LDFLAGS = -mstm8 --out-fmt-ihx -lstm8
|
||||
FIRMWARE := main
|
||||
SRC_FILES := $(wildcard *.c)
|
||||
SRC_FILES := main.c softi2c_master.c eeprom_blockprog.c
|
||||
OBJ_FILES := $(patsubst %.c,%.rel,$(SRC_FILES))
|
||||
|
||||
all: $(FIRMWARE).ihx
|
||||
|
|
44
README.md
44
README.md
|
@ -1,2 +1,42 @@
|
|||
firmware template for ST STM8S micro-controller.
|
||||
includes register definitions using macros and structures.
|
||||
This is the firmware for the [HDMI firewall programmer](https://git.cuvoodoo.info/kingkevin/board/src/branch/hdmi_firewall_programmer).
|
||||
The HDMI firewall programmer copies EDID information onto the [HDMI firewall](https://git.cuvoodoo.info/kingkevin/board/src/branch/hdmi_firewall).
|
||||
|
||||
usage
|
||||
=====
|
||||
|
||||
see [hardware description](https://git.cuvoodoo.info/kingkevin/board/src/branch/hdmi_firewall_programmer/README.md).
|
||||
|
||||
mode of operation
|
||||
=================
|
||||
|
||||
the EDID can be accessed using the I²C lines on the HDMI port.
|
||||
the firmware reads and writes the data from slave device address 0x50.
|
||||
|
||||
connections
|
||||
===========
|
||||
|
||||
the programmer can be flashed using the micro-USB port (not USB signals):
|
||||
|
||||
- the SWIM signal is on USB pin 2 D-
|
||||
- the NRST signal is on USB pin 4 ID
|
||||
|
||||
there is also debugging output using UART (115200 8N1) on STM8S pin 2 PD5/UART_TX.
|
||||
|
||||
code
|
||||
====
|
||||
|
||||
the source code is for a STM8S103F3 micro-controller.
|
||||
|
||||
compile
|
||||
=======
|
||||
|
||||
requirement: SDCC, make
|
||||
|
||||
to compile the source code into a firmware, run `make`.
|
||||
|
||||
flash
|
||||
=====
|
||||
|
||||
requirement: stm8flash, a ST-LINK/V2 programmer
|
||||
|
||||
to flash the firmware on the micro-controller, run `make flash`.
|
||||
|
|
417
main.c
417
main.c
|
@ -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 "softi2c_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
|
||||
static inline void get_ram_section_length() {
|
||||
__asm__("mov _RAM_SEG_LEN, #l_RAM_SEG");
|
||||
}
|
||||
|
||||
// copy functions to RAM
|
||||
static 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,209 @@ static void wait_10us(uint32_t us10)
|
|||
while (us10--); // burn energy
|
||||
}
|
||||
|
||||
void putc(char c)
|
||||
{
|
||||
while (!UART1->SR.fields.TXE); // wait until TX buffer is empty
|
||||
UART1->DR.reg = c; // put character in buffer to be transmitted
|
||||
// don't wait until the transmission is complete
|
||||
}
|
||||
|
||||
void puts(const char* s)
|
||||
{
|
||||
if (NULL == s) {
|
||||
return;
|
||||
}
|
||||
while (*s) {
|
||||
putc(*s++);
|
||||
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
}
|
||||
}
|
||||
|
||||
void putn(uint8_t n)
|
||||
{
|
||||
n &= 0x0f; // ensure it's a nibble
|
||||
if (n < 0xa) {
|
||||
putc('0' + n);
|
||||
} else {
|
||||
putc('a' + (n - 0x0a));
|
||||
}
|
||||
}
|
||||
|
||||
void puth(uint8_t h)
|
||||
{
|
||||
putn(h >> 4);
|
||||
putn(h & 0x0f);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
puts("EDID check: ");
|
||||
// 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]) {
|
||||
puts("invalid header\r\n");
|
||||
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
|
||||
puts("128 bytes\r\n");
|
||||
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)) {
|
||||
puts("256 bytes (with extension)\r\n");
|
||||
return 256;
|
||||
} else {
|
||||
puts("extension CRC error\r\n");
|
||||
return 0; // EDID is broken
|
||||
}
|
||||
}
|
||||
} else {
|
||||
puts("CRC error\r\n");
|
||||
return 0;
|
||||
}
|
||||
} else if (2 == edid[18]) { // EDID 2.0 256-byte structure
|
||||
if (checksum_ok(&edid[0], 256)) {
|
||||
puts("256 bytes (no extension)\r\n");
|
||||
return 256;
|
||||
} else {
|
||||
puts("CRC error\r\n");
|
||||
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)
|
||||
{
|
||||
// modify EDID to include the character indicating the firewall
|
||||
const char firewall_indicator = '|'; // sun character to indicate the firewall
|
||||
// ensure descriptor 4 is for Display name
|
||||
if ((0 == edid[108]) && (0 == edid[109]) && (0 == edid[110]) && (0xfc == edid[111]) && (0 == edid[112])) { // ensure descriptor 4 is for Display name
|
||||
uint8_t last_c; // position of last character
|
||||
for (last_c = 113; last_c < 126 && edid[last_c] != '\n'; last_c++); // find position for inserting our character
|
||||
if (firewall_indicator != edid[last_c - 1]) { // the last character is not yet the sun
|
||||
if (last_c > 125) { // ensure we insert as the last possible character
|
||||
last_c = 125;
|
||||
}
|
||||
edid[last_c++] = firewall_indicator; // insert sun
|
||||
if (last_c < 126) {
|
||||
edid[last_c++] = '\n'; // insert LF to terminate string
|
||||
}
|
||||
while (last_c < 126) {
|
||||
edid[last_c++] = ' '; // insert padding space
|
||||
}
|
||||
// calculate new checksum
|
||||
uint8_t checksum = 0;
|
||||
for (uint8_t i = 0; i < 127; i++) {
|
||||
checksum += edid[i];
|
||||
}
|
||||
edid[127] = (256 - checksum);
|
||||
}
|
||||
}
|
||||
|
||||
return ram_eeprom_blockprog(edid, edid_length());
|
||||
}
|
||||
|
||||
// size in byte of a page in the I²C EEPROM (for faster page write)
|
||||
#define I2C_EEPROM_PAGE_SIZE 16U
|
||||
|
||||
// read EDID from I²C memory
|
||||
// return if succeeded
|
||||
static bool read_edid(void)
|
||||
{
|
||||
if (!softi2c_master_setup(10)) {
|
||||
puts("I²C setup failed");
|
||||
return false;
|
||||
}
|
||||
// read all pages
|
||||
// in theory I could read the whole address space at once, but in practice short clock pulses appear after some bytes, corrupting the data flow. I have no idea what the cause of theses glitches is (I even used WFI to reduce EMI as recommended in the errata)
|
||||
for (uint16_t i = 0; i < ARRAY_LENGTH(edid); i += I2C_EEPROM_PAGE_SIZE) {
|
||||
const uint8_t address[] = {i}; // address of page to read
|
||||
uint8_t data[I2C_EEPROM_PAGE_SIZE]; // data from page
|
||||
bool same = false; // if the data read is the same
|
||||
while (!same) { // read until the data is the same
|
||||
const bool rc = softi2c_master_address_read(I2C_SLAVE, address, ARRAY_LENGTH(address), data, ARRAY_LENGTH(data)); // read I²C EEPROM data
|
||||
if (!rc) {
|
||||
puts("I²C read failed");
|
||||
return false;
|
||||
}
|
||||
// check if the data is the same and copy it to EDID
|
||||
same = true;
|
||||
for (uint16_t j = 0; j < I2C_EEPROM_PAGE_SIZE; j++) {
|
||||
if (data[j] != edid[i + j]) {
|
||||
same = false;
|
||||
}
|
||||
edid[i + j] = data[j]; // save last read data
|
||||
}
|
||||
}
|
||||
}
|
||||
softi2c_master_release(); // release I²C again
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// write EDID to I²C memory
|
||||
// return if succeeded
|
||||
static bool write_edid(void)
|
||||
{
|
||||
if (!softi2c_master_setup(10)) {
|
||||
puts("I²C setup failed");
|
||||
return false;
|
||||
}
|
||||
// write all page
|
||||
const uint16_t length = edid_length();
|
||||
for (uint16_t i = 0; i < length; i += I2C_EEPROM_PAGE_SIZE) {
|
||||
const uint8_t address[] = {i};
|
||||
const bool rc = softi2c_master_address_write(I2C_SLAVE, address, ARRAY_LENGTH(address), &edid[i], I2C_EEPROM_PAGE_SIZE); // write I²C EEPROM page
|
||||
if (!rc) {
|
||||
puts("I²C write failed");
|
||||
return false;
|
||||
}
|
||||
wait_10us((5 + 1) * 100); // wait 5 ms for the page write to complete
|
||||
}
|
||||
softi2c_master_release(); // release I²C again
|
||||
return true;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
sim(); // disable interrupts (while we reconfigure them)
|
||||
|
@ -26,6 +268,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 | CLK_PCKENR1_UART1234; // keep I²C and UART
|
||||
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 +293,16 @@ 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)
|
||||
|
||||
// configure UART for debug output
|
||||
UART1->CR1.fields.M = 0; // 8 data bits
|
||||
UART1->CR3.fields.STOP = 0; // 1 stop bit
|
||||
UART1->BRR2.reg = 0x0B; // set baud rate to 115200 (at 16 MHz)
|
||||
UART1->BRR1.reg = 0x08; // set baud rate to 115200 (at 16 MHz)
|
||||
UART1->CR2.fields.TEN = 1; // enable TX
|
||||
|
||||
// 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 +310,149 @@ 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
|
||||
|
||||
puts("\r\nCuVoodoo HDMI firewall programmer ready\r\n");
|
||||
|
||||
// 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
|
||||
puts("EEPROM EDID erased\r\n");
|
||||
}
|
||||
load_edid(); // load EDID from EEPROM
|
||||
bool edid_valid = (0 != edid_length()); // verify if EDID is valid
|
||||
if (edid_valid) {
|
||||
puts("EEPROM EDID valid\r\n");
|
||||
} else {
|
||||
puts("EEPROM EDID not valid\r\n");
|
||||
}
|
||||
|
||||
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(10000); // wait 100 ms for the noise to be gone
|
||||
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
if (0 == (RW_BUTTON_PORT->IDR.reg & RW_BUTTON_PIN)) { // ensure the button is pressed (the pull-up is really weak)
|
||||
run_led_off(); // clear RUN LED to see result at the end
|
||||
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
|
||||
if (press_duration >= 30) { // 3 seconds already passed
|
||||
break; // stop waiting for button to be released
|
||||
}
|
||||
}
|
||||
led_error = false; // reset error state
|
||||
if (press_duration < 30) { // less than 3 sec
|
||||
puts("read I²C EDID: ");
|
||||
run_led_on(); // indicate we started
|
||||
if (read_edid()) { // read EDID from I²C
|
||||
puts(" OK\r\n");
|
||||
edid_valid = (0 != edid_length()); // verify if EDID is valid
|
||||
if (edid_valid) { // read EDID is valid
|
||||
puts("I²C EDID valid\r\n");
|
||||
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
|
||||
/*
|
||||
puts("EDID data:\r\n");
|
||||
for (uint16_t i = 0; i < ARRAY_LENGTH(edid); i++) {
|
||||
puth(edid[i]);
|
||||
putc(' ');
|
||||
}
|
||||
puts("\r\n");
|
||||
*/
|
||||
|
||||
if (save_edid()) {; // save to EEPROM
|
||||
puts("I²C EDID saved to EEPROM\r\n");
|
||||
} else {
|
||||
led_error = true; // indicate write error
|
||||
puts("could not save EDID to EEPROM\r\n");
|
||||
load_edid(); // re-load EDID from EEPROM
|
||||
edid_valid = (0 != edid_length()); // verify if EDID is valid
|
||||
}
|
||||
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
} else { // read EDID is not valid
|
||||
puts("I²C EDID not valid, reloading from EEPROM\r\n");
|
||||
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
|
||||
puts("\r\n"); // error should have been printed
|
||||
led_error = true; // indicate read error
|
||||
load_edid(); // re-load EDID from EEPROM
|
||||
edid_valid = (0 != edid_length()); // verify if EDID is valid
|
||||
}
|
||||
} else { // button pressed > 3s
|
||||
run_led_on(); // indicate we started
|
||||
if (edid_valid) { // the current EDID we have is valid and is ready to be written
|
||||
led_error = true; // error indication will be cleared if everything succeeded
|
||||
puts("writing I²C EDID: ");
|
||||
if (write_edid()) { // write EDID to I²C EEPROM succeeded
|
||||
puts("OK\r\n");
|
||||
const uint16_t target_length = edid_length(); // remember the length of the EDID we programmed
|
||||
puts("reading back EDID: ");
|
||||
if (read_edid()) { // read EDID back from I²C
|
||||
puts("OK\r\n");
|
||||
if (edid_length() == target_length) { // ensure the EDID length between what we programmed and read is the same
|
||||
puts("verifying EDID: ");
|
||||
bool identical_edid = true; // to find out if EDID is identical
|
||||
for (uint16_t i = 0; i < target_length; i++) { // compare EDID
|
||||
if (edid[i] != *(uint8_t*)(EEPROM_ADDR + i)) { // ensure the data is the same
|
||||
identical_edid = false; // EDID is not identical
|
||||
break; // stop comparing
|
||||
}
|
||||
}
|
||||
if (identical_edid) { // EDID has been successfully programmed
|
||||
puts("OK\r\n");
|
||||
led_error = false; // no error happened
|
||||
} else {
|
||||
puts("failed (wrong data)\r\n");
|
||||
}
|
||||
} else {
|
||||
puts("failed (not length)\r\n");
|
||||
}
|
||||
} else {
|
||||
puts("\r\n"); // error should have been printed
|
||||
}
|
||||
puts("reloading EEPROM EDID\r\n");
|
||||
load_edid(); // re-load EDID from EEPROM after it has been overwritten from the read
|
||||
edid_valid = (0 != edid_length()); // verify if EDID is valid (should be as before
|
||||
} else {
|
||||
puts("\r\n"); // error should have been printed
|
||||
}
|
||||
} else {
|
||||
puts("\r\n"); // error should have been printed
|
||||
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 +460,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
|
||||
}
|
||||
|
|
44
main.h
44
main.h
|
@ -1,2 +1,46 @@
|
|||
// 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
|
||||
|
||||
/** print character
|
||||
* @param[in] c character to print
|
||||
*/
|
||||
void putc(char c);
|
||||
/** print string
|
||||
* @param[in] s string to print
|
||||
*/
|
||||
void puts(const char* s);
|
||||
|
|
63
stm8s.h
63
stm8s.h
|
@ -576,7 +576,7 @@ typedef struct {
|
|||
#define CLK_PCKENR1 (*(volatile uint8_t *)(CLK_BASE + 0x07))
|
||||
#define CLK_PCKENR1_I2C (1U << 0)
|
||||
#define CLK_PCKENR1_SPI (1U << 1)
|
||||
#define CLK_PCKENR1_UART1234 (3U << 2)
|
||||
#define CLK_PCKENR1_UART1234 (3 << 2)
|
||||
#define CLK_PCKENR1_TIM46 (1U << 4)
|
||||
#define CLK_PCKENR1_TIM25 (1U << 5)
|
||||
#define CLK_PCKENR1_TIM3 (1U << 6)
|
||||
|
@ -1895,7 +1895,7 @@ typedef union {
|
|||
uint8_t OC1PE:1; /*!< bit 3: Output compare 1 preload enable */
|
||||
uint8_t OC1M:3; /*!< bit 4..6: Output compare 1 mode */
|
||||
uint8_t OC1CE:1; /*!< bit 7: Output compare 1 clear enable */
|
||||
} output_fields; /*!< structure used for bit access */
|
||||
} outpur_fields; /*!< structure used for bit access */
|
||||
struct {
|
||||
uint8_t CC1S:2; /*!< bit 0..1: Capture/compare 1 selection */
|
||||
uint8_t IC1PSC:2; /*!< bit 2..3: Input capture 1 prescaler */
|
||||
|
@ -1910,7 +1910,7 @@ typedef union {
|
|||
uint8_t :1; /*!< bit 2: Reserved */
|
||||
uint8_t OC1PE:1; /*!< bit 3: Output compare 1 preload enable */
|
||||
uint8_t OC1M:3; /*!< bit 4..6: Output compare 1 mode */
|
||||
} output_fields; /*!< structure used for bit access */
|
||||
} outpur_fields; /*!< structure used for bit access */
|
||||
struct {
|
||||
uint8_t CC1S:2; /*!< bit 0..1: Capture/compare 1 selection */
|
||||
uint8_t IC1PSC:2; /*!< bit 2..3: Input capture 1 prescaler */
|
||||
|
@ -3294,31 +3294,26 @@ typedef union {
|
|||
uint8_t reg; /*!< type used for register access */
|
||||
} OPT_NOPT1_type; /*!< */
|
||||
|
||||
// NOTE: OPT2 fields depends on device's pin count
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t AFR0:1; /*!< bit 0: Alternate function remapping option 0 */
|
||||
uint8_t :1; /*!< bit 0: Reserved */
|
||||
uint8_t AFR1:1; /*!< bit 1: Alternate function remapping option 1 */
|
||||
uint8_t AFR2:1; /*!< bit 2: Alternate function remapping option 2 */
|
||||
uint8_t AFR3:1; /*!< bit 3: Alternate function remapping option 3 */
|
||||
uint8_t AFR4:1; /*!< bit 4: Alternate function remapping option 4 */
|
||||
uint8_t :3; /*!< bit 2..4: Reserved */
|
||||
uint8_t AFR5:1; /*!< bit 5: Alternate function remapping option 5 */
|
||||
uint8_t AFR6:1; /*!< bit 6: Alternate function remapping option 6 */
|
||||
uint8_t AFR7:1; /*!< bit 7: Alternate function remapping option 7 */
|
||||
uint8_t :1; /*!< bit 7: Reserved */
|
||||
} fields; /*!< structure used for bit access */
|
||||
uint8_t reg; /*!< type used for register access */
|
||||
} OPT_OPT2_type; /*!< */
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t NAFR0:1; /*!< bit 0: Alternate function remapping option 0 */
|
||||
uint8_t :1; /*!< bit 0: Reserved */
|
||||
uint8_t NAFR1:1; /*!< bit 1: Alternate function remapping option 1 */
|
||||
uint8_t NAFR2:1; /*!< bit 2: Alternate function remapping option 2 */
|
||||
uint8_t NAFR3:1; /*!< bit 3: Alternate function remapping option 3 */
|
||||
uint8_t NAFR4:1; /*!< bit 4: Alternate function remapping option 4 */
|
||||
uint8_t :3; /*!< bit 2..4: Reserved */
|
||||
uint8_t NAFR5:1; /*!< bit 5: Alternate function remapping option 5 */
|
||||
uint8_t NAFR6:1; /*!< bit 6: Alternate function remapping option 6 */
|
||||
uint8_t NAFR7:1; /*!< bit 7: Alternate function remapping option 7 */
|
||||
uint8_t :1; /*!< bit 7: Reserved */
|
||||
} fields; /*!< structure used for bit access */
|
||||
uint8_t reg; /*!< type used for register access */
|
||||
} OPT_NOPT2_type; /*!< */
|
||||
|
@ -3393,14 +3388,14 @@ typedef struct {
|
|||
|
||||
#define OPT_BASE 0x4800
|
||||
#define OPT ((OPT_type*)OPT_BASE)
|
||||
#define OPT_OPT0 (*(volatile uint8_t *)(OPT_BASE + 0x00))
|
||||
#define ROP OPT_OPT0
|
||||
#define OPT_OPT1 (*(volatile uint8_t *)(OPT_BASE + 0x01))
|
||||
#define UBC OPT_OPT1
|
||||
#define OPT_NOPT1 (*(volatile uint8_t *)(OPT_BASE + 0x02))
|
||||
#define NUBC OPT_NOPT1
|
||||
#define OPT_OPT2 (*(volatile uint8_t *)(OPT_BASE + 0x03))
|
||||
#define AFR OPT_OPT2
|
||||
#define OPT0 (*(volatile uint8_t *)(OPT_BASE + 0x00))
|
||||
#define ROP OPT0
|
||||
#define OPT1 (*(volatile uint8_t *)(OPT_BASE + 0x01))
|
||||
#define UBC OPT1
|
||||
#define NOPT1 (*(volatile uint8_t *)(OPT_BASE + 0x02))
|
||||
#define NUBC NOPT1
|
||||
#define OPT2 (*(volatile uint8_t *)(OPT_BASE + 0x03))
|
||||
#define AFR OPT2
|
||||
#define AFR_AFR0 (1U << 0)
|
||||
#define AFR_AFR1 (1U << 1)
|
||||
#define AFR_AFR2 (1U << 2)
|
||||
|
@ -3409,8 +3404,8 @@ typedef struct {
|
|||
#define AFR_AFR5 (1U << 5)
|
||||
#define AFR_AFR6 (1U << 6)
|
||||
#define AFR_AFR7 (1U << 7)
|
||||
#define OPT_NOPT2 (*(volatile uint8_t *)(OPT_BASE + 0x04))
|
||||
#define NAFR OPT_NOPT2
|
||||
#define NOPT2 (*(volatile uint8_t *)(OPT_BASE + 0x04))
|
||||
#define NAFR NOPT2
|
||||
#define AFR_NAFR0 (1U << 0)
|
||||
#define AFR_NAFR1 (1U << 1)
|
||||
#define AFR_NAFR2 (1U << 2)
|
||||
|
@ -3419,32 +3414,32 @@ typedef struct {
|
|||
#define AFR_NAFR5 (1U << 5)
|
||||
#define AFR_NAFR6 (1U << 6)
|
||||
#define AFR_NAFR7 (1U << 7)
|
||||
#define OPT_OPT3 (*(volatile uint8_t *)(OPT_BASE + 0x05))
|
||||
#define OPT3 (*(volatile uint8_t *)(OPT_BASE + 0x05))
|
||||
#define OPT3_WWDG_HALT (1U << 0)
|
||||
#define OPT3_WWDG_HW (1U << 1)
|
||||
#define OPT3_IWDG_HW (1U << 2)
|
||||
#define OPT3_LSI_EN (1U << 3)
|
||||
#define OPT3_HSI_TRIM (1U << 4)
|
||||
#define OPT_NOPT3 (*(volatile uint8_t *)(OPT_BASE + 0x06))
|
||||
#define NOPT3 (*(volatile uint8_t *)(OPT_BASE + 0x06))
|
||||
#define NOPT3_NWWDG_HALT (1U << 0)
|
||||
#define NOPT3_NWWDG_HW (1U << 1)
|
||||
#define NOPT3_NIWDG_HW (1U << 2)
|
||||
#define NOPT3_NLSI_EN (1U << 3)
|
||||
#define NOPT3_NHSI_TRIM (1U << 4)
|
||||
#define OPT_OPT4 (*(volatile uint8_t *)(OPT_BASE + 0x07))
|
||||
#define OPT4 (*(volatile uint8_t *)(OPT_BASE + 0x07))
|
||||
#define OPT4_PRS_C0 (1U << 0)
|
||||
#define OPT4_PRS_C1 (1U << 1)
|
||||
#define OPT4_CKAWU_SEL (1U << 2)
|
||||
#define OPT4_EXT_CLK (1U << 3)
|
||||
#define OPT_NOPT4 (*(volatile uint8_t *)(OPT_BASE + 0x08))
|
||||
#define NOPT4 (*(volatile uint8_t *)(OPT_BASE + 0x08))
|
||||
#define NOPT4_NPRS_C0 (1U << 0)
|
||||
#define NOPT4_NPRS_C1 (1U << 1)
|
||||
#define NOPT4_NCKAWU_SEL (1U << 2)
|
||||
#define NOPT4_NEXT_CLK (1U << 3)
|
||||
#define OPT_OPT5 (*(volatile uint8_t *)(OPT_BASE + 0x09))
|
||||
#define HSECNT OPT_OPT5
|
||||
#define OPT_NOPT5 (*(volatile uint8_t *)(OPT_BASE + 0x0A))
|
||||
#define NHSECNT OPT_NOPT5
|
||||
#define OPT5 (*(volatile uint8_t *)(OPT_BASE + 0x09))
|
||||
#define HSECNT OPT5
|
||||
#define NOPT5 (*(volatile uint8_t *)(OPT_BASE + 0x0A))
|
||||
#define NHSECNT NOPT5
|
||||
|
||||
// Unique ID
|
||||
typedef struct {
|
||||
|
@ -3456,11 +3451,9 @@ typedef struct {
|
|||
|
||||
/* some useful assembly instructions */
|
||||
// wait for event (go to sleep)
|
||||
#define wfe() { __asm__("wfe\n"); }
|
||||
#define wfe() { __asm__("wfi\n"); }
|
||||
// wait for interrupt (go to sleep)
|
||||
#define wfi() { __asm__("wfi\n"); }
|
||||
// go into halt mode
|
||||
#define halt() { __asm__("halt\n"); }
|
||||
// disable interrupts
|
||||
#define sim() { __asm__("sim"); }
|
||||
// enable interrupts
|
||||
|
|
Loading…
Reference in New Issue