PIC32MX: Driving a Stepper Motor

From Mech
Revision as of 13:53, 16 February 2010 by JarvisSchultz (talk | contribs) (→‎Code)
Jump to navigationJump to search

Original Assignment

Do not erase this section!

Your assignment is to create code that reads a voltage from a potentiometer and controls a stepper motor to rotate at a proportional speed. The middle range of the analog input (i.e., 1.65 V) corresponds to zero speed, the minimum is maximum backward speed, and the maximum is maximum forward speed. You will create an interrupt service routine that is called every 500 microseconds. Each time through the routine, you will add 1 to a counter. When that counter (time, in units of 500 microseconds) exceeds the inverse of the desired speed (time per step), you will change the digital outputs controlling the stepper motor to the next position, depending on the direction of motion, and set the counter back to zero.

To do this, you must create a function that takes the analog input, turns it into a speed, then turns it into an inverse of speed that the ISR uses. Clearly the shortest allowable inverse of speed is 1 unit (500 microseconds per step). But can your motor really do this, or does it just vibrate if you try to rotate that fast? Based on your experiments, you should find the maximum speed that the motor actually tracks and set that as a constant. Then 3.3 V and 0 V map to this speed, in opposite directions.

You will use a geared stepper motor that we provide.

Overview

The goal of this page is to explain several pieces of code and a corresponding circuit that were designed to drive a bipolar stepper motor using a PIC32MX460F512L. Useful information about stepper motors in general can be found here and here. The specific motor that we are using is a Portescap 26M048B1B-V27. This 5V bipolar stepper motor has 48 steps per revolution (7.5 degrees per step), a 20:1 gearhead (for an output step angle of 0.375 degrees), and a resistance per phase of 19.80 ± 10% Ω. The datasheet for this motor can be found here.

In this code, the user executes a processing code, and uses it to communicate with the PIC32. More information about processing, as well as a free download of the program can be found here. The communication is handled with an RS232 cable and the PIC32’s UART peripheral. The processing code provides the user with the ability to send three functionally different sets of information to the PIC32. The three sets of that can be sent are as follows:

1) The ability to transmit some motor speed to the PIC32, and the PIC32 will drive the motor at that speed until the user changes it by transmitting a new speed. They can transmit a positive or a negative speed to change the direction.

2) The ability to transmit a motor speed and number of steps to the PIC32, and the PIC32 will drive the motor at the desired speed until the desired number of steps has been reached, and then it will stop the motor.

3) The ability to send a command to stop the motor right where it is.

The code uses one of the timer modules on the PIC32 to generate the correct pulse train to drive the motor. The desired speed of the motor (in steps/ second) is input into a function that calculates how often the motor should step, and in-turn what value needs to be stored in the timer’s period register such that the timer interrupt service routine is called every time that the motor steps. This ISR is what handles the setting of the phases.

Circuit

General H-Bridge Bipolar Drive Circuit

A bipolar stepper motor has two separate phases. The steps are created by changing the direction of the current flow through each of the phases in the correct sequence. In this circuit, one-half of an h-bridge is connected to each of the four leads of the stepper motor. These h-bridge transistors are used so that the 3.3V available from the PIC32 can be used as control logic alone, and the primary current for the motor can be sourced from somewhere else. These also help to isolate the motor from the PIC32. A general image of this h-bridge setup can be seen at right.


We used an L293D (Digi-Key part number 497-2936-5-ND) chip for the h-bridges. These can be purchased at Digi-Key here.

