
基于立创·天猛星的电机PID控制器
简介
该工程基于立创·天猛星MSPM0G3507,实现了电机PID控制并在LCD屏幕上展示了UI界面
简介:该工程基于立创·天猛星MSPM0G3507,实现了电机PID控制并在LCD屏幕上展示了UI界面开源协议
:GPL 3.0
描述
项目简介
该项目基于立创·天猛星MSPM0G3507开发板,实现了电机PID控制,并在LCD屏幕上展示了UI界面
该项目硬件部分由如下几部分组成:主控制器电路、LCD显示电路、按键电路、电机驱动电路
该项目软件部分由如下及部分组成:按键控制、LCD显示、电机驱动、编码器转速获取、PID控制
接下来着重介绍软件代码
按键控制
在电路上,按键分为上下左右中五个(在实际代码编写中,中键并没有编写对应的代码)

在硬件层面,定义按键状态
KEY_STATUS key_scan(void){ KEY_STATUS states; // 读取每个按键的状态 states.up = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_UP_PIN) ? 1 : 0; states.left = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_LEFT_PIN) ? 1 : 0; states.right = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_RIGHT_PIN) ? 1 : 0; states.down = DL_GPIO_readPins(GPIO_KEY_PORT, GPIO_KEY_PIN_DOWN_PIN) ? 1 : 0;
return states;}
在用户接口层面,定义不同按键状态,从而应对不同的控制要求
static void flex_button_process(void){ int8_t i = 0; flex_button_t* target;
for (target = btn_head, i = 0; target != NULL; target = target->next, i ++) { if (target->status > 0) { target->scan_cnt ++; }
switch (target->status) { case 0: /* is default */ if (trg & (1 << i)) /* is pressed */ { target->scan_cnt = 0; target->click_cnt = 0; target->status = 1; target->event = FLEX_BTN_PRESS_DOWN; EVENT_CB_EXECUTOR(target); } else { target->event = FLEX_BTN_PRESS_NONE; } break;
case 1: /* is pressed */ if (!(cont & (1 << i))) /* is up */ { target->status = 2; } else if ((target->scan_cnt >= target->short_press_start_tick) && (target->scan_cnt < target->long_press_start_tick)) { target->status = 4; target->event = FLEX_BTN_PRESS_SHORT_START; EVENT_CB_EXECUTOR(target); } break;
case 2: /* is up */ if ((target->scan_cnt < target->click_start_tick)) { target->click_cnt++; // 1 if (target->click_cnt == 1) { target->status = 3; /* double click check */ } else { target->click_cnt = 0; target->status = 0; target->event = FLEX_BTN_PRESS_DOUBLE_CLICK; EVENT_CB_EXECUTOR(target); } } else if ((target->scan_cnt >= target->click_start_tick) && (target->scan_cnt < target->short_press_start_tick)) { target->click_cnt = 0; target->status = 0; target->event = FLEX_BTN_PRESS_CLICK; EVENT_CB_EXECUTOR(target); } else if ((target->scan_cnt >= target->short_press_start_tick) && (target->scan_cnt < target->long_press_start_tick)) { target->click_cnt = 0; target->status = 0; target->event = FLEX_BTN_PRESS_SHORT_UP; EVENT_CB_EXECUTOR(target); } else if ((target->scan_cnt >= target->long_press_start_tick) && (target->scan_cnt < target->long_hold_start_tick)) { target->click_cnt = 0; target->status = 0; target->event = FLEX_BTN_PRESS_LONG_UP; EVENT_CB_EXECUTOR(target); } else if (target->scan_cnt >= target->long_hold_start_tick) { /* long press hold up, not deal */ target->click_cnt = 0; target->status = 0; target->event = FLEX_BTN_PRESS_LONG_HOLD_UP; EVENT_CB_EXECUTOR(target); } break;
case 3: /* double click check */ if (trg & (1 << i)) { target->click_cnt++; target->status = 2; target->scan_cnt --; } else if (target->scan_cnt >= target->click_start_tick) { target->status = 2; } break;
case 4: /* is short pressed */ if (!(cont & (1 << i))) /* is up */ { target->status = 2; } else if ((target->scan_cnt >= target->long_press_start_tick) && (target->scan_cnt < target->long_hold_start_tick)) { target->status = 5; target->event = FLEX_BTN_PRESS_LONG_START; EVENT_CB_EXECUTOR(target); } break;
case 5: /* is long pressed */ if (!(cont & (1 << i))) /* is up */ { target->status = 2; } else if (target->scan_cnt >= target->long_hold_start_tick) { target->status = 6; target->event = FLEX_BTN_PRESS_LONG_HOLD; EVENT_CB_EXECUTOR(target); } break;
case 6: /* is long pressed */ if (!(cont & (1 << i))) /* is up */ { target->status = 2; } break; } }}
LCD显示
LCD屏幕显示依靠SPI通信,由于只需MCU向从机发送数据,因此只需定义SPI写的相关函数,字符与图形显示函数这里不再赘述
void spi_write_bus(unsigned char dat){ //发送数据 DL_SPI_transmitData8(SPI_LCD_INST, dat); //等待SPI总线空闲 while(DL_SPI_isBusy(SPI_LCD_INST));}void LCD_Writ_Bus(unsigned char dat){LCD_CS_Clr();spi_write_bus(dat); LCD_CS_Set();}void LCD_WR_DATA8(unsigned char dat){LCD_Writ_Bus(dat);}void LCD_WR_DATA(unsigned int dat){LCD_Writ_Bus(dat>>8);LCD_Writ_Bus(dat);}void LCD_WR_REG(unsigned char dat){LCD_DC_Clr();//写命令LCD_Writ_Bus(dat);LCD_DC_Set();//写数据}void LCD_Address_Set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2){LCD_WR_REG(0x2a);//列地址设置LCD_WR_DATA(x1);LCD_WR_DATA(x2);LCD_WR_REG(0x2b);//行地址设置LCD_WR_DATA(y1+35);LCD_WR_DATA(y2+35);LCD_WR_REG(0x2c);//储存器写}
电机驱动
电机驱动依赖PWM信号的控制,具体代码如下
#include "hw_motor.h"// 设置fi引脚的PWM比较值static void set_fi(uint16_t dat){ DL_TimerG_setCaptureCompareValue(PWM_MOTOR_INST,dat,GPIO_PWM_MOTOR_C1_IDX);}// 设置bi引脚的PWM比较值static void set_bi(uint16_t dat){ DL_TimerG_setCaptureCompareValue(PWM_MOTOR_INST,dat,GPIO_PWM_MOTOR_C0_IDX);}// 限制输入的PWM最大值// 仅内部使用static void restrict_pwm_max_value(uint16_t* value){ if( *value > MOTOR_PWM_MAX ) { *value = MOTOR_PWM_MAX; }}// 设置电机速度void set_motor(uint16_t fi_value, uint16_t bi_value){restrict_pwm_max_value(&fi_value);restrict_pwm_max_value(&bi_value); set_fi(fi_value); set_bi(bi_value);}// 电机停止void stop_motor(void){ set_motor(0,0);}电机运行状态获取
电机运行状态的获取依赖编码器,通过获取编码器前后的值可以计算得到电机运行的方向以及转速,其具体代码如下
#include "hw_encoder.h"static ENCODER_RES motor_encoder;//编码器初始化void encoder_init(void){//编码器引脚外部中断NVIC_ClearPendingIRQ(GPIOB_INT_IRQn);NVIC_EnableIRQ(GPIOB_INT_IRQn);}//获取编码器的值int get_encoder_count(void){return motor_encoder.count;}//获取编码器的方向ENCODER_DIR get_encoder_dir(void){return motor_encoder.dir;}/****************************************************************** * 函 数 说 明:获取实时编码器的值 * 函 数 形 参: * 函 数 返 回:返回对应的编码器值 * 作 者:LC * 备 注:******************************************************************/long long get_temp_encoder(void){return motor_encoder.temp_count;}//编码器数据更新//请间隔一定时间更新void encoder_update(void){motor_encoder.count = motor_encoder.temp_count;//确定方向motor_encoder.dir = ( motor_encoder.count >= 0 ) ? FORWARD : REVERSAL;motor_encoder.temp_count = 0;//编码器计数值清零}//外部中断处理函数void GROUP1_IRQHandler(void){uint32_t gpio_status;//获取中断信号情况gpio_status = DL_GPIO_getEnabledInterruptStatus(GPIO_ENCODER_PORT, GPIO_ENCODER_PIN_A_PIN | GPIO_ENCODER_PIN_B_PIN);//编码器A相上升沿触发 if((gpio_status & GPIO_ENCODER_PIN_A_PIN) == GPIO_ENCODER_PIN_A_PIN){//如果在A相上升沿下,B相为低电平if(!DL_GPIO_readPins(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_B_PIN)){motor_encoder.temp_count--;}else{motor_encoder.temp_count++;}}//编码器B相上升沿触发 else if((gpio_status & GPIO_ENCODER_PIN_B_PIN)==GPIO_ENCODER_PIN_B_PIN){//如果在B相上升沿下,A相为低电平if(!DL_GPIO_readPins(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_A_PIN)){motor_encoder.temp_count++;}else{motor_encoder.temp_count--;}}//清除状态DL_GPIO_clearInterruptStatus(GPIO_ENCODER_PORT,GPIO_ENCODER_PIN_A_PIN|GPIO_ENCODER_PIN_B_PIN);}PID控制
PID控制的目的在于通过调整Kp、Ki、Kd三个参数,增加系统的快速性和稳定性
在自动控制原理课程中,我们学习过Kp起到控制系统快速性的作用,然而并不能消除静差;Ki起到消除静差的作用,但要考虑相位裕度下降以及积分饱和的问题;Kd起到增加阻尼的作用,但并不适合存在尖峰信号的场合
该项目中的PID包括转速PID以及转距PID,对应代码分别如下
转速PID
#include "app_speed_pid.h"#include "hw_encoder.h"#include "hw_motor.h"PID speed_pid;//定速PID初始化void speed_pid_init(void){ pid_init(&speed_pid, 35, 6, 10, MOTOR_PWM_MAX, MOTOR_PWM_MAX,98);}//获取定速PID全局变量的地址PID* get_speed_pid(void){return &speed_pid;}//获取定速PID的目标值int get_speed_pid_target(void){return speed_pid.target;}/************************************************功能:PID电机定速控制器参数:target_speed = 目标值返回:对应PID的地址************************************************/PID motor_speed_control(int target_speed){int PWM;PWM = pid_calc(&speed_pid, target_speed, get_encoder_count() );//控制刷新速度delay_cycles(80000*5);if( PWM > 0 ){set_motor(0, PWM);}else if( PWM < 0 ){PWM = -PWM;set_motor(PWM, 0);}return speed_pid;}/************************************************功能:设置速度PID的参数更新参数:pid_value = 对应PID的地址 select = 0表示调整P,= 1表示调整I,= 2表示调整D,= 3表示调整target add_or_subtract_flag = 0表示加,=1表示减************************************************/void set_speed_pid_parameter(PID* pid_value, int select, int add_or_subtract_flag){if( add_or_subtract_flag == 0 ) //加{switch(select){case 0://调整Ppid_value->kp = pid_value->kp + 0.1;break;case 1://调整Ipid_value->ki = pid_value->ki + 0.1;break;case 2://调整Dpid_value->kd = pid_value->kd + 0.1;break;case 3://调整targetif( pid_value->target < 100 )pid_value->target = pid_value->target + 1;break;}} else //减{switch(select){case 0://调整Pif( pid_value->kp > 0 )pid_value->kp = pid_value->kp - 0.1;pid_value->kp = (pid_value->kp <= -0.0f) ? 0.0f : pid_value->kp; // 消除负零break;case 1://调整Iif( pid_value->ki > 0 )pid_value->ki = pid_value->ki - 0.1;pid_value->ki = (pid_value->ki <= -0.0f) ? 0.0f : pid_value->ki; // 消除负零break;case 2://调整Dif( pid_value->kd > 0 )pid_value->kd = pid_value->kd - 0.1;pid_value->kd = (pid_value->kd <= -0.0f) ? 0.0f : pid_value->kd; // 消除负零break;case 3://调整targetif( pid_value->target > -100 )pid_value->target = (pid_value->target - 1);break;}}}转距PID
#include "app_distance_pid.h"#include "hw_encoder.h"#include "hw_motor.h"#include "stdlib.h"#include "stdio.h"PID distance_pid;//定距PID初始化void distance_pid_init(void){ pid_init(&distance_pid, 75, 2, 10, 9999, 9999, DEFAULT_ANGLE );}//获取定距PID全局变量的地址PID* get_distance_pid(void){return &distance_pid;}//获取定距PID的目标值int get_distance_pid_target(void){return distance_pid.target;}/************************************************功能:PID电机定距控制器参数:target_speed = 目标值返回:对应PID的地址************************************************/PID motor_distance_control(int target_angle){int PWM, target_pulses;// 将目标角度换算成目标脉冲数target_pulses = target_angle / DEGREES_PER_PULSE;//传入PID静态参数、目标值(目标脉冲数)、当前值(当前编码器获取的实时脉冲数)//PID输出的值为控制信号,传入到PWM变量中PWM = pid_calc( &distance_pid, target_pulses, get_temp_encoder() );//控制刷新速度delay_cycles(80000*5);//控制信号转为电机实际控制值if( PWM > 0 )//控制信号大于0{set_motor(0, PWM);//顺时针旋转,pwm为旋转速度}else if( PWM < 0 ){PWM = -PWM;//将负数转为正数(例如PWM=-10,则-(-10)=10)set_motor(PWM, 0);//逆时针旋转,pwm为旋转速度}return distance_pid;}/************************************************功能:设置定距PID的参数更新参数:pid_value = 对应PID的地址 select = 0表示调整P,= 1表示调整I,= 2表示调整D,= 3表示调整target add_or_subtract_flag = 0表示加,=1表示减************************************************/void set_distance_pid_parameter(PID* pid_value, int select, int add_or_subtract_flag){if( add_or_subtract_flag == 0 ) //加{switch(select){case 0://调整Ppid_value->kp = pid_value->kp + 0.1;break;case 1://调整Ipid_value->ki = pid_value->ki + 0.1;break;case 2://调整Dpid_value->kd = pid_value->kd + 0.1;break;case 3://调整targetif( pid_value->target < 360 )pid_value->target = pid_value->target + 1;break;}} else //减{switch(select){case 0://调整Pif( pid_value->kp > 0 )pid_value->kp = pid_value->kp - 0.1f;pid_value->kp = (pid_value->kp <= -0.0f) ? 0.0f : pid_value->kp; // 消除负零break;case 1://调整Iif( pid_value->ki > 0 )pid_value->ki = pid_value->ki - 0.1f;pid_value->ki = (pid_value->ki <= -0.0f) ? 0.0f : pid_value->ki; // 消除负零break;case 2://调整Dif( pid_value->kd > 0 )pid_value->kd = pid_value->kd - 0.1f;pid_value->kd = (pid_value->kd <= -0.0f) ? 0.0f : pid_value->kd; // 消除负零break;case 3://调整targetif( pid_value->target > -360 )//屏幕最多显示5位pid_value->target = (pid_value->target - 1);break;}}}项目综合
在项目主函数中,需要完成各个外设的初始化,并根据获取到的按键信息调整相关参数,控制电机的PID运行,并实时显示在LCD屏幕上。代码显示如下
#include "ti_msp_dl_config.h"#include "mid_debug_led.h"#include "mid_debug_uart.h"#include "string.h"#include "stdio.h"#include "hw_lcd.h"#include "mid_button.h"#include "app_key_task.h"#include "hw_encoder.h"#include "mid_timer.h"#include "app_ui.h"#include "app_sys_mode.h"#include "app_speed_pid.h"#include "hw_motor.h"#include "app_distance_pid.h"void ui_speed_or_distance_page_value_set_quick(LongPressStatus update_status){ if( update_status == LONG_PRESS_END ) return; if(update_status == LONG_PRESS_ADD_START ) { if( get_functional_mode() == SPEED_FUNCTION ) set_speed_pid_parameter(get_speed_pid(), system_status.set_page_flag, 0); else if( get_functional_mode() == DISTANCE_FUNCTION ) set_distance_pid_parameter(get_distance_pid(), system_status.set_page_flag, 0); } else if( update_status == LONG_PRESS_SUBTRACT_START ) { if( get_functional_mode() == SPEED_FUNCTION ) set_speed_pid_parameter(get_speed_pid(), system_status.set_page_flag, 1); else if( get_functional_mode() == DISTANCE_FUNCTION ) set_distance_pid_parameter(get_distance_pid(), system_status.set_page_flag, 1); }}int main(void){SYSCFG_DL_init();//DEBUG串口初始化debug_uart_init();//按键任务初始化user_button_init();//编码器初始化encoder_init();//定时器初始化timer_init();//系统参数初始化sys_event_init();//定速PID初始化 speed_pid_init();//定距PID初始化distance_pid_init();//LCD初始化lcd_init();//LCD显示UIui_home_page();while (1) {if( get_motor_status_flag() == MOTOR_STATUS_ON ) { switch ( get_functional_mode() ) { case SPEED_FUNCTION://计算定速PIDmotor_speed_control( get_speed_pid_target() );//防止任务冲突,再判断一次if( get_motor_status_flag() == MOTOR_STATUS_ON ){//显示PID波形和参数ui_speed_curve();}else if( get_motor_status_flag() == MOTOR_STATUS_OFF ){//停止电机stop_motor();pid_change_zero(get_speed_pid());} break; case DISTANCE_FUNCTION://计算定距PIDmotor_distance_control( get_distance_pid_target() );//防止任务冲突,再判断一次if( get_motor_status_flag() == MOTOR_STATUS_ON ){//显示PID波形和参数ui_distance_curve();}else if( get_motor_status_flag() == MOTOR_STATUS_OFF ){//停止电机stop_motor();pid_change_zero(get_distance_pid());} break; default:enable_task_interrupt(); //开启任务调度pid_change_zero(get_speed_pid());set_motor_status_flag( MOTOR_STATUS_OFF ); break; } }if( get_show_state() == PARAMETER_PAGE ) {//数值的快速加减ui_speed_or_distance_page_value_set_quick( get_long_press_state() ); //屏幕显示参数变化 PID* temp_pid = (get_functional_mode() == SPEED_FUNCTION) ? get_speed_pid() : get_distance_pid(); int encoder_value = (get_functional_mode() == SPEED_FUNCTION) ? get_encoder_count() : (get_temp_encoder() * DEGREES_PER_PULSE); ui_speed_page_value_set(temp_pid->kp, temp_pid->ki, temp_pid->kd, encoder_value, temp_pid->target, 1); }}}结果演示
演示内容见附件
附件内容
- 工程代码
- 定速控制
- 定距控制
设计图
未生成预览图,请在编辑器重新保存一次BOM
暂无BOM
克隆工程知识产权声明&复刻说明
本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。
请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。


评论