Wednesday 29 July 2009

Testing C code

I was relatively new to C programming, but it is much easier than trying to mess around with the assembly level language. My problem was that I did not always know the right thing to do in C code. Testing using the C compiler and trying it out on the microcontroller was OK but not really suitable for small tests of C code I was not so sure about. my main problem was confusion about pointers - I was using some pointers but not getting the correct information. I needed to try out some simple code to test out what I would do in the main C code.
I needed some program in which to run short snippets of C code. I found a program called code::blocks. This is a free open source cross platform C++ IDE. This runs on Vista. Download the binary release with 'mingw' in the title as this includes a compiler.
One downloaded you can open a new projects and the choice of using C or C++ comes up. I chose C.
I found that it did not run the compiler. To sort this out you must go to 'Settings' then 'Compiler and Debugger...' then click on the 'Toolchain Executables' tab. Ensure that the path for the compiler installation directory is correct (mine was pointing at the wrong place). You would need to search the installation directory to find it. The program should then work.
I will be having a play around with the program, but it does seem very nice and easy to use so far.

Wednesday 10 June 2009

PIC 18F4550, Interrupts and USB

One of the main challenges for this unit is to read data, store it and also have USB connectivity.
Obviously the data must be read with high precision timings so I decided to use an interrupt in order to accurately take data samples.
The PIC18F4550 is capable of doing high and low priority interrupt service routines.
I decided to use a high priority interrupt service routine (HISR) for the data acquisition samples and a low priority interrupt service routine (LISR) for doing other things, such as checking if any switches have been pressed and updating the LCD display.

I based all of the code on the Microchip USB framework 2.4 examples, using the 'USB Device - MCHPUSB - Generic Driver Demo' as the basis for my program.

After a lot of going around in circles here are some notes and pointers and things I have learnt:

You must use the polling method for USB if you are using the HISR for something else.
As log as the USB routine is polled every 100ns or so then its all OK. Do not try to use interrupts along with interrupt driven USB, as they dont get along, as much as I have found. There is probably a way to do this but still...

The clock frequency is not the same as the oscillator frequency. I am using an oscillator freq of 20MHz, but, in order to use the USB routines, there is an internal phase locked loop (PLL) which changes the frequency to 48MHz. This is the clock frequency being used. This messed up all my timings and caused much head scratching for quite a while.

Most other things are pretty simple - I'm using timer0 for the HISR and timer 2 for the LISR, with priority set in the interrupt set-up parameters.

I can now read data in the HISR, display data and look at the switches in the LISR, all while the USB connection is working in the background.

Now I need to sort out saving data onto SDcards....

Thursday 14 May 2009

PIC to C# data transfer via USB

This has been a bit of a break through.
This post details a C# program and PIC code to send data taken on a PIC analogue input to a C# program and display the data as a time series.

The first changes were made to the PIC code. Please see previous posts for some background to this. Basically I have been using code from here (from www.piccoder.co.uk) . This follows the generic USB code from Microchip but with some additions.
The Piccoder script had been designed to change LEDs or do a summation when a certain data code was sent to the PIC via the USB.

I have added another case to add another function to the PIC. This is set up so that when I send the PIC a code of 0x40 the PIC will read the ADC and send the data back via the USB pipe.

There are two sections of code to add to the Piccoder example:
One is in user.h. Add the line for the READ_ADC as shown here:
enum
{
READ_VERSION = 0x00,
ID_BOARD = 0x31,
UPDATE_LED = 0x32,
READ_ADC = 0x40,
DO_SUM = 0xEE,
RESET = 0xFF
}CMD;


The other is in user.c. Put this as an additional case:
case READ_ADC:
// This case has been added. When 0x40 is sent then the device will do this:
// Want it to read in a value from the ADC and return it via the USB pipe.

ConvertADC(); // Start conversion
while( BusyADC() ); // Wait for completion
ADCint=ReadADC(); // read the ADC


ADC.v[0] = ADRESL; // Save the lower part (8 bits)
ADC.v[1] = ADRESH; // Save the upper part (2 bits)

dataPacket._byte[1]=LSB(ADC);
dataPacket._byte[2]=MSB(ADC);

