cancel
Showing results for 
Search instead for 
Did you mean: 

problem reading data from SCD30 with NUCLEO-L476RG

JustSomeGuy
Senior

I am having issues with reading data from the SCD30 CO2 sensor. every byte of data I get back is 0xDD (or 221). I am still learning so my methods may be questionable. If anyone can help narrow down why this is happen and how to fix it, I would be very grateful. here is my code. 

 

 

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c3;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */
static const uint16_t SCD30_ADDR = 0x61 << 1; // Use 8-bit address
static const uint16_t CMD_READ_MEASUREMENT = 0x0300;
static const uint16_t CMD_GET_DATA_READY_STATUS = 0x0202;
static const uint16_t CMD_START_CONTINUOUS_MEASUREMENT = 0x0010;
static const uint16_t CMD_SET_MEASUREMENT_INTERVAL = 0x4600;
static const uint16_t CMD_STOP_CONTINUOUS_MEASUREMENT = 0x0104;

uint8_t Buffer[25] = {0};
uint8_t measurementData[18] = {0};
uint8_t newLine[] = "\r\n";
uint8_t tempMSG[] = "Temperature: ";
uint8_t humiMSG[] = "Humidity:    ";
uint8_t buf[12] = {0};
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_I2C3_Init(void);
/* USER CODE BEGIN PFP */
void convertUint16_tToUint8_tArray(uint16_t inputData, uint8_t *storageLocation);
void clearArray(uint8_t *storageLocation);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
	  int16_t val;
	  float temp_c;

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_I2C3_Init();
  /* USER CODE BEGIN 2 */
  //start continuous measurement mode.
  //@todo: crc function for write commands
  convertUint16_tToUint8_tArray(CMD_START_CONTINUOUS_MEASUREMENT, buf);
  while(HAL_OK != HAL_I2C_Master_Transmit(&hi2c3, SCD30_ADDR, buf, sizeof(buf), HAL_MAX_DELAY));
  memset(buf,0,sizeof(buf));
  //while(HAL_OK != HAL_I2C_Master_Transmit(&hi2c3, SCD30_ADDR, buf, sizeof(buf), HAL_MAX_DELAY));
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  //condition the data to be sent to the device. the function also clears the array, so no need to use memset()
	  convertUint16_tToUint8_tArray(CMD_GET_DATA_READY_STATUS, buf);
	    // Tell SCD30 that we want to read from the temperature register

	    if(HAL_OK == HAL_I2C_Master_Transmit(&hi2c3, SCD30_ADDR, buf, sizeof(buf), HAL_MAX_DELAY))
	    {
	    	//clear buffer
	        memset(buf,0,sizeof(buf));
	        //get data. in this case, whether or not the sensor is ready to send data
	    	HAL_I2C_Master_Receive(&hi2c3, SCD30_ADDR, buf, sizeof(buf), HAL_MAX_DELAY);
	        //sensor returns a '1' on the second byte if it is ready
	        if(buf[1])
	        {
		    	convertUint16_tToUint8_tArray(CMD_READ_MEASUREMENT, buf);
			    if(HAL_OK == HAL_I2C_Master_Transmit(&hi2c3, SCD30_ADDR, buf, sizeof(buf), HAL_MAX_DELAY))
			    {
			    	HAL_I2C_Master_Receive(&hi2c3, SCD30_ADDR, measurementData, sizeof(measurementData), HAL_MAX_DELAY);
			    }
	        }
	    }
        HAL_UART_Transmit(&huart2, measurementData, sizeof(measurementData), 10000);
        HAL_UART_Transmit(&huart2, newLine, sizeof(newLine), 10000);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 10;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  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_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

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

/**
  * @brief I2C3 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_I2C3_Init(void)
{

  /* USER CODE BEGIN I2C3_Init 0 */

  /* USER CODE END I2C3_Init 0 */

  /* USER CODE BEGIN I2C3_Init 1 */

  /* USER CODE END I2C3_Init 1 */
  hi2c3.Instance = I2C3;
  hi2c3.Init.Timing = 0x30408CFF;
  hi2c3.Init.OwnAddress1 = 0;
  hi2c3.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c3.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c3.Init.OwnAddress2 = 0;
  hi2c3.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c3.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c3.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c3) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Analogue filter
  */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c3, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Digital filter
  */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c3, 0) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C3_Init 2 */

  /* USER CODE END I2C3_Init 2 */

}

/**
  * @brief USART2 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : B1_Pin */
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LD2_Pin */
  GPIO_InitStruct.Pin = LD2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */
/*convert uint16 into a uint8 array. bytes are stored in the order of MSB to LSB.
 * ex: uint16_t input = 0x1234;
 * uint8_t output[25] = {0x4,0x3,0x2,0x1,0x0,...,0x0}
 */
