cancel
Showing results for 
Search instead for 
Did you mean: 

I2C Slave device implementation ACK/NACK problem

mr4k
Visitor

As mentioned in the topic subject, I would like to implement I2C Slave device which will simulate the BQ769x2 device. For development I'm using STM32F4 Discovery board, but the final product will be deployed on STM32L0.

Master device is first trying to detect if slave device is present and after that some functions will be called in no exact order nor the functions have same length. Therefore I was thinking interrupt mode would be appropriate to implement here for greater control.

For testing purpose I have written small Master device program for Arduino (running on Arduino MKRZERO).
With this example one can send scan or probe command over serial to run the procedure.

 

 

#include <Wire.h>
#include <stdarg.h>
#define PRINTF_BUF 80 // define the tmp buffer size (change if desired)

String inputData;

void setup() {
  Wire.begin();
  Wire.setClock(100000);
  #if defined(WIRE_HAS_TIMEOUT)
    Wire.setWireTimeout(300 * 1000 /* us */, true /* reset_on_timeout */);
  #else
    Wire.setTimeout(500);
  #endif

  Serial.begin(115200);
  while(!Serial)
    delay(10);
  Serial.println("\nI2CScanner started");
}

void loop() {
  // wait for user input
  while(Serial.available() == 0){}

  inputData = Serial.readString();
  inputData.trim();

  if(inputData.equals("scan"))
  {
    Serial.println(inputData);
    i2cdetect();
  }
  else if(inputData.startsWith("probe"))
  {
    uint8_t err = probe(0x48);
    if(!err)
    {
      Serial.println("No error...");
    }
    else
    {
      Serial.print("\nError probing with error: ");
      Serial.println(err);
    }
  }
}

uint8_t probe(uint8_t address)
{
  Wire.beginTransmission(address);
  Wire.write(0x3E);
  Wire.write(0x01);
  Wire.write(0x00);
  uint8_t err = Wire.endTransmission(1);
  return err;
}

void i2cdetect()
{
  uint8_t first = 1, 
          last = 127;
  uint8_t i, j, address, error;

  // header
  Serial.print("   ");
  for (i = 0; i < 16; i++) {
    printf("%3x", i);
  }
  Serial.println("");

  // body
  for (j = 0; j < 8; j++) {
    _printf("%02d:", j*10);
    for (i = 0; i < 16; i++) {
      address = i + j*16;
      if (address >= first && address <= last) {
        Wire.beginTransmission(address);
        error = Wire.endTransmission();
        if (error) {
          Serial.print(" --");
        } else {
          _printf(" %02x", address);
        }
      } 
      else {
        Serial.print("   ");
      }
    }
    Serial.println("");
  }
}

void _printf(const char *format, ...)
{
  char buf[PRINTF_BUF];
  va_list ap;
  va_start(ap, format);
  vsnprintf(buf, sizeof(buf), format, ap);
  for(char *p = &buf[0]; *p; p++) // emulate cooked mode for newlines
  {
    if(*p == '\n')
      Serial.write('\r');
    Serial.write(*p);
  }
  va_end(ap);
}

 

 


This is (with different address) what Master should create:

mr4k_0-1739542590631.png

As you can see, here the example shows clock stretching - I have to work without this functionality.

The problem is, that my STM32 program is not working properly. The code for I2C slave device is provided below.
If HAL_Delay is called, the ACK is sent, but HAL_I2C_ListenCpltCallback is not called. If delay is not present the NACK is generated and Master can not proceed as desired.

Write_3E_0100_NACK1.png

This is what I can see on my oscilloscope after probe procedure is called on the master in case of no delay in the STM32 code and bellow in case when delay is present in STM32 code.

Write_3E_0100_ACK.png
But as said, if ACK is sent STM does not call HAL_I2C_ListenCpltCallback (as it should?) and in case of NACK master can't go on (in real life situation).

Here are parts of my STM32F4 code:

 

/*
 * STM32_I2C_Slave.c
 */

/* include */
#include "I2C_Slave.h"
#include "registers.h"
#include "protocol.h"
#include "stdio.h"
#include "string.h"

volatile i2c_t I2C_slave_obj;

