Daniel Thompson has loads of useful code out there :)

Reflects 07eff5c0b7333cfcc3d98ddc6ebf26e34c9c02ef of that repo.
This commit is contained in:
Harald Welte 2021-04-11 15:41:57 +02:00
parent 13ae607484
commit f9b2ca10b4
4 changed files with 546 additions and 0 deletions

38
librfm3/include/librfm3.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Part of librfm3 (a utility library built on librfn and libopencm3)
*
* Copyright (C) 2014 Daniel Thompson <daniel@redfelineninja.org.uk>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RF_LIBRFM3_H_
/*!
\mainpage
librfm3 is a collection of utility libraries that fit together nicely
with librfn and libopencm3. It is a collection of tools and toys for
ARM Cortex-M microcontrollers, especially the STM32 family.
librfm3 is free software licensed under the GNU Lesser General Public License,
v3 or later.
Source code can be downloaded from https://github.com/daniel-thompson/i2c-star
*/
#include <librfm3/i2c_ctx.h>
#endif // RF_LIBRFM3_H_

View File

@ -0,0 +1,151 @@
/*
* Part of librfm3 (a utility library built on librfn and libopencm3)
*
* Copyright (C) 2014 Daniel Thompson <daniel@redfelineninja.org.uk>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RF_I2C_CTX_H_
#define RF_I2C_CTX_H_
#include <stdbool.h>
#include <stdint.h>
#include <librfn/protothreads.h>
/*!
* \defgroup librfm3_i2c_ctx I2C context manager
*
* \brief Protothreaded I2C driver for libopencm3.
*
* @{
*/
typedef struct i2c_ctx {
pt_t pt; //!< Protothread state for high-level functions
pt_t leaf; //!< Protothread state for low-level functions
int err; //!< Error reporting (becomes non-zero on error)
bool verbose; //!< Automatically print error reports
uint32_t i2c; //!< \private
uint32_t timeout; //!< \private
uint8_t bytes_remaining; //!< \private
int i; //!< \private
} i2c_ctx_t;
typedef struct i2c_device_map {
uint16_t devices[8]; //!< Bitmap recording detected devices
} i2c_device_map_t;
/*!
* \brief Initialize the context structure ready for a single I2C transaction.
*
* The context must be reinitialized before each I2C transaction otherwise
* the transaction may timeout immediately.
*/
void i2c_ctx_init(i2c_ctx_t *c, uint32_t pi2c);
/*!
* \brief Reset and reinitialize the I2C bus.
*/
void i2c_ctx_reset(i2c_ctx_t *c);
/*!
* \brief Send a start condition.
*
* \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN().
*/
pt_state_t i2c_ctx_start(i2c_ctx_t *c);
/*!
* \brief Send the target bus address.
*
* \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN().
*
* If bytes_to_read is zero then launch a write transaction, otherwise
* launch a read and manage the ACK/NACK and STOP generation based on
* the number of bytes to transfer.
*/
pt_state_t i2c_ctx_sendaddr(i2c_ctx_t *c, uint16_t addr, uint8_t bytes_to_read);
/*!
* \brief Write a single byte.
*
* \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN().
*/
pt_state_t i2c_ctx_senddata(i2c_ctx_t *c, uint8_t data);
/*!
* \brief Read a single byte.
*
* \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN().
*/
pt_state_t i2c_ctx_getdata(i2c_ctx_t *c, uint8_t *data);
/*!
* \brief Send a stop condition.
*
* \note This is a low-level protothread; c->leaf must be zeroed by PT_SPAWN().
*
* Complete a low-level I2C write transaction. This is not required for
* reads because the drivers have to request the stop prior to reading
* the final bytes (so the stop is issued automatically).
*
*/
pt_state_t i2c_ctx_stop(i2c_ctx_t *c);
/*!
* \brief Detect attached I2C devices.
*
* \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN().
*/
pt_state_t i2c_ctx_detect(i2c_ctx_t *c, i2c_device_map_t *map);
/*!
* \brief Write to an I2C register.
*
* \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN().
*
* \todo API leaves space for 16-bit addresses and registers but this
* is not implemented.
*/
pt_state_t i2c_ctx_setreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg,
uint8_t val);
/*!
* \brief Read from an I2C register.
*
* \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN().
*
* \todo API leaves space for 16-bit addresses and registers but this
* is not implemented.
*/
pt_state_t i2c_ctx_getreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg,
uint8_t *val);
/*!
* \brief Burst write to an I2C device.
*
* Primarily used for writes to memory devices or to initialize devices
* whose registers can be written to continuously (without a stop/restart).
*
* \note This is a high-level protothread; c->pt must be zeroed by PT_SPAWN().
*/
pt_state_t i2c_ctx_write(i2c_ctx_t *c, uint16_t addr, uint8_t *data,
uint8_t len);
/*! @} */
#endif // RF_I2C_CTX_H_

