Skip to main content
Visitor II
September 9, 2024
Question

Reading a square signal with ADC values

  • September 9, 2024
  • 1 reply
  • 2605 views

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 */

 

    This topic has been closed for replies.

    1 reply

    Technical Moderator
    September 9, 2024

    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.

     

    gioyikAuthor
    Visitor II
    September 10, 2024


    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

     

    gioyikAuthor
    Visitor II
    September 10, 2024

    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.