remove unused library

This commit is contained in:
King Kévin 2020-06-21 10:10:54 +02:00
parent 09dfd6c6f5
commit 940fa5b9b1
46 changed files with 0 additions and 10016 deletions

View File

@ -1,611 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with an SD card flash memory using the SPI mode (code)
* @file flash_sdcard.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017
* @note peripherals used: SPI @ref flash_sdcard_spi
* @warning all calls are blocking
* @implements SD Specifications, Part 1, Physical Layer, Simplified Specification, Version 6.00, 10 April 10 2017
* @todo use SPI unidirectional mode, use DMA, force/wait going to idle state when initializing, filter out reserved values, check sector against size
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
#include "global.h" // global utilities
#include "flash_sdcard.h" // SD card header and definitions
/** @defgroup flash_sdcard_spi SPI used to communication with SD card
* @{
*/
#define FLASH_SDCARD_SPI 1 /**< SPI peripheral */
/** @} */
/** if the card has been initialized successfully */
static bool initialized = false;
/** maximum N_AC value (in 8-clock cycles) (time between the response token R1 and data block when reading data (see section 7.5.4)
* @note this is set to N_CR until we can read CSD (see section 7.2.6)
*/
static uint32_t n_ac = 8;
/** is it a Standard Capacity SD card (true), or High Capacity SD cards (false)
* @note this is indicated in the Card Capacity Status bit or OCR (set for high capacity)
* @note this is important for addressing: for standard capacity cards the address is the byte number, for high capacity cards it is the 512-byte block number
*/
static bool sdsc = false;
/** size of card in bytes */
static uint64_t sdcard_size = 0;
/** size of an erase block bytes */
static uint32_t erase_size = 0;
/** table for CRC-7 calculation for the command messages (see section 4.5)
* @note faster than calculating the CRC and doesn't cost a lot of space
* @note generated using pycrc --width=7 --poly=0x09 --reflect-in=false --reflect-out=false --xor-in=0x00 --xor-out=0x00 --generate=table
*/
static const uint8_t crc7_table[] = {
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
};
/** wait one SPI round (one SPI word)
*/
static void flash_sdcard_spi_wait(void)
{
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
}
/** read one SPI word
* @return SPI word read
*/
static uint16_t flash_sdcard_spi_read(void)
{
spi_send(SPI(FLASH_SDCARD_SPI), 0xffff); // send not command token (i.e. starting with 1)
(void)SPI_DR(SPI(FLASH_SDCARD_SPI)); // clear RXNE flag (by reading previously received data (not done by spi_read or spi_xref)
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until Tx buffer is empty before clearing the (previous) RXNE flag
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_RXNE)); // wait for next data to be available
return SPI_DR(SPI(FLASH_SDCARD_SPI)); // return received adat
}
/** test if card is present
* @return if card has been detected
* @note this use the SD card detection mechanism (CD/CS is high card is inserted due to the internal 50 kOhm resistor)
*/
static bool flash_sdcard_card_detect(void)
{
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for NSS pin port peripheral for SD card CD signal
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS pin as input to read CD signal
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // pull pin low to avoid false positive when card in not inserted
return (0!=gpio_get(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI))); // read CD signal: is card is present the internal 50 kOhm pull-up resistor will override our 1 MOhm pull-down resistor and set the signal high (see section 6.2)
}
/** transmit command token
* @param[in] index command index
* @param[in] argument command argument
*/
static void flash_sdcard_send_command(uint8_t index, uint32_t argument)
{
uint8_t command[5] = { 0x40+(index&0x3f), argument>>24, argument>>16, argument>>8, argument>>0 }; // commands are 5 bytes long, plus 1 bytes of CRC (see section 7.3.1.1)
uint8_t crc7 = 0x00; // CRC-7 checksum for command message
// send command
for (uint8_t i=0; i<LENGTH(command); i++) {
spi_send(SPI(FLASH_SDCARD_SPI), command[i]); // send data
crc7 = (crc7_table[((crc7<<1)^command[i])])&0x7f; // update checksum
}
spi_send(SPI(FLASH_SDCARD_SPI), (crc7<<1)+0x01); // send CRC value (see section 7.3.1.1)
}
/** transmit command token and receive response token
* @param[in] index command index
* @param[in] argument command argument
* @param[out] response response data to read (if no error occurred)
* @param[in] size size of response to read
* @return response token R1 or 0xff if error occurred or card is not present
*/
static uint8_t flash_sdcard_command_response(uint8_t index, uint32_t argument, uint8_t* response, size_t size)
{
// send command token
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.1.1)
flash_sdcard_send_command(index, argument); // send command token
// get response token R1
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
}
if (0x00==(r1&0xfe) && 0!=size && NULL!=response) { // we have to read a response
for (size_t i=0; i<size; i++) {
response[i] = flash_sdcard_spi_read(); // get byte
}
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return r1;
}
/** read a data block
* @param[out] data data block to read (if no error occurred)
* @param[in] size size of response to read (a multiple of 2)
* @return 0 if succeeded, else control token (0xff for other errors)
*/
static uint8_t flash_sdcard_read_block(uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
return 0xff;
}
uint8_t token = 0xff; // to save the control block token (see section 7.3.3)
for (uint32_t i=0; i<n_ac && token==0xff; i++) { // wait for N_AC before reading data block (see section 7.5.2.1)
token = flash_sdcard_spi_read(); // get control token (see section 7.3.3)
}
if (0==(token&0xf0)) { // data error token received (see section 7.3.3.3)
if (0==(token&0x0f)) { // unknown error
token = 0xff;
}
} else if (0xfe==token) { // start block token received (see section 7.3.3.2)
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
// get block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
for (size_t i=0; i<size/2; i++) {
uint16_t word = flash_sdcard_spi_read(); // get word
data[i*2+0] = (word>>8); // save byte
data[i*2+1] = (word>>0); // save byte
}
flash_sdcard_spi_read(); // read CRC (the CRC after the data block should clear the computed CRC)
if (SPI_CRC_RXR(FLASH_SDCARD_SPI)) { // CRC is wrong
token = 0xff;
} else { // no error occurred
token = 0;
}
// switch back to 8-bit SPI frames
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
} else { // start block token not received
token = 0xff;
}
return token;
}
/** write a data block
* @param[in] data data block to write
* @param[in] size size of response to read (a multiple of 2)
* @return data response token (0xff for other errors)
*/
static uint8_t flash_sdcard_write_block(uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
return 0xff;
}
spi_send(SPI(FLASH_SDCARD_SPI), 0xfe); // send start block token (see section 7.3.3.2)
// switch to 16-bits SPI data frame so we can use use built-in CRC-16
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_set_dff_16bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 16 bits
SPI_CRC_PR(FLASH_SDCARD_SPI) = 0x1021; // set CRC-16-CCITT polynomial for data blocks (x^16+x^12+x^5+1) (see section 7.2.3)
spi_enable_crc(SPI(FLASH_SDCARD_SPI)); // enable and clear CRC
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
// send block data (ideally use DMA, but switching makes it more complex and this part doesn't take too much time)
for (size_t i=0; i<size/2; i++) {
uint16_t word = (data[i*2+0]<<8)+data[i*2+1]; // prepare SPI frame
spi_send(SPI(FLASH_SDCARD_SPI), word); // senf data frame
}
spi_set_next_tx_from_crc(SPI(FLASH_SDCARD_SPI)); // send CRC
// switch back to 8-bit SPI frames
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change format
spi_set_next_tx_from_buffer(SPI(FLASH_SDCARD_SPI)); // don't send CRC
spi_disable_crc(SPI(FLASH_SDCARD_SPI)); // disable CRC since we don't use it anymore (and this allows us to clear the CRC next time we use it)
spi_set_dff_8bit(SPI(FLASH_SDCARD_SPI)); // set SPI frame to 8 bits
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
uint8_t token = 0xff;
while (0x01!=(token&0x11)) {
token = flash_sdcard_spi_read(); // get data response token (see section 7.3.3.1)
}
while (0==flash_sdcard_spi_read()); // wait N_EC while the card is busy programming the data
return token;
}
/** get card status
* @param[out] status SD status (512 bits)
* @return response token R2 or 0xffff if error occurred or card is not present
*/
static uint16_t flash_sdcard_status(uint8_t* status)
{
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
uint8_t r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
if ((r1&0xfe)) { // error occurred, not in idle state
return false;
}
// send ACMD13 command
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
flash_sdcard_send_command(13, 0); // send ACMD13 (SD_STATUS) (see table 7-4)
// get response token R2
uint16_t r2 = 0xffff; // response token R2 (see section 7.3.2.3)
for (uint8_t i=0; i<8 && r2&0x8000; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r2 = (flash_sdcard_spi_read()<<8); // get first byte of response (see section 7.3.2.1)
}
if (0==(r2&0x8000)) { // got the first byte
r2 += flash_sdcard_spi_read(); // read second byte (see 7.3.2.3)
}
// get data block
if (0==r2) { // no error
if (flash_sdcard_read_block(status, 64)) { // read 512 bits data block containing SD status
r2 |= (1<<11); // set communication error
}
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return r2;
}
/** transmit command token, receive response token and data block
* @param[in] index command index
* @param[in] argument command argument
* @param[out] data data block to read (if no error occurred)
* @param[in] size size of data to read (a multiple of 2)
* @return response token R1 or 0xff if error occurred or card is not present
*/
static uint8_t flash_sdcard_data_read(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) read odd number of bytes
return 0xff;
}
// send command token
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
flash_sdcard_send_command(index, argument); // send command token
// get response token R1
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
}
// get data block
if (0x00==r1) { // we can read a data block
if (flash_sdcard_read_block(data, size)) { // read data block
r1 |= (1<<3); // set communication error
}
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return r1;
}
/** transmit command token, receive response token and write data block
* @param[in] index command index
* @param[in] argument command argument
* @param[out] data data block to write
* @param[in] size size of data to write (a multiple of 2)
* @return data response token, or 0xff if error occurred or card is not present
* @note at the end of a write operation the SD status should be check to ensure no error occurred during programming
*/
static uint8_t flash_sdcard_data_write(uint8_t index, uint32_t argument, uint8_t* data, size_t size)
{
if (size%2 || 0==size || NULL==data) { // can't (and shouldn't) write odd number of bytes
return 0xff;
}
// send command token
gpio_clear(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS low to select slave and start SPI mode (see section 7.2)
flash_sdcard_spi_wait(); // wait for N_CS (min. 0, but it works better with 8 clock cycles) before writing command (see section 7.5.2.1)
flash_sdcard_send_command(index, argument); // send command token
// get response token R1
uint8_t r1 = 0xff; // response token R1 (see section 7.3.2.1)
for (uint8_t i=0; i<8 && r1&0x80; i++) { // wait for N_CR (1 to 8 8 clock cycles) before reading response (see section 7.5.1.1)
r1 = flash_sdcard_spi_read(); // get response (see section 7.3.2.1)
}
// write data block
uint8_t drt = 0xff; // data response token (see section 7.3.3.1)
if (0x00==r1) { // we have to write the data block
drt = flash_sdcard_write_block(data, size); // write data block
}
// end communication
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
// wait for N_EC (min. 0) before closing communication (see section 7.5.1.1)
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// wait for N_DS (min. 0) before allowing any further communication (see section 7.5.1.1)
return drt;
}
bool flash_sdcard_setup(void)
{
// reset values
initialized = false;
n_ac = 8;
sdcard_size = 0;
erase_size = 0;
// check if card is present
if (!flash_sdcard_card_detect()) {
return false;
}
// configure SPI peripheral
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for clock signal
gpio_set_mode(SPI_SCK_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(FLASH_SDCARD_SPI)); // set SCK as output (clock speed will be negotiated later)
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MOSI signal
gpio_set_mode(SPI_MOSI_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(FLASH_SDCARD_SPI)); // set MOSI as output
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for MISO signal
gpio_set_mode(SPI_MISO_PORT(FLASH_SDCARD_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(FLASH_SDCARD_SPI)); // set MISO as input
gpio_set(SPI_MISO_PORT(FLASH_SDCARD_SPI), SPI_MISO_PIN(FLASH_SDCARD_SPI)); // pull pin high to detect when the card is not answering (or not present) since responses always start with MSb 0
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(FLASH_SDCARD_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal
gpio_set_mode(SPI_NSS_PORT(FLASH_SDCARD_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set NSS (CS) as output
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(RCC_SPI(FLASH_SDCARD_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(FLASH_SDCARD_SPI)); // clear SPI values to default
spi_init_master(SPI(FLASH_SDCARD_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_256, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 256 (72E6/256=281 kHz) since maximum SD card clock frequency (fOD, see section 7.8/6.6.6) during initial card-identification mode is 400 kHz (maximum SPI PCLK clock is 72 Mhz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (from SD card spec, polarity depends on clock phase), use 8 bits frames (as per spec), use MSb first
spi_set_full_duplex_mode(SPI(FLASH_SDCARD_SPI)); // ensure we are in full duplex mode
spi_enable_software_slave_management(SPI(FLASH_SDCARD_SPI)); // control NSS (CS) manually
spi_set_nss_high(SPI(FLASH_SDCARD_SPI)); // set NSS high (internally) so we can output
spi_disable_ss_output(SPI(FLASH_SDCARD_SPI)); // disable NSS output since we control CS manually
gpio_set(SPI_NSS_PORT(FLASH_SDCARD_SPI), SPI_NSS_PIN(FLASH_SDCARD_SPI)); // set CS high to unselect card
// sadly we can't use the interrupts as events to sleep (WFE) since sleep disables also communication (e.g. going to sleep until Rx buffer is not empty prevents transmission)
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI
// start card-identification (see section 7.2.1/4.2)
uint8_t r1 = 0;
// send CMD0 (GO_IDLE_START) to start the card identification (see section 7.2.1)
r1 = flash_sdcard_command_response(0, 0, NULL, 0); // (see table 7-3)
if (0x01!=r1) { // error occurred, not in idle state
return false;
}
// send CMD8 (SEND_IF_COND) to inform about voltage (1: 2.7-3.6V, aa: recommended check pattern) (see section 7.2.1)
uint8_t r7[4] = {0}; // to store response toke R7 (see section 7.3.2.6)
r1 = flash_sdcard_command_response(8, 0x000001aa, r7, sizeof(r7)); // (see table 7-3)
if (0x01==r1) { // command supported, in idle state
if (!(r7[2]&0x1)) { // 2.7-3.6V not supported (see table 5-1)
return false;
} else if (0xaa!=r7[3]) { // recommended pattern not returned (see section 4.3.13)
return false;
}
} else if (0x05!=r1) { // illegal command (cards < physical spec v2.0 don't support CMD8) (see section 7.2.1)
return false;
}
// send CMD58 (READ_OCR) to read Operation Conditions Register (see section 7.2.1)
uint8_t r3[4] = {0}; // to store response token R3 (see section 7.3.2.4)
r1 = flash_sdcard_command_response(58, 0, r3, sizeof(r3)); // (see table 7-3)
if (0x01!=r1) { // error occurred, not in idle state
return false;
} else if (!(r3[1]&0x30)) { // 3.3V not supported (see table 5-1)
return false;
}
do {
// send CMD55 (APP_CMD) to issue following application command (see table 7-4)
r1 = flash_sdcard_command_response(55, 0, NULL, 0); // (see table 7-3)
if (0x01!=r1) { // error occurred, not in idle state
return false;
}
// send ACMD41 (SD_SEND_OP_COND) with Host Capacity Support (0b: SDSC Only Host, 1b: SDHC or SDXC Supported) (see section 7.2.1)
r1 = flash_sdcard_command_response(41, 0x40000000, NULL, 0); // (see table 7-4)
if (r1&0xfe) { // error occurred
return false;
}
} while (0x00!=r1); // wait until card is ready (see section 7.2.1)
// send CMD58 (READ_OCR) to read Card Capacity Status (CCS) (see section 7.2.1)
r1 = flash_sdcard_command_response(58, 0, r3, sizeof(r3)); // (see table 7-3)
if (r1) { // error occurred
return false;
}
// card power up status bit (bit 31) is set when power up is complete (see table 5-1)
if (0x00==(r3[0]&0x80)) {
return false;
}
sdsc = (0==(r3[0]&0x40)); // CCS is bit 30 in OCR (see table 5-1)
// now the card identification is complete and we should be in data-transfer mode (see figure 7-1)
// we can switch clock frequency to fPP (max. 25 MHz) (see section 4.3/6.6.6)
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change clock speed
spi_set_baudrate_prescaler(SPI(FLASH_SDCARD_SPI), SPI_CR1_BR_FPCLK_DIV_4); // set clock speed to 18 MHz (72/4=18, < 25 MHz)
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
// send CMD9 (SEND_CSD) to get Card Specific Data (CSD) and calculate N_AC (see section 7.2.6)
uint8_t csd[16] = {0}; // CSD response (see chapter 7.2.6)
r1 = flash_sdcard_data_read(9, 0, csd, sizeof(csd)); // (see table 7-3)
if (r1) { // error occurred
return false;
}
// check if CSD structure version matches capacity (see section 5.3.1)
if ((sdsc && (csd[0]>>6)) || (!sdsc && 0==(csd[0]>>6))) {
return false;
}
// calculate N_AC value (we use our set minimum frequency 16 MHz to calculate time)
if (sdsc) { // calculate N_AC using TAAC and NSAC
static const float TAAC_UNITS[] = {1E-9, 10E-9, 100E-9, 1E-6, 10E-6, 100E-6, 1E-3, 10E-3}; // (see table 5-5)
static const float TAAC_VALUES[] = {10.0, 1.0, 1.2, 1.3, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 7.0, 8.0}; // (see table 5-5)
double taac = TAAC_VALUES[(csd[1]>>2)&0xf]*TAAC_UNITS[csd[1]&0x7]; // time in ns
n_ac=100*((taac*16E6)+(csd[2]*100))/8; // (see section 7.5.4)
} else { // value is fixed to 100 ms
n_ac=100E-3*16E6/8;
}
// calculate card size
if (sdsc) { // see section 5.3.2
uint16_t c_size = (((uint16_t)csd[6]&0x03)<<10)+((uint16_t)csd[7]<<2)+(csd[8]>>6);
uint8_t c_size_mutl = ((csd[9]&0x03)<<1)+((csd[10]&0x80)>>7);
uint8_t read_bl_len = (csd[5]&0x0f);
sdcard_size = ((c_size+1)*(1UL<<(c_size_mutl+2)))*(1UL<<read_bl_len);
} else { // see section 5.3.3
uint32_t c_size = ((uint32_t)(csd[7]&0x3f)<<16)+((uint16_t)csd[8]<<8)+csd[9];
sdcard_size = (c_size+1)*(512<<10);
}
// calculate erase size
if (sdsc) { // see section 5.3.2
erase_size = (((csd[10]&0x3f)<<1)+((csd[11]&0x80)>>7)+1)<<(((csd[12]&0x03)<<2)+(csd[13]>>6));
} else {
uint8_t status[64] = {0}; // SD status (see section 4.10.2)
uint16_t r2 = flash_sdcard_status(status); // get status (see table 7-4)
if (r2) { // error occurred
return false;
}
erase_size = (8192UL<<(status[10]>>4)); // calculate erase size (see table 4-44, section 4.10.2.4)
}
// ensure block length is 512 bytes for SDSC (should be per default) to we match SDHC/SDXC block size
if (sdsc) {
r1 = flash_sdcard_command_response(16, 512, NULL, 0); // set block size using CMD16 (SET_BLOCKLEN) (see table 7-3)
if (r1) { // error occurred
return false;
}
}
// try to switch to high speed mode (see section 7.2.14/4.3.10)
if (csd[4]&0x40) { // ensure CMD6 is supported by checking if command class 10 is set
uint32_t n_ac_back = n_ac; // backup N_AC
n_ac = 100E-3*16E6/8; // temporarily set timeout to 100 ms (see section 4.3.10.1)
// query access mode (group function 1) to check if high speed is supported (fPP=50MHz at 3.3V, we can be faster)
uint8_t fnc[64] = {0}; // function status response (see table 4-12)
r1 = flash_sdcard_data_read(6, 0x00fffff1, fnc, sizeof(fnc)); // check high speed function using CMD6 (SWITCH_FUNC) to check (mode 0) access mode (function group 1) (see table 7-3/4-30)
if (r1) { // error occurred
return false;
}
if (0x1==(fnc[16]&0x0f)) { // we can to access mode function 1 (see table 4-12)
r1 = flash_sdcard_data_read(6, 0x80fffff1, fnc, sizeof(fnc)); // switch to high speed function using CMD6 (SWITCH_FUNC) to switch (mode 1) access mode (function group 1) (see table 7-3/4-30)
if (r1) { // error occurred
return false;
}
if (0x1!=(fnc[16]&0x0f)) { // could not switch to high speed
return false;
}
// we can switch clock frequency to fPP (max. 50 MHz) (see section 6.6.7)
while (!(SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_TXE)); // wait until the end of any transmission
while (SPI_SR(SPI(FLASH_SDCARD_SPI))&SPI_SR_BSY); // wait until not busy before disabling
spi_disable(SPI(FLASH_SDCARD_SPI)); // disable SPI to change clock speed
spi_set_baudrate_prescaler(SPI(FLASH_SDCARD_SPI), SPI_CR1_BR_FPCLK_DIV_2); // set clock speed to 36 MHz (72/2=36 < 50 MHz)
spi_enable(SPI(FLASH_SDCARD_SPI)); // enable SPI back
n_ac_back /= 2; // since we go twice faster the N_AC timeout has to be halved
}
n_ac = n_ac_back; // restore N_AC
}
initialized = true;
return initialized;
}
uint64_t flash_sdcard_size(void)
{
return sdcard_size;
}
uint32_t flash_sdcard_erase_size(void)
{
return erase_size;
}
bool flash_sdcard_read_data(uint32_t block, uint8_t* data)
{
if (NULL==data) {
return false;
}
if (sdsc) { // the address for standard capacity cards must be provided in bytes
if (block>UINT32_MAX/512) { // check for integer overflow
return false;
} else {
block *= 512; // calculate byte address from block address
}
}
return (0==flash_sdcard_data_read(17, block, data, 512)); // read single data block using CMD17 (READ_SINGLE_BLOCK) (see table 7-3)
}
bool flash_sdcard_write_data(uint32_t block, uint8_t* data)
{
if (NULL==data) {
return false;
}
if (sdsc) { // the address for standard capacity cards must be provided in bytes
if (block>UINT32_MAX/512) { // check for integer overflow
return false;
} else {
block *= 512; // calculate byte address from block address
}
}
uint8_t drt = flash_sdcard_data_write(24, block, data, 512); // write single data block using CMD24 (WRITE_SINGLE_BLOCK) (see table 7-3)
if (0x05!=(drt&0x1f)) { // write block failed
return false;
}
// get status to check if programming succeeded
uint8_t r2[1] = {0}; // to store response token R2 (see section 7.3.2.3)
uint8_t r1 = flash_sdcard_command_response(13, 0, r2, sizeof(r2)); // get SD status using CMD13 (SEND_STATUS) (see table 7-3)
if (0x00!=r1) { // error occurred
return false;
} else if (r2[0]) { // programming error
return false;
}
return true; // programming succeeded
}

View File

@ -1,47 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with an SD card flash memory using the SPI mode (API)
* @file flash_sdcard.h
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017
* @note peripherals used: SPI @ref flash_sdcard_spi
* @warning all calls are blocking
*/
#pragma once
/** setup communication with SD card
* @return if card has been initialized correctly
*/
bool flash_sdcard_setup(void);
/** get size of SD card flash memory
* @return size of SD card flash memory (in bytes)
*/
uint64_t flash_sdcard_size(void);
/** get size of a erase block
* @return size of a erase block (in bytes)
*/
uint32_t flash_sdcard_erase_size(void);
/** read data on flash of SD card
* @param[in] block address of data to read (in block in 512 bytes unit)
* @param[out] data data block to read (with a size of 512 bytes)
* @return if read succeeded
*/
bool flash_sdcard_read_data(uint32_t block, uint8_t* data);
/** write data on flash of SD card
* @param[in] block address of data to write (in block in 512 bytes unit)
* @param[in] data data block to write (with a size of 512 bytes)
* @return if write succeeded
*/
bool flash_sdcard_write_data(uint32_t block, uint8_t* data);

View File

@ -1,735 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate using I²C as master (code)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: I2C
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/cm3/systick.h> // SysTick library
#include <libopencm3/cm3/assert.h> // assert utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/i2c.h> // I²C library
/* own libraries */
#include "global.h" // global utilities
#include "i2c_master.h" // I²C header and definitions
/** get RCC for I²C based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC address for I²C peripheral
*/
static uint32_t RCC_I2C(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
return RCC_I2C1;
break;
case I2C2:
return RCC_I2C2;
break;
default:
return 0;
}
}
/** get RCC for GPIO port for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC GPIO address
*/
static uint32_t RCC_GPIO_PORT_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
case I2C2:
return RCC_GPIOB;
break;
default:
return 0;
}
}
/** get RCC for GPIO port for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return RCC GPIO address
*/
static uint32_t RCC_GPIO_PORT_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
case I2C2:
return RCC_GPIOB;
break;
default:
return 0;
}
}
/** get GPIO port for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PORT_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_BANK_I2C1_RE_SCL;
} else {
return GPIO_BANK_I2C1_SCL;
}
break;
case I2C2:
return GPIO_BANK_I2C2_SCL;
break;
default:
return 0;
}
}
/** get GPIO port for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PORT_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_BANK_I2C1_RE_SDA;
} else {
return GPIO_BANK_I2C1_SDA;
}
break;
case I2C2:
return GPIO_BANK_I2C2_SDA;
break;
default:
return 0;
}
}
/** get GPIO pin for SCL pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PIN_SCL(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_I2C1_RE_SCL;
} else {
return GPIO_I2C1_SCL;
}
break;
case I2C2:
return GPIO_I2C2_SCL;
break;
default:
return 0;
}
}
/** get GPIO pin for SDA pin based on I²C identifier
* @param[in] i2c I²C base address
* @return GPIO address
*/
static uint32_t GPIO_PIN_SDA(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
switch (i2c) {
case I2C1:
if (AFIO_MAPR & AFIO_MAPR_I2C1_REMAP) {
return GPIO_I2C1_RE_SDA;
} else {
return GPIO_I2C1_SDA;
}
break;
case I2C2:
return GPIO_I2C2_SDA;
break;
default:
return 0;
}
}
void i2c_master_setup(uint32_t i2c, uint16_t frequency)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// configure I²C peripheral
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // already put signal high to avoid small pulse
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // setup I²C I/O pins
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C I/O peripheral
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // already put signal high to avoid small pulse
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // setup I²C I/O pins
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternate function
rcc_periph_clock_enable(RCC_I2C(i2c)); // enable clock for I²C peripheral
i2c_reset(i2c); // reset peripheral domain
i2c_peripheral_disable(i2c); // I²C needs to be disable to be configured
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset peripheral
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // clear peripheral reset
if (0==frequency) { // don't allow null frequency
frequency = 1;
} else if (frequency > 400) { // limit frequency to 400 kHz
frequency = 400;
}
i2c_set_clock_frequency(i2c, rcc_apb1_frequency / 1000000); // configure the peripheral clock to the APB1 freq (where it is connected to)
if (frequency>100) { // use fast mode for frequencies over 100 kHz
i2c_set_fast_mode(i2c); // set fast mode (Fm)
i2c_set_ccr(i2c, rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency (fast duty not used)
i2c_set_trise(i2c, (300 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Fm mode (< 400) kHz is 300 ns
} else { // use fast mode for frequencies below 100 kHz
i2c_set_standard_mode(i2c); // set standard mode (Sm)
i2c_set_ccr(i2c, rcc_apb1_frequency / (frequency * 1000 * 2)); // set Thigh/Tlow to generate frequency of 100 kHz
i2c_set_trise(i2c, (1000 / (1000 / (rcc_apb1_frequency / 1000000))) + 1); // max rise time for Sm mode (< 100 kHz) is 1000 ns (~1 MHz)
}
i2c_peripheral_enable(i2c); // enable I²C after configuration completed
}
void i2c_master_release(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
i2c_reset(i2c); // reset I²C peripheral configuration
i2c_peripheral_disable(i2c); // disable I²C peripheral
rcc_periph_clock_disable(RCC_I2C(i2c)); // disable clock for I²C peripheral
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SCL(i2c)); // put I²C I/O pins back to floating
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN_SDA(i2c)); // put I²C I/O pins back to floating
}
bool i2c_master_check_signals(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// enable GPIOs to read SDA and SCL
rcc_periph_clock_enable(RCC_GPIO_PORT_SDA(i2c)); // enable clock for I²C I/O peripheral
rcc_periph_clock_enable(RCC_GPIO_PORT_SCL(i2c)); // enable clock for I²C I/O peripheral
// pull SDA and SDC low to check if there are pull-up resistors
uint32_t sda_crl = GPIO_CRL(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t sda_crh = GPIO_CRH(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t sda_bsrr = GPIO_BSRR(GPIO_PORT_SDA(i2c)); // backup port configuration
uint32_t scl_crl = GPIO_CRL(GPIO_PORT_SCL(i2c)); // backup port configuration
uint32_t scl_crh = GPIO_CRH(GPIO_PORT_SCL(i2c)); // backup port configuration
uint32_t scl_bsrr = GPIO_BSRR(GPIO_PORT_SCL(i2c)); // backup port configuration
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SDA(i2c)); // configure signal as pull down
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_PIN_SCL(i2c)); // configure signal as pull down
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // pull down
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // pull down
bool to_return = (0 != gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)) && 0 != gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))); // check if the signals are still pulled high by external stronger pull-up resistors
GPIO_CRL(GPIO_PORT_SDA(i2c)) = sda_crl; // restore port configuration
GPIO_CRH(GPIO_PORT_SDA(i2c)) = sda_crh; // restore port configuration
GPIO_BSRR(GPIO_PORT_SDA(i2c)) = sda_bsrr; // restore port configuration
GPIO_CRL(GPIO_PORT_SCL(i2c)) = scl_crl; // restore port configuration
GPIO_CRH(GPIO_PORT_SCL(i2c)) = scl_crh; // restore port configuration
GPIO_BSRR(GPIO_PORT_SCL(i2c)) = scl_bsrr; // restore port configuration
return to_return;
}
bool i2c_master_reset(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
bool to_return = true;
// follow procedure described in STM32F10xxC/D/E Errata sheet, Section 2.14.7
i2c_peripheral_disable(i2c); // disable I²C peripheral
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SCL(i2c)); // put I²C I/O pins to general output
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high
to_return &= !gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN_SDA(i2c)); // put I²C I/O pins to general output
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_clear(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set low (try first transition)
to_return &= gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is low
gpio_clear(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set low (try first transition)
to_return &= gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is low
gpio_set(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)); // ensure it is high
gpio_set(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // set high (try second transition)
to_return &= !gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c)); // ensure it is high
gpio_set_mode(GPIO_PORT_SCL(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SCL(i2c)); // set I²C I/O pins back
gpio_set_mode(GPIO_PORT_SDA(i2c), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO_PIN_SDA(i2c)); // set I²C I/O pins back
I2C_CR1(i2c) |= I2C_CR1_SWRST; // reset device
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // reset device
i2c_peripheral_enable(i2c); // re-enable device
return to_return;
}
enum i2c_master_rc i2c_master_start(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
bool retry = true; // retry after reset if first try failed
enum i2c_master_rc to_return; // return code
uint16_t sr1; // read register once, since reading/writing other registers or other events clears some flags
try:
to_return = I2C_MASTER_RC_NONE; // return code
// send (re-)start condition
if (I2C_CR1(i2c) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of I²C peripheral in master mode after a misplaced Stop)
systick_counter_disable(); // disable SysTick to reconfigure it
systick_set_frequency(500, rcc_ahb_frequency); // set timer to 2 ms (that should be long enough to send a start condition)
systick_clear(); // reset SysTick (set to 0)
systick_interrupt_disable(); // disable interrupt to prevent ISR to read the flag
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
i2c_send_start(i2c); // send start condition to start transaction
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(i2c) & I2C_CR1_START) && !((sr1 = I2C_SR1(i2c)) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until start condition has been accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(i2c); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_SB | I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout && I2C_MASTER_RC_NONE == to_return) { // wait until start condition is transmitted
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
sr1 = I2C_SR1(i2c); // be sure to get the current value
if (sr1 & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
} else if (!(sr1 & I2C_SR1_SB)) { // the start bit has not been set although we the peripheral is not busy anymore
to_return = I2C_MASTER_RC_BUS_ERROR;
} else if (!(sr1 & I2C_SR2_MSL)) { // verify if in master mode
to_return = I2C_MASTER_RC_NOT_MASTER;
} else if (timeout) { // timeout has been reached, i.e. the peripheral hangs
to_return = I2C_MASTER_RC_NOT_MASTER;
}
if (I2C_MASTER_RC_NOT_MASTER == to_return && retry) { // error happened
retry = false; // don't retry a second time
I2C_CR1(i2c) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // release peripheral reset
goto try;
}
systick_counter_disable(); // we don't need to timer anymore
return to_return;
}
/** wait until stop is sent and bus is released
* @param[in] i2c I²C base address
* @return I²C return code
*/
static enum i2c_master_rc i2c_master_wait_stop(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc to_return = I2C_MASTER_RC_NONE; // return code
// prepare timer in case the peripheral hangs on sending stop condition (see errata 2.14.4 Wrong behavior of I²C peripheral in master mode after a misplaced Stop)
systick_counter_disable(); // disable SysTick to reconfigure it
systick_set_frequency(500, rcc_ahb_frequency); // set timer to 2 ms (that should be long enough to send a stop condition)
systick_clear(); // reset SysTick (set to 0)
systick_interrupt_disable(); // disable interrupt to prevent ISR to read the flag
systick_get_countflag(); // reset flag (set when counter is going for 1 to 0)
bool timeout = false; // remember if the timeout has been reached
systick_counter_enable(); // start timer
while ((I2C_CR1(i2c) & I2C_CR1_STOP) && !(I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until stop condition is accepted and cleared
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while ((I2C_SR2(i2c) & I2C_SR2_MSL) && !(I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) && !timeout) { // wait until bus released (non master mode)
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while ((I2C_SR2(i2c) & I2C_SR2_BUSY) && !(I2C_SR1(i2c) & (I2C_SR1_BERR)) && !timeout) { // wait until peripheral is not busy anymore
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
to_return = I2C_MASTER_RC_BUS_ERROR;
}
while ((0 == gpio_get(GPIO_PORT_SCL(i2c), GPIO_PIN_SCL(i2c)) || 0 == gpio_get(GPIO_PORT_SDA(i2c), GPIO_PIN_SDA(i2c))) && !timeout) { // wait until lines are really high again
timeout |= systick_get_countflag(); // verify if timeout has been reached
}
if (timeout) { // I2C_CR1_STOP could also be used to detect a timeout, but I'm not sure when
if (I2C_MASTER_RC_NONE == to_return) {
to_return = I2C_MASTER_RC_TIMEOUT; // indicate timeout only when no more specific error has occurred
}
I2C_CR1(i2c) |= I2C_CR1_SWRST; // assert peripheral reset
I2C_CR1(i2c) &= ~I2C_CR1_SWRST; // release peripheral reset
}
systick_counter_disable(); // we don't need to timer anymore
return to_return;
}
enum i2c_master_rc i2c_master_stop(uint32_t i2c)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// sanity check
if (!(I2C_SR2(i2c) & I2C_SR2_BUSY)) { // release is not busy
return I2C_MASTER_RC_NONE; // bus has probably already been released
}
if (I2C_CR1(i2c) & (I2C_CR1_START | I2C_CR1_STOP)) { // ensure start or stop operations are not in progress
return I2C_MASTER_RC_START_STOP_IN_PROGESS;
}
if (!((I2C_SR2(i2c) & I2C_SR2_TRA))) { // if we are in receiver mode
i2c_disable_ack(i2c); // disable ACK to be able to close the communication
}
i2c_send_stop(i2c); // send stop to release bus
return i2c_master_wait_stop(i2c);
}
enum i2c_master_rc i2c_master_select_slave(uint32_t i2c, uint16_t slave, bool address_10bit, bool write)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
uint16_t sr1, sr2; // read register once, since reading/writing other registers or other events clears some flags
if (!((sr1 = I2C_SR1(i2c)) & I2C_SR1_SB)) { // start condition has not been sent
rc = i2c_master_start(i2c); // send start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
}
if (!((sr2 = I2C_SR2(i2c)) & I2C_SR2_MSL)) { // I²C device is not in master mode
return I2C_MASTER_RC_NOT_MASTER;
}
// select slave
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
if (!address_10bit) { // 7-bit address
i2c_send_7bit_address(i2c, slave, write ? I2C_WRITE : I2C_READ); // select slave, with read/write flag
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (sr1 & I2C_SR1_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
}
} else { // 10-bit address
// send first part of address
I2C_DR(i2c) = 11110000 | (((slave >> 8 ) & 0x3) << 1); // send first header (11110xx0, where xx are 2 MSb of slave address)
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADD10 | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until first part of address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (sr1 & I2C_SR1_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
}
// send second part of address
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_DR(i2c) = (slave & 0xff); // send remaining of address
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (sr1 & I2C_SR1_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
}
// go into receive mode if necessary
if (!write) {
rc = i2c_master_start(i2c); // send start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
// send first part of address with receive flag
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
I2C_DR(i2c) = 11110001 | (((slave >> 8) & 0x3) << 1); // send header (11110xx1, where xx are 2 MSb of slave address)
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_ADDR | I2C_SR1_AF | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until remaining part of address is transmitted
if (sr1 & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (sr1 & I2C_SR1_AF) { // address has not been acknowledged
return I2C_MASTER_RC_NAK;
}
}
}
// do not check I2C_SR2_TRA to verify if we really are in transmit or receive mode since reading SR2 also clears ADDR and starting the read/write transaction
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_read(uint32_t i2c, uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// sanity check
if (NULL == data || 0 == data_size) { // no data to read
return I2C_MASTER_RC_NONE;
}
// I²C start condition check
uint16_t sr1 = I2C_SR1(i2c); // read once
if (!(sr1 & I2C_SR1_ADDR)) { // no slave have been selected
return I2C_MASTER_RC_NOT_READY;
}
if (sr1 & I2C_SR1_AF) { // check if the previous transaction went well
return I2C_MASTER_RC_NOT_READY;
}
// prepare (N)ACK (EV6_3 in RM0008)
if (1 == data_size) {
i2c_disable_ack(i2c); // NACK after first byte
} else {
i2c_enable_ack(i2c); // NAK after next byte
}
uint16_t sr2 = I2C_SR2(i2c); // reading SR2 will also also clear ADDR in SR1 and start the transaction
if (!(sr2 & I2C_SR2_MSL)) { // I²C device is not master
return I2C_MASTER_RC_NOT_MASTER;
}
if ((sr2 & I2C_SR2_TRA)) { // I²C device not in receiver mode
return I2C_MASTER_RC_NOT_RECEIVE;
}
// read data
for (size_t i = 0; i < data_size; i++) { // read bytes
// set (N)ACK (EV6_3, EV6_1)
if (1 == data_size - i) { // prepare to sent NACK for last byte
i2c_send_stop(i2c); // already indicate we will send a stop (required to not send an ACK, and this must happen before the byte is transferred, see errata)
i2c_nack_current(i2c); // (N)ACK current byte
i2c_disable_ack(i2c); // NACK received to stop slave transmission
} else if (2 == data_size - i) { // prepare to sent NACK for second last byte
i2c_nack_next(i2c); // NACK next byte
i2c_disable_ack(i2c); // NACK received to stop slave transmission
} else {
i2c_enable_ack(i2c); // ACK received byte to continue slave transmission
}
while (!((sr1 = I2C_SR1(i2c)) & (I2C_SR1_RxNE | I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been received
if (sr1 & (I2C_SR1_BERR|I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
data[i] = i2c_get_data(i2c); // read received byte
}
return i2c_master_wait_stop(i2c);
}
enum i2c_master_rc i2c_master_write(uint32_t i2c, const uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
// sanity check
if (NULL == data || 0 == data_size) { // no data to write
return I2C_MASTER_RC_NONE;
}
// I²C start condition check
uint16_t sr1 = I2C_SR1(i2c); // read once
if (!(sr1 & I2C_SR1_ADDR)) { // no slave have been selected
return I2C_MASTER_RC_NOT_READY;
}
if (sr1 & I2C_SR1_AF) { // check if the previous transaction went well
return I2C_MASTER_RC_NOT_READY;
}
// master check
uint16_t sr2 = I2C_SR2(i2c); // reading SR2 will also also clear ADDR in SR1 and start the transaction
if (!(sr2 & I2C_SR2_MSL)) { // I²C device is not master
return I2C_MASTER_RC_NOT_MASTER;
}
if (!(sr2 & I2C_SR2_TRA)) { // I²C device not in transmitter mode
return I2C_MASTER_RC_NOT_TRANSMIT;
}
// write data
for (size_t i = 0; i < data_size; i++) { // write bytes
I2C_SR1(i2c) &= ~(I2C_SR1_AF); // clear acknowledgement failure
i2c_send_data(i2c, data[i]); // send byte to be written in memory
while (!(I2C_SR1(i2c) & (I2C_SR1_TxE | I2C_SR1_AF)) && !(I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO))); // wait until byte has been transmitted
if (I2C_SR1(i2c) & (I2C_SR1_BERR | I2C_SR1_ARLO)) {
return I2C_MASTER_RC_BUS_ERROR;
}
if (I2C_SR1(i2c) & I2C_SR1_AF) { // data has not been acknowledged
return I2C_MASTER_RC_NAK;
}
}
return I2C_MASTER_RC_NONE;
}
enum i2c_master_rc i2c_master_slave_read(uint32_t i2c, uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, 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(i2c, data, data_size); // read data (includes stop)
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
} else {
i2c_master_stop(i2c); // sent stop condition
}
rc = I2C_MASTER_RC_NONE; // all went well
error:
if (I2C_MASTER_RC_NONE != rc) {
i2c_master_stop(i2c); // sent stop condition
}
return rc;
}
enum i2c_master_rc i2c_master_slave_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, 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(i2c, 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(i2c); // sent stop condition
return rc;
}
enum i2c_master_rc i2c_master_address_read(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, 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(i2c, 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(i2c); // send re-start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, false); // select slave to read
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
rc = i2c_master_read(i2c, data, data_size); // read memory (includes stop)
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
} else {
i2c_master_stop(i2c); // sent stop condition
}
rc = I2C_MASTER_RC_NONE;
error:
if (I2C_MASTER_RC_NONE != rc) { // only send stop on error
i2c_master_stop(i2c); // sent stop condition
}
return rc;
}
enum i2c_master_rc i2c_master_address_write(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size)
{
cm3_assert(I2C1 == i2c || I2C2 == i2c);
if (SIZE_MAX - address_size < data_size) { // prevent integer overflow
return I2C_MASTER_RC_OTHER;
}
if (address_size + data_size > 10 * 1024) { // we won't enough RAM
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;
}
uint8_t buffer[address_size + data_size];
enum i2c_master_rc rc = I2C_MASTER_RC_NONE; // to store I²C return codes
rc = i2c_master_start(i2c); // send (re-)start condition
if (I2C_MASTER_RC_NONE != rc) {
return rc;
}
rc = i2c_master_select_slave(i2c, slave, address_10bit, true); // select slave to write
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
// we can't send the address then the data size short address will cause a stop (because of how crappy the STM32F10x I²C peripheral is)
if (address) {
for (size_t i = 0; i < address_size; i++) {
buffer[i] = address[i];
}
}
if (data) {
for (size_t i = 0; i < data_size; i++) {
buffer[address_size + i] = data[i];
}
}
rc = i2c_master_write(i2c, buffer, address_size + data_size); // send memory address
if (I2C_MASTER_RC_NONE != rc) {
goto error;
}
error:
rc = i2c_master_stop(i2c); // sent stop condition
return rc;
}

View File

@ -1,139 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate using I²C as master (API)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: I2C
*/
#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] i2c I²C base address
* @param[in] frequency frequency to use in kHz (1-400)
* @note Standard mode (Sm) is used for frequencies up to 100 kHz, and Fast mode (Fm) is used for frequencies up to 400 kHz
*/
void i2c_master_setup(uint32_t i2c, uint16_t frequency);
/** release I²C peripheral
* @param[in] i2c I²C base address
*/
void i2c_master_release(uint32_t i2c);
/** reset I2C peripheral, fixing any locked state
* @warning the I2C peripheral needs to be re-setup
* @note to be used after failed start or stop, and bus error
* @param[in] i2c I2C base address
* @return true if complete reset is successful, false if the lines could not be set
*/
bool i2c_master_reset(uint32_t i2c);
/** check if SDA and SCL signals are high
* @param[in] i2c I²C base address
* @return SDA and SCL signals are high
*/
bool i2c_master_check_signals(uint32_t i2c);
/** send start condition
* @param[in] i2c I²C base address
* @return I2C return code
*/
enum i2c_master_rc i2c_master_start(uint32_t i2c);
/** select I²C slave device
* @warning a start condition should be sent before this operation
* @param[in] i2c I²C base address
* @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(uint32_t i2c, uint16_t slave, bool address_10bit, bool write);
/** read data over I²C
* @param[in] i2c I²C base address
* @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(uint32_t i2c, uint8_t* data, size_t data_size);
/** write data over I²C
* @param[in] i2c I²C base address
* @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(uint32_t i2c, const uint8_t* data, size_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(uint32_t i2c);
/** read data from slave device
* @param[in] i2c I²C base address
* @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(uint32_t i2c, uint16_t slave, bool address_10bit, uint8_t* data, size_t data_size);
/** write data to slave device
* @param[in] i2c I²C base address
* @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(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* data, size_t data_size);
/** read data at specific address from an I²C memory slave
* @param[in] i2c I²C base address
* @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(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, uint8_t* data, size_t data_size);
/** write data at specific address on an I²C memory slave
* @param[in] i2c I²C base address
* @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(uint32_t i2c, uint16_t slave, bool address_10bit, const uint8_t* address, size_t address_size, const uint8_t* data, size_t data_size);

View File

@ -1,120 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** BusVoodoo runtime interrupt table
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018
*/
#include "interrupt.h" // own definitions
vector_table_entry_t interrupt_table[NVIC_IRQ_COUNT] = {0};
/** handler intercepting interrupts and running the function provided in the table */
static void isr_handler(void)
{
// get current IRQ number
uint8_t irq = 0;
if (NVIC_IABR(0)) {
irq = __builtin_ffs(NVIC_IABR(0))-1;
} else if (NVIC_IABR(1)) {
irq = __builtin_ffs(NVIC_IABR(1))+31;
} else if (NVIC_IABR(2)) {
irq = __builtin_ffs(NVIC_IABR(2))+64;
} else {
while (true);
}
// check if the it's a valid IRQ number
if (irq >= NVIC_IRQ_COUNT) {
while (true);
}
// run user provided ISR
if (interrupt_table[irq]) {
(*(void(*)(void))(interrupt_table[irq]))();
} else {
while (true);
}
}
/** use the isr_handler as default ISR
* @note the ISR can still point to other defined function
* @remark from libopencm3/stm32/f1/vector_nvic.c
*/
#pragma weak wwdg_isr = isr_handler
#pragma weak pvd_isr = isr_handler
#pragma weak tamper_isr = isr_handler
#pragma weak rtc_isr = isr_handler
#pragma weak flash_isr = isr_handler
#pragma weak rcc_isr = isr_handler
#pragma weak exti0_isr = isr_handler
#pragma weak exti1_isr = isr_handler
#pragma weak exti2_isr = isr_handler
#pragma weak exti3_isr = isr_handler
#pragma weak exti4_isr = isr_handler
#pragma weak dma1_channel1_isr = isr_handler
#pragma weak dma1_channel2_isr = isr_handler
#pragma weak dma1_channel3_isr = isr_handler
#pragma weak dma1_channel4_isr = isr_handler
#pragma weak dma1_channel5_isr = isr_handler
#pragma weak dma1_channel6_isr = isr_handler
#pragma weak dma1_channel7_isr = isr_handler
#pragma weak adc1_2_isr = isr_handler
#pragma weak usb_hp_can_tx_isr = isr_handler
#pragma weak usb_lp_can_rx0_isr = isr_handler
#pragma weak can_rx1_isr = isr_handler
#pragma weak can_sce_isr = isr_handler
#pragma weak exti9_5_isr = isr_handler
#pragma weak tim1_brk_isr = isr_handler
#pragma weak tim1_up_isr = isr_handler
#pragma weak tim1_trg_com_isr = isr_handler
#pragma weak tim1_cc_isr = isr_handler
#pragma weak tim2_isr = isr_handler
#pragma weak tim3_isr = isr_handler
#pragma weak tim4_isr = isr_handler
#pragma weak i2c1_ev_isr = isr_handler
#pragma weak i2c1_er_isr = isr_handler
#pragma weak i2c2_ev_isr = isr_handler
#pragma weak i2c2_er_isr = isr_handler
#pragma weak spi1_isr = isr_handler
#pragma weak spi2_isr = isr_handler
#pragma weak usart1_isr = isr_handler
#pragma weak usart2_isr = isr_handler
#pragma weak usart3_isr = isr_handler
#pragma weak exti15_10_isr = isr_handler
#pragma weak rtc_alarm_isr = isr_handler
#pragma weak usb_wakeup_isr = isr_handler
#pragma weak tim8_brk_isr = isr_handler
#pragma weak tim8_up_isr = isr_handler
#pragma weak tim8_trg_com_isr = isr_handler
#pragma weak tim8_cc_isr = isr_handler
#pragma weak adc3_isr = isr_handler
#pragma weak fsmc_isr = isr_handler
#pragma weak sdio_isr = isr_handler
#pragma weak tim5_isr = isr_handler
#pragma weak spi3_isr = isr_handler
#pragma weak uart4_isr = isr_handler
#pragma weak uart5_isr = isr_handler
#pragma weak tim6_isr = isr_handler
#pragma weak tim7_isr = isr_handler
#pragma weak dma2_channel1_isr = isr_handler
#pragma weak dma2_channel2_isr = isr_handler
#pragma weak dma2_channel3_isr = isr_handler
#pragma weak dma2_channel4_5_isr = isr_handler
#pragma weak dma2_channel5_isr = isr_handler
#pragma weak eth_isr = isr_handler
#pragma weak eth_wkup_isr = isr_handler
#pragma weak can2_tx_isr = isr_handler
#pragma weak can2_rx0_isr = isr_handler
#pragma weak can2_rx1_isr = isr_handler
#pragma weak can2_sce_isr = isr_handler
#pragma weak otg_fs_isr = isr_handler

View File

@ -1,26 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** BusVoodoo runtime interrupt table
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018
*/
#pragma once
#include "libopencm3/cm3/nvic.h"
#include "libopencm3/cm3/vector.h"
/** table of interrupts which can set to user functions
* @note only interrupt using the default handler can be intercepted
*/
extern vector_table_entry_t interrupt_table[NVIC_IRQ_COUNT];

View File

@ -1,202 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to decode InfraRed NEC code
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018-2020
* @note peripherals used: timer channel @ref ir_nec_timer
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // standard boolean type
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
/* own libraries */
#include "ir_nec.h" // own definitions
#include "global.h" // common methods
/** @defgroup ir_nec_timer timer peripheral used to measure signal timing for code decoding
* @{
*/
#define IR_NEC_TIMER 4 /**< timer peripheral */
#define IR_NEC_CHANNEL 3 /**< channel used as input capture */
#define IR_NEC_JITTER 40 /**< signal timing jitter in % tolerated in timing */
/** @} */
volatile bool ir_nec_code_received_flag = false;
struct ir_nec_code_t ir_nec_code_received;
/** if the extended address in the code is used
* the extended address uses all 16-bits instead of having redundant/robust 2x8-bits address
*/
static bool ir_nec_extended = false;
void ir_nec_setup(bool extended)
{
ir_nec_extended = extended; // remember setting
// setup timer to measure signal timing for bit decoding (use timer channel as input capture)
rcc_periph_clock_enable(RCC_TIM_CH(IR_NEC_TIMER, IR_NEC_CHANNEL)); // enable clock for GPIO peripheral
rcc_periph_clock_enable(RCC_TIM(IR_NEC_TIMER)); // enable clock for timer peripheral
rcc_periph_clock_enable(RCC_AFIO); // enable clock for alternative functions
gpio_set(TIM_CH_PORT(IR_NEC_TIMER, IR_NEC_CHANNEL), TIM_CH_PIN(IR_NEC_TIMER, IR_NEC_CHANNEL)); // idle is high (using pull-up resistor)
gpio_set_mode(TIM_CH_PORT(IR_NEC_TIMER, IR_NEC_CHANNEL), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, TIM_CH_PIN(IR_NEC_TIMER, IR_NEC_CHANNEL)); // setup GPIO pin as input
rcc_periph_reset_pulse(RST_TIM(IR_NEC_TIMER)); // reset timer state
timer_set_mode(TIM(IR_NEC_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
// codes are repeated every 110 ms, thus we need to measure at least this duration to detect repeats correctly
// the 16-bit timer is by far precise enough to measure the smallest 560 us burst
timer_set_prescaler(TIM(IR_NEC_TIMER), (110 * (100 + IR_NEC_JITTER) / 100 * (rcc_ahb_frequency / 1000) / (1 << 16)) + 1 - 1); // set the prescaler so this 16 bits timer allows to wait for 110 ms (+ jitter) from the start signal
timer_ic_set_input(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_IN_TI(IR_NEC_CHANNEL)); // configure ICx to use TIn
timer_ic_set_filter(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_CK_INT_N_8); // use small filter (noise reduction is more important than timing)
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_FALLING); // capture on falling edge (IR bursts are active low on IR demodulators)
timer_ic_set_prescaler(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_clear_flag(TIM(IR_NEC_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(IR_NEC_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(IR_NEC_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
timer_clear_flag(TIM(IR_NEC_TIMER), TIM_SR_CCIF(IR_NEC_CHANNEL)); // clear input compare flag
timer_ic_enable(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL)); // enable capture interrupt only when IR burst
timer_enable_irq(TIM(IR_NEC_TIMER), TIM_DIER_CCIE(IR_NEC_CHANNEL)); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(IR_NEC_TIMER)); // catch interrupt in service routine
timer_enable_counter(TIM(IR_NEC_TIMER)); // enable timer
}
/** interrupt service routine called for timer
*
* @remark normally we want to keep the ISR as short as possible, and do the processing in the main loop,
* @remark but because the code needs to be decoded in order to detect repeat burst correctly,
* @remark we do the decoding in the ISR and don't trust the user to run the decoding within 42.42 ms (time until the next code is sent)
*
* @note we don't enforce 110 ms between codes (they can be received earlier), but recognize repeat code after 110 ms
*/
void TIM_ISR(IR_NEC_TIMER)(void)
{
static uint8_t burst_count = 0; // the mark or space count
static uint32_t burst_start = 0; // time of current mark/space start
static uint32_t bits = 0; // the received code bits
static struct ir_nec_code_t code; // the last code received (don't trust the user exposed ir_nec_code_received)
static bool valid = false; // if the last IR activity is a valid code
if (timer_get_flag(TIM(IR_NEC_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(IR_NEC_TIMER), TIM_SR_UIF); // clear flag
goto error; // no code or repeat code has been received in time
} else if (timer_get_flag(TIM(IR_NEC_TIMER), TIM_SR_CCIF(IR_NEC_CHANNEL))) { // edge detected on input capture
uint32_t time = TIM_CCR(IR_NEC_TIMER, IR_NEC_CHANNEL); // save captured bit timing (this also clears the flag)
time = (time * (TIM_PSC(TIM(IR_NEC_TIMER)) + 1)) / (rcc_ahb_frequency / 1000000); // calculate time in us
if (time < burst_start) { // this should not happen
goto error;
}
time -= burst_start; // calculate mark/space burst time
if (0 == burst_count) { // start of very first IR mark for the AGC burst
timer_set_counter(TIM(IR_NEC_TIMER), 0); // reset timer counter
burst_start = 0; // reset code timer
time = 0; // ignore first burst
} else if (1 == burst_count) { // end of AGC mark
if (time > 9000 * (100 - IR_NEC_JITTER) / 100 && time < 9000 * (100 + IR_NEC_JITTER) / 100) { // AGC mark
} else {
goto error;
}
} else if (2 == burst_count) { // end of AGC space
if (time > 4500 * (100 - IR_NEC_JITTER) / 100 && time < 4500 * (100 + IR_NEC_JITTER) / 100) { // AGC code space
bits = 0; // reset previously received bits
valid = false; // invalidate previously received code (since this is not a repeat)
} else if (time > 2250 * (100 - IR_NEC_JITTER) / 100 && time < 2250 * (100 + IR_NEC_JITTER) / 100) { // AGC repeat space
if (valid) {
code.repeat = true;
ir_nec_code_received.repeat = code.repeat;
ir_nec_code_received.address = code.address;
ir_nec_code_received.command = code.command;
ir_nec_code_received_flag = true;
goto reset; // wait for next code
} else {
goto error;
}
} else {
goto reset; // not the correct header
}
} else if (burst_count <= (1 + 32) * 2) { // the code bits
if (burst_count % 2) { // bit mark end
if (time > 560 * (100 - IR_NEC_JITTER) / 100 && time < 560 * (100 + IR_NEC_JITTER) / 100) { // bit mark
} else {
goto error;
}
} else { // bit space end
bits <<= 1;
if (time > (2250 - 560) * (100 - IR_NEC_JITTER) / 100 && time < (2250 - 560) * (100 + IR_NEC_JITTER) / 100) { // bit 1space
bits |= 1; // save bit
} else if (time > (1125 - 560) * (100 - IR_NEC_JITTER) / 100 && time < (1125 - 560) * (100 + IR_NEC_JITTER) / 100) { // bit 0 space
bits |= 0; // save bit
} else {
goto error;
}
}
if ((1 + 32) * 2 == burst_count) { // the code is complete
uint8_t address = (bits >> 24) & 0xff; // get 8 address bits
uint8_t naddress = (bits >> 16) & 0xff; // get negated 8 address bits
uint8_t command = (bits >> 8) & 0xff; // get 8 command bits
uint8_t ncommand = (bits >> 0) & 0xff; // get negate 8 commend bits
if (!ir_nec_extended) { // the 8-bits address has its inverse
if (0xff != (address ^ naddress)) { // the address and its inverse do not match
goto error;
}
}
if (0xff != (command ^ ncommand)) { // the command and its inverse do not match
goto error;
}
valid = true; // remember we have a valid signal
code.repeat = false; // this is not a repeat code
if (ir_nec_extended) {
code.address = (address << 8) + naddress;
} else {
code.address = address; // save decoded address
}
code.command = command; // save decoded command
ir_nec_code_received.repeat = code.repeat; // transfer code to user
ir_nec_code_received.address = code.address; // transfer code to user
ir_nec_code_received.command = code.command; // transfer code to user
ir_nec_code_received_flag = true;
ir_nec_code_received_flag = true; // notify user about the new code
goto reset; // wait for next code
}
} else { // this should not happen
goto error;
}
if (burst_count % 2) {
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_FALLING); // wait for end of space
} else {
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_RISING); // wait for end of mark
}
burst_count++; // wait for next burst
burst_start += time; // save current burst start
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
return;
error:
valid = false; // invalidate previously received code
reset:
timer_ic_set_polarity(TIM(IR_NEC_TIMER), TIM_IC(IR_NEC_CHANNEL), TIM_IC_FALLING); // wait for next IR mark burst
burst_count = 0; // reset state
burst_start = 0; // reset state
}

View File

@ -1,39 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to decode InfraRed NEC code
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018-2020
* @note peripherals used: timer channel @ref ir_nec_timer
*/
#pragma once
/** set when an IR NEC code has been received */
extern volatile bool ir_nec_code_received_flag;
/** IR NEC code */
struct ir_nec_code_t {
bool repeat; /**< if this is only a code repeat (received 42.42 ms after the code, 98.75 ms after a repeat) */
uint16_t address; /**< code address (8-bit for non-extended, 16-bit for extended) */
uint8_t command; /**< code command */
};
/** last IR NEC code received */
extern struct ir_nec_code_t ir_nec_code_received;
/** setup peripherals to receive IR NEC codes
* @param[in] extended if the command address is extended (using 16 bits instead of 8, at the cost of error checking)
*/
void ir_nec_setup(bool extended);

View File

@ -1,796 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communication with Hitacho HD44780 LCD controller
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2019-2020
* @note peripherals used: GPIO @ref lcd_hd44780_gpio
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <stdbool.h> // boolean utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
/* own libraries */
#include "global.h" // common methods
#include "lcd_hd44780.h" // own definitions
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
#include "i2c_master.h" // I²C utilities
#endif
/** include busy time waiting in writes
* @note this removes the need to call lcd_hd44780_wait_busy but prevents you to do something else meanwhile, particularly when reading is enabled
* @note because I²C is already slow enough, there is no need to wait further
*/
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
#define LCD_HD44780_BUSY_WAIT_INCLUDE 1 // lcd_hd44780_wait_busy works, but the I²C bus is often slower
#else // LCD_HD44780_I2C
#define LCD_HD44780_BUSY_WAIT_INCLUDE 1 // you can change this value
#endif // LCD_HD44780_I2C
/** busy wait time for most short writes (37 us with margin) */
#define LCD_HD44780_BUSY_WAIT_SHORT (37 + 5)
/** busy wait time for some long writes (1520 us, but experience shows it's more)
* @note I have no idea why, but longer times increase the contrast darkness
*/
#define LCD_HD44780_BUSY_WAIT_LONG (1520 + 500)
/* 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
*/
/** @defgroup lcd_hd44780_signals HD44780 signals
* @note can be combined using OR
* @{
*/
#define LCD_HD44780_RS (1 << 0) /**< RS : Register Select (high = data, low = instruction) */
#define LCD_HD44780_RnW (1 << 1) /**< R/W: Read/Write (high = read, low = write) */
#define LCD_HD44780_E (1 << 2) /**< enable (falling edge to latch data, high to output register) */
#define LCD_HD44780_DB0 (1 << 3) /**< Data Bit 0 */
#define LCD_HD44780_DB1 (1 << 4) /**< Data Bit 1 */
#define LCD_HD44780_DB2 (1 << 5) /**< Data Bit 2 */
#define LCD_HD44780_DB3 (1 << 6) /**< Data Bit 3 */
#define LCD_HD44780_DB4 (1 << 7) /**< Data Bit 4 */
#define LCD_HD44780_DB5 (1 << 8) /**< Data Bit 5 */
#define LCD_HD44780_DB6 (1 << 9) /**< Data Bit 6 */
#define LCD_HD44780_DB7 (1 << 10) /**< Data Bit 7 */
#define LCD_HD44780_LED (1 << 11) /**< Backlight Cathode */
/** @} */
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
/* I²C backpack PCF8574 GPIO expander pinout:
* - P0: RS
* - P1: RnW
* - P2: E
* - P3: LED
* - P4: D4
* - P5: D5
* - P6: D6
* - P7: D7
*/
/** register select */
#define LCD_HD44780_I2C_RS (1 << 0)
/** select read or write */
#define LCD_HD44780_I2C_RnW (1 << 1)
/** enable pin */
#define LCD_HD44780_I2C_E (1 << 2)
/** enable LED backlight */
#define LCD_HD44780_I2C_LED (1 << 3)
/** data bit 4 */
#define LCD_HD44780_I2C_DB4 (1 << 4)
/** data bit 5 */
#define LCD_HD44780_I2C_DB5 (1 << 5)
/** data bit 6 */
#define LCD_HD44780_I2C_DB6 (1 << 6)
/** data bit 7 */
#define LCD_HD44780_I2C_DB7 (1 << 7)
/** I²C peripheral base address */
#define LCD_HD44780_I2C_PERIPH I2C1
/** I2C address of I²C backpack adapter (7 bits are LSB) */
uint8_t lcd_hd44780_i2c_addr = 0x3f; // default PCF8574A I²C backpack slave address
/** I2C GPIO output state */
static uint8_t lcd_hd44780_i2c_output = 0xff;
#else // LCD_HD44780_I2C
/** @defgroup lcd_hd44780_gpio GPIO used to control the HD44780
* @{
*/
/** register select
* @note pulled up by HD44780
*/
#define LCD_HD44780_GPIO_RS PB9
/** pin allowing writing data bits (on low) or reading them (on high)
* @note remove definition if tied to ground
* @note this enables read back data, but more importantly read the busy flag to know when the controller finished processing the command. Tying R/nW to ground instead of a GPIO saves a pin, but no data can be read back. Also every command needs to wait a minimum time before being able to send the next one, even if the controlled might actually already have processed it and is not busy anymore.
* @note is pulled up by HD44780
*/
#define LCD_HD44780_GPIO_RnW PB10
/** enable pin
* @warning needs an external 10k pull-up resistor
*/
#define LCD_HD44780_GPIO_E PB11
/** data bit 7
* @note pulled up by HD44780
*/
#define LCD_HD44780_GPIO_DB7 PB12
/** data bit 6
* @note pulled up by HD44780
*/
#define LCD_HD44780_GPIO_DB6 PB13
/** data bit 5
* @note pulled up by HD44780
*/
#define LCD_HD44780_GPIO_DB5 PB14
/** data bit 4
* @note pulled up by HD44780
*/
#define LCD_HD44780_GPIO_DB4 PB15
/** data bit 3
* @note pulled up by HD44780
* @note leave undefined when only 4-bit mode is used
*/
//#define LCD_HD44780_GPIO_DB3 P
/** data bit 2
* @note pulled up by HD44780
* @note leave undefined when only 4-bit mode is used
*/
//#define LCD_HD44780_GPIO_DB2 P
/** data bit 1
* @note pulled up by HD44780
* @note leave undefined when only 4-bit mode is used
*/
//#define LCD_HD44780_GPIO_DB1 P
/** data bit 0
* @note pulled up by HD44780
* @note leave undefined when only 4-bit mode is used
*/
//#define LCD_HD44780_GPIO_DB0 P
/** @} */
#endif // LCD_HD44780_I2C
/** set for 8-bit interface data
* @note I²C implies 4-bit
*/
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
#define LCD_HD44780_INTERFACE_DL 0
#elif defined(LCD_HD44780_GPIO_DB3) && defined(LCD_HD44780_GPIO_DB2) && defined(LCD_HD44780_GPIO_DB1) && defined(LCD_HD44780_GPIO_DB0)
#define LCD_HD44780_INTERFACE_DL 1
#else
#define LCD_HD44780_INTERFACE_DL 0
#endif
/** if the display is configured having 2 lines */
static bool lcd_hd44780_n_2lines = true;
/** set signals
* @param[in] signals
*/
static void lcd_hd44780_set_signal(uint16_t signals)
{
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
if (signals & LCD_HD44780_RS) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_RS;
}
if (signals & LCD_HD44780_RnW) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_RnW;
}
if (signals & LCD_HD44780_E) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_E;
}
if (signals & LCD_HD44780_DB4) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB4;
}
if (signals & LCD_HD44780_DB5) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB5;
}
if (signals & LCD_HD44780_DB6) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB6;
}
if (signals & LCD_HD44780_DB7) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB7;
}
if (signals & LCD_HD44780_LED) {
lcd_hd44780_i2c_output |= LCD_HD44780_I2C_LED;
}
#else // LCD_HD44780_I2C
if (signals & LCD_HD44780_RS) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_PIN(LCD_HD44780_GPIO_RS));
}
#ifdef LCD_HD44780_GPIO_RnW
if (signals & LCD_HD44780_RnW) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_PIN(LCD_HD44780_GPIO_RnW));
}
#endif // LCD_HD44780_GPIO_RnW
if (signals & LCD_HD44780_E) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_PIN(LCD_HD44780_GPIO_E));
}
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
if (signals & LCD_HD44780_DB0) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0));
}
if (signals & LCD_HD44780_DB1) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1));
}
if (signals & LCD_HD44780_DB2) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2));
}
if (signals & LCD_HD44780_DB3) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3));
}
#endif // LCD_HD44780_INTERFACE_DL
if (signals & LCD_HD44780_DB4) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4));
}
if (signals & LCD_HD44780_DB5) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5));
}
if (signals & LCD_HD44780_DB6) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6));
}
if (signals & LCD_HD44780_DB7) {
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7));
}
if (signals & LCD_HD44780_LED) {
// no LED GPIO defined
}
#endif // LCD_HD44780_I2C
}
/** clear signals
* @param[in] signals
*/
static void lcd_hd44780_clear_signal(uint16_t signals)
{
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
if (signals & LCD_HD44780_RS) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_RS;
}
if (signals & LCD_HD44780_RnW) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_RnW;
}
if (signals & LCD_HD44780_E) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_E;
}
if (signals & LCD_HD44780_DB4) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB4;
}
if (signals & LCD_HD44780_DB5) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB5;
}
if (signals & LCD_HD44780_DB6) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB6;
}
if (signals & LCD_HD44780_DB7) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB7;
}
if (signals & LCD_HD44780_LED) {
lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_LED;
}
#else // LCD_HD44780_I2C
if (signals & LCD_HD44780_RS) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_PIN(LCD_HD44780_GPIO_RS));
}
#ifdef LCD_HD44780_GPIO_RnW
if (signals & LCD_HD44780_RnW) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_PIN(LCD_HD44780_GPIO_RnW));
}
#endif // LCD_HD44780_GPIO_RnW
if (signals & LCD_HD44780_E) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_PIN(LCD_HD44780_GPIO_E));
}
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
if (signals & LCD_HD44780_DB0) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0));
}
if (signals & LCD_HD44780_DB1) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1));
}
if (signals & LCD_HD44780_DB2) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2));
}
if (signals & LCD_HD44780_DB3) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3));
}
#endif // LCD_HD44780_INTERFACE_DL
if (signals & LCD_HD44780_DB4) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4));
}
if (signals & LCD_HD44780_DB5) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5));
}
if (signals & LCD_HD44780_DB6) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6));
}
if (signals & LCD_HD44780_DB7) {
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7));
}
if (signals & LCD_HD44780_LED) {
// no LED GPIO defined
}
#endif // LCD_HD44780_I2C
}
/** flush the set and cleared signals
* @note this only applies to I²C and helps gain time
*/
static void lcd_hd44780_flush_signal(void)
{
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
i2c_master_slave_write(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &lcd_hd44780_i2c_output, 1); // write the set signals
#endif
}
/** read from controller
* @param[in] data read data (true) or busy flag and address counter (false)
* @return data/AC read
*/
static uint8_t lcd_hd44780_read(bool data)
{
#if defined(LCD_HD44780_GPIO_RnW) || defined(LCD_HD44780_I2C)
lcd_hd44780_set_signal(LCD_HD44780_RnW | LCD_HD44780_DB7 | LCD_HD44780_DB6 | LCD_HD44780_DB5 | LCD_HD44780_DB4); // switch DB direction to input to read data
if (data) {
lcd_hd44780_set_signal(LCD_HD44780_RS); // set high to read data
} else {
lcd_hd44780_clear_signal(LCD_HD44780_RS); // set low to read busy flag and AC
}
lcd_hd44780_flush_signal();
// no need to wait tAS = 40 ns before next step since the instructions are slower
lcd_hd44780_set_signal(LCD_HD44780_E); // set high to have data output
lcd_hd44780_flush_signal();
sleep_us(0); // wait t_DDR = 160 ns before reading
// read data bits
uint8_t input = 0; // to store read data
#if defined(LCD_HD44780_I2C)
uint8_t i2c_data;
if (I2C_MASTER_RC_NONE == i2c_master_slave_read(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &i2c_data, 1)) {
if (i2c_data & LCD_HD44780_I2C_DB7) {
input |= 0x80;
}
if (i2c_data & LCD_HD44780_I2C_DB6) {
input |= 0x40;
}
if (i2c_data & LCD_HD44780_I2C_DB5) {
input |= 0x20;
}
if (i2c_data & LCD_HD44780_I2C_DB4) {
input |= 0x10;
}
}
#else // LCD_HD44780_I2C
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7))) {
input |= 0x80;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6))) {
input |= 0x40;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5))) {
input |= 0x20;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4))) {
input |= 0x10;
}
#endif // LCD_HD44780_I2C
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3))) {
input |= 0x08;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2))) {
input |= 0x04;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1))) {
input |= 0x02;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0))) {
input |= 0x01;
}
#else // LCD_HD44780_INTERFACE_DL
// get second nibble
// don't wait PW_EH = 230 ns since reading took longer
lcd_hd44780_clear_signal(LCD_HD44780_E); // end current data read
lcd_hd44780_flush_signal();
sleep_us(1); // wait t_cycE = 500 ns
lcd_hd44780_set_signal(LCD_HD44780_E); // have next data output
lcd_hd44780_flush_signal();
sleep_us(0); // wait t_DDR = 160 ns before reading
// read second nibble
#if defined(LCD_HD44780_I2C)
if (I2C_MASTER_RC_NONE == i2c_master_slave_read(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &i2c_data, 1)) {
if (i2c_data & LCD_HD44780_I2C_DB7) {
input |= 0x08;
}
if (i2c_data & LCD_HD44780_I2C_DB6) {
input |= 0x04;
}
if (i2c_data & LCD_HD44780_I2C_DB5) {
input |= 0x02;
}
if (i2c_data & LCD_HD44780_I2C_DB4) {
input |= 0x01;
}
}
#else // LCD_HD44780_I2C
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7))) {
input |= 0x80;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6))) {
input |= 0x40;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5))) {
input |= 0x20;
}
if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4))) {
input |= 0x10;
}
#endif // LCD_HD44780_I2C
#endif // LCD_HD44780_INTERFACE_DL
// don't wait PW_EH = 230 ns since reading took longer
lcd_hd44780_clear_signal(LCD_HD44780_E); // end data read
lcd_hd44780_flush_signal();
return input;
#else // LCD_HD44780_GPIO_RnW || LCD_HD44780_I2C
return 0; // read is not supported
#endif
}
uint8_t lcd_hd44780_read_data(void)
{
return lcd_hd44780_read(true);
}
/** write to controller
* @param[in] command true if it is an command, false if it is data
* @param[in] data data bit to write
* @param[in] first_nibble_only if only the first nibble of the data should be written (only applies to 4-bit mode)
*/
static void lcd_hd44780_write(bool command, uint8_t data, bool first_nibble_only)
{
lcd_hd44780_clear_signal(LCD_HD44780_RnW); // switch DB direction to output to write data
if (command) {
lcd_hd44780_clear_signal(LCD_HD44780_RS);
} else {
lcd_hd44780_set_signal(LCD_HD44780_RS);
}
lcd_hd44780_flush_signal();
// no need to wait tAS = 40 ns before next step since the instructions are slower
// write data
if (data & 0x80) {
lcd_hd44780_set_signal(LCD_HD44780_DB7);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB7);
}
if (data & 0x40) {
lcd_hd44780_set_signal(LCD_HD44780_DB6);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB6);
}
if (data & 0x20) {
lcd_hd44780_set_signal(LCD_HD44780_DB5);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB5);
}
if (data & 0x10) {
lcd_hd44780_set_signal(LCD_HD44780_DB4);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB4);
}
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
if (data & 0x08) {
lcd_hd44780_set_signal(LCD_HD44780_DB3);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB3);
}
if (data & 0x04) {
lcd_hd44780_set_signal(LCD_HD44780_DB2);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB2);
}
if (data & 0x02) {
lcd_hd44780_set_signal(LCD_HD44780_DB1);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB1);
}
if (data & 0x01) {
lcd_hd44780_set_signal(LCD_HD44780_DB0);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB0);
}
#else // LCD_HD44780_INTERFACE_DL
if (!first_nibble_only) { // write second nibble
// pulse E to send data
sleep_us(1); // wait t_cycE = 500 ns
lcd_hd44780_set_signal(LCD_HD44780_E); // set high change output data
lcd_hd44780_flush_signal();
sleep_us(1); // wait PW_EH = 230 ns
lcd_hd44780_clear_signal(LCD_HD44780_E); // set low to latch data
lcd_hd44780_flush_signal();
// no need to wait t_H = 10 ns before next step since next instructions are slower
// send second nibble
if (data & 0x08) {
lcd_hd44780_set_signal(LCD_HD44780_DB7);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB7);
}
if (data & 0x04) {
lcd_hd44780_set_signal(LCD_HD44780_DB6);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB6);
}
if (data & 0x02) {
lcd_hd44780_set_signal(LCD_HD44780_DB5);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB5);
}
if (data & 0x01) {
lcd_hd44780_set_signal(LCD_HD44780_DB4);
} else {
lcd_hd44780_clear_signal(LCD_HD44780_DB4);
}
}
#endif // LCD_HD44780_INTERFACE_DL
// pulse E to send data
sleep_us(1); // wait t_cycE = 500 ns
lcd_hd44780_set_signal(LCD_HD44780_E); // set high change output data
lcd_hd44780_flush_signal();
sleep_us(1); // wait PW_EH = 230 ns
lcd_hd44780_clear_signal(LCD_HD44780_E); // set low to latch data
lcd_hd44780_flush_signal();
// no need to wait t_H = 10 ns before next step since next instructions are slower
// no need to wait t_AH = 10 ns before next step since next instructions are slower
}
/** wait until controller is not busy anymore
* @param[in] timeout maximum time to wait in us
* @return address count, or >= 0x80 if timeout expired
*/
static uint8_t lcd_hd44780_wait_busy(uint16_t timeout)
{
#if defined(LCD_HD44780_GPIO_RnW)
uint8_t ac = 0x80; // address counter to return
while (timeout && ((ac = lcd_hd44780_read(false)) >= 0x80)) { // wait until busy flag is low or timeout
// wait a bit
sleep_us(100);
if (timeout > 100) {
timeout -= 100;
} else {
timeout = 0;
}
}
return ac;
#else // I²C is also to slow to read (at least 6400 us per read)
sleep_us(timeout); // just wait
return 0;
#endif
}
/** write function set command
* @param[in] dl_8bit 8-bit (true) or 4-bit (false) data length
* @param[in] n_2lines 2 (true) or 1 (false) lines
* @param[in] f_5x10 5x10 (true) or 5x8 (false) dots font
*/
static void lcd_hd44780_function_set(bool dl_8bit, bool n_2lines, bool f_5x10)
{
uint8_t data = 0x20;
if (dl_8bit) {
data |= 0x10;
}
if (n_2lines) {
data |= 0x08;
}
if (f_5x10) {
data |= 0x04;
}
lcd_hd44780_write(true, data, false);
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
}
bool lcd_hd44780_setup(bool n_2lines, bool f_5x10)
{
sleep_ms(40 + 2); // wait for display to initialise after power on: 15 ms for VCC > 4.5 V, 40 ms for VCC > 2.7 V
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
// configure I²C peripheral
if (!i2c_master_check_signals(LCD_HD44780_I2C_PERIPH)) { // check if there are pull-ups to operator I²C
return false;
}
i2c_master_setup(LCD_HD44780_I2C_PERIPH, 100); // setup I²C bus (PCF8574 supports an I²C clock up to 100 kHz)
lcd_hd44780_i2c_output = 0xff; // put GPIO to input (sort of open drain output)
if (I2C_MASTER_RC_NONE != i2c_master_slave_write(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &lcd_hd44780_i2c_output, 1)) { // check if the device is present and set inputs
return false;
}
#else // LCD_HD44780_I2C
// enable all GPIO
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_E)); // enable clock for GPIO port
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_PIN(LCD_HD44780_GPIO_E)); // start idle low
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_E)); // set GPIO as output
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB7)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB7)); // open drain allows to read and write
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB6)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB6)); // open drain allows to read and write
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB5)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB5)); // open drain allows to read and write
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB4)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB4)); // open drain allows to read and write
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB3)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB3)); // open drain allows to read and write
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB2)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB2)); // open drain allows to read and write
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB1)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB1)); // open drain allows to read and write
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB0)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0)); // idle high
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB0)); // open drain allows to read and write
#endif // LCD_HD44780_INTERFACE_DL
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_RS)); // enable clock for GPIO port
gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_PIN(LCD_HD44780_GPIO_RS)); // set low to read busy flag
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_RS)); // set GPIO as output
#ifdef LCD_HD44780_GPIO_RnW
rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_RnW)); // enable clock for GPIO port
gpio_set(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_PIN(LCD_HD44780_GPIO_RnW)); // set high to read
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_RnW)); // set GPIO as output
#endif // LCD_HD44780_GPIO_RnW
#endif // LCD_HD44780_I2C
// initialize device
lcd_hd44780_write(true, 0x30, true); // 1st function write set to go to state 1 (8-bit) or 2 (4-bit first nibble) (BF cannot be checked)
sleep_ms(4 + 1); // wait 4.1 ms
lcd_hd44780_write(true, 0x30, true); // 2st function write set to go to state 1 (8-bit) or 3 (4-bit second nibble) (BF cannot be checked)
sleep_us(100 + 10); // wait 100 us
lcd_hd44780_write(true, 0x30, true); // 3rd function write set to go to state 1 (8-bit) (BF cannot be checked)
sleep_us(LCD_HD44780_BUSY_WAIT_SHORT); // wait 37 us
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
lcd_hd44780_function_set(1, n_2lines, f_5x10); // function set
#else
lcd_hd44780_write(true, 0x20, true); // switch to 4-bit mode
sleep_us(LCD_HD44780_BUSY_WAIT_SHORT); // wait 37 us
lcd_hd44780_function_set(0, n_2lines, f_5x10); // function set
#endif
lcd_hd44780_n_2lines = n_2lines; // remember number of lines
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
lcd_hd44780_display_control(false, false, false); // display off
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
lcd_hd44780_clear_display(); // display clear
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_LONG);
lcd_hd44780_entry_mode_set(true, false); // entry mode set
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
return true; // I²C configuration succeeded
}
void lcd_hd44780_release(void)
{
#if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C
i2c_master_release(LCD_HD44780_I2C_PERIPH); // release I²C peripheral
#else // LCD_HD44780_I2C
// switch back GPIO back to input
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB7));
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB6));
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB5));
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB4));
#if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB3));
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB2));
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB1));
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB0));
#endif // LCD_HD44780_INTERFACE_DL
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_RS));
#ifdef LCD_HD44780_GPIO_RnW
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_RnW));
#endif // LCD_HD44780_GPIO_RnW
gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_E));
#endif // LCD_HD44780_I2C:
}
void lcd_hd44780_write_data(uint8_t data)
{
lcd_hd44780_write(false, data, false);
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
}
void lcd_hd44780_write_command(uint8_t data)
{
lcd_hd44780_write(true, data, false);
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
}
void lcd_hd44780_clear_display(void)
{
lcd_hd44780_write_command(0x01);
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_LONG);
}
void lcd_hd44780_return_home(void)
{
lcd_hd44780_write_command(0x02);
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_LONG);
}
void lcd_hd44780_entry_mode_set(bool increment, bool shift)
{
uint8_t command = 0x04;
if (increment) {
command |= 0x02;
}
if (shift) {
command |= 0x01;
}
lcd_hd44780_write_command(command);
}
void lcd_hd44780_display_control(bool d, bool c, bool b)
{
uint8_t command = 0x08;
if (d) {
command |= 0x04;
}
if (c) {
command |= 0x02;
}
if (b) {
command |= 0x01;
}
lcd_hd44780_write_command(command);
}
void lcd_hd44780_set_cgram_address(uint8_t acg)
{
lcd_hd44780_write_command(0x40 | (acg & 0x3f));
}
void lcd_hd44780_set_ddram_address(uint8_t add)
{
lcd_hd44780_write_command(0x80 | (add & 0x7f));
}
void lcd_hd44780_write_line(bool line2, const char* data, uint8_t length)
{
if (line2 && !lcd_hd44780_n_2lines) { // writing line 2 when it does not exists is not possible
return;
}
lcd_hd44780_set_ddram_address(line2 ? 0x40 : 0x00); // set start of line
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
for (uint8_t i = 0; i < length && i < (lcd_hd44780_n_2lines ? 0x27 : 0x4f); i++) { // write line
lcd_hd44780_write_data(data[i]); // write character
lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT);
}
}

