基于stm32的减速直流电机PID算法控制

2023-05-16

本例程采用了HAL库进行项目开发(主要使用软件CubexMX和keil5),文章末尾会有代码开源,欢迎各位对文章进行指正和探讨。

基于PID的减速电机控制

一、 硬件模块与原理图   

1、硬件组成   

        硬件组成:stm32f103c8t6最小系统板;0.96寸LED12864(I2C通讯模式);智能小车12v移动电源;25GA370直流减速电机(带霍尔编码器);JDY-31蓝牙模块;L298N电机驱动模块;杜邦线若干;1个面包板;

图片如下:

66d96fdf8b9942c38ecf427755d66194.png

2、模块分析

        1、L298N电机驱动模块

9c01c5dfa63d4618a12352d82354127c.png2c480d1568064774bf3d39f023a71df7.png

        1.模块可驱动两路直流电机,输出A和B各接一直流电机即可;

        2.若使用12V供电,将12V供电端口及GND接上电源正负即可,同时5V供电端可以作为最小系统板的输入电源;

        3.若不需要使用PWM调速,只需要控制电机正反转,则逻辑A与B跳线帽插上即可,相当于始终使能;

        4.若需要使用PWM调速,需将跳线帽拔起,将使能端接上单片机IO口。(定时器IO口,PWM输出模式);

        5.逻辑输入四个端口IN1、IN2、IN3、IN4接单片机四个IO口,每两个端口控制的一路电机。

        温馨提示: 特别不建议新手或者资金有限的情况下,使用电机驱动模块直连成品开发板,很容易烧坏。

        原因:(1) 由于电机的特性,电机在堵转或者高负载下,电流会增大,可能会影响到单片机。(2)新手玩单片机可能出现短路等情况,很容易板子冒烟;

L298N的转动逻辑图:

6ead902160a445f499962b18f1e49ca7.png

        2、0.96寸OLED(I2C通讯)

d58943ba61804ea1b7eebf1772e20d14.pngb9c0b109bbcb49caadb2297ef035107b.png

        (1)目前市面主要分为OLED与LCD这2种屏幕;

        (2)OLED自发光特性,LCD都要背光,而OLED不需要,因为它是自发光。这样同样的显示,OLED效果要来得好一些;

        (3)多种接口方式:6800,8080两种并行接口方式,4线的穿行SPI接口,IIC接口方式(2线);

        (4)不要接过高电压,3.3V就可以正常工作了;

        (5)OLED不足之处是做大之后成本较高。

        本实验采用了0.96寸OLED的屏幕(通讯方式IIC),4个接线柱(SCL,SDA,GND,VCC);         IIC通讯实现方式: IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。高速 IIC 总线一般可达 400kbps 以上。

模拟IIC通讯:

        I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备了。SDA 和SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。其余的 I2C 从器件都挂接到 SDA 和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C设备。

I2C 协议:(1)起始位;(2)停止位;(3)数据传输;(4)应答信号;(5)I2C 写时序;(6)I2C 读时序

I2C 写时序

fa9c12dff5bf4dc48196f0e39a9f5dd9.png

1)、开始信号。

2)、发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决

定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为

1 的话表示这是一个读操作,为 0 的话表示这是一个写操作。

3)、 I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作。

4)、从机发送的 ACK 应答信号。

5)、重新发送开始信号。

6)、发送要写写入数据的寄存器地址。

7)、从机发送的 ACK 应答信号。

8)、发送要写入寄存器的数据。

9)、从机发送的 ACK 应答信号。

10)、停止信号。

I2C 时序

4eb171668a0b40cebded99320af6c4be.png

I2C 单字节读时序比写时序要复杂一点,读时序分为 4 大步,第一步是发送设备地址,第二步是发送要读取的寄存器地址,第三步重新发送设备地址,最后一步就是 I2C 从器件输出要读取的寄存器值,我们具体来看一下这几步。

1)、主机发送起始信号。

2)、主机发送要读取的 I2C 从设备地址。

3)、读写控制位,因为是向 I2C 从设备发送数据,因此是写信号。

4)、从机发送的 ACK 应答信号。

5)、重新发送 START 信号。

6)、主机发送要读取的寄存器地址。

7)、从机发送的 ACK 应答信号。

8)、重新发送 START 信号。

9)、重新发送要读取的 I2C 从设备地址。

10)、读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据。

11)、从机发送的 ACK 应答信号。

12)、从 I2C 器件里面读取到的数据。

13)、主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了。

14)、主机发出 STOP 信号,停止 I2C 通信。

        3、JDY-31蓝牙模块

5d17fadf15304e3b83d8cd1cda15fec9.png0a658fa91da4497aa2f79b705929dedb.png

         市场上蓝牙模块有很多,常见的JDY-xx,HC-xx等系列。其实看似高级的蓝牙功能背后就是简单的串口通讯;  

        USART 的全称是 Universal Synchronous/Asynchronous Receiver/Transmitter,也就是同步/异步串行收发器。相比 UART 多了一个同步的功能,在硬件上体现出来的就是多了一条时钟线。一般 USART 是可以作为 UART 使用的,也就是不使用其同步的功能。

串口通讯协议:

        数据包:串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备得RXD接口,在协议层中规定了数据包的内容,具体包括起始位、主体数据(8位或9位)、校验位以及停止位,通讯的双方必须将数据包的格式约定一致才能正常收发数据。

具体如图所示:

d48a572df6514325bcf8e6e4563a7261.png

        波特率:由于异步通信中没有时钟信号,所以接收双方要约定好波特率,即每秒传输的码元个数,以便对信号进行解码,常见的波特率有4800、9600、115200等。STM32中波特率的设置通过串口初始化结构体来实现。

        注意:MCU设置的波特率大小要与蓝牙APP设置的大小一致!

        4、6线减速电机(带编码器)模块:

        市面上电机有很多,常用的有步进电机,直流减速电机,伺服电机等等; 编码器:用来测量电机转速的仪器元件,常见的有:霍尔编码器,光电编码器等 电机的驱动原理很简单,给电压差即可使得电机转动,调速则利用PWM调节发。

0ab46a4a62884051969e1d0542425de9.png

        编码器原理: 编码器是一种将角位移或者角速度转换成一串电数字脉冲的旋转式传感器。 编码器工作原理: 霍尔编码器是有霍尔马盘和霍尔元件组成。霍尔马盘是在一定直径的圆板上等分的布置有不同的磁极。霍尔马盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。

28535d8124d54c05aee35e73ea017766.png

476a459656cd4486a30d43e47a404368.png

         注意:通过判断A与B相哪一位在前,即可判断出正转还是反转

二、CubexMX设置

        使用的MCU为stm32f103c8t6:

1902f6d03eb540229a5742619c6ba01e.png

        RCC:

f13d2afd3ccb429590fb408805b1ea4a.png

         SYS:

a37986c672e04dfab695fe1a65a36bc8.png

        注意:Debug这里一定要设置成Serial Wire否则可能出现芯片自锁

        GPIO设置:

bdc68e160d0d430f9c8e4e4973c2f01d.png

        定时TIM2用来测速与测量正转反转(计数器模式)

82ccfcd4a8cc4b429a9c31a3ab92f154.png

         定时3:PWM调节

ea2cc40b70c34320b608219f8c6491cb.png

        I2C:

0cbd996c978146208a517ab020b090e4.png         USART1:

43bdbb7ae45f4244ac655269bb0497da.png

        之后按照自己习惯生成初始化文件

三、代码

        自动生成的:

8183cb7e16d54f95a9d149ff143843b2.png

        需要自己编写的:

17d379d74a26416aa0ea9eb11005c69b.png

        I2C代码:

#include "oled.h"
#include "asc.h"
#include "main.h"
void WriteCmd(unsigned char I2C_Command)//???
 {
	HAL_I2C_Mem_Write(&hi2c2,OLED0561_ADD,COM,I2C_MEMADD_SIZE_8BIT,&I2C_Command,1,100);
 }
		
void WriteDat(unsigned char I2C_Data)//???
 {
		HAL_I2C_Mem_Write(&hi2c2,OLED0561_ADD,DAT,I2C_MEMADD_SIZE_8BIT,&I2C_Data,1,100);
  }
	void OLED_Init(void)
{
	HAL_Delay(100); //????????
	
	WriteCmd(0xAE); //display off
	WriteCmd(0x20);	//Set Memory Addressing Mode	
	WriteCmd(0x10);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	WriteCmd(0xb0);	//Set Page Start Address for Page Addressing Mode,0-7
	WriteCmd(0xc8);	//Set COM Output Scan Direction
	WriteCmd(0x00); //---set low column address
	WriteCmd(0x10); //---set high column address
	WriteCmd(0x40); //--set start line address
	WriteCmd(0x81); //--set contrast control register
	WriteCmd(0xff); //???? 0x00~0xff
	WriteCmd(0xa1); //--set segment re-map 0 to 127
	WriteCmd(0xa6); //--set normal display
	WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
	WriteCmd(0x3F); //
	WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	WriteCmd(0xd3); //-set display offset
	WriteCmd(0x00); //-not offset
	WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
	WriteCmd(0xf0); //--set divide ratio
	WriteCmd(0xd9); //--set pre-charge period
	WriteCmd(0x22); //
	WriteCmd(0xda); //--set com pins hardware configuration
	WriteCmd(0x12);
	WriteCmd(0xdb); //--set vcomh
	WriteCmd(0x20); //0x20,0.77xVcc
	WriteCmd(0x8d); //--set DC-DC enable
	WriteCmd(0x14); //
	WriteCmd(0xaf); //--turn on oled panel
}

void OLED_SetPos(unsigned char x, unsigned char y) //???????
{ 
	WriteCmd(0xb0+y);
	WriteCmd(((x&0xf0)>>4)|0x10);
	WriteCmd((x&0x0f)|0x01);
}

void OLED_Fill(unsigned char fill_Data)//????
{
	unsigned char m,n;
	for(m=0;m<8;m++)
	{
		WriteCmd(0xb0+m);		//page0-page1
		WriteCmd(0x00);		//low column start address
		WriteCmd(0x10);		//high column start address
		for(n=0;n<128;n++)
			{
				WriteDat(fill_Data);
			}
	}
}


void OLED_CLS(void)//??
{
	OLED_Fill(0x00);
}

void OLED_ON(void)
{
	WriteCmd(0X8D);  //?????
	WriteCmd(0X14);  //?????
	WriteCmd(0XAF);  //OLED??
}

void OLED_OFF(void)
{
	WriteCmd(0X8D);  //?????
	WriteCmd(0X10);  //?????
	WriteCmd(0XAE);  //OLED??
}

// Parameters     : x,y -- ?????(x:0~127, y:0~7); ch[] -- ???????; TextSize -- ????(1:6*8 ; 2:8*16)
// Description    : ??codetab.h??ASCII??,?6*8?8*16???
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
	unsigned char c = 0,i = 0,j = 0;
	switch(TextSize)
	{
		case 1:
		{
			while(ch[j] != '\0')
			{
				c = ch[j] - 32;
				if(x > 126)
				{
					x = 0;
					y++;
				}
				OLED_SetPos(x,y);
				for(i=0;i<6;i++)
					WriteDat(F6x8[c][i]);
				x += 6;
				j++;
			}
		}break;
		case 2:
		{
			while(ch[j] != '\0')
			{
				c = ch[j] - 32;
				if(x > 120)
				{
					x = 0;
					y++;
				}
				OLED_SetPos(x,y);
				for(i=0;i<8;i++)
					WriteDat(F8X16[c*16+i]);
				OLED_SetPos(x,y+1);
				for(i=0;i<8;i++)
					WriteDat(F8X16[c*16+i+8]);
				x += 8;
				j++;
			}
		}break;
	}
}

// Parameters     : x,y -- ?????(x:0~127, y:0~7); N:???.h????
// Description    : ??ASCII_8x16.h????,16*16??
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
{
	unsigned char wm=0;
	unsigned int  adder=32*N;
	OLED_SetPos(x , y);
	for(wm = 0;wm < 16;wm++)
	{
		WriteDat(F16x16[adder]);
		adder += 1;
	}
	OLED_SetPos(x,y + 1);
	for(wm = 0;wm < 16;wm++)
	{
		WriteDat(F16x16[adder]);
		adder += 1;
	}
}

// ????????????????,????????“??——???——????”??????ascll.h?????(????)
//???????:x:?????  
//								y:???(??0-7)  
//								begin:????????????????ascll.c???????  
//                num:????????
//                ????“??”,??????????????????0,1,???0,??????,??:x:0,y:2,begin:0,num:2
void OLED_ShowCN_STR(u8 x , u8 y , u8 begin , u8 num)
{
	u8 i;
	for(i=0;i<num;i++){OLED_ShowCN(i*16+x,y,i+begin);}    //OLED????
}

// Parameters     : x0,y0 -- ?????(x0:0~127, y0:0~7); x1,y1 -- ?????(???)???(x1:1~128,y1:1~8)
// Description    : ??BMP??
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
	unsigned int j=0;
	unsigned char x,y;

  if(y1%8==0)
		y = y1/8;
  else
		y = y1/8 + 1;
	for(y=y0;y<y1;y++)
	{
		OLED_SetPos(x0,y);
    for(x=x0;x<x1;x++)
		{
			WriteDat(BMP[j++]);
		}
	}
}

