Compare commits
34 Commits
master
...
dachboden_
Author | SHA1 | Date |
---|---|---|
King Kévin | b9fa7d77d0 | |
King Kévin | 74312ca428 | |
King Kévin | b4d28e0ee5 | |
King Kévin | d66bdff1d5 | |
King Kévin | ae2711366f | |
King Kévin | c7f6a3ae38 | |
King Kévin | 9b16b17628 | |
King Kévin | 9696a136f2 | |
King Kévin | ad3b844978 | |
King Kévin | 1ea913a058 | |
King Kévin | 90925ea959 | |
King Kévin | eaa3447fad | |
King Kévin | 7121f8b541 | |
King Kévin | 9fddbd4449 | |
King Kévin | fb2dda3fb3 | |
King Kévin | 2de071edb2 | |
King Kévin | b689c8d94c | |
King Kévin | 02e5fbdbf5 | |
King Kévin | e813e6bf46 | |
King Kévin | 536ca1f755 | |
King Kévin | b493d982ad | |
King Kévin | 058ab29990 | |
King Kévin | 68a096be7b | |
King Kévin | d54b5f1d26 | |
King Kévin | 0e7914ad9d | |
King Kévin | 21067f5f4c | |
King Kévin | 75fb553172 | |
King Kévin | 73d6c4917d | |
King Kévin | eed88beebe | |
King Kévin | cc63108403 | |
King Kévin | c724a5b477 | |
King Kévin | 8535a738de | |
King Kévin | a5d5844237 | |
King Kévin | 53c9af3ea7 |
|
@ -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);
|
||||
|
606
main.c
606
main.c
|
@ -1,5 +1,5 @@
|
|||
/* firmware template for STM8S microcontroller
|
||||
* Copyright (C) 2019-2020 King Kévin <kingkevin@cuvoodoo.info>
|
||||
/* firmware for STM8S003-based dachboden badge
|
||||
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
#include <stdint.h>
|
||||
|
@ -9,7 +9,82 @@
|
|||
#include "stm8s.h"
|
||||
#include "main.h"
|
||||
|
||||
// enable UART debug
|
||||
#define DEBUG 1
|
||||
|
||||
// EEPROM start address
|
||||
#define EEPROM_ADDR 0x4000
|
||||
|
||||
// pinout
|
||||
// pin to power IR demodulator (source on)
|
||||
#define IRM_ON_PIN PA3
|
||||
#define IRM_ON_PORT GPIO_PA
|
||||
// IR demodulator output pin
|
||||
#define IRM_OUT_PIN PC7 // TIM1_CH2
|
||||
#define IRM_OUT_PORT GPIO_PC
|
||||
// RGB LED pins (sink controlled by nMOS)
|
||||
#define LED_RED_PIN PD3 // TIM2_CH2
|
||||
#define LED_RED_PORT GPIO_PD
|
||||
#define LED_GREEN_PIN PD2 // TIM2_CH3
|
||||
#define LED_GREEN_PORT GPIO_PD
|
||||
#define LED_BLUE_PIN PC5 // TIM2_CH1
|
||||
#define LED_BLUE_PORT GPIO_PC
|
||||
// IR LED pin (source on)
|
||||
#define LED_IR_PIN PC4 // TIM1_CH4
|
||||
#define LED_IR_PORT GPIO_PC
|
||||
// UV LED pin (source on)
|
||||
#define LED_UV_PIN PC3 // TIM1_CH3
|
||||
#define LED_UV_PORT GPIO_PC
|
||||
// vibration sensor input (high on vibration)
|
||||
#define SHAKE_PIN PA2
|
||||
#define SHAKE_PORT GPIO_PA
|
||||
#define SHAKE_IRQ IRQ_EXTI0 // port A
|
||||
|
||||
// number of vibrations registered
|
||||
static volatile uint16_t shake_count = 0;
|
||||
// number of time counts (+1 @ 488 Hz)
|
||||
static volatile uint32_t time_count = 0;
|
||||
// last time the badge was shook
|
||||
static volatile uint32_t time_shake = 0;
|
||||
|
||||
// time after last shake to go to sleep, in seconds
|
||||
#define REST_TIME (5 * 60U)
|
||||
// period to share our color code, in seconds
|
||||
#define SHARE_TIME (1U)
|
||||
// period to enforce our color code, in seconds
|
||||
#define MASTER_TIME (1U)
|
||||
|
||||
// time counts per us (1/(16E6/(3+1)) * 1000*1000 = 0.25 us)
|
||||
#define NEC_TICKS_PER_US 4UL
|
||||
// burst error margin in %
|
||||
#define NEC_ERROR 15
|
||||
// AGC burst length (9 ms)
|
||||
#define NEC_AGC_BURST (9000 * NEC_TICKS_PER_US)
|
||||
// AGC slot length (9 + 4.5 ms)
|
||||
#define NEC_AGC_SLOT (NEC_AGC_BURST + (4500 * NEC_TICKS_PER_US))
|
||||
// bit burst length (560 us)
|
||||
#define NEC_BIT_BURST (560 * NEC_TICKS_PER_US)
|
||||
// logical 0 slot length
|
||||
#define NEC_0_SLOT (1125 * NEC_TICKS_PER_US)
|
||||
// logical 1 slot length
|
||||
#define NEC_1_SLOT (2250 * NEC_TICKS_PER_US)
|
||||
|
||||
#define NEC_AGC_SLOT_MIN (NEC_AGC_SLOT * (100 - NEC_ERROR) / 100U)
|
||||
#define NEC_AGC_SLOT_MAX (NEC_AGC_SLOT * (100 + NEC_ERROR) / 100U)
|
||||
#define NEC_0_SLOT_MIN (NEC_0_SLOT * (100 - NEC_ERROR) / 100U)
|
||||
#define NEC_0_SLOT_MAX (NEC_0_SLOT * (100 + NEC_ERROR) / 100U)
|
||||
#define NEC_1_SLOT_MIN (NEC_1_SLOT * (100 - NEC_ERROR) / 100U)
|
||||
#define NEC_1_SLOT_MAX (NEC_1_SLOT * (100 + NEC_ERROR) / 100U)
|
||||
|
||||
// bit position in the NEC message (-2 = invalid, -1 = AGC)
|
||||
static volatile int8_t nec_bit = -2;
|
||||
// complete NEC message
|
||||
static volatile uint8_t nec_msg[4] = {0};
|
||||
// flag set if NEC message has been received
|
||||
static volatile bool nec_flag = false;
|
||||
|
||||
// set when data is received over UART
|
||||
static volatile char uart_c = 0;
|
||||
|
||||
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
|
||||
static void wait_10us(uint32_t us10)
|
||||
|
@ -18,32 +93,486 @@ static void wait_10us(uint32_t us10)
|
|||
while (us10--); // burn energy
|
||||
}
|
||||
|
||||
void putc(char c)
|
||||
{
|
||||
(void)c;
|
||||
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
#if DEBUG
|
||||
while (!UART1->SR.fields.TXE); // wait until TX buffer is empty
|
||||
UART1->DR.reg = c; // put character in buffer to be transmitted
|
||||
// don't wait until the transmission is complete
|
||||
#endif
|
||||
}
|
||||
|
||||
void puts(const char* s)
|
||||
{
|
||||
if (NULL == s) {
|
||||
return;
|
||||
}
|
||||
while (*s) {
|
||||
putc(*s++);
|
||||
}
|
||||
}
|
||||
|
||||
void putn(uint8_t n)
|
||||
{
|
||||
n &= 0x0f; // ensure it's a nibble
|
||||
if (n < 0xa) {
|
||||
n += '0';
|
||||
} else {
|
||||
n = 'a' + (n - 0x0a);
|
||||
}
|
||||
putc(n);
|
||||
}
|
||||
|
||||
void puth(uint8_t h)
|
||||
{
|
||||
putn(h >> 4);
|
||||
putn(h & 0x0f);
|
||||
}
|
||||
|
||||
// ASCII to nibble (0xff if invalid)
|
||||
static uint8_t a2n(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 0xa;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 0xa;
|
||||
} else {
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
// set duty cycle of red LED
|
||||
void led_red(uint16_t bightness)
|
||||
{
|
||||
TIM2->CCR2H.reg = (bightness >> 8); // set duty cycle
|
||||
TIM2->CCR2L.reg = (bightness >> 0); // set duty cycle
|
||||
}
|
||||
|
||||
// set duty cycle of green LED
|
||||
void led_green(uint16_t bightness)
|
||||
{
|
||||
TIM2->CCR3H.reg = (bightness >> 8); // set duty cycle
|
||||
TIM2->CCR3L.reg = (bightness >> 0); // set duty cycle
|
||||
}
|
||||
|
||||
// set duty cycle of blue LED
|
||||
void led_blue(uint16_t bightness)
|
||||
{
|
||||
TIM2->CCR1H.reg = (bightness >> 8); // set duty cycle
|
||||
TIM2->CCR1L.reg = (bightness >> 0); // set duty cycle
|
||||
}
|
||||
|
||||
void led_rgb(uint8_t* rgb)
|
||||
{
|
||||
if (NULL == rgb) {
|
||||
return;
|
||||
}
|
||||
led_red(rgb[0] << 8);
|
||||
led_red(rgb[0] << 8);
|
||||
led_green(rgb[1] << 8);
|
||||
led_green(rgb[1] << 8);
|
||||
led_blue(rgb[2] << 8);
|
||||
// no idea why, but if I don't do it a second time the blue is a bit on when switched off
|
||||
// it is not about the register order or preload
|
||||
led_blue(rgb[2] << 8);
|
||||
}
|
||||
|
||||
// configure timer to capture IR NEC codes
|
||||
static void timer_ir_in(void)
|
||||
{
|
||||
TIM1->CR1.reg = 0; // disable counter before reconfiguring it
|
||||
TIM1->IER.reg = 0; // reset interrupts
|
||||
TIM1->BKR.reg = 0; // reset register
|
||||
TIM1->CCER1.reg = 0; // reset register
|
||||
TIM1->CCER2.reg = 0; // reset register
|
||||
TIM1->PSCRH.reg = 0; // set prescaler to get most precise 9+4.5 ms
|
||||
TIM1->PSCRL.reg = 3; // 16E6/(3+1)/65536 = up to 16 ms
|
||||
TIM1->ARRH.reg = 0xff; // let it count to the end
|
||||
TIM1->ARRL.reg = 0xff; // an overflow means the signal is corrupted
|
||||
TIM1->CCMR1.input_fields.CC1S = 1; // configure channel as input and map CH1 to TI1FP1
|
||||
TIM1->CCER1.fields.CC1P = 1; // trigger on a low level or falling edge of TI1F
|
||||
TIM1->CCMR2.input_fields.CC2S = 2; // configure channel as input and map CH2 to TI1FP2
|
||||
TIM1->CCER1.fields.CC2P = 0; // trigger on a high level or rising edge of TI1F
|
||||
TIM1->SMCR.fields.TS = 5; // set trigger to filtered timer input 1 (TI1FP1)
|
||||
// don't filter the external trigger
|
||||
TIM1->SMCR.fields.SMS = 4; // reset on trigger
|
||||
TIM1->CCER1.fields.CC1E = 1; // enable channel 1 for input capture
|
||||
TIM1->CCER1.fields.CC2E = 1; // enable channel 2 for input capture
|
||||
TIM1->IER.fields.CC1IE = 1; // enable interrupt for channel
|
||||
TIM1->IER.fields.CC2IE = 1; // enable interrupt for channel
|
||||
TIM1->IER.fields.UIE = 1; // enable update interrupt
|
||||
TIM1->CR1.fields.URS = 1; // only update on overflow
|
||||
TIM1->SR1.reg = 0; // clear all flags
|
||||
TIM1->CNTRL.reg = 0; // reset counter
|
||||
TIM1->CNTRH.reg = 0; // reset counter
|
||||
TIM1->EGR.fields.UG = 1; // transfer all registers
|
||||
TIM1->CR1.fields.CEN = 1; // enable counter to start capture
|
||||
nec_bit = -2; // invalidate current packet
|
||||
IRM_ON_PORT->ODR.reg |= IRM_ON_PIN; // switch IR demodulator on
|
||||
}
|
||||
|
||||
#define TIM1_PERIOD 421U // 16E6/(0+1)/38000
|
||||
|
||||
// configure timer to transmit IR burst at 38 kHz
|
||||
static void timer_ir_out(void)
|
||||
{
|
||||
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
|
||||
|
||||
TIM1->CR1.reg = 0; // disable counter before reconfiguring it
|
||||
TIM1->CCER1.reg = 0; // reset register
|
||||
TIM1->CCER2.reg = 0; // reset register
|
||||
TIM1->IER.reg = 0; // reset interrupts
|
||||
TIM1->PSCRH.reg = 0; // set prescaler to get most precise 38 kHz
|
||||
TIM1->PSCRL.reg = 0; // 16E6/(0+1)/65536 = down to 244 Hz
|
||||
TIM1->ARRH.reg = (TIM1_PERIOD >> 8); // set auto-reload register for 38 kHz period
|
||||
TIM1->ARRL.reg = (TIM1_PERIOD & 0xff); // 16E6/(0+1)/38000
|
||||
TIM1->CCR4H.reg = (TIM1_PERIOD / 3) >> 8; // set duty cycle to 33%
|
||||
TIM1->CCR4L.reg = (TIM1_PERIOD / 3 ) & 0xff; // set duty cycle to 33%
|
||||
TIM1->CCMR4.output_fields.OC4M = 6; // set PWM1 mode
|
||||
TIM1->CCMR4.output_fields.CC4S = 0; // use channel as output
|
||||
TIM1->CCER2.fields.CC4E = 1; // enable channel output
|
||||
TIM1->BKR.fields.MOE = 1; // enable outputs
|
||||
TIM1->SR1.reg = 0; // clear all flags
|
||||
TIM1->CNTRL.reg = 0; // reset counter
|
||||
TIM1->CNTRH.reg = 0; // reset counter
|
||||
TIM1->CR1.fields.OPM = 1; // send one pulse at a time
|
||||
TIM1->EGR.fields.UG = 1; // transfer all registers
|
||||
// don't enable timer yet
|
||||
}
|
||||
|
||||
// transmit IR pulses
|
||||
static void nec_pulse(uint16_t pulses, bool mark)
|
||||
{
|
||||
if (mark) {
|
||||
TIM1->CCR4H.reg = (TIM1_PERIOD / 3) >> 8; // set duty cycle to 33%
|
||||
TIM1->CCR4L.reg = (TIM1_PERIOD / 3 ) & 0xff; // set duty cycle to 33%
|
||||
} else {
|
||||
TIM1->CCR4H.reg = 0; // set duty cycle to 0%
|
||||
TIM1->CCR4L.reg = 0; // set duty cycle to 0%
|
||||
}
|
||||
while (pulses--) {
|
||||
TIM1->CR1.fields.CEN = 1; // enable counter to start PWM for 1 pulse
|
||||
while (TIM1->CR1.fields.CEN); // wait until pulse completes
|
||||
}
|
||||
// ensure we are off at the end
|
||||
TIM1->CCR4H.reg = 0; // set duty cycle to 0%
|
||||
TIM1->CCR4L.reg = 0; // set duty cycle to 0%
|
||||
}
|
||||
|
||||
// transmit 4 byte NEC code over IR (LSb first)
|
||||
static void nec_transmit(uint32_t code)
|
||||
{
|
||||
if (NULL == code) {
|
||||
return;
|
||||
}
|
||||
|
||||
timer_ir_out(); // configure to transmit pulses
|
||||
sim(); // disable interrupts to keep timings tight
|
||||
// all time are hand tuned
|
||||
nec_pulse(333, true); // send AGC burst, 9 ms
|
||||
nec_pulse(166, false); // AGC space, 4.5 ms
|
||||
// transmit bits
|
||||
for (uint8_t i = 0; i < 32; i++) {
|
||||
nec_pulse(21, true); // bit burst, 560 us
|
||||
if (code & 0x1) {
|
||||
nec_pulse(61, false); // bit space, 2.25 ms - 560 us
|
||||
} else {
|
||||
nec_pulse(21, false); // bit space, 1.12 ms - 560 us
|
||||
}
|
||||
code >>= 1; // go to next bit
|
||||
}
|
||||
nec_pulse(22, true); // end pulse, 560 us
|
||||
rim(); // re-enable interrupts
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
bool master = false; // if we are not a slave badge, but master controller
|
||||
|
||||
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
|
||||
|
||||
// only power used peripherals
|
||||
CLK_PCKENR1 = CLK_PCKENR1_UART1234 | CLK_PCKENR1_TIM1 | CLK_PCKENR1_TIM25 | CLK_PCKENR1_TIM46;
|
||||
CLK_PCKENR2 = 0;
|
||||
|
||||
// configure option bytes
|
||||
// disable DATA (e.g. option byte) write protection
|
||||
if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) {
|
||||
FLASH_DUKR = FLASH_DUKR_KEY1;
|
||||
FLASH_DUKR = FLASH_DUKR_KEY2;
|
||||
}
|
||||
FLASH_CR2 |= FLASH_CR2_OPT; // set option bytes programming
|
||||
FLASH_NCR2 &= ~FLASH_NCR2_NOPT; // set option bytes programming
|
||||
|
||||
OPT->OPT2.fields.AFR0 = 1; // remap TIM2_CH1 to PC5, TIM1_CH1 to C6, and TIM1_CH2 to C7
|
||||
OPT->NOPT2.fields.NAFR0 = 0; // set complementary option byte
|
||||
OPT->OPT2.fields.AFR1 = 1; // remap TIM2_CH3 to PD2
|
||||
OPT->NOPT2.fields.NAFR1 = 0; // set complementary option byte
|
||||
|
||||
while (!(FLASH_IAPSR & FLASH_IAPSR_EOP)); // wait for write to complete
|
||||
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
|
||||
|
||||
// configure UART for debug output
|
||||
UART1->CR1.fields.M = 0; // 8 data bits
|
||||
UART1->CR3.fields.STOP = 0; // 1 stop bit
|
||||
UART1->BRR2.reg = 0x0B; // set baud rate to 115200 (at 16 MHz)
|
||||
UART1->BRR1.reg = 0x08; // set baud rate to 115200 (at 16 MHz)
|
||||
UART1->CR2.fields.TEN = 1; // enable TX
|
||||
UART1->CR2.fields.REN = 1; // enable RX
|
||||
UART1->CR2.fields.RIEN = 1; // enable RX interrupt
|
||||
char uart_cmd[10]; // buffer for received data
|
||||
uint8_t uart_used = 0; // how much of the buffer is used
|
||||
|
||||
// configure IR demodulator pin
|
||||
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
|
||||
IRM_ON_PORT->CR1.reg |= IRM_ON_PIN; // use as push-pull
|
||||
IRM_ON_PORT->DDR.reg |= IRM_ON_PIN; // switch pin to output
|
||||
|
||||
|
||||
/* use PWM instead of GPIO for controlling RGB LED
|
||||
LED_RED_PORT->ODR.reg &= ~LED_RED_PIN; // switch LED off
|
||||
LED_RED_PORT->CR1.reg |= LED_RED_PIN; // use as push-pull
|
||||
LED_RED_PORT->DDR.reg |= LED_RED_PIN; // use pin to output
|
||||
LED_GREEN_PORT->ODR.reg &= ~LED_GREEN_PIN; // switch LED off
|
||||
LED_GREEN_PORT->CR1.reg |= LED_GREEN_PIN; // use as push-pull
|
||||
LED_GREEN_PORT->DDR.reg |= LED_GREEN_PIN; // use pin to output
|
||||
LED_BLUE_PORT->ODR.reg &= ~LED_BLUE_PIN; // switch LED off
|
||||
LED_BLUE_PORT->CR1.reg |= LED_BLUE_PIN; // use as push-pull
|
||||
LED_BLUE_PORT->DDR.reg |= LED_BLUE_PIN; // use pin to output
|
||||
*/
|
||||
|
||||
// configure timer 2 for PWM-controlling RGB LED
|
||||
TIM2->PSCR.fields.PSC = 0; // set prescaler to to 244 Hz, 16E6/(2**0)/65536 = 244 Hz
|
||||
TIM2->ARRH.reg = 0xff; // set period to max for most precisions
|
||||
TIM2->ARRL.reg = 0xff; // set period to max for most precisions
|
||||
TIM2->CCMR1.output_fields.OC1M = 6; // set PWM1 mode
|
||||
TIM2->CCMR1.output_fields.CC1S = 0; // use channel as output
|
||||
TIM2->CCER1.fields.CC1E = 1; // enable channel output
|
||||
led_blue(0); // switch off blue LED
|
||||
TIM2->CCMR2.output_fields.OC2M = 6; // set PWM1 mode
|
||||
TIM2->CCMR2.output_fields.CC2S = 0; // use channel as output
|
||||
TIM2->CCER1.fields.CC2E = 1; // enable channel output
|
||||
led_red(0); // switch off red LED
|
||||
TIM2->CCMR3.output_fields.OC3M = 6; // set PWM1 mode
|
||||
TIM2->CCMR3.output_fields.CC3S = 0; // use channel as output
|
||||
TIM2->CCER2.fields.CC3E = 1; // enable channel output
|
||||
led_green(0); // switch off green LED
|
||||
TIM2->EGR.fields.UG = 1; // transfer all registers
|
||||
TIM2->CR1.fields.CEN = 1; // enable counter to start PWM
|
||||
|
||||
// load color
|
||||
uint8_t rgb[3]; // eyes color
|
||||
rgb[0] = *(uint8_t*)(EEPROM_ADDR + 0); // load red color
|
||||
rgb[1] = *(uint8_t*)(EEPROM_ADDR + 1); // load green color
|
||||
rgb[2] = *(uint8_t*)(EEPROM_ADDR + 2); // load blue color
|
||||
led_rgb(rgb); // set color
|
||||
|
||||
// configure UV LED
|
||||
LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch LED off
|
||||
LED_UV_PORT->CR1.reg |= LED_UV_PIN; // use as push-pull
|
||||
LED_UV_PORT->DDR.reg |= LED_UV_PIN; // use pin to output
|
||||
|
||||
// configure vibration sensor input
|
||||
SHAKE_PORT->CR1.reg &= ~SHAKE_PIN; // leave floating (pulled down externally)
|
||||
SHAKE_PORT->DDR.reg &= ~SHAKE_PIN; // set as input
|
||||
SHAKE_PORT->CR2.reg |= SHAKE_PIN; // enable external input
|
||||
EXTI->CR1.fields.PAIS = EXTI_RISING_EDGE; // interrupt when vibration is detected
|
||||
shake_count = 0; // reset counter
|
||||
|
||||
// use timer 4 (8-bit) as timeout counter
|
||||
TIM4->PSCR.fields.PSC = 7; // make it as slow as possible 16E6 / 2**7 = 125 kHz, / 256 = 488 Hz
|
||||
TIM4->CNTR.fields.CNT = 0; // reset counter
|
||||
TIM4->IER.fields.UIE = 1; // enable update interrupt
|
||||
time_count = 0; // reset time counter
|
||||
TIM4->CR1.fields.URS = 1; // only update on overflow
|
||||
TIM4->CR1.fields.CEN = 1; // enable counter
|
||||
|
||||
// configure timer to receive IR message
|
||||
timer_ir_in();
|
||||
|
||||
/* don't use the AWU, else it will cause an active-halt instead of halt, using more power
|
||||
// configure auto-wakeup (AWU) to be able to refresh the watchdog
|
||||
// 128 kHz LSI used by default in option bytes CKAWUSEL
|
||||
// we skip measuring the LS clock frequency since there is no need to be precise
|
||||
AWU->TBR.fields.AWUTB = 10; // interval range: 128-256 ms
|
||||
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)
|
||||
AWU->CSR.fields.AWUEN = 1; // enable AWU (start only when entering wait or active halt mode)
|
||||
*/
|
||||
|
||||
/* don't use IWDG since it wakes up from HALT mode and uses (a little) power
|
||||
use WWDG instead
|
||||
// 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
|
||||
*/
|
||||
|
||||
rim(); // re-enable interrupts
|
||||
bool action = false; // if an action has been performed
|
||||
puts("\r\nready\r\n");
|
||||
while (true) {
|
||||
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
||||
if (shake_count) {
|
||||
puts("vibrations: ");
|
||||
puth(shake_count);
|
||||
puts("\r\n");
|
||||
time_shake = time_count; // remember time to stay awake
|
||||
shake_count = 0; // reset count
|
||||
}
|
||||
if (nec_flag) {
|
||||
puts("\r\nr");
|
||||
puth(nec_msg[0]);
|
||||
puth(nec_msg[1]);
|
||||
puth(nec_msg[2]);
|
||||
puth(nec_msg[3]);
|
||||
puts("\r\n");
|
||||
if (0x80 == nec_msg[0] && 0x7f == nec_msg[1]) { // radio remote
|
||||
if (0x04 == nec_msg[2] && 0xfb == nec_msg[3]) { // 1
|
||||
rgb[0] = 0x80;
|
||||
rgb[1] = 0;
|
||||
rgb[2] = 0;
|
||||
} else if (0x05 == nec_msg[2] && 0xfa == nec_msg[3]) { // 2
|
||||
rgb[0] = 0;
|
||||
rgb[1] = 0x80;
|
||||
rgb[2] = 0;
|
||||
} else if (0x06 == nec_msg[2] && 0xf9 == nec_msg[3]) { // 3
|
||||
rgb[0] = 0;
|
||||
rgb[1] = 0;
|
||||
rgb[2] = 0x80;
|
||||
} else if (0x01 == nec_msg[2] && 0xfe == nec_msg[3]) { // mute
|
||||
LED_UV_PORT->ODR.reg |= LED_UV_PIN; // switch UV LED on
|
||||
} else if (0x12 == nec_msg[2] && 0xed == nec_msg[3]) { // power
|
||||
LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch UV LED off
|
||||
rgb[0] = 0;
|
||||
rgb[1] = 0;
|
||||
rgb[2] = 0;
|
||||
}
|
||||
led_rgb(rgb); // ensure [new] color is set
|
||||
} else if (0x01 == nec_msg[0]) { // badge tries to influence us
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
if (nec_msg[1 + i] > rgb[i]) {
|
||||
rgb[i]++;
|
||||
} else if (nec_msg[1 + i] < rgb[i]) {
|
||||
rgb[i]--;
|
||||
}
|
||||
}
|
||||
led_rgb(rgb); // set new color
|
||||
} else if (0x02 == nec_msg[0]) { // master sets our color
|
||||
rgb[0] = nec_msg[1];
|
||||
rgb[1] = nec_msg[2];
|
||||
rgb[2] = nec_msg[3];
|
||||
led_rgb(rgb); // set new color
|
||||
}
|
||||
nec_flag = false; // clear flag
|
||||
action = true; // redo loop
|
||||
}
|
||||
if (!master && 0 == time_count % (488UL * SHARE_TIME)) {
|
||||
uint32_t code = 0x01; // code to send
|
||||
code |= ((uint32_t)rgb[0] << 8);
|
||||
code |= ((uint32_t)rgb[1] << 16);
|
||||
code |= ((uint32_t)rgb[2] << 24);
|
||||
nec_transmit(code);
|
||||
timer_ir_in(); // go back to IR capture
|
||||
putc('t');
|
||||
action = true; // redo main loop
|
||||
}
|
||||
if (master && 0 == time_count % (488UL * MASTER_TIME)) {
|
||||
uint32_t code = 0x02; // code to send
|
||||
code |= ((uint32_t)rgb[0] << 8);
|
||||
code |= ((uint32_t)rgb[1] << 16);
|
||||
code |= ((uint32_t)rgb[2] << 24);
|
||||
nec_transmit(code);
|
||||
putc('m');
|
||||
action = true; // redo main loop
|
||||
}
|
||||
if (time_count > time_shake + 488UL * REST_TIME && !master) {
|
||||
LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch UV LED off
|
||||
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
|
||||
led_red(0); // ensure LED is off
|
||||
led_green(0); // ensure LED is off
|
||||
led_blue(0); // ensure LED is off
|
||||
// save color
|
||||
if (rgb[0] != *(uint8_t*)(EEPROM_ADDR + 0) || rgb[1] != *(uint8_t*)(EEPROM_ADDR + 1) || rgb[2] != *(uint8_t*)(EEPROM_ADDR + 2)) {
|
||||
// disable DATA (e.g. EEPROM) write protection
|
||||
if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) {
|
||||
FLASH_DUKR = FLASH_DUKR_KEY1;
|
||||
FLASH_DUKR = FLASH_DUKR_KEY2;
|
||||
}
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
*(uint8_t*)(EEPROM_ADDR + i) = rgb[i];
|
||||
while (!(FLASH_IAPSR & FLASH_IAPSR_EOP)); // wait until programming is complete
|
||||
}
|
||||
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
|
||||
}
|
||||
puts("rest\r\n\n");
|
||||
halt();
|
||||
IRM_ON_PORT->ODR.reg |= IRM_ON_PIN; // switch IR demodulator on
|
||||
led_rgb(rgb); // set color
|
||||
time_shake = 0; // reset stay awake time
|
||||
time_count = 0; // reset counter
|
||||
}
|
||||
if (uart_c) { // data received over UART
|
||||
putc(uart_c); // echo back
|
||||
if ('\r' == uart_c || '\n' == uart_c) { // end of line received
|
||||
if (uart_used >= 9 && 'T' == uart_cmd[0]) { // transmit command
|
||||
bool code_valid = true; // verify it it's really a hex string
|
||||
uint32_t code = 0; // parsed code
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint32_t n = a2n(uart_cmd[1 + i]);
|
||||
if (n > 0xf) {
|
||||
code_valid = false;
|
||||
break;
|
||||
} else {
|
||||
code |= (n << (i * 4));
|
||||
}
|
||||
}
|
||||
if (code_valid) {
|
||||
nec_transmit(code); // transmit code
|
||||
timer_ir_in(); // go back to IR capture
|
||||
puts("\r\ncode transmitted\r\n");
|
||||
}
|
||||
} else if (uart_used >= 7 && 'M' == uart_cmd[0]) { // switch to master
|
||||
bool code_valid = true; // verify it it's really a hex string
|
||||
uint32_t code = 0x02000000; // parsed master code
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
uint32_t n = a2n(uart_cmd[1 + i]);
|
||||
if (n > 0xf) {
|
||||
code_valid = false;
|
||||
break;
|
||||
} else {
|
||||
code |= (n << ((i + 1) * 4));
|
||||
}
|
||||
}
|
||||
if (code_valid) {
|
||||
rgb[0] = code >> 8; // save color
|
||||
rgb[1] = code >> 16; // save color
|
||||
rgb[2] = code >> 24; // save color
|
||||
led_rgb(rgb); // set color
|
||||
nec_transmit(code); // transmit code
|
||||
if (!master) {
|
||||
master = true; // switch to master mode
|
||||
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
|
||||
puts("\r\nmaster set\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
uart_used = 0; // reset buffer
|
||||
} else if (uart_used < ARRAY_LENGTH(uart_cmd)) {
|
||||
uart_cmd[uart_used++] = uart_c;
|
||||
}
|
||||
uart_c = 0; // clear flag
|
||||
action = true; // redo main loop
|
||||
}
|
||||
if (action) { // something has been performed, check if other flags have been set meanwhile
|
||||
action = false; // clear flag
|
||||
} else { // nothing down
|
||||
|
@ -52,8 +581,77 @@ void main(void)
|
|||
}
|
||||
}
|
||||
|
||||
void awu(void) __interrupt(IRQ_AWU) // auto wakeup
|
||||
// auto wakeup
|
||||
void awu(void) __interrupt(IRQ_AWU)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
// vibration sensor input
|
||||
void shake_isr(void) __interrupt(SHAKE_IRQ)
|
||||
{
|
||||
shake_count++; // register vibration
|
||||
}
|
||||
|
||||
// system time counter
|
||||
void time_isr(void) __interrupt(IRQ_TIM4)
|
||||
{
|
||||
TIM4->SR.fields.UIF = 0; // clear flag
|
||||
time_count++; // remember overflow to count time
|
||||
}
|
||||
|
||||
// IR capture timer counter
|
||||
void nec_uo_isr(void) __interrupt(IRQ_TIM1_UO)
|
||||
{
|
||||
if (TIM1->SR1.fields.UIF) { // timer overflow
|
||||
nec_bit = -2; // set to invalid message since it took longer than NEC slot
|
||||
TIM1->SR1.fields.UIF = 0; // clear flag
|
||||
}
|
||||
}
|
||||
|
||||
// IR capture timer counter
|
||||
void nec_cc_isr(void) __interrupt(IRQ_TIM1_CC)
|
||||
{
|
||||
static uint32_t nec_bits = 0; // temporary buffer to construct message
|
||||
if (TIM1->SR1.fields.CC1IF) { // start of burst
|
||||
const uint16_t slot = (TIM1->CCR1H.reg << 8) + TIM1->CCR1L.reg;
|
||||
if (-1 == nec_bit) { // AGC received
|
||||
nec_bits = 0; // reset message
|
||||
if (slot < NEC_AGC_SLOT_MIN || slot > NEC_AGC_SLOT_MAX) {
|
||||
nec_bit = -2; // slot invalid
|
||||
}
|
||||
} else if (nec_bit >= 0 && nec_bit < 32) {
|
||||
if (slot >= NEC_0_SLOT_MIN && slot <= NEC_0_SLOT_MAX) {
|
||||
// bit should already be 0
|
||||
} else if (slot >= NEC_1_SLOT_MIN && slot <= NEC_1_SLOT_MAX) {
|
||||
nec_bits |= (1UL << nec_bit);
|
||||
} else {
|
||||
nec_bit = -2; // slot invalid
|
||||
}
|
||||
}
|
||||
nec_bit++; // start the bit
|
||||
TIM1->SR1.fields.CC1IF = 0; // clear flag
|
||||
}
|
||||
if (TIM1->SR1.fields.CC2IF) { // start of pause
|
||||
if (32 == nec_bit) {
|
||||
nec_msg[0] = (nec_bits >> 0); // save message
|
||||
nec_msg[1] = (nec_bits >> 8); // save message
|
||||
nec_msg[2] = (nec_bits >> 16); // save message
|
||||
nec_msg[3] = (nec_bits >> 24); // save message
|
||||
nec_flag = true; // notify we received a message
|
||||
nec_bit = -2; // restart message
|
||||
} else if (nec_bit >= 0) {
|
||||
// here we could verify the burst length
|
||||
}
|
||||
TIM1->SR1.fields.CC2IF = 0; // clear flag
|
||||
}
|
||||
}
|
||||
|
||||
// UART RX interrupt
|
||||
void rx_isr(void) __interrupt(IRQ_UART1_RX)
|
||||
{
|
||||
if (UART1->SR.fields.RXNE) { // there is data
|
||||
uart_c = UART1->DR.reg; // read received data, also clears 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