View File

@ -1,94 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communication with Hitacho HD44780 LCD controller
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2019-2020
* @note peripherals used: GPIO @ref lcd_hd44780_gpio, I²C @ref lcd_hd44780_i2c
*/
#pragma once
#include <libopencm3/stm32/i2c.h> // I²C definitions
/** @defgroup lcd_hd44780_i2c I²C peripheral used to control backpack adapter for the HD44780
* @{
*/
/** set I²C peripheral if I²C backpack adapter(s) is(/are) used */
//#define LCD_HD44780_I2C I2C1
/** @} */
#ifdef LCD_HD44780_I2C
/** I²C address of I²C backpack adapter (7 bits are LSb) */
extern uint8_t lcd_hd44780_i2c_addr;
#endif
/** setup peripherals to start communicating with HD4478
* @param[in] n_2lines 2 (true) or 1 (false) lines
* @param[in] f_5x10 5x10 (true) or 5x8 (false) dots font
* @return if the display setup is successful, else the display is probably not on the I²C bus (or the I²C bus does not work)
*/
bool lcd_hd44780_setup(bool n_2lines, bool f_5x10);
/** release peripherals */
void lcd_hd44780_release(void);
/** read data (from CG or DDRAM)
* @return read data
*/
uint8_t lcd_hd44780_read_data(void);
/** write data (to CG or DDRAM)
* @param[in] data data to write
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(37)
*/
void lcd_hd44780_write_data(uint8_t data);
/** write command
* @param[in] data instruction to write
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(37)
*/
void lcd_hd44780_write_command(uint8_t data);
/** clears entire display and sets DDRAM address 0 in address counter
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(1520)
*/
void lcd_hd44780_clear_display(void);
/** sets DDRAM address 0 in address counter, also returns display from being shifted to original position
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(1520);
*/
void lcd_hd44780_return_home(void);
/** sets cursor move direction and specifies display shift
* @param[in] increment true = increment, false = decrement
* @param[in] shift true = accompanies display shift
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(37);
*/
void lcd_hd44780_entry_mode_set(bool increment, bool shift);
/** set display control
* @param[in] d entire display on (true) / off (false)
* @param[in] c cursor on (true) / off (false)
* @param[in] b blinking of cursor position character on (true) / off (false)
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(37);
*/
void lcd_hd44780_display_control(bool d, bool c, bool b);
/** sets CGRAM address. CGRAM data is sent and received after this setting.
* @param[in] acg CGRAM address
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(37);
*/
void lcd_hd44780_set_cgram_address(uint8_t acg);
/** sets DDRAM address. DDRAM data is sent and received after this setting.
* @param[in] add DDRAM address
* @warning does not wait for operation to complete: use lcd_hd44780_wait_busy(37);
*/
void lcd_hd44780_set_ddram_address(uint8_t add);
/** utility to write entire line
* @param[in] line2 write line 1 (false) or 2 (true)
* @param[in] data characters to write on the line
* @param[in] length number of characters to write
*/
void lcd_hd44780_write_line(bool line2, const char* data, uint8_t length);

