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.
adc_counter = 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
• 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.
/* Make sure that the CDC driver is ready for a transmission. */
if(mUSBUSARTIsTxTrfReady() == true)
int bytesToRead = adc_counter;
if(bytesToRead > adc_buffer_size+adc_buffer_size)
else if(bytesToRead>0 && isRunning)
if(readIndex == adc_buffer_size)
isLowBufferRead = !isLowBufferRead;
if(readIndex + bytesToRead < adc_buffer_size)
bytesToRead = adc_buffer_size - readIndex;
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: