Showing posts with label 18F4550. Show all posts
Showing posts with label 18F4550. Show all posts

Thursday, 16 April 2009

Connection from PIC 18F4550 to C# program via USB

OK. The last post covered a C# program to connect via USB to a pre-built interface board. So now I know (almost) how to use .dll files in C#. Next thing is to look at talking to a PIC via USB and C#.
The end result of all this will be to create my own PIC-based hardware that is plug and play via USB, along with software to talk to the PIC-base hardware.

One of my main sources of information was this article on a PIC based weather station (Thanks to Barry B Brey, who wrote the article, and MysteriousLee, who found the article with his super powered searching skills while patiently explaining basic programming stuff while I got stressed).

Firstly I looked at the demo code supplier by Microchip with the MCHPUSB demo program.
To find this you need to look in the 'Microchip Solutions' file, wherever it was put during the installation. On my computer it was installed in the default location of c:\Program Files\.
I wanted a generic demo which I could see what was happening. I chose the "USB Device - MCHPUSB - Generic Driver Demo" as this seemed to be what I wanted (OK in reality I tried a few others but could not figure them out too well...).
I opened the 'USB Device - MCHPUSB - Generic Driver - C18 - PICDEM FSUSB.mcp' PIC code in MPLAB IDE. This was then compiled with no errors.
I then used 'PICDEM FS USB DEMO Tool' to upload the hex code onto the PIC which already had a bootloader (see previous posts).
I opened the C++ example (contained in the "USB Device - MCHPUSB - Generic Driver Demo" folder). I compiled and ran this and it worked - I could read in an analogue value from RA0 of the PIC and this was displayed. Nice. Now I just needed to understand what is going on and redo the code in C#. Easy.
Using a mixture of the article on a PIC based weather station and the C++ example code from Microchip, I eventually got my device going. The code is shown below:

Firstly the program.cs code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
static class Program
{
///
/// The main entry point for the application.
///

[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

Then the form1.cs code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices; // For DLL import

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
// This function gets the version number of the DLL
[DllImport("mpusbapi.dll")]
private static extern int _MPUSBGetDLLVersion();

// Ths function counts the number of devices on the USB
[DllImport("mpusbapi.dll")]
private static extern int _MPUSBGetDeviceCount(byte[] array);

// This opens a link to the PIC
[DllImport("mpusbapi.dll")]
private static extern IntPtr _MPUSBOpen(int a, byte[] b, byte[] c, int d, int e);
[DllImport("mpusbapi.dll")]
private static extern IntPtr _MPUSBWrite(IntPtr a, byte[] b, int c, ref int d, int e);
[DllImport("mpusbapi.dll")]
private static extern IntPtr _MPUSBRead(IntPtr a, byte[] b, int c, ref int d, int e);
[DllImport("mpusbapi.dll")]
private static extern IntPtr _MPUSBReadInt(IntPtr a, byte[] b, int c, int[] d, int e);
[DllImport("mpusbapi.dll")]
private static extern bool _MPUSBClose(IntPtr a);

//************* These are the global variables ***************************************//
private static IntPtr outPipe; // Pointer for the outpipe
private static IntPtr inPipe; // Pointer for the in pipe
private enum MP_MODE { MP_WRITE, MP_READ }; // List of modes that the pipes can be in
bool AttachedState = false;

public Form1()
{
InitializeComponent();
}

private void Form1_Load_1(object sender, EventArgs e)
{
// This is the default PIC vendor and product id numbers
byte[] vid_pid = Encoding.ASCII.GetBytes("vid_04d8&pid_000c");
// This is the output data stream pipe (End Point 1)
byte[] out_pipe = Encoding.ASCII.GetBytes("\\MCHP_EP1");
// This is the input data stream pipe (End Point 1)
byte[] in_pipe = Encoding.ASCII.GetBytes("\\MCHP_EP1");
ConnectData.Text = "Waiting";
int count = _MPUSBGetDeviceCount(vid_pid); // This checks for a connection
if (count > 0)
{
// This case if there is a connection
MessageBox.Show("Opened Data Pipes - Congratulations...");
ConnectData.Text = "Connected!!!!";
AttachedState = true; // tell the global variable so this can be used to make decisions
// If there is a connection then you want to open the in and out pipes for EP1
outPipe = _MPUSBOpen(0, vid_pid, out_pipe, (int)MP_MODE.MP_WRITE, 0);
inPipe = _MPUSBOpen(0, vid_pid, in_pipe , (int)MP_MODE.MP_READ, 0);

}
else
{
MessageBox.Show("Failed to open data pipes...please try again");
ConnectData.Text = "No Connection";
AttachedState = false; // tell the global variable so this can be used to make decisions
// Application.Exit(); // This will close the form if required
}
}

private void takeReading_Click(object sender, EventArgs e)
{
byte[] pipeOutData = {0x37}; // This is relating to the code on the PIC. If this is received then 10bit ADC value is returned
// This example uses the microchip example "USB Device - MCHPUSB - Generic Driver Demo"
// The PIC code in this example was compiled and programmed onto a PIC18F4550
// An Olimex USB development board was used along with a 0-5V input onto RA0 (analogue input 0)
// The PIC code in the example returns the 10-bit ADC value when a code of HEX:37 (= Decimal:55) is sent.

byte[] pipeInData = new byte[4]; // This is the incmming data array (pipeIn). It must be big enough to contain all the data expected on the pipe.

int ActualLength = 0; // Not sure why you need an actual length????

if (AttachedState == true)
{
_MPUSBWrite(outPipe, pipeOutData, 1, ref ActualLength, 1000); //Send the command now over USB
// The _MPUSBWrite takes the inputs (outpipe pointer, outpipe data to send, length of data, ??, ??)
_MPUSBRead(inPipe, pipeInData, 3, ref ActualLength, 1000); //Receive the answer from the device firmware through USB
// The _MPUSBRead takes the inputs (inpipe pointer, inpipe data, length of data, ??, ??)

// Want to have a look at the returned data using the text boxes below:
outBuf0.Text = Convert.ToString(pipeOutData[0]); // Show the first byte of the pipeOutData array

inBuf0.Text = Convert.ToString(pipeInData[0]); // Show the 1st byte of the pipeInData array
inBuf1.Text = Convert.ToString(pipeInData[1]); // Show the 2nd byte of the pipeInData array
inBuf2.Text = Convert.ToString(pipeInData[2]); // Show the 3rd byte of the pipeInData array
inBuf3.Text = Convert.ToString(pipeInData[3]); // Show the 4th byte of the pipeInData array
}
else
{
MessageBox.Show("No connection. Check hardware");
}
}
}
}

