Compare commits
29 Commits
master
...
bb00e9c67d
Author | SHA1 | Date |
---|---|---|
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)
|
||||
|
|
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);
|
||||
|
499
main.c
499
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,47 @@
|
|||
|
||||
#include "stm8s.h"
|
||||
#include "main.h"
|
||||
#include "softi2c_master.h"
|
||||
|
||||
// enable UART debug
|
||||
#define DEBUG 0
|
||||
|
||||
#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 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)
|
||||
|
@ -18,6 +58,148 @@ 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 + (1 + edid[126]) * 128; // get length with all extensions
|
||||
puts("(v1.4 ");
|
||||
putn(edid[126] / 100);
|
||||
putn((edid[126] / 10) % 10);
|
||||
putn(edid[126] % 10);
|
||||
puts(" extension) ");
|
||||
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 +208,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 +227,273 @@ 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 I²C read fail
|
||||
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 (action) { // something has been performed, check if other flags have been set meanwhile
|
||||
action = false; // clear flag
|
||||
} else { // nothing down
|
||||
|
@ -57,3 +507,50 @@ 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
|
||||
// transmit next byte
|
||||
if (eeprom_valid) {
|
||||
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++);
|
||||
} else {
|
||||
I2C_DR = 0xff;
|
||||
}
|
||||
}
|
||||
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
|
||||
if (eeprom_valid) {
|
||||
I2C_DR = *(uint8_t*)(EEPROM_ADDR + addr++); // transmit selected byte
|
||||
} else {
|
||||
I2C_DR = 0xff;
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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