You are on page 1of 20

8B : Sensor Example, IC

STM32 I2C Interface


Rajesh Panicker

[T]EE2028

References:
[1] STM32L4x5-6 Ref Manual_RM0351 Pages 1264 to 1331
[2] hts221 Datasheet
[3] STM32l475vg Datasheet
[4] B-L475E-IOT01A_user_manual_UM2153

Codes provided in this chapter are simplified and incomplete versions of the one in libraries to facilitate understanding; not meant to be compiled
Device example : HTS221 Temperature Sensor

◼ Measures 16-bit temperature and humidity


◼ Temperature accuracy: ± 0.5 °C, 15 to +40 °C
◼ 1 Hz to 12.5 Hz output data rate
◼ SPI and I2C interfaces supported
◼ Configured in I2C mode when CS pin = HIGH or unconnected
◼ Standard mode and Fast mode
◼ 7-bit slave address = 1011111 (0x5F)
◼ Read address : 10111111 (0xBF)
◼ Write address : 10111110 (0xBE)
◼ Hard-coded by the manufacturer. Cannot be changed
◼ Two different HTS221s could send different values for the
same temperature (magnitude)!
◼ Need to make use of calibration data to find the actual temperature
(magnitude)

2
HTS221 Registers
◼ WHO_AM_I (Device identification, Address$ : 0x0F)

◼ Read to ensure that we are talking to the correct device


◼ Value hard-coded by the manufacturer, the value is typically not the same as
slave address
◼ CTRL_REG1 (Control Register 1, Address : 0x20)

◼ PD: power down control. The device is in power-down mode when PD


= 0 (default value after boot). The device is active when PD is set to 1
◼ BDU: block data update. 0 -> continuous update; 1 -> output registers
not updated until MSB and LSB are both ready with the new value
◼ Setting it to 1 prevents the reading of LSB and MSB related to different samples
◼ ODR [1:0] : output data rate selection (see table 17 in page 22 of [2])
◼ BSP library sets it to 01 during initialization, configuring temp. measurement in 1 Hz mode
$ Theseregister addresses are not ‘offsets’, but actual addresses local to the specific I 2C Device.
Another I2C device can have a different register with the same address!
3
[] in this slide is bit index, not array
index, i.e., not really following C syntax

HTS221 Registers
◼ TEMP_OUT_L (Temperature data [LSB], 0x2A)

◼ TEMP_OUT_H (Temperature data [MSB], 0x2B)

◼ TOUT[15:0] is a signed (2’s complement) value


◼ In HTS221, 7 LSB represents the actual register address while the MSB
enables address autoincrement. If the MSB of the 8-bit register address field
is ‘1’, autoincrement is enabled
◼ Allows multiple sequential registers to be read/written in a single operation
◼ We can write (1<<7)|0x2A, i.e., 0xAA, and read two bytes in one transaction to get TOUT
◼ Temperature (magnitude) can be computed by linear interpolation of TOUT
value using calibration regs T0_OUT, T1_OUT, T0_degC_x8, T1_degC_x8 and
T1/T0 msb regs (See Fig. 9 in page 27 of [2]. More about this in tutorial)
◼ T0_OUT is the value (signed) corresponding to the temp. (magnitude) T0_DegC given by
( T1/T0 msb[1:0]<<8 | T0_degC_x8[7:0] )>>3 oC
◼ T1_OUT is the value (signed) corresponding to the temp. (magnitude) T1_DegC given by
( T1/T0 msb[3:2]<<8 | T1_degC_x8[7:0] )>>3 oC
4
Temperature Sensor : BSP Lib Functions
stm32l475e_iot01_tsensor.h/c
hts221.c
#define TSENSOR_I2C_ADDRESS (uint8_t)0xBE
uint32_t BSP_TSENSOR_Init() TSENSOR_DrvTypeDef HTS221_T_Drv =
{ {
tsensor_drv = &HTS221_T_Drv; HTS221_T_Init,
SENSOR_IO_Init(); // Low level (I2C) init 0,
0,
// High-level (TSENSOR) Init HTS221_T_ReadTemp
tsensor_drv->Init(TSENSOR_I2C_ADDRESS, NULL); };

} Some driver functions


expect 7-bit values for slave
addresses; be mindful
tsensor.h
typedef struct // driver function pointers
{
void (*Init)(uint16_t, TSENSOR_InitTypeDef *);
uint8_t (*IsReady)(uint16_t, uint32_t);
uint8_t (*ReadStatus)(uint16_t);
float (*ReadTemp)(uint16_t);
}TSENSOR_DrvTypeDef;