uint8_t buff[2] = {0x00, 0x00};

uint32_t maxBusyDelay = 150;
uint32_t maxCMDDelay = 500;   // delay in MS

uint32_t lastAddrCallback = 0;
uint8_t  readRegAddress = 1;
uint8_t  allowTX = 0;
uint8_t  indexRA = 0;

void i2c_slave_clear(void){
	I2C_slave_obj.reg_address = 0;
	I2C_slave_obj.reg_addr_rcvd = 0;
	I2C_slave_obj.payload = 0;
	I2C_slave_obj.curr_idx = NONE;
}

void i2c_slave_init(I2C_HandleTypeDef *hi2c)
{
	I2C_slave_obj.i2c_handler = hi2c;
	i2c_slave_clear();
	HAL_I2C_EnableListen_IT(hi2c);
}

void i2c_slave_check_timeout(void)
{
	static int rx_busy_counter = 0;
	HAL_I2C_StateTypeDef status = HAL_OK;

	status = HAL_I2C_GetState(I2C_slave_obj.i2c_handler);
  if (status == HAL_I2C_STATE_BUSY_RX_LISTEN) rx_busy_counter++;
  else rx_busy_counter = 0;
  if (rx_busy_counter > 150)
  {
      HAL_I2C_DisableListen_IT(I2C_slave_obj.i2c_handler);
      HAL_I2C_DeInit(I2C_slave_obj.i2c_handler);
      HAL_I2C_Init(I2C_slave_obj.i2c_handler);
      HAL_I2C_EnableListen_IT(I2C_slave_obj.i2c_handler);
      rx_busy_counter = 0;
  }

  if(lastAddrCallback + maxCMDDelay > HAL_GetTick())
  {
    readRegAddress = 0;
    lastAddrCallback = 0;
    HAL_GPIO_WritePin(LD5_GPIO_Port, LD5_Pin, GPIO_PIN_RESET);
  }
  else
  {
    readRegAddress = 1;
  }
}

void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode){
  if(AddrMatchCode == I2C_slave_obj.i2c_handler->Init.OwnAddress1)
    lastAddrCallback = HAL_GetTick();

  if(TransferDirection == I2C_DIRECTION_TRANSMIT)
  {
    // MASTER / Controller wants to write
    // can write CMD or just I2CDetect probing
    if(readRegAddress)
    {
      //HAL_I2C_Slave_Receive_IT(hi2c, (uint8_t*)&I2C_slave_obj.reg_addr_rcvd, 3);
      if(HAL_I2C_Slave_Seq_Receive_IT(hi2c, (uint8_t*)&I2C_slave_obj.reg_addr_rcvd, 1, I2C_FIRST_FRAME) != HAL_OK)
      {
        HAL_I2C_ErrorCallback(hi2c);
      }
      else
      {
        HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
      }
    }
  }
  else
  {
    // MASTER / Controller wants to read
    // we will need to send appropriate data to MASTER / Controller
    HAL_I2C_Slave_Seq_Receive_IT(hi2c, buff, 2, I2C_LAST_FRAME);
    HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
  }

	HAL_I2C_EnableListen_IT(hi2c);
}

void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c){

  if(!I2C_slave_obj.reg_addr_rcvd)
  {
    // If reg_addr_rcvd = 0 its probably I2CDetect probing
    readRegAddress = 1;
  }
  else
  {
    switch(I2C_slave_obj.reg_addr_rcvd)
    {
      case 0x3E:
          //HAL_I2C_Slave_Receive_IT(hi2c, &buff[0], 2);
          if(HAL_I2C_Slave_Seq_Receive_IT(hi2c, &buff[indexRA], 1, (!indexRA) ? I2C_NEXT_FRAME : I2C_LAST_FRAME) != HAL_OK)
          {
            HAL_I2C_ErrorCallback(hi2c);
          }
          else
          {
            indexRA++;
            if(indexRA==2)
            {
              HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
            }
            HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
            // HAL_Delay(20);    
            // this delay allows to make ACK ?... but ListenCpltCallback still not called
          }
        break;
      default:
        readRegAddress = 1;
        indexRA = 0;
        lastAddrCallback = 0;
    }
  }

  if(indexRA == 2)
  {
    readRegAddress = 1;
    indexRA = 0;
    lastAddrCallback = 0;
  }

	HAL_I2C_EnableListen_IT(hi2c);
}