void convertUint16_tToUint8_tArray(uint16_t inputData, uint8_t *storageLocation)
{
     int i = 0,
         j = 0;
     uint8_t temp;

     clearArray(storageLocation);

     //write data into the array
     for(i = 0; i < sizeof(inputData)*2; i++)
    	 storageLocation[i] = (inputData >> (4*i)) % (sizeof(inputData)*8);


     //reverse the data in the array. comment out depending on the endianness of the application.
     for(i = 0, j = (sizeof(inputData)*2)-1; i < j ;i++, j-- )
     {
       temp = storageLocation[i];
       storageLocation[i] = storageLocation[j];
       storageLocation[j] = temp;
     }
}

void clearArray(uint8_t *storageLocation)
{
	int i = 0;
	for(i = 0; i < sizeof(storageLocation)*8; i++) storageLocation[i] = 0;
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @PAram  file: pointer to the source file name
  * @PAram  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

 

 

9 REPLIES 9

Link to the protocol document. Sending 12 bytes seems a bit odd. IMHO

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

here is the link to the sensor and here is the link to the protocol document. I found another person's example code and took the 12 byte buffer from that. if you look on page 8 of the linked SCD30 document, using the HAL i2c function, I would be sending 5 bytes of data, CRC included. I think that's the part that's messing me up. I haven't been able to reproduce the example CRC with any calculator or example code and this MCU does not have integrated CRC functionality.

So the sensor supports I2C, MODBUS, and PWM - are you sure you have it in the correct mode?

The transmit is only 11 bytes of data: the I2C Address is specified separately - not included in the buffer:

AndrewNeil_0-1713976379713.png

 

For receiving, it's (up to) 9 bytes of data:

AndrewNeil_1-1713976502467.png

 

The sensor datasheet shows example scope traces - compare them to what you're seeing.

 


@JustSomeGuy wrote:

 I haven't been able to reproduce the example CRC with any calculator or example code.


Not even the sample code given by Sensirion on their Product Page for this sensor?

https://sensirion.com/products/catalog/SCD30/

https://github.com/Sensirion/embedded-scd/releases/tag/2.1.1

 

I'm familiar with I2C protocol, used to work for Philips Semiconductor..

The protocol for the sensor doesn't send 12-bytes in any circumstances.

It sends a 16-bit command, and then multiples of 3-byte parameters, ie 16-bit data + 8-bit crc.

So 2, 5, 8, 11,.. etc bytes per interaction. Didn't look at the Read methods

The CRC is described, and can be computed in SW, with low effort.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

Oh dang I didn't even see those. I'll give it a shot!

I see. so how would i integrate that with the HAL for i2c? would I send the 3-byte parameters as a separate write command?

yeah i've been following along with those since the beginning. the thing i'm caught up on is how to format the data the right way and send it with the HAL drivers. as for the mode, is the default mode not i2c? I assumed so because on the inferface description, it only says how to set it to Modbus or PWM mode.

As I read it a singular 5-byte write transaction.

 

// CRC8-31 for SCD30
//  sourcer32@gmail.com 24-Apr-2024
// Now accepting coffee  https://buymeacoffee.com/cliveone

// https://sensirion.com/media/documents/D7CEEF4A/6165372F/Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;

uint8_t test_crc8_31(uint8_t crc, uint8_t data)
{
  int i;

  crc = crc ^ data;

  for(i=0; i<8; i++)
    if (crc & 0x80)
      crc = (crc << 1) ^ 0x31;
    else
      crc <<= 1;

  return(crc);
}

uint8_t test_crc8_31_quick(uint8_t crc, uint8_t data)
{
  static const uint8_t crctbl[] = { // 0x31 nibble table - sourcer32@gmail.com
    0x00,0x31,0x62,0x53,0xC4,0xF5,0xA6,0x97,0xB9,0x88,0xDB,0xEA,0x7D,0x4C,0x1F,0x2E };

  crc = crc ^ data;

  crc = (crc << 4) ^ crctbl[crc >> 4]; // Process byte 4-bits at a time
  crc = (crc << 4) ^ crctbl[crc >> 4];

  return(crc);
}

int main(int argc, char **argv)
{
  uint8_t crc;

  crc = 0xFF; // pattern BE EF [92]

  crc = test_crc8_31(crc, 0xBE);
  crc = test_crc8_31(crc, 0xEF);

  printf("%02X == 92 ??\n", crc);

  crc = 0xFF; // pattern BE EF [92]

  crc = test_crc8_31_quick(crc, 0xBE);
  crc = test_crc8_31_quick(crc, 0xEF);

  printf("%02X == 92 ??\n", crc);

  return(1);
}

 

 

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..