Why complicate life things using function pointers?


5
BSP Lib Function : HTS221_T_Init()
hts221.h/c
void HTS221_T_Init(uint16_t DeviceAddr, TSENSOR_InitTypeDef *pInitStruct)
{
uint8_t tmp;

tmp = SENSOR_IO_Read(DeviceAddr, HTS221_CTRL_REG1); // #define HTS221_CTRL_REG1 (uint8_t)0x20


// Reading CTRL_REG1 is necessary since bits marked as reserved should not be modified

// Enable BDU
tmp &= ~HTS221_BDU_MASK; // #define HTS221_BDU_MASK (uint8_t)0x04
tmp |= (1 << HTS221_BDU_BIT); // #define HTS221_BDU_BIT 2

// Set ODR to 1Hz (ODR bits = 01)


tmp &= ~HTS221_ODR_MASK; // #define HTS221_ODR_MASK (uint8_t)0x03
tmp |= (uint8_t)0x01;

tmp |= HTS221_PD_MASK; // Activate the device

// tmp at this point is 0b1xxxx101, where x bits denotes reserved bits

SENSOR_IO_Write(DeviceAddr, HTS221_CTRL_REG1, tmp); // Apply settings to CTRL_REG1


}

Homework : Read and make sense of HTS221_T_ReadTemp() in hts221.h/c


6
BSP Lib Function : HTS221_T_Init()
◼ SENSOR_IO_Read() causes a write of 1 byte followed by a
read of 1 byte
◼ Write causes a total of 2 bytes to be sent over the I2C bus
◼ 0xBE : slave address, to select HTS221 + W
◼ 0x20 : to select HTS221_CTRL_REG1
◼ Read causes a total of 1 byte to be sent, and 1 byte to be received over the I2C
bus
◼ 0xBF : slave address, to select HTS221 + R Actions by slave in blue
◼ 0x?? : CTRL_REG1_content sent by the slave Actions by master in black
◼ The exact sequence on the bus is
S > 0xBE > A > 0x20 > A > Sr > 0xBF > A > 0x?? > A’ > P
◼ SENSOR_IO_Write() causes a single write operation of 2 bytes
◼ Total 3 bytes to be sent over the I2C bus
◼ 0xBE : slave address, to select HTS221 + W
◼ 0x20 : to select HTS221_CTRL_REG1
◼ 0b1xxxx101 : Content to be written into HTS221_CTRL_REG1
◼ The exact sequence on the bus is
S > 0xBE > A > 0x20 > A > 0b1xxxx101 > A > P
7
I2C in STM32L475VG
◼ STM32L475VG chip has 3 I2C bus interfaces handling
communications between the microcontroller and the serial
I2C bus
◼ controls all I2C bus-specific sequencing, protocol, arbitration and
timing
◼ converts data from serial to parallel format and vice versa

◼ The I2C peripheral supports


◼ Slave and master modes
◼ Multi-master capability
◼ Standard-mode, Fast-mode, Fast-mode Plus
◼ 7-bit and 10-bit addressing mode
◼ In slave mode - multiple 7-bit slave addresses and optional clock
stretching
Which I2C interface is used for interfacing the peripherals on the Board?
Which pins are used for SDA and SCL?
Which MODE? Which AF? What PUPD and OTYPE values should be used?
8
Refer to page 1330 of [1] for a summary of all registers and bits

I2C_CR1 (Basic Ctrl / Enables)


◼ Address offset: 0x00, Default value: 0x0000 0000