An image of the full circuit is shown below.
Bipolar Stepper Motor Drive Circuit JSJW.JPG
In the above circuit, pins D0-D3 of the PIC32 are used to as inputs to the h-bridge chip. The timers in the code switch these pins high and low to step the motor. The motor's output leads need to be correctly connected to the h-bridge outputs. Some motor manufacturers specify which color wires correspond to which phase, but some do not. If unsure which wire is which, use a multimeter, and test the resistance of each of the pairs of wires. When one pair shows a resistance that is significant, you know that these two wires are internally connected. This means that they will either need to be connected to outputs 1 and 2 or outputs 3 and 4 of the h-bridge. U2TX and U2RX are connected to an RS232 cable that is correctly grounded and connected to a PC. This cable is used for sending signals to the motor and receiving current motor step counts from the PIC. An interesting note is that this circuit and corresponding code will work for other four-lead bipolar stepper motors as well. If using another motor, the motor supply voltage simply has to be adjusted to match this motor. Be careful not to exceed the limits of the h-bridge chip!

Code

The entire pastable code for the PIC32 is in the text box below, and a link can be found here. Compiling this code and loading it onto the PIC32 is the bare-minimum that is required for controlling the stepper motor. If this is the case, motor control is handled through some serial communication terminal program (hyperterminal, putty, or realterm). Be sure to match the baudrate (115200), the stop bits (1), the parity bits (none), and the data flow (none). In the main function of the program it is possible for the user to extract the correct format for the motor control data. But, for simplicity, it is summarized here as well. A total set of control data consists of 10, 8-bit (one byte) ascii characters. The first character is either an 's', 'd', or 'p'. An 's' implies that the user simply wants to set a motor speed to some value; a 'p' tells the PIC that the user wants to control speed and position of the motor i.e. step this fast for this many degrees; and a 'd' tells the motor to stop right where it is. The second byte is either an ascii '1' or '0'. This byte sets the motor direction. The next three bytes set the motor speed in steps per second. The first byte is the hundreds digit, then the tens digit, then the ones digit. The last five bytes are the number of steps the user wants the motor to step before stopping; these are decoded in the same way except the most significant byte is the ten-thousands digit. If the user sends an 's' as the first byte, the contents of the last 5 bytes will not affect the motor's behavior; if the user sends a 'd', only the first byte is important. As an example, let's say the user wants to the motor to drive 1000 steps in the positive direction (the actual direction will depend on how the motor is wired up) at a speed of 300 steps/ second. Then the user would send the string 'p130001000' to the PIC.

Note: There are limits to how fast the motor can spin. If the user sets the speed too high, the motor will just vibrate. Some motors list their maximum speeds on the data sheets. If the desired motor does not have this listed, a little bit of experimentation should yield the maximum speed.


/*
Jarvis Schultz and Jake Ware
2-3-2010

Stepper Motor Control Code

The primary purpose of this code is to drive a stepper motor.  The stepper
motor that we are working with is a 5V Bipolar stepper motor from Portescap
(26M048B1B).  The drive circuitry is basically one-half of an h-bridge attached
to each of the four leads of the motor.  Pins D0-D3 are used to control the 
transistors of the h-bridge.  The motor driving is done with a timer interrupt
service routine, and a companion code was written in processing that allows
the user to communicate with the PIC and perform various motor control functions.
In processing, the user has the following capabilities:

	1) The ability to transmit some motor speed to the PIC, and the PIC will 
		drive the motor at that speed until the user changes it (they 
		can transmit a positive or a negative speed to change direction).
	2) The ability to transmit a motor speed and number of steps to the PIC,
		and the PIC will drive the motor at the desired speed until the desired 
		number of steps has been reached
	3) The ability to send a command to stop the motor right where it is.
*/


/** Includes ***************************************************/
#include "HardwareProfile.h"
#include "stdlib.h"
#include "string.h"
#include "stdio.h"

/** Global Variables ******************************************/
int mot_speed = 0;		// This variable is the current motor speed in steps/ second
int	number_steps = 0;	// This variable is used for tracking how many steps the motor has taken
int motor_phase = 0;  // This variable can be any integer between 0 and 3, and will be used for
					// switching the control wires in the correct order
