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).

#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 = FALSE;

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.

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] = 0x82; //Echo back to the host PC the command we are fulfilling in the first byte. In this case, the Get Pushbutton State command. //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] = 0x83; //Echo back to the host PC the command we are fulfilling in the first byte. In this case, the Get Pushbutton State command. ToSendDataBuffer[1] = 0x01; ToSendDataBuffer[2] = 50; // length of sequence; 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] = ReceivedDataBuffer[0]; //Echo back to the host PC the command we are fulfilling in the first byte. In this case, the Get Pushbutton State command. 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).

#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 = 0x01; // turn adc module on for (counter = 0; counter < 60; counter++) { // 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 = 0x00; // turn adc module off 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 = 0; counter < numberOfSamples; counter++) { ADCON0bits.GO = 1; // Start AD conversion 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:

void TakeNCompressedSamples(unsigned char* p_buffer, int numberOfSamples) { int index = 0; int sampleNumber = 0; WORD_VAL w; for (sampleNumber = 0; sampleNumber < numberOfSamples;) { // 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] << 6; // aaaaaaaa => aaxxxxxx 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] |= w.v[1]; // ccccccDD index++; // new item use = instead of += p_buffer[index] = w.v[0]; // dddddddd index++; sampleNumber++; } }

And the code for decompressing in the Windows client:

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.

// Real ADC WORD_VAL ReadPOT(void) { WORD_VAL w; w.Val = 0; ADCON0bits.GO = 1; // Start AD conversion 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)

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:

comment=HOLA+ESTOY+INTERESADO+EN+SCOPE+USB%0D%0A

On 2014-06-11, Eeuwe Vandyke wrote:

comment=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.