CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

ADC Conversion jamming on noisy signals.

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
ManuSisko



Joined: 17 Jul 2018
Posts: 5

View user's profile Send private message Send e-mail

ADC Conversion jamming on noisy signals.
PostPosted: Tue Jul 17, 2018 6:13 pm     Reply with quote

Hello. I've constructed a PCB for amplification and filtering from a Photopletysmograph. Basically, a Transimpedance Amp, a 1.5 kHz Band Pass Filter, an IRed Led Modulated at 1.5 kHz and a Photodiode.
I use the PIC 18f2550 to sample the signal, pack the samples and sending it to PC via USB comm. I'm using a 20 MHz crystal with PLL/5 for USB so FOsc = 48 MHz.

Everything seems to work perfectly, but I've got a problem. When the signal from the PPG, (which I take from the output of the BandPass Filter + Inverting OpAmp) gets a little noisy, the ADC takes a really long time to convert it. Now this delay is not noticeable, but it triggers a loop that crashes my device.

Here's how the sequence goes:
1. I use Timer 0 to modulate the Infra Red led at 1.5kHz. Timer0 triggers an interrupt with a 6 kHz frequency. ( Turn on Led -> Sample signal -> Turn Off Led -> Sample Signal -> ...) This ensures that the sampling is sync'ed with the modulation.
2. Timer0 also packs the readings (in my code I read a second signal from another channel but I've disabled it while I fix this problem), for the USB buffer.
3. I have a Qt App in my PC that receives the data, plots the signal, and shows how many samples were received in the last second (i.e. the interruption frequency of T0).
4. Main code sends the packets when they are full and Timer1 handles commands from the PC.

I've configured my ADC like this, ensuring max frequency:
setup_adc(ADC_CLOCK_DIV_64|ADC_TAD_MUL_4);
setup_adc_ports(AN0_TO_AN9|VREF_VDD);
set_adc_channel(PhPin);

Suppose the signal gets a little noisy (abrupt peaks for example), then the Timer0 delays (the AD conversion "hangs" for some reason) to modulate the IR Led, the signal from the PPG is no longer modulated at 1.5kHz, thus the BandPass filter distorts the signal (removing components), and the signal becomes more noisy, resulting in a greater delay in the timer 0 interrupt, which leads to more noise, and so on. The only thing that could delay the T0 Interrupt is the AD Conversion.

When this happends, my PC App receives less and less packets down to a sampling frequency of 200 Hz (from 1470 under normal conditions). I have no clue why this "peaks" in the signal are causing the Timer0/ADC to delay. Any and all suggestions would be appreciated. Below is my code:
USB_main.h
Code:

#include <18F2550.h>
#device ADC=8

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES WDT128                   //Watch Dog Timer uses 1:128 Postscale
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOMCLR                   //Master Clear pin used for I/O
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST                  //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES VREGEN
#FUSES HSPLL
#FUSES CPUDIV1
#FUSES PLL5
#FUSES USBDIV

#use delay(clock=48000000)
//#use FIXED_IO( C_outputs=PIN_C1,PIN_C2 )
#use pwm(CCP1,TIMER=2,FREQUENCY=1500Hz,DUTY=96)

#bit SPEN = 0xFAB.7
#byte TRISC = 0xF94
#bit TC7 = 0xF94.7
#bit TC6 = 0xF94.6
#define USB_USE_FULL_SPEED TRUE
#define USB_HID_DEVICE TRUE
#define USB_CONFIG_HID_TX_SIZE   64
#define USB_CONFIG_HID_RX_SIZE   4
#define TX_SIZE USB_CONFIG_HID_TX_SIZE
#define RX_SIZE USB_CONFIG_HID_RX_SIZE
#define USB_CONFIG_PID 0x01FF                         //Chnage Product Id
#define USB_CONFIG_VID 0x04D8             //Chnage Vendor Id
#ifndef __EX_USB_COMMON_H__
#define __EX_USB_COMMON_H__
#define MAX_PACKET_SIZE 61

#define COM_NO_STATE 0x11
#define COM_EXE_DONE 0x07

#define CMD_T0_ON    0x37
#define CMD_T0_OFF   0x2A
#define CMD_CLR_TIME 0x17
#define CMD_TOG_PUMP 0x1A
#define CMD_TOG_VALV 0x1B

#define PUMP PIN_C7
#define VALVE PIN_C6
#define LED PIN_C1
#define IRLED PIN_B4
#define DELAY 350
#define sampleSize 10
#define PhPin  8
#define PrPin  9
volatile unsigned char sendData[TX_SIZE], recData[RX_SIZE], cmdBuff[sampleSize];
volatile int8 adcData[2], lastPhOffset,prSignal[sampleSize], phSignal[sampleSize],timeL = 0x00,timeH = 0x00;
volatile int8 currentPacket = 4, cmdsLeft = 0, totalCMDCount = 0;
volatile int1 irState[2] = {0,0}, sendPacket = 0;





main.c
Code:
#include <USB_main.h>

#include <math.h>
#include <usb_desc_hid.h>
#include <pic18_usb.h>
#include <usb.c>


#INT_RTCC
void t0_int(void){
   clear_interrupt(INT_TIMER0);
   if(!irState[0]){
      irState[0] = 1;
      if(!irState[1]){
         OUTPUT_LOW(IRLED);
         irState[1] = 1;
         //adcData[1] = read_adc();
      }else{
         OUTPUT_HIGH(IRLED);
         irState[1] = 0;
      }
      set_adc_channel(PhPin);
   }else{
      irState[0] = 0;
      adcData[0] = read_adc();
      set_adc_channel(PrPin);
      /*delay_us(10);
      adcData[1] = read_adc();
      set_adc_channel(PhPin);
      */if(irState[1]){
         timeL += 1;
         if(timeL== 0x00){
            timeH += 1;
         }
         if(currentPacket == 0){
            currentPacket = 4;
         }
         unsigned char lastPhSignal = phSignal[0];
         phSignal[0] = adcData[0] - lastPhOffset;
         prSignal[0] = adcData[1];
         if(currentPacket < MAX_PACKET_SIZE /*&& lastPhSignal != phSignal[0]*/){
            sendData[currentPacket] = timeH;
            sendData[currentPacket+1] = timeL;
            sendData[currentPacket+2] = phSignal[0];
            sendData[currentPacket+3] = prSignal[0];
            currentPacket += 4;
         }
         if(currentPacket >= MAX_PACKET_SIZE){
            sendPacket = 1;
         }
      }else{
         lastPhOffset = 0;//adcData;
      }
   }

}

#INT_TIMER1
void t1_int(void){
   clear_interrupt(INT_TIMER1);
   //pwm_set_duty_percent(1,98);
   while(cmdsLeft != 0){
      int cmdToExe = cmdBuff[0];
      switch(cmdToExe){
         case CMD_CLR_TIME:
            timeH = 0;
            timeL = 0;
            totalCMDCount += CMD_CLR_TIME;
            break;
         case CMD_T0_ON:
            enable_interrupts(INT_TIMER0);
            totalCMDCount += CMD_T0_ON;
            break;
         case CMD_T0_OFF:
            disable_interrupts(INT_TIMER0);
            totalCMDCount += CMD_T0_OFF;
            break;
         case CMD_TOG_PUMP:
            output_toggle(PUMP);
            totalCMDCount += CMD_TOG_PUMP;
            break;
         case CMD_TOG_VALV:
            output_toggle(VALVE);
            totalCMDCount += CMD_TOG_VALV;
            break;
           
      }
      int i;
      cmdBuff[0] = 0;
      for (i=0;cmdBuff[i+1]!=0;i++){
         cmdBuff[i] = cmdBuff[i+1];
         cmdBuff[i+1] = 0;
      }
      cmdsLeft--;
      sendData[0] = COM_EXE_DONE;
      if(sendData[1] = CMD_CLR_TIME || sendData[1] == CMD_T0_OFF){
         sendPacket = 1;
         int n;
         for(n=currentPacket ; n<64 ; n++){
            sendData[n] = 0;
         }
     
      }
   }
   
}

void main()
{
   setup_adc(ADC_CLOCK_DIV_64|ADC_TAD_MUL_4);
   setup_adc_ports(AN0_TO_AN9|VREF_VDD);
   set_adc_channel(PhPin);
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_8|RTCC_8_BIT); //Overflow evert 21.333*RTCC_PRESCALER usec      //51,2 us overflow
   setup_timer_1(T1_INTERNAL|T1_DIV_BY_1);
   enable_interrupts(GLOBAL);
   enable_interrupts(PERIPH);
   disable_interrupts(INT_TIMER1);
   disable_interrupts(INT_TIMER0);
   disable_interrupts(INT_AD);
   disable_interrupts(INT_CCP2);
   setup_ccp1(CCP_PWM);
   SPEN = 0;
   OUTPUT_HIGH(LED);
   OUTPUT_LOW(PIN_C2);
   OUTPUT_LOW(PUMP);
   OUTPUT_LOW(VALVE);
   //setup_ccp2(CCP_PWM);
   pwm_on();
   usb_init_cs();
   //Example blinking LED program
   while(true)
   {
      usb_task();
      if(usb_enumerated()){
         if(usb_kbhit(1)){
            usb_get_packet(1, recData, USB_CONFIG_HID_RX_SIZE); 
            int i;;
            cmdsLeft = 0;              //REMEMBER TO DELETE THIS ON TIME FOR COMMANDS LINE WORKING
            for(i=0;recData[i] != 0x00;i++){
               cmdBuff[cmdsLeft] = recData[i];
               recData[i] = 0;
               cmdsLeft++;
            }
            enable_interrupts(INT_TIMER1);
            delay_us(3);
            //usb_put_packet(1, recData, USB_CONFIG_HID_TX_SIZE, USB_DTS_TOGGLE);
         }
         if(sendPacket){
            disable_interrupts(GLOBAL);
            if(sendData[0] == 0x00){
               sendData[0] = COM_NO_STATE;
            }else if(sendData[0] == COM_EXE_DONE){
               sendData[1] = totalCMDCount;
               totalCMDCount = 0;
            }
            usb_put_packet(1, sendData, USB_CONFIG_HID_TX_SIZE, USB_DTS_TOGGLE);
            currentPacket = 4;
            int i;
            for(i=0;i<64;i++){
               sendData[i] = 0x00;
            }
            sendPacket = 0;
            enable_interrupts(GLOBAL);
         }
         
      }
   }
Ttelmah



Joined: 11 Mar 2010
Posts: 19537

View user's profile Send private message

PostPosted: Tue Jul 17, 2018 11:58 pm     Reply with quote

The ADC only allows it's Vin, to go between it's reference voltages. In your case Vdd and an external Vref. The signal needs to be limited externally to this range. You need to think about your external amplifier design and how to ensure the signal never goes outside this range...
If the signal goes above Vref, the input stage gets reverse biased.

Separately, re-think your RTCC loop.
Currently you have huge delays for the ADC in this loop. Delays for Tacq, and then the actual conversion. Ouch. Especially considering how quickly this loop is called, re-think and sequence the operations. You have a partial state machine, turn it into a complete one. Turn off Tacq.
Then sequentially on the interrupts:
State 0 select an ADC channel
State 1 start an ADC conversion (don't read)
State 2 read the result
State 3 select next channel
etc. etc..
This way the conversion and acquisition is done in the background, between interrupts, not inside the interrupt.

As a general comment, you are wasting an instruction clearing the interrupts in the handlers. The compiler automatically clears interrupts unless you use the instruction 'NOCLEAR'.

Then as another general comment, this:
Code:

         for(n=currentPacket ; n<64 ; n++){
            sendData[n] = 0;
         }

is slower than just using memset.
temtronic



Joined: 01 Jul 2010
Posts: 9241
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Wed Jul 18, 2018 5:03 am     Reply with quote

Just a comment about the hardware.

You should measure the IR sensor/filter-amp power supply. It needs to be decoupled and very stable. This holds true for the PIC VDD as well. ANY minor fluctuation in VDD can and will affect your project. As well ensure good (wide) traces for grounds and use lots of bypass caps.
Hopefully you have access to an oscilloscope so you can actually see the signals, a DMM typically samples at 3Hz, far too slow to see noise.
ManuSisko



Joined: 17 Jul 2018
Posts: 5

View user's profile Send private message Send e-mail

PostPosted: Wed Jul 18, 2018 8:00 am     Reply with quote

Ttelmah wrote:
The ADC only allows it's Vin, to go between it's reference voltages. In your case Vdd and an external Vref. The signal needs to be limited externally to this range. You need to think about your external amplifier design and how to ensure the signal never goes outside this range...
If the signal goes above Vref, the input stage gets reverse biased.

Separately, re-think your RTCC loop.
Currently you have huge delays for the ADC in this loop. Delays for Tacq, and then the actual conversion. Ouch. Especially considering how quickly this loop is called, re-think and sequence the operations. You have a partial state machine, turn it into a complete one. Turn off Tacq.
Then sequentially on the interrupts:
State 0 select an ADC channel
State 1 start an ADC conversion (don't read)
State 2 read the result
State 3 select next channel
etc. etc..
This way the conversion and acquisition is done in the background, between interrupts, not inside the interrupt.

As a general comment, you are wasting an instruction clearing the interrupts in the handlers. The compiler automatically clears interrupts unless you use the instruction 'NOCLEAR'.

Then as another general comment, this:
Code:

         for(n=currentPacket ; n<64 ; n++){
            sendData[n] = 0;
         }

is slower than just using memset.


Your reply has been really helpful, I had missed the problem completely. My Amp circuit is actually powered by a 6 Vdc supply (sensor require higher voltage). So I'll implement a clamper with 2 Schottky diodes and a Resistor, and I'll test it to see the results. Your other suggestions have been insightful as well. Thanks a lot!

Regarding your suggestion about turning into a full state machine. If i follow the sequence you suggested I'd need 12 states (2 signals, one sampled twice per cycle). Is there any problem with doing:
State X: Read ADC result + change channel <- in order to reduce states?

temtronic wrote:
Just a comment about the hardware.

You should measure the IR sensor/filter-amp power supply. It needs to be decoupled and very stable. This holds true for the PIC VDD as well. ANY minor fluctuation in VDD can and will affect your project. As well ensure good (wide) traces for grounds and use lots of bypass caps.
Hopefully you have access to an oscilloscope so you can actually see the signals,, a DMM typically samples at 3Hz, far too slow to see noise.


Thanks for your suggestion, the PIC supply comes from a phone charger + the USB supply when it's connected, and I've placed several bypass caps to ensure it's stable enough. But the Amp/Filters PCB is powered by a 6V battery (4 x 1.5V AA batteries). I assumed this was stable enough but I'll add several bypass caps and will check the supply as soon as I get access to an oscilloscope.
ManuSisko



Joined: 17 Jul 2018
Posts: 5

View user's profile Send private message Send e-mail

PostPosted: Wed Jul 18, 2018 9:32 am     Reply with quote

Ttelmah wrote:
Then as another general comment, this:
Code:

         for(n=currentPacket ; n<64 ; n++){
            sendData[n] = 0;
         }

is slower than just using memset.


Also, would the correct memset statement be:
Code:
memset(&sendData[currentPacket], 0x00, 63);

or:
Code:
memset(sendData[currentPacket], 0x00, 63);


both compile but I'm not sure which one could be correct.
Ttelmah



Joined: 11 Mar 2010
Posts: 19537

View user's profile Send private message

PostPosted: Wed Jul 18, 2018 10:26 am     Reply with quote

You wouldn't use current_packet at all.

memset(sendData, 0x00, 64);

You want to zero 64 bytes not 63. In C, the name of an array is the address of it's first element. So 'sendData' is equivalent to '&sendData[0]'.
ManuSisko



Joined: 17 Jul 2018
Posts: 5

View user's profile Send private message Send e-mail

PostPosted: Wed Jul 18, 2018 10:29 am     Reply with quote

Ttelmah wrote:
You wouldn't use current_packet at all.

memset(sendData, 0x00, 64);

You want to zero 64 bytes not 63. In C, the name of an array is the address of it's first element. So 'sendData' is equivalent to '&sendData[0]'.


Yes, but in that specific case I want to erase only the last segment of the buffer, so I need to send to give an offset into sendData[currentPacket] cause previous packets I want to keep.

EDIT: Both options seem to work fine. Also, I've implemented the full state configuration, and everything is running smoothly (no longer causing my timer0 to stop modulation). I've tested moving the Vref around and as Ttelmah pointed out, this was the cause for my problem. I'll implement the clipping circuit next. Thanks a lot for the help!
Here's the my code for the full state machine, in case it's ever helpful to anyone.
Code:
#INT_RTCC
void t0_int(void){
   switch (sampState){
      case 0:
         OUTPUT_HIGH(IRLED);
         if (adc_done()) read_adc(ADC_START_ONLY);
         sampState++;
         break;
      case 1:
         if (adc_done()) adcData[1] = read_adc(ADC_READ_ONLY);
         set_adc_channel(PhPin);
         sampState++;
         break;
      case 2:
         if (adc_done()) read_adc(ADC_START_ONLY);
         sampState++;
         break;
      case 3:
         if (adc_done()) lastPhOffset = read_adc(ADC_READ_ONLY);
         sampState++;
         break;
      case 4:
         OUTPUT_LOW(IRLED);
         sampState++;
         break;
      case 5:
         if (adc_done()) read_adc(ADC_START_ONLY);
         sampState++;
         break;
      case 6:
         if (adc_done()) adcData[0] = read_adc(ADC_READ_ONLY);
         sampState++;
         break;
      case 7:
         set_adc_channel(PrPin);
         packSamples(FALSE);
         sampState = 0;
         break;
   }
}
Ttelmah



Joined: 11 Mar 2010
Posts: 19537

View user's profile Send private message

PostPosted: Wed Jul 18, 2018 12:30 pm     Reply with quote

You shouldn't need to test adc_done. So long as the delay between interrupts is longer than the slowest ADC operation. Smile

On the buffer yes you can start at the offset point, using &sendData[currentPacket], but in this case you would have to use:

memset(&sendData[currentPacket], 0x00, TX_SIZE-currentPacket);

Your existing code will be clearing bytes in memory beyond the end of the buffer. Think about it, you clear 64 locations, if you start anywhere beyond zero, you will be clearing things you shouldn't....
ManuSisko



Joined: 17 Jul 2018
Posts: 5

View user's profile Send private message Send e-mail

PostPosted: Wed Jul 18, 2018 12:39 pm     Reply with quote

Yes I don't technically need to check, but as I haven't implemented the voltage clipper yet, I use it to prevent my PIC from hanging when the signal goes outside the range.

Yes I forgot to mention that change haha. I used:
memset(sendData[currentPacket], 0x00, 64 - currentPacket);

Thanks mate!
Ttelmah



Joined: 11 Mar 2010
Posts: 19537

View user's profile Send private message

PostPosted: Fri Jul 20, 2018 11:28 am     Reply with quote

There is an interesting erratum on some PIC's, which results in the GO/DONE bit not correctly being set when the conversion completes. This is not listed for your PIC, but a couple of people have experienced it in 'random' circumstances, possibly relating to the nature of the input signal, on other PIC's. It sounds like this is what is happening to you.
One solution is to test the INT_AD bit rather than the GO/DONE bit to test for the end of conversion. The other is to not use the bit but just allow enough time. Doing the conversions using the longer times involved in your interrupt ought to fix this, if this is what is happening on your chip.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group