由于最近在学习电机控制算法之类的东西,看到论文大多使用PID、或以PID衍生的ADRC作为电机的主流控制,于是自己也写了一个stm32控制L298N以驱动直流电机的程序,并用QT做了一个上位机实现了用软件改变PID的参数、电机转速、转向等功能。


霍尔编码器具体型号为JGB37-520,12V供电,一分钟旋转110转(这里指的时全速运转下的转速),两端红白两线为电机的电源(0、12V),棕蓝两线为霍尔编码器的电源(0、3.3V),中间黄绿两线为霍尔编码器的信号线(A、B两相)。

L298N电机驱动模块我们用到了其中的IN1、IN2和OUT1、OUT2以及ENA使能端、接入12V电源即可驱动(注意:使能端ENA的跳帽要拔掉,不然就是默认全速运转、拔开跳帽后可以将其接入PWM信号实现电机调速)
用最简单的stm32模块即可。
这个电源接入电脑的USB接口或其他电源的USB口都可以输出1~24V的可调电压,电压值通过旋钮旋转改变。
此QT界面可以显示你所设定的目标速度,和当前电机旋转的实际速度,功能区可以实现对目标速度的更改,和对PID的比例、积分、微分的修改,同时还可以实现电机的正反向运转和制动。
初始化l298n模块,并实现电机 正转、反转、制动功能。
#include "l298n.h"
#include "Delay.h"
#define IN1 GPIO_Pin_4
#define IN2 GPIO_Pin_5
u8 FLAG = 0;
void L298N_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//控制电机正反转
void L298N_Positive_rotation(void)
{
FLAG = 1;
GPIO_SetBits(GPIOA, IN2); //正转
GPIO_ResetBits(GPIOA, IN1);
}
void L298N_Stop(void)
{
FLAG = 0;
GPIO_ResetBits(GPIOA, IN2); //制动
GPIO_ResetBits(GPIOA, IN1);
}
void L298N_Reverse_rotation(void)
{
FLAG = 2;
GPIO_SetBits(GPIOA, IN1); //反转
GPIO_ResetBits(GPIOA, IN2);
}
声明pid的结构体,初始化pid各个参数,实现pid控制函数接口。
#include "stm32f10x.h"
#include "pwm.h"
#include "key.h"
struct pidstruct{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义电压值(控制执行器的变量)
float integral; //定义积分值
}pid;
extern int32_t INPUT;
void pid_init() // pid参数初始化
{
pid.SetSpeed=0.0;
pid.ActualSpeed=0.0;
pid.err=0.0;
pid.err_last=0.0;
pid.voltage=0.0;
pid.integral=0.0;
pid.Kp=0.1;
pid.Ki=0.01;
pid.Kd=0.3;
}
void pid_set(float Kp,float Ki,float Kd) // pid参数设置函数
{
pid.Kp = Kp;
pid.Ki = Ki;
pid.Kd = Kd;
}
float pid_process() // 简易位移式pid
{
pid.SetSpeed=INPUT;
pid.err=pid.SetSpeed-pid.ActualSpeed;
pid.integral+=pid.err;
pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
pid.err_last=pid.err;
pid.ActualSpeed=pid.voltage*1.0;
return pid.ActualSpeed;
}
利用TIM_EncoderInterfaceConfig函数直接配置正交编码器,本来之前用的时外部中断,但是由于霍尔编码器旋转时产生的脉冲数非常多,用外部中断的话非常占用程序资源,所以直接利用ARM提供的库函数TIM_EncoderInterfaceConfig,利用硬件资源来代替。
#include "stm32f10x.h"|
extern u8 FLAG;
void Encoder_init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
TIM_Cmd(TIM3,ENABLE);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
if(FLAG == 1){
Temp = TIM_GetCounter(TIM3);
}
if(FLAG == 2){
Temp = (uint16_t)65535 - TIM_GetCounter(TIM3);
if(TIM_GetCounter(TIM3) == 0){
Temp = 0;
}
}
if(FLAG == 0){
Temp = 0;
}
//Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return Temp;
}
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity)
这个函数是用来配置正交编码器的,可以直接代替外部中断。
第一个参数是所使用的定时器;
第二个参数是编码器模式,参数可以是TIM_EncoderMode_TI1、TIM_EncoderMode_TI2、TIM_EncoderMode_TI12,这个参数的代表在哪个边沿计数还是双边计数;
后两个参数是反相和不反相的功能;
在定时器中断里获取电机当前速度,通过pid接口函数算出当前应有的实际速度,然后通过PWM调速改变电机速度。
#include "stm32f10x.h" // Device header
#include "timer.h"
#include "led.h"
#include "OLED.h"
#include "l298n.h"
#include "key.h"
#include "pid.h"
#include "pwm.h"
#include "Delay.h"
#include "Encoder.h"
#include "Serial.h"
#include "stdio.h"
extern int32_t INPUT;
extern struct pidstruct{
float SetSpeed; //定义设定值
float ActualSpeed; //定义实际值
float err; //定义偏差值
float err_last; //定义上一个偏差值
float Kp,Ki,Kd; //定义比例、积分、微分系数
float voltage; //定义电压值(控制执行器的变量)
float integral; //定义积分值
}pid;
int16_t speed = 0;
int main(void)
{
pid_init();
Key_Init();
LED_Init();
uart_init();
Encoder_init();
Timer_Init();
OLED_Init();
L298N_Init();
PWM_Init();
OLED_ShowString(1, 1, "TargetSpeed:");
OLED_ShowString(2, 1, "ActualSpeed:");
while(1)
{
OLED_ShowNum(1, 13, INPUT, 4);
OLED_ShowNum(2, 13, speed, 4);
printf("T:%d A:%d",INPUT,speed);
if(Receive_Flag == 1) //接收数据标志位等于1(接收完毕,停止接收)
Receive_Flag = 0; //接收数据标志位置0(可以开始接收)
}
}
void TIM4_IRQHandler (void)
{
static uint32_t i=0;
float tempx = 0;
if(TIM_GetITStatus(TIM4, TIM_IT_Update)==SET)
{
i++;
if(i>100){
LED = !LED;
i=0;
}
speed = Encoder_Get();
pid.ActualSpeed = speed;
tempx = pid_process();
if(tempx>100)
tempx=100;
if(tempx<=0)
tempx=0;
PWM_SetCompare1(tempx*10);
}
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
speed = Encoder_Get();
pid.ActualSpeed = speed;
这两句代码通过 TIM_EncoderInterfaceConfig 函数获取的当前电机旋转的实际速度。
tempx = pid_process();
PWM_SetCompare1(tempx*10);
这两句代码是将获取的实际速度传给pid接口函数,用tempx来接受通过pid算出来的值,然后将算出的速度,用PWM输出给ENA使能端以驱动电机改变电机旋转速度。
这是本人的第一篇博客,写的不好的地方大家可以提出来交流讨论,本人也在学习阶段,如果需要程序源码和QT接口的话之后可以上传。stm32程序在这,需要自取。如果觉得这篇文章对你有用请点赞评论一波谢谢。
当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少
我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新rubygems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems
点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度; 在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
在我的Character模型中,我添加了:字符.rbbefore_savedoself.profile_picture_url=asset_path('icon.png')end但是,对于数据库中已存在的所有角色,它们的profile_picture_url为nil。因此,我想进入控制台并遍历所有这些并进行设置。在我试过的控制台中:Character.find_eachdo|c|c.profile_picture_url=asset_path('icon.png')end但这给出了错误:NoMethodError:undefinedmethod`asset_path'formain:O
当我进入Rails控制台时,我已将pry设置为加载代替irb。我找不到该页面或不记得如何将其恢复为默认行为,因为它似乎干扰了我的Rubymine调试器。有什么建议吗? 最佳答案 我刚发现问题,pry-railsgem。忘记了它的目的是让“railsconsole”打开pry。 关于ruby-on-rails-带有Pry的Rails控制台,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/question