import librfm3 from https://github.com/daniel-thompson/i2c-star.git
Daniel Thompson has loads of useful code out there :) Reflects 07eff5c0b7333cfcc3d98ddc6ebf26e34c9c02ef of that repo.
This commit is contained in:
parent
13ae607484
commit
f9b2ca10b4
|
@ -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_
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue