PIC18F4550 USB Scope speed limits
In my previous blog, I wrote about the PIC18F4550 as USB scope. One of the concerns I had was the speed limit: I noticed that that the fastest measurement takes about 2 milliseconds.
  • Which means that the highest sampling rate is 500 Hz
  • the maximum signal frequency you can capture is 250 Hz
This was not what I was hoping for. So I dug into the documentation of both USB and PIC18F4550. According to the USB spec, the following theoritical speeds are available:
  • USB 1.1 low speed: 1.5 Mbit/s
  • USB 1.1 full speed: 12 Mbit/s
  • USB 2.0 high speed: 480 Mbit/s
  • USB 3.0 superspeed: 5 Gbit/s
The PIC18F4550 supports both low speed and full speed USB. So the theoretical max speed would be 12 Mbit/s. Even if I could reach 50% of the theoretical max, the throughput should be much higher than what I had achieved so far. I checked my config and breadboard to see whether I misconfigured the microcontroller. All seemed fine. My friend google gave me the explanation to the bad throughput. See Microchip's training course Introduction to full speed USB . What I got from this document is dat the method I use to transfer data from microcontroller to PC I would have a theoretical throughput of:
64 Kbytes/sec = 65536 bytes/sec = 1024 frames of 64 bytes of data per second. This is much closer to the 512 frames of 64 bytes I get. Knowing this left me with three options:
  • give up and accept things the way they are
  • changing the transfer method to isochronous
  • do bulk ADC's not getting all the data, but getting a rather good impression of signals that do no change much in frequency.
I choose the latter: bulk ADC and read adc values at lower speed in small segments of data.

