Two years ago I blogged about the PIC18F4550 oscilloscope using the 'hid_custom' from the
Microchip Library for Applications (MLA). A few weeks ago I decided to try different USB transfer modes. In this blog I will use the 'cdc_basic' example of the MLA. It uses USB Bulk transfer to transfer data from the controller to the PC. Let'see if we can build a simple oscilloscope.
I changed the cdc_basic example. I added two command:
- start capturing adc
- stop capturing adc on AN0
For capturing adc values on AN0 I use two interrupts:
- timer1 interrupt for starting ADC capture (TMR1IF)
- ADC ready interrupt (ADIF)
The captured values are stored in a buffer or actually two buffers each 640 bytes. I use a write index, a read index and a adc counter.
When writing data, the adc counter is increased, when reading (=sending data to PC) the adc counter is decreased. On overruns, the buffer the indices and counter are reset.
The code below shows the initialization of the ADC module.
void InitializeADC()
{
adc_counter = 0;
writeIndex =0;
readIndex =0;
isLowBufferRead = true;
isLowBufferWrite = true;
/*
1. Configure the A/D module:
• Configure analog pins, voltage reference and digital I/O (ADCON1)
• Select A/D input channel (ADCON0)
• Select A/D acquisition time (ADCON2)
• Select A/D conversion clock (ADCON2)
• Turn on A/D module (ADCON0)
2. Configure A/D interrupt (if desired):
• Clear ADIF bit
• Set ADIE bit
• Set GIE bit
3. Wait the required acquisition time (if required).
4. Start conversion:
• Set GO/DONE bit (ADCON0 register)
5. Wait for A/D conversion to complete, by either:
• Polling for the GO/DONE bit to be cleared
OR
• Waiting for the A/D interrupt
6. Read A/D Result registers (ADRESH:ADRESL);
• clear bit ADIF, if required.
7. For next conversion, go to step 1 or step 2, as required.
The A/D conversion time per bit is defined as T AD.
• A minimum wait of 3 T AD is required before the next acquisition starts.
*/
PIR1bits.ADIF = 0x00;
PIE1bits.ADIE = 0x00;
IPR1bits.ADIP = 0x00;
TRISAbits.TRISA0 = 1;
ADCON0 = 1;
ADCON1 = 0x0E; // AN0 is analog input
ADCON2bits.ADFM = 1; // ADFM : right justified
ADCON2bits.ADCS2 =1; // 0b110; // 48Mhz => Tad = 64Tosc => ADCS2...0 = 110b;
ADCON2bits.ADCS1 =1; // 0b110; // 48Mhz => Tad = 64Tosc => ADCS2...0 = 110b;
ADCON2bits.ADCS0 =0; // 0b110; // 48Mhz => Tad = 64Tosc => ADCS2...0 = 110b;
ADCON2bits.ACQT2 =1; // 0b101; 8 T AD
ADCON2bits.ACQT1 =0; // 0b101; 8 T AD
ADCON2bits.ACQT0 =0; // 0b101; 8 T AD
ADCON0bits.ADON = 1; // Turn on A/D module (ADCON0)
PIR1bits.ADIF = 0; //Clear ADIF bit
PIE1bits.ADIE = 1; //Set ADIE bit
INTCONbits.GIEH = 1; // Set GIE bit
ADCON0bits.GO = 1;
}
Below you can see the adjusted APP_DeviceCDCBasicDemoTasks which transfers the data to the PC.
void APP_DeviceCDCBasicDemoTasks()
{
/* Make sure that the CDC driver is ready for a transmission. */
if(mUSBUSARTIsTxTrfReady() == true)
{
int bytesToRead = adc_counter;
uint_fast16_t i=0;
if(bytesToRead > adc_buffer_size+adc_buffer_size)
{
readIndex=0;
writeIndex =0;
adc_counter=0;
}
else if(bytesToRead>0 && isRunning)
{
if(readIndex == adc_buffer_size)
{
readIndex =0;
isLowBufferRead = !isLowBufferRead;
}
uint8_t* pBuffer;
if(isLowBufferRead)
{
pBuffer= &adcBuffer[readIndex];
}
else
{
pBuffer= &adcBuffer2[readIndex];
}
if(readIndex + bytesToRead < adc_buffer_size)
{
putUSBUSART(pBuffer, bytesToRead);
readIndex+= bytesToRead;
}
else
{
bytesToRead = adc_buffer_size - readIndex;
if(bytesToRead >0)
{
putUSBUSART(pBuffer, bytesToRead);
}
readIndex=0;
}
adc_counter -= bytesToRead;
}
}
...
}
I adjusted the 'Dynamic CDC Demo' c++ code as such that it sends the corresponding commands 1=start adc and 0=stop adc. It reads the adc bulkdata and stores it in a 1900 bytes buffer and displays the data graphically. You can scale and offset on x-axis. Below you can see the captured output of a 555 timer in astable mode: C1= 100nF, R1 = 10k and R2 = 10k. With 10 procent tolerance the frequency is between 397.521 and 593.828Hz, but approximately/ideally 481 Hz.
The average is 21515.04 ADC/second. Tsample = 0.046479201 msec. As you can see it has 44 points for one cycle = 2.045084825 msec. Which is a frequency of 488.98 Hz. Not bad I'd say
Compared to the previous USB transfer mode (polling) the real-time capturing has been improved a lot: 20k+ samples/second instead of 500 samples/second. Which means an improvment of 4000% or a factor of 40!
The sources of the PIC18F4550, the hex file and the windows Oscilloscope program can be downloaded here: