FreeRTOS의 STM32F103 이륜 밸런싱 카 이식(오픈 소스, 기사 마지막 부분의 코드)

3~4일 정도 소요됐고, 하드웨어에 주로 시간을 쏟았습니다.

소개

1. 시스템 개요

1.1 디자인 작업

stm32f103을 메인 제어로 사용하고 실시간 작업 스케줄링을 위해 FreeRTOS를 이식합니다.

1.2 설계 요구사항

MPU6050을 사용하여 해당 각도를 읽고, STM32를 사용하여 MPU6050에서 읽은 데이터를 처리하고, pid 알고리즘을 사용하여 2륜 자동차를 제어하여 자체 균형을 달성합니다. 그리고 자동차의 이동 방향을 제어할 수 있는 모바일 앱의 기능을 확장합니다.

1.3 하드웨어 목록

여기서 사용한 재료에 대해 이야기하겠습니다.

  • 모터 2개, 모델 JGB37-520 인코딩 모터
  • 2WD 밸런스 카 아크릴 섀시
  • tb6612 모터 드라이버 보드
  • 모형 항공기 배터리 1500
  • 5V 강압 모듈
  • Jialichuang에서는 PCB 보드 프로토타입을 무료로 제공합니다. 인쇄하기 전에 브레드보드를 ​​사용하여 연결된 선이 올바른지 테스트하는 것이 가장 좋습니다. 그렇지 않으면 계속해서 변경해야 하는 번거로움이 있습니다.
  • 여러 여성 행
  • 여러 핀 행
  • DHT11 선박 유형 스위치 1개

2. 계획 설계 및 시연

2.1 칩 선정 계획

stm32F103RCT6을 마스터로 사용

stm32는 1,000회 반복적으로 지우고 쓸 수 있는 4k 바이트 ISP(시스템 내 프로그래밍 가능) 플래시 읽기 전용 프로그램 메모리를 포함하는 저전력, 고성능 32비트 마이크로컨트롤러입니다. 주요 성능은 다음과 같습니다: MCS-51 마이크로컨트롤러 제품과 호환, 완전 정적 작동: 0Hz ~ 33Hz, 3단계 암호화된 프로그램 메모리, 32개의 프로그래밍 가능 I/O 라인, 3개의 16비트 타이머/카운터, 8개의 인터럽트 소스, 전이중 UART 직렬 채널, 전원 차단 후 인터럽트 웨이크업, 워치독 타이머, 듀얼 데이터 포인터, 전원 차단 식별자, 프로그래밍 용이.

시리즈명 STM32F 펄스 폭 변조 2(16비트)(모터 제어)
포장 종류 LQFP 프로그램 메모리 유형 플래시 메모리
설치 유형 표면 실장 너비 10.2mm
핀 수 64 높은 1.45mm
장치 코어 ARM 코어텍스 M3 타이머 해상도 16비트
데이터 버스 폭 32비트 아날로그-디지털 변환기 3(16 x 12비트)
프로그램 메모리 크기 256kb I2C 채널 수 2
최대 주파수 72MHz 길이 10.2mm
메모리 크기 48kb 최대 작동 온도 +85°C
USB 채널 기기 1대 아날로그-디지털 변환기 장치 수
PWM 유닛 수 2 타이머 수 6
아날로그-디지털 변환기 채널 16 아날로그-디지털 변환기 분해능 12비트
SPI 채널 수 최소 작동 온도 -40°C
일반적인 작동 공급 전압 2. →3.6V PWM 해상도 16비트
USART 채널 수 5 명령어 세트 구조 RISC
시간제 노동자 6x16비트 CAN 채널 수 1
크기 10.2x10.2x1.45mm

STM32F103RCT6을 추가합니다. 각 필드의 의미는
STM32(칩 시리즈): STM32는 ARM Cortex-M 코어가 포함된 32비트 마이크로 컨트롤러를 나타냅니다.
103(칩 하위 시리즈): 101 기본 유형, 102 USB 기본 유형(USB2.0 ), 103은 향상된 유형을 나타냅니다. 시리즈, 105 또는 107 상호 연결 유형
F(제품 유형): F는 일반 시리즈
R(핀 수)을 나타냅니다. T=36, C=48, R=64, V=100, Z =144
C(플래시 메모리 용량): 4=16K, 6=32K, 8=64K, B=128K, C=256K, D=384K, E=512K

T(테이블 패키지):
H는 BGA 패키지를 나타냅니다.
T는 LQFP 패키지를 나타냅니다.
U는 VFQFPN 패키지를 나타냅니다.
Y는 WLCSP64를 나타냅니다.

6(작동 온도 범위): 6은 -40 - 85℃를 나타내고, 7은 -40 - 105℃를 나타냅니다.
 

2.2 시스템 개요

이 디자인은 자동 밸런스 조정 기능을 갖춘 이륜차입니다. MPU6050 모듈, 1.44인치 LCD 디스플레이, TB6612 모터 구동 모듈, 홀 모터, 모형 항공기 배터리 전원 공급 회로 및 기타 모듈로 구성됩니다. 이 프로젝트는 마이크로컨트롤러 PID 알고리즘을 사용하여 자체 균형 조정 방식을 연구합니다. 이 솔루션은 나중에 자체 균형을 이루는 운송 도구, 자체 균형을 이루는 자전거 등으로 만들어질 수 있습니다.

2.3 설계 요구사항