◼ Writing a 1 to ANF OFF turns off analog filter on SCL and SDA. DNF
configures the digital filter on SCL and SDA
◼ xxIE is to enable various I2C interrupts. We use polling, so it will all be 0
◼ Writing a 0 to PE disables and performs a soft reset of the I2C interface
◼ SCL and SDA are released. Internal states machines are reset
◼ Some communication control bits (in I2C_CR2, e.g: START, STOP) as well as status bits (in
I2C_ISR, e.g: TXIS, RXNE) go back to their Default value. Other I2C_CR1 bits are
unaffected
◼ Writing a 1 enables the I2C interface. Before doing this,
◼ Set analog / digital noise filters
◼ Configure I2C master clock by setting the SCLH and SCLL bits in the I2C_TIMINGR (which
sets speed modes etc.)
◼ Set an appropriate value for NOSTRETCH (useful only in slave mode) 9
BSP > stm32l475e_iot01.h/c
Note : A function with static
void SENSOR_IO_Init(void) qualifier is a ‘private’ function,
{ meant to be used by other
I2Cx_Init(&hI2cHandler); functions within the file, rather
than directly from the user
} (main) program. Those which
are called from the main
static void I2Cx_Init(I2C_HandleTypeDef *i2c_handler) program are ‘public’ functions
{
// I2C configuration
i2c_handler->Instance = DISCOVERY_I2Cx;
i2c_handler->Init.Timing = DISCOVERY_I2Cx_TIMING;
i2c_handler->Init.OwnAddress1 = 0;
i2c_handler->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
i2c_handler->Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
i2c_handler->Init.OwnAddress2 = 0;
i2c_handler->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
i2c_handler->Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;

// Init the I2C


I2Cx_MspInit(i2c_handler); // Enabling I2C peripheral clock in the clock
// controller, Pin configuration etc.
HAL_I2C_Init(i2c_handler); // HAL function writes to I2C_CR1, I2C_TIMINGR,
// bits unaffected by soft reset in I2C_CR2 etc.

// Configure Analogue filter


HAL_I2CEx_ConfigAnalogFilter(i2c_handler, I2C_ANALOGFILTER_ENABLE);
}
10
Rx & Tx Data Regs (I2C_RXDR, I2C_TXDR)
◼ Receive data register (I2C_RXDR)
◼ Address offset: 0x24, Default value: 0x0000 0000
◼ Bits 31:8 Reserved, must be kept at Default value
◼ Bits 7:0 RXDATA[7:0] -> 8-bit receive data : Data byte received from the I2C bus
◼ The content from the SIPO shift register which samples the serial data on SDA in receive
mode is copied into this register once a complete byte is received

◼ Transmit data register (I2C_TXDR)


◼ Address offset: 0x28, Default value: 0x0000 0000
◼ Bits 31:8 Reserved, must be kept at Default value
◼ Bits 7:0 TXDATA[7:0] -> 8-bit transmit data : Data byte to be transmitted to I2C bus
◼ The content is copied into a PISO shift register which sends the data serially on SDA in
transmit mode

11
Control Reg 2 : Communication Ctrl (I2C_CR2)
◼ Address offset: 0x04, Default value: 0x0000 0000
◼ Software (driver) writes to this register to specify information related to one
transfer in master mode
◼ Setting START=1 will cause the interface to enter the master mode (by default,
interface is in slave mode) and send a S condition over the bus if the bus is free
◼ cleared by hardware after the S followed by the address sequence is sent, by an
arbitration loss, by a timeout error detection, or when PE = 0
◼ Setting STOP will cause P to be generated after current byte transfer
◼ cleared by hardware when a P condition is detected, or when PE = 0. Writing ‘0’ to this
bit has no effect
◼ The I2C interface has a byte counter to generate A / A’, P (optional) as
appropriate to manage byte transfer
◼ while receiving n bytes (n is set in NBYTES) of data, the first n-1 bytes will be A-ed,
the last one will be A’ ed automatically. P will also be generated if AUTOEND bit is set

read or set.
Don’t clear

12
Control Reg 2 : Communication Ctrl (I2C_CR2)
◼ SADD[9:8] and SADD[0] are don’t cares in 7-bit addressing modes, 7-bit slave
address should be written to SADD[7:1]
◼ RD_WRN: transfer direction : 0 -> write, 1 -> read
◼ ADD10: 1 -> 10-bit addressing mode, 0 -> 7-bit mode
◼ RELOAD: NBYTES reload mode
◼ 0-> transfer is completed after the NBYTES data transfer (P / Sr will follow)
◼ 1 -> transfer is not completed after the NBYTES data transfer (NBYTES will need to be
reloaded when TCR* is set). Possible use : when more than 255 bytes are transferred
◼ AUTOEND: Automatic end mode
◼ 0 -> software end mode: TC* is set when NBYTES data are transferred, stretching SCL
low. The software needs to send a P or Sr explicitly
◼ 1 -> automatic end mode: a P condition is automatically sent when NBYTES data are
transferred. Should not be used when we plan to send Sr
◼ Ignored when RELOAD is set
◼ NACK has no effect in master mode
◼ A / A’ is automatically generated when receiving
◼ When transmitting, we can’t read NACK bit to see if the slave has sent a A’ by reading
this bit – we do that by reading I2C_ISR
◼ NBYTES, ADD10, RD_WRN, SADD cannot change when the START bit is set
*bit in I2C_ISR 13
Interrupt and Status Register (I2C_ISR)
◼ Address offset: 0x18, Default value: 0x0000 0001
◼ Contains various status and interrupt status bits
◼ Generally set by the hardware, cleared due to some action by the software
◼ If a bit in this register becomes 1, and the corresponding interrupt is enabled
in I2C_CR1, the interrupt is sent to NVIC and the interrupt handler will be
triggered if the interrupt is enabled in the NVIC as well
◼ There are 2 separate interrupts from each I2C interface to NVIC : event (EV)
and error (ER)
◼ Note that TC and TCR interrupts can only be enabled or disabled together in
I2C_CR1. Similarly, all error interrupts are enabled or disabled together
◼ If not using interrupts, these bits can be polled by the software as status bits

