Compare commits
12 Commits
master
...
i2c_hd4478
Author | SHA1 | Date |
---|---|---|
King Kévin | 42be544bad | |
King Kévin | 8022c73fc0 | |
King Kévin | f9db11e8e4 | |
King Kévin | 785860a37e | |
King Kévin | d4478de954 | |
King Kévin | e2d789ccd0 | |
King Kévin | d37edd9e9d | |
King Kévin | 1e446cbb0b | |
King Kévin | d3b5533f57 | |
King Kévin | 13e8d3b5d4 | |
King Kévin | 57c1be26d3 | |
King Kévin | be90d9a7d1 |
26
README.md
26
README.md
|
@ -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);
|
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);
|
||||
|
452
main.c
452
main.c
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
412
softi2c_master.c
412
softi2c_master.c
|
@ -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;
|
||||
}
|
|
@ -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);
|
Loading…
Reference in New Issue