char RS232_Out_Buffer[32];  // This array is used for temporarily storing data before sending
							// it to the PC; we will be sending current motor steps to the PC
char RS232_In_Buffer[20] = "zzzzzzzzzzzzzzzzzzzz";	//  This is an array that is initialized with 
													// useless data in it; it is used for temporary 
													// storage of data brought in from the PC on UART2
int max_steps = 0;  // This variable is used for position control, we initialize it at a random value
int i = 1;			// This is for marking the position in the RS232_In_Buffer that we are writing into.


/** Defines ***************************************************/
#define phase_a LATDbits.LATD0  //Pin 23 NU Board
#define phase_b LATDbits.LATD1  //Pin 26 NU Board
#define phase_c LATDbits.LATD2  //Pin 27 NU Board
#define phase_d LATDbits.LATD3  //Pin 28 NU Board
#define T3options	T3_ON | T3_PS_1_256 | T3_SOURCE_INT  // These are the setup options for timer three
#define BAUDRATE	(115200)	//This is the rate that we will communicate over RS232 at


/** Function Declarations *************************************/
void initUART2(int pbClk);	// Function for initializing UART2
int GetSteps(void);	 // This function returns the current motor position in steps
void SetSteps(int set_steps);  // This function manually changes the number stored in the number of steps variable
void SetSpeed(int motor_speed);  // This function sets the motor speed at the desired speed and begins driving the motor
void sendDataRS232(void); // Used for sending the current motor position to the PC using UART2


