Announcement

Collapse
No announcement yet.

The basics of USB-PIC

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • The basics of USB-PIC

    In this thread, I'll post the information required to implement a USB interface using a PIC chip. For now, I'll keep it in the CDG section to give us an advantage, but will probably move it in the future to the public area.

    USB offers at least two big benefits: an easy way to change the PIC code, and a way to pull data out.

    Microchip sells several PICs with built-in USB hardware. I bought their 18F4550 demo board which you just plug into the PC, run the software, and *voila* it all works! That's the easy part. The hard part is figuring out how it all works, and how to make changes to do what you want it to do. They include source code for the firmware but, amazingly enough, not for the software.

    So it was 2 weeks of near-full-time work figuring it all out. I'll skip the gory details.

    The final chip I used is the 18F2455, which is a 28-pin SOIC version (4550 is a 44pin QFP). After pins for power, clock, MCLR, and USB, there are 20 GPIO pins left over. There is a 10-input 10b ADC and the usual array of comparators, timers, interrupts, and PWM.

    Programming the PIC is done in 2 stages. First, using a stardard PIC programmer you load the "bootloader" firmware which is fairly small (~1000 bytes). You are done with the PIC programmer, and you can solder down the part. Then, the main firmware is loaded in over the USB, and can be done so repeatedly. Also, your firmware can poll the USB for data input, or send out data, as it is running.

    That does it for the description. I will begin uploading the code details tomorrow.

    - Carl

  • #2
    Didn't have time to pull together the details yet.

    There are 4 pieces to the USB puzzle. Doesn't sound like a lot, but when it doesn't work, it's difficult to figure out which piece is broken. The pieces are:
    1. Bootloader firmware - the small chunk of code that permanently resides in the PIC. It does nothing but allow you to load in the Main firmware, so if the Main firmware doesn't work, you have to first make sure the bootloader is loading it right. Fortunately, Microchip provides a complete bootloader solution.
    2. Main firmware - the main code that does whatever it is you want the PIC to do.
    3. PC software - the interface to the device with the PIC. This piece is optional; it would be used for sending data to the PIC, or reading data from the PIC. A typical metal detector would not do this, but it might be useful for analyzing responses, or for implementing a data logger for site mapping. Or, as I will use as an example, for modifying the circuitry on-the-fly.
    4. USB driver - the PC code that enables the PC to recognize and communicate with the PIC. You need a driver for the bootloader, and a driver for the main code, although it is possible to use the same driver for both. We will be using the driver that Microchip supplies for their demo code, but I'll show how to modify it to make it look custom.
    For this sub-project, the firmware is written entirely in C, including the Microchip bootloader. In fact, I will be doing all my PIC coding in C, it is much much much much easier. For the C compiler, you will need Microchip's C18 tool, which happens to come in a free version (they also have a C16 compiler). Also their MPLab IDE, which is free.

    For the PC software, I am using Microsoft Visual Studio C#. Although it's expensive, Microsoft has the "Visual C# Express Edition" lightweight compiler for the right price: FREE. I have not tried it yet, but for this app, it should work. In any case, we will not tackle the PC software until later.

    Next up is the USB code and the bootloader...

    - Carl

    Comment


    • #3
      Carl maybe this could be interesting for you
      http://piclist.com/images/com/bubble...mo/ROMzap.html

      Comment


      • #4
        Originally posted by strujas View Post
        Carl maybe this could be interesting for you
        http://piclist.com/images/com/bubble...mo/ROMzap.html
        That's a very useful feature, especially for SMT PICs. This is part of what the USB PIC offers, but it offers even more than that. The trade-off is, you're limited to the USB-enabled PICs, whereas ROMzap will work with most any PIC.

        - Carl

        Comment


        • #5
          I had intended to start posting code this weekend, then realized it was on my computer at work. So, moving slowly along, let's look at the bootloader code.

          I won't post all of the code here. It involves a fair number of .c and .h files containing the USB library code, and we're not going to touch that. So what I will do is point you to the Microchip source code, and focus on the things that we might want to change for our own use.

          Click here for Microchip's USB Framework

          Everything installs to the c:\MCHPFSUSB directory. Let's take a look at what we got. There are 3 folders under the main directory:

          c:\MCHPFSUSB\Documents
          c:\MCHPFSUSB\fw
          c:\MCHPFSUSB\Pc

          Documents is self-explanatory. "fw" means "firmware", this is the guts of the whole thing. There are several folders under fw, the only one we care about is

          c:\MCHPFSUSB\fw\boot

          This has the bootloader code. The other folders hold USB-enabled firmware examples, we'll get to those later. The "Pc" folder contains 3 other folders:

          c:\MCHPFSUSB\Pc\MCHPUSB Driver -- The first time you plug in a USB device, the PC looks for a "driver" file that enables the PC to recognize the device. That's what this is. There is a "release" folder and a "debug" folder, we'll use the release folder. Inside that the 2 files that matter are mchpusb.sys and mchpusb.inf. More later.

          c:\MCHPFSUSB\Pc\Mpusbapi -- When you write PC software to talk to a USB device, you need a library (an "API") of USB functions you can call. That's what this is. We'll do software later, so ignore for now.

          c:\MCHPFSUSB\Pc\Pdfsusb -- This folder has a program called PDFSUSB.exe, which let's you program new firmware into a PIC that has bootloader code installed. More later.

          OK, that wraps up the overview of what we downloaded. Let's get back to the bootloader code in c:\MCHPFSUSB\fw\boot. If you start poking around in this folder, you will find lots of sub-folders with .c and .h files scattered around. I don't pretend to understand Microchip's reasoning in this structure, it seems bizarre. It doesn't matter. The only code we want to touch are in the two files in the boot folder: main.c and io_cfg.h. We do not want to edit any of the other code.

          There is one other file we will touch, 18f4550.lkr. This is called a "linker" file, and is used by the C compiler to set up the PIC's memory structure.

          OK, enough for tonight, more tomorrow. Meanwhile, speaking of C compiler, here are some useful links:

          Click here for the MPLAB IDE.
          Click here for the C18 compiler, registration required.

          - Carl

          Comment


          • #6
            As I mentioned, the only 2 files we want to touch are main.c and io_cfg.h. First, io_cfg.h. This file contains mostly a bunch of #defines which are macros that get literally substituted during compile. Ferinstance,

            #define mLED_1 LATDbits.LATD0

            defines the D0 port to be "mLED_1". So these commands do the same thing:

            mLED_1 = 1;
            LATDbits.LATD0 = 1;

            However, some PIC chips don't have "D" ports, like the 18F2455 that I'm using. So you might want to change the #defines to use available ports, like the "B" port:

            #define mLED_1 LATBbits.LATB0

            The main thing all these #defines are used for is to flash some LEDs depending on the status of the USB port. It is for diagnostic purposes. So if you are not going to use the LEDs, nothing in the io_cfg.h file really matters. This same file will show up again when we dive into the main firmware.

            main.c

            OK, here is core of the bootloader:

            Code:
            [FONT=Courier New]void main(void)
            {
                byte temp;
                temp = ADCON1;
                ADCON1 |= 0x0F;
                
                TRISBbits.TRISB4 = 1;
                
                if(PORTBbits.RB4 == 1)
                {
                    ADCON1 = temp;
                    _asm goto RM_RESET_VECTOR _endasm
                }
                
                mInitAllLEDs();
                mInitializeUSBDriver();
                USBCheckBusStatus();
                while(1)
                {
                    USBDriverService();
                    BootService();
                }
            }[/FONT]
            This code is executed only when the PIC is reset (MCLR -> 0). Let's go through it.

            Code:
            [FONT=Courier New]
            temp = ADCON1;
            ADCON1 |= 0x0F;
            [/FONT]
            "temp" stores the ports defined for ADC use; those ports are then defined as general i/o ports.

            Code:
            [FONT=Courier New]TRISBbits.TRISB4 = 1;
            [/FONT]
            Port B4 is defined as an input.

            Code:
            [FONT=Courier New]if(PORTBbits.RB4 == 1)
            {
                ADCON1 = temp;
                _asm goto RM_RESET_VECTOR _endasm
            }
            [/FONT]
            If B4 is HIGH, restore the ADC ports and jump over to the main firmware. In other words, we are NOT going to load in new firmware code, we want to run what is already loaded.

            Code:
              [FONT=Courier New] 
            mInitAllLEDs();
            mInitializeUSBDriver();
            USBCheckBusStatus();
            while(1)
            {
                USBDriverService();
                BootService();
            }
            [/FONT]
            If B4 is LOW, the rest of this code puts the PIC chip in a state where new code can be loaded via the USB port.

            Wow, is that simple or what?

            What it all means is that on reset (MCLR is taken LOW) the bootloader looks at one other port (B4 in this case) to determine whether to load new firmware or execute what is already resident.

            Obviously, you don't have to use B4 as the indicator port, you can use any port you like. So this is where you might want to edit this file.

            There is one other thing we need to look at in this file:

            Code:
            [FONT=Courier New]#pragma code _HIGH_INTERRUPT_VECTOR = 0x000008
            void _high_ISR (void)
            {
                _asm goto RM_HIGH_INTERRUPT_VECTOR _endasm
            }
            
            #pragma code _LOW_INTERRUPT_VECTOR = 0x000018
            void _low_ISR (void)
            {
                _asm goto RM_LOW_INTERRUPT_VECTOR _endasm
            }
            [/FONT]
            These functions remap the PIC's interrupt vectors to new locations. The reason for doing this will become apparent when I get into the memory mapping that is done to make all this stuff work. For now, let's just say that these 2 interrupts really belong to the main firmware, not the bootloader, so we need to remap their locations over to the right area of memory. You really don't want to mess with this, unless you know what you're doing.

            Next up is the main firmware.

            - Carl

            Comment


            • #7
              Before getting into the firmware, I think I should first explain the memory mapping and the linker files. The USB bootloader is basically like having two completely separate programs loaded onto a PIC (there are some rather cool things I can think of doing with this). The bootloader itself resides in low memory, and the main program resides in some higher memory. You have to tell the compiler exactly where they reside, so that they don't overwrite one another.

              The Microchip C18 compiler uses a "linker" file to set this up. Here is the contents of 18f4550.lkr for the bootloader:

              Code:
              [FONT=Courier New]FILES c018i.o
              FILES clib.lib
              FILES p18f4550.lib
              
              CODEPAGE   NAME=vectors    START=0x0            END=0x29           PROTECTED
              CODEPAGE   NAME=page       START=0x2A           END=0x7FFF
              CODEPAGE   NAME=idlocs     START=0x200000       END=0x200007       PROTECTED
              CODEPAGE   NAME=config     START=0x300000       END=0x30000D       PROTECTED
              CODEPAGE   NAME=devid      START=0x3FFFFE       END=0x3FFFFF       PROTECTED
              CODEPAGE   NAME=eedata     START=0xF00000       END=0xF000FF       PROTECTED
              
              ACCESSBANK NAME=accessram  START=0x0            END=0x5F
              DATABANK   NAME=gpr0       START=0x60           END=0xFF
              DATABANK   NAME=gpr1       START=0x100          END=0x1FF
              DATABANK   NAME=gpr2       START=0x200          END=0x2FF
              DATABANK   NAME=gpr3       START=0x300          END=0x3FF
              DATABANK   NAME=usb4       START=0x400          END=0x4FF           PROTECTED
              DATABANK   NAME=usb5       START=0x500          END=0x5FF           PROTECTED
              DATABANK   NAME=usb6       START=0x600          END=0x6FF           PROTECTED
              DATABANK   NAME=usb7       START=0x700          END=0x7FF           PROTECTED
              ACCESSBANK NAME=accesssfr  START=0xF60          END=0xFFF          PROTECTED
              
              SECTION    NAME=CONFIG     ROM=config
              
              STACK SIZE=0x100 RAM=gpr3
              [/FONT]
              The 1st 3 lines are like #includes; if you use a different PIC you obviously want to change "FILES p18f4550.lib" to include the right library.

              The "CODEPAGE" lines define how the memory is carved up. Interrupt vectors take the first 42 bytes. Firmware code (NAME=page) is allowed to take the remaining 32k of memory. I really have no idea what the rest of it means, including the DATABANK lines. Just don't mess with it.

              OK, that's the linker file for the bootloader code. Let's now look at the linker file for the main firmware, rm18f4550.lkr. It's identical except for the first 3 CODEPAGE lines:

              Code:
              [FONT=Courier New]CODEPAGE   NAME=boot       START=0x0            END=0x7FF          PROTECTED
              CODEPAGE   NAME=vectors    START=0x800          END=0x0x829        PROTECTED
              CODEPAGE   NAME=page       START=0x82A          END=0x7FFF
              [/FONT]
              There is a new "boot" section that takes up the first 2k of memory. This is the region of memory dedicated to the bootloader code, so it is critically important that the bootloader code (plus the 42 bytes of interrupt vectors) not exceed 2048 bytes!

              If you look back at the bootloader memory map above, the bootloader code is allowed to use up to 32k of memory. Why the discrepancy? It's not important to tell the bootloader code how big it needs to be; when it compiles, it will be whatever it is, and you just need to make sure the main firmware linker file sets aside a big enough "boot" section. The Microchip bootloader compiles to a little over 1000 bytes, so 2k is a-plenty.

              Next, notice that the interrupt vectors are mapped to 0x800 - 0x829. What does this mean? From my previous post the bootloader firmware included a couple of interrupt functions. Here's one:

              #pragma code _HIGH_INTERRUPT_VECTOR = 0x000008
              void _high_ISR (void)
              {
              _asm goto RM_HIGH_INTERRUPT_VECTOR _endasm
              }
              This function says that when there is an interrupt at vector 0x000008, jump to location "RM_HIGH_INTERRUPT_VECTOR", which is defined in boot.h (buried deeply in the bizarre directory structure) as 0x000808. In other words, interrupt vector 0x008 is remapped (that's what the "RM" means) to 0x808.

              Finally, the main firmware code can use 0x82A - 0x7FFF, which is slightly less than 30k.

              The bottom line to all of this is the following:
              • There are 2 linker files, one for the bootloader and one for the main firmware.
              • You want to make sure they include the library for the PIC you are using, so you may need to edit the FILE line.
              • If you are making no major changes to the bootloader code, then it's highly unlikely you will need to change anything else.
              • Don't make any major changes to the bootloader code.
              - Carl

              Comment


              • #8
                Moving slowly along...

                The last piece of the puzzle is the firmware. The firmware can go one of two ways:
                1. Normal firmware
                2. USB-enabled firmware
                The first is really easy. You just write your program as you always would, the only difference is when you compile it. As discussed in the previous post, you just need to leave a little space in memory for the bootloader, and the linker file does this for you automatically.

                Now you can make changes to code and upload the firmware via USB. No more unplugging the PIC and sticking it in a programmer. This is especially attractive if you're in the field trying to tweak performance; all you need is a laptop. And if you sell detectors, you can send out firmware updates that the owner can easily install.

                Before I get into USB-enabled firmware, let's look at how to install firmware via USB. For that, we need a special PC-based program that can write the firmware to the PIC. I mentioned it in a prior post:

                c:\MCHPFSUSB\Pc\Pdfsusb -- This folder has a program called PDFSUSB.exe, which let's you program new firmware into a PIC that has bootloader code installed.

                Let's say you have a circuit with a PIC chip, and it has the bootloader installed. The first time you connect it to the PC via USB, nothing happens. That's because on power-up, the bootloader passes off control to the main firmware, but there is no main firmware, so the PIC chip appears to be unprogrammed.

                If you run PDFSUSB.exe, there is a drop-down box labeled "Select PICDEM FS USB Board". At this point, there is nothing to select. Now set B4 low and take MCLR low momentarily. This resets the PIC but also tells the bootloader to go into bootloader mode. Now the PC should suddenly recognize a new USB device, and ask to install the driver. This is located at c:\MCHPFSUSB\Pc\MCHPUSB Driver.

                Install the driver, and the drop-down box should have a new board called "PICDEM FS USB 0 (Boot)". Chose that board. Load the firmware hex file you want to send to the PIC chip. Click "Program Device". You're done. To activate the firmware, set B4 high and then take MCLR low momentarily, the PIC will then enter normal firmware mode.

                To summarize:
                • Use your favorite PIC programmer to program a PIC with the bootloader code (one time only).
                • Plug the PIC in your circuit. Connect via USB to a PC. Set B4 low, take MCLR low momentarily.
                • Install the USB driver (only necessary the first time).
                • Select the board, load the hex code, program the chip.
                • Set B4 high, take MCLR low momentarily.
                • Firmware is now running.
                Remember that you can change the bootloader "trigger" bit (default = B4) to whatever bit you want by modifying the bootloader source code.

                - Carl

                Comment


                • #9
                  This thread is getting long, and it seems like implementing USB is difficult, but it is not IF you don't deviate far from the example code Microchip provides. I am trying to include all of the major pieces of the puzzle that might, at some point, get modified, and describe what they do. When I first jumped into this, I didn't know, I quickly found out how easy it is to get the whole thing in a non-working state, and then discovered that there is no good explanation of what everything does.

                  I'm having a lot of fun with this at work. My first project was a 2GHz clock synthesizer, and I used the PIC for a USB-to-SPI translator to control the SPI port on a PLL chip. I have a Windows program where you type in the binary bits to send to the PLL, click a button, and they're sent via USB. My next project controls the SPI port on a VGA chip (gain control), but also controls individual config pins on a DAC. So right now, I have code written for generic SPI writing, and for generic bit setting. I need to add SPI reading and bit reading.

                  One potential use for this is to control digital pots. Some of those have simple up/down wiper control, some have 3-wire SPI control. We could use these to replace mechanical pots, use USB to figure out where to set them, then hard-program in firmware.

                  From the last post, the next topic to go over is USB-enabled firmware. For this, I highly recommend starting with Microchip's demo code and taking baby steps. Along with a Windows program, Microchip's code does the following:
                  • Controls 2 LEDs; they are turned on & off via the Windows software
                  • Reads a mechanical pot via the PIC's ADC and sends the value to Windows software
                  • Reads a temp sensor via the PIC's ADC and sends the value to Windows software. Data logging is done to maintain a short history.
                  So they show you how to send data to the PIC, and how to get data from the PIC. I've been working to take their code and create more generic functionality, functions like:
                  • void WriteBit(port, portnum, value)
                  • int ReadBit(port, portnum)
                  • void WriteSpi(CS, data)
                  So if you want to set A2 "high", just write

                  WriteBit("A", 2, 1);

                  All my code is at work, so I'll post more info on firmware & software next week.

                  My next step is to design a generic little USB board that is basically nothing but a PIC 18F2455, a crystal, and a USB jack. I will then pin out all the PIC I/Os, so they can be patched into any other circuit board. This will enable us to use USB control on any circuit, without having to design it in.

                  - Carl

                  Comment


                  • #10
                    Hi Carl,
                    thanks for posting the information on the USB interface. I don't know yet if I will venture into that area. I have only started to try to write the most simple assembly code and find it hard.
                    Can you recommend a beginner's Microsoft Visual Studio C# tutorial?
                    Thanks
                    Tinkerer

                    Comment


                    • #11
                      Originally posted by Tinkerer View Post
                      I have only started to try to write the most simple assembly code and find it hard.
                      I suggest skipping assembly and going straight to C. Microchip now has free versions of their C16 and C18 compilers, and a free MPLAB IDE. There is no reason to be programming in assembly unless you're fanatical about getting the smallest fastest code possible. I'm only fanatical about getting the job done. I did the assembly thing for a while, and will never go back.

                      Can you recommend a beginner's Microsoft Visual Studio C# tutorial?
                      Do you know C? If so, the best thing to do is just start out with a simple dialog box control program. VS C# does a fantastic job of filling in a lot of background code, and keeps the remaining work simple. I worked for a number of years on a large Windows program in C++, and back then I had to do all that background programming manually. It was tough to keep up with. VS C# is much much easier. I've bought a couple of books on C# but have yet to crack them open.

                      - Carl

                      Comment


                      • #12
                        I drew up a PIC USB circuit, decided to go back to the 18F4550 since it has more pins in a smaller package. I'll just need to get a socket to do the initial bootloader programming.

                        Anyway, my thought is to build up some boards with programmed PICs, and send one to each Design Group member. You guys interested?
                        Attached Files

                        Comment


                        • #13
                          PIC boards

                          Hi Carl,
                          thanks for posting information on the USB interface and the circuit.
                          Yes, I would like to get a board. Do you have any idea what the cost might be?
                          Some of us do not have easy access to parts. On the other hand it would be good if we use as much as possible the same IC's etc. for the developing. It makes the collaboration on the projects more efficient.
                          Would it be possible for you to send a few critical parts together with the board?
                          Thanks
                          Tinkerer

                          Comment


                          • #14
                            What I'm thinking about is just building several boards and sending them to DG members. There aren't that many of us, so it would not be expensive. I expect each one to be ~$20 USD including PCB.

                            Comment


                            • #15
                              Carl,

                              I would be interested in a prototype.

                              Any chance of using Visual Basic? (Ducks)

                              Comment

                              Working...
                              X