In order to do bulk ADC, we need to store the ADC values in some kind of buffer. Because the small amount of available memory, here are some notes about the RAM of the controller:
  • The PIC18F4550 only has 2048 bytes of SRAM and 256 bytes of EEPROM which can be used for data.
  • The RAM memory is devided in 16 banks of 256 bytes. That would make 4096 bytes, but some banks can't be used at all
  • Bank 0 is partially reserved for Access ram (96 bytes)
  • Bank 4 is reserved for USB
  • Bank 5 is partially reserved for USB (128 bytes)
  • Banks 8...14 are unused (= can't be used)
  • Banks 15 is partially unused and 160 bytes are reserved for Special Function Registers

When you want to address more than 256 bytes and you don't want to change the linker options, you have to split the buffer into smaller parts, each max 256 bytes.
Below you see the reserved bytes of bank 5 for the USB input and output buffers:

#pragma udata
#pragma udata USB_VARIABLES=0x500
#define RX_DATA_BUFFER_ADDRESS
#define TX_DATA_BUFFER_ADDRESS
unsigned char ReceivedDataBuffer[64] ; RX_DATA_BUFFER_ADDRESS
unsigned char ToSendDataBuffer[64] ; TX_DATA_BUFFER_ADDRESS
#pragma udata

When your arrays are too big, then the linker can't map them, you get the following error:

    make[2]: *** [dist/PICDEM_FSUSB/production/MPLAB.X.production.hex] Error 1
    make[1]: *** [.build-conf] Error 2
    Error - section 'ADC_VARIABLES1' can not fit the section. Section 'ADC_VARIABLES1' length=0x00000100
    Errors : 1

    make: *** [.build-impl] Error 2
    make[2]: Leaving directory `E:/Microchip Solutions v2012-10-15/USB/Device - HID - Custom Demos/Firmware/MPLAB.X'
    make[1]: Leaving directory `E:/Microchip Solutions v2012-10-15/USB/Device - HID - Custom Demos/Firmware/MPLAB.X'

    BUILD FAILED (exit value 2, total time: 906ms)

So you have to tweak the buffer sizes and play around until you have the maximum available buffer size. I managed to get totally 1434 bytes for a set of buffers. Which can hold 717 uncompressed ADC samples or 1144 compressed ADC samples. When you compress the data by bit-shift operations, you can put 4 measurements into 5 bytes. (4 measurements = 40 bits = 5 bytes, uncompressed 4 measurements use 8 bytes).

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
#pragma udata
#pragma udata ADC_VARIABLES1
#define adcbuffer1_size 256 // 204 compressed measurements =255 bytes , 128 normal measurements
#define adcbuffer1_samples 128
#define adcbuffer1_compressed_samples 204
unsigned char ; adcbuffer1[adcbuffer1_size]
#pragma udata ADC_VARIABLES2
#define adcbuffer2_size 256 // 204 compressed measurements, 128 normal measurements
#define adcbuffer2_samples 128
#define adcbuffer2_compressed_samples 204
unsigned char ; adcbuffer2[adcbuffer2_size]
#pragma udata ADC_VARIABLES3
#define adcbuffer3_size 256 // 204 compressed measurements, 128 normal measurements
#define adcbuffer3_samples 128
#define adcbuffer3_compressed_samples 204
unsigned char ; adcbuffer3[adcbuffer3_size]
#pragma udata ADC_VARIABLES4
#define adcbuffer4_size 256 // 204 compressed measurements, 128 normal measurements
#define adcbuffer4_samples 128
#define adcbuffer4_compressed_samples 204
unsigned char ; adcbuffer4[adcbuffer4_size]
#pragma udata ADC_VARIABLES5
#define adcbuffer5_size 200 // 160 compressed measurements, 100 normal measurements
#define adcbuffer5_samples 100
#define adcbuffer5_compressed_samples 160
unsigned char ; adcbuffer5[adcbuffer5_size]
#pragma udata ADC_VARIABLES6
#define adcbuffer6_size 160 // 128 compressed measurements, 80 normal measurements
#define adcbuffer6_samples 80
#define adcbuffer6_compressed_samples 128
unsigned char ; adcbuffer6[adcbuffer6_size]
#pragma udata ADC_VARIABLES7
#define adcbuffer7_size 50 // 40 compressed measurements, 25 normal measurements
#define adcbuffer7_samples 25
#define adcbuffer7_compressed_samples 40
unsigned char ; adcbuffer7[adcbuffer7_size]
BYTE simulatedMSB = ; 0
BYTE simulatedLSB = ; 0
BOOL compressedADC = ;

As you can see below, with this buffer size, the RAM memory usage is 96 percent!
95 percent of memory in use
95 percent of memory in use

I changed the firmware and split it into the following parts:
  • USB handling
  • initialize bulk ADC
  • perform bulk ADC
  • read captured ADC values
In the method ProcessIO, the bulk adc is initialized when a 0x82 is received. You can set whether you want compressed or uncompressed data, whether you want test data or real ADC data.

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164 
165 
166 
167 
168 
169 
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
void ProcessIO void {
//Blink the LEDs according to the USB device status
if blinkStatusValid {
BlinkUSBStatus ;
}
// User Application USB tasks
if USBDeviceState < CONFIGURED_STATE || USBSuspendControl == 1 ; return
//Check if we have received an OUT data packet from the host
if !HIDRxHandleBusy USBOutHandle {
//We just received a packet of data from the USB host.
//Check the first byte of the packet to see what command the host
//application software wants us to fulfill.
switch ReceivedDataBuffer[0] //Look at the data the host sent, to see what kind of application specific command it sent.
{
...
case 0x82:
{
if !HIDTxHandleBusy USBInHandle {
mLED_3_Off ;
mLED_4_On ;
// bulk adc, uncompressed
if ReceivedDataBuffer[1] == 0x00 {
PIR1bits.ADIF = ; 0x00
PIE1bits.ADIE = ; 0x00
IPR1bits.ADIP = ; 0x00
TRISAbits.TRISA0 = ; 1
ADCON0 = ; 0x01
ADCON1 = ; 0x0E
ADCON2 = ; ReceivedDataBuffer[2]
compressedADC = ; FALSE
doBulkADC = ; TRUE
}
// bulk adc, uncompressed, test data
else if ReceivedDataBuffer[1] == 0x01 {
compressedADC = ; FALSE
simulatedMSB = ; 0
simulatedLSB = ; 0
TakeNTestSamples adcbuffer1, adcbuffer1_samples ;
TakeNTestSamples adcbuffer2, adcbuffer2_samples ;
TakeNTestSamples adcbuffer3, adcbuffer3_samples ;
TakeNTestSamples adcbuffer4, adcbuffer4_samples ;
TakeNTestSamples adcbuffer5, adcbuffer5_samples ;
TakeNTestSamples adcbuffer6, adcbuffer6_samples ;
TakeNTestSamples adcbuffer7, adcbuffer7_samples ;
}
// bulk adc, compressed
else if ReceivedDataBuffer[1] == 0x80 {
PIR1bits.ADIF = ; 0x00
PIE1bits.ADIE = ; 0x00
IPR1bits.ADIP = ; 0x00
TRISAbits.TRISA0 = ; 1
ADCON0 = ; 0x01
ADCON1 = ; 0x0E
ADCON2 = ; ReceivedDataBuffer[2]
compressedADC = ; TRUE
doBulkADC = ; TRUE
}
// bulk adc, compressed, test data 1
else if ReceivedDataBuffer[1] == 0x81 {
simulatedMSB = ; 0
simulatedLSB = ; 0
compressedADC = ; TRUE
TakeNCompressedTestSamples adcbuffer1, adcbuffer1_compressed_samples ;
TakeNCompressedTestSamples adcbuffer2, adcbuffer2_compressed_samples ;
TakeNCompressedTestSamples adcbuffer3, adcbuffer3_compressed_samples ;
TakeNCompressedTestSamples adcbuffer4, adcbuffer4_compressed_samples ;
TakeNCompressedTestSamples adcbuffer5, adcbuffer5_compressed_samples ;
TakeNCompressedTestSamples adcbuffer6, adcbuffer6_compressed_samples ;
TakeNCompressedTestSamples adcbuffer7, adcbuffer7_compressed_samples ;
}
// bulk adc, compressed, test data 2
else if ReceivedDataBuffer[1] == 0x82 {
simulatedMSB = ; 0
simulatedLSB = ; 1
compressedADC = ; TRUE
TakeNCompressedTestSamples adcbuffer1, adcbuffer1_compressed_samples ;
TakeNCompressedTestSamples adcbuffer2, adcbuffer2_compressed_samples ;
TakeNCompressedTestSamples adcbuffer3, adcbuffer3_compressed_samples ;
TakeNCompressedTestSamples adcbuffer4, adcbuffer4_compressed_samples ;
TakeNCompressedTestSamples adcbuffer5, adcbuffer5_compressed_samples ;
TakeNCompressedTestSamples adcbuffer6, adcbuffer6_compressed_samples ;
TakeNCompressedTestSamples adcbuffer7, adcbuffer7_compressed_samples ;
}
// bulk adc, compressed, test data 3
else if ReceivedDataBuffer[1] == 0x83 {
simulatedMSB = ; 0
simulatedLSB = ; 2
compressedADC = ; TRUE
TakeNCompressedTestSamples adcbuffer1, adcbuffer1_compressed_samples ;
TakeNCompressedTestSamples adcbuffer2, adcbuffer2_compressed_samples ;
TakeNCompressedTestSamples adcbuffer3, adcbuffer3_compressed_samples ;
TakeNCompressedTestSamples adcbuffer4, adcbuffer4_compressed_samples ;
TakeNCompressedTestSamples adcbuffer5, adcbuffer5_compressed_samples ;
TakeNCompressedTestSamples adcbuffer6, adcbuffer6_compressed_samples ;
TakeNCompressedTestSamples adcbuffer7, adcbuffer7_compressed_samples ;
}
// bulk adc, compressed, test data 4
else if ReceivedDataBuffer[1] == 0x84 {
simulatedMSB = ; 0
simulatedLSB = ; 3
compressedADC = ; TRUE
TakeNCompressedTestSamples adcbuffer1, adcbuffer1_compressed_samples ;
TakeNCompressedTestSamples adcbuffer2, adcbuffer2_compressed_samples ;
TakeNCompressedTestSamples adcbuffer3, adcbuffer3_compressed_samples ;
TakeNCompressedTestSamples adcbuffer4, adcbuffer4_compressed_samples ;
TakeNCompressedTestSamples adcbuffer5, adcbuffer5_compressed_samples ;
TakeNCompressedTestSamples adcbuffer6, adcbuffer6_compressed_samples ;
TakeNCompressedTestSamples adcbuffer7, adcbuffer7_compressed_samples ;
}
// bulk adc, compressed, test data 5
else if ReceivedDataBuffer[1] == 0x85 {
simulatedMSB = ; 0
simulatedLSB = ; 4
compressedADC = ; TRUE
TakeNCompressedTestSamples adcbuffer1, adcbuffer1_compressed_samples ;
TakeNCompressedTestSamples adcbuffer2, adcbuffer2_compressed_samples ;
TakeNCompressedTestSamples adcbuffer3, adcbuffer3_compressed_samples ;
TakeNCompressedTestSamples adcbuffer4, adcbuffer4_compressed_samples ;
TakeNCompressedTestSamples adcbuffer5, adcbuffer5_compressed_samples ;
TakeNCompressedTestSamples adcbuffer6, adcbuffer6_compressed_samples ;
TakeNCompressedTestSamples adcbuffer7, adcbuffer7_compressed_samples ;
}
// buffer test
else if ReceivedDataBuffer[1] == 0xff {
FillWithTestData ;
}
mLED_3_On ;
//Check to make sure the endpoint/buffer is free before we modify the contents
ToSendDataBuffer[0] = ; //Echo back to the host PC the command we are fulfilling in the first byte. In this case, the Get Pushbutton State command. 0x82
//Prepare the USB module to send the data packet to the host
ToSendDataBuffer[1] = ; ReceivedDataBuffer[2]
USBInHandle = HIDTxPacket HID_EP, BYTE* & ToSendDataBuffer[0], 64 ;
}
; break
}
; break
case 0x83:
{
int offsetForData = ; 3
int sequence = ; 0
if !HIDTxHandleBusy USBInHandle {
ToSendDataBuffer[0] = ; //Echo back to the host PC the command we are fulfilling in the first byte. In this case, the Get Pushbutton State command. 0x83
ToSendDataBuffer[1] = ; 0x01
ToSendDataBuffer[2] = ; // length of sequence; 50
ToSendDataBuffer[3] = ; 0
sequence = ; ReceivedDataBuffer[1]
if compressedADC {
ReadCompressedSequence offsetForData, sequence ;
} else {
ReadSequence offsetForData, sequence ;
}
USBInHandle = HIDTxPacket HID_EP, BYTE* & ToSendDataBuffer[0], 64 ;
}
}
; break
default:
if !HIDTxHandleBusy USBInHandle {
ToSendDataBuffer[0] = ; //Echo back to the host PC the command we are fulfilling in the first byte. In this case, the Get Pushbutton State command. ReceivedDataBuffer[0]
USBInHandle = HIDTxPacket HID_EP, BYTE* & ToSendDataBuffer[0], 64 ;
}
; break
}
//Re-arm the OUT endpoint, so we can receive the next OUT data packet
//that the host may try to send us.
USBOutHandle = HIDRxPacket HID_EP, BYTE* & ReceivedDataBuffer, 64 ;
}
}//end ProcessIO

In the high priority ISR code, the actual bulk ADC is performed. Because we don't have one buffer, but 7 not equally sized buffers, I wrote methods that takes a buffer as parameter and the number of ADC samples you want to take (one for compressed data and one for uncompressed data).

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
#pragma code
//These are your actual interrupt handling routines.
#pragma interrupt YourHighPriorityISRCode
void YourHighPriorityISRCode {
int counter = ; 0
if doBulkADC {
PIR1bits.ADIF = ; 0x00
PIE1bits.ADIE = ; 0x00
IPR1bits.ADIP = ; 0x00
TRISAbits.TRISA0 = ; 1
ADCON0 = ; 0x01
ADCON1 = ; 0x0E
ADCON2 = ; ReceivedDataBuffer[2]
ADCON0bits.ADON = ; // turn adc module on 0x01
for counter = ; counter < 60; counter++) { 0
// wait at least 2us after enabling AD
// 60 cycles at 20Mhz is 3 us
}
if compressedADC {
TakeNCompressedSamples adcbuffer1, adcbuffer1_compressed_samples ;
TakeNCompressedSamples adcbuffer2, adcbuffer2_compressed_samples ;
TakeNCompressedSamples adcbuffer3, adcbuffer3_compressed_samples ;
TakeNCompressedSamples adcbuffer4, adcbuffer4_compressed_samples ;
TakeNCompressedSamples adcbuffer5, adcbuffer5_compressed_samples ;
TakeNCompressedSamples adcbuffer6, adcbuffer6_compressed_samples ;
TakeNCompressedSamples adcbuffer7, adcbuffer7_compressed_samples ;
} else {
TakeNSamples adcbuffer1, adcbuffer1_samples ;
TakeNSamples adcbuffer2, adcbuffer2_samples ;
TakeNSamples adcbuffer3, adcbuffer3_samples ;
TakeNSamples adcbuffer4, adcbuffer4_samples ;
TakeNSamples adcbuffer5, adcbuffer5_samples ;
TakeNSamples adcbuffer6, adcbuffer6_samples ;
TakeNSamples adcbuffer7, adcbuffer7_samples ;
}
ADCON0bits.ADON = ; // turn adc module off 0x00
doBulkADC = ; FALSE
}
USBDeviceTasks ;
} //This return will be a "retfie fast", since this is in a #pragma interrupt section
// uncompressed data
void TakeNSamples unsigned char* p_buffer, int numberOfSamples {
int index = ; 0
int counter = ; 0
for counter = ; counter < numberOfSamples; counter++) { 0
ADCON0bits.GO = ; // Start AD conversion 1
while ADCON0bits.GO ; // Wait for conversion
p_buffer[index] = ; ADRESH
; index++
p_buffer[index] = ; ADRESL
; index++
}
}