void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{      	
	unsigned char c=0,i=0;	
		c=chr-' ';//???????			
		if(x>128-1){x=0;y=y+2;}
		if(Char_Size ==16)
			{
			OLED_SetPos(x,y);	
			for(i=0;i<8;i++)
			WriteDat(F8X16[c*16+i]);
			OLED_SetPos(x,y+1);
			for(i=0;i<8;i++)
			WriteDat(F8X16[c*16+i+8]);
			}
			else {	
				OLED_SetPos(x,y);
				for(i=0;i<6;i++)
				WriteDat(F6x8[c][i]);
				
			}
}
u32 oled_pow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;    
	return result;
}	
//??2???
//x,y :????	 
//len :?????
//size:????
//mode:??	0,????;1,????
//num:??(0~4294967295);	 		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{         	
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/oled_pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
				continue;
			}else enshow=1; 
		}
	 	OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2); 
	}
} 

         UART代码:

#include "uart.h"

uint8_t USART1_RX_BUF[USART1_REC_LEN];//????,??USART_REC_LEN???.
uint16_t USART1_RX_STA=0;//??????//bit15:??????,bit14~0:??????????
uint8_t USART1_NewData;//?????????1????????

extern int flag;

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)//????????
{
    if(huart ==&huart1)
    {
        if((USART1_RX_STA&0x8000)==0)//?????
        {
            if(USART1_NewData==0x5A)//????0x5A
            {
                 USART1_RX_STA|=0x8000;   //?????,?USART2_RX_STA??bit15(15?)?1
            }
            else
            {
                   USART1_RX_BUF[USART1_RX_STA&0X7FFF]=USART1_NewData; 

							if(USART1_RX_BUF[1] == 0x01)
							{
								flag = 2;
							}
							
                   USART1_RX_STA++;  //???????1
                   if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//??????,??????

            }
        }
        HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1); 

    }
}

         常规的编写如上,但是本人的MCU存在问题,单片机并未接收到预设的数据。

        所以,本人项目中采用了下方代码:

#include "uart.h"

uint8_t USART1_RX_BUF[USART1_REC_LEN];//????,??USART_REC_LEN???.
uint16_t USART1_RX_STA=0;//??????//bit15:??????,bit14~0:??????????
uint8_t USART1_NewData;//?????????1????????

extern int flag;

void  HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)//????????
{
    if(huart ==&huart1)
    {
                              
              USART1_RX_BUF[USART1_RX_STA&0X7FFF]=USART1_NewData; 					
              USART1_RX_STA++;  //???????1
			
                   if(USART1_RX_STA>(USART1_REC_LEN-1))USART1_RX_STA=0;//??????,??????
			
							if(USART1_RX_BUF[USART1_RX_STA-4] == 0xA0)
							{
								flag = 1;
							}
							if(USART1_RX_BUF[USART1_RX_STA-4] == 0x90)
							{
								flag = 2;
							}
							if(USART1_RX_BUF[USART1_RX_STA-4] == 0xD0)
							{
								flag = 3;
							}
							if(USART1_RX_BUF[USART1_RX_STA-4] == 0x88)
							{
								flag = 4;
							}
							if(USART1_RX_BUF[USART1_RX_STA-4] == 0x48)
							{
								flag = 5;
							}							
        
        HAL_UART_Receive_IT(&huart1,(uint8_t *)&USART1_NewData,1); 

    }
}

         如果大家自己使用的花,可以根据自己的蓝牙APP写这段程序,有问题欢迎留言

        Motor代码:

#include "motor.h"

void MOTOR_GO()
{
	__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1,3000);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
}

void MOTOR_BACK()
{
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
}

void MOTOR_STOP()
{
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
}

void MOTOR_UP()
{
	__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1,1);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
}

void MOTOR_DOWN()
{
	__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1,400);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
}

         PID:

PID算法:

PID分为位置型和增量型

增量型即通过 u(k)-u(k-1) 从而得出式子:

公式的第一部分是比例式 是为了让值按一定比例达到目标值;

第二部分是积分值,正值,在计算的过程中往往会受到环境等一些其他因素的影响,导致值不能到达目标值;

第三部分是微分值,通常是负值,后一次偏差值往往小于前一次偏差值,目的是为了防止值增加过大,通常起一个阻碍的作用;

f2215a87662c470c9bb5a82cdbd7a3a9.png

        PID代码:

#include "pid.h"
#include "tim.h"
#include "main.h"
#include "math.h"
#include "i2c.h"
#include "oled.h"

unsigned int MotorSpeed;		//È«¾Ö±äÁ¿£¬µç»úµ±Ç°×ªËÙ
int SpeedTarget = 750;				//Ä¿±êתËÙ
int MotorOutput;						//µç»úÊä³ö

//1.ÀûÓÃTIM2¼ÆËãµç»úתËÙ

