Also the Maple lib is C++, while my code is pure C.
The code is not published anywhere, till now.
I am attaching it here.
If you have any problems, I can create a new github repo for you.
/****************************************************************************************//**
* @file i2c.c
*
* @author Fotis Panagiotopoulos
* @license WTFPL
*
* I2C driver for the STM32F103. This is a software implementation.
*
*******************************************************************************************/
#include "i2c.h"
#include "hal.h"
#include "hal_os.h"
#include "hal_assert.h"
#include "hal_types.h"
#include "gpio.h"
#include "delay.h"
#include "hal_config.h"
#if CPU_FREQ != 72000000
#warning "The current I2C implementation is a software one. The selected CPU frequency may cause non accurate I2C frequency!"
#endif
//This sets the pin setting functions' overhead. Assumes a clock of 72MHz.
//Needs re-calibration if the CPU frequency is changed. Has no effect when
//max speed is set.
#define I2C_SOFT_OVERHEAD_US 5
typedef struct {
GPIO_Pin_t sda;
GPIO_Pin_t scl;
uint32_t delay;
I2C_Status_t status;
} I2C_Soft_t;
OS_mutex_t I2C0_mtx;
OS_mutex_t I2C1_mtx;
OS_mutex_t * I2C_mtx[] = { &I2C0_mtx, &I2C1_mtx };
static I2C_Soft_t I2C_Soft_1 = { .sda = PB7, .scl = PB6 };
static I2C_Soft_t I2C_Soft_2 = { .sda = PB11, .scl = PB10 };
static I2C_Soft_t * I2Cx[] = { &I2C_Soft_1, &I2C_Soft_2 };
static void SDA(I2C_Soft_t * channel, int state);
static void SCL(I2C_Soft_t * channel, int state);
void I2C_init(uint32_t channel, uint32_t frequency, uint32_t config)
{
HAL_ASSERT(channel == 0 || channel == 1);
if (channel == I2C_1)
{
if (config & I2C_1_REMAP)
{
I2Cx[0]->sda = PB9;
I2Cx[0]->scl = PB8;
}
else
{
I2Cx[0]->sda = PB7;
I2Cx[0]->scl = PB6;
}
}
GPIO_setState(I2Cx[channel]->sda, HIGH);
GPIO_setState(I2Cx[channel]->scl, HIGH);
GPIO_setMode(I2Cx[channel]->sda, OUTPUT_OPEN_DRAIN);
GPIO_setMode(I2Cx[channel]->scl, OUTPUT_OPEN_DRAIN);
int delay = ((1000000 / frequency) / 2) - I2C_SOFT_OVERHEAD_US;
if (delay < 0)
delay = 0;
I2Cx[channel]->delay = delay;
I2Cx[channel]->status = I2C_OK;
OS_mutex_init(I2C_mtx[channel]);
}
void I2C_deinit(uint32_t channel)
{
GPIO_setMode(I2Cx[channel]->sda, INPUT);
GPIO_setMode(I2Cx[channel]->scl, INPUT);
}
I2C_Status_t I2C_status(uint32_t channel)
{
return I2Cx[channel]->status;
}
int I2C_send(uint32_t channel, uint8_t address, const uint8_t * buffer, uint32_t size)
{
HAL_ASSERT(channel == 0 || channel == 1);
I2Cx[channel]->status = I2C_OK;
//Send the start bit
SCL(I2Cx[channel], HIGH);
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], LOW);
//Send the address
for (int i = 0; i < 8; i++)
{
SDA(I2Cx[channel], !!(((address << 1) | I2C_WRITE) & (1 << (7 - i))));
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
//Wait for ACK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
int j = 0;
while(GPIO_getState(I2Cx[channel]->sda) && j < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
j++;
}
if (j == I2C_SLAVE_TIMEOUT)
{
I2Cx[channel]->status = I2C_NO_RESPONSE;
return 0;
}
SCL(I2Cx[channel], LOW);
while(size--)
{
//Send the data
for (int i = 0; i < 8; i++)
{
SDA(I2Cx[channel], !!(*buffer & (1 << (7 - i))));
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
//Wait for ACK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
j = 0;
while(GPIO_getState(I2Cx[channel]->sda) && j < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
j++;
}
if (j == I2C_SLAVE_TIMEOUT)
{
I2Cx[channel]->status = I2C_NACK;
return 0;
}
SCL(I2Cx[channel], LOW);
buffer++;
}
//Send stop
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], HIGH);
SDA(I2Cx[channel], HIGH);
return 1;
}
int I2C_sendCmd(uint32_t channel, uint8_t address, uint8_t * buffer, uint32_t writeSize, uint32_t readSize)
{
HAL_ASSERT(channel == 0 || channel == 1);
I2Cx[channel]->status = I2C_OK;
//Send the start bit
SCL(I2Cx[channel], HIGH);
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], LOW);
//Send the address
int i;
for (i = 0; i < 8; i++)
{
SDA(I2Cx[channel], !!(((address << 1) | I2C_WRITE) & (1 << (7 - i))));
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
//Wait for ACK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
int j = 0;
while(GPIO_getState(I2Cx[channel]->sda) && j < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
j++;
}
if (j == I2C_SLAVE_TIMEOUT)
{
I2Cx[channel]->status = I2C_NO_RESPONSE;
return 0;
}
SCL(I2Cx[channel], LOW);
int k;
for (k = 0; k < writeSize; k++)
{
//Send the data
for (i = 0; i < 8; i++)
{
SDA(I2Cx[channel], !!(buffer[k] & (1 << (7 - i))));
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
//Wait for ACK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
j = 0;
while(GPIO_getState(I2Cx[channel]->sda) && j < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
j++;
}
if (j == I2C_SLAVE_TIMEOUT)
{
I2Cx[channel]->status = I2C_NACK;
return 0;
}
SCL(I2Cx[channel], LOW);
if (I2Cx[channel]->status != I2C_OK)
return 0;
}
return I2C_receive(channel, address, buffer, readSize);
}
int I2C_receive(uint32_t channel, uint8_t address, uint8_t * buffer, uint32_t size)
{
HAL_ASSERT(channel == 0 || channel == 1);
I2Cx[channel]->status = I2C_OK;
//Send the start bit
SCL(I2Cx[channel], HIGH);
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], LOW);
//Send the address
for (int i = 0; i < 8; i++)
{
SDA(I2Cx[channel], !!(((address << 1) | I2C_READ) & (1 << (7 - i))));
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
//Wait for ACK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
int j = 0;
while(GPIO_getState(I2Cx[channel]->sda) && j < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
j++;
}
if (j == I2C_SLAVE_TIMEOUT)
{
I2Cx[channel]->status = I2C_NACK;
return 0;
}
SCL(I2Cx[channel], LOW);
for (int i = 0; (i < size); i++)
{
SDA(I2Cx[channel], HIGH);
buffer[i] = 0;
for (j = 0; j < 8; j++)
{
SCL(I2Cx[channel], HIGH);
buffer[i] |= GPIO_getState(I2Cx[channel]->sda) << (7 - j);
SCL(I2Cx[channel], LOW);
}
if (i < (size -1))
{
//Send ACK
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
else
{
//On the last byte send NAK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
if (I2Cx[channel]->status != I2C_OK)
return 0;
}
//Send stop
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], HIGH);
SDA(I2Cx[channel], HIGH);
return 1;
}
int I2C_poll(uint32_t channel, uint8_t address)
{
HAL_ASSERT(channel == 0 || channel == 1);
//Start
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], LOW);
//Send the address
int byte = (address << 1) | I2C_WRITE;
for (int i = 0; i < 8; i++)
{
SDA(I2Cx[channel], !!(byte & (1 << (7 - i))));
SCL(I2Cx[channel], HIGH);
SCL(I2Cx[channel], LOW);
}
//Wait for ACK
SDA(I2Cx[channel], HIGH);
SCL(I2Cx[channel], HIGH);
int j = 0;
while(GPIO_getState(I2Cx[channel]->sda) && j < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
j++;
}
SCL(I2Cx[channel], LOW);
//Send stop
SDA(I2Cx[channel], LOW);
SCL(I2Cx[channel], HIGH);
SDA(I2Cx[channel], HIGH);
if (j < I2C_SLAVE_TIMEOUT)
return 1;
return 0;
}
void SDA(I2C_Soft_t * channel, int state)
{
#if !I2C_MAX_SPEED
delayASM(channel->delay);
#endif
GPIO_setState(channel->sda, state);
}
void SCL(I2C_Soft_t * channel, int state)
{
#if !I2C_MAX_SPEED
delayASM(channel->delay);
#endif
GPIO_setState(channel->scl, state);
//Allow for clock stretching
if (state == HIGH)
{
int i = 0;
while((GPIO_getState(channel->scl) == 0) && i < I2C_SLAVE_TIMEOUT)
{
delayASM(1);
i++;
if (i == I2C_SLAVE_TIMEOUT)
{
channel->status = I2C_TIMEOUT;
}
}
}
}
/****************************************************************************************//**
* @file i2c.h
*
* @author Fotis Panagiotopoulos
* @license WTFPL
*
* I2C driver for the STM32F103. Since in these microcontrollers the I2C peripheral is
* broken, this is a software implementation. Frequency may not be accurate, especially
* if the CPU speed is changed. It uses the same pinout, as the hardware controllers,
* aliased with the same channel numbers, for compatibility with the other hal calls,
* and the actual hardware controllers, in case there is a working hardware implementation
* in the future.
*
* All functions provided are thread-safe only when different ports are used
* by the different threads. This means that any side effects the function may
* have, are limited in the scope of this specific port. Different threads
* may use different ports without any extra synchronization.
* When the same port must be shared by multiple threads, the functions have to
* be synchronized externally. Here a separate mutex is provided for each port for convenience
* (It is only initialized and provided for external use. It is nut used internally.)
*
* Channel Mapping
* -I2C1
* -SDA1 PB7
* -SCL1 PB6
* or
* -SDA1 PB9
* -SCL1 PB8
* -I2C2
* -SDA2 PB11
* -SCL2 PB10
*
*
*******************************************************************************************/
#ifndef I2C_H_
#define I2C_H_
#include "hal_types.h"
#include "hal_os.h"
#include "hal_config.h"
/** Channels aliases */
#define I2C_1 0
#define I2C_2 1
/** Definition for write, in START direction */
#define I2C_WRITE 0
/** Definition for read, in START direction */
#define I2C_READ 1
/**
* Default configuration mask. Sets controller in standard mode, and the pin
* multiplexer to the default pins.
*/
#define I2C_DEFAULT_CONFIG 0x00
/**
* Config mask. Only applicable for the I2C0 controller. Sets the controller in fast
* mode (400KHz, instead of the normal 100KHz without this mask, or for the other
* controllers).
*
* @note As this is a software implementation, this is not used.
*/
#define I2C_FAST_MODE 0x01
/**
* Config mask. Will force the I2C1 controller to use pins PB8 and PB9
* for I2C communications (else PB6 and PB7 will be used).
*/
#define I2C_1_REMAP 0x02
/**
* Slave communication timeout, in microseconds. May not be accurate,
* but it is there for safety only.
*/
#ifndef I2C_SLAVE_TIMEOUT
#define I2C_SLAVE_TIMEOUT 1000
#endif
/**
* Set to completely remove the delaying functions in the low
* level routines, and enable maximum transmission frequency,
* limited only by the overhead of the GPIO functions. Using
* a clock of 72MHz, this is measured to be approximately
* 170Khz.
*/
#ifndef I2C_MAX_SPEED
#define I2C_MAX_SPEED 1
#endif
/**
* Error codes returned by the @ref I2C_status() function.
*/
typedef enum {
I2C_OK,
I2C_NO_RESPONSE,
I2C_NACK,
I2C_TIMEOUT,
I2C_ERROR
} I2C_Status_t;
/** I2C 0 mutex. */
extern OS_mutex_t I2C0_mtx;
/** I2C 1 mutex. */
extern OS_mutex_t I2C1_mtx;
/** I2C mutexes. */
extern OS_mutex_t * I2C_mtx[2];
/**
* Initializes the I2C controller, enables clock, sets the prescallers, and sets
* the pins in open drain mode. In RTOS builds the corresponding mutex is also
* initialized.
*
* @note For Standard Mode communication (100kHz) the APB1 must be clocked
* at 2MHz or more, and for Fast Mode (400kHz) at 4MHz or more.
*
* @apram channel The I2C channel to use
* @param frequency The desired bus frequency. Can be lowered by a slave (after
* clock stretching). Duty cycle is always 50%.
* @param config Configuration mask. Sets fast mode for I2C0 and the pin
* multiplexing for the I2C1
*/
void I2C_init(uint32_t channel, uint32_t frequency, uint32_t config);
/**
* Deinitializes the I2C controller. Clocking is stopped, the interrupts are
* disabled, and the pins are configured again as normal GPIOS.
*
* @param channel The channel to disable
*/
void I2C_deinit(uint32_t channel);
/**
* Returns the status of the last operation of the I2C controller.
*
* @param channel The I2C channel to return the status.
* @return Returns the status of the last operation.
*/
I2C_Status_t I2C_status(uint32_t channel);
/**
* Sends a data buffer to the bus. START, address and STOP are automatically send.
*
* @param channel The channel to send the data.
* @param address The slave address.
* @param buffer The data to be send.
* @param size The size in bytes of the data buffer
* @return Returns 1 if succeeds, 0 otherwise.
*/
int I2C_send(uint32_t channel, uint8_t address, const uint8_t * buffer, uint32_t size);
/**
* Sends a data buffer to the slave, and reads back from it after a repeated-START.
* The data are returned at the provided buffer.
*
* @param channel The channel to use for communication.
* @param address The slave address.
* @param buffer The buffer to use for communications. Initially the buffer must
* contain the data to be sent. After that, the received data are
* written again at the same buffer, overwritting the original
* contents.
* @param writeSize The size in bytes of the data to send.
* @param readSize The size in bytes of the expected to receive data.
* @return Returns 1 if succeeds, 0 otherwise.
*/
int I2C_sendCmd(uint32_t channel, uint8_t address, uint8_t * buffer, uint32_t writeSize, uint32_t readSize);
/**
* Receives data from the bus. The data are returned at the provided buffer.
*
* @param channel The channel to receive from
* @param address The slave address
* @param buffer The data buffer to store the read data
* @param size The size in bytes of the expected data.
* @return Returns 1 if succeeds, 0 otherwise.
*/
int I2C_receive(uint32_t channel, uint8_t address, uint8_t * buffer, uint32_t size);
/**
* Polls a slave. Sends the START frame and waits for ACK response from it. No data are
* transfered during this operation (however the direction bit is set to write).
*
* @param channel The channel to use for polling
* @param address The slave address.
* @return 1 if the slave responded, or 0 otherwise.
*/
int I2C_poll(uint32_t channel, uint8_t address);
#endif