View File

@ -1,267 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with a Titan Micro MAX7219 IC attached to a 4-digit 7-segment (code)
* @file led_max7219.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017
* @note peripherals used: GPIO @ref led_max7219_gpio, timer @ref led_tm1637_timer
* @warning all calls are blocking
*
* bit vs segment: 0bpabcdefg
* +a+
* f b
* +g+
* e c p
* +d+
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <string.h> // string utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
#include <libopencm3/stm32/timer.h> // timer library
#include "global.h" // global utilities
#include "led_max7219.h" // MAX7219 header and definitions
/** @defgroup led_max7219_gpio GPIO used to control MAX7219 IC load line
* @{
*/
#define LED_MAX7219_LOAD_PORT B /**< port for load line */
#define LED_MAX7219_LOAD_PIN 12 /**< pin for load line */
/** @} */
/** @defgroup led_max7219_spi SPI used to communication with MAX7219 IC
* @{
*/
#define LED_MAX7219_SPI 2 /**< SPI to send data */
/** @} */
/** ASCII characters encoded for the 7 segments digit block
* @note starts with space
*/
static const uint8_t ascii_7segments[] = {
0x00, // space
0x06, // ! (I)
0x22, // "
0x1d, // # (o)
0x5b, // $ (s)
0x25, // % (/)
0x5f, // & (6)
0x02, // '
0x4e, // ( ([)
0x78, // )
0x07, // *
0x31, // +
0x04, // ,
0x01, // -
0x04, // . (,)
0x25, // /
0x7e, // 0
0x30, // 1
0x6d, // 2
0x79, // 3
0x33, // 4
0x5b, // 5
0x5f, // 6
0x70, // 7
0x7f, // 8
0x7b, // 9
0x09, // : (=)
0x09, // ; (=)
0x0d, // <
0x09, // =
0x19, // >
0x65, // ?
0x6f, // @
0x77, // A
0x7f, // B
0x4e, // C
0x3d, // D
0x4f, // E
0x47, // F
0x5e, // G
0x37, // H
0x06, // I
0x3c, // J
0x37, // K
0x0e, // L
0x76, // M
0x76, // N
0x7e, // O
0x67, // P
0x6b, // Q
0x66, // R
0x5b, // S
0x0f, // T
0x3e, // U
0x3e, // V (U)
0x3e, // W (U)
0x37, // X (H)
0x3b, // Y
0x6d, // Z
0x4e, // [
0x13, // '\'
0x78, // /
0x62, // ^
0x08, // _
0x20, // `
0x7d, // a
0x1f, // b
0x0d, // c
0x3d, // d
0x6f, // e
0x47, // f
0x7b, // g
0x17, // h
0x04, // i
0x18, // j
0x37, // k
0x06, // l
0x15, // m
0x15, // n
0x1d, // o
0x67, // p
0x73, // q
0x05, // r
0x5b, // s
0x0f, // t
0x1c, // u
0x1c, // v (u)
0x1c, // w (u)
0x37, // x
0x3b, // y
0x6d, // z
0x4e, // { ([)
0x06, // |
0x78, // } ([)
0x01, // ~
};
/** number of display in the chain */
uint8_t lex_max7219_displays = 0;
/** write data on SPI bus and handle load signal
* @param[in] data bytes to write
* @param[in] display display number in chain (0xff for all)
*/
static void led_max7219_write(uint16_t data, uint8_t display)
{
if (lex_max7219_displays<=display && 0xff!=display) { // display no in chain
return;
}
gpio_clear(GPIO(LED_MAX7219_LOAD_PORT), GPIO(LED_MAX7219_LOAD_PIN)); // ensure load pin is low (data is put in MAX7219 register on rising edge)
for (uint8_t i=lex_max7219_displays; i>0; i--) { // go though all displays
while (SPI_SR(SPI(LED_MAX7219_SPI))&SPI_SR_BSY); // wait until not busy
if (0xff==display || i==(display+1)) { // right display or broadcast message
spi_send(SPI(LED_MAX7219_SPI), data); // send data
} else {
spi_send(SPI(LED_MAX7219_SPI), 0x0000); // send no-op command to shift command to correct display or out
}
while (!(SPI_SR(SPI(LED_MAX7219_SPI))&SPI_SR_TXE)); // wait until Tx is empty (reference manual says BSY should also cover this, but it doesn't)
while (SPI_SR(SPI(LED_MAX7219_SPI))&SPI_SR_BSY); // wait until not busy (= transmission completed)
}
gpio_set(GPIO(LED_MAX7219_LOAD_PORT), GPIO(LED_MAX7219_LOAD_PIN)); // create rising edge on load pin for data to be set in MAX7219 register
}
void led_max7219_setup(uint8_t displays)
{
// saved number of displays
lex_max7219_displays = displays;
// configure GPIO for load line
rcc_periph_clock_enable(RCC_GPIO(LED_MAX7219_LOAD_PORT)); // enable clock for GPIO peripheral
gpio_clear(GPIO(LED_MAX7219_LOAD_PORT), GPIO(LED_MAX7219_LOAD_PIN)); // idle low (load on rising edge)
gpio_set_mode(GPIO(LED_MAX7219_LOAD_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_MAX7219_LOAD_PIN)); // set as output
// configure SPI peripheral
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(LED_MAX7219_SPI)); // enable clock for GPIO peripheral for clock signal
gpio_set_mode(SPI_SCK_PORT(LED_MAX7219_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(LED_MAX7219_SPI)); // set as output (max clock speed for MAX7219 is 10 MHz)
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(LED_MAX7219_SPI)); // enable clock for GPIO peripheral for MOSI signal
gpio_set_mode(SPI_MOSI_PORT(LED_MAX7219_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(LED_MAX7219_SPI)); // set as output
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(RCC_SPI(LED_MAX7219_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(LED_MAX7219_SPI)); // clear SPI values to default
spi_init_master(SPI(LED_MAX7219_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_8, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_16BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 8 since max MAX7219 clock is 10 MHz and max SPI PCLK clock is 72 Mhz, depending on which SPI is used, set clock polarity to idle low (as in the datasheet of the MAX7219, but not that important), set clock phase to go high when bit is set (depends on polarity) as data is stored on MAX7219 on rising edge), use 16 bits frames (as used by MAX7219), use MSB first
spi_set_unidirectional_mode(SPI(LED_MAX7219_SPI)); // we only need to transmit data
spi_enable(SPI(LED_MAX7219_SPI)); // enable SPI
}
void led_max7219_on(uint8_t display)
{
led_max7219_write(0x0C01, display); // put in normal operation more (registers remain as set)
}
void led_max7219_off(uint8_t display)
{
led_max7219_write(0x0C00, display); // put in shutdown mode (registers remain as set)
}
void led_max7219_test(bool test, uint8_t display)
{
if (test) {
led_max7219_write(0x0F01, display); // go into display test mode
} else {
led_max7219_write(0x0F00, display); // go into normal operation mode
}
}
void led_max7219_intensity(uint8_t intensity, uint8_t digits, uint8_t display)
{
if (intensity>15) { // intensity must be 0-15 (corresponds to (2*brightness+1)/32)
return;
}
if (digits<1 || digits>8) { // scan limit must bit 0-7
return;
}
led_max7219_write(0x0A00+intensity, display); // set brightness
led_max7219_write(0x0B00+digits-1, display); // set scan limit to display digits
}
bool led_max7219_text(char* text, uint8_t display)
{
for (uint8_t i=0; i<8; i++) { // input text should only contain printable character (8th bit is used for dots)
if ((text[i]&0x7f)<' ' || (text[i]&0x7f)>=' '+LENGTH(ascii_7segments)) {
return false;
}
}
led_max7219_write(0x0900, display); // disable BCD decoding on all 7 digits
for (uint8_t i=0; i<8; i++) { // display text
led_max7219_write(((i+1)<<8)+(ascii_7segments[(text[7-i]&0x7f)-' '])+(text[7-i]&0x80), display); // send digit (in reverse order)
}
return true;
}
void led_max7219_number(uint32_t number, uint8_t dots, uint8_t display)
{
led_max7219_write(0x09FF, display); // enable BCD decoding on all 7 digits
for (uint8_t digit=0; digit<8; digit++) { // go through digits
if (0==digit) { // display 0 on 0 only to first digit
led_max7219_write(((digit+1)<<8) + (number%10) + (((dots>>digit)&0x01)<<7), display); // display digit
} else if (0==number) { // display blank on other digits
led_max7219_write(((digit+1)<<8) + 0x0F + (((dots>>digit)&0x01)<<7), display); // display blank
} else {
led_max7219_write(((digit+1)<<8) + (number%10) + (((dots>>digit)&0x01)<<7), display); // display digit
}
number /= 10; // get next digit
}
}

View File

@ -1,64 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with a Maxim MAX7219 IC attached to a 8-digit 7-segment (API)
* @file led_max7219.h
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017
* @note peripherals used: GPIO @ref led_max7219_gpio, SPI @ref led_max7219_spi
* @warning all calls are blocking
*/
#pragma once
/** setup communication with MAX7219 IC
* @param[in] displays number of displays in the chain
*/
void led_max7219_setup(uint8_t displays);
/** do nothing (no operation)
* @param[in] display display number in chain (0xff for all)
* @note send it to the last display in the chain to clear the previous command from the chain
*/
void led_max7219_nop(uint8_t display);
/** switch display on
* @param[in] display display number in chain (0xff for all)
*/
void led_max7219_on(uint8_t display);
/** switch display off
* @param[in] display display number in chain (0xff for all)
*/
void led_max7219_off(uint8_t display);
/** switch display in test or normal operation mode
* @param[in] test switch in test mode (else normal operation)
* @param[in] display display number in chain (0xff for all)
*/
void led_max7219_test(bool test, uint8_t display);
/** set display intensity
* @param[in] intensity level to set (0-15)
* @param[in] digits number of digits to display (1-8)
* @param[in] display display number in chain (0xff for all)
*/
void led_max7219_intensity(uint8_t intensity, uint8_t digits, uint8_t display);
/** display text
* @param[in] text text to display (8 characters)
* @param[in] display display number in chain (0xff for all)
* @note use first bit of each character to enable dot
* @return false if string has unsupported characters
*/
bool led_max7219_text(char* text, uint8_t display);
/** display number
* @param[in] number number to display (8 digits max)
* @param[in] dots set bit if dot on corresponding digit should be displayed
* @param[in] display display number in chain (0xff for all)
*/
void led_max7219_number(uint32_t number, uint8_t dots, uint8_t display);

View File

@ -1,346 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with a Titan Micro TM1637 IC attached to a 4-digit 7-segment
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: GPIO @ref led_tm1637_gpio, timer @ref led_tm1637_timer
* @note the protocol is very similar to I2C but incompatible for the following reasons: the capacitance is too large for open-drain type output with weak pull-up resistors (push-pull needs to be used, preventing to get ACKs since no indication of the ACK timing is provided); the devices doesn't use addresses; the STM32 I2C will switch to receiver mode when the first sent byte (the I2C address) has last bit set to 1 (such as for address commands with B7=1 where B7 is transmitted last), preventing to send further bytes (the data byte after the address)
* @warning all calls are blocking
*
* bit vs segment: 0bpgfedcba
* +a+
* f b p
* +g+
* e c p
* +d+
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <string.h> // string utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer library
#include "global.h" // global utilities
#include "led_tm1637.h" // TM1637 header and definitions
/** @defgroup led_tm1637_gpio GPIO used to communication with TM1637 IC
* @{
*/
#define LED_TM1637_CLK_PIN PB6 /**< pin for CLK signal */
#define LED_TM1637_DIO_PIN PB7 /**< pin for DIO signal */
/** @} */
/** @defgroup led_tm1637_timer timer used to communication with TM1637 IC
* @{
*/
#define LED_TM1637_TIMER 3 /**< timer to create signal */
/** @} */
/** display brightness */
static enum led_tm1637_brightness_t display_brightness = LED_TM1637_14DIV16;
/** if display is on */
static bool display_on = false;
/** ASCII characters encoded for the 7 segments digit block
* @note starts with space
*/
static const uint8_t ascii_7segments[] = {
0x00, // 0b00000000 space
0x30, // 0b00110000 ! (I)
0x22, // 0b00100010 "
0x5c, // 0b01011100 # (o)
0x6d, // 0b01101101 $ (s)
0x52, // 0b01010010 % (/)
0x7d, // 0b01111101 & (6)
0x20, // 0b00100000 '
0x39, // 0b00111001 ( ([)
0x0f, // 0b00001111 )
0x70, // 0b01110000 *
0x46, // 0b01000110 +
0x10, // 0b00010000 ,
0x40, // 0b01000000 -
0x10, // 0b00010000 . (,)
0x52, // 0b01010010 /
0x3f, // 0b00111111 0
0x06, // 0b00000110 1
0x5b, // 0b01011011 2
0x4f, // 0b01001111 3
0x66, // 0b01100110 4
0x6d, // 0b01101101 5
0x7d, // 0b01111101 6
0x07, // 0b00000111 7
0x7f, // 0b01111111 8
0x6f, // 0b01101111 9
0x48, // 0b01001000 : (=)
0x48, // 0b01001000 ; (=)
0x58, // 0b01011000 <
0x48, // 0b01001000 =
0x4c, // 0b01001100 >
0x53, // 0b01010011 ?
0x7b, // 0b01111011 @
0x77, // 0b01110111 A
0x7f, // 0b01111111 B
0x39, // 0b00111001 C
0x5e, // 0b01011110 D
0x79, // 0b01111001 E
0x71, // 0b01110001 F
0x3d, // 0b00111101 G
0x76, // 0b01110110 H
0x30, // 0b00110000 I
0x1e, // 0b00011110 J
0x76, // 0b01110110 K
0x38, // 0b00111000 L
0x37, // 0b00110111 M
0x37, // 0b00110111 N
0x3f, // 0b00111111 O
0x73, // 0b01110011 P
0x6b, // 0b01101011 Q
0x33, // 0b00110011 R
0x6d, // 0b01101101 S
0x78, // 0b01111000 T
0x3e, // 0b00111110 U
0x3e, // 0b00111110 V (U)
0x3e, // 0b00111110 W (U)
0x76, // 0b01110110 X (H)
0x6e, // 0b01101110 Y
0x5b, // 0b01011011 Z
0x39, // 0b00111001 [
0x64, // 0b01100100 '\'
0x0f, // 0b00001111 /
0x23, // 0b00100011 ^
0x08, // 0b00001000 _
0x02, // 0b00000010 `
0x5f, // 0b01011111 a
0x7c, // 0b01111100 b
0x58, // 0b01011000 c
0x5e, // 0b01011110 d
0x7b, // 0b01111011 e
0x71, // 0b01110001 f
0x6f, // 0b01101111 g
0x74, // 0b01110100 h
0x10, // 0b00010000 i
0x0c, // 0b00001100 j
0x76, // 0b01110110 k
0x30, // 0b00110000 l
0x54, // 0b01010100 m
0x54, // 0b01010100 n
0x5c, // 0b01011100 o
0x73, // 0b01110011 p
0x67, // 0b01100111 q
0x50, // 0b01010000 r
0x6d, // 0b01101101 s
0x78, // 0b01111000 t
0x1c, // 0b00011100 u
0x1c, // 0b00011100 v (u)
0x1c, // 0b00011100 w (u)
0x76, // 0b01110110 x
0x6e, // 0b01101110 y
0x5b, // 0b01011011 z
0x39, // 0b00111001 { ([)
0x30, // 0b00110000 |
0x0f, // 0b00001111 } ([)
0x40, // 0b01000000 ~
};
void led_tm1637_setup(void)
{
// configure GPIO for CLK and DIO signals
rcc_periph_clock_enable(GPIO_RCC(LED_TM1637_CLK_PIN)); // enable clock for GPIO peripheral
gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // idle high
gpio_set_mode(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_CLK_PIN)); // master start the communication (capacitance is to large for open drain), only switch to input for ack from slave
rcc_periph_clock_enable(GPIO_RCC(LED_TM1637_DIO_PIN)); // enable clock for GPIO peripheral
gpio_set(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // idle high
gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_DIO_PIN)); // master start the communication (capacitance is to large for open drain), only switch to input for ack from slave
// first clock then data high also stands for stop condition
// setup timer to create signal timing (each tick is used for a single GPIO transition)
rcc_periph_clock_enable(RCC_TIM(LED_TM1637_TIMER)); // enable clock for timer block
rcc_periph_reset_pulse(RST_TIM(LED_TM1637_TIMER)); // reset timer state
timer_set_mode(TIM(LED_TM1637_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(TIM(LED_TM1637_TIMER), 0); // don't prescale to get most precise timing ( 1/(72E6/1/(2**16))=0.91 ms > 0.5 us )
timer_set_period(TIM(LED_TM1637_TIMER), 500); // set the clock frequency (emprical value until the signal starts to look bad)
timer_clear_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF); // clear flag
timer_update_on_overflow(TIM(LED_TM1637_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
}
/** wait until clock tick (timer overflow) occurred
*/
static inline void led_tm1637_tick(void)
{
while (!timer_get_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF)); // wait until counter overflow update event happens
timer_clear_flag(TIM(LED_TM1637_TIMER), TIM_SR_UIF); // clear event flag
}
/** write data on bus
* @param[in] data bytes to write
* @param[in] length number of bytes to write
* @return if write succeeded
* @note includes start and stop conditions
*/
static bool led_tm1637_write(const uint8_t* data, size_t length)
{
bool to_return = true; // return if write succeeded
if (NULL == data || 0 == length) { // verify there it data to be read
return false;
}
// enable timer for signal generation
timer_set_counter(TIM(LED_TM1637_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(LED_TM1637_TIMER)); // enable timer to generate timing
led_tm1637_tick(); // wait to enforce minimum time since last write
// send start condition (DIO then CLK low)
gpio_clear(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO low
led_tm1637_tick(); // wait for next tick
gpio_clear(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK low
// send data bytes (MSb first)
for (size_t i = 0; i < length; i++) { // send all bytes
uint8_t byte = data[i];
for (uint8_t b = 0; b < 8; b++) { // send all bits
if (byte & 0x1) { // send a 1
gpio_set(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO high
} else {
gpio_clear(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO low
}
byte >>= 1; // shift data
led_tm1637_tick(); // wait for next tick
gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK high
led_tm1637_tick(); // wait for next tick (no DIO transition when CLK is high)
gpio_clear(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK low
}
gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LED_TM1637_DIO_PIN)); // switch DIO as input to read ACK
led_tm1637_tick(); // wait for next tick (when the slave should ACK)
gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK high
if (gpio_get(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN))) { // no ACK received
to_return = false; // remember there was an error
break; // stop sending bytes
}
led_tm1637_tick(); // wait for next tick
gpio_clear(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK low
gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_DIO_PIN)); // switch DIO back to output to send next byte
}
// send stop condition
gpio_set_mode(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(LED_TM1637_DIO_PIN)); // ensure DIO is output (in case no ACK as been received
led_tm1637_tick(); // wait for next tick
gpio_set(GPIO_PORT(LED_TM1637_CLK_PIN), GPIO_PIN(LED_TM1637_CLK_PIN)); // put CLK high
led_tm1637_tick(); // wait for next tick
gpio_set(GPIO_PORT(LED_TM1637_DIO_PIN), GPIO_PIN(LED_TM1637_DIO_PIN)); // put DIO high
timer_disable_counter(TIM(LED_TM1637_TIMER)); // stop timer since it's not used anymore
return to_return;
}
bool led_tm1637_on(void)
{
uint8_t data[] = { 0x88 + display_brightness }; // command to turn display on (use set brightness)
bool to_return = false; // result to return
if (led_tm1637_write(data, LENGTH(data))) { // send command
display_on = true; // remember display is on
to_return = true; // command succeeded
}
return to_return; // return result
}
bool led_tm1637_off(void)
{
uint8_t data[] = { 0x80 + display_brightness }; // command to turn display off (use set brightness)
if (led_tm1637_write(data, LENGTH(data))) { // send command
display_on = false; // remember display is off
return true; // command succeeded
}
return false; // return result
}
bool led_tm1637_brightness(enum led_tm1637_brightness_t brightness)
{
display_brightness = brightness; // save brightness
if (display_on) { // adjust brightness if display is on
return led_tm1637_on(); // adjust brightness
} else {
return true; // command succeeded
}
return false;
}
bool led_tm1637_number(uint16_t number, bool zero)
{
const uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal
const uint8_t digits[] = { // digits to display
(number / 1000) % 10,
(number / 100) % 10,
(number / 10) % 10,
(number / 1) % 10,
};
uint8_t data[] = { 0xc0, 0, 0, 0, 0}; // number to be displayed
// convert digits to text to be displayed
if (0 == digits[0] && !zero) {
data[1] = ascii_7segments[' ' - ' '];
} else {
data[1] = ascii_7segments[digits[0] + '0' - ' '];
}
if (0 == digits[0] && 0 == digits[1] && !zero) {
data[2] = ascii_7segments[' ' - ' '];
} else {
data[2] = ascii_7segments[digits[1] + '0' - ' '];
}
if (0 == digits[0] && 0 == digits[1] && 0 == digits[2] && !zero) {
data[3] = ascii_7segments[' ' - ' '];
} else {
data[3] = ascii_7segments[digits[2] + '0' - ' '];
}
data[4] = ascii_7segments[digits[3] + '0' - ' '];
// display number
return led_tm1637_write(write_data, LENGTH(write_data)) && led_tm1637_write(data, LENGTH(data)); // send commands
}
bool led_tm1637_time(uint8_t hours, uint8_t minutes)
{
uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal
uint8_t data[] = { 0xc0, ascii_7segments[((hours / 10) % 10) + '0' - ' '], ascii_7segments[((hours / 1) % 10) + '0' - ' '] | 0x80, ascii_7segments[((minutes / 10) % 10) + '0' - ' '], ascii_7segments[((minutes / 1) % 10) + '0' - ' '] }; // set address C0H and add data
if (led_tm1637_write(write_data, LENGTH(write_data)) && led_tm1637_write(data, LENGTH(data))) { // send commands
return true;
}
return false;
}
bool led_tm1637_text(char* text)
{
if (strlen(text) != 4) { // input text should have exactly 4 characters
return false;
}
for (uint8_t i = 0; i < 4; i++) { // input text should only contain printable character (8th bit is used for dots)
if ((text[i] & 0x7f) < ' ' || (text[i] & 0x7f) >= ' ' + LENGTH(ascii_7segments)) {
return false;
}
}
uint8_t write_data[] = { 0x40 }; // command: write data, automatic address adding, normal
uint8_t data[] = { 0xc0, ascii_7segments[(text[0] & 0x7f) - ' '] | (text[0] & 0x80), ascii_7segments[(text[1] & 0x7f) - ' ']|(text[1] & 0x80), ascii_7segments[(text[2] & 0x7f) - ' ']|(text[2] & 0x80), ascii_7segments[(text[3] & 0x7f) - ' ']|(text[3] & 0x80) }; // set address C0H and add data
if (led_tm1637_write(write_data, LENGTH(write_data)) && led_tm1637_write(data, LENGTH(data))) { // send commands
return true;
}
return false;
}

View File

@ -1,72 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with a Titan Micro TM1637 IC attached to a 4-digit 7-segment
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: GPIO @ref led_tm1637_gpio, timer @ref led_tm1637_timer
* @warning all calls are blocking
*/
#pragma once
/** display brightness levels
*/
enum led_tm1637_brightness_t {
LED_TM1637_1DIV16 = 0,
LED_TM1637_2DIV16 = 1,
LED_TM1637_4DIV16 = 2,
LED_TM1637_10DIV16 = 3,
LED_TM1637_11DIV16 = 4,
LED_TM1637_12DIV16 = 5,
LED_TM1637_13DIV16 = 6,
LED_TM1637_14DIV16 = 7,
};
/** setup communication with TM1637 IC
*/
void led_tm1637_setup(void);
/** switch display on
* @return if transmission succeeded
*/
bool led_tm1637_on(void);
/** switch display off
* @return if transmission succeeded
*/
bool led_tm1637_off(void);
/** set display brightness
* @param[in] brightness brightness level to set
* @return if transmission succeeded
*/
bool led_tm1637_brightness(enum led_tm1637_brightness_t brightness);
/** display number
* @param[in] number number to display (0-9999)
* @param[in] zero pad number with zero on the left
* @return if transmission succeeded
*/
bool led_tm1637_number(uint16_t number, bool zero);
/** display time
* @param[in] hours hours to display (0-99)
* @param[in] minutes minutes to display (0-99)
* @note display separator between hours and minutes
* @return if transmission succeeded
*/
bool led_tm1637_time(uint8_t hours, uint8_t minutes);
/** display text
* @param[in] text text to display (4 characters)
* @note use first bit of each character to enable dot
* @return if transmission succeeded
*/
bool led_tm1637_text(char* text);

View File

@ -1,311 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate using microwore as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: GPIO @ref microwire_master_gpio, timer @ref microwire_master_timer
* microwire is a 3-Wire half-duplex synchronous bus. It is very similar to SPI without fixed length messages (bit-wised).
* @note the user has to handle the slave select pin (high during operations) so to be able to handle multiple slaves.
* @warning this library implements the M93Cx8 EEPROM operation codes. Other microwire-based ICs might use different ones.
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer utilities
#include "global.h" // global utilities
#include "microwire_master.h" // microwire header and definitions
/** @defgroup microwire_master_gpio GPIO peripheral used to communicate
* @{
*/
#define MICROWIRE_MASTER_SDO_PIN PA0 /**< SDO output signal pin (to be connected on D slave signal) */
#define MICROWIRE_MASTER_SDI_PIN PA2 /**< SDO input signal pin (to be connected on Q slave signal) */
#define MICROWIRE_MASTER_SCK_PIN PA4 /**< SCK output signal pin (to be connected on C slave signal) */
/** @} */
/** @defgroup microwire_master_timer timer peripheral used to generate timing for the signal
* @{
*/
#define MICROWIRE_MASTER_TIMER 4 /**< timer peripheral */
/** @} */
/** address size used in operations (slave specific) */
uint8_t mirowire_master_address_size = 0;
/** organization used (true=x16, false=x8) */
bool mirowire_master_organization_x16 = true;
void microwire_master_setup(uint32_t frequency, bool organization_x16, uint8_t address_size)
{
// sanity checks
if (0 == frequency || 0 == address_size) {
return;
}
mirowire_master_address_size = address_size; // save address size
mirowire_master_organization_x16 = organization_x16; // save organisation
// setup GPIO
rcc_periph_clock_enable(GPIO_RCC(MICROWIRE_MASTER_SDO_PIN)); // enable clock for GPIO domain for SDO signal
gpio_set_mode(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // set SDO signal as output (controlled by the master)
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // SDO is idle low
rcc_periph_clock_enable(GPIO_RCC(MICROWIRE_MASTER_SDI_PIN)); // enable clock for GPIO domain for SDI signal
gpio_set_mode(GPIO_PORT(MICROWIRE_MASTER_SDI_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(MICROWIRE_MASTER_SDI_PIN)); // set SDI signal as output (controlled by the slave)
rcc_periph_clock_enable(GPIO_RCC(MICROWIRE_MASTER_SCK_PIN)); // enable clock for GPIO domain for SCK signal
gpio_set_mode(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // set SCK signal as output (controlled by the master)
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // SCK is idle low
// setup timer to generate timing for the signal
rcc_periph_clock_enable(RCC_TIM(MICROWIRE_MASTER_TIMER)); // enable clock for timer domain
rcc_periph_reset_pulse(RST_TIM(MICROWIRE_MASTER_TIMER)); // reset timer state
timer_set_mode(TIM(MICROWIRE_MASTER_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
uint16_t prescaler = rcc_ahb_frequency / (frequency * 2) / (uint32_t)(1 << 16) + 1; // calculate prescaler for most accurate timing for this speed
timer_set_prescaler(TIM(MICROWIRE_MASTER_TIMER), prescaler - 1); // set calculated prescaler
uint16_t period = (rcc_ahb_frequency / prescaler) / (frequency * 2); // calculate period to get most accurate timing based on the calculated prescaler
timer_set_period(TIM(MICROWIRE_MASTER_TIMER), period - 1); // set calculated period
timer_update_on_overflow(TIM(MICROWIRE_MASTER_TIMER)); // only use counter overflow as UEV source (use overflow as timeout)
SCB_SCR |= SCB_SCR_SEVONPEND; // enable wake up on event (instead of using ISR)
timer_enable_irq(TIM(MICROWIRE_MASTER_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
}
/** wait for clock tick used to synchronise communication */
static void microwire_master_wait_clock(void)
{
while ( !timer_get_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF)) { // wait for timer overflow event for clock change
__asm__("wfe"); // go to sleep and wait for event
}
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up)
}
/** send bit over microwire
* @param[in] bit bit to send (true = '1', false = '0')
*/
static void microwire_master_send_bit(bool bit)
{
if (bit) {
gpio_set(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // set '1' on output
} else {
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // set '0' on output
}
microwire_master_wait_clock(); // wait for clock timing
gpio_set(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // make rising edge for slave to sample
microwire_master_wait_clock(); // keep output signal stable while clock is high
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // put clock back to idle
}
/** initialize microwire communication and send header (with leading start bit '1')
* @param[in] operation operation code to send (2 bits)
* @param[in] address slave memory address to select
*/
static void microwire_master_start(uint8_t operation, uint32_t address)
{
// to sanity checks
if (0==mirowire_master_address_size) { // can't send address
return;
}
// initial setup
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is low (to sample on rising edge)
timer_set_counter(TIM(MICROWIRE_MASTER_TIMER), 0); // reset timer counter
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up)
timer_enable_counter(TIM(MICROWIRE_MASTER_TIMER)); // start timer to generate timing
// send '1' start bit
microwire_master_send_bit(true); // send start bit
// send two bits operation code
if (operation & 0x2) { // send first bit (MSb first)
microwire_master_send_bit(true); // send '1'
} else {
microwire_master_send_bit(false); // send '2'
}
if (operation & 0x1) { // send second bit (LSb last)
microwire_master_send_bit(true); // send '1'
} else {
microwire_master_send_bit(false); // send '2'
}
// send address
for (uint8_t bit = mirowire_master_address_size; bit > 0; bit--) {
if ((address >> (bit - 1)) & 0x01) {
microwire_master_send_bit(true); // send '1' address bit
} else {
microwire_master_send_bit(false); // send '0' address bit
}
}
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // ensure output is idle low (could be floating)
}
/** stop microwire communication and end all activities */
static void microwire_master_stop(void)
{
timer_disable_counter(TIM(MICROWIRE_MASTER_TIMER)); // disable timer
timer_set_counter(TIM(MICROWIRE_MASTER_TIMER), 0); // reset timer counter
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is idle low
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SDO_PIN), GPIO_PIN(MICROWIRE_MASTER_SDO_PIN)); // ensure output is idle low
}
/** read bit from microwire communication
* @return bit value (true = '1', false = '0')
*/
static bool microwire_master_read_bit(void)
{
microwire_master_wait_clock(); // wait for clock timing
gpio_set(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // make rising edge for slave to output data
microwire_master_wait_clock(); // wait for signal to be stable
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // set clock low again
return 0 != gpio_get(GPIO_PORT(MICROWIRE_MASTER_SDI_PIN), GPIO_PIN(MICROWIRE_MASTER_SDI_PIN)); // read input signal
}
void microwire_master_read(uint32_t address, uint16_t* data, size_t length)
{
// to sanity checks
if (NULL == data || 0 == length || 0 == mirowire_master_address_size) { // can't save data
return;
}
microwire_master_start(0x02, address); // send '10' READ instruction and memory address
// there should already be a '0' dummy bit
if (0!=gpio_get(GPIO_PORT(MICROWIRE_MASTER_SDI_PIN), GPIO_PIN(MICROWIRE_MASTER_SDI_PIN))) { // the dummy bit wasn't '0'
goto clean;
}
// read data
for (size_t i = 0; i < length; i++) {
for (uint8_t b = (mirowire_master_organization_x16 ? 16 : 8); b > 0; b--) {
if (microwire_master_read_bit()) { // read bit, MSb first
data[i] |= (1 << (b - 1)); // set bit
} else {
data[i] &= ~(1 << (b - 1)); // clear bit
}
}
}
clean:
microwire_master_stop(); // stop communication and clean up
}
void microwire_master_write_enable(void)
{
// to sanity checks
if (mirowire_master_address_size < 2) { // can't send '11...' address
return;
}
microwire_master_start(0x0, 0x3 << (mirowire_master_address_size - 2)); // send '00' WEN operation code and '11...' address
microwire_master_stop(); // clean up
}
void microwire_master_write_disable(void)
{
// to sanity checks
if (mirowire_master_address_size < 2) { // can't send '00...' address
return;
}
microwire_master_start(0x0, 0); // send '00' WDS operation code and '00...' address
microwire_master_stop(); // clean up
}
void microwire_master_write(uint32_t address, uint16_t data)
{
// to sanity checks
if (0==mirowire_master_address_size) { // can't send address
return;
}
microwire_master_start(0x01, address); // send '01' WRITE operation code and memory address
// write data (MSb first)
for (uint8_t b=(mirowire_master_organization_x16 ? 16 : 8); b>0; b--) {
if (data&(1<<(b-1))) { // bit is set
microwire_master_send_bit(true); // send '1' data bit
} else {
microwire_master_send_bit(false); // send '0' data bit
}
}
microwire_master_stop(); // clean up
}
void microwire_master_wait_ready(void)
{
// initial setup
gpio_clear(GPIO_PORT(MICROWIRE_MASTER_SCK_PIN), GPIO_PIN(MICROWIRE_MASTER_SCK_PIN)); // ensure clock is low (to sample on rising edge)
timer_set_counter(TIM(MICROWIRE_MASTER_TIMER), 0); // reset timer counter
timer_clear_flag(TIM(MICROWIRE_MASTER_TIMER), TIM_SR_UIF); // clear timer flag
nvic_clear_pending_irq(NVIC_TIM_IRQ(MICROWIRE_MASTER_TIMER)); // clear IRQ flag (else event doesn't wake up)
timer_enable_counter(TIM(MICROWIRE_MASTER_TIMER)); // start timer to generate timing
// SDI low on busy, high on ready, clock is ignored
while (!microwire_master_read_bit()); // wait until slave is ready
microwire_master_stop(); // clean up
}
void microwire_master_erase(uint32_t address)
{
// sanity checks
if (0==mirowire_master_address_size) { // can't send address
return;
}
microwire_master_start(0x03, address); // send '11' ERASE operation code and memory address
microwire_master_stop(); // clean up
}
void microwire_master_erase_all(void)
{
// sanity checks
if (mirowire_master_address_size < 2) { // can't send '11...' address
return;
}
microwire_master_start(0x00, 0x2 << (mirowire_master_address_size - 2)); // send '00' ERAL operation code and '10...' address
microwire_master_stop(); // clean up
}
void microwire_master_write_all(uint16_t data)
{
// sanity checks
if (0 == mirowire_master_address_size) { // can't send address
return;
}
microwire_master_start(0x00, 0x1 << (mirowire_master_address_size - 2)); // send '00' WRAL operation code and '01...' address
// write data (MSb first)
for (uint8_t b = (mirowire_master_organization_x16 ? 16 : 8); b > 0; b--) {
if (data&(1 << (b - 1))) { // bit is set
microwire_master_send_bit(true); // send '1' data bit
} else {
microwire_master_send_bit(false); // send '0' data bit
}
}
microwire_master_stop(); // clean up
}

View File

@ -1,68 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate using microwore as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: GPIO @ref microwire_master_gpio, timer @ref microwire_master_timer
* microwire is a 3-Wire half-duplex synchronous bus. It is very similar to SPI without fixed length messages (bit-wised).
* @note the user has to handle the slave select pin (high during operations) so to be able to handle multiple slaves.
* @warning this library implements the M93Cx8 EEPROM operation codes. Other microwire-based ICs might use different ones.
*/
#pragma once
/** setup microwire peripheral
* @param[in] frequency clock frequency in Hz
* @param[in] organization_x16 if x16 memory organization (16-bits) is used, or x8 (8-bits)
* @param[in] address_size address size in bits
* @note frequency practically limited to 500 kHz due to the software implementation nature
*/
void microwire_master_setup(uint32_t frequency, bool organization_x16, uint8_t address_size);
/** read data from slave memory
* @param[in] address memory address of data to read
* @param[out] data array to store read data
* @param[in] length number of data bytes/words to read
*/
void microwire_master_read(uint32_t address, uint16_t* data, size_t length);
/** enable write and erase operations
* @note on slave boot write is disable to prevent corruption
*/
void microwire_master_write_enable(void);
/** disable write and erase operations
* @note this should be done after every complete write operation to protect against corruption
*/
void microwire_master_write_disable(void);
/** write data to slave memory
* @param[in] address memory address of data to read
* @param[in] data byte/word to write
* @note after each write and before the next operation user should wait for the slave to be ready
*/
void microwire_master_write(uint32_t address, uint16_t data);
/** wait until slave is ready after a write or erase */
void microwire_master_wait_ready(void);
/** erase memory
* @param[in] address memory address of data to read
* @note after each erase and before the next operation user should wait for the slave to be ready
*/
void microwire_master_erase(uint32_t address);
/** erase all memory
* @note after each erase and before the next operation user should wait for the slave to be ready
*/
void microwire_master_erase_all(void);
/** write data to all slave memory
* @param[in] data byte/word to write
* @note after each write and before the next operation user should wait for the slave to be ready
*/
void microwire_master_write_all(uint16_t data);

View File

@ -1,140 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** SSD1306 OLED library (code)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018-2019
* @note peripherals used: I2C @ref oled_ssd1306_i2c
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean type
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/i2c.h> // I2C library
/* own libraries */
#include "global.h" // global utilities
#include "oled_ssd1306.h" // OLED definitions
#include "i2c_master.h" // I²C header and definitions
/** SSD1306 OLED display I²C slave address */
static uint8_t oled_ssd1306_slave_addr = 0x3c;
/** @defgroup oled_ssd1306_i2c I²C peripheral to communicate with the SSD1306 OLED
* @{
*/
#define OLED_SSD1306_I2C I2C1 /**< I²C peripheral */
/** @} */
bool oled_ssd1306_setup(uint8_t slave_addr)
{
if (!i2c_master_check_signals(OLED_SSD1306_I2C)) { // check if there are pull-ups to operator I²C
return false;
}
oled_ssd1306_slave_addr = slave_addr; // save I²C slave address of SSD1306
i2c_master_setup(OLED_SSD1306_I2C, 400); // setup I²C bus (SSD1306 supports an I²C clock up to 400 kHz)
const uint8_t oled_init[] = {
0x00, // control byte: continuous (multiple byes), command
0xae, // Set Display ON/OFF: OFF
// hardware configuration
0xa8, 0x3f, // Set Multiplex Ratio: 64
0xd3, 0x00, // Set Display Offset: 0
0xa1, // Set Segment Re-map: column address 0 is mapped to SEG127
0xc8, // Set COM Output Scan Direction: normal mode (RESET) Scan from COM[N-1] to COM[0]
0xda, 0x12, // Set COM Pins Hardware Configuration: Alternative COM pin configuration, Disable COM Left/Right remap
0x40, // Set Display Start Line: start line register from 0
// fundamental commands
0x81, 0xff, // Set Contrast Control: 256
0xa6, // Set Normal/Inverse Display: Normal display (RESET)
// Timing & Driving Scheme Setting
0xd5, 0xf0, // Set Display Clock Divide Ratio/Oscillator Frequency: Divide ratio=129, F_OSC=1
0xd9, 0x22, // Set Pre-charge Period: Phase 1=2 DCLK, Phase 2=2DCLK
0xdb, 0x20, // Set V_COMH Deselect Level: ~0.77xV_CC
// Charge Pump
0x8d, 0x14, // Charge Pump Setting: Enable Charge Pump
// Addressing Setting
0x20, 0x00 // Set Memory Addressing Mode: Horizontal Addressing Mode
}; // command to initialize the display
return I2C_MASTER_RC_NONE == i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_init, LENGTH(oled_init)); // send command to initialize display
}
void oled_ssd1306_on(void)
{
const uint8_t oled_display_on[] = {
0x80, // control byte: no continuation, command
0xaf, // Set Display ON/OFF: ON
}; // command to switch on display
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_display_on, LENGTH(oled_display_on)); // sent command to switch on display
}
void oled_ssd1306_off(void)
{
const uint8_t oled_display_off[] = {
0x80, // control byte: no continuation, command
0xae, // Set Display ON/OFF: OFF
}; // command to switch off display
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_display_off, LENGTH(oled_display_off)); // sent command to switch font display
}
void oled_ssd1306_test(void)
{
const uint8_t oled_entire_display_on[] = {
0x80, // control byte: no continuation, command
0xa5 // Entire Display ON: Entire display ON Output ignores RAM content
}; // command to set entire display on
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_entire_display_on, LENGTH(oled_entire_display_on)); // send command to switch entire display on
oled_ssd1306_on(); // set display on
sleep_ms(200); // let is on for a bit (enough for the user to see it is completely on
oled_ssd1306_off(); // set display off
const uint8_t oled_entire_display_ram[] = {
0x80, // control byte: no continuation, command
0xa4 // Entire Display ON: Resume to RAM content display
}; // command to display RAM
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_entire_display_ram, LENGTH(oled_entire_display_ram)); // send command to display RAM
}
void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length)
{
// verify input
if (0 == display_length || NULL == display_data) {
return;
}
const uint8_t oled_start_page[] = {
0x00, // control byte: continuous (multiple byes), command
0xb0, // Set Page Start Address for Page Addressing Mode: PAGE0
0x00, // Set Lower Column Start Address for Page Addressing Mode: 0
0x10 // Set Higher Column Start Address for Page Addressing Mode: 0
}; // command to set addressing mode
i2c_master_slave_write(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, oled_start_page, LENGTH(oled_start_page)); // send command to set addressing mode
if (I2C_MASTER_RC_NONE != i2c_master_start(OLED_SSD1306_I2C)) { // send start condition
return;
}
if (I2C_MASTER_RC_NONE != i2c_master_select_slave(OLED_SSD1306_I2C, oled_ssd1306_slave_addr, false, true)) { // select OLED display
return;
}
uint8_t oled_data[display_length + 1]; // we have to copy the data because we need to send a single block since i2c_master_write sends a stop
oled_data[0] = 0x40; // control byte: continuous (multiple byes), data
for (uint16_t i = 0; i < display_length; i++) {
oled_data[i + 1] = display_data[i];
}
if (I2C_MASTER_RC_NONE != i2c_master_write(OLED_SSD1306_I2C, oled_data, display_length + 1)) { // send data header
return;
}
i2c_master_stop(OLED_SSD1306_I2C); // send stop condition
}

View File

@ -1,38 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** SSD1306 OLED library (API)
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018-2019
* @note peripherals used: I2C @ref oled_ssd1306_i2c
*/
/** setup OLED display
* @param[in] slave_addr I²C slave address of SSD1306 device (least significant 7-bit)
* @return if the display setup is successful, else the display is probably not on the I²C bus
* @warning only one display on the I²C bus is currently supported
*/
bool oled_ssd1306_setup(uint8_t slave_addr);
/** switch OLED display on */
void oled_ssd1306_on(void);
/** switch OLED display off */
void oled_ssd1306_off(void);
/** test OLED display: switch entire screen on for a brief time */
void oled_ssd1306_test(void);
/** send data to display to OLED display
* @param[in] display_data data to display (first byte is left column, MSb is top pixel, warps pages)
* @param[in] display_length length of data to display
*/
void oled_ssd1306_display(const uint8_t* display_data, uint16_t display_length);

View File

@ -1,519 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library for 1-wire protocol as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: timer @ref onewire_master_timer, GPIO @ref onewire_master_gpio
* @note overdrive mode is not provided
* @implements 1-Wire protocol description from Book of iButton Standards
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean type
#include <stddef.h> // NULL definition
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer library
/* own libraries */
#include "global.h" // help macros
#include "interrupt.h" // runtime interrupt table
#include "onewire_master.h" // own definitions
/** @defgroup onewire_master_gpio GPIO used for 1-wire signal
* @note external pull-up resistor on pin is required (< 5 kOhm)
* @{
*/
#define ONEWIRE_MASTER_PIN PC9 /**< GPIO pin */
/** @} */
/** @defgroup onewire_master_timer timer used to measure 1-wire signal timing
* @{
*/
#define ONEWIRE_MASTER_TIMER 5 /**< timer ID */
/** @} */
/** set if the timer ISR should be set in the interrupt table instead of the vector table
* @note the vector table is faster, but doesn't allow to change the ISR
*/
#define ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE false
/** state of 1-Wire communication */
volatile enum {
ONEWIRE_STATE_IDLE, /**< no current communication */
ONEWIRE_STATE_DONE, /**< communication complete */
ONEWIRE_STATE_ERROR, /**< communication error */
ONEWIRE_STATE_MASTER_RESET, /**< reset pulse started */
ONEWIRE_STATE_SLAVE_PRESENCE, /**< waiting for slave response to reset pulse */
ONEWIRE_STATE_MASTER_WRITE, /**< master is writing bits */
ONEWIRE_STATE_MASTER_READ, /**< master is reading bits */
ONEWIRE_MAX /** to count the number of possible states */
} onewire_master_state = ONEWIRE_STATE_IDLE;
static volatile bool slave_presence = false; /**< if slaves have been detected */
static uint8_t* buffer = NULL; /**< input/output buffer for read/write commands/functions */
static uint32_t buffer_size = 0; /**< size of buffer in bits */
static volatile uint32_t buffer_bit = 0; /**< number of bits read/written */
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
static void (*isr_backup)(void) = NULL; /**< backup for the existing timer ISR */
static bool irq_backup = false; /**< backup for the existing timer IRQ */
#endif
/** interrupt service routine called for timer */
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
static void onewire_master_timer_isr(void)
#else
void TIM_ISR(ONEWIRE_MASTER_TIMER)(void)
#endif
{
if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_UIF); // clear flag
switch (onewire_master_state) {
case ONEWIRE_STATE_MASTER_RESET: // reset pulse has been started
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // enable compare interrupt for presence detection
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // set signal high again for slaves to respond
onewire_master_state = ONEWIRE_STATE_SLAVE_PRESENCE; // set new state
break;
case ONEWIRE_STATE_SLAVE_PRESENCE: // waiting for slave presence but none received
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // disable compare interrupt for presence detection
onewire_master_state = ONEWIRE_STATE_DONE; // go to next state
break;
case ONEWIRE_STATE_MASTER_READ: // end of time slot and recovery time for reading bit
case ONEWIRE_STATE_MASTER_WRITE: // end of time slot and recovery time for writing bit
if (buffer_bit < buffer_size) { // check if byte to read/write are remaining
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start next slot
} else { // all bytes read/written
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // disable compare interrupt for master pull low
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable compare interrupt for read/write bit
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // disable compare interrupt for end of slot
onewire_master_state = ONEWIRE_STATE_DONE; // set end state
}
break;
default: // unknown state for this stage
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // disable all compare interrupt
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable all compare interrupt
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // disable all compare interrupt
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC4IE); // disable all compare interrupt
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high (idle state)
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
}
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF)) { // compare event happened for master pull low end for read
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear flag
switch (onewire_master_state) {
case ONEWIRE_STATE_MASTER_READ: // master has to read a bit
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
break;
default: // unknown state for this stage
break; // let the overflow handle the error if any
}
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF)) { // compare event happened for bit sampling/setting
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear flag
switch (onewire_master_state) {
case ONEWIRE_STATE_MASTER_WRITE: // master has to write a bit
if (buffer_bit < buffer_size) { // check if byte to send are remaining
if (buffer[buffer_bit / 8] & (1 << (buffer_bit % 8))) { // check bit (LSb first)
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // set signal high again to write "1"
}
buffer_bit++; // got to next bit
} else {
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable compare interrupt
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
}
break;
case ONEWIRE_STATE_MASTER_READ: // master has to read a bit set by slave
if (buffer_bit<buffer_size) { // check if byte to send are remaining
if (gpio_get(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN))) { // check if the slave kept it low
buffer[buffer_bit / 8] |= (1 << (buffer_bit % 8)); // save bit "1"
} else {
buffer[buffer_bit / 8] &= ~(1 << (buffer_bit % 8)); // save bit "0"
}
buffer_bit++; // got to next bit
} else {
timer_disable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // disable compare interrupt
onewire_master_state = ONEWIRE_STATE_ERROR; // indicate error
}
break;
default: // unknown state for this stage
break; // let the overflow handle the error if any
}
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF)) { // compare event happened for end to time slot
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear flag
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal high to end time slot
} else if (timer_get_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF)) { // compare event happened for slave presence detection
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear flag
if (gpio_get(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN))) { // check is a slave let its presence know by pulling low
slave_presence = false; // remember no slave(s) responded
} else {
slave_presence = true; // remember slave(s) responded
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}
void onewire_master_setup(void)
{
// setup GPIO with external interrupt
rcc_periph_clock_enable(GPIO_RCC(ONEWIRE_MASTER_PIN)); // enable clock for GPIO peripheral
gpio_set(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // idle is high (using pull-up resistor)
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
// setup timer to generate/measure signal timing
rcc_periph_clock_enable(RCC_TIM(ONEWIRE_MASTER_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(ONEWIRE_MASTER_TIMER)); // reset timer state
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to configure it
timer_set_mode(TIM(ONEWIRE_MASTER_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(TIM(ONEWIRE_MASTER_TIMER), 1 - 1); // don't use prescale since this 16 bits timer allows to wait > 480 us used for the reset pulse ( 1/(72E6/1/(2**16))=910us )
// use comparator to time signal (without using the output), starting at slot start
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear flag
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC1, 1 * (rcc_ahb_frequency / 1000000) - 1); // use compare function to time master pulling low when reading (1 < Tlowr < 15)
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear flag
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC2, 7 * (rcc_ahb_frequency / 1000000) - 1); // use compare function to read or write 0 or 1 (1 < Trw < 15)
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear flag
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC3, 62 * (rcc_ahb_frequency / 1000000) - 1); // use compare function to end time slot (60 < Tslot < 120), this will be followed by a recovery time (end of timer)
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC4IF); // clear flag
timer_set_oc_value(TIM(ONEWIRE_MASTER_TIMER), TIM_OC4, (70 - 10) * (rcc_ahb_frequency / 1000000) - 1); // use compare function to detect slave presence (15 < Tpdh < 60 + 60 < Tpdl < 240), with hand tuning
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_UIF); // clear update (overflow) flag
timer_update_on_overflow(TIM(ONEWIRE_MASTER_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
isr_backup = interrupt_table[NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)]; // backup timer ISR
interrupt_table[NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)] = &onewire_master_timer_isr; // set the 1-wire timer ISR
irq_backup = nvic_get_irq_enabled(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // backup timer IRQ setting
#endif
nvic_enable_irq(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // catch interrupt in service routine
slave_presence = false; // reset state
onewire_master_state = ONEWIRE_STATE_IDLE; // reset state
}
void onewire_master_release(void)
{
// release timer
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer
rcc_periph_reset_pulse(RST_TIM(ONEWIRE_MASTER_TIMER)); // reset timer state
rcc_periph_clock_disable(RCC_TIM(ONEWIRE_MASTER_TIMER)); // disable clock for timer peripheral
// release GPIO
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(ONEWIRE_MASTER_PIN)); // put back to input floating
// disable timer ISR
#if defined(ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE) && ONEWIRE_MASTER_TIMER_USE_INTERRUPT_TABLE
if (!irq_backup) { // don't disable the IRQ if there was already enabled
nvic_disable_irq(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // stop timer IRQ
}
interrupt_table[NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)] = isr_backup; // set back original timer ISR
#else
nvic_disable_irq(NVIC_TIM_IRQ(ONEWIRE_MASTER_TIMER)); // stop timer IRQ
#endif
}
bool onewire_master_reset(void)
{
// prepare timer
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to reconfigure it
timer_set_counter(TIM(ONEWIRE_MASTER_TIMER), 0); // reset counter
timer_set_period(TIM(ONEWIRE_MASTER_TIMER), 490 * (rcc_ahb_frequency / 1000000) - 1); // set timeout to > 480 us (480 < Trst)
slave_presence = false; // reset state
onewire_master_state = ONEWIRE_STATE_MASTER_RESET; // set new state
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start reset (it's not important if it was low in the first place since the reset pulse has no maximum time)
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
while (onewire_master_state != ONEWIRE_STATE_DONE && onewire_master_state != ONEWIRE_STATE_ERROR) { // wait until reset procedure completed
__WFI(); // go to sleep
}
if (ONEWIRE_STATE_ERROR == onewire_master_state) { // an error occurred
return false;
}
return slave_presence;
}
/** write bits on 1-Wire bus
* @warning buffer_size must be set to the number of bits to writen and buffer must contain the data to write
* @return if write succeeded
*/
static bool onewire_master_write(void)
{
buffer_bit = 0; // reset bit index
onewire_master_state = ONEWIRE_STATE_MASTER_WRITE; // set new state
// prepare timer
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to reconfigure it
timer_set_counter(TIM(ONEWIRE_MASTER_TIMER), 0); // reset counter
timer_set_period(TIM(ONEWIRE_MASTER_TIMER), TIM_CCR3(TIM(ONEWIRE_MASTER_TIMER)) + 2 * (rcc_ahb_frequency / 1000000)); // set time for new time slot (recovery timer Trec>1, after time slot end )
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // enable compare interrupt for bit setting
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // enable compare interrupt for end of time slow
// start writing
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN),GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
while (onewire_master_state != ONEWIRE_STATE_DONE && onewire_master_state != ONEWIRE_STATE_ERROR) { // wait until write procedure completed
__WFI(); // go to sleep
}
if (ONEWIRE_STATE_ERROR == onewire_master_state) { // an error occurred
return false;
}
return true;
}
/** read bits on 1-Wire bus
* @warning buffer_size must be set to the number of bits to read
* @return if read succeeded
*/
static bool onewire_master_read(void)
{
if (0 == buffer_size) { // check input
return false;
}
buffer_bit = 0; // reset bit index
onewire_master_state = ONEWIRE_STATE_MASTER_READ; // set new state
// prepare timer
timer_disable_counter(TIM(ONEWIRE_MASTER_TIMER)); // disable timer to reconfigure it
timer_set_counter(TIM(ONEWIRE_MASTER_TIMER), 0); // reset counter
timer_set_period(TIM(ONEWIRE_MASTER_TIMER), TIM_CCR3(TIM(ONEWIRE_MASTER_TIMER)) + 2*(rcc_ahb_frequency / 1000000)); // set time for new time slot (recovery timer Trec>1, after time slot end )
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC1IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC1IE); // enable compare interrupt for stop pulling low
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC2IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC2IE); // enable compare interrupt for bit setting
timer_clear_flag(TIM(ONEWIRE_MASTER_TIMER), TIM_SR_CC3IF); // clear output compare flag
timer_enable_irq(TIM(ONEWIRE_MASTER_TIMER), TIM_DIER_CC3IE); // enable compare interrupt for end of time slow
// start reading
gpio_set_mode(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_MASTER_PIN)); // normal 1-Wire communication (only using external pull-up resistor)
gpio_clear(GPIO_PORT(ONEWIRE_MASTER_PIN), GPIO_PIN(ONEWIRE_MASTER_PIN)); // pull signal low to start slot
timer_enable_counter(TIM(ONEWIRE_MASTER_TIMER)); // start timer
while (onewire_master_state != ONEWIRE_STATE_DONE && onewire_master_state != ONEWIRE_STATE_ERROR) { // wait until read procedure completed
__WFI(); // go to sleep
}
if (ONEWIRE_STATE_ERROR == onewire_master_state) { // an error occurred
return false;
}
return true;
}
uint8_t onewire_master_crc(uint8_t* data, uint32_t length)
{
if (NULL == data || 0 == length) { // check input
return 0; // wrong input
}
uint8_t crc = 0x00; // initial value
for (uint8_t i = 0; i < length; i++) { // go through every byte
crc ^= data[i]; // XOR byte
for (uint8_t b = 0; b < 8; b++) { // go through every bit
if (crc & 0x01) { // least significant bit is set (we are using the reverse way)
crc = (crc >> 1) ^ 0x8C; // // shift to the right (for the next bit) and XOR with (reverse) polynomial
} else {
crc >>= 1; // just shift right (for the next bit)
}
}
}
return crc;
}
bool onewire_master_read_byte(uint8_t* data)
{
if (NULL == data) { // check input
return false; // wrong input
}
// read data
buffer_size = 8; // save number of bits to read (1 byte)
buffer = data; // set the buffer to the data to write
if (!onewire_master_read()) { // read bits from slave
return false; // an error occurred
}
return true;
}
bool onewire_master_write_byte(uint8_t data)
{
// send data byte
buffer_size = 8; // function command is only one byte
buffer = &data; // set the buffer to the function code
if (!onewire_master_write()) { // send command
return false; // an error occurred
}
return true;
}
bool onewire_master_function_read(uint8_t function, uint8_t* data, uint32_t bits)
{
// send function command
if (!onewire_master_write_byte(function)) {
return false; // an error occurred
}
if (NULL == data || 0 == bits) { // there is no data to read
return true; // operation completed
}
// read data
buffer_size = bits; // save number of bits to read
buffer = data; // set the buffer to the data to write
if (!onewire_master_read()) { // read bits from slave
return false; // an error occurred
}
return true;
}
bool onewire_master_function_write(uint8_t function, uint8_t* data, uint32_t bits)
{
// send function command
if (!onewire_master_write_byte(function)) {
return false; // an error occurred
}
if (NULL == data || 0 == bits) { // there is no data to read
return true; // operation completed
}
// copy data from user buffer
buffer_size = bits; // save number of bits to write
buffer = data; // set the buffer to the data to write
// write data
if (!onewire_master_write()) { // read bits from slave
return false; // an error occurred
}
return true;
}
uint64_t onewire_master_rom_read(void)
{
uint8_t rom_code[8] = {0}; // to store 64 bits ROM code
if (!onewire_master_function_read(0x33, rom_code, 64)) { // read ROM code (I'm cheating because the ROM command isn't a function command, but it works the same way in the end)
return 0; // an error occurred
}
if (onewire_master_crc(rom_code, LENGTH(rom_code))) { // verify checksum
return 0; // checksum is wrong (not 0)
}
// return ROM code
uint64_t code = 0;
for (uint32_t i = 0; i < 8; i++) {
code += (uint64_t)rom_code[i] << (8 * i); // add byte
}
return code;
}
bool onewire_master_rom_search(uint64_t* code, bool alarm)
{
static uint8_t conflict_last = 64; // on which bit has the conflict been detected (64 means there hasn't been)
uint8_t conflict_current = 64; // to remember on which bit the last unknown conflict has been detected
uint8_t bits[1] = {0}; // small buffer to store the bits used to search the ROM codes
// send SEARCH ROM command
uint8_t command = 0xf0; // SEARCH ROM command
if (alarm) { // looking only for ROM codes for slaves in alarm state
command = 0xec; // use ALARM SEARCH ROM command instead
}
if (!onewire_master_function_read(command, NULL, 0)) { // send SEARCH ROM command
goto end; // an error occurred
}
if (conflict_last >= 64) { // no previous conflict has been detected
*code = 0; // restart search codes
}
buffer = bits; // buffer to read up to two bits
for (uint8_t bit = 0; bit < 64; bit++) { // go through all 64 bits ROM code
buffer_size = 2; // read two first bits to detect conflict
if (!onewire_master_read()) { // read ROM ID from slave
goto end; // an error occurred
}
switch (buffer[0] & 0x03) { // check 2 bits received
case 0: // collision detected
if (bit == conflict_last) { // this conflict is known
*code |= (((uint64_t)1) << bit); // use 0 as next bit
} else { // unknown conflict
conflict_current = bit; // remember conflict
*code &= ~(((uint64_t)1) << bit); // use 1 as next bit
}
break;
case 1: // no conflict, valid bit is 1
*code |= (((uint64_t)1) << bit); // remember valid bit 1
break;
case 2: // no conflict, valid bit is 0
*code &= ~(((uint64_t)1) << bit); // remember valid bit 0
break;
default: // two 1's indicate there is no slave
conflict_current = 64; // remember there was no conflict because there is no slave
goto end; // an error has occurred
}
buffer_size = 1; // to send next bit
buffer[0] = ((*code) >> bit); // set bit to send
if (!onewire_master_write()) { // send bit
goto end; // an error has occurred
}
}
// verify ROM code
uint8_t rom_code[8] = {0}; // to store ROM code
for (uint8_t i = 0; i < LENGTH(rom_code); i++) {
rom_code[i] = (*code) >> (8 * i); // split and save last code in ROM code
}
if (onewire_master_crc(rom_code, LENGTH(rom_code))) { // verify checksum
*code = 0; // return the last code found since it's valid
}
end:
conflict_last = conflict_current; // update the last seen and unknown conflict
if (conflict_current < 64) { // we have seen an unknown conflict
return true; // tell there are more slaves
} else { // no conflict seen
return false; // no more slaves
}
}
bool onewire_master_rom_skip(void)
{
if (!onewire_master_function_write(0xcc, NULL, 0)) { // send SKIP ROM command
return false; // an error occurred
}
return true;
}
bool onewire_master_rom_match(uint64_t code)
{
uint8_t rom_code[8] = {0}; // to store ROM code
for (uint8_t i = 0; i < LENGTH(rom_code); i++) {
rom_code[i] = code >> (8 * i); // split and save code in ROM code
}
if (!onewire_master_function_write(0x55, rom_code, 64)) { // send MATCH ROM command with ROM code
return false; // an error occurred
}
return true;
}

View File

@ -1,379 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library for 1-wire protocol as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: timer @ref onewire_master_timer, GPIO @ref onewire_master_gpio
* @note overdrive mode is not provided
*/
#pragma once
/** setup 1-wire peripheral
*/
void onewire_master_setup(void);
/** release 1-wire peripheral
*/
void onewire_master_release(void);
/** send reset pulse
* @return if slaves have indicated their presence
*/
bool onewire_master_reset(void);
/** compute CRC for 1-Wire
* @note this CRC-8 uses normal polynomial 0x31, reverse polynomial 0x8C, start value 0x00
* @param[in] data bytes on which to calculate CRC checksum on
* @param[in] length number of bytes in data
* @return computed CRC checksum
*/
uint8_t onewire_master_crc(uint8_t* data, uint32_t length);
/** send READ ROM command and read ROM code response
* @note user needs to send reset pulse before
* @return ROM code read
*/
uint64_t onewire_master_rom_read(void);
/** send SEARCH ROM command
* @note user needs to send reset pulse before
* @warning undefined behaviour if a ROM code different than the last found is provided
* @param[in,out] code use 0 to start search ROM code from scratch, or last know value to search next; writes back next ROM code found, or 0 if error occurred
* @param[in] alarm search only for ROM codes for slaves with an alarm flag set
* @return if an additional slave has been detected
* @warning when the code found is 0 it very probably means that the 1-wire line is not pulled up instead of actually having found a slave with ROM code 0
*/
bool onewire_master_rom_search(uint64_t* code, bool alarm);
/** send SKIP ROM command (all slaves on the bus will be selected)
* @note user needs to send reset pulse before
* @return if operation succeeded
*/
bool onewire_master_rom_skip(void);
/** send MATCH ROM command to select a specific slave
* @note user needs to send reset pulse before
* @param[in] code ROM code of slave to select
* @return if operation succeeded
*/
bool onewire_master_rom_match(uint64_t code);
/** read data byte
* @note it is up to the user to send the reset pulse
* @param[out] data buffer to save data read
* @return if operation succeeded
*/
bool onewire_master_read_byte(uint8_t* data);
/** write data byte
* @note it is up to the user to send the reset pulse
* @param[in] data byte to write
* @return if operation succeeded
*/
bool onewire_master_write_byte(uint8_t data);
/** issue function and read data
* @note user needs to send a ROM command before
* @param[in] function function command to send
* @param[out] data buffer to save read bits (NULL if only the function command should be sent)
* @param[in] bits number of bits to read (0 if only the function command should be sent)
* @return if operation succeeded
*/
bool onewire_master_function_read(uint8_t function, uint8_t* data, uint32_t bits);
/** issue function and write data
* @note user needs to send a ROM command before
* @param[in] function function command to send
* @param[out] data data to write (NULL if only the function command should be sent)
* @param[in] bits number of bits to write (0 if only the function command should be sent)
* @return if operation succeeded
*/
bool onewire_master_function_write(uint8_t function, uint8_t* data, uint32_t bits);
/** device corresponding to a family code
*/
struct onewire_family_code_t {
uint8_t code; /**< ROM ID code */
const char* device; /**< device name(s) */
};
/** list of possible devices corresponding to the family code
* sources:
* - http://owfs.org/index.php?page=family-code-list
* - http://owfs.sourceforge.net/family.html
* - https://www.maximintegrated.com/en/app-notes/index.mvp/id/155
* - https://github.com/owfs/owfs-doc/wiki/1Wire-Device-List
* - IDs seen or reported
*/
static const struct onewire_family_code_t onewire_family_codes[] = {
{
.code = 0x01,
.device = "DS1990R/DS2401/DS2411/DS2490A",
},
{
.code = 0x02,
.device = "DS1991/DS1425",
},
{
.code = 0x04,
.device = "DS1994/DS2404",
},
{
.code = 0x05,
.device = "DS2405",
},
{
.code = 0x06,
.device = "DS1993",
},
{
.code = 0x08,
.device = "DS1992",
},
{
.code = 0x09,
.device = "DS1982/DS2502/DS2703/DS2704",
},
{
.code = 0x0a,
.device = "DS1995",
},
{
.code = 0x0b,
.device = "DS1985/DS2505",
},
{
.code = 0x0c,
.device = "DS1996",
},
{
.code = 0x0f,
.device = "DS1986/DS2506",
},
{
.code = 0x10,
.device = "DS1920/DS18S20",
},
{
.code = 0x12,
.device = "DS2406/DS2407",
},
{
.code = 0x14,
.device = "DS1971/DS2430A",
},
{
.code = 0x16,
.device = "DS1954/DS1957",
},
{
.code = 0x18,
.device = "DS1963S/DS1962",
},
{
.code = 0x1a,
.device = "DS1963L",
},
{
.code = 0x1b,
.device = "DS2436",
},
{
.code = 0x1c,
.device = "DS28E04-100",
},
{
.code = 0x1d,
.device = "DS2423",
},
{
.code = 0x1e,
.device = "DS2437",
},
{
.code = 0x1f,
.device = "DS2409",
},
{
.code = 0x20,
.device = "DS2450",
},
{
.code = 0x21,
.device = "DS1921",
},
{
.code = 0x22,
.device = "DS1922",
},
{
.code = 0x23,
.device = "DS1973/DS2433",
},
{
.code = 0x24,
.device = "DS1904/DS2415",
},
{
.code = 0x26,
.device = "DS2438",
},
{
.code = 0x27,
.device = "DS2417",
},
{
.code = 0x28,
.device = "DS18B20",
},
{
.code = 0x29,
.device = "DS2408",
},
{
.code = 0x2c,
.device = "DS2890",
},
{
.code = 0x2d,
.device = "DS1972/DS2431",
},
{
.code = 0x2e,
.device = "DS2770",
},
{
.code = 0x2f,
.device = "DS28E01-100",
},
{
.code = 0x30,
.device = "DS2760/DS2761/DS2762",
},
{
.code = 0x31,
.device = "DS2720",
},
{
.code = 0x32,
.device = "DS2780",
},
{
.code = 0x33,
.device = "DS1961S/DS2432",
},
{
.code = 0x34,
.device = "DS2703",
},
{
.code = 0x35,
.device = "DS2755",
},
{
.code = 0x36,
.device = "DS2740",
},
{
.code = 0x37,
.device = "DS1977",
},
{
.code = 0x3a,
.device = "DS2413",
},
{
.code = 0x3b,
.device = "DS1825/MAX31826/MAX31850",
},
{
.code = 0x3d,
.device = "DS2781",
},
{
.code = 0x41,
.device = "DS1922/DS1923/DS2422",
},
{
.code = 0x42,
.device = "DS28EA00",
},
{
.code = 0x43,
.device = "DS28EC20",
},
{
.code = 0x44,
.device = "DS28E10",
},
{
.code = 0x51,
.device = "DS2751",
},
{
.code = 0x7e,
.device = "EDS00xx",
},
{
.code = 0x81,
.device = "DS1420/DS2490R/DS2490B",
},
{
.code = 0x82,
.device = "DS1425",
},
{
.code = 0x84,
.device = "DS2404S",
},
{
.code = 0x89,
.device = "DS1982U/DS2502",
},
{
.code = 0x8b,
.device = "DS1985U/DS2505",
},
{
.code = 0x8f,
.device = "DS1986U/DS2506",
},
{
.code = 0xa0,
.device = "mRS001",
},
{
.code = 0xa1,
.device = "mVM001",
},
{
.code = 0xa2,
.device = "mCM001",
},
{
.code = 0xa6,
.device = "mTS017",
},
{
.code = 0xb1,
.device = "mTC001",
},
{
.code = 0xb2,
.device = "mAM001",
},
{
.code = 0xb3,
.device = "DS2432/mTC002",
},
{
.code = 0xfc,
.device = "BAE0910/BAE0911",
},
{
.code = 0xff,
.device = "Swart LCD",
}
};

