/* firmware template for STM8S microcontroller * Copyright (C) 2019-2022 King Kévin * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include "stm8s.h" #include "main.h" #include "softi2c_master.h" #define EEPROM_ADDR 0x4000 // EEPROM start address // LED pin (sink for on) #define LED_PORT GPIO_PA #define LED_PIN PA3 // pull ups for device facing I²C port #define SDA_PU_PORT GPIO_PC #define SDA_PU_PIN PC3 #define SCL_PU_PORT GPIO_PC #define SCL_PU_PIN PC4 static bool i2c_fwd = false; // if the I²C source lines are connected to sink // external EEPROM write protect pin #define WP_PORT GPIO_PC #define WP_PIN PC5 // hot plug detect pull up #define HPD_PORT GPIO_PC #define HPD_PIN PC6 // 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 new data started static volatile bool i2c_input_new = false; // 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) { 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); } 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 pull-ups SDA_PU_PORT->DDR.reg |= SDA_PU_PIN; // switch pin to output SDA_PU_PORT->CR1.reg &= ~SDA_PU_PIN; // switch pin to open-drain SDA_PU_PORT->ODR.reg &= ~SDA_PU_PIN; // pull SDA line low wait_10us(0); // wait shortly to clear tristate SDA_PU_PORT->DDR.reg &= ~SDA_PU_PIN; // switch pin to input (already floating) if (SDA_PU_PORT->IDR.reg & SDA_PU_PIN) { // line is pulled up i2c_fwd = true; // remember the I²C line(s) are forwarded } SCL_PU_PORT->DDR.reg |= SCL_PU_PIN; // switch pin to output SCL_PU_PORT->CR1.reg &= ~SCL_PU_PIN; // switch pin to open-drain SCL_PU_PORT->ODR.reg &= ~SCL_PU_PIN; // pull SDA line low wait_10us(0); // wait shortly to clear tristate SCL_PU_PORT->DDR.reg &= ~SCL_PU_PIN; // switch pin to input (already floating) if (SCL_PU_PORT->IDR.reg & SCL_PU_PIN) { // line is pulled up i2c_fwd = true; // remember the I²C line(s) are forwarded } if (!i2c_fwd) { // I²C lines are not pulled up SDA_PU_PORT->DDR.reg |= SDA_PU_PIN; // switch pin to output SDA_PU_PORT->CR1.reg |= SDA_PU_PIN; // switch pin to push pull SDA_PU_PORT->ODR.reg |= SDA_PU_PIN; // pull up SDA line SCL_PU_PORT->DDR.reg |= SCL_PU_PIN; // switch pin to output SCL_PU_PORT->CR1.reg |= SCL_PU_PIN; // switch pin to push pull SCL_PU_PORT->ODR.reg |= SCL_PU_PIN; // pull up SCL line } // configure external EEPROM (actually not used) WP_PORT->DDR.reg |= WP_PIN; // switch pin to output WP_PORT->CR1.reg &= ~WP_PIN; // switch pin to open drain WP_PORT->ODR.reg |= WP_PIN; // let write protect pulled up // configure I²C if (!i2c_fwd) { // we will act as I²C slave EEPROM 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 push pull HPD_PORT->ODR.reg |= HPD_PIN; // pull up HPD line to indicate EDID is ready // TODO check if connected to sink/monitor /* 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. */ // 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 if (0 == (EDID_PORT->ODR.reg & EDID_PIN)) { // EDID switched on // TODO copy EDID from sink/monitor /* 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. */ } 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 puts("I²C ready\r\n"); // read sink EDID if (!i2c_fwd) { // ensure we are the only master on the I²C sink lines IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog puts("reading sink EDID: "); softi2c_master_setup(100); // start the I²C master to talk to sink bool i2c_success = false; // if the complete read succeeded if (!softi2c_master_start()) { // start transaction goto i2c_end; } if (!softi2c_master_select_slave(0x50, true)) { // select EDID goto i2c_end; } const uint8_t edid_addr = 0x00; // EDID address to read if (!softi2c_master_write(&edid_addr, 1)) { // write address to read goto i2c_end; } if (!softi2c_master_start()) { // re-start transaction goto i2c_end; } if (!softi2c_master_select_slave(0x50, false)) { // re-select EDID goto i2c_end; } uint8_t edid_sink[256]; // buffer for sink EDID if (!softi2c_master_read(edid_sink, ARRAY_LENGTH(edid_sink))) { // read EDID goto i2c_end; } i2c_success = true; // complete read succeeded i2c_end: softi2c_master_stop(); if (i2c_success) { puts("success\r\n"); } else { puts("fail\r\n"); } } bool action = false; // if an action has been performed LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate we are ready 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 (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 { static uint8_t addr = 0; // EEPROM address to read // 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 + addr++); // transmit next byte } if (sr1 & I2C_SR1_RXNE) { // receive buffer is full const uint8_t data = I2C_DR; // read data (also clears flag) if (I2C_CR2 & I2C_CR2_ACK) { addr = data; // we only take the first address byte } I2C_CR2 &= I2C_CR2_ACK; // NACK next received byte } 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 + addr++); // transmit selected byte i2c_input_new = false; // notify we send data } else { // we will receive data 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 } }