The compression algorithm is just a series of bit shift operations:

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
void TakeNCompressedSamples unsigned char* p_buffer, int numberOfSamples {
int index = ; 0
int sampleNumber = ; 0
WORD_VAL ; w
for sampleNumber = ; sampleNumber < numberOfSamples;) { 0
// Measurement 1
w = ReadPOT ; // measurement N xxxxxxxxAA aaaaaaaa
// new item use = instead of +=
p_buffer[index] = w.v[1] << 6 ; // xxxxxxAA => AAxxxxxx
p_buffer[index] |= w.v[0] >> 2 ; // aaaaaaaa => AAaaaaaa
; index++
// new item use = instead of +=
p_buffer[index] = w.v[0] << ; // aaaaaaaa => aaxxxxxx 6
; sampleNumber++
// Measurement 2
w = ReadPOT ; // measurement N+1 xxxxxxxxBB bbbbbbbb
p_buffer[index] |= w.v[1] << 4 ; // aaBBxxxx
p_buffer[index] |= w.v[0] >> 4 ; // aaBBbbbb
; index++
// new item use = instead of +=
p_buffer[index] = w.v[0] << 4 ; // bbbbxxxx
; sampleNumber++
// Measurement 3
w = ReadPOT ; // measurement N+2 (total 30 bits, 4 bytes)
p_buffer[index] |= w.v[1] << 2 ; // bbbbCCxx
p_buffer[index] |= w.v[0] >> 6 ; // bbbbCCcc
; index++
// new item use = instead of +=
p_buffer[index] = w.v[0] << 2 ; // ccccccXX
; sampleNumber++
// Measurement 4
w = ReadPOT ; // measurement N+3 (total 40 bits, 5 bytes)
p_buffer[index] |= ; // ccccccDD w.v[1]
; index++
// new item use = instead of +=
p_buffer[index] = ; // dddddddd w.v[0]
; index++
; sampleNumber++
}
}

