Compare commits

...

12 Commits

8 changed files with 457 additions and 1185 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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
}

View File

@ -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);

452
main.c
View File

@ -1,30 +1,334 @@
/* firmware template for STM8S microcontroller
* Copyright (C) 2019-2020 King Kévin <kingkevin@cuvoodoo.info>
/* firmware to control LCD using HD44780 driver over I²C
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include "stm8s.h"
#include "main.h"
// nMOS gate to drive backlight LED
#define BL_PORT GPIO_PA
#define BL_PIN PA3
/* usual HD44780 pinout:
* - 1 GND: ground
* - 2 VCC: 5V (3.3V versions also exist, but a less common)
* - 3 V0 : LCD bias voltage, connect to 10-20k potentiometer (VCC to GND)
* - 4 RS : Register Select (high = data, low = instruction)
* - 5 R/W: Read/Write (high = read, low = write)
* - 6 E : Enable (falling edge to latch data, high to output register)
* - 7 DB0: Data Bit 0 (for 8-bit transfer)
* - 8 DB1: Data Bit 1 (for 8-bit transfer)
* - 9 DB2: Data Bit 2 (for 8-bit transfer)
* - 10 DB3: Data Bit 3 (for 8-bit transfer)
* - 11 DB4: Data Bit 4 (for 4-bit transfer)
* - 12 DB5: Data Bit 5 (for 4-bit transfer)
* - 13 DB6: Data Bit 6 (for 4-bit transfer)
* - 14 DB7: Data Bit 7 (for 4-bit transfer)
* - 15 BLA: Backlight Anode
* - 16 BLK: Backlight Cathode
*
* we use 4-bit mode since we are fast enough to send the whole data while receiving a byte, and this saves 4 I/Os
*/
#define HD44780_RS_PORT GPIO_PC
#define HD44780_RS_PIN PC3
#define HD44780_RW_PORT GPIO_PC
#define HD44780_RW_PIN PC4
#define HD44780_E_PORT GPIO_PC
#define HD44780_E_PIN PC5
#define HD44780_DB4_PORT GPIO_PC
#define HD44780_DB4_PIN PC6
#define HD44780_DB5_PORT GPIO_PC
#define HD44780_DB5_PIN PC7
#define HD44780_DB6_PORT GPIO_PD
#define HD44780_DB6_PIN PD2
#define HD44780_DB7_PORT GPIO_PD
#define HD44780_DB7_PIN PD3
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
// the I²C address of this slave
static uint8_t i2c_addr = 0x47U;
#define A0_PORT GPIO_PD
#define A0_PIN PD4
#define A1_PORT GPIO_PD
#define A1_PIN PD5
#define A2_PORT GPIO_PD
#define A2_PIN PD6
// the functions we can call over I²C
enum i2c_mode_t {
// custom modes
MODE_INIT, // initialise HD44780
MODE_LINE1, // write to line 1
MODE_LINE2, // write to line 2
MODE_DISPLAY_ON, // turn display on
MODE_DISPLAY_OFF, // turn display off
MODE_BRIGHTNESS, // set backlight brightness
// raw instructions, directly mapping to HD44780
MODE_CLEAR_DISPLAY,
MODE_RETURN_HOME,
MODE_ENTRY_MODE_SET,
MODE_DISPLAY,
MODE_CURSOR_DISPLAY_SHIFT,
MODE_FUNCTION_SET,
MODE_CGRAM_ADDR,
MODE_DDRAM_ADDR,
MODE_DATA,
MODE_COUNT, // number of modes
};
static enum i2c_mode_t i2c_mode = MODE_COUNT; // set invalid value
/* actually we are fast enough to process bytes as they are received, even in 4-bit mode, except when the clear display instruction is used */
static volatile uint8_t i2c_input_buffer[2 * 20 + 1] = {0}; /**< ring buffer for received data (enough for two lines) */
static volatile uint8_t i2c_input_i = 0; /**< current position of read received data */
static volatile uint8_t i2c_input_used = 0; /**< how much data has been received and not read */
static volatile bool i2c_input_new = false; /**< if a transaction with new data started */
/**
* @warning only up to UINT32_MAX / 10 to be safe
*/
static void wait_10us(uint32_t us10)
{
us10 = ((us10 / (1 << CLK->CKDIVR.fields.HSIDIV)) * 1000) / 206; // calibrated for 1 ms
while (us10--); // burn energy
}
static void hd44780_data_direction(bool read)
{
if (read) { // switch data pins to input, with pull-up (should already be on the LCD module)
HD44780_DB7_PORT->DDR.reg &= ~HD44780_DB7_PIN; // switch data pins to input
HD44780_DB6_PORT->DDR.reg &= ~HD44780_DB6_PIN; // switch data pins to input
HD44780_DB5_PORT->DDR.reg &= ~HD44780_DB5_PIN; // switch data pins to input
HD44780_DB4_PORT->DDR.reg &= ~HD44780_DB4_PIN; // switch data pins to input
HD44780_RW_PORT->ODR.reg |= HD44780_RW_PIN; // set high to read
while (!(HD44780_RW_PORT->IDR.reg & HD44780_RW_PIN)); // wait for RW to be high
} else {
HD44780_RW_PORT->ODR.reg &= ~HD44780_RW_PIN; // set low to write
HD44780_DB7_PORT->DDR.reg |= HD44780_DB7_PIN; // switch data pins to output
HD44780_DB6_PORT->DDR.reg |= HD44780_DB6_PIN; // switch data pins to output
HD44780_DB5_PORT->DDR.reg |= HD44780_DB5_PIN; // switch data pins to output
HD44780_DB4_PORT->DDR.reg |= HD44780_DB4_PIN; // switch data pins to output
while ((HD44780_RW_PORT->IDR.reg & HD44780_RW_PIN)); // wait for RW to be low
}
}
/**
* @note the direction and instruction/data should already be set
*/
static void hd44780_write_nibble(uint8_t nibble)
{
HD44780_E_PORT->ODR.reg |= HD44780_E_PIN; // set enable high so we can change the data
// set I/O according to nibble
if (nibble & 0x1) {
HD44780_DB4_PORT->ODR.reg |= HD44780_DB4_PIN;
} else {
HD44780_DB4_PORT->ODR.reg &= ~HD44780_DB4_PIN;
}
if (nibble & 0x2) {
HD44780_DB5_PORT->ODR.reg |= HD44780_DB5_PIN;
} else {
HD44780_DB5_PORT->ODR.reg &= ~HD44780_DB5_PIN;
}
if (nibble & 0x4) {
HD44780_DB6_PORT->ODR.reg |= HD44780_DB6_PIN;
} else {
HD44780_DB6_PORT->ODR.reg &= ~HD44780_DB6_PIN;
}
if (nibble & 0x8) {
HD44780_DB7_PORT->ODR.reg |= HD44780_DB7_PIN;
} else {
HD44780_DB7_PORT->ODR.reg &= ~HD44780_DB7_PIN;
}
// wait t_DSW = 195 ns or PW_EH = 450 ns
HD44780_E_PORT->ODR.reg &= ~HD44780_E_PIN; // set enable low to latch data
// no need to wait t_H = 10 ns before next step since next instructions are slower
}
/**
* @note the direction and instruction/data should already be set
*/
static uint8_t hd44780_read_nibble(void)
{
HD44780_E_PORT->ODR.reg |= HD44780_E_PIN; // set enable to have data output
// wait t_DDR = 360 ns for the data to be output
uint8_t nibble = 0;
if (HD44780_DB7_PORT->IDR.reg & HD44780_DB7_PIN) {
nibble |= 0x8;
}
if (HD44780_DB6_PORT->IDR.reg & HD44780_DB6_PIN) {
nibble |= 0x4;
}
if (HD44780_DB5_PORT->IDR.reg & HD44780_DB5_PIN) {
nibble |= 0x2;
}
if (HD44780_DB4_PORT->IDR.reg & HD44780_DB4_PIN) {
nibble |= 0x1;
}
// no need to wait PW_EH = 450 ns
HD44780_E_PORT->ODR.reg &= ~HD44780_E_PIN; // set enable low end read
return nibble;
}
static uint8_t hd44780_read_byte(void)
{
hd44780_data_direction(true); // switch to read direction
uint8_t data = (hd44780_read_nibble() << 4); // get first nibble
// no need to wait t_cycE = 500 ns before next write
data |= hd44780_read_nibble(); // get second nibble
// no need to wait tAS = 40 ns before next step since the instructions are slower
return data;
}
static uint8_t hd44780_read_bfac(void)
{
HD44780_RS_PORT->ODR.reg &= ~HD44780_RS_PIN; // set low for instruction
return hd44780_read_byte();
}
/**
* @note instruction/data should already be set
*/
static void hd44780_write_byte(uint8_t data)
{
hd44780_data_direction(false); // switch to write direction
// no need to wait tAS = 40 ns before next step since the instructions are slower
hd44780_write_nibble(data >> 4); // send first nibble
// no need to wait t_cycE = 500 ns before next write
hd44780_write_nibble(data); // send second nibble
// no need to wait t_cycE = 500 ns before next write
}
static void hd44780_write_instruction(uint8_t instruction)
{
while (hd44780_read_bfac() & 0x80); // wait until busy flag is cleared
HD44780_RS_PORT->ODR.reg &= ~HD44780_RS_PIN; // set low for instruction
hd44780_write_byte(instruction);
}
static void hd44780_write_data(uint8_t data)
{
while (hd44780_read_bfac() & 0x80); // wait until busy flag is cleared
HD44780_RS_PORT->ODR.reg |= HD44780_RS_PIN; // set high for data
hd44780_write_byte(data);
}
static void hd44780_init(void)
{
// configure display (as per datasheet)
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
HD44780_RS_PORT->ODR.reg &= ~HD44780_RS_PIN; // set low for instruction
hd44780_data_direction(false); // switch to write direction
wait_10us(4000 + 1000); // wait 40 ms after power up
hd44780_write_nibble(3); // 1st function write set to go to state 1 (8-bit) or 2 (4-bit first nibble) (BF cannot be checked)
wait_10us(410 + 100); // wait 4.1 ms
hd44780_write_nibble(3); // 2st function write set to go to state 1 (8-bit) or 3 (4-bit second nibble) (BF cannot be checked)
wait_10us(10 + 1); // wait 100 us
hd44780_write_nibble(3); // 3rd function write set to go to state 1 (8-bit) (BF cannot be checked)
wait_10us(4 + 1); // wait 37 us
hd44780_write_nibble(2); // switch to 4-bit mode
wait_10us(4 + 1); // wait 37 us (BF could be checked at this point)
// we are now for sure in 8-bit more (and could switch do 4-bit). 8-bit mode is actually the default after power up
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
hd44780_write_instruction(0x28); // function set (DL=1: 4-bit mode, N=1: 2 lines, F=0: 5x8 dots)
hd44780_write_instruction(0x01); // display clear
hd44780_write_instruction(0x06); // entry mode set
hd44780_write_instruction(0x02); // return home
hd44780_write_instruction(0x0c); // display on
}
void main(void)
{
sim(); // disable interrupts (while we reconfigure them)
CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide internal 16 MHz clock
CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency to 16 MHz
while (!CLK->ICKR.fields.HSIRDY); // wait for internal oscillator to be ready
CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide high speed internal (HSI) 16 MHz clock, we need it to process the data fast enough
// HSI clock is used for as master clock per default
CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency for now (will be master clock)
while (!(CLK_ICKR & CLK_ICKR_HSIRDY)); // wait for internal oscillator to be ready
// save power by disabling unused peripheral
CLK_PCKENR1 = CLK_PCKENR1_I2C | CLK_PCKENR1_TIM25; // only keep I²C and timer 2
CLK_PCKENR2 = CLK_PCKENR2_AWU; // only keep AWU
// configure independent watchdog (very loose, just it case the firmware hangs)
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
IWDG_KR = IWDG_KR_KEY_ENABLE; // start watchdog
IWDG_KR = IWDG_KR_KEY_ACCESS; // allows changing the prescale
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
/* configure HD44780 pins
* the display is a lot more stable the operated in push-pull mode
* but they can also be operated in open-drain mode (as does the PCF8574),
* since all pins are pulled up by the HD44780 , except E which requires an external pull-up resistor (~1 kO).
* when operated in open-drain mode, wait the recommended maximum operation times since reading is error prone
*/
HD44780_RS_PORT->DDR.reg |= HD44780_RS_PIN; // switch pin to output
HD44780_RS_PORT->CR1.reg &= ~HD44780_RS_PIN; // use in open-drain mode
HD44780_RS_PORT->CR1.reg |= HD44780_RS_PIN; // use in push-pull mode
HD44780_RW_PORT->DDR.reg |= HD44780_RW_PIN; // switch pin to output
HD44780_RW_PORT->CR1.reg &= ~HD44780_RW_PIN; // use in open-drain mode
HD44780_RW_PORT->CR1.reg |= HD44780_RW_PIN; // use in push-pull mode
HD44780_E_PORT->DDR.reg |= HD44780_E_PIN; // switch pin to output
HD44780_E_PORT->CR1.reg |= HD44780_E_PIN; // use in push-pull mode
HD44780_E_PORT->ODR.reg &= ~HD44780_E_PIN; // start idle low
HD44780_DB7_PORT->DDR.reg |= HD44780_DB7_PIN; // switch pin to output
HD44780_DB7_PORT->CR1.reg &= ~HD44780_DB7_PIN; // use in open-drain mode
HD44780_DB7_PORT->CR1.reg |= HD44780_DB7_PIN; // use in push-pull mode
HD44780_DB6_PORT->DDR.reg |= HD44780_DB6_PIN; // switch pin to output
HD44780_DB6_PORT->CR1.reg &= ~HD44780_DB6_PIN; // use in open-drain mode
HD44780_DB6_PORT->CR1.reg |= HD44780_DB6_PIN; // use in push-pull mode
HD44780_DB5_PORT->DDR.reg |= HD44780_DB5_PIN; // switch pin to output
HD44780_DB5_PORT->CR1.reg &= ~HD44780_DB5_PIN; // use in open-drain mode
HD44780_DB5_PORT->CR1.reg |= HD44780_DB5_PIN; // use in push-pull mode
HD44780_DB4_PORT->DDR.reg |= HD44780_DB4_PIN; // switch pin to output
HD44780_DB4_PORT->CR1.reg &= ~HD44780_DB4_PIN; // use in open-drain mode
HD44780_DB4_PORT->CR1.reg |= HD44780_DB4_PIN; // use in push-pull mode
hd44780_data_direction(false); // configure pins as output
// read I²C address bits
A0_PORT->DDR.reg &= ~A0_PIN; // switch pin to input
A0_PORT->CR1.reg |= A0_PIN; // enable pull-up
if (A0_PORT->IDR.reg & A0_PIN) {
i2c_addr |= 0x01;
} else {
i2c_addr &= ~0x01;
}
A1_PORT->DDR.reg &= ~A1_PIN; // switch pin to input
A1_PORT->CR1.reg |= A1_PIN; // enable pull-up
if (A1_PORT->IDR.reg & A1_PIN) {
i2c_addr |= 0x02;
} else {
i2c_addr &= ~0x02;
}
A2_PORT->DDR.reg &= ~A2_PIN; // switch pin to input
A2_PORT->CR1.reg |= A2_PIN; // enable pull-up
if (A2_PORT->IDR.reg & A2_PIN) {
i2c_addr |= 0x04;
} else {
i2c_addr &= ~0x04;
}
// configure I²C
GPIO_PB->CR1.reg |= (PB4 | PB5); // enable internal pull-up on SCL/SDA
GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral
I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral (must be done before any other register is written)
I2C_CR2 |= I2C_CR2_STOP; // release lines
I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit
while (0 == (GPIO_PB->IDR.reg & PB4)); // wait for SCL line to be released
while (0 == (GPIO_PB->IDR.reg & PB5)); // wait for SDA line to be released
I2C_CR2 &= ~I2C_CR2_SWRST; // release reset
I2C_CR1 |= I2C_CR1_ENGC; // enable general call
I2C_CR1 |= I2C_CR1_PE; // re-enable I²C peripheral
I2C_FREQR = 16; // the peripheral frequency is 4 MHz (must match CPU frequency)
//I2C_CR1 |= I2C_CR1_ENGC; // enable general call (I was not able to have slave select with address 0x00 ACKed)
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 = (i2c_addr << 1U); // set slave address
I2C_ITR |= (I2C_ITR_ITBUFEN | I2C_ITR_ITEVTEN); // enable buffer and event interrupts
// configure auto-wakeup (AWU) to be able to refresh the watchdog
// 128 kHz LSI used by default in option bytes CKAWUSEL
@ -33,22 +337,99 @@ void main(void)
AWU->APR.fields.APR = 0x3e; // set time to 256 ms
AWU_CSR |= AWU_CSR_AWUEN; // enable AWU (start only when entering wait or active halt mode)
// configure independent watchdog (very loose, just it case the firmware hangs)
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
IWDG->KR.fields.KEY = IWDG_KR_KEY_ENABLE; // start watchdog
IWDG->KR.fields.KEY = IWDG_KR_KEY_ACCESS; // allows changing the prescale
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
// configure PWM output for backlight
BL_PORT->DDR.reg |= BL_PIN; // switch pin to output
BL_PORT->CR1.reg |= BL_PIN; // use in push-pull mode
TIM2->CCMR3.output_fields.OC3M = 6; // PWM mode 1
TIM2->CCMR3.output_fields.CC3S = 0; // configure channel as output
TIM2->CCER2.fields.CC3P = 0; // active high
TIM2->CCER2.fields.CC3E = 1; // output enable
TIM2->ARRH.fields.ARR15_8 = 0; // set reload to 0xff
TIM2->ARRL.fields.ARR7_0 = 0xfe; // set reload to 0xff
TIM2->PSCR.fields.PSC = 6; // set frequency to 1 kHz
TIM2->CCR3H.fields.CCR3 = 0; // start with PWM off
TIM2->CCR3L.fields.CCR3 = 0; // start with PWM off
TIM2->EGR.fields.UG = 1; // force reload of values
TIM2->CR1.fields.CEN = 1; // enable timer
hd44780_init(); // initialise display
rim(); // re-enable interrupts
bool action = false; // if an action has been performed
while (true) {
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
if (action) { // something has been performed, check if other flags have been set meanwhile
action = false; // clear flag
} else { // nothing down
wfi(); // go to sleep (wait for any interrupt, including periodic AWU)
while (i2c_input_used) { // I²C data is available
sim(); // disable interrupt while reading buffer to not corrupt indexes
uint8_t input_data = i2c_input_buffer[i2c_input_i]; // get start buffered data
i2c_input_i = (i2c_input_i + 1) % ARRAY_LENGTH(i2c_input_buffer); // update used buffer
i2c_input_used--; // update used buffer
rim(); // re-enable interrupts
if (i2c_input_new) { // this is the start of a transaction, the first byte indicates the mode to be used
i2c_mode = input_data; // set user provided mode (no need to check since the undefined modes don't do anything)
i2c_input_new = false; // clear flag
// process mode switch
switch (i2c_mode) {
case MODE_INIT: // re-initialise display
hd44780_init();
break;
case MODE_CLEAR_DISPLAY: // clear display
hd44780_write_instruction(0x01); // clear display instruction
break;
case MODE_LINE1:
hd44780_write_instruction(0x80); // set DDRAM address to 0 (line 1)
break;
case MODE_LINE2:
hd44780_write_instruction(0xc0); // set DDRAM address to 0x40 (line 2)
break;
case MODE_DISPLAY_ON:
hd44780_write_instruction(0x0c); // display on
break;
case MODE_DISPLAY_OFF:
hd44780_write_instruction(0x08); // display off
break;
case MODE_RETURN_HOME:
hd44780_write_instruction(0x02); // return home
break;
default:
break; // waiting for data
}
} else {
// process data
// note set RS at every byte since read busy always switches it to instruction
switch (i2c_mode) {
case MODE_LINE1:
case MODE_LINE2:
case MODE_DATA:
hd44780_write_data(input_data);
break;
case MODE_ENTRY_MODE_SET:
hd44780_write_instruction(0x04 | (input_data & 0x3));
break;
case MODE_DISPLAY:
hd44780_write_instruction(0x08 | (input_data & 0x7));
break;
case MODE_CURSOR_DISPLAY_SHIFT:
hd44780_write_instruction(0x10 | (input_data & 0xc));
break;
case MODE_FUNCTION_SET:
hd44780_write_instruction(0x20 | (input_data & 0xc)); // keep DL=0 4-bit mode
break;
case MODE_CGRAM_ADDR:
hd44780_write_instruction(0x40 | (input_data & 0x3f));
break;
case MODE_DDRAM_ADDR:
hd44780_write_instruction(0x80 | (input_data & 0x3f));
break;
case MODE_BRIGHTNESS:
TIM2->CCR3L.fields.CCR3 = input_data; // set duty cycle
break;
default: // read values do not make sense
break;
}
}
}
//for (volatile uint32_t wait = 0; wait < 100000; wait++);
wfi(); // go to wait mode (halt would prevent slave select to be acknowledged)
}
}
@ -57,3 +438,36 @@ 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
uint8_t sr1 = I2C_SR1;
uint8_t sr2 = I2C_SR2;
uint8_t sr3 = I2C_SR3;
if (sr1 & I2C_SR1_TXE) { // transmission buffer is empty
I2C_DR = 0xff; // read is not a valid command, return anything
}
if (sr1 & I2C_SR1_RXNE) { // receive buffer is full
uint8_t data = I2C_DR; // read data (also clears flag);
if (i2c_input_used < ARRAY_LENGTH(i2c_input_buffer)) { // only store when there is place, else drop new data
i2c_input_buffer[(i2c_input_i + i2c_input_used) % ARRAY_LENGTH(i2c_input_buffer)] = data; // save new data
i2c_input_used++; // update used buffer
}
}
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
if (sr3 & I2C_SR3_TRA) { // we will have to transmit data
} else { // we will receive data
i2c_input_new = true; // informs a new transaction started
}
}
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
}
}