338
librfm3/src/i2c_ctx.c Normal file
View File

@ -0,0 +1,338 @@
/*
* Part of librfm3 (a utility library built on librfn and libopencm3)
*
* Copyright (C) 2014 Daniel Thompson <daniel@redfelineninja.org.uk>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <librfm3/i2c_ctx.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/rcc.h>
#include <librfn/regdump.h>
#include <librfn/time.h>
#include <librfn/util.h>
#define D(x) { #x, x }
static const regdump_desc_t i2c_sr1_desc[] = { { "I2C_SR1", 0 },
D(I2C_SR1_SMBALERT),
D(I2C_SR1_TIMEOUT),
D(I2C_SR1_PECERR),
D(I2C_SR1_OVR),
D(I2C_SR1_AF),
D(I2C_SR1_ARLO),
D(I2C_SR1_BERR),
D(I2C_SR1_TxE),
D(I2C_SR1_RxNE),
D(I2C_SR1_STOPF),
D(I2C_SR1_ADD10),
D(I2C_SR1_BTF),
D(I2C_SR1_ADDR),
D(I2C_SR1_SB),
{ NULL, 0 } };
#undef D
static bool i2c_ctx_is_timed_out(i2c_ctx_t *c)
{
if (cyclecmp32(time_now(), c->timeout) > 0) {
if (c->verbose) {
printf("I2C TRANSACTION TIMED OUT\n");
regdump(I2C_SR1(c->i2c), i2c_sr1_desc);
}
return true;
}
return false;
}
void i2c_ctx_init(i2c_ctx_t *c, uint32_t pi2c)
{
memset(c, 0, sizeof(*c));
c->i2c = pi2c;
c->timeout = time_now() + 100000;
}
void i2c_ctx_reset(i2c_ctx_t *c)
{
/* TODO: Latest opencm3 has this code built in */
switch (c->i2c) {
case I2C1:
rcc_periph_reset_pulse(RST_I2C1);
rcc_periph_reset_pulse(RST_I2C1);
break;
case I2C2:
rcc_periph_reset_pulse(RST_I2C2);
rcc_periph_reset_pulse(RST_I2C2);
break;
#ifdef STM32F4
case I2C3:
rcc_periph_reset_pulse(RST_I2C3);
rcc_periph_reset_pulse(RST_I2C3);
break;
#endif
}
/* freq's numeric value ends up in MHz (i.e. in this case, 30) */
#ifdef STM32F4
uint16_t freq = I2C_CR2_FREQ_30MHZ;
#else
uint16_t freq = 36;
#endif
/* CCR is the number of APB bus cycles in *half* an I2C bus
* cycle. For Sm (100Khz) this ends up as:
* freq * 1MHz / 2 * 100KHz
* freq * 1000000 / 200000
* freq * 5
*
* Similar trise is the number of APB bus cycles in the rise
* time (plus 1). For Sm (1us) this ends up as:
* freq * 1Mhz / (1/1us) + 1
* freq * 1MHz / 1MHz + 1
* freq + 1
*/
/* peripheral configuration */
i2c_peripheral_disable(c->i2c);
i2c_set_clock_frequency(c->i2c, freq);
i2c_set_ccr(c->i2c, freq * 5);
i2c_set_trise(c->i2c, freq + 1);
i2c_set_own_7bit_slave_address(c->i2c, 0x32);
i2c_peripheral_enable(c->i2c);
}
pt_state_t i2c_ctx_start(i2c_ctx_t *c)
{
PT_BEGIN(&c->leaf);
i2c_send_start(c->i2c);
while (!i2c_ctx_is_timed_out(c) &&
!((I2C_SR1(c->i2c) & I2C_SR1_SB) &
(I2C_SR2(c->i2c) & (I2C_SR2_MSL | I2C_SR2_BUSY))))
PT_YIELD();
if (!I2C_SR1(c->i2c) & I2C_SR1_SB) {
i2c_ctx_reset(c);
c->err = EIO;
}
PT_END();
}
pt_state_t i2c_ctx_sendaddr(i2c_ctx_t *c, uint16_t addr,
uint8_t bytes_to_read)
{
PT_BEGIN(&c->leaf);
c->bytes_remaining = bytes_to_read;
i2c_send_7bit_address(c->i2c, addr, !!bytes_to_read);
while (!i2c_ctx_is_timed_out(c) && // bad
!(I2C_SR1(c->i2c) & I2C_SR1_AF) && // bad
!(I2C_SR1(c->i2c) & I2C_SR1_ADDR)) // good
PT_YIELD();
if (!(I2C_SR1(c->i2c) & I2C_SR1_ADDR)) {
i2c_ctx_reset(c);
c->err = EIO;
}
/* If we are only reading one byte we must get ready to NACK the
* final byte.
*/
if (c->bytes_remaining == 1)
I2C_CR1(c->i2c) &= ~I2C_CR1_ACK;
else if (c->bytes_remaining >= 2)
I2C_CR1(c->i2c) |= I2C_CR1_ACK;
/* Read sequence has side effect or clearing I2C_SR1_ADDR */
uint32_t reg32 __attribute__((unused));
reg32 = I2C_SR2(c->i2c);
if (c->bytes_remaining == 1)
i2c_send_stop(c->i2c);
PT_END();
}
pt_state_t i2c_ctx_senddata(i2c_ctx_t *c, uint8_t data)
{
PT_BEGIN(&c->leaf);
i2c_send_data(c->i2c, data);
while (!i2c_ctx_is_timed_out(c) && !(I2C_SR1(c->i2c) & I2C_SR1_BTF))
PT_YIELD();
if (!(I2C_SR1(c->i2c) & I2C_SR1_BTF)) {
i2c_ctx_reset(c);
c->err = EIO;
}
PT_END();
}
pt_state_t i2c_ctx_getdata(i2c_ctx_t *c, uint8_t *data)
{
PT_BEGIN(&c->leaf);
while (!i2c_ctx_is_timed_out(c) && !(I2C_SR1(c->i2c) & I2C_SR1_RxNE))
PT_YIELD();
if (!(I2C_SR1(c->i2c) & I2C_SR1_RxNE)) {
i2c_ctx_reset(c);
c->err = EIO;
PT_EXIT();
}
/* Need to NACK the final byte and STOP the transaction */
if (--c->bytes_remaining == 1) {
I2C_CR1(c->i2c) &= ~I2C_CR1_ACK;
i2c_send_stop(c->i2c);
}
*data = i2c_get_data(c->i2c);
PT_END();
}
pt_state_t i2c_ctx_stop(i2c_ctx_t *c)
{
PT_BEGIN(&c->leaf);
while (!i2c_ctx_is_timed_out(c) &&
!(I2C_SR1(c->i2c) & (I2C_SR1_BTF | I2C_SR1_TxE)))
PT_YIELD();
if (!(I2C_SR1(c->i2c) & (I2C_SR1_BTF | I2C_SR1_TxE))) {
i2c_ctx_reset(c);
c->err = EIO;
PT_EXIT();
}
i2c_send_stop(c->i2c);
/* TODO: is it safe to just drop out of this */
PT_END();
}
pt_state_t i2c_ctx_detect(i2c_ctx_t *c, i2c_device_map_t *map)
{
PT_BEGIN(&c->pt);
memset(map, 0, sizeof(*map));
for (c->i = 0; c->i < 0x80; c->i++) {
c->timeout = time_now() + 10000;
c->err = 0;
PT_SPAWN(&c->leaf, i2c_ctx_start(c));
if (c->err)
continue;
PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, c->i, I2C_WRITE));
if (c->err)
continue;
PT_SPAWN(&c->leaf, i2c_ctx_stop(c));
if (c->err)
continue;
map->devices[c->i / 16] |= 1 << (c->i % 16);
}
PT_END();
}
pt_state_t i2c_ctx_setreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg,
uint8_t val)
{
PT_BEGIN(&c->pt);
PT_SPAWN(&c->leaf, i2c_ctx_start(c));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, reg));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, val));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_stop(c));
PT_END();
}
pt_state_t i2c_ctx_getreg(i2c_ctx_t *c, uint16_t addr, uint16_t reg,
uint8_t *val)
{
PT_BEGIN(&c->pt);
PT_SPAWN(&c->leaf, i2c_ctx_start(c));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, reg));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_start(c));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_READ));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_getdata(c, val));
/* For reads STOP is generated automatically by sendaddr and/or
* getdata
*/
PT_END();
}
pt_state_t i2c_ctx_write(i2c_ctx_t *c, uint16_t addr, uint8_t *data,
uint8_t len)
{
PT_BEGIN(&c->pt);
PT_SPAWN(&c->leaf, i2c_ctx_start(c));
PT_EXIT_ON(c->err);
PT_SPAWN(&c->leaf, i2c_ctx_sendaddr(c, addr, I2C_WRITE));
PT_EXIT_ON(c->err);
for (c->i = 0; c->i < len; c->i++) {
PT_SPAWN(&c->leaf, i2c_ctx_senddata(c, data[c->i]));
PT_EXIT_ON(c->err);
}
PT_SPAWN(&c->leaf, i2c_ctx_stop(c));
PT_END();
}

19
mk/librfm3.rules.mk Normal file
View File

@ -0,0 +1,19 @@
#
# librfm3.rules.mk
#
# This file is part of the i2c-star project.
#
# 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.
#
LIBRFM3_DIR = ../../librfm3
OBJS += \
i2c_ctx.o
vpath %.c $(LIBRFM3_DIR)/src
CPPFLAGS += -I$(LIBRFM3_DIR)/include