View File

@ -1,408 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library for 1-wire protocol as master
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: GPIO and timer @ref onewire_slave_timer, GPIO @ref onewire_slave_gpio
* @note overdrive mode is not supported
* @implements 1-Wire protocol description from Book of iButton Standards
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdbool.h> // boolean type
#include <stddef.h> // NULL definition
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/timer.h> // timer library
#include <libopencm3/stm32/exti.h> // external interrupt library
/* own libraries */
#include "global.h" // help macros
#include "onewire_slave.h" // own definitions
/** @defgroup onewire_slave_timer timer used to measure 1-wire signal timing
* @{
*/
#define ONEWIRE_SLAVE_TIMER 2 /**< timer ID */
/** @} */
/** @defgroup onewire_slave_gpio GPIO used for 1-wire signal
* @warning ensure no same pin number on other parts are used for external interrupts
* @note external pull-up resistor on pin is required (< 5 kOhm), generally provided by the master
* @{
*/
#define ONEWIRE_SLAVE_PIN PA4 /**< GPIO pin */
/** @} */
/** state of 1-Wire communication */
static volatile enum {
ONEWIRE_STATE_IDLE, /**< no current communication */
ONEWIRE_STATE_RESET, /**< reset pulse has been detected */
ONEWIRE_STATE_WAIT_PRESENCE, /**< waiting before sending the presence pulse */
ONEWIRE_STATE_PULSE_PRESENCE, /**< sending the presence pulse */
ONEWIRE_STATE_ROM_COMMAND, /**< slave is reading ROM command bits */
ONEWIRE_STATE_ROM_READ, /**< slave is sending ROM code in response to ROM command READ ROM */
ONEWIRE_STATE_ROM_MATCH, /**< master is sending ROM code to select slave */
ONEWIRE_STATE_ROM_SEARCH_TRUE, /**< master is searching ROM code, slave will send first bit (not negated) */
ONEWIRE_STATE_ROM_SEARCH_FALSE, /**< master is searching ROM code, slave will send first bit (not negated) */
ONEWIRE_STATE_ROM_SEARCH_SELECT, /**< master is searching ROM code, slave will read selected bit */
ONEWIRE_STATE_FUNCTION_COMMAND, /**< slave is reading function command bits */
ONEWIRE_STATE_FUNCTION_DATA, /**< waiting for user to provide data to transfer */
ONEWIRE_STATE_FUNCTION_READ, /**< slave is reading bits */
ONEWIRE_STATE_FUNCTION_WRITE, /**< slave is writing bits */
ONEWIRE_MAX /** to count the number of possible states */
} onewire_slave_state = ONEWIRE_STATE_IDLE;
static uint8_t onewire_slave_rom_code[8] = {0}; /**< slave ROM code */
volatile bool onewire_slave_function_code_received = false;
volatile uint8_t onewire_slave_function_code = 0;
volatile bool onewire_slave_transfer_complete = false;
static volatile uint8_t bits_buffer = 0; /**< buffer for the incoming bits (up to one byte) */
static volatile uint32_t bits_bit = 0; /**< number of incoming bits */
static volatile uint8_t* onewire_slave_transfer_data = NULL; /**< data to transfer (read or write) */
static volatile uint32_t onewire_slave_transfer_bits = 0; /**< number of bits to transfer */
/** compute CRC for 1-Wire
* @note this CRC-8 uses normal polynomial 0x31, reverse polynomial 0x8C, start value 0x00
* @param[in] data bytes on which to calculate CRC checksum on
* @param[in] length number of bytes in data
* @return computed CRC checksum
*/
static uint8_t onewire_slave_crc(uint8_t* data, uint32_t length)
{
if (NULL==data || 0==length) { // check input
return 0; // wrong input
}
uint8_t crc = 0x00; // initial value
for (uint8_t i=0; i<length; i++) { // go through every byte
crc ^= data[i]; // XOR byte
for (uint8_t b=0; b<8; b++) { // go through every bit
if (crc&0x01) { // least significant bit is set (we are using the reverse way)
crc = (crc>>1)^0x8C; // // shift to the right (for the next bit) and XOR with (reverse) polynomial
} else {
crc >>= 1; // just shift right (for the next bit)
}
}
}
return crc;
}
void onewire_slave_setup(uint8_t family, uint64_t serial)
{
// save ROM code (LSB first)
onewire_slave_rom_code[0] = family;
onewire_slave_rom_code[1] = serial >> 40;
onewire_slave_rom_code[2] = serial >> 32;
onewire_slave_rom_code[3] = serial >> 24;
onewire_slave_rom_code[4] = serial >> 16;
onewire_slave_rom_code[5] = serial >> 8;
onewire_slave_rom_code[6] = serial >> 0;
onewire_slave_rom_code[7] = onewire_slave_crc(onewire_slave_rom_code, 7); // calculate CRC
// setup timer to generate/measure signal timing
rcc_periph_clock_enable(RCC_TIM(ONEWIRE_SLAVE_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(ONEWIRE_SLAVE_TIMER)); // reset timer state
timer_set_mode(TIM(ONEWIRE_SLAVE_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(TIM(ONEWIRE_SLAVE_TIMER), 1 - 1); // don't use prescale since this 16 bits timer allows to wait > 480 us used for the reset pulse ( 1/(72E6/1/(2**16))=910us )
// use comparator to time signal (without using the output), starting at slot start
timer_set_period(TIM(ONEWIRE_SLAVE_TIMER), 480 * (rcc_ahb_frequency / 1000000) - 1 - 1300); // minimum time needed for a reset pulse (480 < Trst), plus hand tuning
timer_set_oc_mode(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC1, TIM_OCM_FROZEN);
timer_set_oc_value(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC1, 16 * (rcc_ahb_frequency / 1000000) - 1); // time to wait before sending the presence pulse, after the rising edge of the reset pulse (15 < Tpdh < 60)
timer_set_oc_mode(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC2, TIM_OCM_FROZEN);
timer_set_oc_value(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC2, 45 * (rcc_ahb_frequency / 1000000) - 1 - 350); // time to sample the bit after being set (1 < Tlow1 < 15, 60 < Tslot < 120), or stop sending the bit use compare function to detect slave presence (15 = Trdv + 0 < Trelease < 45), plus hand tuning
timer_set_oc_value(TIM(ONEWIRE_SLAVE_TIMER), TIM_OC3, 90 * (rcc_ahb_frequency / 1000000) - 1); // time to stop the presence pulse (60 < Tpdl < 120)
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF); // clear all interrupt flags
timer_update_on_overflow(TIM(ONEWIRE_SLAVE_TIMER)); // only use counter overflow as UEV source (use overflow as start time or timeout)
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_UIE); // enable update interrupt for overflow
nvic_enable_irq(NVIC_TIM_IRQ(ONEWIRE_SLAVE_TIMER)); // catch interrupt in service routine
onewire_slave_function_code_received = false; // reset state
onewire_slave_state = ONEWIRE_STATE_IDLE; // reset state
onewire_slave_transfer_complete = false; // reset state
// setup GPIO with external interrupt
rcc_periph_clock_enable(GPIO_RCC(ONEWIRE_SLAVE_PIN)); // enable clock for GPIO peripheral
gpio_set(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN)); // idle is high (using pull-up resistor)
gpio_set_mode(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(ONEWIRE_SLAVE_PIN)); // control output using open drain (this mode also allows to read the input signal)
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
exti_select_source(GPIO_EXTI(ONEWIRE_SLAVE_PIN), GPIO_PORT(ONEWIRE_SLAVE_PIN)); // mask external interrupt of this pin only for this port
exti_set_trigger(GPIO_EXTI(ONEWIRE_SLAVE_PIN), EXTI_TRIGGER_BOTH); // trigger on signal change
exti_enable_request(GPIO_EXTI(ONEWIRE_SLAVE_PIN)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(ONEWIRE_SLAVE_PIN)); // enable interrupt
}
bool onewire_slave_function_read(uint8_t* data, size_t size)
{
if (NULL == data || 0 == size) { // verify input
return false;
}
if (UINT32_MAX / 8 < size) { // too many bits to transfer
return false;
}
if (onewire_slave_state != ONEWIRE_STATE_FUNCTION_DATA) { // not in the right state to transfer data
return false;
}
onewire_slave_transfer_data = data; // save buffer to write to
onewire_slave_transfer_bits = size*8; // number of bits to read
onewire_slave_transfer_complete = false; // reset state
bits_bit = 0; // reset number of bits read
onewire_slave_state = ONEWIRE_STATE_FUNCTION_READ; // read data
return true;
}
bool onewire_slave_function_write(const uint8_t* data, size_t size)
{
if (NULL == data || 0 == size) { // verify input
return false;
}
if (onewire_slave_state != ONEWIRE_STATE_FUNCTION_DATA) { // not in the right state to transfer data
return false;
}
if (UINT32_MAX / 8 < size) { // too many bits to transfer
return false;
}
onewire_slave_transfer_data = (uint8_t*)data; // save buffer to read from
onewire_slave_transfer_bits = size*8; // number of bits to write
onewire_slave_transfer_complete = false; // reset state
bits_bit = 0; // reset number of bits written
bits_buffer = onewire_slave_transfer_data[0]; // prepare byte to write
onewire_slave_state = ONEWIRE_STATE_FUNCTION_WRITE; // write data
return true;
}
/** interrupt service routine called when 1-Wire signal changes */
void GPIO_EXTI_ISR(ONEWIRE_SLAVE_PIN)(void)
{
exti_reset_request(GPIO_EXTI(ONEWIRE_SLAVE_PIN)); // reset interrupt
if (gpio_get(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN))) { // it's a rising edge
switch (onewire_slave_state) {
case ONEWIRE_STATE_RESET: // reset pulse has ended
timer_disable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // stop timer for reconfiguration
timer_set_counter(TIM(ONEWIRE_SLAVE_TIMER), 0); // reset timer counter
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC1IF); // clear flag
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC1IE); // enable compare interrupt for presence pulse
timer_enable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // start timer to generate timing
onewire_slave_state = ONEWIRE_STATE_WAIT_PRESENCE; // set new stated
break;
case ONEWIRE_STATE_PULSE_PRESENCE: // we stopped sending the presence pulse
onewire_slave_state = ONEWIRE_STATE_ROM_COMMAND; // we now expect a ROM command
bits_bit = 0; // reset buffer bit count
break; // no need to stop the time, the reset will be checked correctly
default: // rising edge is not important is the other cases
break; // nothing to do
}
} else { // it's a falling edge, the beginning of a new signal
timer_disable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // stop timer for reconfiguration
timer_set_counter(TIM(ONEWIRE_SLAVE_TIMER), 0); // reset timer counter
timer_disable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE); // disable all timers
switch (onewire_slave_state) {
case ONEWIRE_STATE_PULSE_PRESENCE: // we started sending the presence pulse
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC3IE); // enable timer for end of pulse
break;
case ONEWIRE_STATE_ROM_COMMAND: // read ROM command bits
case ONEWIRE_STATE_ROM_MATCH: // read ROM code bits
case ONEWIRE_STATE_FUNCTION_COMMAND: // read function command bits
case ONEWIRE_STATE_ROM_SEARCH_SELECT: // read selected ROM code bit
case ONEWIRE_STATE_FUNCTION_READ: // read function data bit
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC2IE); // enable timer for reading bit
break;
case ONEWIRE_STATE_ROM_READ: // send ROM code bit
case ONEWIRE_STATE_ROM_SEARCH_TRUE: // send ROM code bit while searching ROM, not negated
case ONEWIRE_STATE_ROM_SEARCH_FALSE: // send ROM code bit while searching ROM, already negated
case ONEWIRE_STATE_FUNCTION_WRITE: // write function data bit
timer_enable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC2IE); // enable timer for reading bit
if (0 == (bits_buffer & (1 << (bits_bit % 8)))) { // need to send a 0 bit
gpio_clear(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN)); // hold low to send 0 bit
}
break;
case ONEWIRE_STATE_IDLE: // we only expect a reset
default: // we don't expect any falling edge in other states
onewire_slave_state = ONEWIRE_STATE_IDLE; // unexpected signal, reset to idle state
break; // the timer overflow will confirm detect reset pulses
}
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF); // clear all flags
timer_enable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // start timer to measure the configured timeouts
}
}
/** interrupt service routine called for timer */
void TIM_ISR(ONEWIRE_SLAVE_TIMER)(void)
{
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF)) { // reset timer triggered, verify if it's a reset
if (0 == gpio_get(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN))) { // signal it still low, thus it must be a reset
onewire_slave_state = ONEWIRE_STATE_RESET; // update state
}
timer_disable_counter(TIM(ONEWIRE_SLAVE_TIMER)); // stop timer since there is nothing more to measure
timer_disable_irq(TIM(ONEWIRE_SLAVE_TIMER), TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE); // disable all timers
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF); // clear all flag (I have no idea why the others are get too, even when the interrupt is not enabled)
}
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC1IF)) { // wait for presence pulse timer triggered
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC1IF); // clear flag
if (ONEWIRE_STATE_WAIT_PRESENCE == onewire_slave_state) { // we can now send the pulse
onewire_slave_state = ONEWIRE_STATE_PULSE_PRESENCE; // save new state
gpio_clear(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN)); // send presence pulse (will also trigger the timer start)
}
}
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC2IF)) { // time to read the bit, or stop writing it
// read/write bit depending on bit
switch (onewire_slave_state) {
case ONEWIRE_STATE_ROM_COMMAND: // read ROM command code bit
case ONEWIRE_STATE_ROM_MATCH: // read ROM code
case ONEWIRE_STATE_FUNCTION_COMMAND: // read function command code bit
case ONEWIRE_STATE_ROM_SEARCH_SELECT: // read selected ROM code bit
case ONEWIRE_STATE_FUNCTION_READ: // read function data bit
if (gpio_get(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN))) { // bit is set to 1
bits_buffer |= (1 << (bits_bit % 8)); // set bit
} else { // bit is set to 0
bits_buffer &= ~(1 << (bits_bit % 8)); // clear bit
}
bits_bit++; // go to next bit
break;
case ONEWIRE_STATE_ROM_READ: // write ROM code
case ONEWIRE_STATE_FUNCTION_WRITE: // write function data bit
gpio_set(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN)); // stop sending bit
bits_bit++; // go to next bit
break;
case ONEWIRE_STATE_ROM_SEARCH_TRUE: // ROM code bit is sent
case ONEWIRE_STATE_ROM_SEARCH_FALSE: // ROM code bit is sent
gpio_set(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN)); // stop sending bit
break;
default: // these states don't need read/write
break;
}
static uint8_t rom_code_byte; // which byte of the ROM code is processed
// act on bit count
switch (onewire_slave_state) {
case ONEWIRE_STATE_ROM_COMMAND: // read ROM command
if (bits_bit > 7) { // complete ROM command code received
bits_bit = 0; // reset buffer
rom_code_byte = 0; // reset ROM code byte index
switch (bits_buffer) { // act depending on ROM command code
case 0x33: // READ ROM
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // prepare to send the first byte
onewire_slave_state = ONEWIRE_STATE_ROM_READ; // write ROM code
break;
case 0xcc: // SKIP ROM
onewire_slave_state = ONEWIRE_STATE_FUNCTION_COMMAND; // now read function command code
break;
case 0x55: // MATCH ROM
onewire_slave_state = ONEWIRE_STATE_ROM_MATCH; // read ROM code
break;
case 0xf0: // SEARCH ROM
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // prepare to search code
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_TRUE; // prepare to start sending first new bit
break;
default: // unknown ROM code
onewire_slave_state = ONEWIRE_STATE_IDLE; // go back to default idle state
break;
}
}
break;
case ONEWIRE_STATE_ROM_READ: // send ROM code
if (bits_bit > 7) { // complete byte transmitted
rom_code_byte++; // go to next ROM code byte
if (rom_code_byte>LENGTH(onewire_slave_rom_code)) { // complete ROM code send
onewire_slave_state = ONEWIRE_STATE_IDLE; // go back to default idle state
} else {
bits_bit = 0; // reset buffer
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // send next ROM code byte
}
}
break;
case ONEWIRE_STATE_ROM_MATCH: // compare ROM code
if (bits_bit > 7) { // complete byte received
if (bits_buffer == onewire_slave_rom_code[rom_code_byte]) { // ROM code byte matches
bits_bit = 0; // reset buffer
rom_code_byte++; // go to next ROM code byte
if (rom_code_byte >= LENGTH(onewire_slave_rom_code)) { // complete ROM code matches
onewire_slave_state = ONEWIRE_STATE_FUNCTION_COMMAND; // now read function command code
}
} else { // ROM code does not match
onewire_slave_state = ONEWIRE_STATE_IDLE; // stop comparing and go back to idle
}
}
break;
case ONEWIRE_STATE_ROM_SEARCH_TRUE: // ROM code bit is send, prepare to send negated version
bits_buffer ^= (1 << bits_bit); // negate bit
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_FALSE; // send negated version
break;
case ONEWIRE_STATE_ROM_SEARCH_FALSE: // negated ROM code bit is send, prepare to read selected bit
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_SELECT; // read selected
break;
case ONEWIRE_STATE_ROM_SEARCH_SELECT: // check if we are selected
if ((bits_buffer&(1 << (bits_bit - 1))) == (onewire_slave_rom_code[rom_code_byte] & (1 << (bits_bit - 1)))) { // we have been selected
onewire_slave_state = ONEWIRE_STATE_ROM_SEARCH_TRUE; // prepare to compare next bit
} else { // we are no selected
onewire_slave_state = ONEWIRE_STATE_IDLE; // go back to idle
}
if (bits_bit > 7) { // complete byte searched
bits_bit = 0; // reset buffer
rom_code_byte++; // go to next ROM code byte
if (rom_code_byte >= LENGTH(onewire_slave_rom_code)) { // complete ROM code search
onewire_slave_state = ONEWIRE_STATE_FUNCTION_COMMAND; // now read function command code
} else {
bits_buffer = onewire_slave_rom_code[rom_code_byte]; // prepare next ROM code byte
}
}
break;
case ONEWIRE_STATE_FUNCTION_COMMAND: // read function command
if (bits_bit > 7) { // complete function command code received
onewire_slave_function_code = bits_buffer; // save function command code to user buffer
onewire_slave_state = ONEWIRE_STATE_FUNCTION_DATA; // let the user transfer data
onewire_slave_function_code_received = true; // notify user
}
break;
case ONEWIRE_STATE_FUNCTION_READ: // save function data bit
if (0 == bits_bit % 8) { // complete byte received
onewire_slave_transfer_data[(bits_bit - 1) / 8] = bits_buffer; // save received bytes
}
if (bits_bit >= onewire_slave_transfer_bits) { // read transfer complete
onewire_slave_transfer_data[(bits_bit - 1) / 8] = bits_buffer; // save last bits
onewire_slave_state = ONEWIRE_STATE_FUNCTION_DATA; // let the user transfer more data
onewire_slave_transfer_complete = true; // notify user
}
break;
case ONEWIRE_STATE_FUNCTION_WRITE: // update function data bit to write
if (0 == bits_bit % 8) { // complete byte transfer
bits_buffer = onewire_slave_transfer_data[bits_bit / 8]; // prepare next byte to write
}
if (bits_bit >= onewire_slave_transfer_bits) { // write transfer complete
onewire_slave_state = ONEWIRE_STATE_FUNCTION_DATA; // let the user transfer more data
onewire_slave_transfer_complete = true; // notify user
}
break;
default: // no action needed
break;
}
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC2IF); // clear flag
}
if (timer_interrupt_source(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC3IF)) { // end of presence pulse timer triggered
timer_clear_flag(TIM(ONEWIRE_SLAVE_TIMER), TIM_SR_CC3IF); // clear flag
if (ONEWIRE_STATE_PULSE_PRESENCE == onewire_slave_state) {
gpio_set(GPIO_PORT(ONEWIRE_SLAVE_PIN), GPIO_PIN(ONEWIRE_SLAVE_PIN)); // stop sending presence pulse
// if the pin stays low the reset timer will catch it
}
}
}