IIC 통신
PID 알고리즘을 조정하려면 지터가 1cm를 초과하지 않아야 합니다.
자동차는 일반적으로 자체 균형을 달성할 수 있습니다.
LCD는 요/피치/롤 각도의 데이터를 표시합니다.
자동차는 제어를 통해 간단히 앞뒤로 이동할 수 있습니다.

2.4 전체 시스템 설계

stm32 및 MPU6050을 사용하여 통신하고 mpu6050에서 보낸 데이터를 실시간으로 획득하여 1.44LCD에 표시합니다. PID 알고리즘과 결합된 mpu6050의 데이터를 사용하여 모터 드라이브 블록을 제어하여 모터의 제곱 회전을 제어합니다. 자기 균형을 이루기 위해.

2.5 중요 기능 모듈의 프로그램 구현 원리 분석

2.5.1 MPU6050 모듈 소개

실제로, 일반인의 관점에서 이 모듈은 자동차의 자세각을 측정하는 데 사용됩니다.

자세각을 논하기 전에 먼저 신체좌표계를 이해할 수 있다.

신체 좌표계

자동차의 자세각 - 오일러각

항공기가 위아래로 고개를 끄덕이는 모습을 상상할 수 있는데, 이때 동체의 상반부와 수평면이 이루는 각도가 피치각입니다.

자동차의 자세 각도 - 요 각도

 비행기의 기수가 좌우로 흔들리는 각도와 초기 기수 위치를 상상할 수 있습니다.

자동차의 자세각 - 롤 각도

비행기가 공중에서 수평으로 굴러가는 모습을 상상해 보세요

 2.5.2 ESP8266 모듈

아마도 이런 것 같아요

주로 AT 명령을 통해 제어

esp8266.c
#include "esp8266.h"
#include "string.h"
#include "usart.h"
#include "usart2.h"
#include "delay.h"

char a[]="AT+CWMODE=1";
char b[]="AT+RST";
char c[]="AT+CWJAP=\"lfh\",\"81009738\"";                
char d[]="AT+CIPMUX=1";
char e[]="AT+CIPSTART=0,\"TCP\",\"115.29.109.104\",6552";
char f[]="AT+CWLAP";

void esp8266_start_trans(void)
{
		//重启
	esp8266_send_cmd1((u8 *)b);
    delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);


	//设置工作模式 1:station模式   2:AP模式  3:兼容 AP+station模式
	esp8266_send_cmd1((u8 *)a);
	delay_ms(1000);
	delay_ms(1000);
	
	//重启
	esp8266_send_cmd1((u8 *)b);
  delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);

	//连接WIFI
	esp8266_send_cmd1((u8 *)c);
  delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);
	delay_ms(1000);

	
	esp8266_send_cmd1((u8 *)d);
  delay_ms(1000);
	delay_ms(1000);

	esp8266_send_cmd1((u8 *)e);
  delay_ms(1000);
	delay_ms(1000);

}

void esp8266_send_cmd1(u8 *cmd)
{
  u2_printf("%s\r\n",cmd);	//发送命令,需要加换行符

}
 

esp8266.h
#ifndef __ESP8266_H
#define __ESP8266_H
#include "stdio.h"	
#include "stm32f10x.h"


//函数
void esp8266_send_cmd1(u8 *cmd);
void esp8266_start_trans(void);

#endif

3.PID 알고리즘은 균형을 유지하기 위해 자동차를 제어합니다.

원저자의 밸런스카 PID는 이렇게 조정해 주어야 합니다! ! ! - 지후(zhihu.com)

이걸 보면 누구나 PID 알고리즘을 접했을 텐데요, 여기서 MPU6050 데이터를 읽어서 계산한 자세각은 주로 모터의 회전 속도를 제어하여 균형을 유지하는 데 사용됩니다.

밸런싱 카의 PID에는 직립 루프, 속도 루프, 조향 루프 의 세 가지 유형의 PID가 있습니다 . PID의 최종 계산은 모터에 할당되어야 하는 모터 PWM입니다. 차례로 중첩되어 모터의 최종 제어 PWM이 획득됩니다.

3.1  직립형 링 PID

직립형 링에서 PID의 입력 매개변수는 균형차의 자세각과 자세각에 해당하는 각속도입니다.

MPU6050에서 얻은 세 가지 자세각은 PITCH(피치 각도), ROLL(롤 각도), YAW(요 각도)입니다.

일반적으로 MPU6050은 밸런스 카와 평평하고 평행하게 배치되므로 매개변수를 조정하는 것이 더 직관적입니다.

평평하게 놓고 평행하게 설치한 경우 직립 링 PID의 입력 매개변수는 피치 또는 롤입니다. 직립 링에는 기계적 중앙값 이라는 더 중요한 개념이 있습니다 . 일반인의 관점에서 말하면, 자동차는 외부 힘이나 모터 동작을 받아들이지 않고도 스스로 균형을 잡을 수 있는 각도를 찾을 수 있습니다. 이 문장을 이해하는 방법: 매우 간단합니다. 자동차의 모터는 회전하지 않습니다. 사람이 차를 손으로 잡으면 자동차는 항상 단기간에 각도를 찾아 스스로 균형을 잡을 수 있습니다 . 이때의 각도는 기계적 중앙값이다.

암호

