Compare commits
38 Commits
master
...
hdmi_firew
Author | SHA1 | Date |
---|---|---|
King Kévin | 888a5af823 | |
King Kévin | 8bc856095e | |
King Kévin | 7ba053d34f | |
King Kévin | 9e35864548 | |
King Kévin | 41435806b8 | |
King Kévin | df611e4436 | |
King Kévin | d02facf754 | |
King Kévin | df482ca7c4 | |
King Kévin | 695e5dc9f4 | |
King Kévin | bb00e9c67d | |
King Kévin | 7a28b2abb5 | |
King Kévin | ed8a561430 | |
King Kévin | 3653c58772 | |
King Kévin | 87fda7e878 | |
King Kévin | af416909d1 | |
King Kévin | e211a5446a | |
King Kévin | 5d76a67bd3 | |
King Kévin | 31815bd66e | |
King Kévin | 2f0ffb9c5a | |
King Kévin | f486521126 | |
King Kévin | 298f0ea3ca | |
King Kévin | 319a783de0 | |
King Kévin | e7d3a86e45 | |
King Kévin | ba1752a409 | |
King Kévin | 5bbe2eb5c7 | |
King Kévin | b3d2ea58e5 | |
King Kévin | 2ffbe5da77 | |
King Kévin | 03e6875a94 | |
King Kévin | 5be8874b3c | |
King Kévin | 82e5d984bc | |
King Kévin | e345b61860 | |
King Kévin | 2859bb7a09 | |
King Kévin | 06c14750fc | |
King Kévin | 1770362588 | |
King Kévin | d247e2e1e8 | |
King Kévin | e2cdc89a30 | |
King Kévin | f7ed7670f3 | |
King Kévin | 9e865cc0c7 |
7
Makefile
7
Makefile
|
@ -15,7 +15,12 @@ $(FIRMWARE).ihx: $(OBJ_FILES)
|
|||
$(CC) $(CFLAGS) --compile-only $<
|
||||
|
||||
flash: $(FIRMWARE).ihx
|
||||
stm8flash -c stlinkv2 -p stm8s103f3 -w $<
|
||||
stm8flash -c stlinkv2 -p stm8s103f3 -s flash -w $<
|
||||
stm8flash -c stlinkv2 -p stm8s103f3 -s flash -v $<
|
||||
|
||||
eeprom: edid_cuvoodoo.bin
|
||||
stm8flash -c stlinkv2 -p stm8s103f3 -s eeprom -w $<
|
||||
stm8flash -c stlinkv2 -p stm8s103f3 -s eeprom -v $<
|
||||
|
||||
clean:
|
||||
rm -f $(FIRMWARE).asm $(FIRMWARE).ihx $(FIRMWARE).cdb $(FIRMWARE).lst $(FIRMWARE).map $(FIRMWARE).lk $(FIRMWARE).rel $(FIRMWARE).rst $(FIRMWARE).sym $(OBJ_FILES)
|
||||
|
|
61
README.md
61
README.md
|
@ -1,2 +1,59 @@
|
|||
firmware template for ST STM8S micro-controller.
|
||||
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`.
|
||||
the serial configuration in 115200 8N1.
|
||||
|
|
Binary file not shown.
|
@ -1,69 +0,0 @@
|
|||
/** library to program EEPROM using block programming
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2021
|
||||
* @warning functions need to be put in and run from RAM (because block programming is used)
|
||||
*/
|
||||
|
||||
// RAM code-putting from https://lujji.github.io/blog/executing-code-from-ram-on-stm8/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // boolean types
|
||||
|
||||
#include "stm8s.h" // STM8S definitions
|
||||
#include "eeprom_blockprog.h" // own definitions
|
||||
|
||||
// start address of EEPROM
|
||||
#define EEPROM_ADDR 0x4000
|
||||
// block size from low-density devices (including STM8S103)
|
||||
#define DATA_BLOCK_SIZE 64U
|
||||
|
||||
#pragma codeseg RAM_SEG
|
||||
bool eeprom_blockprog(const uint8_t* data, uint16_t length)
|
||||
{
|
||||
if (0 == length) {
|
||||
return true; // nothing to do
|
||||
}
|
||||
if (!data) {
|
||||
return false; // data missing
|
||||
}
|
||||
if (0 != (length % DATA_BLOCK_SIZE)) {
|
||||
return false; // we can only program whole blocks
|
||||
}
|
||||
|
||||
// disable DATA (e.g. EEPROM) write protection
|
||||
// don't check if it is locked this it does not save that much time and uses memory)
|
||||
FLASH_DUKR = FLASH_DUKR_KEY1;
|
||||
FLASH_DUKR = FLASH_DUKR_KEY2;
|
||||
// don't verify if unlock succeeded to save memory
|
||||
// if (!(FLASH_IAPSR & FLASH_IAPSR_DUL)) { // un-protecting failed
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
// program data
|
||||
uint8_t* eeprom = (uint8_t*)(EEPROM_ADDR);
|
||||
while (length) {
|
||||
// enable standard block programming
|
||||
FLASH_CR2 |= FLASH_CR2_PRG;
|
||||
FLASH_NCR2 &= ~FLASH_NCR2_NPRG;
|
||||
// program block
|
||||
for (uint8_t i = 0; i < DATA_BLOCK_SIZE; i++) {
|
||||
*(eeprom++) = *(data++);
|
||||
}
|
||||
length -= DATA_BLOCK_SIZE;
|
||||
// wait until program completed
|
||||
while (FLASH_CR2 & FLASH_CR2_PRG);
|
||||
// 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
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
|
||||
return true;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/** library to program EEPROM using block programming
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2021
|
||||
* @warning functions need to be put in and run from RAM (because block programming is used)
|
||||
*/
|
||||
|
||||
/** program EEPROM using block programming
|
||||
* @param[in] data data to be programmed
|
||||
* @param[in] length length of data to be programmed (must be a multiple of the block length)
|
||||
* @return if program succeeded
|
||||
*/
|
||||
bool eeprom_blockprog(const uint8_t* data, uint16_t length);
|
469
i2c_master.c
469
i2c_master.c
|
@ -1,469 +0,0 @@
|
|||
/** library to communicate using I²C as master
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2017-2021
|
||||
* @note the I²C peripheral is not well specified and does not cover all cases. The following complexity is the best I could do to cope with it
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdbool.h> // boolean types
|
||||
#include <stdlib.h> // general utilities
|
||||
|
||||
/* own libraries */
|
||||
#include "stm8s.h" // STM8S definitions
|
||||
#include "i2c_master.h" // I²C header and definitions
|
||||
|
||||
bool i2c_master_setup(uint16_t freq_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
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
I2C_FREQR = 16; // the peripheral frequency (must match CPU frequency)
|
||||
if (freq_khz > 100) {
|
||||
uint16_t ccr = (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_t ccr = (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
|
||||
return true;
|
||||
}
|
||||
|
||||
void i2c_master_release(void)
|
||||
{
|
||||
I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral
|
||||
}
|
||||
|
||||
bool i2c_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 (volatile uint8_t t = 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 (volatile uint8_t t = 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
|
||||
}
|
||||
|
||||
void i2c_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
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_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
|
||||
return I2C_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
|
||||
if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (I2C_CR2 & I2C_CR2_STOP) {
|
||||
return I2C_MASTER_RC_TIMEOUT;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable I²C interrupts
|
||||
wfi(); // got to sleep to prevent EMI causing glitches
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
/** wait until stop is sent and bus is released
|
||||
* @return I²C return code
|
||||
*/
|
||||
static enum i2c_master_rc i2c_master_wait_stop(void)
|
||||
{
|
||||
I2C_SR2 = 0; // clear error flags
|
||||
while (I2C_CR2 & I2C_CR2_STOP) { // wait until stop condition is accepted and cleared
|
||||
if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
// there is no interrupt flag we can use here
|
||||
}
|
||||
// this time we can't use I2C_CR2_STOP to check for timeout
|
||||
if (I2C_SR3 & I2C_SR3_MSL) { // ensure we are not in master mode anymore
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
if (I2C_SR3 & I2C_SR3_BUSY) { // ensure bus is released
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
/*
|
||||
if (!i2c_master_check_signals()) { // ensure lines are released
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
*/
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_stop(void)
|
||||
{
|
||||
// sanity check
|
||||
if (!(I2C_SR3 & I2C_SR3_BUSY)) { // ensure bus is not already released
|
||||
return I2C_MASTER_RC_NONE; // bus has probably already been released
|
||||
}
|
||||
if (I2C_CR2 & (I2C_CR2_START | I2C_CR2_STOP)) { // ensure start or stop operations are not in progress
|
||||
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
|
||||
}
|
||||
|
||||
I2C_CR2 |= I2C_CR2_STOP; // send stop to release bus
|
||||
return i2c_master_wait_stop();
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write)
|
||||
{
|
||||
if (!(I2C_SR1 & I2C_SR1_SB)) { // start condition has not been sent yet
|
||||
enum i2c_master_rc rc = i2c_master_start(); // send start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
|
||||
// select slave
|
||||
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
|
||||
if (!address_10bit) { // 7-bit address
|
||||
I2C_DR = (slave << 1) | (write ? 0 : 1); // select slave, with read/write flag
|
||||
I2C_SR2 = 0; // clear error flags
|
||||
rim(); // enable interrupts
|
||||
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or error)
|
||||
if (I2C_CR2 & I2C_CR2_STOP) {
|
||||
return I2C_MASTER_RC_TIMEOUT;
|
||||
}
|
||||
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
} else if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
|
||||
wfi(); // got to sleep to prevent EMI causing glitches
|
||||
}
|
||||
} else { // 10-bit address
|
||||
// send first part of address
|
||||
I2C_DR = 11110000 | (((slave >> 8 ) & 0x3) << 1); // send first header (11110xx0, where xx are 2 MSb of slave address)
|
||||
I2C_SR2 = 0; // clear error flags
|
||||
rim(); // enable interrupts
|
||||
while (!(I2C_SR1 & I2C_SR1_ADD10)) { // wait until address is transmitted (or error)
|
||||
if (I2C_CR2 & I2C_CR2_STOP) {
|
||||
return I2C_MASTER_RC_TIMEOUT;
|
||||
}
|
||||
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
} else if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
|
||||
wfi(); // got to sleep to prevent EMI causing glitches
|
||||
}
|
||||
// send second part of address
|
||||
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
|
||||
I2C_DR = (slave & 0xff); // send remaining of address
|
||||
I2C_SR2 = 0; // clear error flags
|
||||
rim(); // enable interrupts
|
||||
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or error)
|
||||
if (I2C_CR2 & I2C_CR2_STOP) {
|
||||
return I2C_MASTER_RC_TIMEOUT;
|
||||
}
|
||||
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
} else if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
|
||||
wfi(); // got to sleep to prevent EMI causing glitches
|
||||
}
|
||||
// go into receive mode if necessary
|
||||
if (!write) {
|
||||
enum i2c_master_rc rc = i2c_master_start(); // send start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
// send first part of address with receive flag
|
||||
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
|
||||
I2C_DR = 11110001 | (((slave >> 8) & 0x3) << 1); // send header (11110xx1, where xx are 2 MSb of slave address)
|
||||
I2C_SR2 = 0; // clear error flags
|
||||
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address is transmitted (or error)
|
||||
if (I2C_CR2 & I2C_CR2_STOP) {
|
||||
return I2C_MASTER_RC_TIMEOUT;
|
||||
}
|
||||
if (I2C_SR2 & I2C_SR2_AF) { // address has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
} else if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable relevant I²C interrupts
|
||||
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
|
||||
// verify if we are in the right mode
|
||||
// final check
|
||||
if (write && !(I2C_SR3 & I2C_SR3_TRA)) {
|
||||
return I2C_MASTER_RC_NOT_TRANSMIT;
|
||||
} else if (!write && (I2C_SR3 & I2C_SR3_TRA)) {
|
||||
return I2C_MASTER_RC_NOT_RECEIVE;
|
||||
}
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_read(uint8_t* data, uint16_t data_size)
|
||||
{
|
||||
if (NULL == data || 0 == data_size) { // no data to read
|
||||
return I2C_MASTER_RC_OTHER; // we indicate an error because we don't send a stop
|
||||
}
|
||||
if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
// we can't check if the address phase it over since ADDR has been cleared when checking for mode
|
||||
if (I2C_SR3 & I2C_SR3_TRA) { // ensure we are in receive mode
|
||||
return I2C_MASTER_RC_NOT_RECEIVE;
|
||||
}
|
||||
|
||||
// read data
|
||||
I2C_CR2 |= I2C_CR2_ACK; // enable ACK by default
|
||||
I2C_SR2 = 0; // clear error flags
|
||||
rim(); // enable interrupts
|
||||
for (uint16_t i = 0; i < data_size; i++) { // read bytes
|
||||
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
// set (N)ACK (EV6_3, EV6_1)
|
||||
if (1 == (data_size - i)) { // prepare to sent NACK for last byte
|
||||
I2C_CR2 &= ~(I2C_CR2_ACK); // disable ACK
|
||||
I2C_CR2 |= I2C_CR2_STOP; // prepare to send the stop
|
||||
}
|
||||
rim(); // enable interrupts
|
||||
while (!(I2C_SR1 & I2C_SR1_RXNE)) { // wait until data is received (or error)
|
||||
if (I2C_SR2) { // an error occurred
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable all I²C interrupts
|
||||
wfi(); // got to sleep to prevent EMI causing glitches
|
||||
}
|
||||
data[i] = I2C_DR; // read the received byte
|
||||
}
|
||||
|
||||
return i2c_master_wait_stop();
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_write(const uint8_t* data, uint16_t data_size)
|
||||
{
|
||||
if (NULL == data || 0 == data_size) { // no data to read
|
||||
return I2C_MASTER_RC_NONE; // we don't indicate an error because the stop is done separately
|
||||
}
|
||||
if (!(I2C_SR3 & I2C_SR3_MSL)) { // I²C device is not in master mode
|
||||
return I2C_MASTER_RC_NOT_MASTER;
|
||||
}
|
||||
// we can't check if the address phase it over since ADDR has been cleared when checking for mode
|
||||
if (!(I2C_SR3 & I2C_SR3_TRA)) { // ensure we are in transmit mode
|
||||
return I2C_MASTER_RC_NOT_TRANSMIT;
|
||||
}
|
||||
|
||||
// write data
|
||||
for (uint16_t i = 0; i < data_size; i++) { // write bytes
|
||||
I2C_SR2 &= ~(I2C_SR2_AF); // clear acknowledgement failure
|
||||
(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
|
||||
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
if (I2C_CR2 & I2C_CR2_STOP) {
|
||||
return I2C_MASTER_RC_TIMEOUT;
|
||||
}
|
||||
if (I2C_SR2 & I2C_SR2_AF) { // data has not been acknowledged
|
||||
return I2C_MASTER_RC_NAK;
|
||||
} else if (I2C_SR2) {
|
||||
return I2C_MASTER_RC_BUS_ERROR;
|
||||
}
|
||||
I2C_ITR = (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN | I2C_ITR_ITERREN); // enable all I²C interrupts
|
||||
wfi(); // got to sleep to prevent EMI causing glitches
|
||||
}
|
||||
}
|
||||
|
||||
return I2C_MASTER_RC_NONE;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, uint16_t data_size)
|
||||
{
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
|
||||
rc = i2c_master_start(); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
if (NULL != data && data_size > 0) { // only read data if needed
|
||||
rc = i2c_master_read(data, data_size); // read data (includes stop)
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
i2c_master_stop(); // sent stop condition
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went well
|
||||
error:
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
i2c_master_stop(); // sent stop condition
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, uint16_t data_size)
|
||||
{
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
|
||||
rc = i2c_master_start(); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
if (NULL != data && data_size > 0) { // write data only is some is available
|
||||
rc = i2c_master_write(data, data_size); // write data
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went well
|
||||
error:
|
||||
i2c_master_stop(); // sent stop condition
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size)
|
||||
{
|
||||
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
|
||||
rc = i2c_master_start(); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// write address
|
||||
if (NULL != address && address_size > 0) {
|
||||
rc = i2c_master_write(address, address_size); // send memory address
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
// read data
|
||||
if (NULL != data && data_size > 0) {
|
||||
rc = i2c_master_start(); // send re-start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(slave, address_10bit, false); // select slave to read
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
rc = i2c_master_read(data, data_size); // read memory (includes stop)
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
i2c_master_stop(); // sent stop condition
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE;
|
||||
error:
|
||||
if (I2C_MASTER_RC_NONE != rc) { // only send stop on error
|
||||
i2c_master_stop(); // sent stop condition
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size)
|
||||
{
|
||||
if (UINT16_MAX - address_size < data_size) { // prevent integer overflow
|
||||
return I2C_MASTER_RC_OTHER;
|
||||
}
|
||||
if (address_size > 0 && NULL == address) {
|
||||
return I2C_MASTER_RC_OTHER;
|
||||
}
|
||||
if (data_size > 0 && NULL == data) {
|
||||
return I2C_MASTER_RC_OTHER;
|
||||
}
|
||||
|
||||
enum i2c_master_rc rc; // to store I²C return codes
|
||||
rc = i2c_master_start(); // send (re-)start condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
return rc;
|
||||
}
|
||||
rc = i2c_master_select_slave(slave, address_10bit, true); // select slave to write
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (address_size && address) {
|
||||
rc = i2c_master_write(address, address_size); // send memory address
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (data_size && data) {
|
||||
rc = i2c_master_write(data, data_size); // send memory data
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = i2c_master_stop(); // sent stop condition
|
||||
if (I2C_MASTER_RC_NONE != rc) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = I2C_MASTER_RC_NONE; // all went fine
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
void i2c_master_isr(void) __interrupt(IRQ_I2C) // I²C event or error happened
|
||||
{
|
||||
I2C_ITR = 0; // disable all interrupt sources to stop looping in ISR and let current loop check the right status flags
|
||||
}
|
117
i2c_master.h
117
i2c_master.h
|
@ -1,117 +0,0 @@
|
|||
/** library to communicate using I²C as master
|
||||
* @file
|
||||
* @author King Kévin <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2017-2021
|
||||
* @warning the I²C peripheral is very glitchy (sending random clock pulses), thus prefer the software implementation alternative, which is simpler, more flexible, smaller, and very stable (it just draws more energy)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/** I²C return codes */
|
||||
enum i2c_master_rc {
|
||||
I2C_MASTER_RC_NONE = 0, /**< no error */
|
||||
I2C_MASTER_RC_START_STOP_IN_PROGESS, /**< a start or stop condition is already in progress */
|
||||
I2C_MASTER_RC_NOT_MASTER, /**< not in master mode */
|
||||
I2C_MASTER_RC_NOT_TRANSMIT, /**< not in transmit mode */
|
||||
I2C_MASTER_RC_NOT_RECEIVE, /**< not in receive mode */
|
||||
I2C_MASTER_RC_NOT_READY, /**< slave is not read (previous operations has been NACKed) */
|
||||
I2C_MASTER_RC_NAK, /**< not acknowledge received */
|
||||
I2C_MASTER_RC_BUS_ERROR, /**< an error on the I²C bus occurred */
|
||||
I2C_MASTER_RC_TIMEOUT, /**< a timeout has occurred because an operation has not completed in the expected time */
|
||||
I2C_MASTER_RC_OTHER, /** any other error (does not have to be I²C related) */
|
||||
};
|
||||
|
||||
/** setup I²C peripheral
|
||||
* @param[in] freq_khz desired clock frequency, in kHz
|
||||
* @return if I²C bus is ready to be used (same as i2c_master_check_signals)
|
||||
*/
|
||||
bool i2c_master_setup(uint16_t freq_khz);
|
||||
/** release I²C peripheral */
|
||||
void i2c_master_release(void);
|
||||
/** reset I²C peripheral, fixing any locked state
|
||||
* @warning the I²C peripheral needs to be re-setup
|
||||
* @note to be used after failed start or stop, and bus error
|
||||
*/
|
||||
void i2c_master_reset(void);
|
||||
/** check if SDA and SCL signals are pulled high
|
||||
* @return if SDA and SCL signals are pulled high
|
||||
*/
|
||||
bool i2c_master_check_signals(void);
|
||||
/** send start condition
|
||||
* @return I2C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_start(void);
|
||||
/** select I²C slave device
|
||||
* @warning a start condition should be sent before this operation
|
||||
* @param[in] slave I²C address of slave device to select
|
||||
* @param[in] address_10bit if the I²C slave address is 10 bits wide
|
||||
* @param[in] write this transaction will be followed by a read (false) or write (true) operation
|
||||
* @return I²C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_select_slave(uint16_t slave, bool address_10bit, bool write);
|
||||
/** read data over I²C
|
||||
* @param[out] data array to store bytes read
|
||||
* @param[in] data_size number of bytes to read
|
||||
* @return I²C return code
|
||||
* @warning the slave device must be selected before this operation
|
||||
* @note a stop condition will be sent at the end (I²C does not permit multiple reads, and this is necessary for 1-byte transfer)
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_read(uint8_t* data, uint16_t data_size);
|
||||
/** write data over I²C
|
||||
* @param[in] data array of byte to write to slave
|
||||
* @param[in] data_size number of bytes to write
|
||||
* @return I²C return code
|
||||
* @warning the slave device must be selected before this operation
|
||||
* @note no stop condition is sent at the end, allowing multiple writes
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_write(const uint8_t* data, uint16_t data_size);
|
||||
/** sent stop condition
|
||||
* @param[in] i2c I²C base address
|
||||
* @return I²C return code
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_stop(void);
|
||||
/** read data from slave device
|
||||
* @param[in] slave I²C address of slave device to select
|
||||
* @param[in] address_10bit if the I²C slave address is 10 bits wide
|
||||
* @param[out] data array to store bytes read
|
||||
* @param[in] data_size number of bytes to read
|
||||
* @return I²C return code
|
||||
* @note start and stop conditions are included
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_slave_read(uint16_t slave, bool address_10bit, uint8_t* data, uint16_t data_size);
|
||||
/** write data to slave device
|
||||
* @param[in] slave I²C address of slave device to select
|
||||
* @param[in] address_10bit if the I²C slave address is 10 bits wide
|
||||
* @param[in] data array of byte to write to slave
|
||||
* @param[in] data_size number of bytes to write
|
||||
* @return I²C return code
|
||||
* @note start and stop conditions are included
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_slave_write(uint16_t slave, bool address_10bit, const uint8_t* data, uint16_t data_size);
|
||||
/** read data at specific address from an I²C memory slave
|
||||
* @param[in] slave I²C address of slave device to select
|
||||
* @param[in] address_10bit if the I²C slave address is 10 bits wide
|
||||
* @param[in] address memory address of slave to read from
|
||||
* @param[in] address_size address size in bytes
|
||||
* @param[out] data array to store bytes read
|
||||
* @param[in] data_size number of bytes to read
|
||||
* @return I²C return code
|
||||
* @note start and stop conditions are included
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_address_read(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size);
|
||||
/** write data at specific address on an I²C memory slave
|
||||
* @param[in] slave I²C address of slave device to select
|
||||
* @param[in] address_10bit if the I²C slave address is 10 bits wide
|
||||
* @param[in] address memory address of slave to write to
|
||||
* @param[in] address_size address size in bytes
|
||||
* @param[in] data array of byte to write to slave
|
||||
* @param[in] data_size number of bytes to write
|
||||
* @return I²C return code
|
||||
* @note start and stop conditions are included
|
||||
*/
|
||||
enum i2c_master_rc i2c_master_address_write(uint16_t slave, bool address_10bit, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size);
|
||||
/** interrupt service routine used to wake up
|
||||
* @note not sure why the declaration need to be in main for it to work
|
||||
*/
|
||||
void i2c_master_isr(void) __interrupt(IRQ_I2C);
|
||||
|
540
main.c
540
main.c
|
@ -1,6 +1,7 @@
|
|||
/* firmware template for STM8S microcontroller
|
||||
* Copyright (C) 2019-2020 King Kévin <kingkevin@cuvoodoo.info>
|
||||
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* for HDMI firewall v2.37
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
@ -8,8 +9,53 @@
|
|||
|
||||
#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)
|
||||
|
@ -18,6 +64,150 @@ static void wait_10us(uint32_t us10)
|
|||
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)
|
||||
|
@ -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)
|
||||
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
|
||||
|
@ -57,3 +549,49 @@ 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
static uint16_t period = 0;
|
||||
|
||||
// port for data line
|
||||
#define SDA_PORT GPIO_PB
|
||||
#define SDA_PORT GPIO_PD
|
||||
// pin for data line
|
||||
#define SDA_PIN PB5
|
||||
#define SDA_PIN PD2
|
||||
// port for clock line
|
||||
#define SCL_PORT GPIO_PB
|
||||
#define SCL_PORT GPIO_PD
|
||||
// pin for clock line
|
||||
#define SCL_PIN PB4
|
||||
#define SCL_PIN PD3
|
||||
// operation timeout (in half period)
|
||||
#define TIMEOUT 10U
|
||||
|
||||
|
@ -83,11 +83,11 @@ bool softi2c_master_setup(uint16_t freq_khz)
|
|||
|
||||
// switch pins to open drain
|
||||
SCL_PORT->ODR.reg |= SCL_PIN; // ensure clock is high
|
||||
SCL_PORT->DDR.reg |= SCL_PIN; // switch pin to output
|
||||
SCL_PORT->CR1.reg &= ~SCL_PIN; // use in open-drain mode
|
||||
SCL_PORT->DDR.reg |= SCL_PIN; // switch pin to output
|
||||
SDA_PORT->ODR.reg |= SDA_PIN; // ensure data is high
|
||||
SDA_PORT->DDR.reg |= SDA_PIN; // switch pin to output
|
||||
SDA_PORT->CR1.reg &= ~SDA_PIN; // use in open-drain mode
|
||||
SDA_PORT->DDR.reg |= SDA_PIN; // switch pin to output
|
||||
|
||||
I2C_delay(); // give time to get high
|
||||
return (read_SCL() && read_SDA()); // line is ready when the two lines are high
|
||||
|
|
Loading…
Reference in New Issue