I have tried to include lots of comments on the program but to give a bit of an overview:
The program first needs to define the functions from the .dll. These are at the top of the form1.cs code. Each function must use a DllImport and be defined with the input and output parameters.
When the code is run, the Form1_Load_1 function fires (NOTE: You can get this by double clicking on the background of the form in design mode).
In this function the vid and pid (vendor and product ID numbers) are defined, along with the end points for the in and out pipes (see USB-in-a-nutshell). Only one set of in and out pipes is required in this application.
If there is a connected device with the vid and pid code then the you can say the device is connected (message box to say so) and the in and out pipes can be opened.
Check the vid and pid as I spent a while tyring to get this working but realising I had used a very slightly different pid. This must be EXACTLY correct.
When the button is clicked another function fires which sends data down the out pipe and reads any data back from the in pipe.
The 'USB Device - MCHPUSB - Generic Driver - C18 - PICDEM FSUSB.mcp' code does a few different things but I am only using the analogue read part. The documentation for these examples is bad (in my opinion...) and generally consists of 'read the code'. For whatever reason the program has been designed so that when a value of hex:37 or decimal:55 is sent then the PIC will read the analogue channel and send back the data. This is why the pipeOutData is set to 0x37 (HEX:37). A byte array for the incomming data is also defined but only 4 bytes long as we are only expecting a little bit of data back each time.
If the device is attached, then we send out the 0x37 value and then read whatever comes back to the 'in' pipe. The values of the out and in byte arrays are then shown on the form.
From the data read back I've managed to figure out a few things:
When the 0x37 is sent to the PIC the PIC will respond by sending back that value, then the next byte is part of the reading fom the ADC.
As the ADC is 10 bit, there are 1024 levels. This cannot be transmitted in one byte (1 byte = 8bits = 256 levels), so the next byte gives details of the extra 2bits.
So from the in pipe we get:
pipeIndata[0] = 55 (the code sent to the PIC = hex:37 or decimal:55)
pipeIndata[1] = 0-255 (the 8 least significant bits of the ADC value)
pipeIndata[2] = 0-3 (the 2 most significant bits of the ADC value)

So doing all this, you should be able to send data from the host computer to the PIC and receive data back from the PIC, all via USB. Good eh?
Thats my week challenge over so I think I deserve a pint. Please leave a comment or email if you would like any of the source code or have any ideas or suggestions.

Wednesday, 1 April 2009

Starting a PIC project in MPLAB

This post is a work in progress - I'll get back to it later....