void GetMotorSpeed(void)
{
//		int CaptureNumber =	(short)__HAL_TIM_GET_COUNTER(&htim2);	  //HAL¿âº¯Êý¼ÆËãÂö³å´ÎÊý
//	
//	//µç»úתËÙת»»Speed=1sÄÚµÄÂö³åÊý/44(һȦ11¸öÐźţ¬4±¶Æµ·¨)/34¼õËÙ±È
//		int MotorSpeed=CaptureNumber*20/44/34*2*3.14*3;
//		OLED_ShowNum(40,0,MotorSpeed,4,16);
//	
//		__HAL_TIM_GET_COUNTER(&htim2) = 0;													//¼ÆÊýÆ÷ÇåÁã
	
	int CaptureNumber =	(short)__HAL_TIM_GET_COUNTER(&htim2);	  //???????
	__HAL_TIM_GET_COUNTER(&htim2) = 0;
//	int Speed=CaptureNumber*5/44/34*2*3.14*3;
    int 	Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2); 		
		if(Direction == 1)
		{
				CaptureNumber -= 65535;
		}
	MotorSpeed=CaptureNumber;
	OLED_ShowNum(40,0,MotorSpeed,4,16);
	HAL_Delay(100);
	OLED_CLS();
//	__HAL_TIM_GET_COUNTER(&htim2) = 0;
}

//2.ÔöÁ¿Ê½PID¿ØÖÆÆ÷£¨PID³£¼û·ÖΪλÖÃPIDºÍÔöÁ¿Ê½PID£©

int Error_Last,Error_Prev;		//ÉÏ´ÎÎó²î£¬ÉÏÉÏ´ÎÎó²î
int Pwm_add,Pwm;							//PWMÔöÁ¿,PWMÕ¼¿Õ±È

int Kp = 5, Ki = 3, Kd = 1;//PIDË㷨ϵÊý£¬¸¡µãÀàÐÍ£¬Ð¾Æ¬¼ÆËãÄÜÁ¦Ò»°ãʱ½¨ÒéÕûÐÍ£¬»òÕß*1024

int SpeedInnerControl(int Speed,int Target)		//ËÙ¶ÈÄÚ»·¿ØÖÆ
{
    int Error = Target - Speed;		//Îó²î = Ä¿±êËÙ¶È - ʵ¼ÊËÙ¶È 

    Pwm_add = Kp * (Error - Error_Last) + 										//±ÈÀý
							Ki * Error +																		//»ý·Ö
							Kd * (Error - 2.0f * Error_Last + Error_Prev);	//΢·Ö


    Pwm += Pwm_add;		//Êä³öÁ¿=ԭʼÁ¿+ÔöÁ¿
	
		Error_Prev = Error_Last;	//±£´æÉÏÉÏ´ÎÎó²î
    Error_Last = Error;				//±£´æÉÏ´ÎÎó²î

    if(Pwm > 4999) Pwm = 3000;	//ÏÞÖÆÉÏÏÂÏÞ£¬·ÀÖ¹PWM³¬³öÁ¿³Ì
    if(Pwm <-4999) Pwm =-3000;

    return Pwm;	//·µ»ØÊä³öÖµ
}

//3.µç»úתËÙÓë·½ÏòµÄº¯Êý£¨PID¿ØÖÆ£©

void SetMotorVoltageAndDirection(int Pwm)
{
    if(Pwm < 0)			//Èç¹ûPWMСÓÚ0
    {        
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
        
			Pwm = (-Pwm);			//PWMÖ»ÄÜÈ¡ÕýÖµ£¬Èç¹ûΪ¸ºÊý£¬Ö±½ÓÈ¡·´
        
			__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, Pwm);	//PWMµ÷ËÙ
			
    } else
    {
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
			HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
			
      __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, Pwm);	//PWMµ÷ËÙ
    }
}

void ModePID()
{
  GetMotorSpeed();
  MotorOutput = SpeedInnerControl(MotorSpeed,SpeedTarget);
  SetMotorVoltageAndDirection(MotorOutput);
}

         主函数代码:

 while (1)
 {  
    		 switch(flag)    
    		 {
    		 case(1):MOTOR_GO();break;
    		 case(2):MOTOR_BACK();break;
    		 case(3):MOTOR_STOP();break;
    		 case(4):MOTOR_UP();break;
    		 case(5):ModePID();break;
    		 default:break;
    		 }     
		
    /* USER CODE END WHILE */
		if(flag != 5)
		{
		int CaptureNumber =	(short)__HAL_TIM_GET_COUNTER(&htim2);	  //???????
		__HAL_TIM_GET_COUNTER(&htim2) = 0;
//    int 	Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2); 
		//µç»úתËÙת»»Speed=1sÄÚµÄÂö³åÊý/44(һȦ11¸öÐźţ¬4±¶Æµ·¨)/34¼õËÙ±È
//		int Speed=CaptureNumber*5/44/34*2*3.14*3;
    int 	Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2); 		
		if(Direction == 1)
		{
				CaptureNumber -= 65535;
		}
			
		int Speed=CaptureNumber;
    OLED_ShowNum(40,0,Speed,5,16);
		HAL_Delay(100);
		OLED_CLS();			
		}
		
    int 	Direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim2); 			
		OLED_ShowCN_STR(0,0,0,3);