/** Main Function: ********************************************/
// The main function serves several purposes in this code.  First it initializes some local variables, and runs the
// functions that initialize the necessary PIC32 peripherals.  Then it enters an infinite while loop that scans the
// RS232_In_Buffer array looking for one of three specific characters each of which corresponds to one of the three
// cases described at the top of this code.  Also, every time through the while loop, we check and see if the current
// step count has reached the desired step count (if it has been set through the UART2), and if the two are equal,
// we stop the motor.  Also, every thousandth time through the loop, we transmit the current motor position back to 
// the PC so that it can be plotted in processing.  There is an ISR for UART communication reads data brought in on
// the receive pin and places it in the buffer.  The buffer is set up as a first-in-first-out buffer.  There is also
// an ISR for timer three.  When the motor speed is set using the SetSpeed() function, a number is calculated and 
// stored in the timer three period register.  This number is calculated such that the timer ISR will be called every
// time the motor should take a step, and the ISR is what actually makes the motor step.  
int main()
{

	char data;  // This variable is the current character in the RS232_In_Buffer
	char state; // This variable is the variable that determines which of the three cases the user has sent to the PIC
	char speedsign; // This variable determines which direction the motor should go
	int speed; // This is the speed of the motor that the user has sent to the PIC (in steps/ second)
	int steps; // If the user has sent a desired number of steps, this is the variable where it gets stored

	int dir; // This corresponds to the speedsign variable, but speedsign is a char, and this is an int
	int PbClk; // Frequency of the peripheral bus clock
	unsigned int count = 0; // This variable is for timing the sending of motor counts to PC
	int j = 1; // This is used for stepping through the RS232_In_Buffer array
	int imark; // This variable is for saving the location of a particular entry in the RS232_In_Buffer


	// Let's set the integer pbClk to be the value of the frequency
	// of the peripheral bus clock
	PbClk = SYSTEMConfigPerformance(SYS_FREQ);		
	
	// Let's set the pins D0-D3 to be low digital outputs:
	LATD |= 0x0000; 
	TRISD &= 0xFFF0;

	// Initialize the LED's:
	mInitAllLEDs();

	// Allow vector interrupts
	INTEnableSystemMultiVectoredInt();

	// Initialize UART Communication
	initUART2(PbClk);

	putsUART2("Program Started\r\n");
	
	while(1)
	{
		// This checks to see how many times we have gone through this while loop, 
		// if it is a multiple of 1000, we transmit the current step count
		while(BusyUART2());
		if  ((count%10000 == 0)) sendDataRS232();  
		
		// This checks to see if the current step count is equal to the desired steps,
		// if it is, we set the motor speed to zero.
		if  (GetSteps() == max_steps && max_steps != 0) SetSpeed(0);
		// Increment to count of how many times we have gone through the loop:
		count++;
		// Set the current byte of the RS232_In_Buffer to be equal to data:
		data = RS232_In_Buffer[j%20];
		// Increment our variable for indicating which variable in the buffer we are reading
		j++;		
		
		if (data == 's'||data == 'd'||data == 'p')
		{
		j--;
		imark = i+8;
		// Let's make sure that we have received all 10 bytes from Processing before we go on:
		while(i <= imark);
		while(BusyUART2());
		putsUART2(RS232_In_Buffer);
		// Now let's convert the 10 bytes of chars into useful data
		state = RS232_In_Buffer[j%20];
		speedsign = RS232_In_Buffer[(j+1)%20];
		speed = 100*((int) RS232_In_Buffer[(j+2)%20]-48)+10*((int) RS232_In_Buffer[(j+3)%20]-48)+((int) RS232_In_Buffer[(j+4)%20]-48);
		steps = 10000*((int) RS232_In_Buffer[(j+5)%20]-48)+1000*((int) RS232_In_Buffer[(j+6)%20]-48)+
			100*((int) RS232_In_Buffer[(j+7)%20]-48)+10*((int) RS232_In_Buffer[(j+8)%20]-48)+((int) RS232_In_Buffer[(j+9)%20]-48);

	

		switch(state)
		{
			case 's':  // This case indicates the speed of the motor alone has been set
				dir = atoi(&speedsign); // Convert the speed sign char into an int
				if (dir == 1) 
				{
					SetSpeed(speed);
					max_steps = 0;
				}
				else
				{
					SetSpeed(-1*speed);
					max_steps = 0;
				}
				
				break;
			case 'd':  // This case indicates the motor has been stopped
				SetSpeed(0);
				max_steps = 0;
				
				break;
			case 'p': // This case indicates the speed and a desired position have been sent
				dir = atoi(&speedsign);
				if (dir == 1) 
				{
					SetSpeed(speed);
					SetSteps(0);
					max_steps = steps;
				}
				else
				{
					SetSpeed(-1*speed);
					SetSteps(0);
					max_steps = -steps;
				}
				break;
		}
		// After we have actually received good data, lets clear the buffer
		strcpy(&RS232_In_Buffer[0],"zzzzzzzzzzzzzzzzzzzz");
		// Also, let's clear the current data
		strcpy(&data,"z");
		}
	}
		
}


/** Interrupt Handler Functions:****************************************/
	 
// Define this function to be the interrupt service routine for timer 3
void __ISR( _TIMER_3_VECTOR, ipl7) T3Interrupt( void) 
{
  mT3ClearIntFlag();             // clear interrupt flag

  // Increment or decrement phase depending on direction
  if (mot_speed > 0) 
	{
	number_steps++;
	motor_phase++;
	if (motor_phase > 3) motor_phase = 0;  // Rollover for motor phase
	}
  else 
	{
	number_steps--;
	motor_phase--;
	if (motor_phase < 0) motor_phase = 3;
	}
  
  // Set the current phase on pins D0-D3:
  switch (motor_phase)
  {
	case 0:
	{
	  phase_a = 0;
	  phase_b = 1;
	  phase_c = 0;
	  phase_d = 1;
	  break;
	}
	case 1:
	{
	  phase_a = 0;
	  phase_b = 1;
	  phase_c = 1;
	  phase_d = 0;
	  break;
	}
	case 2:
	{
	  phase_a = 1;
	  phase_b = 0;
	  phase_c = 1;
	  phase_d = 0;
	  break;
	}
	case 3:
	{
	  phase_a = 1;
	  phase_b = 0;
	  phase_c = 0;
	  phase_d = 1;
	  break;
	}
  }
}