View File

@ -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();
set_SCL();
uint8_t timeout = TIMEOUT;
while (read_SCL() == 0 && timeout) { // Clock stretching
I2C_delay();
timeout--;
}
if (0 == timeout) {
return false;
}
I2C_delay(); // Stop bit setup time, minimum 4us
// SCL is high, set SDA from 0 to 1
set_SDA();
I2C_delay();
if (read_SDA() == 0) {
return false;
}
started = false;
return true;
}
// Write a bit to I²C bus
static bool softi2c_master_write_bit(bool bit) {
// set data bit
if (bit) {
set_SDA();
} else {
clear_SDA();
}
I2C_delay(); // SDA change propagation delay
set_SCL(); // Set SCL high to indicate a new valid SDA value is available
I2C_delay(); // Wait for SDA value to be read by slave, minimum of 4us for standard mode
uint8_t timeout = TIMEOUT;
while (read_SCL() == 0 && timeout) { // Clock stretching
I2C_delay();
timeout--;
}
if (0 == timeout) {
return false;
}
// SCL is high, now data is valid
if (bit && (read_SDA() == 0)) { // If SDA is high, check that nobody else is driving SDA
return false;
}
clear_SCL(); // Clear the SCL to low in preparation for next change
return true;
}
// Read a bit from I²C bus
static bool softi2c_master_read_bit(void) {
set_SDA(); // Let the slave drive data
I2C_delay(); // Wait for SDA value to be written by slave, minimum of 4us for standard mode
set_SCL(); // Set SCL high to indicate a new valid SDA value is available
uint8_t timeout = TIMEOUT;
while (read_SCL() == 0 && timeout) { // Clock stretching
I2C_delay();
timeout--;
}
if (0 == timeout) {
return false;
}
I2C_delay(); // Wait for SDA value to be written by slave, minimum of 4us for standard mode
const bool bit = read_SDA(); // SCL is high, read out bit
clear_SCL(); // Set SCL low in preparation for next operation
return bit;
}
// Write a byte to I2C bus. Return true if ACK by the slave.
static bool softi2c_master_write_byte(uint8_t byte)
{
for (uint8_t bit = 0; bit < 8; ++bit) {
softi2c_master_write_bit((byte & 0x80) != 0);
byte <<= 1;
}
const bool nack = softi2c_master_read_bit();
return !nack;
}
// Read a byte from I²C bus
static uint8_t softi2c_master_read_byte(bool nack)
{
uint8_t byte = 0;
for (uint8_t bit = 0; bit < 8; ++bit) {
byte = (byte << 1) | softi2c_master_read_bit();
}
softi2c_master_write_bit(nack);
return byte;
}
bool softi2c_master_select_slave(uint8_t slave, bool write)
{
if (!softi2c_master_start()) { // send (re-)start condition
return false;
}
const uint8_t byte = (slave << 1) | (write ? 0 : 1); // select slave, with read/write flag
return softi2c_master_write_byte(byte); // select slave
}
bool softi2c_master_read(uint8_t* data, uint16_t data_size)
{
if (NULL == data || 0 == data_size) { // no data to read
return false;
}
for (uint16_t i = 0; i < data_size; i++) { // read bytes
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
if (1 == (data_size - i)) { // last byte
data[i] = softi2c_master_read_byte(true); // NACK after reading byte
} else {
data[i] = softi2c_master_read_byte(false); // ACK after reading byte
}
}
return softi2c_master_stop();
}
bool softi2c_master_write(const uint8_t* data, uint16_t data_size)
{
if (NULL == data || 0 == data_size) { // no data to read
return true; // we don't indicate an error because the stop is done separately
}
// write data
for (uint16_t i = 0; i < data_size; i++) { // write bytes
if (!softi2c_master_write_byte(data[i])) { // write byte
return false;
}
}
return true;
}
bool softi2c_master_slave_read(uint8_t slave, uint8_t* data, uint16_t data_size)
{
if (NULL == data && data_size > 0) { // no data to read
return false;
}
if (!softi2c_master_select_slave(slave, false)) { // select slave to read
softi2c_master_stop();
return false;
}
if (NULL != data && data_size > 0) { // only read data if needed
if (!softi2c_master_read(data, data_size)) { // read data (includes stop)
return false;
}
}
return true;
}
bool softi2c_master_slave_write(uint8_t slave, const uint8_t* data, uint16_t data_size)
{
if (NULL == data && data_size > 0) { // no data to read
return false;
}
bool rc = false;
if (!softi2c_master_select_slave(slave, true)) { // select slave to write
goto error;
}
if (NULL != data && data_size > 0) { // write data only is some is available
if (!softi2c_master_write(data, data_size)) { // write data
goto error;
}
}
rc = true; // all went well
error:
rc = softi2c_master_stop() && rc; // sent stop condition
return rc;
}
bool softi2c_master_address_read(uint8_t slave, const uint8_t* address, uint16_t address_size, uint8_t* data, uint16_t data_size)
{
if (address_size > 0 && NULL == address) {
return false;
}
if (data_size > 0 && NULL == data) {
return false;
}
bool rc = false;
rc = softi2c_master_select_slave(slave, true); // select slave to write
if (!rc) {
goto error;
}
// write address
if (NULL != address && address_size > 0) {
rc = softi2c_master_write(address, address_size); // send memory address
if (!rc) {
goto error;
}
}
// read data
if (NULL != data && data_size > 0) {
rc = softi2c_master_select_slave(slave, false); // re-select slave to read
if (!rc) {
goto error;
}
rc = softi2c_master_read(data, data_size); // read memory (includes stop)
if (!rc) {
goto error;
}
} else {
softi2c_master_stop(); // sent stop condition
}
rc = true;
error:
if (!rc) { // only send stop on error
softi2c_master_stop(); // sent stop condition
}
return rc;
}
bool softi2c_master_address_write(uint8_t slave, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size)
{
if (address_size > 0 && NULL == address) {
return false;
}
if (data_size > 0 && NULL == data) {
return false;
}
bool rc = false;
rc = softi2c_master_select_slave(slave, true); // select slave to write
if (!rc) {
goto error;
}
if (address_size && address) {
rc = softi2c_master_write(address, address_size); // send memory address
if (!rc) {
goto error;
}
}
if (data_size && data) {
rc = softi2c_master_write(data, data_size); // send memory data
if (!rc) {
goto error;
}
}
rc = softi2c_master_stop(); // sent stop condition
if (!rc) {
return false;
}
rc = true; // all went fine
error:
if (!rc) {
softi2c_master_stop(); // send stop on error
}
return rc;
}