And the code for decompressing in the Windows client:

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
private void ExtractCompressedADCValues(Byte[] INBuffer, int numberofbytesinsample)
{
for (int x = 0; x < Math.Min(numberofbytesinsample, 60); x += 5)
{
// 5 bytes contain 4 measurements
byte[] bytes = new byte[5];
bytes[0] = INBuffer[4 + x]; // AAaaaaaa
bytes[1] = INBuffer[4 + x + 1]; // aaBBbbbb
bytes[2] = INBuffer[4 + x + 2]; // bbbbCCcc
bytes[3] = INBuffer[4 + x + 3]; // ccccccDD
bytes[4] = INBuffer[4 + x + 4]; // dddddddd
// measurement 1
int msb = bytes[0]; // AAaaaaaa
msb >>= 6; // 000000AA
int lsb = bytes[0]; // AAaaaaaa
lsb &= b00111111; // 00aaaaaa
lsb <<= 2; // aaaaaa00
int lsb2 = bytes[1];
lsb2 &= b11000000; // aa000000
lsb2 >>= 6; // 000000aa
lsb |= lsb2; // aaaaaaaa
int ADCValue = (msb << 8) + lsb; // xxxxxxAA aaaaaaaa
adcValues[adcIndex] = ADCValue;
System.Diagnostics.Trace.Write(ADCValue + " ");
IncrementADCIndex();
// measurement 2
msb = bytes[1]; // aaBBbbbb
msb >>= 4; // 0000aaBB
msb &= b00000011; // 000000BB
lsb = bytes[1]; // aaBBbbbb
lsb &= b00001111; // 0000bbbb
lsb <<= 4; // bbbb0000
lsb2 = bytes[2]; // bbbbCCcc
lsb2 >>= 4; // 0000bbbb
lsb |= lsb2; // bbbbbbbb
ADCValue = (msb << 8) + lsb; // xxxxxxBB bbbbbbbb
adcValues[adcIndex] = ADCValue;
System.Diagnostics.Trace.Write(ADCValue + " ");
IncrementADCIndex();
// measurement 3
msb = bytes[2]; // bbbbCCcc
msb >>= 2; // 00bbbbCC
msb &= b00000011; // 000000CC
lsb = bytes[2]; // bbbbCCcc
lsb &= b00000011; // 000000cc
lsb <<= 6; // cc000000
lsb2 = bytes[3]; // ccccccDD
lsb2 >>= 2; // 00cccccc
lsb |= lsb2; // cccccccc
ADCValue = (msb << 8) + lsb; // xxxxxxCC cccccccc
adcValues[adcIndex] = ADCValue;
System.Diagnostics.Trace.Write(ADCValue + " ");
IncrementADCIndex();
// measurement 4
msb = bytes[3]; // ccccccDD
msb &= b00000011; // 000000DD
lsb = bytes[4]; // dddddddd
ADCValue = (msb << 8) + lsb; // xxxxxxDD dddddddd
adcValues[adcIndex] = ADCValue;
System.Diagnostics.Trace.Write(ADCValue + " ");
IncrementADCIndex();
}
System.Diagnostics.Trace.WriteLine("");
}

