【HUST狼牙实验室梯队学习项目】第一节 底盘搭建

  • 主控选择
  • 电机控制部分
    • PWM调波
      • 定时器
      • A4950电机驱动
    • PID闭环控制
  • 串口通信
    • 串口通信配置:
    • 串口接收
    • 串口发送

主控选择

该项目选择的是stm32f103rct6的主控

电机控制部分

本项目采用的是减速比1:30、带霍尔编码器的直流减速电机,实测中这个减速比的电机可以做到扭矩和转速的平衡,且控制简单,易于上手。霍尔编码盘编码稳定度较光电编码器高,且价格低廉,用于轮式里程计中精度可以达到预期

PWM调波

定时器

电机的PWM调波使用了TIM8定时器的四个通道CH1,CH2,CH3,CH4,对应的GPIO引脚分别是PC6,PC7,PC8,PC9

定时器初始化函数如下,其中arr为自动重装寄存器的值,psc为预分频值,72Mhz/(arr+1)/(psc+1)为分频值

void TIM8_PWM_Init(uint16_t arr,uint16_t psc)  //
{GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);//  GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM8_CH3GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽,GPIO输出来自于定时器GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; //TIM8_CH4GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM8_CH1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7; //TIM8_CH2GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOC, &GPIO_InitStructure);TIM_TimeBaseStructure.TIM_Period = arr;//设置自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc;//设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;   //选择定时器模式:TIM脉冲宽度调制模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    //比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC3Init(TIM8, &TIM_OCInitStructure);    TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable); //在写入ccr值后才开始输出pwmTIM_CtrlPWMOutputs(TIM8,ENABLE);TIM_OC4Init(TIM8, &TIM_OCInitStructure);    TIM_OC4PreloadConfig(TIM8, TIM_OCPreload_Enable); //在写入ccr值后才开始输出pwmTIM_CtrlPWMOutputs(TIM8,ENABLE);TIM_OC1Init(TIM8, &TIM_OCInitStructure);    TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable); //在写入ccr值后才开始输出pwmTIM_CtrlPWMOutputs(TIM8,ENABLE);TIM_OC2Init(TIM8, &TIM_OCInitStructure);    TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable); //在写入ccr值后才开始输出pwmTIM_CtrlPWMOutputs(TIM8,ENABLE);TIM_Cmd(TIM8, ENABLE);
}

A4950电机驱动

该电机驱动PWM频率不宜超过1khz,故arr=7200-1,psc=10-1

经过测试,Forward/Reverse Slow Decay在PID闭环时阻尼较大,不容易振荡,故采用该模式进行电机驱动;为了实现电机的反转等操作,两个电机的两个通道都要配置为PWM复用推挽输出,对定时器资源消耗较大。

PWM的配置被封装成了四个函数,使用时可以直接调用

void PWM_Forward_1(uint16_t goal_speed)
{PWM_State_1=1;Goal_speed_1=goal_speed;TIM_SetCompare1(TIM8,7200-1);TIM_SetCompare2(TIM8,7160-17*goal_speed); //开环的近似线性函数,便于PID闭环的收敛pwm_space_1=7160-17*goal_speed;}void PWM_Backward_1(uint16_t goal_speed)
{PWM_State_1=-1;Goal_speed_1=goal_speed;TIM_SetCompare2(TIM8,7200-1);TIM_SetCompare1(TIM8,7160-17*goal_speed);pwm_space_1=7160-17*goal_speed;}void PWM_Forward_2(uint16_t goal_speed)
{PWM_State_2=1;Goal_speed_2=goal_speed;TIM_SetCompare3(TIM8,7200-1);TIM_SetCompare4(TIM8,7160-17*goal_speed);pwm_space_2=7160-17*goal_speed;
}void PWM_Backward_2(uint16_t goal_speed)
{PWM_State_2=-1;Goal_speed_2=goal_speed;TIM_SetCompare4(TIM8,7200-1);TIM_SetCompare3(TIM8,7160-17*goal_speed);pwm_space_2=7160-17*goal_speed;
}

PID闭环控制

导航小车需要对速度控制较为准确,便于上位机进行导航,所以使用PID闭环控制,使用TIM2和TIM4的CH1和CH2编码器接口对电机进行测速,再进行PID。

测速配置:

void Encoder_Init()
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //TIM2_CH1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM2_CH2GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //TIM4_CH1GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //TIM4_CH2GPIO_Init(GPIOB, &GPIO_InitStructure);TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_TimeBaseStructure.TIM_Period = 65536-1;//设置自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =1-1;//设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(&TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;//TIM通道1TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//输入上升沿捕获;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  // 每次边缘事件捕获一次;TIM_ICInitStructure.TIM_ICFilter = 0x0F; //指定输入捕获滤波器的值TIM_ICInit(TIM2,&TIM_ICInitStructure);TIM_ICInit(TIM4,&TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;//TIM通道1TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//输入上升沿捕获;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  // 每次边缘事件捕获一次;TIM_ICInitStructure.TIM_ICFilter = 0x0F; //指定输入捕获滤波器的值TIM_ICInit(TIM2,&TIM_ICInitStructure);TIM_ICInit(TIM4,&TIM_ICInitStructure);TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);TIM_Cmd(TIM2,ENABLE);TIM_Cmd(TIM4,ENABLE);
}

