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.

6 comments:

  1. Hello, nice work. Can you help me a bit, please. Please, make a step by step tutorial, how to use a usb in C for pic and in c# for visual express. Just to get the point, send/receive. It would be very nice. I searched throw the internet and you probably know, how it's hard to find a good tutorial.

    Thank you, a lot.

    My e-mAil: Dominykasvilp@gmail.com

    ReplyDelete
  2. Hi, kindly drop me an email on the full coding on C#'s side to princetan91@gmail.com. Your help would be greatly appreciated as I'm picking C# up by myself as well.

    ReplyDelete
  3. Murat Altıntas24 December 2012 at 05:20

    Hello your work is marvelous, but I'm having trouble your pic c program because piccoder website is closed for some reason. I'm also trying to reading adc from 18f4550 through usb I'm using c# too. but all my work has ended up as a failure could you please help me? Thanks in advance

    ReplyDelete
  4. hi!great job! can you send me an email with full coding on c# to el_dimious@hotmail.com
    you will help me a lot! thanks

    ReplyDelete
  5. can you give me your full code C#? thanks
    nguyentai325@gmail.com

    ReplyDelete
  6. HI! very nice and helpfull tools. Please send me a copy for a full coding on C#. Use: info.dali@yahoo.de.
    God bless you.

    Barry Yan

    ReplyDelete