/*******************************************************************
函数功能:直立PD控制
入口参数:角度、机械平衡角度(机械中值)、角速度
返回  值:直立控制PWM
作    者:张巧龙
******************************************************************/
int balance_UP(float Angle,float Mechanical_balance,float Gyro)
{  
   float Bias;//角度误差
   int balance;//直立环计算出来的电机控制pwm
   Bias=Angle-Mechanical_balance;                   
   //===求出平衡的角度中值和机械相关
   balance=balance_UP_KP*Bias+balance_UP_KD*Gyro;  
   //===计算平衡控制的电机PWM  PD控制   kp是P系数 kd是D系数 
   return balance;
}

절차적 관점에서 보면:

Balance_UP_KP는 직립 링의 P입니다 .

Balance_UP_KD는 직립 링의 D입니다.

P와 D의 크기와 극성을 결정하는 방법은 무엇입니까?

3.1.1 직립 링 P의 범위 결정  :

먼저 PWM 범위를 결정해야 합니다.예를 들어 타이머의 최대 PWM은 7200입니다. 이때 듀티 사이클은 100%이고 모터는 최고 속도로 작동해야 합니다. 자동차를 똑바로 세워야 하는 경우 스윙 진폭은 10° 이하 여야 합니다 . , 이 범위를 초과하면 자동차가 더 심하게 흔들리게 됩니다. 직립 링의 절차에 따르면:

//***********平衡车控制******************************************
//函数功能:控制小车保持直立
//Angle:采集到的实际角度值
//Gyro: 采集到的实际角速度值
int zhili(float Angle,float Gyro)
{  
   float err;
	 int pwm_zhili;
	 err=Car_zero-Angle+Car_zero_offset;    //期望值-实际值,这里期望小车平衡,因此期望值就是机械中值       
	 pwm_zhili=zhili_Kp*err+Gyro*zhili_Kd;//计算平衡控制的电机PWM
	 return pwm_zhili;
}

최대 PWM은 7200 이고 각도는 10° 이므로 반전을 통해 얻을 수 있습니다. 직립 링의 P 옵션 범위는 0~700이어야 합니다.

물론 이는 대략적인 범위일 뿐이며 추가 디버깅이 필요합니다.

3.1.2  직립 링 P의 극성 결정:

극성은 부호이며 P는 양수이기도 하고 음수이기도 합니다.

kp에 직접 양수 값과 음수 값을 부여한 후 현상을 관찰합니다.

정상적인 현상은 부정적인 피드백으로 자동차가 그 방향으로 떨어지면 모터가 회전하여 자동차가 떨어지는 방향으로 추격하게 됩니다. 이렇게 하면 차가 반대 방향으로 세워질 수 있습니다!

자동차가 어느 방향으로 떨어질지 긍정적인 피드백이 있으면 모터가 회전하여 자동차가 빠르게 떨어지게 됩니다. 이 현상은 단순히 잘못된 것입니다.

3.1.3  직립 링 P의 크기 결정

천천히 시행착오를 거치면서 작은 것부터 큰 것까지 반응이 점차 가속됩니다. 즉, 자동차가 큰 저주파 지터를 가질 때까지 추락 후 수직으로 돌아오는 시간이 점점 더 짧아지고 있습니다 !

이때 P를 결정할 수 있다.

3.1.4   직립 링 D의 극성 결정

D의 극성은 상대적으로 쉽게 판단할 수 있는데, P를 0으로 설정하고 D에 양수와 음수 값을 부여한 뒤, 따로 실험해 보면 효과를 볼 수 있다.

자동차를 들어 회전시키면 자동차의 바퀴가 자동차와 같은 방향으로 회전해야 합니다. 이는 극성이 올바른 것을 의미합니다.

자동차의 바퀴가 자동차와 다른 방향으로 회전한다면 극성이 반대라는 뜻입니다!

3.1.5  직립 링 D의 크기 결정

D의 크기를 P와 연동하여 디버깅해야 합니다. P를 조정한 후 D를 추가하고 작은 것부터 큰 것으로 천천히 시도해 보십시오. 프로그램 PD에서 각속도가 더 높기 때문에 D가 각속도에 해당함을 알 수 있습니다. 4자리 이상의 값이므로 0.1부터 시작해 볼 수 있습니다.

자동차가 고주파로 격렬하게 진동하기 시작할 때까지.

자동차의 기계적 메커니즘이 모든 면에서 고르게 분포되어 있고, 무게가 잘 분산되어 있고, 무게 중심이 낮다면 간단한 직립 링으로 자동차를 일시적으로 안정시킬 수 있다는 점에 유의해야 합니다.

하지만 일반적으로 자동차의 기계적 구조가 아주 좋은 사람은 없습니다.

따라서 단순히 수직 링에만 의존하여 트롤리를 안정시키는 것은 불가능합니다. 속도 루프를 추가해야 합니다.

간단한 수직 링으로 트롤리가  5초 동안  정지할 수 있다면 조정이 매우 잘 되었다는 뜻입니다!

3.2 속도 루프

속도 루프에서는 PI 제어가 사용되며 적분 제어와 비례 제어 사이에는 일정한 비례 관계가 있습니다.

200이라고 판단할 수 있습니다. 이유는 묻지 마세요. 이유가 없습니다. 200만 달라고 하세요!

속도 루프의 입구 매개변수는 속도 측정인 자동차의 두 모터 인코더의 값입니다!

자동차 속도에 대한 실시간 피드백이 없으면 속도 폐쇄 루프도 없습니다.

