Using ADC with more than 2 channels

If you have a simple question and just want an answer.
hybridalienrob
Member
Posts: 11
Joined: Thu Nov 20, 2014 9:19 pm

Using ADC with more than 2 channels

Post by hybridalienrob »

Does anyone have any working example code showing how to use the ADC with more than 2 channels ?

Ultimately, I need to convert 7 channels every 100us. I'm using lib_a_series_support, but I can't figure out the correct sequencing of calls to work around the 5 word buffering limitation mentioned in the api header and here http://www.xcore.com/forum/viewtopic.ph ... &hilit=ADC

/**
 * Minimum guaranteed buffer depth. Exceeding this number may cause lock-up on read_packet.
 * Use multiple read commands for more than 5 enabled ADC channels
 */
#define XS1_MAX_SAMPLES_PER_PACKET 5
 
I can get it to work fine at samples_per_packet=2 and bits_per_sample = 16 or bits_per_sample = 8 and samples_per_packet = 4 but samples_per_packet =4 and bits_per_sample=16 causes the core to hang in inuchar.
 
I've tried converting 4x 16bit channels by calling at_adc_trigger_packet twice with samples_per_packet=2 but then the channel sequencing messes up and I no longer get the correct channels in order (after capturing last 50 readings from each channel - can see my "known channel voltage" jumping between channel buffers. This doesn't occur with the configurations that get all channels transferred in one packet. 
 
Any ideas ? Here's the essense of the code that attempts to read 4 channels as 2 packets of 2 samples:-
 
enum {
    numAdcChannels = 4
};
 
#define SMP_PER_PACKET  2
#define ADC_NUM_PACKETS 2
// moving average filter
#define ADC_MA_LENGTH 50
#define XMOS_ADC_TRIGGER_PERIOD_MS 5
#define ADC_PERIOD (XMOS_ADC_TRIGGER_PERIOD_MS * 100000 / ADC_MA_LENGTH)                  //Comvert to timer cycles
  
#define ADC_TRIG_MASK   0x04
out port trigger_port =  on tile[0] : XS1_PORT_4B;
 
 
[[combinable]]
void adc_task(
        chanend c_adc,             /* chan to the analog tile ADC */
        streaming chanend c_out  )      /* output to controller task, passes scan events */
{
    uint32_t adc_samps[numAdcChannels] = {0, 0, 0, 0 }; //The samples (7 lots of 32 bits (12 bit msb justified)
    int32_t adc_time;                   //Used to time periodic triggers
    timer t_adc_timer;                  //Timer for periodic ADC trigger
    uint32_t curr_ch = 0;
    at_adc_config_t adc_config = {{ 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0, 0 }; //initialise all ADC to off
    for (uint16_t ch=0; ch < numAdcChannels; ch++)
    {
        adc_config.input_enable[ch] = 1; // Enable input
    }
 
    adc_config.bits_per_sample = ADC_16_BPS;    // Samples will be placed in the MSB 12 bits of the half wor
    adc_config.samples_per_packet = SMP_PER_PACKET; // 2x16-bit words per packet
    adc_config.calibration_mode = 0; //Normal ADC operation - disable self calibration
 
    at_adc_enable(adc_tile, c_adc, trigger_port, ADC_TRIG_MASK, adc_config);
    
    t_adc_timer :> adc_time;         //Set ADC timer for first loop tick
    adc_time += ADC_PERIOD;
 
    for (int pkt=0; pkt < ADC_NUM_PACKETS; pkt++)
    {
        at_adc_trigger_packet(trigger_port, ADC_TRIG_MASK, adc_config); //Fire the ADC!
    }
 
    while(1)
    {
        select
        {
        case t_adc_timer when timerafter(adc_time) :> void:
            for (int pkt=0; pkt < ADC_NUM_PACKETS; pkt++)
            {
                at_adc_trigger_packet(trigger_port, ADC_TRIG_MASK, adc_config);    //Trigger 1st ADC conversion
            }
           adc_time += ADC_PERIOD;                             //Setup time for next ADC rx event
           break;
       case at_adc_read_packet(c_adc, adc_config, &adc_samps[curr_ch]): //if data ready to be read from ADC
          curr_ch += SMP_PER_PACKET;
          if ( curr_ch == numAdcChannels)
          {
              curr_ch = 0;
              // parse voltages
              handle_adc_data( adc_samps, c_out );
          }
          break;
    }//select
  }//while 1
 
}
 

 

hybridalienrob
Member
Posts: 11
Joined: Thu Nov 20, 2014 9:19 pm

Post by hybridalienrob »

I figured it out & am including the solution here for anyone else who has problems with this. The answer was to NOT use a timer to trigger the ADC, but instead trigger the next conversion after each read...

 


[[combinable]]
void adc_task(
        chanend c_adc,             /* chan to the analog tile ADC */
        streaming chanend c_out  )      /* output to controller task, passes scan events */
{
    uint32_t adc_samps[numAdcChannels]; //The samples (7 lots of 32 bits (12 bit msb justified)
    uint32_t curr_ch = 0;
    uint32_t adc_read_word;

    at_adc_config_t adc_config = {{ 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0, 0 }; //initialise all ADC to off
    for (uint16_t ch=0; ch < numAdcChannels; ch++)
    {
        adc_config.input_enable[ch] = 1; // Enable input
    }

    adc_config.bits_per_sample = ADC_16_BPS;    // Samples will be placed in the MSB 12 bits of the half wor
    adc_config.samples_per_packet = SMP_PER_PACKET; // 1x16-bit word per packet
    adc_config.calibration_mode = 0; //Normal ADC operation - disable self calibration

    at_adc_enable(adc_tile, c_adc, trigger_port, ADC_TRIG_MASK, adc_config);
    memset(adcHistory, 0, numAdcChannels*ADC_MA_LENGTH*sizeof(uint16_t));

    // trigger the first channel conversion
    at_adc_trigger(trigger_port, ADC_TRIG_MASK);

    while(1)
    {
        select
        {
        case at_adc_read(c_adc, adc_config, adc_read_word):  // reading from next channel is available
            adc_samps[ curr_ch++ ] = adc_read_word;
            if ( curr_ch == numAdcChannels)
            {
                curr_ch = 0;
                // parse new set of voltages
                handle_adc_data( adc_samps, c_out );
            }
            // trigger next ch conversion
            at_adc_trigger(trigger_port, ADC_TRIG_MASK);
            break;
        }//select
    }//while 1
}