View File

@ -1,53 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library for 1-wire protocol as slave
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2017-2020
* @note peripherals used: timer @ref onewire_slave_timer, GPIO @ref onewire_slave_gpio
* @note overdrive mode is not supported
*/
#pragma once
/** set when a function command code has been received
* @note needs to be cleared by user
*/
extern volatile bool onewire_slave_function_code_received;
/** last function command code received */
extern volatile uint8_t onewire_slave_function_code;
/** set when data read/write transfer has been completed
* @note needs to be cleared by user
*/
extern volatile bool onewire_slave_transfer_complete;
/** setup 1-wire peripheral
* @param[in] family family code for slave ROM code (8 bits)
* @param[in] serial serial number for slave ROM code (48 bits)
*/
void onewire_slave_setup(uint8_t family, uint64_t serial);
/** read data from master
* @param[out] data buffer to save read bits
* @param[in] size number of bytes to read
* @return if transfer initialization succeeded
* @note onewire_slave_transfer_complete is set when transfer is completed
*/
bool onewire_slave_function_read(uint8_t* data, size_t size);
/** write data to master
* @param[in] data data to write
* @param[in] size number of bytes to write
* @return if transfer initialization succeeded
* @note onewire_slave_transfer_complete is set when transfer is completed
*/
bool onewire_slave_function_write(const uint8_t* data, size_t size);

View File