암호

//*****************************************************************
//函数功能:控制小车速度
//encoder_left: 左轮编码器值
//encoder_right:右轮编码器值   因为程序周期执行,所以这里编码器的值可以理解为速度
int sudu(int encoder_left,int encoder_right)
{  
	  static int pwm_sudu,Encoder_Least,Encoder;
	  static int Encoder_Integral;	
		Encoder_Least =(encoder_left+encoder_right)-Movement;  //获取最新速度偏差==测量速度(左右编码器之和)-目标速度(此次为零) 
		Encoder *= 0.8;		                             //一阶低通滤波器       
		Encoder += Encoder_Least*0.2;	                 //一阶低通滤波器    
  	Encoder_Integral +=Encoder;                     //积分出位移 积分时间:5ms
		if(Encoder_Integral>8000)  Encoder_Integral=8000;  //积分限幅
		if(Encoder_Integral<-8000)	Encoder_Integral=-8000; //积分限幅	
		pwm_sudu=sudu_Kp*Encoder+sudu_Ki*Encoder_Integral;     //速度PI控制器	
		if((pitch>=80)||(pitch<=-80))  //小车跌倒后清零
		{
		  Encoder_Integral=0;    
		}			
	  return pwm_sudu;
}

3.2.1 속도 루프 P 범위 결정:

마찬가지로 직립 링 P의 크기 범위를 결정하는 것과 마찬가지로 모터 인코더의 최대값과 PWM의 최대값 사이의 관계를 알아야 합니다!

프로그램에서 볼 수 있듯이, 우리가 비교해야 할 것은 두 모터의 속도 편차와 PWM 사이의 관계입니다.

예: STM32 타이머의 직교 디코딩 모드를 사용하여 10ms마다 한 번씩 모터 속도를 측정합니다.

트롤리 모터가 최대 속도로 회전할 때 왼쪽 및 오른쪽 모터와 인코더의 합은 160에 도달할 수 있습니다.

속도 편차(실제 측정값과 이상값)가 50%에 도달하면 최대 회전에 도달하는 것으로 가정합니다.

그러면 160/2=80, 7200/80=90이 있는데, 이는 최대 kp가 90임을 의미합니다.

(참고로 이는 50%만 가정한 것입니다.)

90은 참고값일 뿐이며 구체적인 값은 실제 테스트 효과를 기반으로 해야 합니다.

3.2.2 속도 루프 P의 극성 결정:

P의 극성을 결정하려면 위에서 언급한 직립 루프를 닫아야 합니다. 이는 전체 시스템의 제어 매개변수가 속도 루프의 P만을 가질 수 있음을 의미합니다.

업라이트 링만으로 차량을 제어할 경우 차량은 짧은 시간 동안 똑바로 서 있을 수 있지만 앞뒤로 움직이다가 넘어지는 현상이 발생하는데 스피드 링을 사용하여 이러한 현상을 억제합니다.

위 프로그램에서 볼 수 있듯이:

 Encoder_Least =(Encoder_Left+Encoder_Right)-0;      
 //===获取最新速度偏差==测量速度(左右编码器之和)-目标速度(此处为零)

이 프로그램의 의미는 최신 속도 편차를 구해 자동차의 목표 속도를 0으로 제어하는 ​​것입니다.

직립 링은 자동차가 떨어지는 것을 방지하기 위해 자동차의 각도를 제어하는 ​​데 사용되므로 직립 링의 기계적 중앙값은 다음과 같습니다 .

속도 루프는 자동차가 떨어지는 것을 방지하여 자동차의 속도를 제어하는 ​​데 사용되므로 속도 루프의 "중간값"은 속도가 0입니다.

이해하기 어렵지 않을 것입니다!

그렇다면 자동차 속도를 0으로 억제하는 방법은 무엇입니까?

자동차의 현재 속도를 알 수 있으므로 속도 루프의 P가 양의 피드백인 한 자동차가 앞으로 넘어지면 자동차가 직립을 유지하기 위해 더 빠른 속도로 앞으로 돌진한다는 의미입니다.

마찬가지로 위에서 언급한 직립 루프를 차폐하고 속도 루프 P에 각각 양의 값과 음의 값을 부여하여 현상을 살펴봅니다.

긍정적 피드백 현상은 다음과 같습니다.

바퀴 중 하나가 회전하면 두 바퀴 모두 같은 방향으로 최대 속도로 회전합니다. 이는 긍정적인 피드백이어야 합니다. 이때의 현상은 속도 루프의 P 극성이 정확하다는 것을 보여줍니다!

한 바퀴가 회전하면 다른 바퀴도 반대 방향으로 회전하므로 편차가 0이 되는 경향이 있습니다. 이것은 부정적인 피드백입니다. 이는 P의 극성이 잘못되었음을 의미합니다!

3.2.3 속도 루프 P의 크기 결정:

P의 극성과 크기를 결정한 후 P와 I는 비례하고 P는 I의 200배이므로! P와 I의 크기를 함께 조정할 수 있으며 P와 I의 매개변수를 작은 것부터 큰 것까지 점차적으로 시도하여 자동차의 효과를 확인할 수 있습니다.

다음과 같은 효과가 나타나는 경우:

1. 자동차를 지면에 내려놓고 시간이 지나면서 천천히 자동차가 앞뒤로 흔들리는데 이때 P와 I의 매개변수가 너무 작다고 볼 수 있다.

