logo资料库

Motor Control using PWM and PID.docx

第1页 / 共12页
第2页 / 共12页
第3页 / 共12页
第4页 / 共12页
第5页 / 共12页
第6页 / 共12页
第7页 / 共12页
第8页 / 共12页
资料共12页,剩余部分请下载后查看
Motor Control using PWM and PID
PWM Control of an H-Bridge
Preventing Shoot-through
A Snubber Circuit to Decrease Noise
PID Control
Example
Hardware Setup
Source Code
Results
Conclusion
Motor Control using PWM and PID Bi-directional motor control is can be done using an H-bridge circuit with pulse-width modulation (PWM) from a microcontroller to vary the speed. Several design challenges include preventing shoot-through, implementing a snubber circuit, as well as open and closed loop (such as PID) control mechanisms. PWM Control of an H-Bridge An H-bridge circuit consists of four transistors (usually two PMOS's and two NMOS's). To maximize efficiency, the transistors are driven at a higher voltage than the microcontroller. A typical H-bridge circuit with logic scaling circuitry is shown above. Each side of the H-bridge has two transistors with the gates tied together resulting in complementary operation--Q3 is always off when Q1 is on and vice versa. The same is true for Q2 and Q4. The circuit works by setting PWMB to logic zero (Q2 on; Q4 off) and then setting PWMA to logic high (Q1 off; Q3 on). The motor direction can be reversed by toggling PWMA and PWMB. PWM is a simple way to vary the voltage applied to the motor. Most microcontrollers have dedicated PWM hardware, but an output compare timer can also generate a PWM signal. PWM works by rapidly turning the motor on and off. For example, if the motor supply is 12V, the motor can be driven at 6V by applying a 50% duty cycle where half the time 12V is applied, and half the time 0V is applied as shown by the green signal in the plot below.
While using PWM is simple, it introduces a problem called shoot-through which occurs when current flows directly from the power supply to ground when the transistors are being switched. For example, when the PWMA input signal switches from high to low, Q1 turns on and Q3 turns off. For a brief period of time, both Q1 and Q3 are partially on allowing current to flow from the supply to ground. This causes efficiency to plummet and introduces heating problems in the transistors. To overcome this problem additional circuitry must be added to ensure Q3 turns completely off before Q1 starts to turn on. Preventing Shoot-through The easiest way to implement a shoot-through prevention circuit is to use an integrated circuit (IC) that has shoot-through protection built-in. The Si9986 IC from Vishay is an H-bridge motor driver circuit with built-in shoot-through protection as well as logic translation circuitry (Q5A and Q5B in the diagram above). It is a great solution for controlling a small DC motor using a PWM signal from a microcontroller. When using the Si9986, as with using any motor driver, a snubber circuit is required to reduce electromagnetic noise in the system. A Snubber Circuit to Decrease Noise A snubber circuit is used to suppress the voltage transients caused by PWM switching (as well as by the inherent switching in brushed motors). A DC motor is an inductive load; the voltage across which is proportional to the change in current, given by:
When the PWM signal switches the motor from on to off, there is a rapid change in is large) which causes a voltage spike. Without a snubber circuit, the current (i.e. energy from the voltage spike can result in arcing, damage to the body diode in the H- bridge transistors, or cause electromagnetic interference in nearby circuitry. The snubber circuit safely dissipates the energy in passive elements. A simple, effective snubber circuit consists of a resistor and capacitor in series across the terminals of the motor as shown below. PID Control With an H-bridge, a pair of PWM signals, and a snubber circuit, the motor is ready for bi-directional control. The simplest form of control is open loop. This means the controller simply applies a voltage (a PWM signal in this case) but has no way to measure the effect of the applied voltage. The controller simply assumes higher voltage makes the motor go faster. Closed loop control uses feedback from the motor, such as the motor current or speed, to adjust the PWM signal. A PID loop--verbosely known as proportional, integral, differential loop--is a popular algorithm in many closed loop systems. Image from Wikipedia
The diagram above represents a PID controlled process. In this case, the "process" or "plant" is the motor. The feedback mechanism--y(t) called the process variable--can be either the motor current or speed. The set point, u(t), is the desired current or speed. The PID loop takes the difference (or error), e(t), between the set point and the process variable, applies namesake adjustments--proportional, integral, as well as differential--and then sums the result to get the new value of the manipulated variable (the PWM duty cycle). The following code is a PID loop implementation using floating point variables. The structure (pid_f_t) is first initialized using pid_init_f() with the minimum and maximum values for the manipulated variable accepted as parameters. The minimum is useful if the motor requires some minimum PWM duty cycle to make it turn while the maximum ensures the PID algorithm does not try to exceed 100% duty cycle. The pid_update_f() function uses the current set point and the process variable as well as the constants stored in the pid_f_t structure to compute the manipulated variable. The application loop includes a function to measure the motor current or speed, call pid_update_f(), and set the PWM value according to the manipulated variable. typedef struct{ float max /*! Max manipulated value */; float min /*! Miniumum manipulated value */; float e /*! Error value */; float i /*! Integrator value */; float kp /*! Proportional constant */; float ki /*! Integrator constant */; float kd /*! Differential constant */; } pid_f_t; /*! \details This function initializes the data in a PID structure. * */ void pid_init_f(pid_f_t * ptr /*! A pointer to the PID data structure */, float min /*! The manipulated variable's minimum value */, float max /*! The manipulated variable's maximum value */){ memset(ptr, 0, sizeof(pid_f_t)); ptr->min = min; ptr->max = max; } /*! \details This function updates the value of the manipulated variable (MV) * based on the current state of the PID loop. */ float pid_update_f(float sp /*! The set point */, float pv /*! The process variable */, pid_f_t * ptr /*! A pointer to the PID constants */){ float temp; float e; float p; float manp; float tmpi; e = ptr->e; ptr->e = sp - pv; tmpi = ptr->i + ptr->e; //bound the integral manp = ptr->kp * ptr->e + ptr->ki * tmpi + ptr->kd * (e - ptr->e);
ptr->i = tmpi; manp = ptr->max; manp = ptr->min; if ( (manp < ptr->max) && (manp > ptr->min) ){ } else if ( manp > ptr->max ){ } else if ( manp < ptr->min ){ } return manp; } It is important to properly apply the manipulated variable to the process. In the H- bridge shown above, Q5 inverts the PWM signal which means the software needs to invert the PWM value before applying it. Also while switching Q2 and Q4, the states of Q1 and Q3 determine the motor direction; this affects how the sign of the manipulated variable is interpreted. These nuances in the circuit can make debugging a PID loop tricky but can be overcome with sound analysis as well as trial and error. Example Here is an example circuit and code using the Laertes Lite Microcomputer running CoActionOS, a 12V DC motor connected to a rotary encoder, and the Si9986 H- Bridge driver chip. The image below shows the breadboard setup. Hardware Setup The parts include:  Laertes Lite Microcomputer
SMT to DIP Adapter and Si89986 H-Bridge PWM Driver   Generic 12V DC Brushed Motor  Rotary Motor Encoder (200 counts per revolution from Sparkfun Electronics)  10K Potentiometer (used for user input) The following drawing shows the specific connections. The user input (to set the speed) consists of a potentiometer wiper connected to P0.2 (ADC Channel 7). The rotary encoder is connected to P1.26 which also serves as a clock input for timer zero. Finally, 2.3 and 2.4 are PWM outputs connected to the input of the Si9986 H-bridge driver. For more details, please see the Laertes-Lite page where the schematic and datasheet are available. Source Code The source code uses a PID loop to control the motor speed. First, it reads the ADC to calculate a speed setting from 1500RPM to 10,000RPM which is set by the user using the potentiometer. After the ADC is sampled, it is filtered using an exponential moving average, low-pass filter. This prevents noise on the ADC from causing changes to the speed setting. The code then calculates the motor speed in revolutions per minute. It measures the rising edges of the rotary encoder (200 counts per revolution) using the timer clock input. It then uses the POSIX function (provided by CoActionOS) clock_gettime() to calculate the change in time since the last measurement. The current speed is then the change in number of revolutions divided by the change in time. The current speed and the set point speed are then passed to the PID calculation which returns the duty cycle which is applied to the PWM output. The CoActionOS application shown below uses eLib for the EMA filtering and PID loop calculations. It relies on CoActionOS-Applib to access peripheral hardware. To view the code for these functions, please see the source code download page. #include #include #include #include
#include #include #include #define TMR_PORT 0 //Timer port used to capture encoder data #define ADC_PORT 0 //ADC port for getting user input #define ADC_INPUT_CHAN (7) //ADC channel for user input #define PWM_PORT 1 //PWM port for Si9986 #define PWM_CHAN_HIGH (PWM_CHANNEL5) #define PWM_CHAN_LOW (PWM_CHANNEL4) #define PWM_FREQ (1000000) #define PWM_TOP 1000 //Top value for PWM clock #define PWM_PERIOD (PWM_TOP*1000/PWM_FREQ) //Period in ms static int init(void); //initilize timer, PWM and ADC static int init_tmr(void); static int init_pwm(void); static int init_adc(void); //Set the output duty cycle static void set_duty(int duty); int main(int argc, char * argv[]){ //PWM clock frequency pwm_reqattr_t req; int i; //These are for sampling the ADC (user input) adc_sample_t samp; int32_t samp_avg; uint16_t alpha; uint32_t set_speed; //This is for measuring the motor speed uint32_t delta_ms; //elapsed time in milliseconds uint32_t last_ms; uint32_t now_ms; uint32_t ref_ms; struct timespec now; uint32_t current_pos; //current position uint32_t last_pos; //position from last measurement uint32_t delta_pos; uint32_t speed; //PID variables pid_i32_t pid; uint32_t duty; if( init() < 0 ){ bogus last_ms = 0; //exit unsuccessfully if any hardware failed to initialize exit(1); } //Initialize the ADC averaging (EMA filter) alpha = DSP_EMA_I32_ALPHA(0.1); adc_read(ADC_PORT, ADC_INPUT_CHAN, &samp, sizeof(adc_sample_t)); samp_avg = samp; //Since there is no initial measurement, the first speed will be
//initialize the PID pid_init_i32(&pid, 200, 900); //Bound the duty between 20% and 90% pid_setconstants_i32(&pid, //These constants take some tweaking to get everything to work smoothly PID_I32_CONSTANT(0.05), //P constant PID_I32_CONSTANT(0.01), //I constant PID_I32_CONSTANT(0.0001) ); //D constant usleep(50*1000); set_duty(0); //Get the initial time so that the program start time is close to zero in the output data clock_gettime(CLOCK_REALTIME, &now); ref_ms = (now.tv_sec * 1000 + (now.tv_nsec + 500000) / 1000000); //convert to milliseconds i = 0; while(1){ //Read and averge the ADC to get the set point adc_read(ADC_PORT, ADC_INPUT_CHAN, &samp, sizeof(adc_sample_t)); samp_avg = dsp_ema_i32(samp, samp_avg, alpha); set_speed = samp_avg * 8500 / ADC_MAX + 1500; //Now measure the position and calculate the speed (pos/time) clock_gettime(CLOCK_REALTIME, &now); now_ms = (now.tv_sec * 1000 + (now.tv_nsec + 500000) / 1000000); delta_ms = now_ms - last_ms; last_ms = now_ms; //convert to milliseconds current_pos = tmr_get(TMR_PORT); delta_pos = current_pos - last_pos; //calculate in RPM (1000*60 ms in a min and 200 counts per revolution) speed = (delta_pos*(1000*60/200))/(delta_ms); last_pos = current_pos; //Calculate the new duty cycle duty = pid_update_i32(set_speed, speed, &pid); set_duty(duty); //Display the data and wait for the next update if ( i++ > 0 ){ //first sample is garbage because the delta printf("%d %d %d %d;\n", now_ms - ref_ms, set_speed, speed, values are invalid duty); } usleep(25*1000); //update every 25 ms } return 0; } int init(void){ if( init_tmr() < 0 ){ return -1; } if( init_adc() < 0 ){ return -1; }
分享到:
收藏