In order to test both the windows client as the compression algorithm, I provided the option to use predefined test data. Instead of reading actual ADC data, a lineair ADC function generates data from 0...1023. The next code section shows both the real ADC as the test data generator.

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
// Real ADC
WORD_VAL ReadPOT void {
WORD_VAL ; w
w.Val = ; 0
ADCON0bits.GO = ; // Start AD conversion 1
while ADCON0bits.GO ; // Wait for conversion
w.v[0] = ; ADRESL
w.v[1] = ; ADRESH
return ; w
}//end ReadPOT
// Test data: generates lineair data between 0...1023
WORD_VAL SimulatePOT {
WORD_VAL ; w
w.v[0] = ; simulatedLSB
w.v[1] = ; simulatedMSB
if simulatedLSB == 255 {
simulatedLSB = ; 0
if simulatedMSB == 3 {
simulatedMSB = ; 0
} else {
; simulatedMSB++
}
} else {
; simulatedLSB++
}
return ; w
}
Lineair test samples
Lineair test samples

I changed my C# scope program a little bit, such that I could change the ADCON2 settings and switch between normal capture and bulk ADC. (The code is based on the Microchip Library of Applications, USB Device - HID Custom Demo (both the microcontroller code as the C# code)

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
private void StartBulkADC(Byte[] OUTBuffer, Byte[] INBuffer, ref uint BytesWritten, ref uint BytesRead, out uint ErrorStatusWrite, out uint ErrorStatusRead)
{
ErrorStatusWrite = 0;
ErrorStatusRead = 0;
//Initialize 64-byte packet to "0xFF". Binary '1' bits do not use as much power, and do not cause as much EMI
//when they move across the USB cable. USB traffic is "NRZI" encoded, where '1' bits do not cause the D+/D- signals to toggle states.
for (uint i = 0; i < 65; i++)
{
OUTBuffer[i] = 0xFF;
}
int outIndex = 0;
OUTBuffer[outIndex++] = 0x00; //The first byte is the "Report ID" and does not get sent over the USB bus. Always set = 0.
OUTBuffer[outIndex++] = 0x82; // Do bulk ADC command
if (compressedADC)
{
OUTBuffer[outIndex++] = (byte)(testSamples ? 0x81 : 0x80); // Compressed Bulk ADC or compressed test data
OUTBuffer[outIndex++] = (byte)adcon2; // Use ADCON2 settings as selected in User Interface of scope program.
}
else
{
OUTBuffer[outIndex++] = (byte)(testSamples ? 0x16 : 0x00); // Normal Bulk, no compression or test data
OUTBuffer[outIndex++] = (byte)adcon2; // Use ADCON2 settings as selected in User Interface of scope program.
}
if (WriteFile(WriteHandleToUSBDevice, OUTBuffer, 65, ref BytesWritten, IntPtr.Zero)) //Blocking function, unless an "overlapped" structure is used
{
ErrorStatusWrite = (uint)Marshal.GetLastWin32Error();
if (ErrorStatusWrite != ERROR_SUCCESS)
{
Debug.WriteLine("Error in write");
}
INBuffer[0] = 0;
//Now get the response packet from the firmware.
if (ReadFileManagedBuffer(ReadHandleToUSBDevice, INBuffer, 65, ref BytesRead, IntPtr.Zero)) //Blocking function, unless an "overlapped" structure is used
{
//INBuffer[0] is the report ID, which we don't care about.
if (INBuffer[1] == 0x82)
{
System.Diagnostics.Trace.WriteLine("Bulk ADC initialized.");
}
ErrorStatusRead = (uint)Marshal.GetLastWin32Error();
if (ErrorStatusRead != ERROR_SUCCESS)
{
Debug.WriteLine("Error in read");
}
}
ErrorStatusRead = (uint)Marshal.GetLastWin32Error();
if (ErrorStatusRead != ERROR_SUCCESS)
{
Debug.WriteLine("Error in read");
}
}
BytesWritten = HandleToggleLEDsPending(OUTBuffer, BytesWritten);
doBulkADC = false; // stop Bulk ADC, next step read bulk ADC
}