2. 차를 지면에 내려놓고 손으로 밀어주세요. 차가 원위치로 돌아가지 못하고 계속 앞뒤로 흔들리는 경우, 앞뒤로 흔들면 차체가 크게 기울어지게 됩니다. 이때 P와 I의 매개변수가 너무 크다고 생각했습니다. 차체가 크게 기울어지지 않고 차가 앞뒤로 흔들리기만 한다면 P와 I의 매개변수가 너무 작은 것으로 간주할 수 있습니다.

3.3 스티어링 링

3.3.1 스티어링 링 P 범위 결정

MPU6050에 의해 출력된 자이로스코프의 원시 데이터를 얻었습니다. 데이터를 관찰함으로써 우리는 다음을 발견했습니다.

최대값은 4자리(밸런싱 트롤리에서 일반적으로 사용되는 경우)를 초과하지 않으며 7200은 듀티 사이클을 나타냅니다.

100%보다 높으므로 kp 값은 0과 2 사이에 있어야 한다고 추정합니다.

3.3.2 스티어링 링 P의 극성 결정:

먼저 kp=-0.6으로 설정하면 자동차를 땅에 대고 회전시키면 다음이 가능하다는 것을 알 수 있습니다.

자동차가 쉽게 회전할 수 있다는 것은 자동차가 음의 피드백을 통해 목표 각속도를 0에 가깝게 제어하지 못한다는 것을 의미합니다.

이는 긍정적인 피드백을 사용하여 자동차를 회전시키는 데 도움을 주며, 이는 현재 자동차의 조향 시스템이 긍정적이고 부정적임을 보여줍니다.

피드백. 그런 다음 kp=0.6으로 설정했는데, 이때 자동차를 지면에 누르고 회전시켜 보면 활용도가 매우 크다는 것을 알 수 있습니다.

힘이 없어도 차를 돌리기가 어려운데, 자동차는 모터를 통해 우리에게 저항하고 각속도를 0으로 유지하는 것이 전형적인 예이다.

각속도의 음의 피드백 효과도 우리가 보아야 할 효과입니다.

3.3.3 스티어링 링 P의 크기 결정

디버깅 매개변수: 작은 것부터 큰 것까지, 그리고 자동차의 직선을 보고 결정합니다.

자동차 회전 방법: 스티어링 링은 이해하기 쉽습니다. 매개 변수는 mpu6050의 헤딩 각도의 각속도 자이로입니다. 회전하려면 최종 출력 스티어링 PWM에 편차를 주면 됩니다.

//*************************************************************
//函数功能:控制小车转向
//Set_turn:目标旋转角速度
//Gyro_Z:陀螺仪Z轴的角速度
//不是一个严格的PD控制器,为小车的叠加控制
int zhuan(float Set_turn,float Gyro_Z)
{
  int PWM_Out=0; 
	if(Set_turn==0)
	{
	 PWM_Out=zhuan_Kd*Gyro_Z; //没有转向需求,Kd约束小车转向
	}
	if(Set_turn!=0)
	{
	 PWM_Out=zhuan_Kp*Set_turn; //有转向需求,Kp为期望小车转向 
	}
	return PWM_Out;
}

3.4 PID 알고리즘에 대한 자세한 설명

사실 무엇을 하든 상관없이 무엇을 배우는가가 가장 중요합니다.이제 주요 개념을 요약하겠습니다.

PID 알고리즘(Proportional-Integral-Derivative Algorithm)은 일반적으로 사용되는 제어 알고리즘으로 산업용 제어 시스템에 널리 사용됩니다. 제어 대상의 상태를 측정하고 제어량을 계산하여 예상 목표를 달성할 수 있도록 제어 대상의 출력을 조정합니다.

PID 알고리즘은 비례항(Proportional), 적분항(Integral), 미분항(Derivative)의 세 부분으로 구성됩니다.

  1. 비례: 비례항은 제어 대상의 현재 편차(실제 값과 목표 값의 차이)를 기준으로 제어량을 계산합니다 . 비례 이득 계수 Kp는 비례 효과의 강도를 조정하는 데 사용됩니다. 편차가 크면 비례항의 역할도 커지고 제어량의 조정 범위도 커집니다.

  2. 적분 항(Integral): 적분 항은 일정 기간 동안 제어 대상의 누적된 편차를 고려 하고 시스템의 정적 편차를 제거하는 데 사용됩니다 . 적분 이득 계수 Ki는 적분 효과의 강도를 조정하는 데 사용됩니다. 적분 항은 장기적인 편차를 처리하는 데 더 효과적이며 비례 항은 시스템이 정상 상태 오류를 신속하게 제거하는 데 도움이 될 수 있습니다.

  3. 미분: 미분항은 제어 대상의 현재 편차 변화율을 기준으로 제어량을 계산합니다 . 미분 이득 계수 Kd는 미분 효과의 강도를 조정하는 데 사용됩니다. 미분항은 시스템의 미래 변화 추세를 예측하고, 편차의 변화율을 줄여 시스템의 안정성을 향상시킬 수 있습니다.

PID 알고리즘의 계산식은 제어량 = Kp * 편차 + Ki * 적분 편차 + Kd * 편차 변화율입니다.

그 중 편차는 실제값과 목표값의 차이이고, 적분편차는 편차의 누적이며, 편차변화율은 편차의 미분이다.