void HAL_I2C_AbortCpltCallback (I2C_HandleTypeDef *hi2c){
	HAL_I2C_EnableListen_IT(hi2c);
}

void HAL_I2C_ListenCpltCallback (I2C_HandleTypeDef *hi2c)
{
  HAL_GPIO_TogglePin(LD6_GPIO_Port, LD6_Pin);
  HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
  HAL_I2C_EnableListen_IT(hi2c);
}

void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c){
	I2C_slave_obj.reg_addr_rcvd = 0;
	I2C_slave_obj.reg_address = 0;

	HAL_I2C_EnableListen_IT(hi2c);
}

void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
  HAL_GPIO_WritePin(LD5_GPIO_Port, LD5_Pin, GPIO_PIN_SET);

	uint32_t error_code = HAL_I2C_GetError(hi2c);
	uint32_t BERR = (error_code & HAL_I2C_ERROR_BERR);
	uint32_t ARLO = (error_code & HAL_I2C_ERROR_ARLO);
	uint32_t AF   = (error_code & HAL_I2C_ERROR_AF);
	uint32_t OVR  = (error_code & HAL_I2C_ERROR_OVR);
	uint32_t DMAE = (error_code & HAL_I2C_ERROR_DMA);
	uint32_t TOUT = (error_code & HAL_I2C_ERROR_TIMEOUT);
	uint32_t SIZE = (error_code & HAL_I2C_ERROR_SIZE);
	uint32_t DMA  = (error_code & HAL_I2C_ERROR_DMA_PARAM);
	uint32_t STR  = (error_code & HAL_I2C_WRONG_START);

	if (BERR)
	{
	  HAL_GPIO_TogglePin(LD5_GPIO_Port, LD5_Pin);
	  HAL_I2C_DisableListen_IT(hi2c);
          HAL_I2C_DeInit(hi2c);
          HAL_I2C_Init(hi2c);
          HAL_I2C_EnableListen_IT(hi2c);
	}
	else if (AF)
	{
	  // I2CDetect was called most probably
		__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);
		readRegAddress = 1;
		lastAddrCallback = 0;
		I2C_slave_obj.reg_addr_rcvd = 0;
		memset(buff, 0, sizeof(buff));
	}
	HAL_I2C_EnableListen_IT(hi2c);
}

/////// this is from main.c
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_I2S3_Init();
  MX_USB_HOST_Init();
  MX_I2C1_Init();

  //Init i2c-slave and call HAL_I2C_EnableListen_IT inside
  i2c_slave_init(&hi2c1);

  while (1)
  {
    // check for timeouts
    i2c_slave_check_timeout();
    // all other code for I2C is handled in I2C_Slave

    MX_USB_HOST_Process();
  }
}

// and init functions for clk and i2c
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_I2C1_Init(void)
{
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 144;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_ENABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
}

 


Looks like I'm handling something wrong or some flags are not cleared as they should be... maybe __disable_irq/__enable_irq at some point... using interrupts only for setting flags and implement logic in main program... I don't know anymore :)
Also I'm not sure if HAL_I2C_EnableListen_IT should be really called in all those places.

I hope I was clear with my question - head is allover the place right now.

Basically - I would like to support i2cdetect (in a way that STM32 will not stuck) and presented command in first picture. Right now - or slave or master don't work as desired. 

Thanks for help in advance.

1 REPLY 1
TDK
Guru

Start from an example that works and use that code as a template.

STM32CubeF4/Projects/STM32F4-Discovery/Examples/I2C/I2C_TwoBoards_RestartAdvComIT/Src/main.c at 57d6d1cd0488d2572982edffc2e23b1df471374b · STMicroelectronics/STM32CubeF4

 

> HAL_I2C_DeInit(hi2c);
> HAL_I2C_Init(hi2c);

It is not good to have stuff like this in your code. If anything, it will silently hide the error happening. Solve the error instead.

If you feel a post has answered your question, please click "Accept as Solution".