Compare commits
12 Commits
master
...
i2c_hd4478
Author | SHA1 | Date |
---|---|---|
|
42be544bad | 11 months ago |
|
8022c73fc0 | 11 months ago |
|
f9db11e8e4 | 11 months ago |
|
785860a37e | 11 months ago |
|
d4478de954 | 11 months ago |
|
e2d789ccd0 | 11 months ago |
|
d37edd9e9d | 11 months ago |
|
1e446cbb0b | 11 months ago |
|
d3b5533f57 | 11 months ago |
|
13e8d3b5d4 | 11 months ago |
|
57c1be26d3 | 11 months ago |
|
be90d9a7d1 | 11 months ago |
@ -1,2 +1,24 @@ |
||||
firmware template for ST STM8S micro-controller. |
||||
includes register definitions using macros and structures. |
||||
STM8S003 firmware for the [I²C to HD44780 adapter](https://git.cuvoodoo.info/kingkevin/board/src/branch/i2c_hd44780). |
||||
|
||||
I²C commands (first byte). |
||||
|
||||
simplified commands: |
||||
|
||||
- 0x0 INIT: initialize LCD, as 2-line, 5x8 dots, no blinking or cursor |
||||
- 0x1 LINE1: write to line 1, followed by ASCII text |
||||
- 0x2 LINE2: write to line 2, followed by ASCII text |
||||
- 0x3 DISPLAY_ON: turn display on |
||||
- 0x4 DISPLAY_OFF: turn display off |
||||
- 0x5 BRIGHTNESS: set backlight brightness, followed by 0-255 value |
||||
|
||||
raw instructions, directly mapping to HD44780, followed by instruction byte or data bytes if applicable: |
||||
|
||||
- 0x6 CLEAR_DISPLAY |
||||
- 0x7 RETURN_HOME |
||||
- 0x8 ENTRY_MODE_SET |
||||
- 0x9 DISPLAY |
||||
- 0xa CURSOR_DISPLAY_SHIFT |
||||
- 0xb FUNCTION_SET |
||||
- 0xc CGRAM_ADDR |
||||
- 0xd DDRAM_ADDR |
||||
- 0xe DATA |
||||
|
@ -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); |
@ -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
|
||||
} |
@ -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); |
||||
|
@ -1,412 +0,0 @@ |
||||
/** library to communicate using I²C as master, implemented in software
|
||||
* @file |
||||
* @author King Kévin <kingkevin@cuvoodoo.info> |
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later |
||||
* @date 2021 |
||||
* @note I implemented I²C in software because the hardware peripheral is hard to use, and buggy (I was not able to get rid of clock glitches corrupting the communication, undetected) |
||||
* @note some methods copied from Wikipedia https://en.wikipedia.org/wiki/I%C2%B2C
|
||||
*/ |
||||
|
||||
/* 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 "softi2c_master.h" // software I²C header and definitions |
||||
|
||||
// half period to wait for I²C clock
|
||||
static uint16_t period = 0; |
||||
|
||||
// port for data line
|
||||
#define SDA_PORT GPIO_PB |
||||
// pin for data line
|
||||
#define SDA_PIN PB5 |
||||
// port for clock line
|
||||
#define SCL_PORT GPIO_PB |
||||
// pin for clock line
|
||||
#define SCL_PIN PB4 |
||||
// operation timeout (in half period)
|
||||
#define TIMEOUT 10U |
||||
|
||||
// delay for half a period
|
||||
static void I2C_delay(void) |
||||
{ |
||||
for (volatile uint16_t i = 0; i < period; i++); |
||||
} |
||||
|
||||
// Return current level of SCL line, 0 or 1
|
||||
static inline bool read_SCL(void) |
||||
{ |
||||
return (SCL_PORT->IDR.reg & SCL_PIN); |
||||
} |
||||
|
||||
// Return current level of SDA line, 0 or 1
|
||||
static inline bool read_SDA(void) |
||||
{ |
||||
return (SDA_PORT->IDR.reg & SDA_PIN); |
||||
} |
||||
|
||||
// Do not drive SCL (set pin high-impedance)
|
||||
static inline void set_SCL(void) |
||||
{ |
||||
SCL_PORT->ODR.reg |= SCL_PIN; |
||||
} |
||||
|
||||
// Actively drive SCL signal low
|
||||
static inline void clear_SCL(void) |
||||
{ |
||||
SCL_PORT->ODR.reg &= ~SCL_PIN; |
||||
} |
||||
|
||||
// Do not drive SDA (set pin high-impedance)
|
||||
static inline void set_SDA(void) |
||||
{ |
||||
SDA_PORT->ODR.reg |= SDA_PIN; |
||||
} |
||||
|
||||
// Actively drive SDA signal low
|
||||
static inline void clear_SDA(void) |
||||
{ |
||||
SDA_PORT->ODR.reg &= ~SDA_PIN; |
||||
} |
||||
|
||||
bool softi2c_master_setup(uint16_t freq_khz) |
||||
{ |
||||
// enforce minimal frequency
|
||||
if (0 == freq_khz) { |
||||
freq_khz = 1; |
||||
} |
||||
// calculated period from frequency (hand tuned value using 16 MHz clock)
|
||||
period = 589 / (1 << CLK->CKDIVR.fields.HSIDIV) / (1 << CLK->CKDIVR.fields.CPUDIV) / 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
|
||||
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
|
||||
|
||||
I2C_delay(); // give time to get high
|
||||
return (read_SCL() && read_SDA()); // line is ready when the two lines are high
|
||||
} |
||||
|
||||
void softi2c_master_release(void) |
||||
{ |
||||
SCL_PORT->DDR.reg &= ~SCL_PIN; // switch pin to input
|
||||
SDA_PORT->DDR.reg &= ~SDA_PIN; // switch pin to input
|
||||
} |
||||
|
||||
// if transaction has already started
|
||||
static bool started = false; |
||||
|
||||
bool softi2c_master_start(void) |
||||
{ |
||||
if (started) {
|
||||
// if started, do a restart condition
|
||||
// set SDA to 1
|
||||
set_SDA(); |
||||
I2C_delay(); |
||||
set_SCL(); |
||||
uint8_t timeout = TIMEOUT; |
||||
while (read_SCL() == 0 && timeout) { // Clock stretching
|
||||
I2C_delay(); |
||||
timeout--; |
||||
} |
||||
if (0 == timeout) { |
||||
return false; |
||||
} |
||||
|
||||
// Repeated start setup time, minimum 4.7us
|
||||
I2C_delay(); |
||||
} |
||||
|
||||
if (read_SDA() == 0) { |
||||
return false; |
||||
} |
||||
|
||||
// SCL is high, set SDA from 1 to 0.
|
||||
clear_SDA(); |
||||
I2C_delay(); |
||||
clear_SCL(); |
||||
started = true; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool softi2c_master_stop(void) |
||||
{ |
||||
// set SDA to 0
|
||||
clear_SDA(); |
||||
I2C_delay( |