View File

@ -1,83 +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
*/
#pragma once
/** setup I²C peripheral
* @param[in] freq_khz desired clock frequency, in kHz
* @return if I²C bus is ready
*/
bool softi2c_master_setup(uint16_t freq_khz);
/** release I²C peripheral */
void softi2c_master_release(void);
/** send start condition
* @return if start sent (else arbitration lost)
*/
bool softi2c_master_start(void);
/** sent stop condition
* @param[in] i2c I²C base address
* @return if stop sent (else arbitration lost)
*/
bool softi2c_master_stop(void);
/** select I²C slave device
* @param[in] slave I²C address of slave device to select
* @param[in] write this transaction will be followed by a read (false) or write (true) operation
* @return if slave ACKed
* @note includes (re-)start condition
*/
bool softi2c_master_select_slave(uint8_t slave, 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 if read succeeded (else arbitration lost)
* @warning the slave device must be selected before this operation
* @note includes sending stop (after having NACKed last received byte)
*/
bool softi2c_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 if write succeeded (else data has been NACKed)
* @warning the slave device must be selected before this operation
* @note no stop condition is sent at the end, allowing multiple writes
*/
bool softi2c_master_write(const uint8_t* data, uint16_t data_size);
/** read data from slave device
* @param[in] slave I²C address of slave device to select
* @param[out] data array to store bytes read
* @param[in] data_size number of bytes to read
* @return if read succeeded (else arbitration has been lost)
* @note start and stop conditions are included
*/
bool softi2c_master_slave_read(uint8_t slave, 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] data array of byte to write to slave
* @param[in] data_size number of bytes to write
* @return if write succeeded
* @note start and stop conditions are included
*/
bool softi2c_master_slave_write(uint8_t slave, 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 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 if read succeeded
* @note start and stop conditions are included
*/
bool softi2c_master_address_read(uint8_t slave, 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 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 if write succeeded
* @note start and stop conditions are included
*/
bool softi2c_master_address_write(uint8_t slave, const uint8_t* address, uint16_t address_size, const uint8_t* data, uint16_t data_size);