Go Down

Topic: Reading from a image sensor onto a PC (Read 2230 times) previous topic - next topic

Amrit_Patnaik_148

May 17, 2018, 07:09 pm Last Edit: May 18, 2018, 10:37 am by Amrit_Patnaik_148
Hello World,
This is my first post on the forum. I am a largely self taught arduino hobbyist and much of my knowledge regarding this device has come from extensively going through the Due forums and from the atmel manual of course. With that said, let me present my problems below and the ask for the clarifications that I seek.

I am trying to read the data from a linear image sensor chip rigged from a bar code scanner, which has 3694 elements/pixels in it. The data from this chip is read out every 15 milliseconds. I am able to successfully read the data on a borrowed oscilloscope. I am now looking to eliminate the oscilloscope completely from the setup and read the data from the Due itself, since this can obviously do the job. For this, I need to initialize an ADC channel, save the data in a DMA buffer, and transmit it to the PC from the Arduino due (relatively straightforward). My code for the same looks something like this -
Code: [Select]


volatile int bufn,obufn;
const uint16_t bufsize=4096;
uint16_t buf[4][bufsize];   // Size of buffer must be a power of 2


void startA2DConvTimer(uint32_t ADCPulseWidth,uint32_t ADCdelay) //low going pulse
//this function is responsible for flushing out data from linear image sensor after every ADCdelay
{
 
  REG_PMC_PCER0 |= PMC_PCER0_PID28;                 
  REG_PIOA_ABSR |= PIO_ABSR_P2;                 
  REG_PIOA_PDR |= PIO_PDR_P2;                     
   
  REG_TC0_CMR1 = TC_CMR_ACPC_SET |               
                 TC_CMR_ACPA_CLEAR |               
                 TC_CMR_WAVE |                   
                 TC_CMR_WAVSEL_UP_RC |         
                 TC_CMR_TCCLKS_TIMER_CLOCK1;       

  REG_TC0_RA1 = ADCPulseWidth;                             
  REG_TC0_RC1 = ADCdelay;                             
 
  NVIC_SetPriority(TC1_IRQn, 0);   
  NVIC_EnableIRQ(TC1_IRQn);         // Connect TC1 to Nested Vector Interrupt Controller (NVIC)
  REG_TC0_IER1 |= TC_IER_CPCS;      // Enable interrupts for TC1 (TC0 Channel 1)
  REG_TC0_CCR1 = TC_CCR_SWTRG | TC_CCR_CLKEN;       // Enable the timer TC1
  REG_TC0_BCR=1;
}

void setup()
{
  startA2DConvTimer(252,504000);
  SerialUSB.begin(250000);
  while(!SerialUSB);
  pinMode(LED_BUILTIN, OUTPUT);
}

void TC1_Handler()                // ISR TC1 (TC0 Channel 1)
{
adc_setup ();
TC0->TC_CHANNEL[1].TC_SR;  // Clear status register, how to write this in REG form??!!
}

void adc_setup() {

   REG_PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power ON
   REG_ADC_CR = ADC_CR_SWRST;                           // Reset ADC
   REG_ADC_MR |=  ADC_MR_TRGEN_DIS |
                  ADC_MR_FREERUN_ON |
                  ADC_MR_PRESCAL(1) |
                  ADC_MR_LOWRES_BITS_12 ;  // ADC in free running mode
   REG_ADC_CHDR = 0xFFFF ;      // disable all channels
   REG_ADC_CHER = ADC_CHER_CH0;         // enable just pin A7
   REG_ADC_CGR = 0x15555555 ;   // All gains set to x1
   REG_ADC_COR = 0x00000000 ;   // All offsets off
   REG_ADC_IER |= ADC_IER_ENDRX ;

   NVIC_EnableIRQ(ADC_IRQn);               // Enable ADC interrupt


  REG_ADC_RPR = (uint32_t)buf[0];        // DMA buffer
  REG_ADC_RCR = bufsize;
  REG_ADC_RNPR = (uint32_t)buf[1];       // next DMA buffer
  REG_ADC_RNCR = bufsize;
  bufn = obufn = 1;
  REG_ADC_PTCR |= ADC_PTCR_RXTEN;        // Enable PDC receiver channel request
  REG_ADC_CR = ADC_CR_START;
}

void ADC_Handler() {                // move DMA pointer to next buffer

  if ( REG_ADC_ISR & ADC_ISR_ENDRX) {
    bufn = (bufn + 1) & 3;
    REG_ADC_RNPR = (uint32_t)buf[bufn];
    REG_ADC_RNCR = bufsize;
    REG_ADC_CHDR = 0xFFFF ;      // disable all channels until the adc_setup() is called again
  }
}