//    OLED_ShowNum(40,0,Speed,4,16);
		OLED_ShowStr(90,0,"cm/s",2);
		OLED_ShowCN_STR(0,3,3,2);		
    if(Direction==0)
		{
			OLED_ShowCN_STR(40,3,5,2);	
		}
		if(Direction==1)
		{
			OLED_ShowCN_STR(40,3,7,2);
		}		
//		HAL_Delay(1000);
//		OLED_CLS();
    /* USER CODE BEGIN 3 */
  }

        蓝牙APP源代码以及技术论文:链接:https://pan.baidu.com/s/1-rbicxuyLVCq6rglCWcJTg 
提取码:huzm

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于stm32的减速直流电机PID算法控制 的相关文章

  • Carsim 2019 Run Now 按钮灰色

    安装carsim后 xff0c Run control with Simulink 模块中的Run Now 和Send to simulink 按钮灰色如下图所示 xff1a 解决办法 xff1a 在License Setting中 xff
  • Ubuntu 添加串口权限

    ubuntu串口添加权限方法 Ubuntu 添加串口权限前言一 添加用户组 xff0c 可长期使用二 给当前终端权限 xff08 单次有效 xff09 1 指定串口2 通用 三 修改文件 Ubuntu 添加串口权限 提示 xff1a 文章写
  • Ubuntu 虚拟机右上角网络连接符号消失

    这里写自定义目录标题 Ubuntu 虚拟机右上角网络连接符号消失解决方案 xff1a Ubuntu 虚拟机右上角网络连接符号消失 Ubuntu 虚拟机右上角网络连接符号消失 xff0c 如下图所示 解决方案 xff1a span class
  • C/C++中局部变量static用法实例

    1 普通局部变量存储于进程栈空间 xff0c 使用完毕会立即释放 xff0c 静态局部变量使用static修饰符定义 xff0c 即使在声明时未赋初值 xff0c 编译器也会把它初始化为0 xff0c 并且静态局部变量存储于进程的全局数据区
  • 嵌入式C语言经典面试题(一)

    1 用预处理指令 define 声明一个常数 xff0c 用以表明1年中有多少秒 xff08 忽略闰年问题 xff09 define SECONDS PER YEAR 60 60 24 365 UL 我在这想看到几件事情 xff1a 1 d
  • 更新Ubuntu内核到最新版本

    想起自己多年前玩Linux的时候知道了两个命令 xff1a sudo apt get update sudo apt get upgrade 以为是能够更新所有软件的 xff0c 后来发现 系统还是不能够更新的 那么 xff0c 系统应该如
  • RK3308 按键Key与LED灯

    硬件原理图 LED指示灯 麦克风阵列子板上使用12颗RGB灯作为效果指示灯 用户可以通过I2C总线配置LED灯驱动IC来是实现不同场景下的灯效 按键Key 麦克风阵列子板上集成五个控制按键 xff0c 分别为 xff1a 控制音量增减的VO
  • if选择结构

    if单选择结构 if双选择结构 if多选择结构 span class token keyword if span span class token punctuation span score span class token operat
  • Windows10下Eclipse+Python环境配置与新项目创建

    最近心血来潮 xff0c 突然想学一下python xff0c 按理说应该不用Eclipse xff0c 但是一想以后还可能会用Java xff0c 那还是装这个 xff0c 然后配置一下环境吧 xff0c 其中也有很多坑 xff0c 希望
  • 理解地址空间和逻辑地址生成

    1 1 地址空间 物理地址 xff1a 硬件 例如内存条 所支持地址空间 xff0c 地址空间的管理由硬件完成 逻辑地址 虚拟地址 xff1a 运行地址所看到的地址空间 xff0c 地址空间是一维的 xff0c 应用程序更加容易访问和管理
  • QT DirectShowPlayerService::doSetUrlSource: Unresolved error code 0x800c000d ()

    使用QT播放音频的时候出现如下错误 DirectShowPlayerService doSetUrlSource Unresolved error code 0x800c000d 原因是url错误
  • 3种蓝牙架构实现方案(蓝牙协议栈方案)

    导言 不同的蓝牙架构可以用在不同的场景中 从而协议帧的架构方案也会不同 转载自 xff1a https www cnblogs com schips p 12293141 html 三种蓝牙架构实现方案 xff08 蓝牙协议栈方案 xff0
  • 驱动遍历句柄表

    xfeff xfeff 驱动遍历句柄表附加第二个方法的反汇编代码 其中还有对其拦截的方式的一些需要HOOK处比如伪造句柄表 因为大量使用硬编码所以此份代码通用性不强一切均在虚拟机XP3下操作 include 34 ntddk h 34 ty
  • Javascript案例:猜数字游戏

    要求 程序随机生成一个1 10之间的数字 xff0c 并让用户输入一个数字 如果大于该数字 xff0c 就提示 xff0c 数字大了 xff0c 继续猜如果小于该数字 xff0c 就提示 xff0c 数字小了 xff0c 继续猜如果等于该数
  • 操作系统之进程 (五) --- 进程、进程实体、PCB...

    文章目录 进程什么叫进程什么叫进程实体进程与进程实体的关系PCB的存储信息与分类 进程的组织方式链接方式索引方式 进程的特征总结感谢 进程 什么叫进程 进程和程序差不多 xff0c 他们有所联系也有所区别 我们以我们熟悉的程序入手 xff0
  • 如何让树莓派默认启动进入图形界面

    设置Raspbian图形启动 当你第一次安装Raspbian系统时 xff0c 确实有一些选项需要你来配置 xff0c 由于匆忙 xff0c 我没有注意到这些 xff0c 只是快速完成屏幕上的选项 如果你遇到了和我一样的情况 xff0c 最
  • ROS与stm32通信

    0 概述 ros和stm32等嵌入式单片机的最大区别在于ros主要用于处理slam 机器视觉 人工智能这种对于算力要求高 xff0c 算法复杂的问题 xff1b 而stm32和arduino等主要用来处理一些边缘事件 xff0c 比如亮个L
  • 硬件仪器的使用

    示波器的使用 用于红外捕捉 xff0c 一开始可以把探头扣在探头补偿的位置 xff0c 显示出一个正常的方波信号5V 1KHz 按下CH1的菜单 xff0c 能够弹出右边的选项 xff0c 注意设置为直流和10X电压 按下触发处的Menu菜
  • pytorch显存越来越多的一个潜在原因-- 这个函数还没有在torch.cuda.Tensor中定义

    最近在用pytorch跑实验 xff0c 有如下操作需要用到 xff08 pytorch版本为0 3 1 xff09 class SpatialFilter nn Module def init self mode 61 True sf r
  • KPI异常检测【一】- 时间序列分解算法

    目录 1 相关概念 2 常见的时间序列 3 时间序列分解 3 1 方法介绍 3 2 经典方法 3 3 Holt Winter 指数平滑 3 4 STL分解 4 异常准则 5 异常检测算法 1 相关概念 1 1 异常 时序异常检测通常形式化为

