站内搜索
发作品签到
专业版

基于立创·天猛星的电机PID控制器

工程标签

82
0
0
0

简介

该工程基于立创·天猛星MSPM0G3507,实现了电机PID控制并在LCD屏幕上展示了UI界面

简介:该工程基于立创·天猛星MSPM0G3507,实现了电机PID控制并在LCD屏幕上展示了UI界面
电赛TI训练营-简易PID项目

开源协议

GPL 3.0

创建时间:2025-05-04 10:55:46更新时间:2025-06-10 10:57:02

描述

项目简介

该项目基于立创·天猛星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://调整P
pid_value->kp = pid_value->kp + 0.1;
break;
case 1://调整I
pid_value->ki = pid_value->ki + 0.1;
break;
case 2://调整D
pid_value->kd = pid_value->kd + 0.1;
break;
case 3://调整target
if( pid_value->target < 100 )
pid_value->target = pid_value->target + 1;
break;
}
else //减
{
switch(select)
{
case 0://调整P
if(  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://调整I
if( 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://调整D
if( 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://调整target
if( 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://调整P
pid_value->kp = pid_value->kp + 0.1;
break;
case 1://调整I
pid_value->ki = pid_value->ki + 0.1;
break;
case 2://调整D
pid_value->kd = pid_value->kd + 0.1;
break;
case 3://调整target
if( pid_value->target < 360 )
pid_value->target = pid_value->target + 1;
break;
}
else //减
{
switch(select)
{
case 0://调整P
if(  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://调整I
if( 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://调整D
if( 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://调整target
if( 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显示UI
ui_home_page();
 
while (1) 
{
if( get_motor_status_flag() == MOTOR_STATUS_ON )
        {
            switch ( get_functional_mode() ) 
            {
                case SPEED_FUNCTION:
 
//计算定速PID
motor_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:
 
//计算定距PID
motor_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);
        }
}
}

结果演示

演示内容见附件

附件内容

  1. 工程代码
  2. 定速控制
  3. 定距控制

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

3D模型

序号文件名称下载次数
暂无数据

附件

序号文件名称下载次数
1
Keil.rar
0
2
定速控制.mp4
0
3
定速控制.gif
0
4
定距控制.gif
0
克隆工程
添加到专辑
0
0
分享
侵权投诉
知识产权声明&复刻说明

本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。

请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。

评论

全部评论(1
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航