void loop()
{
  while (obufn == bufn); // wait for buffer to be full
  SerialUSB.write((uint8_t *)buf[obufn], 512); // send it - 512 bytes = 256 uint16_t
  obufn = (obufn + 1) & 3;
}





From the above code, I wish to enable the ADC only when the data starts flushing out of the image sensor and then save it in the DMA buffer. Once the buffer is full and transmitted to the PC, the ADC interrupt is fired which disables the ADC. It will be renabled again by virtue of the timer function, when the counter hits the RC1 value. With that said, here are my queries/doubts-
1.How do I read the data in the PC?! The Arduino serial monitor and serial plotter are only reading noise. Is it due to the errors in the code or due to the baudrate not matching? (while monitoring the data, I am reading or rather trying to read from the Native USB port on the appropriate COM port) Is there an easier way/alternative software package to monitor the data arriving at the COM port apart from python? I do not have access to matlab or labview, and therefore a simpler, easier to grasp open source alternative to python would be helpful. Further the serial monitor would only plot the value it receives vs time. However, my aim is to have a plot between pixel number j on x axis and pixel intensity buf(j) along y axis. Can anybody suggest an alternative?

2.As an aside, the ADC code section here was copied from https://gist.github.com/pklaus/5921022. The data I am reading is from a sensor 3694 elements long. I have set bufsize here to be 4096, since it is a power of 2, since I remember reading it in one of the posts on timer triggered ADC conversions. I expect that after 3694 conversions are done the rest of the 402 elements should be filled with 0 or something close to that. Can I set this bufsize to be an arbitrary number like 3694? If not, what is the logic behind setting the bufsize to a value that is a power of 2? Also, in both cases can someone tell me, what are the changes, if any, that I need to make in the SerialUSB.write argument of the loop section, since the bufsize will not be 256 anymore?

3.This is a major confusion which I haven't been able to trouble shoot. As you can see my timer interrupt is called each time RC1 hits 504000. This is a very important step since I want the A to D Conversion to start once the timer is set, and hence the adc setup function is called each time the counter hits this RC value and ushers in the flushing out of the analog data from the sensor chip. The reason I am calling up adc setup over and over again is because I want the ADC to be active only when the analog data from the chip is flushed in to the A7 pin. Once the digital data is transmitted to the PC, I am disabling the A7 pin. What I want to know is if this is a good practice since I have seen numerous posts which have suggested that the handler function should be kept as short as possible. If this is not a good thing to do, can anyone suggest something more clean?

4.Another clarification as to how the ADC works (and I ask this at the risk of sounding like a fool!). I assume that the output data rate from my linear array sensor is 250 KHz. I assume that each signal from a pixel of the sensor thus serially arrives at the ADC pin after an interval of (1/(250KHz)) = 4 microseconds. In the free running mode with prescal set to 1, I presume that the analog data is converted to digital data in about 1 microsecond (say). When I am working with the DMA buffers, is the EOC bit supposed to be cleared after every conversion by writing some code or does initialising the buffer automatically ensure that this digital data is placed in the buffer post conversion and this EOC bit is cleared on its own which now makes the ADC pin ready for the next signal? This is something intricate that I cant figure out from this code. Further, if the whole process of data arrival, conversion and housing in the buffer takes 2 microseconds (say), does the ADC remain idle until the next analog signal arrives? Or does it see the absence of any signal as a corresponding "Low" signal and convert it to 0? I will be really indebted to anyone who can clear this funda for me! This again would also tie in with the question raised in 2. If after 3694 elements, no further signal arrives from the chip, how will my buffer be filled now?

Thanks in advance for your patience in reading my question! Any leads/help will be solicited!

ard_newbie


I am trying to read the data from a linear image sensor chip rigged from a bar code scanner, which has 3800 elements/pixels in it.

Post a link to the datasheet of your analog sensor.


BTW, more than 10 questions inside a single post may be a bit too much, don't you think ?

Amrit_Patnaik_148

Hello ard_newbie! Thank you for your interest in this post. Yes 10 questions in a single post is definitely an overkill but I really wanted all of these points to be covered in a single thread instead of starting multiple threads or going to multiple threads and posting there!
As for the chip, this is a TCD1304 AP chip that I rigged from the bar code scanner. The data sheet is found here-
http://www.spectralproducts.com/pdf/TCD1304.pdf
The exact total elements coming out of the chip are 3694 and not 3800 as I had erroneously mentioned.

ard_newbie

#3
May 18, 2018, 01:56 pm Last Edit: May 18, 2018, 02:00 pm by ard_newbie
In " Optical Spectrometer using a  Linear CCD image sensor "