counter = 0x03; // This is a command saying that data should be sent back

DisplayValue(ADCint); // This displays the ADC value on the LCD screen

break;


The DisplayValue function is an additional function to put the data onto an attached LCD. Dont worry about this at the moment and comment that line out. I might do another post about that sometime soon...

oh yes - also you must initialise the ADC and include the ADC files at the top of that code:
Put this in the include list at the top:
#include '<'adc.h'>' (Do not use the ' quotes - they just make the arrow brackets appear)

And also put this initialise function into your code:
/************Initialise ADC Subroutine********************/
void mInitADC(void)
{
/* Initialise everything for the ADC. MUST include adc.h
/** ADC ***********************************************************/
// Inputs: AN0, AD Clock = Fosc/32, Acq.Time = 6TAD, Right justified, Vref+=VDD, Vref-=VSS
OpenADC( ADC_FOSC_32 & ADC_RIGHT_JUST & ADC_6_TAD, ADC_CH0 & ADC_REF_VDD_VSS, 14 );

return;
}

This function should also be called in the main program initialise routine.
OK so that is the PIC code sorted. This should compile OK. All that has been added is an additional case which, when called with the value 0x40, will return with an ADC value. You should note that the USB can only send bytes so the 10bit ADC value must be converted into 2 bytes, one with thr 8 least significant bits (LSB) and one with the 2 most significant bits (MSB). These two values are sent back as two bytes on the data packet. The first byte (dataPacket._byte[0]) will be the code sent to the PIC.

Next we need to look at the C# code. It turned out it was pretty easy to view the value as a number (just look at the data sent back, just like my previous post) but turning that into a graph became a bit tricky.
C# does not come with any built-in graphing tools. You can produce your own by drawing evey element of the graph but that is a LOT of work and has been done before. So I looked into open source options. One suggested to me (cheers MysteriousLee) was ZedGraph.
ZedGraph is an open source charting class library. Download the source file from the download pages.
You then need to ensure that C# knows about it so you must right click on the background to the 'Toolbox'. Click on 'Choose Items...' and it will bring up a window. Click on 'Browse' and then navigate to where you saved the ZedGraph files. Search around in the there until you find the ZedGraph.dll file (it should be pretty easy to find) and click OK. This will allow you to insert a ZedGraphControl onto your form. Try this out - it brings up a whole chart with axis etc...
You must also make sure that you insert:
using ZedGraph;

at the top of the form so that everything will work.

So now you've got a graph. But how do you make it display anything?
Luckily, like most things in life, someone has done it before.
In this case you are looking for an example of dynamic data. An example using ZedGraph can be found here . This was the one I used.
I'm still playing with all the functions and making it do what I want, but here is the code from my first trial which just shows the ADC data being read in and displayed.
This follows on from my other C# programs so the program.cs file has not changed (see previous posts). So here is 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
using ZedGraph; // For graphing stuff - ZedGraph used here....

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;
int valueADC; // This is to store the calculated ADC real 10bit value
int tickStart = 0; // for the graph

public Form1()
{
InitializeComponent();


//******** Initialise the graph**************
GraphPane myPane = zgc1.GraphPane;
myPane.Title.Text = "Test of Dynamic Data Update with ZedGraph\n" +
"(After 25 seconds the graph scrolls)";
myPane.XAxis.Title.Text = "Time, Seconds";
myPane.YAxis.Title.Text = "Sample Potential, Volts";

// Save 1200 points. At 50 ms sample rate, this is one minute
// The RollingPointPairList is an efficient storage class that always
// keeps a rolling set of point data without needing to shift any data values
RollingPointPairList list1 = new RollingPointPairList(1200);


// Initially, a curve is added with no data points (list is empty)
// Color is blue, and there will be no symbols
LineItem curve1 = myPane.AddCurve("Channel 1", list1, Color.Blue, SymbolType.None);


// Save the beginning time for reference
tickStart = Environment.TickCount;

// Scale the axes
zgc1.AxisChange();

}

