ProcessingPIC32Communication

From Mech
Revision as of 09:29, 16 July 2010 by NickMarchuk (talk | contribs) (→‎UBW32 Setup)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Tutorial: Communication between a PIC32 and Processing

This tutorial will demonstrate how to use Processing to communicate with a microcontroller, specifically the UBW32, a PIC32MX460F512L device. An FTDI Serial to USB cable will be used to communicate over USB.

UBW32 Setup

Download and install the latest version of MPLAB (v8.53) here.

Also download and install the C compiler here and Microchip Applications Library (4/28/2010) here if you have never installed MPLAB on your computer.

Download this Media:ubw32_tutorial.zip that has code specific to the UBW32. Unzip it into the directory you will work in today.

Start a new project in MPLAB and include the files from ubw32_tutorial.zip. Note that this procdefs.ld file is specific to the UBW32, if you use the one for the NU32, you will overwrite your bootloader! Your project should look like:

Ubw32 objectwindow.jpg

Don't forget to tell the compiler where to find some source files:

Ubw32 buildoptionswindow.jpg

We will program the UBW32 to:

  • Interrupt when it receives a character over RS232
  • Read an analog value and send it over RS232 to the PC
  • Change how often it outputs data according to the character it receives

Specifically:

  • Initially output at 5Hz
  • Output at 10Hz if a "b" is received
  • Output at 100Hz if a "c" is received
  • Output at 5Hz if a "a" is received
  • Stop outputting if a "p" is received
  • Start outputting if a "t" is received
  • Toggle an LED on the UBW32 board if a "w" is received

The following code achieves this:


#include "GenericTypeDefs.h"
#include "Compiler.h"
#include "HardwareProfile.h"

// NOTE THAT BECAUSE WE USE THE BOOTLOADER, NO CONFIGURATION IS NECESSARY
// THE BOOTLOADER PROJECT ACTUALLY CONTROLS ALL OF OUR CONFIG BITS

#define SYS_FREQ (80000000L)
#define DESIRED_BAUDRATE (115200) // The desired rs232 BaudRate

unsigned short int channel4;	// conversion result as read from result buffer

char UART1_out_buffer[64];
int hz5 = 62500; // 80000000/256/5
int hz10 = 31250; // 80000000/256/10
int hz100 = 3125; // 80000000/256/100

// functions
void init_serial();
void init_analog();
void init_timer(int freq);

int main(void)
{   
    // Configure the proper PB frequency and the number of wait states
	SYSTEMConfigPerformance(80000000L);

	// Turn off JTAG so we get the pins back
 	mJTAGPortEnable(0);

    //Initialize all of the LED pins
	mInitAllLEDs();

    // enable multi-vector interrupts
	INTEnableSystemMultiVectoredInt();

	init_serial();
	init_timer(hz10);
	init_analog();

    while(1)
    {
		// empty, interrupt driven code	    
    }
}

// UART 1 interrupt handler, gets info from processing
// it is set at priority level 1
void __ISR(_UART1_VECTOR, ipl1) IntUart1Handler(void)
{
	char data;
	// Is this an RX interrupt?
	if(mU1RXGetIntFlag())
	{
		// Clear the RX interrupt Flag
	    mU1RXClearIntFlag();

		data = (char) ReadUART1();
		
		// Echo what we just received.
		//putcUART1(data);
		
		if (data == 'a'){
			init_timer(hz5);
		}
		else if (data == 'b'){
			init_timer(hz10);
		}
		else if (data == 'c'){
			init_timer(hz100);
		}
		else if (data == 'p'){
			mT3IntEnable(0);
		}
		else if (data == 't'){
			init_timer(hz5);
		}
		else if (data == 'w'){
			mLED_3_Toggle();
		}
	}

	// We don't care about TX interrupt
	if ( mU1TXGetIntFlag() )
	{
		mU1TXClearIntFlag();
	}
} // end UART1 interrupt

// interrput code for the timer 3, priority level 2
void __ISR( _TIMER_3_VECTOR, ipl2) T3Interrupt( void)
{
	// read analog
	channel4 = ReadADC10(0); // read the result of channel 4

	sprintf(UART1_out_buffer,"a %d\r\n",channel4);
	putsUART1(UART1_out_buffer);

	// clear interrupt flag and exit
	mT3ClearIntFlag();
} // end T3 Interrupt

void init_serial() {
	int	pbClk;
	// Configure the system performance
	pbClk = SYSTEMConfigPerformance(SYS_FREQ); 

	// define setup Configuration 1 for OpenUARTx
	#define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN
	
	// define setup Configuration 2 for OpenUARTx
	#define config2	UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR

    // Open UART1 with config1 and config2
	OpenUART1( config1, config2, pbClk/16/DESIRED_BAUDRATE-1); //U1RX=F2, U1TX=F8

	// Configure UART1 RX Interrupt with priority 1
	ConfigIntUART1(UART_INT_PR1 | UART_RX_INT_EN);
} // end init serial