https://www.cezarchirila.com/documents/SSET_2017_Cezar_Chirila.pdf

You will find a tutorial to get started with the TC1340 sensor. They are using a slower ARM Cortex M3 than the Sam3x (DUE), hence there is no real issue to read this sensor with a DUE.

Page 3 of this tutorial:

The OS (output) signal has a frequency of ϕM/4 = 250KHz, meaning that for every 4 periods of master clock (1 MHz), the value of a new pixel is shifted to the output signal, until either a new ICG pulse commences or all 3694 pixels have been in rotation, including the dummy ones. Note that Master frequency could be either 0.8 MHz.

You should first focus on the first part of the project, reading TC1340 properly once. The output of results should be addressed later.

You need to program and synchronize several timers, at least 4, you need to start ADC samplings, OS, when ICG rises up with a frequency of 250 KHz, and finally you need to log ADC samplings (it's a good idea to use a PDC DMA to free the uc core for the printing process in step 2).

From the above thoughs, it's obvious that you can't use the Free Running mode for ADC samplings. Here is an example sketch to sample at 250 KHz and log results with a PDC DMA:

Code: [Select]

/**************************************************************************/
/*                       ADC sampling frequency  = 250 KHz                */
/**************************************************************************/

volatile int bufn, obufn;
const uint16_t bufsize = 256;
uint16_t buf[4][bufsize];  

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  adc_setup();
  tc_setup();
}

void loop()
{

}

/*************  Configure ADC function  *******************/
void adc_setup() {

  PMC->PMC_PCER1 |= PMC_PCER1_PID37;                    // ADC power on
  ADC->ADC_CR = ADC_CR_SWRST;                           // Reset ADC
  ADC->ADC_MR |=  ADC_MR_TRGEN_EN                       // Hardware trigger select
                  | ADC_MR_TRGSEL_ADC_TRIG3;            // Trigger by TIOA2

  ADC->ADC_CHER = ADC_CHER_CH7;                        // Enable ADC CH7 = A0

  ADC->ADC_IER |= ADC_IER_ENDRX ;

  NVIC_EnableIRQ(ADC_IRQn);               // Enable ADC interrupt

  /*************      PDC/DMA  buffer filling *******************/

  ADC->ADC_RPR = (uint32_t)buf[0];        // DMA buffer
  ADC->ADC_RCR = bufsize;
  ADC->ADC_RNPR = (uint32_t)buf[1];       // next DMA buffer
  ADC->ADC_RNCR = bufsize;
  bufn = obufn = 1;
  ADC->ADC_PTCR |= ADC_PTCR_RXTEN;        // Enable PDC receiver channel request
  ADC->ADC_CR = ADC_CR_START;

}

void ADC_Handler() {                // move DMA pointer to next buffer
  

 // if ( ADC->ADC_ISR & ADC_ISR_ENDRX) { // Useless since this is the only one triggered
    bufn = (bufn + 1) & 3;
    ADC->ADC_RNPR = (uint32_t)buf[bufn];
    ADC->ADC_RNCR = bufsize;
 // }

  // For debugging only
  static uint32_t Count;
  if (Count++ ==  976) { // 250000/256 ~976
    Count = 0;
    PIOB->PIO_ODSR ^= PIO_ODSR_P27;  // Toggle LED_BUILTIN every 1 Hz
  }
}


/*************  Timer Counter 0 Channel 2 to generate PWM pulses thru TIOA2  ************/
void tc_setup() {

  PMC->PMC_PCER0 |= PMC_PCER0_PID29;                      // TC2 power ON : Timer Counter 0 channel 2 IS TC2
  TC0->TC_CHANNEL[2].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC       // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR         // Clear TIOA2 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA2 on RC compare match


  TC0->TC_CHANNEL[2].TC_RC = 168;  //<*********************  Frequency = (Mck/2)/TC_RC  Hz
  TC0->TC_CHANNEL[2].TC_RA = 40;  //<********************   Any Duty cycle in between 1 and TC_RC

  TC0->TC_CHANNEL[2].TC_IER = TC_IER_CPCS;
  NVIC_EnableIRQ(TC2_IRQn);
  TC0->TC_CHANNEL[2].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC2 counter and enable

}

void TC2_Handler() {
  static uint32_t Count;
  TC0->TC_CHANNEL[2].TC_SR;
  /*
    if (Count++ == 250000) {
      PIOB->PIO_ODSR ^= PIO_ODSR_P27;
      Count = 0;
    }
  */
}



Program each of the 4 timers you need as per Figure 2, page 3, and log ADC samplings until everything is correct.

Go Up