0
\$\begingroup\$

I have a LM75A that I communicating with over I2C, using the STM32F4x Hal.

I am using a STM32F446RE board.

My code is abstracted in multiple layers to allow for some modularity.

LM75A_Driver.c and .h files are simple abstractions of the hal functions HAL_I2C_Master_Transmit / Receive

This the code for LM75A_Driver.h

#ifndef __LM75A_DRIVER_H
#define __LM75A_DRIVER_H

#include "stm32f4xx_hal.h"
#include "stm32f4xx_hal_i2c.h"
#include "gpio_mapping.h"
#include "error_handler.h"
#include "COM_Driver.h"

#define timeout 325

#define LM75_TEMP_REG 0x00  // Temperature Register
#define LM75_CONF_REG 0x01  // Configuration Register
#define LM75_THYST_REG 0x02 // Temperature Hysteresis Register
#define LMS75_TOS_REG 0x03  // Over-temp Shutdown threshold Register
#define LM75_INFO_REG 0x07  // Information Register

#define LM75A_ADDRESS 0x48 << 1 // LM75A address

struct I2C_Module
{
    I2C_HandleTypeDef instance;
    uint16_t sdaPin;
    GPIO_TypeDef *sdaPort;
    uint16_t sclPin;
    GPIO_TypeDef *sclPort;
};

// Function prototypes
void LM75_Init(void);
HAL_StatusTypeDef LM75_ReadReg(uint8_t register_address, uint8_t *data, uint16_t size);
HAL_StatusTypeDef LM75_WriteReg(uint8_t register_address, uint8_t *data, uint16_t size);

void I2C_ClearBusyFlagErratum(struct I2C_Module *i2c);

#endif

Code for LM75A_Driver.c

#include "LM75A_Driver.h"

I2C_HandleTypeDef hi2c;

// Initialize I2C peripheral and GPIO pins
void LM75_Init(void)
{

    // Enable clock for I2C peripheral and GPIO port
    I2Cx_CLK_ENABLE();
    I2Cx_GPIO_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct;

    // Configure SCL, SDA pins as alternate function
    GPIO_InitStruct.Pin = I2Cx_SCL_PIN | I2Cx_SDA_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = I2Cx_AF;

    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_I2C1_CLK_ENABLE();

    HAL_GPIO_Init(I2Cx_GPIO_PORT, &GPIO_InitStruct);

    // I2C peripheral configuration
    hi2c.Instance = I2Cx;
    hi2c.Init.ClockSpeed = 300000; // Adjust as needed
    hi2c.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c.Init.OwnAddress1 = 0;
    hi2c.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c.Init.OwnAddress2 = 0;
    hi2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;

    if (HAL_I2C_Init(&hi2c) != HAL_OK)
    {
        // Initialization Error
        Error_Handler();
    }
}

// Write data to an I2C device
HAL_StatusTypeDef LM75_WriteReg(uint8_t register_address, uint8_t *data, uint16_t size)
{
    if (register_address == LM75_TEMP_REG || register_address == LM75_INFO_REG)
    {
        return HAL_ERROR;
    }

    // Check if the device is ready
    HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(&hi2c, LM75A_ADDRESS, 10, 1000);
    if (status != HAL_OK)
    {
        HAL_Delay(1000);
        return HAL_ERROR;
    }

    return HAL_I2C_Master_Transmit(&hi2c, LM75A_ADDRESS, data, size, HAL_MAX_DELAY);
}

// Read data from an I2C device
HAL_StatusTypeDef LM75_ReadReg(uint8_t register_address, uint8_t *data, uint16_t size)
{
    // Check if the device is ready
    HAL_StatusTypeDef status = HAL_I2C_IsDeviceReady(&hi2c, LM75A_ADDRESS, 10, 1000);
    if (status != HAL_OK)
    {
        if (status == HAL_BUSY)
        {
            I2C_ClearBusyFlagErratum(&hi2c);
        }
    }

    return HAL_I2C_Master_Receive(&hi2c, LM75A_ADDRESS, data, size, HAL_MAX_DELAY);
}

This code excludes the function I2C_ClearBusyFlagErratum()

which I sourced from this question/answer: Link

The Erratum Clear Flag code is as follows:

void I2C_ClearBusyFlagErratum(struct I2C_Module *i2c)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    // 1. Clear PE bit.
    i2c->instance.Instance->CR1 &= ~(0x0001);

    //  2. Configure the SCL and SDA I/Os as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStructure.Alternate = I2Cx_AF;
    GPIO_InitStructure.Pull = GPIO_PULLUP;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;

    GPIO_InitStructure.Pin = i2c->sclPin;
    HAL_GPIO_Init(i2c->sclPort, &GPIO_InitStructure);
    HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET);

    GPIO_InitStructure.Pin = i2c->sdaPin;
    HAL_GPIO_Init(i2c->sdaPort, &GPIO_InitStructure);
    HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET);

    // 3. Check SCL and SDA High level in GPIOx_IDR.
    while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sclPort, i2c->sclPin))
    {
        asm("nop");
    }

    while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sdaPort, i2c->sdaPin))
    {
        asm("nop");
    }

    // 4. Configure the SDA I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_RESET);

    //  5. Check SDA Low level in GPIOx_IDR.
    while (GPIO_PIN_RESET != HAL_GPIO_ReadPin(i2c->sdaPort, i2c->sdaPin))
    {
        asm("nop");
    }

    // 6. Configure the SCL I/O as General Purpose Output Open-Drain, Low level (Write 0 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_RESET);

    //  7. Check SCL Low level in GPIOx_IDR.
    while (GPIO_PIN_RESET != HAL_GPIO_ReadPin(i2c->sclPort, i2c->sclPin))
    {
        asm("nop");
    }

    // 8. Configure the SCL I/O as General Purpose Output Open-Drain, High level (Write 1 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sclPort, i2c->sclPin, GPIO_PIN_SET);

    // 9. Check SCL High level in GPIOx_IDR.
    while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sclPort, i2c->sclPin))
    {
        asm("nop");
    }

    // 10. Configure the SDA I/O as General Purpose Output Open-Drain , High level (Write 1 to GPIOx_ODR).
    HAL_GPIO_WritePin(i2c->sdaPort, i2c->sdaPin, GPIO_PIN_SET);

    // 11. Check SDA High level in GPIOx_IDR.
    while (GPIO_PIN_SET != HAL_GPIO_ReadPin(i2c->sdaPort, i2c->sdaPin))
    {
        asm("nop");
    }

    // 12. Configure the SCL and SDA I/Os as Alternate function Open-Drain.
    GPIO_InitStructure.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStructure.Alternate = I2Cx_AF;

    GPIO_InitStructure.Pin = i2c->sclPin;
    HAL_GPIO_Init(i2c->sclPort, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = i2c->sdaPin;
    HAL_GPIO_Init(i2c->sdaPort, &GPIO_InitStructure);

    // 13. Set SWRST bit in I2Cx_CR1 register.
    i2c->instance.Instance->CR1 |= 0x8000;

    asm("nop");

    // 14. Clear SWRST bit in I2Cx_CR1 register.
    i2c->instance.Instance->CR1 &= ~0x8000;

    asm("nop");

    // 15. Enable the I2C peripheral by setting the PE bit in I2Cx_CR1 register
    i2c->instance.Instance->CR1 |= 0x0001;

    // Call initialization function.
    HAL_I2C_Init(&(i2c->instance));

    COM_printf("I2C bus reset\n");
}

The code gets stuck on the first while loop inside the erratum solution since the pin is never set.

I suspect it is the same reason as why I am keep getting the HAL_BUSY flag. I have checked hardware, my device address, and connections. Everything seems fine.

I have not however taken a Logic Analyzer to the device yet since one is not readily available for me to use.

I have found a few resources discussing the flag, some either talk about the analog filter problem, or timing issues from GPIO pin setup. None of those solutions have worked for me.

If you have any suggestions or advice, it would be very much appreciated.

Edit: As I was asked to do so, I have added the function calling the LM75A driver.

void vTelemetryLM75AReadTask(void *pvParameters)
{
    LM75_Init();
    COM_Init();

    uint8_t data[2];
    float temperature;
    char tes[8];
    for (;;)
    {
        HAL_StatusTypeDef status = LM75_ReadReg(LM75_TEMP_REG, data, 2);

        if (status != HAL_OK)
        {
            COM_printf("Error reading LM75A");
            vTaskDelay(1000);
            continue;
        }
        if (status == HAL_BUSY)
        {
            COM_printf("LM75A busy");
        }

        temperature = (float)((data[0] << 8) | data[1]) / 256;
        sprintf(tes, "%f", temperature);
        COM_printf(tes);
        vTaskDelay(1000);
    }
}
\$\endgroup\$
8
  • \$\begingroup\$ It is not clear how you call these functions and is the I2C even initialized. The erratum function is for STM32F1, they might not share a similar I2C peripheral, and the F4 does not have a bug in errata regarding stuck analog filters. Please debug what part is succesful and where the code first reports busy. If it fails it should report a timeout. And also those register read and write functions don't read or write registers. And are you sure the IsDeviceReady is not causing the problem, why use it if the LM75 can never signal it is not ready? \$\endgroup\$
    – Justme
    Commented Feb 1 at 23:11
  • \$\begingroup\$ @Justme I will edit the question to include the info you require. From my understanding, LM75A should be able to send ACK followed by the temperature in its default setting. And the function IsDeviceReady() should be able to use that ACK to signal device ready. On the part of Erratum, in the link I provided, the top answer had the same problem solved running the code on their STM32F4 board. I have also debugged this such that I know the problem occurs in the code I provided! I will add the additional info to the question. \$\endgroup\$
    – Liferafter
    Commented Feb 2 at 0:27
  • \$\begingroup\$ The erratum function might work on F4 because it resets and reinits the I2C peripheral. It just is not stuck due to bug in analog filters like on F1 so the reason must be elsewhere. You now reveal it is a multitasking system, do you use same I2C bus from more than one task? \$\endgroup\$
    – Justme
    Commented Feb 2 at 5:13
  • \$\begingroup\$ Have you measured the voltage levels on your I2C lines? Do you have appropriate external pull-up resistors on them? Getting stuck inside that 1st while loop implies that the SCL pin is never going high - and the only thing that can pull it high is a pull-up resistor. \$\endgroup\$
    – brhans
    Commented Feb 2 at 14:49
  • \$\begingroup\$ @Justme The system currently is only running this I2C task, without any other tasks or scheduling. \$\endgroup\$
    – Liferafter
    Commented Feb 4 at 5:48

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.