cancel
Showing results for 
Search instead for 
Did you mean: 

Reading a square signal with ADC values

gioyik
Associate II

Hello, I am working on a project which receives an analog square signal and the idea is to read the ADC values, and try to recreate (based on the ADC values) the square signal inside the microcontroller, but not to output in another pin, instead to detect/understand (with a threshold) what ADC values refer to the low and high positions of the square wave being feed and decode a message if any. To note, the square wave low means 0 and high means 1.

I have tried with single (stm32f4 series) and double (stm32l4 series) ADC, using DMA for both cases, but in both scenarios I get ADC values I can't make sense to recreate the frequency of the input signal and recreate any encoded message. I am able to identify minimum and maximum values, but the result is a wave which doesn't get similar to the one feed as input. I am attaching readings values from the ADC.

Has someone tried to do this? I feel I am missing something, maybe the use of a clock or my ADC configuration is wrong, the sampling. My last attempt is with a stm32l4 series microcontroller, DMA and using FreeRTOS.

This is the configuration code to configuring two ADC to work together:

/**
  * @brief ADC1 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_MultiModeTypeDef multimode = {0};
  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */

  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure the ADC multi-mode
  */
  multimode.Mode = ADC_DUALMODE_INTERL;
  multimode.DMAAccessMode = ADC_DMAACCESSMODE_12_10_BITS;
  multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_5CYCLES;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}

/**
  * @brief ADC2 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_ADC2_Init(void)
{

  /* USER CODE BEGIN ADC2_Init 0 */

  /* USER CODE END ADC2_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */

  /** Common config
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc2.Init.LowPowerAutoWait = DISABLE;
  hadc2.Init.ContinuousConvMode = ENABLE;
  hadc2.Init.NbrOfConversion = 1;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.DMAContinuousRequests = ENABLE;
  hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc2.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */

  /* USER CODE END ADC2_Init 2 */

}

 The callback functions:

/**
  * @brief  Conversion complete callback in non blocking mode
  * @PAram  AdcHandle : ADC handle
  * @note   This example shows a simple way to report end of conversion
  *         and get conversion result. You can add your own implementation.
  * @note   When ADC_TRIGGER_FROM_TIMER is disabled, conversions are software-triggered
  *         and are too fast for DMA post-processing. Therefore, to reduce the computational 
  *         load, the output buffer filled up by the DMA is post-processed only when 
  *         ADC_TRIGGER_FROM_TIMER is enabled.
  * @retval None
  */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
  uint32_t tmp_index = 0;
  
  /* For the purpose of this example, dispatch dual conversion values         */
  /* into 2 arrays corresponding to each ADC conversion values.               */
  for (tmp_index = (ADC_BUFFER_SIZE/2); tmp_index < ADC_BUFFER_SIZE; tmp_index++)
  {
    aADCxConvertedValues[tmp_index] = (uint16_t) GET_ADC1_RESULT(aADCDualConvertedValues[tmp_index]);
    aADCyConvertedValues[tmp_index] = (uint16_t) GET_ADC2_RESULT(aADCDualConvertedValues[tmp_index]);
  }

  /* Reset variable to report DMA transfer status to main program */
  ubADCDualConversionComplete = SET;


}

/**
  * @brief  Conversion DMA half-transfer callback in non blocking mode 
  * @PAram  hadc: ADC handle
  * @note   When ADC_TRIGGER_FROM_TIMER is disabled, conversions are software-triggered
  *         and are too fast for DMA post-processing. Therefore, to reduce the computational 
  *         load, the output buffer filled up by the DMA is post-processed only when 
  *         ADC_TRIGGER_FROM_TIMER is enabled.
  * @retval None
  */
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
  uint32_t tmp_index = 0;
  
  /* For the purpose of this example, dispatch dual conversion values         */
  /* into 2 arrays corresponding to each ADC conversion values.               */
  for (tmp_index = 0; tmp_index < (ADC_BUFFER_SIZE/2); tmp_index++)
  {
    aADCxConvertedValues[tmp_index] = (uint16_t) GET_ADC1_RESULT(aADCDualConvertedValues[tmp_index]);
    aADCyConvertedValues[tmp_index] = (uint16_t) GET_ADC2_RESULT(aADCDualConvertedValues[tmp_index]);
  }

  /* Reset variable to report DMA transfer status to main program */
  ubADCDualConversionComplete = RESET;
}

 The FreeRTOS task:

  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    if (ubADCDualConversionComplete == RESET) {
      for (uint32_t i = 0; i < 128; i++)
      {
        // Average just to test if I get a better reconstruction
        uint16_t averageRead = (aADCxConvertedValues[i] + aADCyConvertedValues[i]) / 2;
        printf("%u\r\n", averageRead);
        if (averageRead >= 500) {
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
        } else {
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
        }
      }
    }
  }
  // In case we accidentally exit from task loop
  osThreadTerminate(NULL);
  /* USER CODE END 5 */

 

5 REPLIES 5
SofLit
ST Employee

Hello and welcome to the community,


@gioyik wrote:

the square wave low means 0 and high means 1.

 


What do you mean by 0 and 1? voltage levels 0 and 1V?


@gioyik wrote:

I get ADC values I can't make sense to recreate the frequency of the input signal and recreate any encoded message. 


