/* firmware template for STM8S microcontroller * Copyright (C) 2019-2022 King Kévin * SPDX-License-Identifier: GPL-3.0-or-later * for HDMI firewall v2.37 */ #include #include #include #include "stm8s.h" #include "main.h" #include "softi2c_master.h" // enable UART debug #define DEBUG 1 #define EEPROM_ADDR 0x4000 // EEPROM start address static bool eeprom_valid = true; // if the EDID can be read from EEPROM // LED pin (sink for on) #define LED_PORT GPIO_PA #define LED_PIN PA3 // I²C pins (sink and source) #define SDA_SRC_PORT GPIO_PB #define SDA_SRC_PIN PB5 #define SCL_SRC_PORT GPIO_PB #define SCL_SRC_PIN PB4 #define SDA_SRC_PU_PORT GPIO_PC #define SDA_SRC_PU_PIN PC3 #define SCL_SRC_PU_PORT GPIO_PC #define SCL_SRC_PU_PIN PC4 #define SDA_SNK_PORT GPIO_PD #define SDA_SNK_PIN PD2 #define SCL_SNK_PORT GPIO_PD #define SCL_SNK_PIN PD3 #define SDA_SNK_PU_PORT GPIO_PD #define SDA_SNK_PU_PIN PD6 #define SCL_SNK_PU_PORT GPIO_PC #define SCL_SNK_PU_PIN PC5 static bool i2c_fwd = false; // if the I²C source lines are connected to sink // hot plug detect pull up #define HPD_PORT GPIO_PC #define HPD_PIN PC6 static bool hpd_fwd = false; // if the I²C source line is connected to sink // copy EDID setting #define EDID_PORT GPIO_PC #define EDID_PIN PC7 // if an I²C transaction started static volatile bool i2c_transaction_new = false; // if an I²C transaction with incoming data started static volatile bool i2c_input_new = false; // if the byte should be programmed static volatile bool i2c_prog = false; // the address of the byte to be read or programmed static volatile uint8_t i2c_addr = 0; // the data to be programmed static volatile uint8_t i2c_data = 0; // blocking wait (in 10 us steps, up to UINT32_MAX / 10) static void wait_10us(uint32_t us10) { us10 = ((us10 / (1 << CLK->CKDIVR.fields.HSIDIV)) * 1000) / 206; // calibrated for 1 ms while (us10--); // burn energy } void putc(char c) { (void)c; IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog #if DEBUG 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 #endif } void puts(const char* s) { if (NULL == s) { return; } while (*s) { putc(*s++); } } void putn(uint8_t n) { n &= 0x0f; // ensure it's a nibble if (n < 0xa) { n += '0'; } else { n = 'a' + (n - 0x0a); } putc(n); } 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 its length (including extensions), or 0 if invalid /* 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 (and is deprecated, replaced by E-EDID) * DisplayID with its variable-length structure meant to replace E-EDID only seems to be used in DisplayPort */ static uint16_t edid_length(const uint8_t* edid) { 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)) { // ensure checksum of base EDID is ok const uint16_t length = 128 + edid[126] * 128; // get length with all extensions puts("(v1.3/1.4, "); putn(edid[126] / 100); putn((edid[126] / 10) % 10); putn(edid[126] % 10); puts(" extension) "); if (edid[126]) { // extensions are present for (uint16_t i = 128; i < length && i < 256; i += 128) { // verify checksum of each extension (we actually only support one extension) if (!checksum_ok(&edid[i], 128)) { puts("extension CRC error\r\n"); return 0; // EDID is broken } } } puts("CRC OK\r\n"); return length; } else { puts("base CRC error\r\n"); return 0; } } else if (2 == edid[18]) { // EDID 2.0 256-byte structure if (checksum_ok(&edid[0], 256)) { puts("(v2) CRC ok\r\n"); return 256; } else { puts("CRC error\r\n"); return 0; } } return 0; } // modify EDID to indicate firewall static void edid_modify(uint8_t* edid) { // modify EDID to include the character indicating the firewall const char firewall_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_t i = 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_t last_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_t checksum = 0; for (uint8_t i = 0; i < 127; i++) { checksum += edid[i]; } edid[127] = (256 - checksum); } void main(void) { sim(); // disable interrupts (while we reconfigure them) CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide internal 16 MHz clock 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 AWU->TBR.fields.AWUTB = 10; // interval range: 128-256 ms 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 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 IWDG->KR.fields.KEY = IWDG_KR_KEY_ACCESS; // allows changing the prescale IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s) IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog // 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 // configure I²C lines (sink and source sides) SDA_SRC_PU_PORT->ODR.reg |= SDA_SRC_PU_PIN; // pull up SDA line SDA_SRC_PU_PORT->CR1.reg |= SDA_SRC_PU_PIN; // switch pin to push pull SDA_SRC_PU_PORT->DDR.reg |= SDA_SRC_PU_PIN; // switch pin to output SCL_SRC_PU_PORT->ODR.reg |= SCL_SRC_PU_PIN; // pull up SCL line SCL_SRC_PU_PORT->CR1.reg |= SCL_SRC_PU_PIN; // switch pin to push pull SCL_SRC_PU_PORT->DDR.reg |= SCL_SRC_PU_PIN; // switch pin to output SCL_SRC_PORT->ODR.reg |= SCL_SRC_PIN; // release SCL line SCL_SRC_PORT->CR1.reg &= ~SCL_SRC_PIN; // switch pin to open-drain SCL_SRC_PORT->DDR.reg |= SCL_SRC_PIN; // switch pin to output SDA_SRC_PORT->ODR.reg |= SDA_SRC_PIN; // release SDA line SDA_SRC_PORT->CR1.reg &= ~SDA_SRC_PIN; // switch pin to open-drain SDA_SRC_PORT->DDR.reg |= SDA_SRC_PIN; // switch pin to output SCL_SNK_PORT->ODR.reg |= SCL_SNK_PIN; // release SCL line SCL_SNK_PORT->CR1.reg &= ~SCL_SNK_PIN; // switch pin to open-drain SCL_SNK_PORT->DDR.reg |= SCL_SNK_PIN; // switch pin to output SDA_SNK_PORT->ODR.reg |= SDA_SNK_PIN; // release SDA line SDA_SNK_PORT->CR1.reg &= ~SDA_SNK_PIN; // switch pin to open-drain SDA_SNK_PORT->DDR.reg |= SDA_SNK_PIN; // switch pin to output // test I²C forwarding SDA_SNK_PORT->ODR.reg &= ~SDA_SNK_PIN; // assert SDA line (send start condition) SCL_SNK_PORT->ODR.reg &= ~SCL_SNK_PIN; // assert SCL line (send start condition) wait_10us(1); // wait 1 clock cycle if (0 == (SCL_SRC_PORT->IDR.reg & SCL_SRC_PIN) || 0 == (SDA_SRC_PORT->IDR.reg & SDA_SRC_PIN)) { i2c_fwd = true; // remember the I²C line(s) are forwarded puts("I²C lines forwarded\r\n"); } SCL_SNK_PORT->ODR.reg |= SCL_SNK_PIN; // release SCL line (send stop condition) SDA_SNK_PORT->ODR.reg |= SDA_SNK_PIN; // release SDA line (send stop condition) // configure I²C pull-ups if (i2c_fwd) { // I²C lines are not pulled up SDA_SRC_PU_PORT->CR1.reg &= ~SDA_SRC_PU_PIN; // disable pull up SDA_SRC_PU_PORT->DDR.reg &= ~SDA_SRC_PU_PIN; // switch pin to input SCL_SRC_PU_PORT->CR1.reg &= ~SCL_SRC_PU_PIN; // disable pull up SCL_SRC_PU_PORT->DDR.reg &= ~SCL_SRC_PU_PIN; // switch pin to input } // configure I²C if (!i2c_fwd) { // we will act as I²C slave EEPROM /* HDMI 1.3 spec: The Sink shall be capable of responding with EDID 1.3 data and up to 255 extension blocks, each 128 bytes long (up to 32K bytes total E-EDID memory) whenever the Hot Plug Detect signal is asserted. The Sink should be capable of providing E-EDID information over the Enhanced DDC channel whenever the +5V Power signal is provided. This should be available within 20msec after the +5V Power signal is provided. firewall: we will only be able to provide 1 extension block since we can only respond to one I²C address 0x50 for the EDID, and not to the 0x30 Segment Pointer (for the other pages/extension blocks) */ GPIO_PB->CR1.reg |= (PB4 | PB5); // enable internal pull-up on SCL/SDA GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral (must be done before any other register is written) I2C_CR2 |= I2C_CR2_STOP; // release lines I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit while (0 == (GPIO_PB->IDR.reg & PB4)); // wait for SCL line to be released while (0 == (GPIO_PB->IDR.reg & PB5)); // wait for SDA line to be released I2C_CR2 &= ~I2C_CR2_SWRST; // release reset I2C_CR1 |= I2C_CR1_PE; // re-enable I²C peripheral I2C_FREQR = 16; // the peripheral frequency is 4 MHz (must match CPU frequency) I2C_CR2 |= I2C_CR2_ACK; // enable acknowledgement if address matches // since we are slave and not master, we don't have to set CCR I2C_OARL = (0x50U << 1U); // set slave address for EEPROM I2C_ITR |= (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN); // enable buffer and event interrupts } // configure hot plug detect HPD_PORT->DDR.reg |= HPD_PIN; // switch pin to output HPD_PORT->CR1.reg &= ~HPD_PIN; // switch pin to open drain HPD_PORT->ODR.reg &= ~HPD_PIN; // drain line wait_10us(0); // wait shortly to clear tristate HPD_PORT->DDR.reg &= ~HPD_PIN; // switch pin to input (already floating) if (HPD_PORT->IDR.reg & HPD_PIN) { // line is pulled up hpd_fwd = true; // remember the HPD line is pulled up } if (!hpd_fwd) { // we have to pull up HPD_PORT->DDR.reg |= HPD_PIN; // switch pin to output HPD_PORT->CR1.reg |= HPD_PIN; // switch pin to push pull HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready } rim(); // re-enable interrupts // even if we don't pull up HPD ourself, we should be able to respond to I²C within 20 ms if (!i2c_fwd) { puts("I²C source ready\r\n"); } // check if EDID should be copied EDID_PORT->DDR.reg &= ~EDID_PIN; // switch pin to output EDID_PORT->CR1.reg |= EDID_PIN; // enable pull up wait_10us(1); // wait for pull up to take effect if (EDID_PORT->IDR.reg & EDID_PIN) { // EDID switched off puts("EDID protected\r\n"); } else if (i2c_fwd) { // we are not the only master on the I²C sink lines puts("can't read EDID: I²C lines forwarded\r\n"); LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate error } else { IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog puts("reading sink EDID: "); LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate we are saving EDID SDA_SNK_PU_PORT->ODR.reg |= SDA_SNK_PU_PIN; // pull up SDA line SDA_SNK_PU_PORT->CR1.reg |= SDA_SNK_PU_PIN; // switch pin to push pull SDA_SNK_PU_PORT->DDR.reg |= SDA_SNK_PU_PIN; // switch pin to output SCL_SNK_PU_PORT->ODR.reg |= SCL_SNK_PU_PIN; // pull up SCL line SCL_SNK_PU_PORT->CR1.reg |= SCL_SNK_PU_PIN; // switch pin to push pull SCL_SNK_PU_PORT->DDR.reg |= SCL_SNK_PU_PIN; // switch pin to output if (0 == (SDA_SNK_PORT->IDR.reg & SDA_SNK_PIN)) { // SDA line is stuck puts ("bus clear "); // perform bus clear for (uint8_t i = 0; i < 9; i++) { SCL_SNK_PORT->ODR.reg &= ~SCL_SNK_PIN; wait_10us(1); SCL_SNK_PORT->ODR.reg |= SCL_SNK_PIN; wait_10us(1); } } uint8_t i2c_rc = 1; // if the complete read succeeded if (!softi2c_master_setup(400)) { // start the I²C master to talk to sink i2c_rc = 2; goto i2c_end; } if (!softi2c_master_start()) { // start transaction i2c_rc = 3; goto i2c_end; } if (!softi2c_master_select_slave(0x50, true)) { // select EDID i2c_rc = 4; goto i2c_end; } const uint8_t edid_addr = 0x00; // EDID address to read if (!softi2c_master_write(&edid_addr, 1)) { // write address to read i2c_rc = 5; goto i2c_end; } if (!softi2c_master_start()) { // re-start transaction i2c_rc = 6; goto i2c_end; } if (!softi2c_master_select_slave(0x50, false)) { // re-select EDID puts("sink not present "); i2c_rc = 7; goto i2c_end; } uint8_t edid_sink[256]; // buffer for sink EDID if (!softi2c_master_read(edid_sink, ARRAY_LENGTH(edid_sink))) { // read EDID i2c_rc = 8; goto i2c_end; } i2c_rc = 0; // complete read succeeded i2c_end: softi2c_master_stop(); if (0 == i2c_rc) { puts("success\r\n"); uint16_t edid_len = edid_length(edid_sink); // get length if (edid_len > 256) { // we only support up to one extension edid_len = 256; } if (edid_len) { // EDID is valid edid_modify(edid_sink); // modify EDID to include firewall indication // compare saved/source and sink EDID bool edid_equal = true; for (uint16_t i = 0; i < edid_len && edid_equal; i++) { if (*(uint8_t*)(EEPROM_ADDR + i) != edid_sink[i]) { edid_equal = false; } } if (edid_equal) { LED_PORT->ODR.reg |= LED_PIN; // switch LED off to indicate EDID is same puts("EDID not changed\r\n"); } else { puts("saving EDID: "); // note: the STM8S103 does not support RWW // try to make fast small operations to not stall I²C communication eeprom_valid = false; // invalidate saved EDID while re-programming it bool flash_success = true; // disable DATA (e.g. EEPROM) write protection if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) { FLASH_DUKR = FLASH_DUKR_KEY1; FLASH_DUKR = FLASH_DUKR_KEY2; } // erase EEPROM (we don't do faster block erase since it needs to be done from RAM) for (uint16_t i = 0; i < 256; i += 4U) { // go through word IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog FLASH_CR2 |= FLASH_CR2_WPRG; // set word programming FLASH_NCR2 &= ~FLASH_NCR2_NWPRG; // set word programming *(uint32_t*)(EEPROM_ADDR + i) = 0; // erase word while (FLASH_CR2 & FLASH_CR2_WPRG); // wait for erase to complete // check if programming failed // we don't check for WR_PG_DIS (while checking EOP) because EEPROM isn't (and can't be) write protected if (!(FLASH_IAPSR & FLASH_IAPSR_EOP)) { FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection flash_success = false; // remember we had a flashing error puts("failed\r\n"); break; } } // save need EDID for (uint16_t i = 0; i < edid_len && flash_success; i += 4U) { // go through word IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog FLASH_CR2 |= FLASH_CR2_WPRG; // set word programming FLASH_NCR2 &= ~FLASH_NCR2_NWPRG; // set word programming *(uint32_t*)(EEPROM_ADDR + i) = *(uint32_t*)(&edid_sink[i]); // write word while (FLASH_CR2 & FLASH_CR2_WPRG); // wait for write to complete // check if programming failed // we don't check for WR_PG_DIS (while checking EOP) because EEPROM isn't (and can't be) write protected if (!(FLASH_IAPSR & FLASH_IAPSR_EOP)) { FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection flash_success = false; // remember we had a flashing error puts("failed\r\n"); break; } } FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection if (flash_success) { eeprom_valid = true; // re-enable EDID reading LED_PORT->ODR.reg |= LED_PIN; // switch LED off to indicate we are completed saving EDID puts("done\r\n"); } else { puts("flashing error\r\n"); } // indicate there is a new EDID if (!hpd_fwd) { // we have to pull up /* HDMI v1.3 spec: An HDMI Sink shall indicate any change to the contents of the E-EDID by driving a low voltage level pulse on the Hot Plug Detect pin. This pulse shall be at least 100 msec. */ HPD_PORT->ODR.reg &= ~HPD_PIN; // pull down HPD line to indicate EDID change wait_10us(200 * 100); // wait over 100 ms HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready } } } } else { LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate I²C read fail puts("fail (rc="); putc('0' + i2c_rc); puts(")\r\n"); } } // verify stored EDID validity if (!edid_length((uint8_t*)EEPROM_ADDR)) { LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate EDID is invalid puts("EEPROM EDID invalid\r\n"); } bool action = false; // if an action has been performed puts("loop\r\n"); while (true) { IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog /* if (i2c_transaction_new) { // an I²C transaction started i2c_transaction_new = false; // clear flag if (i2c_input_new) { puts("I²C write\r\n"); } else { puts("I²C read\r\n"); } } */ if (i2c_prog) { // received data over i2c to be programmed if (EDID_PORT->IDR.reg & EDID_PIN) { // EDID switched off puts("I²C prog disabled\r\n"); I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte to indicate programming it disabled } else { // EDID programming allowed /* puts("I²C prog "); puth(i2c_data); puts("@"); puth(i2c_addr); puts("\r\n"); */ IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog // disable DATA (e.g. EEPROM) write protection if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) { FLASH_DUKR = FLASH_DUKR_KEY1; FLASH_DUKR = FLASH_DUKR_KEY2; } // save need EDID *(uint8_t*)(EEPROM_ADDR + i2c_addr) = i2c_data; // write byte while (FLASH_CR2 & FLASH_CR2_WPRG); // wait for write to complete // check if programming failed // we don't check for WR_PG_DIS (while checking EOP) because EEPROM isn't (and can't be) write protected if (!(FLASH_IAPSR & FLASH_IAPSR_EOP)) { FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate programming failed I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte to indicate programming error puts("EEPROM byte prog failed\r\n"); } FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection } action = true; // re-run loop i2c_prog = false; // clear flag } 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) } } } 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) // let the main loop kick the dog } void i2c(void) __interrupt(IRQ_I2C) // auto wakeup { // make copies of status registers, since some bits might be cleared meanwhile const uint8_t sr1 = I2C_SR1; const uint8_t sr2 = I2C_SR2; const uint8_t sr3 = I2C_SR3; // clears ADDR after reading SR1 if (sr1 & I2C_SR1_TXE) { // transmission buffer is empty I2C_DR = *(uint8_t*)(EEPROM_ADDR + i2c_addr++); // transmit next byte (even if invalid) } if (sr1 & I2C_SR1_RXNE) { // receive buffer is full i2c_data = I2C_DR; // read data (also clears flag) if (i2c_input_new) { // we just received the first byte i2c_addr = i2c_data; // we only take the first address byte i2c_input_new = false; // next byte is not the first } else { // received data byte i2c_prog = true; // notify main loop data needs to be programmed } if (EDID_PORT->IDR.reg & EDID_PIN) { // EDID programming is not enabled I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte to indicate programming it disabled } } if (sr1 & I2C_SR1_STOPF) { // stop received I2C_CR2 |= I2C_CR2_ACK; // this is just to clear the flag } if (sr1 & I2C_SR1_ADDR) { // our slave address has been selected i2c_transaction_new = true; // notify main loop transaction started if (sr3 & I2C_SR3_TRA) { // start data transmission I2C_DR = *(uint8_t*)(EEPROM_ADDR + i2c_addr++); // transmit selected byte i2c_input_new = false; // notify we send data } else { // we will receive data if (EDID_PORT->IDR.reg & EDID_PIN) { // EDID switched off I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte to indicate programming it disabled } else { I2C_CR2 |= I2C_CR2_ACK; // ACK next received byte } i2c_input_new = true; // notify we get data } } if (sr1 & I2C_SR1_BTF) { // byte transfer finished (only set when stretching has been enabled) // cleared by reading/writing from/to DR or when stop is received } if (sr2 & I2C_SR2_AF) { // NACK received (e.g. end of read transaction) I2C_SR2 &= ~I2C_SR2_AF; // clear flag } }