read or set.
ER EV Don’t clear

◼ Clearing some bits (e.g: NACKF) require writing to I2C_ICR (e.g: NACK
CF). Bits are changed to their Default values when PE=0
14
Interrupt and Status Register (I2C_ISR)
◼ TCR: Transfer Complete Reload
◼ Set when RELOAD=1 and NBYTES data have been transferred
◼ Cleared when NBYTES is written with a non-zero value
◼ TC: Transfer Complete (master mode)
◼ Set when RELOAD=0, AUTOEND=0 and NBYTES data have been transferred
◼ Cleared when START bit or STOP bit is set
◼ RXNE: Receive data register not empty
◼ Set when the received data is copied into the I2C_RXDR register, and is ready to be read
◼ Cleared when I2C_RXDR is read
◼ TXIS: Transmit interrupt status
◼ Set when the I2C_TXDR register is empty and the data to be transmitted must (TXIS is not
set during a read operation even when TXDR is empty, for example) be written in the I2C_TXDR register
◼ Cleared when the next data to be sent is written in the I2C_TXDR register
◼ Can be written to ‘1’ by software in order to generate a TXIS event
◼ TXE: Transmit data register empty. Default value : 1
◼ Set by hardware whenever the I2C_TXDR register is empty and can be written (TXE is set
even during a read operation when TXDR is empty, for example )
◼ Cleared when the next data to be sent is written to I2C_TXDR register
◼ I2C_TXDR can be written only when TXE=1
◼ Can be written to ‘1’ by software in order to flush the transmit data register I2C_TXDR 15
BSP > stm32l475e_iot01.h/c

uint8_t SENSOR_IO_Read(uint8_t Addr, uint8_t Reg)


{
uint8_t read_value = 0;
I2Cx_ReadMultiple(&hI2cHandler, Addr, Reg, I2C_MEMADD_SIZE_8BIT, (uint8_t*)&read_value, 1);
return read_value;
}

uint16_t SENSOR_IO_ReadMultiple(uint8_t Addr, uint8_t Reg, uint8_t *Buffer, uint16_t Length)


{
return I2Cx_ReadMultiple(&hI2cHandler, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, Buffer, Length);
}

static HAL_StatusTypeDef I2Cx_ReadMultiple(I2C_HandleTypeDef *i2c_handler, uint8_t Addr, uint16_t


Reg, uint16_t MemAddress, uint8_t *Buffer, uint16_t Length)
{
status = HAL_I2C_Mem_Read(i2c_handler, Addr, (uint16_t)Reg, MemAddress, Buffer, Length, 1000);
}

Similar hierarchy for Write functions, ‘Read’ -> ‘Write’ in the functions above

16
BUSY TXIS Flag set by h/w, polled by s/w in red
S > DevAddress_W > A > MemAddress > A > Action by s/w in response to flag in green
START=1, SADD=DevAddress, RD_WRN=0, TXDR=MemAddress
NBYTES=1, AUTOEND=0, RELOAD=1
TCR TXIS TXIS STOPF

> WriteByte_1 > A >….. > WriteByte_Size > A > P


TXDR=*(pData+0) TXDR=*(pData+Size-1) STOPCF=1, Reset CR2
NBYTES=Size, AUTOEND=1, RELOAD=0

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t


MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
// Size is the num. bytes to be written, excluding slave addr and slave reg addr
// MemAddSize is the num. bytes needed to represent a slave reg. addr. This is assumed to be 1 (as with most slaves)
uint32_t tickstart = HAL_GetTick();
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY, tickstart)!= HAL_OK){return HAL_TIMEOUT;}
// Send S, Slave Address, Address of register within the slave to be written
I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart)
// Do not send Sr, just reload NBYTES assuming hi2c->XferCount < 256. Writes to I2C_CR2
I2C_TransferConfig(hi2c, DevAddress, Size, I2C_AUTOEND_MODE, I2C_NO_STARTSTOP);

do // Send 'Size' bytes


{ // for the sensors on the board, hi2c->Instance = I2C2
// Wait until TXIS flag in I2C_ISR is set
if (I2C_WaitOnTXISFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) {return HAL_TIMEOUT;}
hi2c->Instance->TXDR = (*pData++); // Write data to I2C_TXDR
Size--;
// Need to wait until TCR flag in I2C_ISR is set and reload NBYTES if Size > 255 (code not shown)
} while (Size > 0U);

// No need to Check TC flag in I2C_ISR, with AUTOEND mode the stop is automatically generated
// Wait until STOPF flag in I2C_ISR is set
if (I2C_WaitOnSTOPFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK){return HAL_TIMEOUT;}

__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_STOPF); // Clear STOP Flag


I2C_RESET_CR2(hi2c); // Clear I2C_CR2
return HAL_OK;
}
HAL > stm32l4xx_hal_i2c.h/c 17
BUSY TXIS
S > DevAddress_W > A > MemAddress > A >
START=1, SADD=DevAddress, RD_WRN=0, TXDR=MemAddress
NBYTES=1, AUTOEND=0, RELOAD=0
TC RXNE RXNE STOPF

Sr > DevAddress_R > A > ReadByte_1 > A >….. > ReadByte_Size > A’ > P
START=1, SADD=DevAddress, RD_WRN=1, *(pData+0)=RXDR *(pData+ STOPCF=1, Reset CR2
NBYTES=Size, AUTOEND=1, RELOAD=0 Size-1)=RXDR

HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t


MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
// Size is the num. bytes to be read
// MemAddSize is the num. bytes needed to represent a slave reg. addr. This is assumed to be 1 (as with most slaves)
uint32_t tickstart = HAL_GetTick();
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY, tickstart)!= HAL_OK){return HAL_TIMEOUT;}
// Send S, Slave Address, Address of register within the slave to be read
I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart);
// Send Sr, Slave Address, load NBYTES assuming hi2c->XferCount < 256. Writes to I2C_CR2.
I2C_TransferConfig(hi2c, DevAddress, Size, I2C_AUTOEND_MODE, I2C_GENERATE_START_READ);

do // Receive 'Size' bytes


{ // for the sensors on the board, hi2c->Instance = I2C2
// Wait until RXNE flag in I2C_ISR is set
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_RXNE, RESET, Timeout, tickstart) != HAL_OK){return HAL_TIMEOUT;}
(*pData++) = hi2c->Instance->RXDR; // Read data from I2C_RXDR
Size--;
// Need to wait until TCR flag in I2C_ISR is set and reload NBYTES if Size > 255 (code not shown)
} while (Size > 0U);

// No need to Check TC flag in I2C_ISR, with AUTOEND mode the stop is automatically generated
// Wait until STOPF flag in I2C_ISR is set
if (I2C_WaitOnSTOPFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK){return HAL_TIMEOUT;}

__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_STOPF); // Clear STOP Flag


I2C_RESET_CR2(hi2c); // Clear I2C_CR2
return HAL_OK;
}
HAL > stm32l4xx_hal_i2c.h/c 18
HAL > stm32l4xx_hal_i2c.h/c

static HAL_StatusTypeDef I2C_RequestMemoryRead(....)


{
// Writes to I2C_CR2.
I2C_TransferConfig(hi2c, DevAddress, MemAddSize, I2C_SOFTEND_MODE, I2C_GENERATE_START_WRITE);
I2C_RELOAD_MODE for I2C_RequestMemoryWrite()
// Wait until TXIS in I2C_ISR flag is set
if (I2C_WaitOnTXISFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK){return HAL_TIMEOUT;}

// Address of register within the slave to be read. Only LSB for 8-bit reg. address
hi2c->Instance->TXDR = I2C_MEM_ADD_LSB(MemAddress);

// Wait until TC flag in I2C_ISR is set I2C_FLAG_TCR for I2C_RequestMemoryWrite()


if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_TC, RESET, Timeout, Tickstart) != HAL_OK){return
HAL_TIMEOUT;}

return HAL_OK;
}

19
Questions?

20

You might also like