PID 알고리즘의 매개변수 조정은 중요한 문제이며 실제 조건에 따라 다양한 애플리케이션을 디버깅하고 최적화해야 합니다. 일반적인 매개변수 조정 방법에는 경험적 매개변수 조정, Ziegler-Nichols 방법, 퍼지 제어 등이 포함됩니다.

간단히 말해서, PID 알고리즘은 비례, 적분, 미분 제어 전략을 통해 제어 대상의 정밀한 제어를 달성하며, 간단하고 안정적이며 구현이 쉬우며 산업 자동화 분야에서 널리 사용됩니다.

4. 타이머 캡처 모드 판독 펄스

인코더의 작동 원리에서 인코더는 두 신호 채널(일반적으로 A 위상 및 B 위상이라고 함)의 펄스 신호를 통해 로터의 위치와 방향을 실시간으로 반영합니다. 이러한 펄스 신호의 변화로 인해 엔코더 내부의 카운터(CNT) 값이 그에 따라 증가하거나 감소합니다.

TIM2 및 TIM3은 STM32 타이머 모듈로, 외부 신호의 하이 또는 로우 레벨 지속 시간을 측정하고 해당 레지스터에 결과를 저장하는 데 사용할 수 있는 입력 캡처 기능이 있습니다.

이 코드에서는 TIM2 및 TIM3의 CH1 및 CH2 채널을 입력 캡처 모드로 구성하여 인코더 펄스 신호를 수신할 때 TIM2 및 TIM3이 자동으로 캡처된 펄스 수를 기록하고 CNT 레지스터에 저장합니다. 따라서 TIM2와 TIM3의 CNT 레지스터 값을 읽어 엔코더의 카운트 값을 얻어 로터의 위치와 방향을 알 수 있다.

구체적인 운영과정은 다음과 같습니다

#include "encoder.h"


//这里采用TIM2和TIM3的CH1和CH2通道进行编码器的接口输入
//定时器				CH1					CH2
//TIM2				PA0					PA1
//TIM3				PA6					PA7

//这里的定时器2、3挂载在APB1上
//**********************编码器时钟初始化*********************
void Encoder_Count_RCC(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
}
//**********************编码器引脚初始化*********************
void Encoder_Count_GPIO(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	//**********TIM2,PA0,PA1****************
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	//**********TIM3,PA6,PA7****************
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);

}
//**********************编码器功能初始化*********************
void Encoder_Count_Configuration(void)
{
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	//**********TIM2,PA0,PA1***********************************
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_1;
	TIM_ICInitStruct.TIM_ICFilter=0xF;  //滤波
	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInit(TIM2, &TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_2;
	TIM_ICInitStruct.TIM_ICFilter=0xF;
	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInit(TIM2, &TIM_ICInitStruct);	
	TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_Period=65535;    //65536-1
	TIM_TimeBaseInitStruct.TIM_Prescaler=0;     //1-1
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	TIM_Cmd(TIM2,ENABLE);  
	//**********TIM3,PA6,PA7***********************************
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_1;
	TIM_ICInitStruct.TIM_ICFilter=0xF;
	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_Channel=TIM_Channel_2;
	TIM_ICInitStruct.TIM_ICFilter=0xF;
	TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);	
	
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_Period=65535;    //65536-1
	TIM_TimeBaseInitStruct.TIM_Prescaler=0;     //1-1
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
	
	TIM_Cmd(TIM3,ENABLE);  
}
//**********************编码器初始化*********************
void Encoder_Count_Init(void)
{
  Encoder_Count_RCC();
	Encoder_Count_GPIO();
	Encoder_Count_Configuration();
}
//******************编码器数据读取********************************
int Encoder_Value(TIM_TypeDef* TIMx)
{ 
	int channal_val=0;
	
	channal_val = TIMx ->CNT;
	if(channal_val>>15)//  channal_val 的最高位(即符号位)移到最低位,然后判断该最低位是否为 1。
										//  如果是1那么证明从0开始反转,65535开始减,那么第十六这时候就是1
	
	{			
		channal_val =  (channal_val&0x7FFF)-32767;//通过 channal_val & 0x7FFF 将 channal_val 的最高位清零,保留其他位的值
	}	
  return channal_val;
}
//****************编码器清零*************************************
void Encoder_Count_Clear(TIM_TypeDef* TIMx)
{
  TIMx ->CNT = 0;
}


  1. #include "encoder.h": 인코더 관련 함수 및 매크로 정의를 선언하는 데 사용되는 헤더 파일 인코더.h가 포함되어 있습니다.

  2. Encoder_Count_RCC(): 인코더에서 사용하는 타이머(TIM2 및 TIM3)의 시계를 구성합니다.

  3. Encoder_Count_GPIO(): 엔코더 핀의 모드와 속도를 설정합니다. 여기서 TIM2는 PA0 및 PA1 핀에 연결되고 TIM3은 PA6 및 PA7 핀에 연결됩니다.

  4. Encoder_Count_Configuration(): 인코더의 기능을 구성합니다. 구체적인 단계는 다음과 같습니다:

  5. a. TIM2 및 TIM3의 입력 캡처 매개변수(TIM_ICInitStruct)를 초기화하고 채널을 1과 2로 설정합니다.
    b. 입력 캡처 필터와 극성을 설정합니다. 둘 다 상승 에지 트리거입니다 .
    c. TIM2 및 TIM3의 입력 캡처 구성을 초기화합니다.
    d. 인코더 모드를 TI12로 구성합니다(두 개의 신호 에지가 동시에 트리거됨 ).
    e. 타이머 클럭 분할 계수, 주기 및 프리스케일러 값을 구성합니다.
    f. TIM2 및 TIM3 타이머를 활성화합니다.

  6. Encoder_Count_Init(): 이전 함수를 호출하여 인코더 초기화를 완료합니다.

  7. Encoder_Value(TIM_TypeDef* TIMx): 엔코더 값을 읽습니다.
    a.TIMx의 CNT 레지스터를 사용하여 엔코더 카운트 값을 읽습니다.
    b. 최상위 비트가 1인지 확인합니다. 그렇다면 반전이 발생했음을 의미합니다.
    c. 가장 높은 비트를 지우고 다른 비트의 값을 유지한 후 결과에서 32767(최대 카운트 값의 절반)을 뺍니다.

  8. Encoder_Count_Clear(TIM_TypeDef* TIMx): 엔코더 카운트 값을 삭제합니다.