void init_analog() {
	// configure and enable the ADC
	CloseADC10();	// ensure the ADC is off before setting the configuration

	// define setup parameters for OpenADC10
	// Turn module on | output in integer | trigger mode auto | enable  autosample
	#define PARAM1  ADC_MODULE_ON | ADC_FORMAT_INTG | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_ON

	// define setup parameters for OpenADC10
	// ADC ref external    | disable offset test    | enable scan mode | perform 2 samples | use one buffer | use MUXA mode
	#define PARAM2  ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_ON | ADC_SAMPLES_PER_INT_2 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF

	// define setup parameters for OpenADC10
	// 				  use ADC internal clock | set sample time
	#define PARAM3  ADC_CONV_CLK_INTERNAL_RC | ADC_SAMPLE_TIME_15

	// define setup parameters for OpenADC10
	// set AN4 
	#define PARAM4	ENABLE_AN4_ANA

	// define setup parameters for OpenADC10
	// do not assign channels to scan
	#define PARAM5	SKIP_SCAN_AN0 | SKIP_SCAN_AN1 | SKIP_SCAN_AN2 | SKIP_SCAN_AN3 | SKIP_SCAN_AN6 | SKIP_SCAN_AN7 | SKIP_SCAN_AN8 | SKIP_SCAN_AN9 | SKIP_SCAN_AN10 | SKIP_SCAN_AN11 | SKIP_SCAN_AN12 | SKIP_SCAN_AN13 | SKIP_SCAN_AN14 | SKIP_SCAN_AN15

	// use ground as neg ref for A | use AN4 (B4) and AN5(B5) for input A
	// configure to sample AN4
	SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF); // configure to sample AN4
	OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using parameter define above

	EnableADC10(); // Enable the ADC
 	while ( ! mAD1GetIntFlag() ) { } // wait for the first conversion to complete so there will be valid data in ADC result registers
} // end init_analog

void init_timer(int freq) {
	// init Timer3 mode and period (PR3)
	OpenTimer3( T3_ON | T3_PS_1_256 | T3_SOURCE_INT, freq); // 30Hz
	mT3SetIntPriority( 2); 	// set Timer3 Interrupt Priority
	mT3ClearIntFlag(); 		// clear interrupt flag
	mT3IntEnable( 1);		// enable timer3 interrupts
}

Test the UBW32 code

Plug your FTDI cable into your computer and determine its port number (ex COM3). Run HyperTerminal (in XP only) or PuTTY. Open the correct com port and set the baud. Turn on the UBW32. Data should start to stream to the terminal. Type each character programmed and see the effect.

Processing Setup

Download Processing (v1.2.1).

Open Processing and save the blank sketch. This will create a folder for all the files the sketch will use.

Download and extract the controlP5 GUI library for Processing. Put it a folder called "libraries" in the Processing folder of My Documents (the same root directory as the folder that was made when you saved your blank sketch).

The sketch we will make will open the UBW32's virtual serial port and communicate with the UBW32. It will plot the analog values the UBW32 sends and have buttons to stop and start the data, and change the data rate.

The following code achieves this:



// serial variables
import processing.serial.*; // serial library
Serial[] myPorts = new Serial[1]; // lets only use one port in this sketch

// GUI variables
import controlP5.*; // controlP5 library
ControlP5 controlP5; // create the handler to allow for controlP5 items
Textlabel txtlblWhichcom; // text label displaying which comm port is being used
ListBox commListbox; // list of available com ports

// globals
int num_x = 200;
int num_y = 200;
int buffer_index = 0;
int[] rolling_buffer = new int[num_x];
int draw_x_offset = 200;
int draw_y_offset = 100;