// UART 2 interrupt handler
// it is set at priority level 2
void __ISR(_UART2_VECTOR, ipl2) IntUart2Handler(void)
{
	//putsUART2("Check Interrupt");
	// Is this an RX interrupt?
	if(mU2RXGetIntFlag())
	{
		// Clear the RX interrupt Flag
		mU2RXClearIntFlag();
	
		// Read a char into our FIFO buffer:
		RS232_In_Buffer[i%20] = ReadUART2();
		i++;
		
		// Toggle LED to indicate UART activity
		mLED_0_Toggle();
 
	}

	// We don't care about TX interrupt
	if ( mU2TXGetIntFlag() )
	{
		mU2TXClearIntFlag();
	}
}




/** User Called Functions ************************/
// Following function returns the number of 
// steps that the motor has taken:
int GetSteps(void)
{
  return number_steps;
}


// Next function allows to user to re-set a "home" position by
// manually changing the number of steps:
void SetSteps(int set_steps)
{
	INTEnable(INT_T3, 0);
	number_steps = set_steps;
	INTEnable(INT_T3, 1);
}


// This function allows the user to specify the desired motor speed:
void SetSpeed(int motor_speed)  // motor speed in steps/s
{
  if (motor_speed == 0)
  {
	// Turn off timer if motor is stopped:
	T3CONbits.ON = 0; 
	// Also, let's turn off all of the phases:
	phase_a = 0;
	phase_b = 0;
	phase_c = 0;
	phase_d = 0;
  }
  else
  {
	mot_speed = motor_speed;
	int Per;
	Per = (80000000/abs(mot_speed))/256-1;  // set the value in the period register
	OpenTimer3(T3options, Per);
	mT3SetIntPriority( 7); 	// set Timer3 Interrupt Priority
	mT3ClearIntFlag(); 		// clear interrupt flag
	mT3IntEnable( 1);		// enable timer3 interrupts
  }
}



// This function is used for sending the current number of steps 
// that the motor has taken back to the PC:
void sendDataRS232()
{
	sprintf(RS232_Out_Buffer,"%d\n",number_steps);
	while(BusyUART2()); // Let's make sure that the UART is not busy!
	putsUART2(RS232_Out_Buffer);
}


// This function is used for initilizing the UART2
void initUART2(int pbClk)
{
	// define setup Configuration 1 for OpenUARTx
		// Module Enable 
		// Work in IDLE mode 
		// Communication through usual pins 
		// Disable wake-up 
		// Loop back disabled 
		// Input to Capture module from ICx pin 
		// no parity 8 bit 
		// 1 stop bit 
		// IRDA encoder and decoder disabled 
		// CTS and RTS pins are disabled 
		// UxRX idle state is '1' 
		// 16x baud clock - normal speed
	#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
		// IrDA encoded UxTX idle state is '0'
		// Enable UxRX pin
		// Enable UxTX pin
		// Interrupt on transfer of every character to TSR 
		// Interrupt on every char received
		// Disable 9-bit address detect
		// Rx Buffer Over run status bit clear
	#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 UART2 with config1 and config2
	OpenUART2( config1, config2, pbClk/16/BAUDRATE-1);	// calculate actual BAUD generate value.
		
	// Configure UART2 RX Interrupt with priority 2
	ConfigIntUART2(UART_INT_PR2 | UART_RX_INT_EN);
}


As mentioned at the top, this code was designed to work with processing. A user interface has been programmed and can be found here. This zip file contains the main code (Stepper Motor/Stepper_Motor.pde). This folder should be unzipped to the default processing applications folder (something like ...My Documents/Processing). The other folder (controlP5) contains the necessary library to execute this code. It should be unzipped to ...My Documents/Processing/libraries. Information on this library can be found here.