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

Reply via email to