Your samples have two values ~1490 and ~149. So the square is there.

But what is the frequency of your signal input and what is the sampling frequency of the ADC?

The Nyquist-Shannon sampling theorem states that to accurately reconstruct a signal, it must be sampled at a rate at least twice its highest frequency.

 

To give better visibility on the answered topics, please click on "Accept as Solution" on the reply which solved your issue or answered your question.


What do you mean by 0 and 1? voltage levels 0 and 1V?


0 and 3.3V

I am using an external pyboard to generate the square wave signal, I am attaching the code here for reference.

 

from pyb import Pin
import time

# Initialize the pin
p = Pin('X1', Pin.OUT_PP)  # X1 as a general output pin

bit_sequence = [1,1,1,0,1,0]  # Example sequence

# Define the bit duration in microseconds:
# 1 = 1 Mbps
# 2 = 500kbps
# 4 = 250kbps
# 8 = 125kbps
bit_duration_us = 2

# Infinite loop to transmit the sequence repeatedly
while True:
    # Loop through the bit sequence and modulate
    for bit in bit_sequence:
        if bit == 1:
            # Transmit the carrier wave for the duration of the bit
            p.high()
            time.sleep_us(bit_duration_us)
        else:
            # No signal for the duration of the bit (carrier off)
            p.low()
            time.sleep_us(bit_duration_us)


Your samples have two values ~1490 and ~149. So the square is there.


I have done another run feeding the pyboard square signal directly to the ADC pin, and now I am getting values from 0 up to ~4079. I am attaching another dump file with values. Yes, the square is kinda there, my problem to recreate or detect the square is the sampling numbers for "each square" is not consistent or I get zeros in between or small high values in betten those zeros. This is a small piece of the dump as example, but you can give it a check directly to the whole dump:

0
0
0
2043
4076
4066
4082
4084
4087
1
1
4
0
4080
4074
4074
4080
4075
4083
4076
2038
4089
0
1
2034
1
4090
4084
4078
4085
4080
4078
0
0
0
7
4076
4077
4087
4074
4087
4067
2041
0
0
0
4089
4082
4088
4083
4086
4083
2043
0
0
4076
4073
0
4090
4082
4090
4076
4091
4082
4080
0
0
1
0

Maybe I am having noise and is the reason why the values are inconsistent or break unexpectedly? I have tried to compensate for this "noise" or antipattern by updating the code in the task to:

  uint8_t bit_converted = 0;
  uint8_t high_sample_counter = 0;
  uint8_t low_sample_counter = 0;

  /* Infinite loop */
  for(;;)
  {
    if (ubADCDualConversionComplete == RESET) {
      for (uint32_t i = 0; i < 128; i++)
      {
        uint16_t average_read = (aADCxConvertedValues[i] + aADCyConvertedValues[i]) / 2;
        //printf("%u\r\n", average_read);
        if (average_read >= 4000) {
          high_sample_counter++;
          low_sample_counter = 0;
          if (high_sample_counter == 3) {
            printf("1");
            high_sample_counter = 0;
            bit_converted = 1;
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
          }
        } else {
          low_sample_counter++;
          high_sample_counter = 0;
          if (bit_converted == 1 && low_sample_counter >= 3) {
            printf("0");
            low_sample_counter = 0;
            bit_converted = 0;
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET);
          }
        }
      }
    }
  }
  // In case we accidentally exit from task loop
  osThreadTerminate(NULL);

with this code, I am trying to match a minimal number of values to decide if is HIGH (1) or LOW (0). The result is, I am able to recreate a few occurrences of the input signal (111010) but there are plenty other values which do not correspond to the input.


But what is the frequency of your signal input and what is the sampling frequency of the ADC?


currently the input frequency is 500kbps, the ADC is configured to 12bit resolution, sampling time of 247.5 cycles. In the first post you can see the settings for both ADC. The clock is set to 80MHz.

Screenshot 2024-09-10 at 14.36.13.png

 

ok, something I have found, the `printf` over UART might be influencing the final wave, as you can see, I am making one GPIO pin LOW and HIGH to check on the oscilloscope, when I am using printf statements I get some more defined squares as signal, without printf I get more uneven square shape signals in the output.

currently the input frequency is 500kbps,


This is not a frequency but a bitrate. As I said you need to adapt your ADC sampling frequency according to your signal input.

 

To give better visibility on the answered topics, please click on "Accept as Solution" on the reply which solved your issue or answered your question.

ok, I think I have nailed the ADC parameters with the signal and I can see better output results in the oscilloscope. I see the frequency on the pyboard to 500kHz and the ADC is configured to the max sampling frequency with two ADC. However, I see some gaps, see the screenshots. The green one is the input signal and the yellow is the signal I generate based on the ADC values.

 

In this one, the generated signal is a bit shifted/delayed compared to the input signal, but it matches most of the time, ignoring the wide one on the left side which I don't know what happened.

image_1.jpg

If I zoom out the gaps a more dramatic:

image_2.jpgimage.jpg

Is it possible to know what could be causing these, or how could they be corrected?

 

I removed any possible interrupt or print (even with DMA) to avoid any problem in the signal generation. I found my printf with DMA was causing the output signal to malform and not reflect the ADC values correctly.