@ -1,175 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to send data using ESP8266 WiFi SoC (code)
* @file radio_esp8266.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016
* @note peripherals used: USART @ref radio_esp8266_usart
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <string.h> // string and memory utilities
#include <stdio.h> // string utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include "radio_esp8266.h" // radio header and definitions
#include "global.h" // common methods
/** @defgroup radio_esp8266_usart USART peripheral used for communication with radio
* @{
*/
#define RADIO_ESP8266_USART 2 /**< USART peripheral */
/** @} */
/* input and output buffers and used memory */
static uint8_t rx_buffer[24] = {0}; /**< buffer for received data (we only expect AT responses) */
static volatile uint16_t rx_used = 0; /**< number of byte in receive buffer */
static uint8_t tx_buffer[256] = {0}; /**< buffer for data to transmit */
static volatile uint16_t tx_used = 0; /**< number of bytes used in transmit buffer */
volatile bool radio_esp8266_activity = false;
volatile bool radio_esp8266_success = false;
/** transmit data to radio
* @param[in] data data to transmit
* @param[in] length length of data to transmit
*/
static void radio_esp8266_transmit(uint8_t* data, uint8_t length) {
while (tx_used || !usart_get_flag(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // wait until ongoing transmission completed
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable transmit interrupt
__WFI(); // sleep until something happened
}
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // ensure transmit interrupt is disable to prevent index corruption (the ISR should already have done it)
radio_esp8266_activity = false; // reset status because of new activity
for (tx_used=0; tx_used<length && tx_used<LENGTH(tx_buffer); tx_used++) { // copy data
tx_buffer[tx_used] = data[length-1-tx_used]; // put character in buffer (in reverse order)
}
if (tx_used) {
usart_enable_tx_interrupt(USART(RADIO_ESP8266_USART)); // enable interrupt to send bytes
}
}
void radio_esp8266_setup(void)
{
/* enable USART I/O peripheral */
rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (USART)
rcc_periph_clock_enable(USART_PORT_RCC(RADIO_ESP8266_USART)); // enable clock for USART port peripheral
rcc_periph_clock_enable(USART_RCC(RADIO_ESP8266_USART)); // enable clock for USART peripheral
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, USART_PIN_TX(RADIO_ESP8266_USART)); // setup GPIO pin USART transmit
gpio_set_mode(USART_PORT(RADIO_ESP8266_USART), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, USART_PIN_RX(RADIO_ESP8266_USART)); // setup GPIO pin USART receive
gpio_set(USART_PORT(RADIO_ESP8266_USART), USART_PIN_RX(RADIO_ESP8266_USART)); // pull up to avoid noise when not connected
/* setup USART parameters for ESP8266 AT firmware */
usart_set_baudrate(USART(RADIO_ESP8266_USART), 115200); // AT firmware 0.51 (SDK 1.5.0) uses 115200 bps
usart_set_databits(USART(RADIO_ESP8266_USART), 8);
usart_set_stopbits(USART(RADIO_ESP8266_USART), USART_STOPBITS_1);
usart_set_mode(USART(RADIO_ESP8266_USART), USART_MODE_TX_RX);
usart_set_parity(USART(RADIO_ESP8266_USART), USART_PARITY_NONE);
usart_set_flow_control(USART(RADIO_ESP8266_USART), USART_FLOWCONTROL_NONE);
nvic_enable_irq(USART_IRQ(RADIO_ESP8266_USART)); // enable the USART interrupt
usart_enable_rx_interrupt(USART(RADIO_ESP8266_USART)); // enable receive interrupt
usart_enable(USART(RADIO_ESP8266_USART)); // enable USART
/* reset buffer states */
rx_used = 0;
tx_used = 0;
radio_esp8266_activity = false;
radio_esp8266_success = false;
radio_esp8266_transmit((uint8_t*)"AT\r\n",4); // verify if module is present
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
radio_esp8266_transmit((uint8_t*)"AT+RST\r\n",8); // reset module
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
while(rx_used<13 || memcmp((char*)&rx_buffer[rx_used-13], "WIFI GOT IP\r\n", 13)!=0) { // wait to have IP
__WFI(); // sleep until something happened
}
radio_esp8266_transmit((uint8_t*)"ATE0\r\n",6); // disable echoing
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
}
void radio_esp8266_tcp_open(char* host, uint16_t port)
{
char command[256] = {0}; // string to create command
int length = snprintf(command, LENGTH(command), "AT+CIPSTART=\"TCP\",\"%s\",%u\r\n", host, port); // create AT command to establish a TCP connection
if (length>0) {
radio_esp8266_transmit((uint8_t*)command, length);
}
}
void radio_esp8266_send(uint8_t* data, uint8_t length)
{
char command[16+1] = {0}; // string to create command
int command_length = snprintf(command, LENGTH(command), "AT+CIPSEND=%u\r\n", length); // create AT command to send data
if (command_length>0) {
radio_esp8266_transmit((uint8_t*)command, command_length); // transmit AT command
while (!radio_esp8266_activity || !radio_esp8266_success) { // wait for response
__WFI(); // sleep until something happened
}
if (!radio_esp8266_success) { // send AT command did not succeed
return; // don't transmit data
}
radio_esp8266_transmit(data, length); // transmit data
}
}
void radio_esp8266_close(void)
{
radio_esp8266_transmit((uint8_t*)"AT+CIPCLOSE\r\n", 13); // send AT command to close established connection
}
/** USART interrupt service routine called when data has been transmitted or received */
void USART_ISR(RADIO_ESP8266_USART)(void)
{
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_TXE)) { // data has been transmitted
if (tx_used) { // there is still data in the buffer to transmit
usart_send(USART(RADIO_ESP8266_USART),tx_buffer[tx_used-1]); // put data in transmit register
tx_used--; // update used size
} else { // no data in the buffer to transmit
usart_disable_tx_interrupt(USART(RADIO_ESP8266_USART)); // disable transmit interrupt
}
}
if (usart_get_interrupt_source(USART(RADIO_ESP8266_USART), USART_SR_RXNE)) { // data has been received
while (rx_used>=LENGTH(rx_buffer)) { // if buffer is full
memmove(rx_buffer,&rx_buffer[1],LENGTH(rx_buffer)-1); // drop old data to make space (ring buffer are more efficient but harder to handle)
rx_used--; // update used buffer information
}
rx_buffer[rx_used++] = usart_recv(USART(RADIO_ESP8266_USART)); // put character in buffer
// if the used send a packet with these strings during the commands detection the AT command response will break (AT commands are hard to handle perfectly)
if (rx_used>=4 && memcmp((char*)&rx_buffer[rx_used-4], "OK\r\n", 4)==0) { // OK received
radio_esp8266_activity = true; // response received
radio_esp8266_success = true; // command succeeded
rx_used = 0; // reset buffer
} else if (rx_used>=7 && memcmp((char*)&rx_buffer[rx_used-7], "ERROR\r\n", 7)==0) { // ERROR received
radio_esp8266_activity = true; // response received
radio_esp8266_success = false; // command failed
rx_used = 0; // reset buffer
}
}
}

View File

@ -1,47 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to send data using ESP8266 WiFi SoC (API)
* @file radio_esp8266.h
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016
* @note peripherals used: USART @ref radio_esp8266_usart
*/
#pragma once
/** a response has been returned by the radio */
extern volatile bool radio_esp8266_activity;
/** the last command has succeeded */
extern volatile bool radio_esp8266_success;
/** setup peripherals to communicate with radio
* @note this is blocking to ensure we are connected to the WiFi network
*/
void radio_esp8266_setup(void);
/** establish TCP connection
* @param[in] host host to connect to
* @param[in] port TCP port to connect to
* @note wait for activity to get success status
*/
void radio_esp8266_tcp_open(char* host, uint16_t port);
/** send data (requires established connection)
* @param[in] data data to send
* @param[in] length size of data to send
* @note wait for activity to get success status
*/
void radio_esp8266_send(uint8_t* data, uint8_t length);
/** close established connection
* @note wait for activity to get success status
*/
void radio_esp8266_close(void);

View File