随机推荐

  • KPI异常检测【三】- 机器学习算法

    目录 1 相关概念 1 1 异常类型 1 2 检测方法 2 点异常检测算法 2 1 基于统计 2 2 基于相似度 2 2 1 基于距离 2 2 2 基于密度 2 2 3 基于聚类 2 2 4 基于树 2 3 基于谱 spectral 3 上
  • 手把手教你如何安装ROS+Gazebo+PX4开发环境(ubuntu18.04 + Melodic)

    参考PX4官网 xff0c 做如下总结 xff1a 1 安装 Ubuntu 43 ROS 步骤省略 xff0c 提前配置好梯子 xff0c 很多安装问题都是网络原因引起的 2 下载并安装PX4源码 git clone https githu
  • ROS+Gazebo+PX4仿真步骤

    本文参考链接 xff1a 入门教程 PX4 Gazebo仿真 知乎 zhihu com 1 参照上一个帖子 手把手教你如何安装ROS 43 Gazebo 43 PX4开发环境 xff08 ubuntu18 04 43 Melodic xff
  • ROS+Gazabo+PX4仿真学习常用网站记录

    1 PX4官网 xff1a PX4 User Guide 中文网站 xff1a https docs px4 io master zh ros mavros custom messages html 2 ROS Wiki xff1a cn
  • 如何订阅(Subscribe)现有功能包节点的话题,介绍实现思路

    我们从github或其他途径获取到的ROS功能包 xff0c 如何快速的加入到自己的工程里边 xff0c 下边介绍一下我实现的思路 1 首先运行功能包 xff0c 利用 rqt graph 命令查看 节点图 xff0c 对功能包的节点和话题
  • PX4仿真时,如何在Gazebo下添加物理环境

    1 安装Gazebo xff0c 步骤略 2 下载安装Gazebo需要的世界模型 xff0c 否则自动安装速度很慢 3 在Ubuntu终端里输入 gazebo xff0c 打开一个空的 Gazebo 界面 4 点击左上方的 insert x
  • 树莓派如何使用串口,树莓派连接pixhawk

    参考链接 xff1a xff08 写的非常详细 xff0c 下述方法亲测有效 xff09 pi 3 How do I make serial work on the Raspberry Pi3 PiZeroW Pi4 or later mo
  • 树莓派备份系统

    使用 rpi backup 脚本进行备份 首先下载该脚本 xff1a git clone https github com nanhantianyi rpi backup git cd rpi backup 用法 xff1a sudo ba
  • Linux和Windows可执行文件的区分

    一些后缀区分 现在PC平台流行的可执行文件格式 xff08 Executable xff09 xff0c 主要有以下两种格式 xff08 COFF xff08 Common file format xff09 格式的变种 xff09 xff
  • 树莓派+ubuntu18.04+ROS-melodic+MAVROS+librealsense+vio+realsense_ros

    目录 一 树莓派安装ubuntu18 04 1 下载ubuntu系统文件 2 将系统文件烧入SD卡 3 强制修改HDMI输出分辨率 xff08 此步骤可忽略 xff09 4 设置wifi xff08 此步骤也可忽略 xff0c 后续连接网线
  • APM+Gazebo 垂起固定翼VTOL软件在环仿真

    APM 43 Gazebo 垂起固定翼VTOL软件在环仿真 1 APM软件环境下载并安装APM固件加入环境变量 2 运行APM仿真3 Gazebo环境安装4 增加VTOL模型5 多机仿真6 使用mavros 1 APM软件环境 参考官网链接
  • 进程间通信的五种方式

    进程间通信的意思就是在不同进程之间传递信息 它是一组编程接口 xff0c 让程序员协调不同进程 xff0c 使能够相互传递消息 IPC目的 1 xff09 数据传输 xff1a 一个进程需要将它的数据发送给另一个进程 xff0c 发送的数据
  • Arduino采集Microzone遥控器信号(ROSMicrozoneBridge)

    关注微信公众号 混沌无形 xff0c 后台回复 xff1a 13462F6 免费获取完整工程源码 xff01 本文采用Arduino mega2560的6个外部中断来采集Microzone接收机的6个通道的PPM信号 xff0c 主要是测量
  • 路径规划之基于优化的规划算法

    关注同名微信公众号 混沌无形 xff0c 阅读更多有趣好文 xff01 原文链接 xff1a 机器人曲线插值拟合算法研究现状简述 xff08 包含原文PDF百度云下载链接 xff09 第三类算法是主要是将多目标多变量多约束耦合的规划模型转化
  • 常见移动机器人多角度对比分析

    混沌无形 混沌系统是世界本质 xff0c 无形之中存在规律 机器人智能化发展从线性过渡到混沌 xff0c 本号将分享机器人全栈技术 xff08 感知 规划 控制 xff1b 软件 机械 硬件等 xff09 43篇原创内容 公众号 文末提供原
  • 麦克纳姆轮运动特性分析

    混沌无形 混沌系统是世界本质 xff0c 无形之中存在规律 机器人智能化发展从线性过渡到混沌 xff0c 本号将分享机器人全栈技术 xff08 感知 规划 控制 xff1b 软件 机械 硬件等 xff09 43篇原创内容 公众号 文末提供原
  • 全向轮运动特性分析

    文末提供原文PDF免费下载 期刊论文版式 混沌无形 混沌系统是世界本质 xff0c 无形之中存在规律 机器人智能化发展从线性过渡到混沌 xff0c 本号将分享机器人全栈技术 xff08 感知 规划 控制 xff1b 软件 机械 硬件等 xf
  • matlab稳态和时变卡尔曼滤波器Kalman filter的设计和仿真植物动力学模型案例研究

    最近我们被客户要求撰写关于卡尔曼滤波器的研究报告 xff0c 包括一些图形和统计输出 本案例研究说明了卡尔曼滤波器的设计和仿真 考虑稳态和时变卡尔曼滤波器 植物动力学 考虑一个在输入u n 上有加性高斯噪声w n 此外 xff0c 让 yv
  • 正点原子-操作系统

    正点原子另一个学习网站 http www openedv com docs book videos zdyzshipin 4free zdyz freertos book html
  • 基于stm32的减速直流电机PID算法控制

    本例程采用了HAL库进行项目开发 xff08 主要使用软件CubexMX和keil5 xff09 xff0c 文章末尾会有代码开源 xff0c 欢迎各位对文章进行指正和探讨 基于PID的减速电机控制 一 硬件模块与原理图 1 硬件组成 硬件