Because of the small memory size, I used compressed ADC in my first bulk ADC attempt: one measurement has 10 bits, uncopressed this eqauls 2 bytes. Compressed I can put 4 measurements in 5 bytes. 4 * 10 bits / 8 bits per byte= 5 bytes instead of 8 bytes. One can not just declare a buffer of a size > 256 bytes because the PIC memory is devided in banks of 256 bytes. The compiler does not auto solve this so in order to store more than 256 bytes of ADC values, you have to split the ADC buffer in sub buffers. This way I can store 1024 compressed ADC samples or 700 uncompressed ADC samples. The latter is faster but has the disadvantage of capturing less samples. Bank 4 is reserved for usb data, bank 5 is partially reserved for USB in and out buffers. Bank 0 is reserverd for variables.

Eventually I was able to reserve 4 banks of 250 bytes and additionally two buffers of 125 bytes and one of 152 bytes.

The resulting scope view of a 4800 Hz signal is shown below. I choose the following ADCON2 settings:
  • Tad = Fosc / 16 = 1/(20Mhz / 16) = 0.8 usec
  • Acquisition time: 4 Tad = 4 * 0.8 usec = 3.2 usec
  • right adjusted ADC
Each measurement takes 4 Tad (acquisition) + 11 Tad (conversion) + 1 Tad (discharging) = 16 Tad = 16*0.8 usec= 12.6 usec
ADCON2 adjustable settings 4800 Hz
ADCON2 adjustable settings 4800 Hz
Sampling 4800 Hz signal
Sampling 4800 Hz signal