private void Form1_Load(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
// The generic code is _MPUSBOpen( device number, vid_pid, end point, type of connection (0=write(out),1=read(in))
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 timer1_Tick_1(object sender, EventArgs e)
{
byte[] pipeOutData = new byte[3]; // This defines the size (4 bytes) of the output data message (pipeOut)
byte[] pipeInData = new byte[3]; // This is the incmming data array (pipeIn).


pipeOutData[0] = 0x40; // Send the command to the PIC whic will return the ADC value

// 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, 4, ref ActualLength, 1000); //Send the command now over USB
// The _MPUSBWrite takes the inputs (outpipe pointer, outpipe data to send, length of data, Sent data length, Receive Delay)
_MPUSBRead(inPipe, pipeInData, 4, ref ActualLength, 1000); //Receive the answer from the device firmware through USB
// The _MPUSBRead takes the inputs (inpipe pointer, inpipe data, length of data, Sent data length, Receive Delay)

// Want to have a look at the returned data using the text boxes below:
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

valueADC = pipeInData[1] + (256 * pipeInData[2]); // pipeInData[1] has the 8 LSB and pipeInData[2] has the 2 MSB
ADCvalue.Text = Convert.ToString(valueADC); // Display the actual ADC value

test.Text = Convert.ToString((valueADC * 100 / 1023)); // TEST line - just to check things out
}

//*************This is all for the graph test********************//
// Make sure that the curvelist has at least one curve
if (zgc1.GraphPane.CurveList.Count <= 0)
return;

// Get the first CurveItem in the graph
LineItem curve1 = zgc1.GraphPane.CurveList[0] as LineItem;


// If no curves initialised then stop
if (curve1 == null ) // If no curves initialised then stop
return;

// Get the PointPairList
IPointListEdit list1 = curve1.Points as IPointListEdit;


// Time is measured in seconds
double time = (Environment.TickCount - tickStart) / 1000.0;

// Display the ADC value...
list1.Add(time, (valueADC*500/1024));



// Keep the X scale at a rolling 30 second interval, with one
// major step between the max X value and the end of the axis
Scale xScale = zgc1.GraphPane.XAxis.Scale;
if (time > xScale.Max - xScale.MajorStep)
{
xScale.Max = time + xScale.MajorStep;
xScale.Min = xScale.Max - 30.0;
}

// Make sure the Y axis is rescaled to accommodate actual data
zgc1.AxisChange();

// Force a redraw
zgc1.Invalidate();
}



}
}


Hopefully the comments help you out. Again - please email if you would like the full C# files. This initialised the graph. Data is put into a buffer of 1200 samples long. Everything works on a timer function set to whatever you like (I'm using 10milliseconds). Each time there is a timer click then the program will send 0x40 to the PIC, get a returned ADC value and then add this into the list of displayed data which will then be shown on an updated graph.
It all works pretty well and looks quite nice.
i need to know more about the ZedGraph commands but that is it for this post.

Notes on file names in MPLAB