测速需要每隔0.05s读取一次计数器数据,读取频率太高测速精度低,读取频率过低则容易产生振荡
PID部分在使用团队开源代码的基础上略有改动

//PID.c
#define ABS(x) ((x)>0? (x):(-(x)))
#define LIMIT_MAX_MIN(x, max, min)  (((x) <= (min)) ? (min):(((x) >= (max)) ? (max) : (x)))/**********************************************************************************************************
*函 数 名: PID_Calc
*功能说明: PID+各种优化
*形    参: PID_Struct *P  PID参数结构体*        ActualValue    PID计算反馈量
*返 回 值: PID反馈计算输出值
**********************************************************************************************************/void PID_Init(Pid_Typedef* Pid_speed, float SetSpeed, float P, float I, float D)
{Pid_speed->P = P;Pid_speed->I = I;Pid_speed->D = D;Pid_speed->IMax = 20; //推荐误差的最大值*10Pid_speed->SetPoint = SetSpeed;Pid_speed->OutMax = 1000.0f;  //输出最大值
}float PID_Calc(Pid_Typedef *P, float ActualValue, float goal_speed)
{P->SetPoint = goal_speed;P->ActValue=(ABS(ActualValue)> P->DeadZone)?ActualValue:0;  P->PreError = P->SetPoint - P->ActValue;P->dError = P->PreError - P->LastError;P->SetPointLast = P->SetPoint;if(P->PreError > -P->ErrorMax && P->PreError < P->ErrorMax){P->SumError += P->PreError;}P->LastError = P->PreError;if(P->SumError >= P->IMax)P->SumError = P->IMax;else if(P->SumError <= -P->IMax)P->SumError = -P->IMax;P->POut = P->P * P->PreError;P->IOut = P->I * P->SumError;P->DOut = P->D * P->dError;return LIMIT_MAX_MIN(P->POut+P->IOut+P->DOut,P->OutMax,-P->OutMax);
}
//PID.h
typedef struct PID{float SetPoint;          //设定目标值float SetPointLast;float ActValue;     //实际值float DeadZone;     //设置死区float P;                       //比例常数float I;                      //积分常数float D;                      //微分常数float LastError;      //前次误差float PreError;           //当前误差float SumError;           //积分误差float dError;float ErrorMax;          //偏差上限 超出偏差则不计算积分作用float IMax;                  //积分限制float POut;                   //比例输出float IOut;                   //积分输出float DOut;                   //微分输出float OutMax;       //限幅
}Pid_Typedef;float PID_Calc(Pid_Typedef *P, float ActualValue, float goal_speed);
void PID_Init(Pid_Typedef* Pid_speed, float SetSpeed, float P, float I, float D);

整定PID参数时,用Jlink连接主控板,打开Jscope监测测速曲线,调到略有振荡且没有静差即可,最终电机可以在0.2s内收敛到稳定状态

串口通信

底盘需要接受上位机发送的速度控制指令,以及上报编码器测速数据,就要与上位机进行串口通信。我这里使用USB转TTL实现上位机与stm32主控的通信,一方面这样可以有效保护上位机的GPIO口,另一方面USB转TTL集成的LED指示可以便于调试

串口通信配置:

