diff --git a/main.c b/main.c index ef00e44..7459ff6 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ /* firmware template for STM8S microcontroller - * Copyright (C) 2019-2020 King Kévin + * Copyright (C) 2019-2022 King Kévin * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -9,7 +9,29 @@ #include "stm8s.h" #include "main.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 +// 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) @@ -26,6 +48,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 +67,83 @@ 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 + // 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 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 + // TODO verify if I²C lines are connected to monitor + + // 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 + 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 bool action = false; // if an action has been performed + LED_PORT->ODR.reg &= ~LED_PIN; // switch LED on to indicate we are ready + puts("ready\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 @@ -57,3 +157,41 @@ 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 + } +}