includes register definitions using macros and structures.
firmware for [HDMI firewall v2](https://git.cuvoodoo.info/kingkevin/board/src/branch/hdmi_firewall).
usage
=====
functions:
- upon request by the device/source over the HDMI DDC interface, the HDMI firewall returns the EDID stored in its EEPROM
- if the EDID/7 switch is on the ALLOW/ON position, when powered (e.g. device plugs in), it will retrieve the monitors EDID and store it in EEPROM (ERROR LED will blink once)
- once the EDID copied, it will try to re-connect to device by pulsing the Hot Plug Detect (HPD)
- stored EDID adds a '|' at the end of the name to indicate firewall is used
- if DDC is forwarded (e.g. SCL and SDA switches are on the ALLOW position), the HDMI firewall does no interfere with the signals, does not return the stored EDID, and does not firewall the communication
if the ERROR LED is on, the possible cause is one of the following:
- no EDID might is present in the EEPROM
- tried reading the EDID from the monitor, but it is not connected
- communication with monitor failed, due to damaged cable
- monitor EDID is invalid
- storing EDID in EEPROM failed
the firewall only acts as an I²C EEPROM at address 0x50 toward the HDMI device to provide the EDID information.
if the EDID switch is on the BLOCK position, the EEPROM is read only.
if the EDID switch is on the ALLOW position, writing the EEPROM is possible over the HDMI connection using standard I²C write operations.
limitations
===========
stored EDID has only up 1 EDID extension.
some monitors might use more to offer additional features, but I haven't encountered this case yet.
flashing
========
the firmware is for an STM8S103.
the debug port on the HDMI firewall allows to flash and debug the firmware.
to compile the firmware using [SDCC](http://sdcc.sourceforge.net/):
~~~
make
~~~
to flash the firmware with [stm8flash](https://github.com/vdudouyt/stm8flash) using an ST-LINK/V2 (clone):
~~~
make flash
~~~
to store the generic HD EDID profile in EEPROM:
~~~
make eeprom
~~~
this uses the `edid_cuvoodoo.bin` binary EDID, which you can replace with your own.
to enable printf debugging, set `DEBUG` to `1` in `main.c`.
#include"i2c_master.h" // I²C header and definitions
booli2c_master_setup(uint16_tfreq_khz)
{
// configure I²C peripheral
I2C_CR1&=~I2C_CR1_PE;// disable I²C peripheral to configure it
/*
if(!i2c_master_check_signals()){// check the signal lines
returnfalse;
}
*/
I2C_FREQR=16;// the peripheral frequency (must match CPU frequency)
if(freq_khz>100){
uint16_tccr=(I2C_FREQR*1000)/(3*freq_khz);
if(ccr>0x0fff){
ccr=0x0fff;
}
I2C_CCRL=(ccr&0xff);// set SCL at 320 kHz (for less error)
I2C_CCRH=((ccr>>8)&0x0f)|(I2C_CCRH_FS);// set fast speed mode
I2C_TRISER=((I2C_FREQR*3/10)+1);// set rise time
}else{
uint16_tccr=(I2C_FREQR*1000)/(2*freq_khz);
if(ccr>0x0fff){
ccr=0x0fff;
}
I2C_CCRL=(ccr&0xff);// set SCL at 320 kHz (for less error)
I2C_CCRH=((ccr>>8)&0x0f);// set fast speed mode
I2C_TRISER=(I2C_FREQR+1);// set rise time
}
I2C_CR1|=I2C_CR1_PE;// enable I²C peripheral
returntrue;
}
voidi2c_master_release(void)
{
I2C_CR1&=~I2C_CR1_PE;// disable I²C peripheral
}
booli2c_master_check_signals(void)
{
i2c_master_release();// ensure PB4/PB5 are not used as alternate function
GPIO_PB->CR1.reg&=~(PB4|PB5);// operate in open-drain mode
GPIO_PB->DDR.reg|=(PB4|PB5);// set SCL/SDA as output to test pull-up
GPIO_PB->ODR.reg|=PB4;// ensure SCL is high
GPIO_PB->ODR.reg&=~PB5;// set SDA low (start condition)
for(volatileuint8_tt=0;t<10;t++);// wait a bit to be sure signal is low
GPIO_PB->ODR.reg|=PB5;// set SDA high (stop condition)
GPIO_PB->DDR.reg&=~(PB4|PB5);// set SCL/SDA as input before it is used as alternate function by the peripheral
for(volatileuint8_tt=0;t<50;t++);// wait 10 us for pull-up to take effect
return((GPIO_PB->IDR.reg&PB4)&&(GPIO_PB->IDR.reg&PB5));// test if both lines are up
}
voidi2c_master_reset(void)
{
I2C_CR2|=I2C_CR2_STOP;// release lines
// don't check if BUSY is cleared since its state might be erroneous
// rewriting I2C_CR2 before I2C_CR2_STOP is cleared might cause a second STOP, but at this point we don't care
I2C_CR2|=I2C_CR2_SWRST;// reset peripheral, in case we got stuck and the dog bit
// be sure a watchdog is present as this can take forever
while((0==(GPIO_PB->IDR.reg&PB4)&&(0==(GPIO_PB->IDR.reg&PB5))));// wait for SDA/SCL line to be released
I2C_CR2&=~I2C_CR2_SWRST;// release reset
I2C_CR1&=~I2C_CR1_PE;// disable I²C peripheral to clear some bits
}
enumi2c_master_rci2c_master_start(void)
{
// send (re-)start condition
if(I2C_CR2&(I2C_CR2_START|I2C_CR2_STOP)){// ensure start or stop operations are not in progress
returnI2C_MASTER_RC_START_STOP_IN_PROGESS;
}
// don't check BUSY flag as this might be for a re-start
I2C_CR2|=I2C_CR2_START;// sent start condition
I2C_SR2=0;// clear error flags
rim();// enable interrupts
while((I2C_CR2&I2C_CR2_START)||!(I2C_SR1&I2C_SR1_SB)||!(I2C_SR3&I2C_SR3_MSL)){// wait until start condition has been accepted, send, and we are in aster mode
wfi();// got to sleep to prevent EMI causing glitches
}
}
}
// I2C_SR3_TRA should be set after I2C_SR1_ADDR is cleared (end of address transmission), but this is not the case and the TRM/errata does not provide more info
(void)(I2C_SR1&I2C_SR1_BTF);// clear BTF (when followed by write) in case the clock is stretched because there was no data to send on the next transmission slot
I2C_DR=data[i];// send byte
I2C_SR2=0;// clear error flags
rim();// enable interrupts
while(!(I2C_SR1&I2C_SR1_TXE)){// wait until byte has been transmitted
// modify EDID to include the character indicating the firewall
constcharfirewall_indicator='|';// pipe/wall character to indicate the firewall
// ensure we only have up to one extension
if(edid[126]>1){
edid[126]=1;
}
for(uint8_ti=0;i<4;i++){// go through descriptors
if((0!=edid[54+i*18+0])||(0!=edid[54+i*18+1])||(0!=edid[54+i*18+2])||(0xfc!=edid[54+i*18+3])||(0!=edid[54+i*18+4])){// ensure descriptor is for Display name
continue;
}
uint8_tlast_c;// position of last character
for(last_c=54+i*18+5;last_c<54+i*18+18&&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 pipe
if(last_c>54+i*18+17){// ensure we insert as the last possible character
last_c=54+i*18+17;
}
edid[last_c++]=firewall_indicator;// insert pipe
if(last_c<54+i*18+17){
edid[last_c++]='\n';// insert LF to terminate string
}
while(last_c<54+i*18+18){
edid[last_c++]='';// insert padding space
}
}
}
// calculate new checksum
uint8_tchecksum=0;
for(uint8_ti=0;i<127;i++){
checksum+=edid[i];
}
edid[127]=(256-checksum);
}
voidmain(void)
{
sim();// disable interrupts (while we reconfigure them)
@ -26,6 +216,11 @@ 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
// configure LED
LED_PORT->DDR.reg|=LED_PIN;// switch pin to output
LED_PORT->CR1.reg&=~LED_PIN;// use in open-drain mode
LED_PORT->ODR.reg|=LED_PIN;// switch LED off
// 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
@ -40,10 +235,307 @@ void main(void)
IWDG->PR.fields.PR=IWDG_PR_DIV256;// set prescale to longest time (1.02s)