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