그런 다음 모터.c에서 MOTO_Speed_Read() 함수를 정의하여 레지스터에서 펄스 수인 값을 추출합니다.

void Moto_Speed_Read(u8 n)
{ 
	//1号电机测速
	if(n==1)
	{
	  speed_num1=Encoder_Value(TIM2); //读取XXms以后的编码器数值
		speed_num1=speed_num1*10;
		Encoder_Count_Clear(TIM2);       //将编码器清零,用于下次计数		
	}
	//2号电机测速
	if(n==2)
	{
	  speed_num2=-(Encoder_Value(TIM3));//读取XXms以后的编码器数值
		speed_num2=speed_num2*10;
		Encoder_Count_Clear(TIM3);      //将编码器清零,用于下次计数
	}	 
}

5. 발생한 문제를 공유하세요

베어메탈 개발 하드웨어 회로 문제

  1. PCB 제작시 반드시 구멍을 뚫어야 하며, 육각동기둥과 연결하려는 자동차 섀시의 구멍에 따라 위치가 결정되어야 하며, 크기도 PCB의 개구부 크기에 따라 결정되어야 합니다. 섀시 결국 동일한 육각형 구리 기둥이 사용됩니다.
  2. PCB 레이아웃 전에 브레드보드를 ​​사용하여 연결할 수 있습니다.
  3. 우송료를 절약하려면 더 많은 핀 헤더와 암 헤더를 구입하세요.
  4. 엔코더를 거꾸로 연결하지 않도록 주의하세요. 올바르게 연결되면 모터의 LED 표시등이 켜집니다.
  5. 용접시에는 용접하는 것을 잊어버리거나 약하게 용접하는 일이 없도록 주의하시고, 전압계를 사용하여 경로를 측정하시는 것이 좋습니다.
  6. 또한 모듈을 천천히 추가하기 전에 PCB의 전압을 측정해야 합니다.
  7. 칩이 위쪽을 향하도록 MPU6050 모듈을 수평으로 배치했습니다. 긴 쪽이 북쪽을 향하고 있으며 이는 피치 각도에 따라 결정될 수 있습니다. 코드가 다를 경우 수정이 필요하므로 변경이 쉽지 않을 수도 있으니 주의하세요.

베어메탈 개발 소프트웨어 관련 문제

문제점 : 타이머를 초기화하지 않고 단순히 프로그램을 굽기만 하면 차가 회전하지 않습니다.

알아두어야 할 점: 동일한 타이머를 두 번 초기화하면 후속 타이머 초기화 값이 이전 타이머를 덮어쓰게 됩니다. 왜냐하면 pwm이 먼저 초기화되고 그 다음에 인코더 기능이 초기화되기 때문입니다. 여기에 설정된 타이머 3의 PWM 카운터 재로드 값은 7200이며, 이는 엔코더에 의해 초기화된 재로드 값 65535로 덮어쓰기됩니다. TIM 비교 레지스터의 값이 720으로 설정되어 있으므로 이때 자동차의 듀티 사이클은 예상한 10%가 아니지만 매우 작아서 저항을 이기지 못해 회전이 발생하지 않습니다.

타이머는 CH1과 CH2만 인코더의 펄스를 읽는 데 사용되지만 채널 3과 4는 더 이상 PWM을 출력하는 데 사용할 수 없습니다. (이건 잘 모르겠습니다. 가능해야 하지만 다른 것을 사용하는 것이 가장 좋습니다. 것들)

처음에는 타이머 3의 CH1과 CH2를 사용하여 인코더 펄스를 캡처한 다음 CH3과 CH4를 사용하여 두 개의 PWM을 출력하여 모터를 제어했습니다.

그런데 제가 PWM을 입출력하는데 사용하는 타이머 1에서는 설정된 카운트 값이 7200이고, 타이머 2의 엔코더 카운팅 모드로 보면 TIMx의 카운터 값이 65535(즉, TIM_TimeBaseInitStruct.TIM_Period)로 설정되어 있습니다. = 65535 ), 이는 카운터의 최대값이 65535임을 나타냅니다. 이는 인코더가 일반적으로 신호 위상차가 90도인 두 개의 구형파를 사용하여 계산하기 때문입니다. 각 구형파 사이클은 펄스를 생성하므로 카운터는 완전한 구형파 사이클을 수용할 수 있어야 합니다. 구형파의 수를 기록할 만큼 충분히 큰 수 범위를 가져야 합니다. 사용된 DC감속모터의 감속비는 21.3이고, 물리적인 펄스는 11이므로 1회전에 발생하는 펄스는 234.3이다. 그러니까 7200이면 앞으로 30~40바퀴 정도만 차가 넘치겠죠. 장기간 보관이 가능한 65535와 달리 차량에서 위치 PID를 사용하면 조작할 수 있는 공간이 더 많아집니다.

