How do I get the current value AC using a TIMER, PPI, SAADC and FreeRTOS on the nRF52833?

Hi all.

Could you help me with an example or some ideas about how to create an application that allows me to measure the AC current through an Analog Pin like AIN1 from the nRF52833?

I mean, I want to create an application that allows me to print, maybe using UART or J-Link RTT Viewer, the Voltage (AC) measured from AIN1. I've been checking, and perhaps I must use one algorithm like RMS, but I'm not completely positive.

Could you help me, please, with some ideas for starting?

Thanks in advance,

Regards,

Fer Mercado.

Parents Reply Children
  • Hi Hieu, thanks for the answer,

    Right now, I have developed a code that allows me to measure ADC samples, but I'm not sure if this is correct because, through these ADC samples, I get an RMS value, but I'm not very positive if this RMS value is correct. Can you help me verify if my code is okay, please.

    I'll leave my code below, whatever advice you leave, it'll be received. Thanks.

    Notes: I'm using FreeRTOS (One Task, One Queue to save the ADC samples), SAADC (enable interrupt for event END), TIMER1 (1.68 ms), PPI.

    main.c
    =========================================================================================
    int main(void){
        ret_code_t err_code;
        /* Initialize clock driver for better time accuracy in FREERTOS */
        err_code = nrf_drv_clock_init(); // If we forget to initialize the clock, the timer is not going to work
        APP_ERROR_CHECK(err_code);
    
        /* Create task for print_task_function with priority set to 2 */
        UNUSED_VARIABLE(xTaskCreate(print_task_function, "print_task_function", (configMINIMAL_STACK_SIZE + 200), NULL, 2, &print_task_handle));
        
        // Create the queue
        xQueue = xQueueCreate(QUEUE_LENGTH, sizeof(samplesADC));
        if( xQueue == NULL ){        
            /* Queue was not created and must not be used. */      
            SEGGER_RTT_printf(0, "Queue was not created\r\n");
        }         
    
        /* Activate deep sleep mode */
        SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
        
        /* Start FreeRTOS scheduler. */
        vTaskStartScheduler();
    
        while(true){
            /* FreeRTOS should not be here... FreeRTOS goes back to the start of stack
             * in vTaskStartScheduler function. */
        }
    }
    
    static void print_task_function(void *pvParameter){
        UNUSED_PARAMETER(pvParameter);  
          
        ADC_initBufferSamples(ISRsamplesValuesADC);
        ADC_InitSAADC_PPI_TIMER(ADC_read);
        
        int16_t cont_Frames = 0;
       
        // Receive data from the queue
        samplesADC samplesValuesADCfromQueue[NRF_SAADC_RESULT_MAXCNT];
    
        while(1){
            SEGGER_RTT_printf(0, "print_task_function: xQueueReceive <-- Blocking...\r\n"); 
            double alpha = 0.25;
            ADC_processADCsamples(&xQueue, samplesValuesADCfromQueue, &cont_Frames, &contadorFramesISR, ISRsamplesValuesADC, true, &alpha);     
        }
    }
    
    void SAADC_IRQHandler(void){
        if(NRF_SAADC->EVENTS_CALIBRATEDONE == 1){
            SEGGER_RTT_printf(0, "SAADC event: CALIBRATEDONE\r\n");        
        }
        /* This IRQHandler must be executed each 1.68 ms, then 1 Frame = 1.68ms */
        else if(NRF_SAADC->EVENTS_END == 1){ // EVENTS_END = The SAADC has filled up the result buffer
            
            ADC_copySamplesToBuffer(&contadorFramesISR, TOTAL_SAMPLES_EACH_CYCLE, ADC_read, ISRsamplesValuesADC, &xQueue, &xHigherPriorityTaskWoken);
            
            NRF_SAADC->EVENTS_END = 0;                                                                  
        }
        else{
            ;
        }
    }
    =========================================================================================
    adc.c
    void ADC_saadc_init(int16_t *ADC_read){
        ////////////////////////////// NRF_SAADC_CH_0
        // Configure SAADC singled-ended channel, Internal reference (0.6V) and 1/4 gain.    
        NRF_SAADC->CH[NRF_SAADC_CH_0].CONFIG = (SAADC_CH_CONFIG_RESP_Pullup     << SAADC_CH_CONFIG_RESP_Pos)   | // Pull-up to VDD
                                               (SAADC_CH_CONFIG_RESN_Pulldown   << SAADC_CH_CONFIG_RESN_Pos)   | // Pull-down to GND 
                                               (SAADC_CH_CONFIG_GAIN_Gain1_4    << SAADC_CH_CONFIG_GAIN_Pos)   | // Gain1_4                                           
                                               (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) | // Internal reference (0.6 V)
                                               (SAADC_CH_CONFIG_TACQ_40us       << SAADC_CH_CONFIG_TACQ_Pos)   | // Acquisition time, the time the SAADC uses to sample the input voltage, 40us 
                                               (SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos)   | // Single-ended, PSELN will be ignored, negative input to SAADC shorted to GND
                                               (SAADC_CH_CONFIG_BURST_Disabled  << SAADC_CH_CONFIG_BURST_Pos);   // Burst mode is disabled (normal operation)
    
        /* Configure the SAADC channel with AIN1 as positive input, no negative input(single ended). */
        // Input positive pin selection for CH[0]
        NRF_SAADC->CH[NRF_SAADC_CH_0].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput1 << SAADC_CH_PSELP_PSELP_Pos; // AnalogInput1, AIN1 (Pin P0.03) --> AC_SENSOR
    
        // Input negative pin selection for CH[0]
        NRF_SAADC->CH[NRF_SAADC_CH_0].PSELN = SAADC_CH_PSELN_PSELN_NC  << SAADC_CH_PSELN_PSELN_Pos; // NC, Not connected
    
        // Configure the SAADC resolution.
        NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_10bit << SAADC_RESOLUTION_VAL_Pos; // 10 bits
        
        // Configure result to be put in RAM at the location of "ADC_read" variable.
        // Maximum number of 16-bit samples to be written to output RAM buffer    
        NRF_SAADC->RESULT.MAXCNT = NRF_SAADC_RESULT_MAXCNT; // (RESULT EasyDMA channel)    
        //NRF_SAADC->RESULT.PTR = (uint32_t)&ADC_read; // Data pointer (2to16 = 65,536), (65,536/2 = 32,768) --> [-327687, 32767]
        NRF_SAADC->RESULT.PTR = (uint32_t)ADC_read; // Data pointer (2to16 = 65,536), (65,536/2 = 32,768) --> [-327687, 32767]
        
        // No automatic sampling, will trigger with TASKS_SAMPLE.
        NRF_SAADC->SAMPLERATE = SAADC_SAMPLERATE_MODE_Task << SAADC_SAMPLERATE_MODE_Pos; // Rate is controlled from SAMPLE task
    
        // // Enable SAADC (would capture analog pins if they were used in CH[0].PSELP)
        /* When enabled, the SAADC will acquire access to analog input pins specified in registers CH[0].PSELP and CH[0].PSELN */
        NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
        
        // INTENSET = Enable interrupt
        // Read: Enabled
        NRF_SAADC->INTENSET = SAADC_INTENSET_END_Enabled << SAADC_INTENSET_END_Pos; // Write '1' to enable interrupt for event END
        NRF_SAADC->EVENTS_END = 0;
        NVIC_EnableIRQ(SAADC_IRQn);
        
        // Calibrate the SAADC (only needs to be done once in a while)
        NRF_SAADC->EVENTS_CALIBRATEDONE = 0; // Calibration is complete, NotGenerated = 0 = Event not generated, Generated = 1 = Event generated
        NRF_SAADC->TASKS_CALIBRATEOFFSET = 1; // Starts offset auto-calibration, 1 = Trigger task
        while(NRF_SAADC->EVENTS_CALIBRATEDONE == 0)
            vTaskDelay(pdMS_TO_TICKS(1));
        NRF_SAADC->EVENTS_CALIBRATEDONE = 0;    
    
        // Ready = 0 = SAADC is ready. No on-going conversions.
        // Busy  = 1 = SAADC is busy. Conversion in progress.
        while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos)) // if status = busy
            vTaskDelay(pdMS_TO_TICKS(1));
        SEGGER_RTT_printf(0, "NRF_SAADC->STATUS = 0, Ready, No on-going conversions.\r\n");    
    }
    
    void ADC_timers_ppi_init(void){
        // Configure PPI channel with connection between (TIMER->EVENTS_COMPARE[0]) = 1 and NRF_SAADC->TASKS_SAMPLE
        // Channel "PPI_CHANNEL" event endpoint
        NRF_PPI->CH[PPI_CHANNEL].EEP = (uint32_t)&NRF_TIMER1->EVENTS_COMPARE[0];  // wire timer compare, Pointer to event register. Accepts only addresses to registers
           
        // Channel "PPI_CHANNEL" task endpoint
        // NRF_SAADC->TASKS_START -> Starts the SAADC and prepares the result buffer in RAM
        NRF_PPI->CH[PPI_CHANNEL].TEP = (uint32_t)&NRF_SAADC->TASKS_START; // Pointer to task register. Accepts only addresses to registers
    
        /* 
          Each TEP implements a fork mechanism that enables a second task to be triggered at the same time as
          the task specified in the TEP is triggered.
        */
        // Channel "PPI_CHANNEL" task endpoint
        // NRF_SAADC->TASKS_SAMPLE -> Takes one SAADC sample
        NRF_PPI->FORK[PPI_CHANNEL].TEP = (uint32_t)&NRF_SAADC->TASKS_SAMPLE; // Sub-task, Do a SAADC sample, will put the result in the configured RAM buffer.
        
        // Channel enable set register, Enable PPI channel
        //NRF_PPI->CHENSET = PPI_CHENSET_CH0_Enabled << PPI_CHENSET_CH0_Pos;
        NRF_PPI->CHENSET = PPI_CHENSET_CH0_Enabled << PPI_CHANNEL;
    }
    
    void ADC_timers_init(void){   
        NRF_TIMER1->TASKS_STOP = 1; // (@ 0x00000004) Stop Timer 
        
        // 0 = Select Timer mode, In Timer mode, the TIMER's internal Counter register is incremented by one for every tick of the timer frequency fTIMER
        // TIMER_MODE_MODE_Timer = 0UL
        NRF_TIMER1->MODE        = TIMER_MODE_MODE_Timer;
        
        // Configure the number of bits used by the TIMER, 32 bit timer bit width (3 = 0b11), 
        NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos; // Check for what it is.
    
        //NRF_TIMER1->PRESCALER   = 7; // 125KHz = 8us timer period 
        NRF_TIMER1->PRESCALER   = 4; // 1MHz = 1us timer period 
        NRF_TIMER1->TASKS_CLEAR = 1; // (@ 0x0000000C) Clear time 
        
        // When the Counter value becomes equal to the value specified in a capture compare register CC[n], the corresponding compare event COMPARE[n] is generated.
        //NRF_TIMER1->CC[0] = 210; // Compare event on CC[0] match, ---> 1,680 us = 1.68 ms compare value, generates EVENTS_COMPARE[0]
        NRF_TIMER1->CC[0] = VALUE_CAPTURE_COMPARE_REGISTER; // Compare event on CC[0] match, ---> 1,680 us = 1.68 ms compare value, generates EVENTS_COMPARE[0]
    
        // Enable shortcut, Clear the timer when COMPARE[0] event is triggered
        // Shortcut between event COMPARE[A], A = BitPos 0 = (i = 0) and task CLEAR
        NRF_TIMER1->SHORTS      = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos); // Shortcuts between local events and tasks
    
        NRF_TIMER1->TASKS_START = 1;  // (@ 0x00000000) Start Timer    
    }
    
    float ADC_convertValueRMSToValueVoltage(float rms_voltageDigital, uint8_t channel){   
          /*
          - RESULT = DIGITAL VALUE OBTAINED FROM THE SAADC
    
          - The digital output value from the SAADC is calculated using a formula.
          - RESULT = (V(P) - V(N)) * (GAIN/REFERENCE) * 2(RESOLUTION - m)     , m = 0 for single-ended channels
          - Result = [V(p) - V(n)] * GAIN/REFERENCE * 2^(RESOLUTION)
          - Result = (VDD - 0) * ((1/4) / 0.6) * (2^10)
          - Result = (VDD) * (0.4166) * (2^10)
          - Result = VDD * 426.66
          - VDD = Result / 426.7
     
          - VDD(0V)    * 426.6 = 0(Digital)
          - VDD(1V)    * 426.6 = 427(Digital)
          - VDD(2.3V)  * 426.6 = 981(Digital)
          - VDD(2.38V) * 426.6 = 1019(Digital)
          - VDD(2.39V) * 426.6 = 1022(Digital)
          - VDD(2.4V)  * 426.6 = 1023(Digital) = (2^10) - 1 = 1023
          
          - Input range (maximum and minimum ADC input) = (0.6V)/(1/4) = 2.4 [0V to 2.4V]
    
          - Gain = Input range / Vref = (2.4 / 0.6) = 4
    
          - Resolution = Vuc/2^(n-1)
          - Resolution = (2.4)/(2^(10-1)) = 0.00234375V (Es la diferencia entre cada valor Digital convertido a Voltage)
       */ 
        if(channel == NRF_SAADC_CH_0){ // AIN1 (Pin P0.03) --> AC_SENSOR
          return ((float)rms_voltageDigital / 426.7f);
       }
    }  
    
    double ADC_calculateRMS(uint8_t channel, samplesADC *adcSamples, int16_t num_samples, bool applyFilterBefore, double *alpha){
        /* Low-pass filter parameter (adjust alpha based on your requirements) */
        if(applyFilterBefore){
            if(alpha != NULL){
                ADC_applyLowPassFilter(channel, adcSamples, num_samples, *alpha);
            }
            else{
                SEGGER_RTT_printf(0, "alpha == NULL, Filter applied with alpha = [DEFAULT_ALPHA_FILTER]\r\n");
                ADC_applyLowPassFilter(channel, adcSamples, num_samples, DEFAULT_ALPHA_FILTER);            
            }   
        }    
        int16_t i;
        double sum_of_squares = 0.0;
    
        for(i = 0; i < num_samples; i++){
            sum_of_squares += adcSamples[channel].sample[i].adcValue * adcSamples[channel].sample[i].adcValue;
        }
    
        double mean_squared = sum_of_squares / num_samples;
    
        double voltage_rms = sqrt(mean_squared);
    
        return voltage_rms;
    
    }
    
    void ADC_printBufferSamples_ADC_value(uint8_t channel, samplesADC *ValuesADC){
        SEGGER_RTT_printf(0, "+++++++++++++++++++++++++++++++++\r\n");
        for(int16_t sample = 0; sample < TOTAL_SAMPLES_EACH_CYCLE; sample++){        
            SEGGER_RTT_printf(0, "sample No. [%d], Value_ADC CH_%d = [%d]\r\n", sample, channel, (int16_t)ValuesADC[channel].sample[sample].adcValue);                
        }
        SEGGER_RTT_printf(0, "+++++++++++++++++++++++++++++++++\r\n");
    }
    
    void ADC_initBufferSamples(samplesADC *samplesValuesADC){    
        for(int16_t channel = 0; channel < NRF_SAADC_RESULT_MAXCNT; channel++){
            for(int16_t sample = 0; sample < TOTAL_SAMPLES_EACH_CYCLE; sample++){
                samplesValuesADC[channel].sample[sample].adcValue = 0;
                //samplesValuesADC[channel].sample[sample].voltageValue = 0.0;
            }
        }
    }
    
    void ADC_printInDecimalVoltage(float limitVoltagePrint, float voltageValue){
        int16_t intPart = 0;
        int16_t decimalPart = 0;
    
        intPart = limitVoltagePrint;
        decimalPart = (limitVoltagePrint - intPart) * PRECISION;
        SEGGER_RTT_printf(0, "[0V to %d.%dV]: ", intPart, decimalPart);           
    
        intPart = voltageValue;
        decimalPart = (voltageValue - intPart) * PRECISION;
        SEGGER_RTT_printf(0, "CH_%d = [%d.%dV]", NRF_SAADC_CH_0, intPart, decimalPart);           
        SEGGER_RTT_printf(0, "\r\n");
    }
    
    void ADC_processADCsamples(QueueHandle_t *xQueue, samplesADC *samplesValuesADCfromQueue, int16_t *cont_Frames, 
                                  int16_t *contadorFramesISR, samplesADC *ISRsamplesValuesADC, bool applyFilterBefore, double *alpha){
        if(xQueueReceive(*xQueue, samplesValuesADCfromQueue, portMAX_DELAY) == pdPASS) {   
            if(*cont_Frames < MAX_INT16_T){                                                          
                SEGGER_RTT_printf(0, "====================================================\r\n"); 
                SEGGER_RTT_printf(0, "FRAME [%d], NUM_SAMPLES by FRAME = [%d]\r\n", *cont_Frames, TOTAL_SAMPLES_EACH_CYCLE);                 
                *cont_Frames += 1;
                // For NRF_SAADC_CH_0
    #if 0
                printBufferSamples_ADC_value(NRF_SAADC_CH_0, &samplesValuesADCfromQueue);           
    #endif                           
                    
    #if 1                         
                double rms_voltageDigital = ADC_calculateRMS(NRF_SAADC_CH_0, samplesValuesADCfromQueue, TOTAL_SAMPLES_EACH_CYCLE, applyFilterBefore, alpha);
                SEGGER_RTT_printf(0, "RMS_Voltage_Digital CH_%d = [%d]\r\n", NRF_SAADC_CH_0, (int16_t)rms_voltageDigital);
                                  
                float voltageValue = ADC_convertValueRMSToValueVoltage(rms_voltageDigital, NRF_SAADC_CH_0);
                                
                ADC_printInDecimalVoltage(2.4f, voltageValue);
                
                //float realVoltageAC = (113.2822 * voltageValue) - 10.8775;            
                float realVoltageAC = (169.70563 * voltageValue) - 10.8775;  // sqrt(2)*120     
                //float realVoltageAC = (178.19090 * voltageValue) - 10.8775;  // sqrt(2)*126     
                //float Vpico = (float)sqrt(2) * realVoltageAC;
                SEGGER_RTT_printf(0, "VALUE_AC_REAL: CH_%d = [%dV AC]\r\n", NRF_SAADC_CH_0, (int16_t)realVoltageAC);
                                
                ADC_printInDecimalVoltage(120.0f, realVoltageAC); // 126V             
    #endif 
                *contadorFramesISR = 0;
    
                ADC_initBufferSamples(ISRsamplesValuesADC);
    
                SEGGER_RTT_printf(0, "====================================================\r\n");
                vTaskDelay(pdMS_TO_TICKS(200));                
            }
            else{
                *cont_Frames = 0;
            }              
        }
    }
    
    void ADC_copySamplesToBuffer(int16_t *contadorFrames, int16_t total_samples_each_cycle, int16_t *ADC_read,
                                  samplesADC *_samplesValuesADC, QueueHandle_t *xQueue, BaseType_t *xHigherPriorityTaskWoken){
    #if (!PRINT_DEBUG_ADC)
        SEGGER_RTT_printf(0, "SAADC event: DONE\r\n");
        SEGGER_RTT_printf(0, "Sample buffer address == [%p]\r\n", ADC_read);
        SEGGER_RTT_printf(0, "Sample buffer address == [%p]\r\n", NRF_SAADC->RESULT.PTR);
        uint16_t samples_number = NRF_SAADC->RESULT.AMOUNT;
        SEGGER_RTT_printf(0, "samples_number = [%d]\r\n", (uint16_t)samples_number);     
        uint16_t max_samples_number = NRF_SAADC->RESULT.MAXCNT;
        SEGGER_RTT_printf(0, "max_samples_number = [%d]\r\n", (uint16_t)max_samples_number);
    #endif        
        //SEGGER_RTT_printf(0, "contadorFrames = [%u]\r\n", (uint16_t)*contadorFrames);
        if(*contadorFrames < total_samples_each_cycle){            
            _samplesValuesADC[NRF_SAADC_CH_0].sample[*contadorFrames].adcValue = ADC_read[NRF_SAADC_CH_0]; // AC-Sensor
            _samplesValuesADC[NRF_SAADC_CH_1].sample[*contadorFrames].adcValue = ADC_read[NRF_SAADC_CH_1];
            _samplesValuesADC[NRF_SAADC_CH_2].sample[*contadorFrames].adcValue = ADC_read[NRF_SAADC_CH_2];
            _samplesValuesADC[NRF_SAADC_CH_3].sample[*contadorFrames].adcValue = ADC_read[NRF_SAADC_CH_3];            
            *contadorFrames += 1; 
        }
        else{
            xHigherPriorityTaskWoken = pdFALSE;
                                  
            // Send data to the queue
            if(xQueueSendToFrontFromISR(*xQueue, _samplesValuesADC, xHigherPriorityTaskWoken) == pdPASS){
    #if (!PRINT_DEBUG_ADC)
                SEGGER_RTT_printf(0, "Sent form ISR\r\n");
    #endif
            }
                       
            // If the ISR unblocks a higher-priority task, request a context switch
            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
        }
    }
     

  • Hi Fermando,

    As Susheel has responded to you on the same question in your other ticket, please let me know if I could close this one.

    Regards,

    Naeem

  • Hello Naeem, yes, please.

    You can close this ticket, thank you.

    Regards.

Related