I was trying to create a new project based upon a previous project, but in a different folder. Easy right?
I thought just copy to another folder and rename. This did not work so well.
Here are some things to consider:
Firstly move the files (don't forget to include everything like the .c and .h files) to your chosen location.
Then open the project.
Choose 'Save Project as' from the 'Project' Menu. (I would not use the 'save workspace as' option as this caused me some hassles).
You then need to make sure that the directories all point to the correct place. Do this by checking in Project - Build Options - Project. Then click on the Directories tab and go through all the directories paths in the drop down menu.
Lastly - the code will not fully compile if the full path name exceeds 62 characters. This was really annoying and not quite sure why this is. The code will build but the correct hex output file will not be created. So keep this in mind when doing all these steps.

Wednesday 13 May 2009

Controlling PIC through C# code

In this post I will try to explain how to send and receive data to a PIC from C# code via the USB interface.
In this example I will be using PIC code that has already been written.
The code along with some good notes is available here (from www.piccoder.co.uk) .
I will be using the PIC code from this example, but with my own C# code.
First thing to do is to check that the example works correctly on the device.
To do this I loaded the code onto the PIC.
I then opened the supplied C# code and tried to run that.
The code compiled and ran but did nothing with the PIC.
This was a bit strange and took a while to sort out. It turns out that there are some diferences between the 2005 and 2008 versions of C# Visual Studio and also with Vista and XP. The code was written using a machine on XP, but I am using Vista (please dont if you have the choice...). The only way I could get the code to work was to take the .exe output file (found in the obj/debug/ folder and entitled "WindowsApplication3.exe"). This can then be run but with XP compatibility mode. This can be done by right clicking on the file and looking at the file properties. Under the 'Compatibility' heading you can choose to run the program in Windows XP (service pack 2) Compatibility mode. Then the program will work correctly.

This was a hassle and the available C# code cannot be easily used/debugged as it will not run under Vista and the newer version of Visual Studio. I want to be able to use the same PIC code and do basically the same thing (switch LEDs on/off via C# program and USB). So lets try that....

A C# program was written that would control the two output LEDs on the PIC. These were on pins RD2 and RD3 and are labelled LED3 and LED4 (check the piccoder article ).
Nothing really clever in this code, just a very simple way of showing how to send data.
Basically when a check box is checked in the C# form then data is sent to the PIC. This data consists of 3 bytes. The first byte is a code to tell the PIC what to do (in this case the code used is 0x32 which means change the LEDs) the next item is the LED number (in this case either 0x03 or 0x04) and the last byte is the satus of the LED (either 0x01 (ON) or 0x00 (OFF)). Easy eh?

The code is here. Please email for the full files as, unless you can recreate the form, then it is hard to use this code in C#. One of these days I will sort out a zip file for all these examples.

Here is 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());
}
}
}

Next is the form.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(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
// The generic code is _MPUSBOpen( device number, vid_pid, end point, type of connection (0=write(out),1=read(in))
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);
Set_LED(0x03, false); // Initialise the output pins to zero
Set_LED(0x04, false); // Initialise the output pins to zero
}
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
}
}


public void Set_LED(uint LED, bool LEDstate)
{
// This is a subroutine which, when called, will update the LED status.
// This is called with two variables: LED = number of LED to change & LEDstate = state to put LED into

// From the PIC code from www.piccoder.co.uk this is required:
// To set the LED's, the host must send the UPDATE_LED
// command which is defined as 0x32, followed by the LED to update,
// then the state to chance the LED to.
//
// i.e. <0x01><0x01>
//
// Would activate LED 1
//
// ENSUR YOU LOOK AT THE CORRECT LED - I was using LEDs 3 and 4.
// The receive buffer size must be equal to or larger than the maximum
// endpoint size it is communicating with. In this case, it is set to 64 bytes.

byte[] pipeOutData = new byte[3]; // This defines the size (4 bytes) of the output data message (pipeOut)

pipeOutData[0] = 0x32;
pipeOutData[1] = (byte)LED;
pipeOutData[2] = (byte)(LEDstate?1:0);

byte[] pipeInData = new byte[3]; // 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, 4, ref ActualLength, 1000); //Send the command now over USB
// The _MPUSBWrite takes the inputs (outpipe pointer, outpipe data to send, length of data, Sent data length, Receive Delay)
_MPUSBRead(inPipe, pipeInData, 4, ref ActualLength, 1000); //Receive the answer from the device firmware through USB
// The _MPUSBRead takes the inputs (inpipe pointer, inpipe data, length of data, Sent data length, Receive Delay)

// 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
outBuf1.Text = Convert.ToString(pipeOutData[1]); // Show the 2nd byte of the pipeOutData array
outBuf2.Text = Convert.ToString(pipeOutData[2]); // Show the 3rd 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

}
else
{
MessageBox.Show("No connection. Check hardware");
}

}

private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (((CheckBox)sender).Checked == true)
{
((CheckBox)sender).Text = "LED 1 ON";
Set_LED(0x03, true);
}
else
{
((CheckBox)sender).Text = "LED 1 OFF";
Set_LED(0x03, false);
}

}

private void LEDcheckBox2_CheckedChanged(object sender, EventArgs e)
{
if (((CheckBox)sender).Checked == true)
{
((CheckBox)sender).Text = "LED 2 ON";
Set_LED(0x04, true);
}
else
{
((CheckBox)sender).Text = "LED 2 OFF";
Set_LED(0x04, false);
}
}


}
}