1) " PWM 입력 주파수의 최소값이 PWM 출력 값보다 작을 수 없는 현상을 발견했습니다 ", 이는 확실합니다! PWM 출력으로 사용할 경우 카운터는 0부터 설정한 temp_fre까지 누적되므로, PWM 입력으로 사용할 경우 temp_fre 이하의 값만 캡쳐할 수 있습니다.

2) " 오실로스코프를 사용하여 관찰하면 TIM2의 PWM 출력 주파수는 50HZ보다 작을 수 없습니다. 프로그램에 설정된 30HZ는 이론적 계산에 따라 주파수를 72로 나눈 후 1000000/65536 < 20HZ입니다. 그러나 방법이 없습니다. 50HZ보다 낮은 PWM을 출력하려면." 이해가 안 돼요 . 무엇이 보이나요?

3) " STM32F103C8T6을 사용하여 주파수와 펄스 폭을 조정할 수 있는 4채널의 PWM을 출력하고, 또한 PWM의 채널을 입력하고 싶습니다. " 1)을 따르고 최소 주파수를 PWM 입력으로 사용하는 타이머를 찾으십시오. 입력이 주파수가 상대적으로 낮기 때문에 PWM 출력 중에 오버플로 인터럽트를 사용하고, 인터럽트 처리에서 카운트한 다음, 입력에서 캡처한 값으로 보상할 수 있습니다.

  • 문제: 자동차를 지면에 놓았을 때 틸트 각도가 작을 때 갑자기 왼쪽으로 기울었다가 다시 오른쪽으로 쏠리게 되는데, 기울일수록 각도가 커지며 기본 유지가 불가능합니다
    . 균형. 그런 다음 차를 들어 올리고 차를 약간 기울였는데, 차가 작은 각도에 있을 때 바퀴가 차의 균형을 유지하기 위해 해당 기울기 각도로 굴러가지 않는다는 것을 발견했습니다. 이때 KP 조정이 너무 작을 수도 있다고 생각하지만 조정이 너무 크더라도 계속 롤오버됩니다. 이때 차의 각도가 바뀌는 것을 깨달았습니다. 휠이 즉시 응답하지 않았으며 인터럽트 서비스 기능이 너무 느리게 실행되었을 수 있습니다. 다른 타이머를 사용하여 피치 각도를 화면에 표시하는 코드를 표시한 후 문제가 해결되었음을 확인했습니다.

  • 자동차 밸런스의 타이머 인터럽트 내에서 화면에 무언가를 표시하는 등 너무 많은 프로그램을 실행하지 마십시오. 이는 CPU 시간을 많이 차지합니다. 다른 타이머를 시작할 수 있습니다

인터럽트는 현재 우선순위보다 낮은 우선순위를 가진 인터럽트에 의해 선점될 수 없으며 스스로 선점될 수도 없습니다. -------CM3 최종 가이드

자동차 밸런스의 타이머 인터럽트 내에서 너무 많은 프로그램을 실행하면 CPU 시간을 차지하게 되어 타이머 인터럽트에 대한 응답이 지연되어 자동차의 밸런스 제어 성능에 영향을 미칠 수 있습니다. 이를 방지하려면 다른 작업을 처리하기 위해 추가 타이머를 시작하는 것이 좋습니다. 추가 타이머를 시작하면 일부 오래 지속되는 작업이나 주기적인 작업을 기본 타이머 인터럽트에서 분리하여 시스템의 응답 속도와 안정성을 향상시킬 수 있습니다.

        문제: 모바일 앱을 사용하여 esp8266을 통해 직렬 포트 2로 데이터를 보낼 때 응답이 민감하지 않습니다.

지식 포인트: 직렬 포트 및 타이머 우선순위 구성에 주의하세요. 우선순위가 높은 인터럽트는 우선순위가 낮은 인터럽트를 인터럽트합니다.

제가 사용하던 esp8266은 시리얼 포트 2의 PA2, PA3 핀을 통해 연결되었습니다. 구성된 인터럽트 우선순위가 자동차 밸런스의 time7 우선순위보다 낮게 설정된 경우 시리얼 포트 2의 인터럽트 서비스 기능이 실행되면 1ms 타이머가 작동합니다. 인터럽트로 인해 제어 명령이 완전히 수신되지 않거나 완전히 수신되었으나 처리할 시간이 없게 됩니다. 해결 방법은 직렬 포트 2의 우선 순위를 가장 높은 수준으로 높이는 것이지만, 이는 빈번한 전송, 즉 지속적인 명령 전송 시 밸런싱 작업을 차단합니다. 그래서 RTOS를 활용하자는 아이디어가 나왔습니다.

6. 실제 사진, PCB 사진, 회로도

    

7. 코드 링크: https://pan.baidu.com/s/1ezhjzNXToPOS33J8Adapwg?pwd=rtos 
추출 코드: rtos

추천

출처blog.csdn.net/qq_51519091/article/details/132413993