@ -1,294 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to get time from a DCF77 module
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
* @note peripherals used: GPIO @ref rtc_dcf77_gpio, timer @ref rtc_dcf77_timer
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
#include <libopencm3/stm32/timer.h> // timer library
#include "rtc_dcf77.h" // RTC DCF77 library API
#include "global.h" // common methods
/** @defgroup rtc_dcf77_gpio output to enable DCF module and input to capture DCF signal
* @{
*/
#define RTC_DCF77_ENABLE_PIN PA2 /**< GPIO pinto enable the module */
#define RTC_DCF77_SIGNAL_PIN PA3 /**< GPIO pin to capture the DCF signal */
/** @} */
/** @defgroup rtc_dcf77_timer timer to sample DCF77 signal
* @{
*/
#define RTC_DCF77_TIMER 4 /**< timer peripheral */
/** @} */
volatile bool rtc_dcf77_time_flag = false;
struct rtc_dcf77_time_t rtc_dcf77_time = {
.valid = false,
.milliseconds = 0,
.seconds = 0,
.minutes = 0,
.hours = 0,
.day = 0,
.weekday = 0,
.month = 0,
.year = 0,
};
/** the received DCF77 frame bits */
static volatile uint64_t rtc_dcf77_frame = 0;
/** values of the DCF77 signal over 10 ms for 1 s (how many times it is high) */
static uint8_t rtc_dcf77_bins[100] = {0};
/** the bin shift for the bit phase */
static uint8_t rtc_dcf77_phase = 0;
/** the maximum phase value */
static int16_t rtc_dcf77_phase_max = INT16_MIN;
/** if the current phase has been verified */
static bool rtc_dcf77_phase_locked = false;
/** number of invalid decoding */
static uint8_t rtc_dcf77_invalid = 0;
/** maximum number of invalid decoding before switching off */
#define RTC_DCF77_INVALID_MAX 5
void rtc_dcf77_setup(void)
{
// setup enable output
rcc_periph_clock_enable(GPIO_RCC(RTC_DCF77_ENABLE_PIN)); // enable clock GPIO peripheral
gpio_set_mode(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(RTC_DCF77_ENABLE_PIN)); // set pin to output push-pull to be able to enable the module
rtc_dcf77_off(); // disable module at start
// setup signal input
rcc_periph_clock_enable(GPIO_RCC(RTC_DCF77_SIGNAL_PIN)); // enable clock for signal input peripheral
gpio_set_mode(GPIO_PORT(RTC_DCF77_SIGNAL_PIN), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(RTC_DCF77_SIGNAL_PIN)); // set signal pin to input
// setup timer to sample signal at 1kHz
rcc_periph_clock_enable(RCC_TIM(RTC_DCF77_TIMER)); // enable clock for timer peripheral
rcc_periph_reset_pulse(RST_TIM(RTC_DCF77_TIMER)); // reset timer state
timer_set_mode(TIM(RTC_DCF77_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock, edge alignment (simple count), and count up
timer_set_prescaler(TIM(RTC_DCF77_TIMER), 1); // set prescaler to divide frequency by two, to be able to have a period of 1 kHz (72MHz/2/2^16=549Hz<1kHz)
timer_set_period(TIM(RTC_DCF77_TIMER), rcc_ahb_frequency / 2 / 1000 - 1); // set period to 1kHz (plus hand tuning)
timer_clear_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF); // clear update event flag
timer_update_on_overflow(TIM(RTC_DCF77_TIMER)); // only use counter overflow as UEV source
timer_enable_irq(TIM(RTC_DCF77_TIMER), TIM_DIER_UIE); // enable update interrupt for timer
nvic_enable_irq(NVIC_TIM_IRQ(RTC_DCF77_TIMER)); // catch interrupt in service routine
}
void rtc_dcf77_on(void)
{
if (!gpio_get(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_PIN(RTC_DCF77_ENABLE_PIN))) { // receiver is already turned on
return; // do nothing
}
rtc_dcf77_frame = 0; // reset frame
rtc_dcf77_phase_locked = false; // reset phase lock
rtc_dcf77_phase_max = INT16_MIN; // restart searching for phase
rtc_dcf77_invalid = 0; // reset invalid count
gpio_clear(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_PIN(RTC_DCF77_ENABLE_PIN)); // enable module by pulling pin low
timer_set_counter(TIM(RTC_DCF77_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(RTC_DCF77_TIMER)); // start timer to sample signal
}
void rtc_dcf77_off(void)
{
gpio_set(GPIO_PORT(RTC_DCF77_ENABLE_PIN), GPIO_PIN(RTC_DCF77_ENABLE_PIN)); // disable module by pull pin high
timer_disable_counter(TIM(RTC_DCF77_TIMER)); // stop timer since we don't need to sample anymore
}
/** decode rtc_dcf77_frame DCF77 frame into rtc_dcf77_time DCF77 time
* @note check valid for validity
*/
static void rtc_dcf77_decode(void)
{
rtc_dcf77_time.valid = false; // reset validity
if (rtc_dcf77_frame == 0) { // no time received yet
return;
}
if (!(rtc_dcf77_frame & (1ULL << 20))) { // start of encoded time should always be 1
return;
}
// check minute parity
uint8_t parity = 0; // to check parity
for (uint8_t bit = 21; bit <= 28; bit++) {
if (rtc_dcf77_frame & (1ULL << bit)) {
parity++; // count the set bits
}
}
if (parity%2) { // parity should be even
return;
}
rtc_dcf77_time.minutes = 1 * ((rtc_dcf77_frame >> 21) & (0x1)) + 2 * ((rtc_dcf77_frame >> 22) & (0x1)) + 4 * ((rtc_dcf77_frame >> 23) & (0x1)) + 8 * ((rtc_dcf77_frame >> 24) & (0x1)) + 10 * ((rtc_dcf77_frame >> 25) & (0x1)) + 20 * ((rtc_dcf77_frame >> 26) & (0x1)) + 40 * ((rtc_dcf77_frame >> 27) & (0x1)); // read minute (00-59)
if (rtc_dcf77_time.minutes > 59) { // minutes should not be more than 59
return;
}
// check hour parity
parity = 0;
for (uint8_t bit = 29; bit <= 35; bit++) {
if (rtc_dcf77_frame & (1ULL << bit)) {
parity++; // count the set bits
}
}
if (parity % 2) { // parity should be even
return;
}
rtc_dcf77_time.hours = 1 * ((rtc_dcf77_frame >> 29) & (0x1)) + 2 * ((rtc_dcf77_frame >> 30) & (0x1)) + 4 * ((rtc_dcf77_frame >> 31) & (0x1)) + 8 * ((rtc_dcf77_frame >> 32) & (0x1)) + 10 * ((rtc_dcf77_frame >> 33) & (0x1)) + 20 * ((rtc_dcf77_frame >> 34) & (0x1)); // read hour (00-23)
if (rtc_dcf77_time.hours > 23) { // hours should not be more than 23
return;
}
// check date parity
parity = 0;
for (uint8_t bit = 36; bit <= 58; bit++) {
if (rtc_dcf77_frame & (1ULL << bit)) {
parity++; // count the set bits
}
}
if (parity % 2) { // parity should be even
return;
}
rtc_dcf77_time.day = 1 * ((rtc_dcf77_frame >> 36) & (0x1)) + 2 * ((rtc_dcf77_frame >> 37) & (0x1)) + 4 * ((rtc_dcf77_frame >> 38) & (0x1)) + 8 * ((rtc_dcf77_frame >> 39) & (0x1)) + 10 * ((rtc_dcf77_frame >> 40) & (0x1)) + 20 * ((rtc_dcf77_frame >> 41) & (0x1)); // read day of the month (01-31)
if (rtc_dcf77_time.day == 0 || rtc_dcf77_time.day > 31) { // day of the month should be 1-31
return;
}
rtc_dcf77_time.weekday = 1 * ((rtc_dcf77_frame >> 42) & (0x1)) + 2*((rtc_dcf77_frame >> 43) & (0x1)) + 4 * ((rtc_dcf77_frame >> 44) & (0x1)); // read day of the week (1=Monday - 7=Sunday)
if (rtc_dcf77_time.weekday == 0 || rtc_dcf77_time.weekday > 7) { // day of the week should be 1-7
return;
}
rtc_dcf77_time.month = 1 * ((rtc_dcf77_frame >> 45) & (0x1)) + 2 * ((rtc_dcf77_frame >> 46) & (0x1)) + 4 * ((rtc_dcf77_frame >> 47) & (0x1)) + 8*((rtc_dcf77_frame >> 48) & (0x1)) + 10 * ((rtc_dcf77_frame >> 49) & (0x1)); // read month of the year (01-12)
if (rtc_dcf77_time.month == 0 || rtc_dcf77_time.month > 12) { // month of the year should be 1-12
return;
}
rtc_dcf77_time.year = 1 * ((rtc_dcf77_frame >> 50) & (0x1)) + 2 * ((rtc_dcf77_frame >> 51) & (0x1)) + 4 * ((rtc_dcf77_frame >> 52) & (0x1)) + 8 * ((rtc_dcf77_frame >> 53) & (0x1)) + 10 * ((rtc_dcf77_frame >> 54) & (0x1)) + 20 * ((rtc_dcf77_frame >> 55) & (0x1)) + 40 * ((rtc_dcf77_frame >> 56) & (0x1)) + 80 * ((rtc_dcf77_frame >> 57) & (0x1)); // read year of the century (00-99)
if (rtc_dcf77_time.year > 99) { // year should be <100
return;
}
rtc_dcf77_time.valid = true; // if we managed it until here the decoding is successful
}
/** find phase of 1 seconds DCF77 signal in the bins
* searches the complete second for the highest correlation if the phase it not locked
* searches only around the last phase if locked
* saves the new phase with the highest correlation
*/
static void rtc_dcf77_phase_detector(void) {
uint8_t integral_i = 0; // which bin has the highest integral/correlation
int16_t integral_max = 0; // maximum integral value found
for (uint8_t start = 0; start < (rtc_dcf77_phase_locked ? 10 : LENGTH(rtc_dcf77_bins)); start++) { // which bin has been used to start the convolution (only use +/- 15 bits of previous phase if locked)
int16_t integral = 0; // value of the integral
for (uint8_t bin = 0; bin < LENGTH(rtc_dcf77_bins); bin++) { // go through bins to calculate correlation
int8_t dfc77_signal = -1; // the signal of the reference DCF77 signal
if (bin < 10) { // the signal is always high for the first 100 ms
dfc77_signal = 1; // use highest values
} else if (bin < 20) { // the signal has 50% chance of being high for the next 100 ms (encoding the bit)
dfc77_signal = 0; // use middle value
}
// the rest of the time the signal is low (keep lowest value)
integral += rtc_dcf77_bins[(start + bin + rtc_dcf77_phase + LENGTH(rtc_dcf77_bins) - 5) % LENGTH(rtc_dcf77_bins)] * dfc77_signal; // calculate the correlation at this point and integrate it (start with previous phase - 15 bins)
}
if (integral>integral_max) { // we found a better correlation result
integral_max = integral; // save new best result
integral_i = (start + rtc_dcf77_phase + LENGTH(rtc_dcf77_bins) - 5) % LENGTH(rtc_dcf77_bins); // start new best phase start
}
}
if ((int16_t)(integral_max + 40) > rtc_dcf77_phase_max) { // only save new phase if it is better than the last one, with some margin to compensate for the drift (perfect correlation = 100, worst correlation = -800)
rtc_dcf77_phase_max = integral_max; // save best phase value
rtc_dcf77_phase = integral_i; // save bin index corresponding to start of the phase
}
}
/** interrupt service routine called for timer */
void TIM_ISR(RTC_DCF77_TIMER)(void)
{
static uint8_t bin_state = 0; // how many samples have been stored in the bin
static uint8_t bin_i = 0; // current bin filled
static uint8_t bit_i = 0; // current bit in the DCF77 minute frame
if (timer_get_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(RTC_DCF77_TIMER), TIM_SR_UIF); // clear flag
// fill bin with current sample state
if (gpio_get(GPIO_PORT(RTC_DCF77_SIGNAL_PIN), GPIO_PIN(RTC_DCF77_SIGNAL_PIN))) {
rtc_dcf77_bins[bin_i]++; // only need to increase if the signal is high
}
bin_state++; // remember we filled the bin
if (bin_state>=10) { // bin has 10x1 ms samples, it is now full
bin_i = (bin_i + 1) % LENGTH(rtc_dcf77_bins); // go to next bin
rtc_dcf77_bins[bin_i] = 0; // restart bin
bin_state = 0; // restart collecting
}
if (0 == bin_i && 0 == bin_state) { // we have 1 s of samples
if (bit_i < 59) {
rtc_dcf77_phase_detector(); // detect phase in signal
// check modulation of first 100 ms
uint16_t modulation = 0;
for (uint8_t bin = 0; bin < 10; bin++) {
modulation += rtc_dcf77_bins[(rtc_dcf77_phase + bin) % LENGTH(rtc_dcf77_bins)];
}
if (modulation < 50) { // signal is not modulated, it might be the 60th pause bit
bit_i = 0; // restart frame
rtc_dcf77_frame = 0; // restart frame
rtc_dcf77_phase_max = INT16_MIN; // restart searching for phase
rtc_dcf77_phase_locked = false; // unlock phase since the decoding seems wrong
} else { // modulation detected
// check modulation of next 100 ms
modulation = 0;
for (uint8_t bin = 10; bin < 20; bin++) {
modulation += rtc_dcf77_bins[(rtc_dcf77_phase + bin) % LENGTH(rtc_dcf77_bins)];
}
if (modulation < 50) { // it's a 0
// bit is already cleared
} else { // it's a 1
rtc_dcf77_frame |= (1ULL << bit_i); // set bit
}
bit_i++; // go to next bit
}
} else { // complete DCF77 frame received
rtc_dcf77_decode(); // decode frame
if (rtc_dcf77_time.valid) { // decoded time is valid
rtc_dcf77_time.milliseconds = rtc_dcf77_phase * 10; // save milliseconds corresponding to phase
rtc_dcf77_phase_locked = true; // lock phase since decoding succeeded
rtc_dcf77_invalid = 0; // remember we had an valid decoding
rtc_dcf77_time_flag = true; // notify user we have time
} else {
rtc_dcf77_invalid++; // remember we had an invalid decoding
}
if (rtc_dcf77_invalid >= RTC_DCF77_INVALID_MAX) { // too many invalid decoding
rtc_dcf77_off(); // switch off receiver so it can re-tune
}
bit_i = 0; // restart frame
rtc_dcf77_frame = 0; // restart frame
}
}
} else { // no other interrupt should occur
while (true); // unhandled exception: wait for the watchdog to bite
}
}

View File

@ -1,49 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to get time from a DCF77 module
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2020
* @note peripherals used: GPIO @ref rtc_dcf77_gpio, timer @ref rtc_dcf77_timer
*/
#pragma once
/** set when time information has been received */
extern volatile bool rtc_dcf77_time_flag;
/** DCF77 time structure */
struct rtc_dcf77_time_t {
bool valid; /**< if the time is valid */
uint8_t milliseconds; /**< milliseconds (00-99) */
uint8_t seconds; /**< seconds (always 0) */
uint8_t minutes; /**< minutes (00-49) */
uint8_t hours; /**< hours (00-23) */
uint8_t day; /**< day of the month (01-31) */
uint8_t weekday; /**< day of the week (1-7=Monday-Sunday) */
uint8_t month; /**< month (01-12) */
uint8_t year; /**< year within century (00-99) */
};
/** decoded DCF77 time received */
extern struct rtc_dcf77_time_t rtc_dcf77_time;
/** setup DCF77 time receiver module */
void rtc_dcf77_setup(void);
/** switch on DCF77 time receiver module
* @note it switches back off after too many invalid decoding attempts
*/
void rtc_dcf77_on(void);
/** switch off DCF77 time receiver module */
void rtc_dcf77_off(void);

View File

@ -1,397 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with the Maxim DS1307 I2C RTC IC (code)
* @file rtc_ds1307.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2017
* @note peripherals used: I²C
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdio.h> // standard I/O facilities
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/i2c.h> // I2C library
#include "global.h" // global utilities
#include "rtc_ds1307.h" // RTC header and definitions
#include "i2c_master.h" // i2c utilities
#define RTC_DS1307_I2C_ADDR 0x68 /**< DS1307 I2C address (fixed to 0b1101000) */
void rtc_ds1307_setup(void)
{
// configure I2C peripheral
i2c_master_setup(false); // DS1307 only supports normal mode (up to 100 kHz)
}
bool rtc_ds1307_oscillator_disabled(void)
{
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x00}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing CH value
return false;
}
return data[0]&0x80; // return CH bit value to indicate if oscillator is disabled
}
uint16_t rtc_ds1307_read_square_wave(void)
{
uint16_t to_return = 0; // square wave frequency to return (in Hz)
uint8_t data[1] = {0}; // to read data over I2C
const uint16_t rtc_ds1307_rs[] = {1, 4096, 8192, 32768}; // RS1/RS0 values
const uint8_t address[] = {0x07}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing control register
return 0xffff; // error occurred
}
if (data[0]&0x10) { // verify if the square wave is enabled (SQWE)
to_return = rtc_ds1307_rs[data[0]&0x03]; // read RS1/RS0 and get value
} else {
to_return = 0; // square wave output is disabled
}
return to_return;
}
uint8_t rtc_ds1307_read_seconds(void)
{
uint8_t to_return = 0; // seconds to return
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x00}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
to_return = ((data[0]&0x70)>>4)*10+(data[0]&0x0f); // convert BCD coding into seconds
return to_return;
}
uint8_t rtc_ds1307_read_minutes(void)
{
uint8_t to_return = 0; // minutes to return
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x01}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
to_return = (data[0]>>4)*10+(data[0]&0x0f); // convert BCD coding into minutes
return to_return;
}
uint8_t rtc_ds1307_read_hours(void)
{
uint8_t to_return = 0; // hours to return
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x02}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
if (data[0]&0x40) { // 12 hour mode
if (data[0]&0x02) { // PM
to_return += 12; // add the 12 hours
}
to_return += ((data[0]&0x10)>>4)*10; // convert BCD coding into hours (first digit)
} else {
to_return = ((data[0]&0x30)>>4)*10; // convert BCD coding into hours (first digit)
}
to_return += (data[0]&0x0f); // convert BCD coding into hours (second digit)
return to_return;
}
uint8_t rtc_ds1307_read_day(void)
{
uint8_t to_return = 0; // day to return
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x03}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
to_return = (data[0]&0x07); // convert BCD coding into days
return to_return;
}
uint8_t rtc_ds1307_read_date(void)
{
uint8_t to_return = 0; // date to return
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x04}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
to_return = ((data[0]&0x30)>>4)*10+(data[0]&0x0f); // convert BCD coding into date
return to_return;
}
uint8_t rtc_ds1307_read_month(void)
{
uint8_t to_return = 0; // month to return
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x05}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
to_return = ((data[0]&0x10)>>4)*10+(data[0]&0x0f); // convert BCD coding into month
return to_return;
}
uint8_t rtc_ds1307_read_year(void)
{
uint8_t data[1] = {0}; // to read data over I2C
const uint8_t address[] = {0x06}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read a single byte containing value
return 0xff;
}
uint8_t to_return = ((data[0]&0xf0)>>4)*10+(data[0]&0x0f); // convert BCD coding into year
return to_return;
}
uint8_t* rtc_ds1307_read_time(void)
{
static uint8_t time[7] = {0}; // store time {seconds, minutes, hours, day, date, month, year}
uint8_t data[7] = {0}; // to read data over I2C
const uint8_t address[] = {0x00}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read all time bytes
return NULL; // error occurred
}
time[0] = ((data[0]&0x70)>>4)*10+(data[0]&0x0f); // convert seconds from BCD
time[1] = (data[1]>>4)*10+(data[1]&0x0f); // convert minutes from BCD
time[2] = 0; // re-initialize hours
if (data[2]&0x40) { // 12 hour mode
if (data[2]&0x02) { // PM
time[2] += 12; // add the 12 hours
}
time[2] += ((data[2]&0x10)>>4)*10; // convert BCD coding into hours (first digit)
} else {
time[2] = ((data[2]&0x30)>>4)*10; // convert BCD coding into hours (first digit)
}
time[2] += (data[2]&0x0f); // convert BCD coding into hours (second digit)
time[3] = (data[3]&0x07); // convert BCD coding into days
time[4] = ((data[4]&0x30)>>4)*10+(data[4]&0x0f); // convert BCD coding into date
time[5] = ((data[5]&0x10)>>4)*10+(data[5]&0x0f); // convert BCD coding into month
time[6] = ((data[6]&0xf0)>>4)*10+(data[6]&0x0f); // convert BCD coding into year
return time;
}
bool rtc_ds1307_read_ram(uint8_t* data, uint8_t start, uint8_t length)
{
// sanity checks
if (data==NULL || length==0) { // nothing to read
return false;
}
if (start>55 || start+length>56) { // out of bounds RAM
return false;
}
const uint8_t address[] = {0x08+start}; // memory address for data
return i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // read RAM (starting at 0x08)
}
bool rtc_ds1307_oscillator_disable(void)
{
uint8_t data[1] = {0}; // to write CH value data over I2C
const uint8_t address[] = {0x00}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read seconds with CH value
return false;
}
data[0] |= 0x80; // set CH to disable oscillator
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with CH value
}
bool rtc_ds1307_oscillator_enable(void)
{
uint8_t data[1] = {0}; // to write CH value data over I2C
const uint8_t address[] = {0x00}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read seconds with CH value
return false;
}
data[0] &= 0x7f; // clear CH to enable oscillator
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with CH value
}
bool rtc_ds1307_write_square_wave(uint16_t frequency)
{
uint8_t data[1] = {0}; // to write control register value data over I2C
switch (frequency) { // set RS1/RS0 based on frequency
case 0:
data[0] = 0;
break;
case 1:
data[0] = 0|(1<<4);
break;
case 4096:
data[0] = 1|(1<<4);
break;
case 8192:
data[0] = 2|(1<<4);
break;
case 32768:
data[0] = 3|(1<<4);
break;
default: // unspecified frequency
return false;
}
const uint8_t address[] = {0x07}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with CH value
}
bool rtc_ds1307_write_seconds(uint8_t seconds)
{
if (seconds>59) {
return false;
}
uint8_t data[1] = {0}; // to read CH value data and write seconds value over I2C
const uint8_t address[] = {0x00}; // memory address for data
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data))) { // read seconds with CH value
return false;
}
data[0] &= 0x80; // only keep CH flag
data[0] |= (((seconds/10)%6)<<4)+(seconds%10); // encode seconds in BCD format
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write current seconds with previous CH value
}
bool rtc_ds1307_write_minutes(uint8_t minutes)
{
if (minutes>59) {
return false;
}
uint8_t data[1] = {0}; // to write time value
data[0] = (((minutes/10)%6)<<4)+(minutes%10); // encode minutes in BCD format
const uint8_t address[] = {0x01}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_hours(uint8_t hours)
{
if (hours>24) {
return false;
}
uint8_t data[1] = {0}; // to write time value
data[0] = (((hours/10)%3)<<4)+(hours%10); // encode hours in BCD 24h format
const uint8_t address[] = {0x02}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_day(uint8_t day)
{
if (day<1 || day>7) {
return false;
}
uint8_t data[1] = {0}; // to write time value
data[0] = (day%8); // encode day in BCD format
const uint8_t address[] = {0x03}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_date(uint8_t date)
{
if (date<1 || date>31) {
return false;
}
uint8_t data[1] = {0}; // to write time value
data[0] = (((date/10)%4)<<4)+(date%10); // encode date in BCD format
const uint8_t address[] = {0x04}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_month(uint8_t month)
{
if (month<1 || month>12) {
return false;
}
uint8_t data[1] = {0}; // to write time value
data[0] = (((month/10)%2)<<4)+(month%10); // encode month in BCD format
const uint8_t address[] = {0x05}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_year(uint8_t year)
{
if (year>99) {
return false;
}
uint8_t data[1] = {0}; // to write time value
data[0] = (((year/10)%10)<<4)+(year%10); // encode year in BCD format
const uint8_t address[] = {0x06}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_time(uint8_t seconds, uint8_t minutes, uint8_t hours, uint8_t day, uint8_t date, uint8_t month, uint8_t year)
{
uint8_t data[7] = {0}; // to write all time values
const uint8_t address[] = {0x00}; // memory address for data
// seconds
if (seconds>59) {
return false;
}
if (!i2c_master_read(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, 1)) { // read seconds with CH value
return false;
}
data[0] &= 0x80; // only keep CH flag
data[0] |= (((seconds/10)%6)<<4)+(seconds%10); // encode seconds in BCD format
// minutes
if (minutes>59) {
return false;
}
data[1] = (((minutes/10)%6)<<4)+(minutes%10); // encode minutes in BCD format
// hours
if (hours>24) {
return false;
}
data[2] = (((hours/10)%3)<<4)+(hours%10); // encode hours in BCD 24h format
// day
if (day<1 || day>7) {
return false;
}
data[3] = (day%8); // encode day in BCD format
// date
if (date<1 || date>31) {
return false;
}
data[4] = (((date/10)%4)<<4)+(date%10); // encode date in BCD format
// month
if (month<1 || month>12) {
return false;
}
data[5] = (((month/10)%2)<<4)+(month%10); // encode month in BCD format
// year
if (year>99) {
return false;
}
data[6] = (((year/10)%10)<<4)+(year%10); // encode year in BCD format
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write time value on RTC
}
bool rtc_ds1307_write_ram(uint8_t* data, uint8_t start, uint8_t length)
{
// sanity checks
if (data==NULL || length==0) { // nothing to read
return false;
}
if (start>55 || start+length>56) { // out of bounds RAM
return false;
}
const uint8_t address[] = {0x08+start}; // memory address for data
return i2c_master_write(RTC_DS1307_I2C_ADDR, address, LENGTH(address), data, LENGTH(data)); // write RAM (starting at 0x08)
}

View File

@ -1,140 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communicate with the Maxim DS1307 I2C RTC IC (API)
* @file rtc_ds1307.h
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016-2017
* @note peripherals used: I²C
*/
#pragma once
/** setup communication with RTC IC
* configure the I2C port defined in the sources
*/
void rtc_ds1307_setup(void);
/** verify if oscillator is disabled
* @return if oscillator is disabled (or if communication error occurred)
*/
bool rtc_ds1307_oscillator_disabled(void);
/** read square wave output frequency (in Hz)
* @return square wave output frequency in Hz, 0 if disabled (0xffff if communication error occurred)
*/
uint16_t rtc_ds1307_read_square_wave(void);
/** read seconds from RTC IC
* @return number of seconds (0-59) of the current time (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_seconds(void);
/** read minutes from RTC IC
* @return number of minutes (0-59) of the current time (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_minutes(void);
/** read hours from RTC IC
* @return number of hours (0-23) of the current time (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_hours(void);
/** read day from RTC IC
* @return day of the week (1-7, 1 is Sunday) of the current time, 1 being Sunday (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_day(void);
/** read date from RTC IC
* @return day of the month (1-31) of the current time (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_date(void);
/** read month from RTC IC
* @return month of the year (1-12) of the current time (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_month(void);
/** read year from RTC IC
* @return year of the century (00-99) of the current time (0xff if communication error occurred)
*/
uint8_t rtc_ds1307_read_year(void);
/** read time from RTC IC
* @return array of {seconds, minutes, hours, day, date, month, year} as defined above (NULL if communication error occurred)
*/
uint8_t* rtc_ds1307_read_time(void);
/** read user RAM from RTC IC
* @param[out] data array to store the RAM read
* @param[in] start start of the user RAM to read (0-55)
* @param[in] length number of user RAM bytes to read (0-55)
* @return if read succeeded
*/
bool rtc_ds1307_read_ram(uint8_t* data, uint8_t start, uint8_t length);
/** disable RTC IC oscillator
* @return if disabling oscillator succeeded
*/
bool rtc_ds1307_oscillator_disable(void);
/** enable RTC IC oscillator
* @return if enabling oscillator succeeded
*/
bool rtc_ds1307_oscillator_enable(void);
/** write square wave output frequency (in Hz)
* @param[in] frequency square wave output frequency in Hz (0 to disable, 1, 4096, 8192, 32768)
* @return if write succeeded
*/
bool rtc_ds1307_write_square_wave(uint16_t frequency);
/** write seconds into RTC IC
* @param[in] seconds number of seconds (0-59)
* @return if write succeeded
*/
bool rtc_ds1307_write_seconds(uint8_t seconds);
/** write minutes into RTC IC
* @param[in] minutes number of minutes (0-59)
* @return if write succeeded
*/
bool rtc_ds1307_write_minutes(uint8_t minutes);
/** write hours into RTC IC
* @param[in] hours number of hours (0-23)
* @return if write succeeded
*/
bool rtc_ds1307_write_hours(uint8_t hours);
/** write day into RTC IC
* @param[in] day day of the week (1-7, 1 is Sunday)
* @return if write succeeded
*/
bool rtc_ds1307_write_day(uint8_t day);
/** write date into RTC IC
* @param[in] date day of the month (1-31)
* @return if write succeeded
*/
bool rtc_ds1307_write_date(uint8_t date);
/** write month into RTC IC
* @param[in] month month of the year (1-12)
* @return if write succeeded
*/
bool rtc_ds1307_write_month(uint8_t month);
/** write year into RTC IC
* @param[in] year year of the century (00-99)
* @return if write succeeded
*/
bool rtc_ds1307_write_year(uint8_t year);
/** write time into RTC IC
* @param[in] seconds number of seconds (0-59)
* @param[in] minutes number of minutes (0-59)
* @param[in] hours number of hours (0-23)
* @param[in] day day of the week (1-7, 1 is Sunday)
* @param[in] date day of the month (1-31)
* @param[in] month month of the year (1-12)
* @param[in] year year of the century (00-99)
* @return if write succeeded
*/
bool rtc_ds1307_write_time(uint8_t seconds, uint8_t minutes, uint8_t hours, uint8_t day, uint8_t date, uint8_t month, uint8_t year);
/** write user RAM from RTC IC
* @param[in] data array of byte to write in RAM
* @param[in] start start of the user RAM to write (0-55)
* @param[in] length number of user RAM bytes to write (0-55)
* @return if write succeeded
*/
bool rtc_ds1307_write_ram(uint8_t* data, uint8_t start, uint8_t length);

View File

@ -1,572 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communication with ams AS3935 Franklin lightning sensor IC using SPI
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2019
* @note peripherals used: SPI @ref sensor_as3935_spi, GPIO @ref sensor_as3935_gpio
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <stdbool.h> // boolean utilities
#include <math.h> // math utilities
#include <strings.h> // fff utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
#include <libopencm3/stm32/exti.h> // external interrupt defines
#include <libopencm3/cm3/nvic.h> // interrupt handler
/* own libraries */
#include "global.h" // common methods
#include "sensor_as3935.h" // own definitions
/** @defgroup sensor_as3935_spi SPI peripheral used to communicate with the AS3935
* @{
*/
#define SENSOR_AS3935_SPI 1 /**< SPI peripheral */
/** @} */
/** @defgroup sensor_as3935_gpio GPIO used to control the AS3935
* @{
*/
#define SENSOR_AS3935_GPIO_SI PA3 /**< AS3935 Select Interface pin (high = SPI, low = I2C) */
#define SENSOR_AS3935_GPIO_IRQ PA2 /**< AS3935 Interrupt pin (active high) */
#define SENSOR_AS3935_GPIO_EN_VREG PA0 /**< AS3935 Voltage Regulator Enable pin (active high) */
/** @} */
/** register details */
struct sensor_as3935_register_info_t {
uint8_t address; /**< register address */
uint8_t mask; /**< mask of relevant register bits */
bool writable; /**< if register is writable */
};
/** AS3935 registers description */
static const struct sensor_as3935_register_info_t sensor_as3935_register_infos[] = {
[SENSOR_AS3935_REGISTER_AFE_GB] = {
.address = 0x00,
.mask = 0x3e,
.writable = true,
},
[SENSOR_AS3935_REGISTER_PWD] = {
.address = 0x00,
.mask = 0x01,
.writable = true,
},
[SENSOR_AS3935_REGISTER_NF_LEV] = {
.address = 0x01,
.mask = 0x70,
.writable = true,
},
[SENSOR_AS3935_REGISTER_WDTH] = {
.address = 0x01,
.mask = 0x0f,
.writable = true,
},
[SENSOR_AS3935_REGISTER_CL_STAT] = {
.address = 0x02,
.mask = 0x40,
.writable = true,
},
[SENSOR_AS3935_REGISTER_MIN_NUM_LIGH] = {
.address = 0x02,
.mask = 0x30,
.writable = true,
},
[SENSOR_AS3935_REGISTER_SREJ] = {
.address = 0x02,
.mask = 0x0f,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LCO_FDIV] = {
.address = 0x03,
.mask = 0xc0,
.writable = true,
},
[SENSOR_AS3935_REGISTER_MASK_DIST] = {
.address = 0x03,
.mask = 0x20,
.writable = true,
},
[SENSOR_AS3935_REGISTER_INT] = {
.address = 0x03,
.mask = 0x0f,
.writable = false,
},
[SENSOR_AS3935_REGISTER_S_LIG_L] = {
.address = 0x04,
.mask = 0xff,
.writable = false,
},
[SENSOR_AS3935_REGISTER_S_LIG_M] = {
.address = 0x05,
.mask = 0xff,
.writable = false,
},
[SENSOR_AS3935_REGISTER_S_LIG_MM] = {
.address = 0x06,
.mask = 0x1f,
.writable = false,
},
[SENSOR_AS3935_REGISTER_DISTANCE] = {
.address = 0x07,
.mask = 0x3f,
.writable = false,
},
[SENSOR_AS3935_REGISTER_DISP_LCO] = {
.address = 0x08,
.mask = 0x80,
.writable = true,
},
[SENSOR_AS3935_REGISTER_DISP_SRCO] = {
.address = 0x08,
.mask = 0x40,
.writable = true,
},
[SENSOR_AS3935_REGISTER_DISP_TRCO] = {
.address = 0x08,
.mask = 0x20,
.writable = true,
},
[SENSOR_AS3935_REGISTER_TUN_CAP] = {
.address = 0x08,
.mask = 0x0f,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT1] = {
.address = 0x09,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT2] = {
.address = 0x0a,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT3] = {
.address = 0x0b,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT4] = {
.address = 0x0c,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT5] = {
.address = 0x0d,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT6] = {
.address = 0x0e,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT7] = {
.address = 0x0f,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT8] = {
.address = 0x10,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT9] = {
.address = 0x11,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT10] = {
.address = 0x12,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT11] = {
.address = 0x13,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT12] = {
.address = 0x14,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT13] = {
.address = 0x15,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT14] = {
.address = 0x16,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT15] = {
.address = 0x17,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT16] = {
.address = 0x18,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT17] = {
.address = 0x19,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT18] = {
.address = 0x1a,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT19] = {
.address = 0x1b,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT20] = {
.address = 0x1c,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT21] = {
.address = 0x1d,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT22] = {
.address = 0x1e,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT23] = {
.address = 0x1f,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT24] = {
.address = 0x20,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT25] = {
.address = 0x21,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT26] = {
.address = 0x22,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT27] = {
.address = 0x23,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT28] = {
.address = 0x24,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT29] = {
.address = 0x25,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT30] = {
.address = 0x26,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT31] = {
.address = 0x27,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT32] = {
.address = 0x28,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT33] = {
.address = 0x29,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT34] = {
.address = 0x2a,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT35] = {
.address = 0x2b,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT36] = {
.address = 0x2c,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT37] = {
.address = 0x2d,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT38] = {
.address = 0x2e,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT39] = {
.address = 0x2f,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT40] = {
.address = 0x30,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT41] = {
.address = 0x31,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_LDLUT42] = {
.address = 0x32,
.mask = 0xff,
.writable = true,
},
[SENSOR_AS3935_REGISTER_TRCO_CALIB_DONE] = {
.address = 0x3a,
.mask = 0x80,
.writable = false,
},
[SENSOR_AS3935_REGISTER_TRCO_CALIB_NOK] = {
.address = 0x3a,
.mask = 0x40,
.writable = false,
},
[SENSOR_AS3935_REGISTER_SRCO_CALIB_DONE] = {
.address = 0x3b,
.mask = 0x80,
.writable = false,
},
[SENSOR_AS3935_REGISTER_SRCO_CALIB_NOK] = {
.address = 0x3b,
.mask = 0x40,
.writable = false,
},
};
/** flag set if an interrupt has been received */
volatile bool sensor_as3935_interrupt = false;
/** if we are currently calibrating the sensor */
static bool sensor_as3935_calibrating = false;
/** number of interrupts received */
static volatile uint16_t sensor_as3935_interrupt_count = 0;
bool sensor_as3935_setup(void)
{
// disable internal voltage regulator since we already provide 3.3V (plus VREG is tied to VCC using a 0R resistor)
rcc_periph_clock_enable(GPIO_RCC(SENSOR_AS3935_GPIO_EN_VREG)); // enable clock for GPIO port
gpio_clear(GPIO_PORT(SENSOR_AS3935_GPIO_EN_VREG), GPIO_PIN(SENSOR_AS3935_GPIO_EN_VREG)); // set low to disable voltage regulator
gpio_set_mode(GPIO_PORT(SENSOR_AS3935_GPIO_EN_VREG), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(SENSOR_AS3935_GPIO_EN_VREG)); // set GPIO as output
// select SPI interface
rcc_periph_clock_enable(GPIO_RCC(SENSOR_AS3935_GPIO_SI)); // enable clock for GPIO port
gpio_clear(GPIO_PORT(SENSOR_AS3935_GPIO_SI), GPIO_PIN(SENSOR_AS3935_GPIO_SI)); // pull low since it's active high
gpio_set_mode(GPIO_PORT(SENSOR_AS3935_GPIO_SI), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(SENSOR_AS3935_GPIO_SI)); // set GPIO as output
// configure interrupt
rcc_periph_clock_enable(GPIO_RCC(SENSOR_AS3935_GPIO_IRQ)); // enable clock for GPIO port
gpio_set_mode(GPIO_PORT(SENSOR_AS3935_GPIO_IRQ), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(SENSOR_AS3935_GPIO_IRQ)); // set interrupt as input
// setup SPI
rcc_periph_clock_enable(RCC_SPI_SCK_PORT(SENSOR_AS3935_SPI)); // enable clock for GPIO peripheral for clock signal
gpio_set_mode(SPI_SCK_PORT(SENSOR_AS3935_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_SCK_PIN(SENSOR_AS3935_SPI)); // set SCK as output (clock speed will be negotiated later)
rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(SENSOR_AS3935_SPI)); // enable clock for GPIO peripheral for MOSI signal
gpio_set_mode(SPI_MOSI_PORT(SENSOR_AS3935_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, SPI_MOSI_PIN(SENSOR_AS3935_SPI)); // set MOSI as output
rcc_periph_clock_enable(RCC_SPI_MISO_PORT(SENSOR_AS3935_SPI)); // enable clock for GPIO peripheral for MISO signal
gpio_set_mode(SPI_MISO_PORT(SENSOR_AS3935_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, SPI_MISO_PIN(SENSOR_AS3935_SPI)); // set MISO as input
rcc_periph_clock_enable(RCC_SPI_NSS_PORT(SENSOR_AS3935_SPI)); // enable clock for GPIO peripheral for NSS (CS) signal
gpio_set_mode(SPI_NSS_PORT(SENSOR_AS3935_SPI), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, SPI_NSS_PIN(SENSOR_AS3935_SPI)); // set NSS (CS) as output
rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function
rcc_periph_clock_enable(RCC_SPI(SENSOR_AS3935_SPI)); // enable clock for SPI peripheral
spi_reset(SPI(SENSOR_AS3935_SPI)); // clear SPI values to default
spi_init_master(SPI(SENSOR_AS3935_SPI), SPI_CR1_BAUDRATE_FPCLK_DIV_64, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_2, SPI_CR1_DFF_16BIT, SPI_CR1_MSBFIRST); // initialise SPI as master, divide clock by 64 (72E6/64=1125 kHz, max AS3935 SCK is 2 MHz, maximum SPI PCLK clock is 72 Mhz, depending on which SPI is used), set clock polarity to idle low (not that important), set clock phase to do bit change on falling edge (polarity depends on clock phase), use 16 bits frames , use MSb first
spi_set_full_duplex_mode(SPI(SENSOR_AS3935_SPI)); // ensure we are in full duplex mode
spi_enable_software_slave_management(SPI(SENSOR_AS3935_SPI)); // control NSS (CS) manually
spi_set_nss_high(SPI(SENSOR_AS3935_SPI)); // set NSS high (internally) so we can output
spi_disable_ss_output(SPI(SENSOR_AS3935_SPI)); // disable NSS output since we control CS manually
gpio_set(SPI_NSS_PORT(SENSOR_AS3935_SPI), SPI_NSS_PIN(SENSOR_AS3935_SPI)); // set CS high to unselect device
// sadly we can't use the interrupts as events to sleep (WFE) since sleep disables also communication (e.g. going to sleep until Rx buffer is not empty prevents transmission)
spi_enable(SPI(SENSOR_AS3935_SPI)); // enable SPI
sleep_ms(2); // be sure TCO has enough time to start
// configure device
sensor_as3935_command(SENSOR_AS3935_OPERATION_DIRECT_COMMAND, SENSOR_AS3935_DIRECT_COMMAND_PRESET_DEFAULT, SENSOR_AS3935_DIRECT_COMMAND_VALUE); // reset all values to default
if (0x24 != sensor_as3935_command(SENSOR_AS3935_OPERATION_READ, 0, 0)) { // check if communication works by checking default register value
return false;
}
sensor_as3935_power_down(); // power down to save power
rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt
// configure IRQ interrupt
exti_select_source(GPIO_EXTI(SENSOR_AS3935_GPIO_IRQ), GPIO_PIN(SENSOR_AS3935_GPIO_IRQ)); // mask external interrupt of the IRQ pin only for this port
exti_set_trigger(GPIO_EXTI(SENSOR_AS3935_GPIO_IRQ), EXTI_TRIGGER_RISING); // IRQ goes high in interrupt
exti_enable_request(GPIO_EXTI(SENSOR_AS3935_GPIO_IRQ)); // enable external interrupt
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(SENSOR_AS3935_GPIO_IRQ)); // enable interrupt
sensor_as3935_interrupt = (0 != gpio_get(GPIO_PORT(SENSOR_AS3935_GPIO_IRQ), GPIO_PIN(SENSOR_AS3935_GPIO_IRQ))); // update interrupt status
return true;
}
void sensor_as3935_release(void)
{
nvic_disable_irq(GPIO_NVIC_EXTI_IRQ(SENSOR_AS3935_GPIO_IRQ));
exti_disable_request(GPIO_EXTI(SENSOR_AS3935_GPIO_IRQ));
spi_reset(SPI(SENSOR_AS3935_SPI));
spi_disable(SPI(SENSOR_AS3935_SPI));
rcc_periph_clock_disable(RCC_SPI(SENSOR_AS3935_SPI));
gpio_set_mode(SPI_NSS_PORT(SENSOR_AS3935_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_NSS_PIN(SENSOR_AS3935_SPI));
gpio_set_mode(SPI_MISO_PORT(SENSOR_AS3935_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_MISO_PIN(SENSOR_AS3935_SPI));
gpio_set_mode(SPI_MOSI_PORT(SENSOR_AS3935_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_MOSI_PIN(SENSOR_AS3935_SPI));
gpio_set_mode(SPI_SCK_PORT(SENSOR_AS3935_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_SCK_PIN(SENSOR_AS3935_SPI));
gpio_set_mode(GPIO_PORT(SENSOR_AS3935_GPIO_IRQ), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(SENSOR_AS3935_GPIO_IRQ));
gpio_set_mode(GPIO_PORT(SENSOR_AS3935_GPIO_SI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(SENSOR_AS3935_GPIO_SI));
gpio_set_mode(GPIO_PORT(SENSOR_AS3935_GPIO_EN_VREG), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(SENSOR_AS3935_GPIO_EN_VREG));
}
uint8_t sensor_as3935_command(enum sensor_as3935_operation_t operation, uint8_t address_command, uint8_t data)
{
gpio_clear(SPI_NSS_PORT(SENSOR_AS3935_SPI), SPI_NSS_PIN(SENSOR_AS3935_SPI)); // set CS low to select device
spi_send(SPI(SENSOR_AS3935_SPI), ((operation & 0x3) << 14) | ((address_command & 0x3f) << 8) | data); // send command
(void)SPI_DR(SPI(SENSOR_AS3935_SPI)); // clear RXNE flag (by reading previously received data (not done by spi_read or spi_xref)
while (!(SPI_SR(SPI(SENSOR_AS3935_SPI)) & SPI_SR_TXE)); // wait until Tx buffer is empty before clearing the (previous) RXNE flag
while (!(SPI_SR(SPI(SENSOR_AS3935_SPI)) & SPI_SR_RXNE)); // wait for next data to be available
gpio_set(SPI_NSS_PORT(SENSOR_AS3935_SPI), SPI_NSS_PIN(SENSOR_AS3935_SPI)); // set CS high to unselect device
return SPI_DR(SPI(SENSOR_AS3935_SPI)); // return received data
}
uint8_t sensor_as3935_read_register(enum sensor_as3935_register_t name)
{
if (name >= LENGTH(sensor_as3935_register_infos)) { // check if register exists
return 0;
}
if (SENSOR_AS3935_REGISTER_INT == name) { // you need to wait 2 ms after an interrupt before reading the register
sleep_ms(2);
}
struct sensor_as3935_register_info_t register_info = sensor_as3935_register_infos[name]; // get register information
uint8_t value = sensor_as3935_command(SENSOR_AS3935_OPERATION_READ, register_info.address, 0); // get register value
value &= register_info.mask; // only keep relevant bits
value >>= (ffs(register_info.mask) - 1); // shit value bits
return value;
}
bool sensor_as3935_write_register(enum sensor_as3935_register_t name, uint8_t value)
{
if (name >= LENGTH(sensor_as3935_register_infos)) { // check if register exists
return false;
}
struct sensor_as3935_register_info_t register_info = sensor_as3935_register_infos[name]; // get register information
if (!register_info.writable) {
return false;
}
uint8_t data = sensor_as3935_command(SENSOR_AS3935_OPERATION_READ, register_info.address, 0); // get register data
value <<= (ffs(register_info.mask) - 1); // shit value bits
value &= register_info.mask; // only keep relevant bits
data &= ~register_info.mask; // clear relevant bit is final data
data |= value; // put value in data
sensor_as3935_command(SENSOR_AS3935_OPERATION_WRITE, register_info.address, data); // write register data
return true;
}
void sensor_as3935_power_down(void)
{
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_PWD, 1);
}
int8_t sensor_as3935_power_up(void)
{
nvic_disable_irq(GPIO_NVIC_EXTI_IRQ(SENSOR_AS3935_GPIO_IRQ)); // no need to count the next clock calibration
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_PWD, 0); // power up
sensor_as3935_command(SENSOR_AS3935_OPERATION_DIRECT_COMMAND, SENSOR_AS3935_DIRECT_COMMAND_CALIB_RCO, SENSOR_AS3935_DIRECT_COMMAND_VALUE); // start RCO calibration
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_DISP_SRCO, 1); // output SRCO to start calibration
sleep_ms(2); // as defined in the calibration procedure
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_DISP_SRCO, 0); // stop SRCO output
nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(SENSOR_AS3935_GPIO_IRQ)); // re-enable interrupt
sensor_as3935_interrupt = (0 != gpio_get(GPIO_PORT(SENSOR_AS3935_GPIO_IRQ), GPIO_PIN(SENSOR_AS3935_GPIO_IRQ))); // update interrupt status
// check if calibration succeeded
if (0 == sensor_as3935_read_register(SENSOR_AS3935_REGISTER_SRCO_CALIB_DONE)) {
return -1;
}
if (1 == sensor_as3935_read_register(SENSOR_AS3935_REGISTER_SRCO_CALIB_NOK)) {
return -2;
}
// the datasheet does not mention how to calibrate TRCO, but it is not equivalent to the SRCO mechanism and just happens if you try it often enough
if (0 == sensor_as3935_read_register(SENSOR_AS3935_REGISTER_TRCO_CALIB_DONE)) {
return -3;
}
if (1 == sensor_as3935_read_register(SENSOR_AS3935_REGISTER_TRCO_CALIB_NOK)) {
return -4;
}
return 0;
}
bool sensor_as3935_tune(void)
{
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_PWD, 0); // power up (without calibration)
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_LCO_FDIV, 0); // divide LCO by 16 (500 kHz LCO becomes 31250 Hz output)
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_DISP_LCO, 1); // output LCO on INT to measure frequency
uint8_t best_tune = 0; // remember best internal tuning capacitor setting
uint16_t best_offset = UINT16_MAX; // remember best LCO frequency offset
sensor_as3935_calibrating = true; // count the pulses now
for (uint8_t tune_cap = 0; tune_cap < 16; tune_cap++) { // test all internal tuning capacitor settings
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_TUN_CAP, tune_cap); // set internal capacitor
sleep_ms(1); // wait a bit until the output stabilize
sensor_as3935_interrupt_count = 0; // start counting
sleep_ms(100); // wait to measure frequency precisely enough (3.5% corresponds to 109 pulses)
uint16_t offset = abs(sensor_as3935_interrupt_count - 3125); // calculate offset to ideal frequency (500 kHz / 16 during 100 ms)
if (offset < best_offset) { // check if the tune frequency is closed to the ideal
best_offset = offset; // remember new best offset
best_tune = tune_cap; // remember we found a new better tuning setting
}
}
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_DISP_LCO, 0); // switch back INT output for interrupts
sensor_as3935_calibrating = false; // stop counting the pulses
if (best_offset > 109 + 10) { // no setting has an error < 3.5% for 500 kHz (500 kHz / 16 * 3.5% during 100 ms, plus some measurement error margin)
return false;
}
sensor_as3935_write_register(SENSOR_AS3935_REGISTER_TUN_CAP, best_tune); // set best capacitor tuning setting
return true;
}
/** interrupt service routine called when IRQ does high to indicate interrupt (or clock outputs) */
void GPIO_EXTI_ISR(SENSOR_AS3935_GPIO_IRQ)(void)
{
exti_reset_request(GPIO_EXTI(SENSOR_AS3935_GPIO_IRQ)); // reset interrupt
if (sensor_as3935_calibrating) {
sensor_as3935_interrupt_count++; // increment pulse count to calculate to frequency
} else {
sensor_as3935_interrupt = true; // notify user
}
}

View File

@ -1,162 +0,0 @@
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** library to communication with ams AS3935 Franklin lightning sensor IC using SPI
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2019
* @note peripherals used: SPI @ref sensor_as3935_spi, GPIO @ref sensor_as3935_gpio
*/
#pragma once
/** a interrupt has been received */
extern volatile bool sensor_as3935_interrupt;
/** SPI operation mode */
enum sensor_as3935_operation_t {
SENSOR_AS3935_OPERATION_WRITE = 0, /**< write to register */
SENSOR_AS3935_OPERATION_DIRECT_COMMAND = 0, /**< send direct command (equivalent to write to register) */
SENSOR_AS3935_OPERATION_READ = 1, /**< read from register */
};
/** register names */
enum sensor_as3935_register_t {
SENSOR_AS3935_REGISTER_AFE_GB, /**< AFE Gain Boost */
SENSOR_AS3935_REGISTER_PWD, /**< Power-down */
SENSOR_AS3935_REGISTER_NF_LEV, /**< Noise Floor Level */
SENSOR_AS3935_REGISTER_WDTH, /**< Watchdog threshold */
SENSOR_AS3935_REGISTER_CL_STAT, /**< Clear statistics */
SENSOR_AS3935_REGISTER_MIN_NUM_LIGH, /**< Minimum number of lightning */
SENSOR_AS3935_REGISTER_SREJ, /**< Spike rejection */
SENSOR_AS3935_REGISTER_LCO_FDIV, /**< Frequency division ration for antenna tuning */
SENSOR_AS3935_REGISTER_MASK_DIST, /**< Mask Disturber */
SENSOR_AS3935_REGISTER_INT, /**< Interrupt */
SENSOR_AS3935_REGISTER_S_LIG_L, /**< Energy of the Single Lightning LSBYTE */
SENSOR_AS3935_REGISTER_S_LIG_M, /**< Energy of the Single Lightning MSBYTE */
SENSOR_AS3935_REGISTER_S_LIG_MM, /**< Energy of the Single Lightning MMSBYTE */
SENSOR_AS3935_REGISTER_DISTANCE, /**< Distance estimation */
SENSOR_AS3935_REGISTER_DISP_LCO, /**< Display LCO on IRQ pin */
SENSOR_AS3935_REGISTER_DISP_SRCO, /**< Display SRCO on IRQ pin */
SENSOR_AS3935_REGISTER_DISP_TRCO, /**< Display TRCO on IRQ pin */
SENSOR_AS3935_REGISTER_TUN_CAP, /**< Internal Tuning Capacitors */
SENSOR_AS3935_REGISTER_LDLUT1, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT2, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT3, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT4, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT5, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT6, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT7, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT8, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT9, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT10, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT11, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT12, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT13, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT14, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT15, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT16, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT17, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT18, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT19, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT20, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT21, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT22, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT23, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT24, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT25, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT26, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT27, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT28, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT29, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT30, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT31, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT32, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT33, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT34, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT35, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT36, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT37, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT38, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT39, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT40, /**< Lightning Detection Look-up table */
SENSOR_AS3935_REGISTER_LDLUT41, /**< Lightning Detection Look-up table */