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

1 comment: