0
\$\begingroup\$

Documentation on the 10-bit ADC gives numerous samples. But none of these samples explain in clear what happens and what should the software do once the DONE bit is set.

Let's consider the section 17.16.1 Sampling and Converting a Single Channel Multiple Times. It is the most simple. Documentation gives a very nice chart:

enter image description here

So, what happens after the DONE and ADIF signals are set? It is to the right, outside of this chart.

  1. Is it right understanding that module with continue its work filling and refilling the buffer (assuming that software will not do anything)?
  2. Will the DONE signal be reset shortly after it was set, when the 17-th conversion will start?
  3. If the answer to the first question is true, this means that software has very short time (maybe just a few instruction depending on the system clock speed) when the value in the ADCBUF0 will be overwritten and lost. Is that correct understanding?

How should I modify the setup described in this section if I want ADC to grab 16 samples and stop? After that I will process the interrupt and start a new ADC session later when it will be the right time to do that.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You did not mentioned the exact dsPIC model so I am assuming it is dsPIC30-series and the documentation you are referring to is this one: http://ww1.microchip.com/downloads/en/devicedoc/70064e.pdf

Your assumptions are correct. I don't think there's a way to setup this ADC to automatically stop after certain number of sample / conversions. However, you can manually disable the whole module in the ISR occurring after 16th conversion. This will abort 17th sample / conversion and ADCBUF0 will not be overwritten. You can then later re-enable the ADC when you have processed the buffer.

volatile uint8_t newValues = 0;
void __attribute__((interrupt, no_auto_psv)) _ADC1Interrupt(void)
{
    AD1CON1bits.ADON = 0;   // this must be executed before the end of 17th conversion
    IFS0bits.AD1IF = 0;
    newValues = 1;
}

int main(void)
{
    AD1CON1bits.SSRC = 0b111;   // auto-convert
    AD1CON1bits.ASAM = 1;       // auto-sample    
    AD1CON2bits.SMPI = (16-1);  // interrupt after 16th sample/conversion
    AD1CON3bits.SAMC = 31;      // sample time
    AD1CON3bits.ADCS = 0b111111;// clock setup
    IFS0bits.AD1IF = 0;     // clear ADC int. flag
    IEC0bits.AD1IE = 1;     // enable ADC int.
    AD1CON1bits.ADON = 1;   // enable ADC -> starts sequence of 16 sample/conversions
    while(1)
    {   
        if(newValues)
        {
            newValues = 0;
            // read and process ADCBUF0...F  
            AD1CON1bits.ADON = 1;   // start next sequence
        }
    }
    return 0;
}

However, I think a better way is to utilize alternating buffer filling mode, described in chapter 17.14.3. In this mode, interrupt will occur twice per full buffer update. Software should have plenty of time (8 * sample / conversion time) to copy ADCBUF values to some other buffer for later processing.

#define ADC_RINGBUF_SIZE     32     // must be multiple of 8
volatile uint16_t adcRingBuf[ADC_RINGBUF_SIZE];
volatile uint8_t adcRingBufWr, adcRingBufRd;
void __attribute__((interrupt, no_auto_psv)) _ADC1Interrupt(void)
{
    IFS0bits.AD1IF = 0;
    if(adcRingBufWr >= ADC_RINGBUF_SIZE)
    {
        adcRingBufWr = 0;
    }
    if(AD1CON2bits.BUFS)
    {
        adcRingBuf[adcRingBufWr++] = ADCBUF0;
        adcRingBuf[adcRingBufWr++] = ADCBUF1;
        adcRingBuf[adcRingBufWr++] = ADCBUF2;
        adcRingBuf[adcRingBufWr++] = ADCBUF3;
        adcRingBuf[adcRingBufWr++] = ADCBUF4;
        adcRingBuf[adcRingBufWr++] = ADCBUF5;
        adcRingBuf[adcRingBufWr++] = ADCBUF6;
        adcRingBuf[adcRingBufWr++] = ADCBUF7;
    }
    else
    {
        adcRingBuf[adcRingBufWr++] = ADCBUF8;
        adcRingBuf[adcRingBufWr++] = ADCBUF9;
        adcRingBuf[adcRingBufWr++] = ADCBUFA;
        adcRingBuf[adcRingBufWr++] = ADCBUFB;
        adcRingBuf[adcRingBufWr++] = ADCBUFC;
        adcRingBuf[adcRingBufWr++] = ADCBUFD;
        adcRingBuf[adcRingBufWr++] = ADCBUFE;
        adcRingBuf[adcRingBufWr++] = ADCBUFF;
    }
}

int main(void)
{
    AD1CON1bits.SSRC = 0b111;   // auto-convert
    AD1CON1bits.ASAM = 1;       // auto-sample    
    AD1CON2bits.SMPI = (8-1);   // interrupt after 8th sample/conversion
    AD1CON2bits.BUFM = 1;       // alternate buffer half on each interrupt
    AD1CON3bits.SAMC = 31;      // sample time
    AD1CON3bits.ADCS = 0b111111;// clock setup
    IFS0bits.AD1IF = 0;     // clear ADC int. flag
    IEC0bits.AD1IE = 1;     // enable ADC int.
    AD1CON1bits.ADON = 1;   // enable ADC
    while(1)
    {   
        if(adcRingBufWr > adcRingBufRd)
        {
            if((adcRingBufWr - adcRingBufRd) >= 16) // how many values we want to process at a time
            {
                // process adcRingBuf[adcRingBufRd] ... adcRingBuf[adcRingBufRd+15]
                adcRingBufRd += 16;
            }
        }
        else if(adcRingBufWr < adcRingBufRd)
        {
            if(adcRingBufRd >= ADC_RINGBUF_SIZE)
            {
                adcRingBufRd = 0;
            }
        }
    }
    return 0;
}

Note: This is very naive ring buffer implementation (just to show idea).

\$\endgroup\$
5
  • \$\begingroup\$ Thanks for your answer. It is kind of surprising that with so many registers and flags there is no mode where it grabs samples from a set of inputs and stops. ISR will help, but it brings into the game a lot of additional conditions. There can be ton of situations when other devices are working and require attention right at this point. \$\endgroup\$ May 6, 2019 at 1:06
  • \$\begingroup\$ It is possible to stop ADC automatically for one channel or several S/H channels in SIMSAM mode: put no ASAM, set SAMP on from software. After making one sampling session ADC will stop. But for scanning inputs (CSCNA mode) to my understanding ASAM is unavoidable. And this standing ASAM will cause continuation of work after reaching DONE if there is no software intervention. Do you agree with this? \$\endgroup\$ May 6, 2019 at 1:08
  • \$\begingroup\$ Regarding the BUFM mode, your comment is right but it has nothing to do with the original question. Question was not on getting help on basic programming. It was on what is not written in the docs. You maybe remove the second part of your answer. \$\endgroup\$ May 6, 2019 at 1:11
  • \$\begingroup\$ I tried variant where I clear ADON in the ISR and poll DONE bit in the main thread. This looks to work fine. This way I am avoiding the newValues flag. \$\endgroup\$ May 6, 2019 at 2:15
  • \$\begingroup\$ Yes, when SAMP = 1 and ASAM = 0 (with any other parameters), it will make only one sample. You can avoid ISR with BUFM mode, because you don't need to react very fast, unlike with the first example. Just poll AD1IF and do the same what is done in the ISR in my example. \$\endgroup\$
    – user930473
    May 6, 2019 at 18:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.