The next thing is to now change the PIC code that it will send back analogue data at regular intervals and this data will be displayed on a moving graph....

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.

Tuesday 14 April 2009

USB, C# and Velleman K8055 USB Interface Board

The first step to using a PIC with USB connectivity was to find out a bit about USB communications.
The first thing to do was read up about USB protocol.
The 650ish page USB document is something I have not yet read (I have a little bit of a life) but I think there will be times I need to reference it.
The guide 'USB in a nutshell' is a great simple starter document.
So I read that and had a bit of an idea about the USB protocol. I learn better by doing practical things so I set myself a challenge to write a simple program to talk via USB to some inputs/outputs.
I saw the Velleman kit K8055 USB Experiment Interface Board from Maplin Electronics (around £30) and thought that would be useful to have a play around with. This was to learn about the host computer side of USB interfacing with a known and working hardware board. I could then play with the PIC hardware later.
I had decided to write my code in C# using the visual studio 2008 express edition as I had been told of the benefits - ease of use and benefits of both visual basic and C++. I'd not used it before so that will also be a learning curve.

The kit K8055 came with some example code written in Visual Basic and some code written in C++. It also came with a DLL to talk to the board (K8055D.dll).

I have now played around with C# and have managed to figure out how C# code can be written in order to use the functions within the K8055D.dll. This took a while so I'll try and explain it here...

Firstly the K8055D.dll file must be put into the C:\Windows\System32 folder.
(I made the mistake of using the updated .dll file that Velleman supply. This caused an amount of head scratching as the open device functions are slightly different.
I reverted back to the older K8055D.dll file.)

The functions within the .dll are outlined in the document: "MAN_UK_K8055_DLL.pdf" supplied with the kit. This gives all the names of the functions and some descriptions of their function - though it's pretty obvious.

Next came sorting out the C# code. The process involves using the DllImport command which imports the various functions from the .dll.

Two main things to realise:

1: You must put the following with the 'using' commands at the top of the code:
using System.Runtime.InteropServices;

This will allow the DllImport command to work.

2: You then have to import the .dll function.
This is done with the generic code:
[DllImport("DllName.dll")]
static extern type functionName(param1, param2....);

Where the bits in italic are changed depending upon your application.
An actual example from my code is:
[DllImport("K8055D.dll")]
static extern long OpenDevice(long CardAddress);

This Opens the device depending upon the card address. The function can then be called, for example:
DevOpen = OpenDevice(0);

This opens device 0 and returns the card address (which is a long integer).

The code in full is here:

Within my program.cs:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

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


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

}
}

And within a form:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace MyProject1
{
public partial class Form1 : Form
{
[DllImport("K8055D.dll")]
static extern long OpenDevice(long CardAddress);
[DllImport("K8055D.dll")]
static extern void CloseDevice();
[DllImport("K8055D.dll")]
static extern int SearchDevices();
[DllImport("K8055D.dll")]
static extern void SetAllDigital();
[DllImport("K8055D.dll")]
static extern void ClearAllDigital();
[DllImport("K8055D.dll")]
static extern void SetDigitalChannel(long SetChannel);

public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
long DevOpen;
DevOpen = OpenDevice(0);
textBox1.Text = Convert.ToString(DevOpen);
button1.Text = "Device Connected";
SetDigitalChannel(1);
}

private void LED1_Click(object sender, EventArgs e)
{

if (LED1.Text == "I am ON")
{
LED1.Text = "I am OFF";
ClearAllDigital();
}
else
{
LED1.Text = "I am ON";
SetAllDigital();

}
}

private void button2_Click(object sender, EventArgs e)
{
CloseDevice();
// this.Close();

}


}

}


I hope that is helpful to people trying to do the same thing.
One thing to note is the with de-bugging turned on, when the form is closed then you will get a "vshost.exe has stopped working" error. This does not happen if you run without de-bugging, or if you run the .exe file for the project. I am unsure why this happens - if anyone has an idea of how to correctly stop this problem, please add a comment or email.