80.5 cycles in 1024 measurements = 12.7 measurements per cycle. Each measurement is approx. 12.6 usec. So one cycle = 162.8 usec which equals a signal of 6141 Hz. So either the measurement cycle is bigger than 12.6 usec or the signal has a higher frequency than 4800 Hz (or both).
The signal is the output of a 555-timer with R1 and R2 of 1k (10%) and C1 of 0.1uF (20%). See 555 calculator So the frequency is somewhere between and 3.6 kHz and 6.7 kHz.
After measuring the R1 and R2 value both (0.93k) I still have a C1 with a value of 0.1 uF(20%). So the signal is somewhere between 4.3 kHz and 6.4 kHz, but a value of 6.1 kHz is within this range.
In order to determine the exact time per measurement you need to use a reference signal.
But the current setting at least shows that we can now capture signals above 20 kHz (compared to 500 Hz this is a big improvement). If the time per measurement is exactly 12.6 usec then we can capture signals up to 39 kHz (sampling rate is 78 kHz, maximum frequency of signal that can be captured is 1/2 * sampling rate = 39 kHz)

Downloads:
  • The Visual Studio Solution. Slightly updated. It is not beautiful code. It is "rapid developed proof of concept code" (RDPOCC).
  • The main.c file An adjusted version of Device - HID - Custom Demos
  • The HEX-fileThe compiled version of the adjusted version of Device - HID - Custom Demos


On 2013-04-22, VICTOR64_934 wrote:

HOLA ESTOY INTERESADO EN SCOPE USB

On 2014-06-11, Eeuwe Vandyke wrote:

I added the HEX-file to the download section.

Back to List

All form fields are required.
A confirmation mail for the comments will be send to you.