// setup
void setup() {
  size(600,400);
  frameRate(30);

  controlP5 = new ControlP5(this); // initialize the GUI controls

    println(Serial.list()); // print the comm ports to the debug window for debugging purposes

  // make a listbox and populate it with the available comm ports
  commListbox = controlP5.addListBox("myList",5,25,120,120); //addListBox(name,x,y,width,height)
  commListbox.captionLabel().toUpperCase(false);
  commListbox.captionLabel().set("Listbox label");
  for(int i=0;i<Serial.list().length;i++) {
    commListbox.addItem("port: "+Serial.list()[i],i); // addItem(name,value)
  }

  // text label for which comm port selected
  txtlblWhichcom = controlP5.addTextlabel("txtlblWhichcom","No Port Selected",150,25); // textlabel(name,text,x,y)
  
  // a button to send the letter a
  controlP5.addButton("Send_5Hz",1,5,210,80,19); // buton(name,value,x,y,width,height)
  
  // a button to send the letter b
  controlP5.addButton("Send_10Hz",1,5,230,80,19); // buton(name,value,x,y,width,height)
  
  // a button to send the letter c
  controlP5.addButton("Send_100Hz",1,5,250,80,19); // buton(name,value,x,y,width,height)
  
  // a button to send the letter p
  controlP5.addButton("Send_Stop",1,5,270,80,19); // buton(name,value,x,y,width,height)
  
  // a button to send the letter t
  controlP5.addButton("Send_Start",1,5,290,80,19); // buton(name,value,x,y,width,height)
  
  // a button to send the letter w
  controlP5.addButton("Send_Toggle",1,5,310,80,19); // buton(name,value,x,y,width,height)
  
  // initialize the rolling buffer
  for(int i = 0; i < num_x; i++) {
   rolling_buffer[i] = 200;
  }
}

// infinite loop
void draw() {
  background(0,0,0);
  
  // draw the data in the buffer
  strokeWeight(4); // weight of lines between data pnts
  stroke(0,255,0); // green line
  for(int i = 0; i < num_x-1; i++) {
   line(i+draw_x_offset,rolling_buffer[i]+draw_y_offset,i+1+draw_x_offset,rolling_buffer[i+1]+draw_y_offset); 
  }
}

// print the name of the control being triggered (for debugging) and see if it was a Listbox event
public void controlEvent(ControlEvent theEvent) {
  // ListBox is if type ControlGroup, you need to check the Event with if (theEvent.isGroup())to avoid an error message from controlP5
  if (theEvent.isGroup()) {
    // an event from a group
    if (theEvent.name()=="myList") {
      InitSerial(theEvent.group().value()); // initialize the serial port selected
      //println("got myList"+"   value = "+theEvent.group().value()); // for debugging
    }
  }
  else {
    //println(theEvent.controller().name()); // for debugging
  }
}

// run this when button is triggered, send a char
public void Send_5Hz(int theValue) { 
  myPorts[0].write("a"); // write an a
  println("sent a"); // print it to the debug screen as well
}

// run this when button is triggered, send a char
public void Send_10Hz(int theValue) { 
  myPorts[0].write("b"); // write an a
  println("sent b"); // print it to the debug screen as well
}

// run this when button is triggered, send a char
public void Send_100Hz(int theValue) { 
  myPorts[0].write("c"); // write an a
  println("sent c"); // print it to the debug screen as well
}

// run this when button is triggered, send a char
public void Send_Stop(int theValue) { 
  myPorts[0].write("p"); // write an a
  println("sent p"); // print it to the debug screen as well
}

// run this when button is triggered, send a char
public void Send_Start(int theValue) { 
  myPorts[0].write("t"); // write an a
  println("sent t"); // print it to the debug screen as well
}

// run this when button is triggered, send a char
public void Send_Toggle(int theValue) { 
  myPorts[0].write("w"); // write an a
  println("sent w"); // print it to the debug screen as well
}

// initialize the serial port selected in the listBox
void InitSerial(float portValue) {
  println("initializing serial " + int(portValue) + " in serial.list()"); // for debugging

  String portPos = Serial.list()[int(portValue)]; // grab the name of the serial port 
  
  txtlblWhichcom.setValue("COM Initialized = " + portPos);
  
  myPorts[0] = new Serial(this, portPos, 115200); // initialize the port

  // read bytes into a buffer until you get a linefeed (ASCII 10):
  myPorts[0].bufferUntil('\n');
  println("done init serial");
}

// serial event, check which port generated the event 
// just in case there are more than 1 ports open
void serialEvent(Serial thisPort) { 
  // variable to hold the number of the port:
  int portNumber = -1;

  // iterate over the list of ports opened, and match the 
  // one that generated this event:
  for (int p = 0; p < myPorts.length; p++) {
    if (thisPort == myPorts[p]) {
      portNumber = p;
    }
  }

  // read the serial buffer until a newline appears
  String myString = thisPort.readStringUntil('\n');
  
  // the following is for streaming packets of data that need to be parsed

  // if you got any bytes other than the newline
  if (myString != null) {
    
    String[] match_a = match(myString, "a");
    
    if (match_a != null) { // data starting with an a
      myString = trim(myString); // ditch the newline
      int[] sensor = int(split(myString.substring(2), ' '));
      //println(sensor[0]);
      rolling_buffer[buffer_index] = int(map(sensor[0],0,1023,num_y,0)); // map(current number,min current number, max current number, min new number, max new number)
      //println(int(map(sensor[0],0,1023,num_y,0)));
      buffer_index++;
      if (buffer_index >= num_y) {
        buffer_index = 0;
      }
    }    
  }
} // end serialEvent