Hierodule 1.6.2
Utility module set for STM32 MCUs
Loading...
Searching...
No Matches
I2C Module

With this module, you can

  • Initiate an I2C transmission as master transmitter or master receiver.
  • Unidirectionally parse the data received as slave receiver, which is kept in a ring buffer.
  • Set up the data to be transmitted as master or slave during the next transmission.
  • Assign callback routines to be invoked at the end of a transmission, for the four transmission modes.

HIERODULE_I2C_Wrapper is the main item in the module, with which you can access the variables related to the ring buffer and use the module routines to implement your I2C comm procedure.

The module does not address peripheral configuration and initialization, it is assumed those are performed beforehand. Make sure to have configured your I2C peripheral accordingly. Also, make sure to use external pull up resistors since most of the time internal pull-ups won't be strong enough for high speed. Typically, a 4.7kΩ resistor on each line should be enough but it's best to consult the device manual to find out the recommended resistance value for the I2C lines.

Also notice that the I2C IRQs are defined in the module's source file. The compiler will throw a multiple definition error if an IRQ is defined somewhere else.

To start using the module, first thing you need is a double pointer to HIERODULE_I2C_Wrapper to create an instance for the I2C peripheral.

HIERODULE_I2C_Wrapper **My_I2C1_Wrapper = NULL;
Struct that keeps variables for the data buffers, a pointer to the I2C peripheral,...

It has to be a double pointer, since the wrapper initializer passes you the wrapper pointer defined in the module by reference. The wrapper instance is handled in the module with a pointer and not with a variable of type HIERODULE_I2C_Wrapper, since you use pointers for dynamic memory allocation.

Next, define the four callback routines to be called at the end of transmissions.

void SRX1_Handler(void)
{
/*
* This will be performed at the end of a slave receiver mode transmission.
*/
}
void MTX1_Handler(void)
{
/*
* This will be performed at the end of a master transmitter mode transmission.
*/
}
void STX1_Handler(void)
{
/*
* This will be performed at the end of a slave transmitter mode transmission.
*/
}
void MRX1_Handler(void)
{
/*
* This will be performed at the end of a master receiver mode transmission.
*/
}


Unlike other modules, callback functions you assign to the wrapper do not follow the routine-per-interrupt scheme, a.k.a. an ISR. In other words, you do not assign routines to be invoked at each received or transmitted byte, or to handle interrupt signals. The four routines you provide will only be invoked at the end of an I2C transmission.
You only need to implement the callback routines according to the device role and the consequent procedure, and let the module handle the communication. It's possible to switch to a different callback routine for a transmission mode as well, as long as the pointer to the routine is of type void(*)(void).
You can also just pass a null pointer if you do not need anything done at the end of a certain transmission mode.

Next, initialize the wrapper with the callback routines and an arbitrary SRX buffer size, 24 for this instance.

My_I2C1_Wrapper = HIERODULE_I2C_InitWrapper(I2C1, 24, SRX1_Handler, MTX1_Handler, STX1_Handler, MRX1_Handler);
HIERODULE_I2C_Wrapper ** HIERODULE_I2C_InitWrapper(I2C_TypeDef *_I2C, uint16_t SRX_BufferSize, void(*SRX_Handler)(void), void(*MTX_Handler)(void), void(*STX_Handler)(void), void(*MRX_Handler)(void))
Initializes a wrapper for the specified I2C peripheral.


And you're good to go. Now your wrapper can handle comm requests from masters, perform the callback routines you've assigned to the modes and update the slave receiver buffer when a master is done transmitting data. You can parse the data received as slave via HIERODULE_I2C_GetNextByte.

To transmit data as master, call the master transmit routine with your wrapper, a slave address, a byte array and the number of bytes to be transmitted.

uint8_t SlaveAddress = 0b01000101;
uint8_t MTX1_Buffer[6];
/*
* ...
*/
HIERODULE_I2C_MasterTransmit(*My_I2C1_Wrapper, SlaveAddress, MTX1_Buffer, 6);
void HIERODULE_I2C_MasterTransmit(HIERODULE_I2C_Wrapper *Wrapper, uint8_t SlaveAddress, uint8_t *MTX_Buffer, uint32_t Size)
Puts the peripheral in master transmitter mode and handles the transmission.


Receiving data from a slave is pretty similar.

uint8_t MRX1_Buffer[12];
HIERODULE_I2C_MasterReceive(*My_I2C1_Wrapper, SlaveAddress, MRX1_Buffer, 12);
void HIERODULE_I2C_MasterReceive(HIERODULE_I2C_Wrapper *Wrapper, uint8_t SlaveAddress, uint8_t *MRX_Buffer, uint32_t Size)
Puts the peripheral in master receiver mode and handles the transmission.

You do not need to shift or set the LSB of the device address, the routines take care of that.

You can "release" the instance of your wrapper to free memory:

HIERODULE_I2C_ReleaseWrapper(*My_I2C1_Wrapper);
void HIERODULE_I2C_ReleaseWrapper(HIERODULE_I2C_Wrapper *Wrapper)
Frees the memory allocated to an I2C wrapper.

Keep in mind this only frees the wrapper pointer in the module and not your double pointer that points to it. It's best to free and nullify that, as well:

free(My_I2C1_Wrapper);
My_I2C1_Wrapper = NULL;

Notice that trying to access a freed memory address might turn out ugly for your run-time. However, you can reuse your double pointer to re-initialize the I2C peripheral after releasing it.