//使用空闲中断+DMA实现对定长数据的高效接收
void Serial_init()
{MyDMA_Init();RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);     //开启APB2外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;      //复用推挽输出GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;            //A9作为发送端GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;          //上拉输入GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;     //A10作为接收端GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);USART_InitTypeDef USART_InitStructure;           //初始化USART1USART_InitStructure.USART_BaudRate=115200;      //设置波特率USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;           //Tx发送,Rx接收USART_InitStructure.USART_Parity=USART_Parity_No;        //检验位USART_InitStructure.USART_StopBits=USART_StopBits_1;      //停止位USART_InitStructure.USART_WordLength=USART_WordLength_8b;     //发送字长为8bit,无校验位USART_Init(USART1,&USART_InitStructure);USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //配置空闲中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         //优先级分组方式:第三类分组NVIC_InitTypeDef NVIC_InitStructure;      //配置NVICNVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;            //配置NVIC中断channelNVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;         //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;        //响应优先级NVIC_Init(&NVIC_InitStructure);USART_Cmd(USART1,ENABLE);USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
}void Serial_SendByte(uint8_t _Byte)
{USART_SendData(USART1,_Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
}void Serial_SendString(char* _string)
{while(*_string){Serial_SendByte((uint8_t)*(_string++));}
}
extern char Serial_RxPacket[15];void MyDMA_Init()//转运数据起始地址
{RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_BufferSize=14;DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;  //从外设寄存器转运到SRAMDMA_InitStructure.DMA_M2M=DMA_M2M_Disable;DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)Serial_RxPacket;      //接受缓冲地址DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&(USART1->DR);     //转运数据起始地址DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;        //八位数据宽度DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //地址是否自增DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;DMA_Init(DMA1_Channel5,&DMA_InitStructure);DMA_Cmd(DMA1_Channel5,ENABLE);
}

串口接收

void USART1_IRQHandler(void) //解析上位机指令,结构为L:(+/-)X.XXR:(+/-)X.XX
{if(USART_GetFlagStatus(USART1,USART_FLAG_IDLE)==SET){DMA_Cmd(DMA1_Channel5,DISABLE); (void)USART1->SR;            (void)USART1->DR;DMA_SetCurrDataCounter(DMA1_Channel5,14);int i=0;if(Serial_RxPacket[0]=='L'){Goal_speed_1_temp=Serial_RxPacket[3]-'0';Goal_speed_1_temp=Goal_speed_1_temp+(Serial_RxPacket[5]-'0')*0.1;Goal_speed_1_temp=Goal_speed_1_temp+(Serial_RxPacket[6]-'0')*0.01;}elseGoal_speed_1_temp=0;if(Serial_RxPacket[2]=='-'){//Goal_speed_1_temp=Goal_speed_1_temp*(-1);PWM_Backward_1(360*Goal_speed_1_temp);}else if(Serial_RxPacket[2]=='+'){PWM_Forward_1(360*Goal_speed_1_temp);}//Goal_speed_1=Goal_speed_1_temp;//OLED_ShowSignedNum(1,1,(int)(100*Goal_speed_1_temp),5);if(Serial_RxPacket[7]=='R'){Goal_speed_2_temp=Serial_RxPacket[10]-'0';Goal_speed_2_temp=Goal_speed_2_temp+(Serial_RxPacket[12]-'0')*0.1;Goal_speed_2_temp=Goal_speed_2_temp+(Serial_RxPacket[13]-'0')*0.01;}if(Serial_RxPacket[9]=='-'){PWM_Backward_2(360*Goal_speed_2_temp);}else if(Serial_RxPacket[9]=='+'){PWM_Forward_2(360*Goal_speed_2_temp);}//OLED_ShowSignedNum(2,1,(int)(100*Goal_speed_2_temp),5);//Goal_speed_2=Goal_speed_2_temp;DMA_Cmd(DMA1_Channel5,ENABLE);}}

串口发送

char Tx[20]="L:+000R:+000\r\n\0";Serial_SendString(Tx);

Tx内容在编码器测速时确定,含义是单位时间(0.05s)内编码器脉冲数。主控机除PID外不进行数值运算

【HUST狼牙实验室梯队学习项目】第一节 底盘搭建相关推荐

  1. MySQL入门学习的第一节(SQL语句)

    MySQL入门学习的第一节(SQL语句) SQL语句

  2. Angular学习笔记第一节 基本概念

    1.Let do it! ####1.ng的基础概念 在学习ng之前,我们只需要掌握HTML.CSS.JS即可. 简称ng.名字不错,吊! 重要的特性 有了ng,我们就可以轻松的构建SPA应用,而且n ...

  3. 零基础学习SQL第一节

    零基础学习SQL第一大节 第一节:数据库与SQL 我们身边的数据库 大家都有过下面这样的经历吧? 收到曾经为自己诊治过的牙医寄来的明信片,上面写着"距上次检查已有半年,请您再来做个牙齿健康查 ...

  4. CCSA学习笔记 第一节 思科安全解决方案综述

    CCSA学习笔记 CCSA第二期:01思科安全解决方案综述 CCSA安全学习的目标 CCSA第二期:01思科安全解决方案综述 CCSA安全学习的目标 1.能够对思科安全的网络安全解决方案有初步认识 2 ...

  5. 新手零基础学习Python第一步,搭建开发环境!

    如何在电脑上面搭建Python开发环境?本文会解答这个问题. Python是一门计算机编程语言,通过给计算机下达精确的指令以完成相应的任务或者事情. 人类掌握Python后,就可以利用这个工具告诉计算 ...

  6. 《左耳听风》学习体验第一节

    就在前几天,朋友圈得技术大佬们不约而同得转发一篇公众号推文,大意就是知名程序大拿,陈浩因心梗去世.我就在想,这个陈浩是谁,搜索了一下,他的文章.发现他在某app上拥有自己的付费专栏,注册app送7天会 ...

  7. maven学习笔记第一节一-maven install 模块之间相互引用

    为什么80%的码农都做不了架构师?>>>    我们再做项目的时候,有很多模块是可以重复使用的,maven提供了很好的解决模块之间相互引用的方法,具体流程如下: 1.建立共用模块 2 ...

  8. c++基础学习 (第一节课)

    一.new和delete 一.介绍 new:关键字,申请内存 delete:关键字,释放内存 二.使用和特点 一.new的使用 /*new的使用*///申请内存//1事情单个内存(一个自己所需要的大小 ...

  9. AutoHotKey学习总结第一节:AHK基本语法

    AHK需要安装AHK环境,编写AHK脚本完成快捷键设定或者自动化工作.AHK我个人觉得是一个挺方便简单的语言,写一些好用的小工具或者挂机脚本不错. 一.基本语法 重映射 OriginKey::Dest ...

最新文章

  1. 对硕士而言,编制和稳定究竟有多重要?
  2. 我热爱计算机作文500字,电脑吸引了我
  3. Struts 2的基石——拦截器(Interceptor)
  4. **设计模式中的常用原则
  5. centos7虚拟机开启端口后 外部不能访问的问题
  6. gzip算法源代码 - tankzhouqiang - 博客园
  7. U盘的用法用途与维护
  8. 养成10个优秀的习惯
  9. IOS工作笔记001---windows下安装通过VmWare来安装IOS系统_并连接上网_来吧超级详细
  10. zookeeper 3.6.0安装以及基本使用
  11. linux tick异常变化,linux tickGet()
  12. 如何查看Windows 10系统版本号?
  13. GPO 安装 .net 4.5和WMF4
  14. Java 设计模式 之 代理模式(Proxy)
  15. php中好看的对话框面板,有关对话框的课程推荐10篇
  16. 微信自动回复 html 点击文字,【微信开发】公众号自动回复文字和图文链接(示例代码)...
  17. ZBT的计算几何模板
  18. 菜鸟应用发布 全民跨入APP2.0时代
  19. python求圆周率马青公式_Python 实现丘德诺夫斯基(Chudnovsky)法計算高精度圓周率...
  20. 自律·财大自习·Java

热门文章

  1. (符号数)二进制乘法(从补码讲起)
  2. deluge webui不能添加部分torrent种子解决记录
  3. BFD库的使用介绍 nm工具源码分析
  4. pandas 转换为文本类型_Pandas对文本数据处理
  5. 速汇金:蚂蚁金服收购被否,却与瑞波达成合作
  6. ajax实现搜索提示源码,Jquery实现搜索框提示功能示例代码
  7. 计算机及应用自考考研学校,【王道论坛统计】2010 34所自主划线院校计算机复试线及相关详细报考信息(感谢gumuguo)...
  8. C++ char类型转string类型的两种方法
  9. 草,电脑BUG如此之多,元芳你怎么看?
  10. 国庆被困校园想出去想疯了_被迫抓包学校微信小程序