이 프로젝트 정보
PID 컨트롤러 및 PWM 출력에 의한 DC 모터의 속도 및 방향 제어
소개
거의 모든 사용 가능한 프로젝트에서 작성자는 모터 속도와 방향을 함께 제어하기를 원하지만 모터 제어 회로를 통해서라도 주로 PWM을 DC 모터로 직접 보내는 것을 선호합니다. 그러나 이러한 방법은 "마찰"과 "관성"이라는 쌍둥이 형제 때문에 원하는 대로 정확히 속도를 맞춰야 하는 경우 항상 실패합니다.
(Twins를 비난하지 마십시오. 무엇이든, 어떤 것이 있든 없든, 언제든지 Twins가 와서 모든 것을 통제할 수 있도록 돕기 위해 즉시 조치를 취합니다. Inertia는 행동을 취하기 전에 상황을 "생각"하도록 허용하지만, 마찰은 가속도와 속도를 제한합니다. 그리고 "힘"은 완전히 제어되지 않으면 "아무것도"가 아닙니다.)
따라서 입력을 PWM 신호로 출력으로 보내 모터의 속도를 직접 제어하려고 하면 실제 속도가 설정값과 절대 일치하지 않으며 바로 위의 이미지에서 볼 수 있듯이 상당한 차이(오차)가 발생합니다. 여기에서 다른 방법이 필요하며 "PID 제어"라고 합니다.
PID 컨트롤러
PID 제어란? 자동차를 운전하는 방법을 상상해 보십시오. 정지 상태에서 출발하려면 일반 크루즈보다 가속 페달을 더 밟아야 합니다. (거의) 일정한 속도로 이동하는 동안 가속 페달을 너무 많이 밟을 필요는 없지만 필요할 때 속도 손실을 복구하면 됩니다. 게다가 가속도가 필요보다 높으면 약간 해제합니다. 이것이 "효율적인 운전"의 방법이기도 합니다.
따라서 PID 컨트롤러는 정확히 동일한 작업을 수행합니다. 컨트롤러는 설정값과 실제 출력 간의 차이 "오류 신호(e)"를 읽습니다. "비례", "적분" 및 "파생"이라는 3가지 구성 요소가 있습니다. 그래서 컨트롤러의 이름은 각각의 첫 글자 뒤에 나옵니다. 비례 성분은 단순히 실제 오류 신호에 대한 컨트롤러 출력의 기울기(가속도)를 정의합니다. 적분 부분은 최종 오류를 최소화하기 위해 시간에 따라 오류 신호를 합산합니다. 그리고 Derivative 컴포넌트는 에러 신호의 가속도를 감시하고 "조정"을 합니다. 더 이상 자세한 내용은 여기에서 제공하지 않겠습니다. 관심이 있는 경우 인터넷에서 추가로 검색하십시오.
내 Arduino 프로그램에서 PID 컨트롤러는 아래와 같은 함수로 작성됩니다.
float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd){ float P, I, D; /* 기본 공식:U =_Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */ P =_Kp * _E; /* 비례 성분 */ I =_Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; /* 적분 성분 */ D =_Kp * _Kd * (_E-_Eprev) / _dT; /* 파생 컴포넌트 */ return (P+I+D);}
그러면 현재 출력값과 PID 제어기의 출력값을 더하면 최종 출력값이 됩니다. 오류 신호 및 PID 제어기 출력의 계산과 함께 메인 프로그램의 다음 섹션입니다.
/* 오류 신호, PID 컨트롤러 출력 및 모터에 대한 최종 출력(PWM) */E =RPMset - RPM;float cPID =controllerPID(E, Eprev, dT, Kp, Ki, Kd);if ( RPMset ==0 ) OutputRPM =0, 그렇지 않으면 OutputRPM =OutputRPM + cPID; if ( 출력RPM <_minRPM ) 출력RPM =_minRPM; 코드>
DC 모터 공급 회로
물론 Arduino 또는 유사한 제어 보드의 출력에서 직접 DC 모터를 구동하는 것은 절대 권장하지 않습니다. DC 모터는 컨트롤러 카드의 출력으로 공급할 수 없는 것과 비교하여 상당한 양의 전류가 필요합니다. 따라서 릴레이 코일을 구동해야 합니다. 그러나 여기서 또 다른 문제가 발생합니다. 릴레이에는 기계 부품이 있으며 중장기적으로 고장날 수 있습니다. 여기에 또 다른 구성 요소인 트랜지스터가 필요합니다.
실제로 DC 모터는 전압이 아닌 전류로 구동됩니다. 그래서 이 원리를 이용해서 트랜지스터를 사용하기로 했습니다. 그러나 모터 전류를 견딜 수 있는 올바른 트랜지스터를 선택해야 합니다. 처음에는 전원에 직접 연결하여 모터를 구동하고 최대 동작 조건에서 전류를 측정하거나 제조사 사양을 참조하십시오.
이 작업을 수행한 후 "브리지"에 4개의 BC307A PNP 바이폴라 접합 트랜지스터를 사용하여 모터 코일을 통과하는 전류 방향을 결정하기로 결정했습니다(실제로 NPN BC337 세트는 훨씬 더 높은 콜렉터 전류를 견딜 수 있기 때문에 더 잘 작동하지만, t 그 시간에).
모터 전류는 트랜지스터의 Emitter-Collector 경로를 통과해야 하므로 DC Current Gain(hfe) 계수가 거의 동일한 트랜지스터를 사용해야 합니다. 그것을 확인하려면 다음 회로를 사용하고 전류계에서 거의 동일한 전류 판독 값을 제공하는 트랜지스터를 수집할 수 있습니다. 이러한 예비 회로를 설계하기 위해 다음을 고려해야 합니다.
<울> “Base-Emitter On-Voltage 찾기 ” (VBEon ) 트랜지스터. 트랜지스터를 켜기 위해 베이스에 인가하는 최소 전압입니다.
<울> 일반적인 "DC 전류 이득 찾기 "(hfe ) 모터 전류에 가까운 컬렉터 전류 부근에서 트랜지스터의. 일반적으로 수집기 전류 간의 비율입니다. (IC ) 및 기본 전류 (IB ), hfe =IC / IB .
<울> “최대 연속 수집기 전류 찾기 ”의 트랜지스터(ICmax ). 모터의 DC 전류는 절대 값으로 이 값을 초과해서는 안됩니다. 내가 사용하는 모터는 70mA가 필요하고 트랜지스터는 ICmax(abs) =100mA가 필요하므로 BC307을 사용할 수 있습니다.
이제 Base에 연결할 저항 값을 결정할 수 있습니다. 먼저 컨트롤러 카드의 출력 제한을 고려하고 Base 전류를 가능한 한 최소로 유지해야 합니다(따라서 트랜지스터의 DC Current Gain을 최대로 선택하는 것이 좋습니다. 컨트롤러 보드의 출력 전압 정격을 "트리거 전압으로 취하십시오. ” (VT ), 필요한 기본 전류(IBreq ) 모터 전류(IM )에서 DC 전류 이득(hfe)으로 ) 트랜지스터:IBreq =IM / hfe .
그런 다음 저항(VR ), Base-Emitter On-Voltage 빼기 (VBon ) 트리거 전압 :VR =VT - VBEon .
마지막으로 전압을 저항 에 걸쳐 떨어뜨립니다. (VR )에서 필수 기본 전류로 (IBreq ) 저항 값 찾기 (알 ):R =VR / IBreq .
[ 조합 공식:R =( VT - VBEon ) * hfe / IM ]
제 경우:
<울> 모터 전류:IM =70mA
<울> BC307A 매개변수:ICmax =100mA, hfe =140(대략 측정), VBEon =0.62V
<울> 트리거 전압:VT =3.3V(Arduino Due의 PWM 출력)
<울> R =5360옴(그래서 2K2 및 2K7에서 만든 4900옴을 사용하기로 결정했습니다. 전체 RPM 범위를 보장하고 회로는 PWM 출력에서 ~0.6mA를 빨아들입니다. 적절한 디자인입니다.)
되돌리기 방향 및 중요 참고사항
DC 모터의 방향을 되돌리려면 전류의 흐름을 되돌리면 됩니다. 그렇게 하려면 4개의 트랜지스터 세트로 브리지 회로를 간단히 만들 수 있습니다. 회로도에서; PWM Output#2는 T1A 및 T1B를 활성화하고 PWM Output#3은 T2A 및 T2B를 활성화하므로 모터에 흐르는 전류가 변경됩니다.
그러나 여기서 또 다른 문제를 고려해야 합니다. 전기 모터는 방금 시작했을 때 정상/연속 작동 중에 읽는 공칭 전류보다 훨씬 더 높은 과도 시동 전류를 빨아들였습니다(제조업체는 공칭 전류만 제공함). 기동 전류는 소형 전력 모터의 경우 공칭 전류의 약 130%일 수 있으며 모터 전력에 따라 증가합니다. 따라서 모터를 전압원에서 직접 공급하고 작동 중에 즉시 극성을 바꾸면 모터가 완전히 정지되지 않았기 때문에 극도의 전류를 빨아들입니다. 마지막으로 전원이 끊어지거나 모터 코일이 타버릴 수 있습니다. 이것은 그다지 중요하지 않고 매우 작은 파워 모터에 대해 느껴질 수 있지만 작업 중인 파워 레벨이 증가하면 중요해집니다. 그러나 트랜지스터 또는 트랜지스터 세트(Darlington Couple과 같은)를 통해 모터에 전원을 공급하는 경우 트랜지스터가 이미 전류를 제한하기 때문에 그러한 문제는 없습니다.
어쨌든, 나는 프로그램에 대한 작은 루틴을 고려했습니다. 실행 중에 방향 선택이 변경되면 프로그램은 처음에 두 명령 출력을 모두 0으로 구동하고 모터가 완전히 멈출 때까지 기다립니다. 그런 다음 임무를 완료하고 모든 제어 권한을 기본 루틴으로 되돌립니다.
if ( Direction !=prevDirection ) { /* 모터에 대한 두 PWM 출력 종료 */ analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); /* 모터 속도가 감소할 때까지 대기 */ do { RPM =60*(float)readFrequency(_chSpeedRead,4)/_DiscSlots; } 동안 ( RPM> _minRPM ); } 코드>
속독
내 응용 프로그램에서 저렴한 HC-020K 속도 센서를 사용했습니다. 공급 전압 수준에서 펄스를 보내고 데이터 시트에는 공급 전압이 5V라고 나와 있습니다. 그러나 내 보드는 Arduino Due이며 받아 들일 수 없습니다. 그래서 저는 Due의 3.3V 출력에서 직접 전원을 공급받았고 예, 작동했습니다. 그리고 주파수와 HC-020K 출력을 읽기 위해 다음과 같은 함수를 작성합니다.
int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed) { pinMode(_DI_FrequencyCounter_Pin,INPUT); 바이트 _DigitalRead, _DigitalRead_Previous =0; 부호 없는 긴 _Time =0, _Time_Init; float _Frequency =0; if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1); else { _Time_Init =마이크로(); do { _DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if ( (_DigitalRead_Previous==1) &&(_DigitalRead==0) ) _Frequency++; _DigitalRead_Previous =_DigitalRead; _시간 =마이크로(); } 동안 ( _Time <(_Time_Init + (1000000/_ReadingSpeed)) ); } 반환(_ReadingSpeed * _Frequency); } 코드>
HC-020K의 휠에는 20개의 슬롯이 있습니다. 읽기 주파수는 초당 회전수를 주파수로 얻기 위해 20으로 나누어야 합니다. 그런 다음 결과에 60을 곱하여 RPM을 구해야 합니다.
RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots;
그래픽 수정
입력과 결과를 표시하기 위해 Makeblock Me TFT LCD를 사용하고 CommandToTFT() 함수를 작성하여 명령을 보냈습니다. 실제로 이 기능을 사용하는 이유는 필요할 때 프로그램의 한 행에서만 직렬 연결 지점을 변경하기 위함입니다.
Cartesian_Setup(), Cartesian_ClearPlotAreas() 및 Cartesian_Line() 함수는 각각 그래픽 플롯 영역을 준비하고 수평 축(여기서는 "시간") 끝에 도달하면 플롯 영역을 지우고 그래픽을 플롯하도록 작성되었습니다. 여기서 그래픽 기능에 관심이 있는 경우 자세한 내용은 Makeblock Me TFT LCD 설명서를 참조하십시오. 실제로는 이 블로그의 범위를 벗어나므로 여기에서 설명하지 않겠습니다.
종료
여기에서는 그래픽 기능이 있거나 없는 프로그램을 별도로 복사하여 속도 및 방향 제어 구현을 단독으로 또는 그래픽과 함께 검토할 수 있습니다. 또한 코드에서 프로그래밍된 기능에 대한 추가 설명을 찾을 수 있습니다.
마지막으로, 일반적으로 DC 모터의 속도는 부하가 없더라도 정격 속도의 10-20% 미만으로 감소할 수 없습니다. 그러나 무부하 DC 모터가 시동되면 PID 제어를 사용하여 거의 5%까지 감소시킬 수 있습니다.
수정(2018년 2월 25일): PNP 대신 NPN 트랜지스터를 사용하려면 두 유형 모두에서 역방향 전류 흐름도 고려해야 합니다. 트리거(스위치 켜짐)되면 전류가 PNP 트랜지스터의 이미터에서 컬렉터로 흐르지만 NPN 유형(컬렉터에서 에미터로)의 경우 반대입니다. 따라서 PNP 트랜지스터는 E(+) C(-)로 분극되고 NPN의 경우 C(+) E(-)여야 합니다.
섹션> <섹션 클래스="섹션 컨테이너 섹션 축소 가능" id="코드"> 코드
<울> 그래픽 수정이 포함된 코드
그래픽 수정 없는 코드
그래픽 수정을 사용한 코드Arduino
<사전>/* ############################################## ## Makeblock Me TFT LCD용 색상 상수####################################### ###### */#define _BLACK 0#define _RED 1#define _GREEN 2#define _BLUE 3#define _YELLOW 4#define _CYAN 5#define _PINK 6#define _WHITE 7/* ######## ######################################## I/O 할당####### ######################################### */int _chSpeedSet =A0, // 속도 setpoint _chKp =A1, // PID 제어기에 대한 비례 계수 판독 _chKi =A2, // PID 제어기에 대한 적분 계수 판독 _chKd =A3, // PID 제어기에 대한 미분 계수 판독 _chMotorCmdCCW =3, // 카운터-용 모터에 대한 PWM 출력 시계 방향 회전 _chMotorCmdCW =2, // 시계 방향 회전에 대한 PWM 출력 모터 _chSpeedRead =24, // 속도 판독 _chDirection =25; // 방향 선택기 읽기/* ########################################### #### 기타 상수 ############################################ ### */#define _minRPM 0 // 방향 변경을 시작하기 위한 최소 RPM#define _maxRPM 6000 // 최대 RPM 제한#define _Tmax 90 // 그래프를 위한 최대 시간 제한#define _DiscSlots 20 // 인덱스 디스크의 슬롯 수/ * ################################################### 전역 변수 ############################################### */끈 Cartesian_SetupDetails;boolean Direction, prevDirection;// 알람 설정float RALL=500.0, RAL=1000.0, RAH=4000.0, RAHH=4500.0;float Seconds=0.0, prevSeconds=0.0, prevRPM=0.0, prevRPM, RPMset=0.prevRPM 0.0, OutputRPM=0.0, Kp=0.0, Ki=0.0, Kd=0.0, Kpmax=2.0, Kimax=1.0, Kdmax=1.0, E=0.0, Eprev=0.0, dT=1.0;/* ###### ######################################### CommandToTFT(TFTCmd) Makeblock Me 명령 기능 TFT LCD 입력 매개변수:(문자열) TFTCmd :명령 문자열###################################### ########## */void CommandToTFT(String TFTCmd){ /* 디스플레이에 사용되는 직렬 연결 */ Serial1.println(TFTCmd); delay(5);}/* ########### CommandToTFT()의 끝 ########### *//* ########### #################################### *//* ############ #################################### Cartesian_Setup(Xmin, Xmax, Ymin, Ymax, Window_X1, Window_Y1, Window_X2 , Window_Y2, MinDashQty, ColorF, ColorX, ColorY) Makeblock Me TFT LCD에 대한 직교 XY 축 그리기 기능 입력 매개변수:(float) Xmin, Xmax, Ymin, Ymax :축 범위 값(int) Window_X1, Window_Y1___:왼쪽 상단 그래프 창(int) Window_X2, Window_Y2___:그래프 창의 오른쪽 하단 모서리(int) MinDashQty_____________:최단축의 대시 수(int) ColorB, ColorX, ColorY :Frame, X축 및 Y축의 그리기 색상 용도 외부 함수 CommandToTFT().############################################# ### */String Cartesian_Setup( float Xmin, float Xmax, float Ymin, float Ymax, int Window_X1, int Window_Y1, int Window_X2, int Window_Y2, int MinDashQty, int ColorF, int ColorX, int ColorY ){ /* 화면 제한 * / const int 디스플레이 해상도X =319, 디스플레이 해상도Y =239; /* 제목 문자열 제한 */ String XminTxt; if (abs(Xmin)>=1000000000) XminTxt ="X=" + 문자열(Xmin/1000000000) + "G"; else if (abs(Xmin)>=1000000) XminTxt ="X=" + 문자열(Xmin/1000000) + "M"; else if (abs(Xmin)>=1000) XminTxt ="X=" + 문자열(Xmin/1000) + "K"; else XminTxt ="X=" + 문자열(Xmin); 문자열 XmaxTxt; if (abs(Xmax)>=1000000000) XmaxTxt ="X=" + 문자열(Xmax/1000000000) + "G"; else if (abs(Xmax)>=1000000) XmaxTxt ="X=" + 문자열(Xmax/1000000) + "M"; else if (abs(Xmax)>=1000) XmaxTxt ="X=" + 문자열(Xmax/1000) + "K"; else XmaxTxt ="X=" + 문자열(Xmax); 문자열 YminTxt; if (abs(Ymin)>=1000000000) YminTxt ="Y=" + 문자열(Ymin/1000000000) + "G"; else if (abs(Ymin)>=1000000) YminTxt ="Y=" + 문자열(Ymin/1000000) + "M"; else if (abs(Ymin)>=1000) YminTxt ="Y=" + 문자열(Ymin/1000) + "K"; else YminTxt ="Y=" + 문자열(Ymin); 문자열 YmaxTxt; if (abs(Ymax)>=1000000000) YmaxTxt ="Y=" + 문자열(Ymax/1000000000) + "G"; else if (abs(Ymax)>=1000000) YmaxTxt ="Y=" + 문자열(Ymax/1000000) + "M"; else if (abs(Ymax)>=1000) YmaxTxt ="Y=" + 문자열(Ymax/1000) + "K"; else YmaxTxt ="Y=" + 문자열(Ymax); /* 제한 */ int XminPx =Window_X1+1; int XmaxPx =Window_X2-1; 정수 YmaxPx =Window_Y1+1; int YminPx =Window_Y2-1; /* 원점 */ int OriginX =XminPx + (int)( (XmaxPx - XminPx) * abs(Xmin) / (abs(Xmax)+abs(Xmin)) ); int OriginY =YmaxPx + (int)( (YminPx - YmaxPx) * abs(Ymax) / (abs(Ymax)+abs(Ymin)) ); /* 프레임 */ CommandToTFT ( "BOX(" + String(Window_X1) + "," + String(Window_Y1)+ "," + String(Window_X2) + "," + String(Window_Y2)+ "," + String( 색상F) + ");" ); /* X 축 */ CommandToTFT ( "PL(" + String(Window_X1+1) + "," + String(OriginY) + "," + String(Window_X2-1) + "," + String(OriginY) + " ," + 문자열(ColorX) + ");" ); /* Y 축 */ CommandToTFT ( "PL(" + String(OriginX) + "," + String(Window_Y1+1) + "," + String(OriginX) + "," + String(Window_Y2-1) + " ," + 문자열(색상Y) + ");" ); /* 대시:대시의 최소 양은 "MinDashQty"로 지정되며 원점을 기준으로 가장 짧은 축 쪽에 대시로 표시됩니다. 그 외의 구간에서 표시할 대시는 최단축 측면에 대한 비율을 고려하여 결정한다. */ /* 대시 */ int XlengthLeft =abs(XminPx-OriginX); int XlengthRight =abs(XmaxPx-OriginX); int YlengthLower =abs(YminPx-OriginY); int YlengthUpper =abs(YmaxPx-OriginY); int XlengthLeft_Mod, XlengthRight_Mod, YlengthLower_Mod, YlengthUpper_Mod; if (XlengthLeft<=1) XlengthLeft_Mod=32767; 그렇지 않으면 XlengthLeft_Mod=XlengthLeft; if (XlengthRight<=1) XlengthRight_Mod=32767; 그렇지 않으면 XlengthRight_Mod=XlengthRight; if (YlengthLower<=1) YlengthLower_Mod=32767; 그렇지 않으면 YlengthLower_Mod=YlengthLower; if (YlengthUpper<=1) YlengthUpper_Mod=32767; 그렇지 않으면 YlengthUpper_Mod=YlengthUpper; int MinAxisLength =min(최소(XlengthLeft_Mod,XlengthRight_Mod), min(YlengthLower_Mod,YlengthUpper_Mod)); int XdashesLeft =MinDashQty * XlengthLeft / MinAxisLength; int XdashesRight =MinDashQty * XlengthRight / MinAxisLength; int YdashesLower =MinDashQty * YlengthLower / MinAxisLength; int YdashesUpper =MinDashQty * YlengthUpper / MinAxisLength; int DashingInterval=2; // Min.interval btw.dashes /* X-Dash L */ DashingInterval =(int) (XlengthLeft / XdashesLeft); if (!(DashingInterval<2)) for (int i=OriginX; i>=XminPx; i-=DashingInterval) CommandToTFT( "PL(" + String(i) + "," + String(OriginY-2) + " ," + 문자열(i) + "," + 문자열(OriginY+2) + "," + 문자열(ColorX) + ");" ); /* X-Dash R */ DashingInterval =(int) (XlengthRight / XdashesRight); if (!(DashingInterval<2)) for (int i=OriginX; i<=XmaxPx; i+=DashingInterval) CommandToTFT( "PL(" + String(i) + "," + String(OriginY-2) + ", " + String(i) + "," + String(OriginY+2) + "," + String(ColorX) + ");" ); /* Y-대시-L */ DashingInterval =(int) (YlengthLower / YdashesLower); if (!(DashingInterval<2)) for (int i=OriginY; i<=YminPx; i+=DashingInterval) CommandToTFT( "PL(" + String(OriginX-2) + "," + String(i) + ", " + String(OriginX+2) + "," + String(i) + "," + String(ColorY) + ");" ); /* Y-대시-U */ DashingInterval =(int) (YlengthUpper / YdashesUpper); if (!(DashingInterval<2)) for (int i=OriginY; i>=YmaxPx; i-=DashingInterval) CommandToTFT( "PL(" + String(OriginX-2) + "," + String(i) + " ," + String(OriginX+2) + "," + String(i) + "," + String(ColorY) + ");" ); /* 축 끝점 값을 표시하기 위한 좌표 계산 */ int XminTxtX =Window_X1 - (int)(XminTxt.length()*6) - 1, XminTxtY =OriginY, XmaxTxtX =Window_X2 + 1, XmaxTxtY =OriginY, YminTxtX =OriginX, YminTxt =Window_Y2 + 1, YmaxTxtX =OriginX, YmaxTxtY =Window_Y1 - 12 - 1; /* 제어:좌표가 -1이면 표시 한계를 벗어나 해당 값이 표시되지 않습니다. */ if (XminTxtX<0) XminTxtX =-1; if ((XminTxtY-12) <0 ) XminTxtY =-1; if ( (XmaxTxtX+6*XmaxTxt.length())> DisplayResolutionX ) XmaxTxtX =-1; if ( (XmaxTxtY+12)> DisplayResolutionY ) XmaxTxtY =-1; if ( ((YminTxtX+6*YminTxt.length())> DisplayResolutionX ) YminTxtX =-1; if ( (YminTxtY+12)> DisplayResolutionY ) YminTxtY =-1; if ( ((YmaxTxtX+6*YmaxTxt.length())> DisplayResolutionX ) YmaxTxtX =-1; if (YmaxTxtY<0) YmaxTxtY =-1; /* 범위 제한 제목 */ if ( ( XminTxtX !=-1 ) &&( XminTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(XminTxtX) + "," + String(XminTxtY) + ",'" + 문자열(XminTxt) + "'," + 문자열(ColorX) + ");" ); if ( ( XmaxTxtX !=-1 ) &&( XmaxTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(XmaxTxtX) + "," + String(XmaxTxtY) + ",'" + String(XmaxTxt) + " '," + 문자열(ColorX) + ");" ); if ( ( YminTxtX !=-1 ) &&( YminTxtY !=-1 ) ) CommandToTFT( "DS12(" + 문자열(YminTxtX) + "," + 문자열(YminTxtY) + ",'" + 문자열(YminTxt) + " '," + 문자열(색상Y) + ");" ); if ( ( YmaxTxtX !=-1 ) &&( YmaxTxtY !=-1 ) ) CommandToTFT( "DS12(" + String(YmaxTxtX) + "," + String(YmaxTxtY) + ",'" + String(YmaxTxt) + " '," + 문자열(색상Y) + ");" ); /* 반환 값 문자열 Cartesian_Setup()은 다음 형식으로 그래픽 구성을 패킹하는 문자열을 반환합니다. "" 문자열은 '<'로 시작하고 '>'로 끝납니다. . 각 값은 ','로 구분됩니다. */ /* Initialize */ String Cartesian_SetupDetails ="<"; Cartesian_SetupDetails +=( 문자열(Xmin) + "," ); Cartesian_SetupDetails +=( 문자열(Xmax) + "," ); Cartesian_SetupDetails +=( 문자열(Ymin) + "," ); Cartesian_SetupDetails +=( 문자열(Ymax) + "," ); Cartesian_SetupDetails +=( String(Window_X1) + "," ); Cartesian_SetupDetails +=( String(Window_Y1) + "," ); Cartesian_SetupDetails +=( String(Window_X2) + "," ); Cartesian_SetupDetails +=( String(Window_Y2) + "," ); /* 종료 */ Cartesian_SetupDetails +=">";return Cartesian_SetupDetails;}/* ########### Cartesian_Setup()의 끝 ########### *// * ################################################## * //* ################################################## Cartesian_ClearPlotAreas(Descriptor, Color) Makeblock Me TFT LCD 입력 매개변수를 위한 플롯 영역 재설정/지우기 기능 ().################################################ */void Cartesian_ClearPlotAreas(문자열 설명자, int 색상){ int X1,Y1,X2,Y2; /* 플롯 영역의 경계 좌표 */ /* 설명자에서 값 추출 */ /* L[0] L[1] L[2] L[3] W[0] W[1] W[2] W[3 ] */ /* Xmin Xmax Ymin Ymax Window_X1 Window_Y1 Window_X2 Window_Y2 */ float L[4]; 정수 W[4]; /* 디스크립터에 저장된 값 */ int j=0; /* 카운터 */ 문자열 D_Str =""; for (int i=1; i<=(Descriptor.length()-1); i++) if ( Descriptor[i] ==',' ) { if (j<4) L[j]=D_Str.toFloat( ); 그렇지 않으면 W[j-4]=D_Str.toInt(); D_Str=""; j++; } else D_Str +=설명자[i]; /* 원점 */ int OriginX =(W[0]+1) + (int)( ( (W[2]-1) - (W[0]+1) ) * abs(L[0]) / ( abs(L[1])+abs(L[0])) ); int OriginY =(W[1]+1) + (int)( ( (W[3]-1) - (W[1]+1) ) * abs(L[3]) / (abs(L[3] ])+abs(L[2])) ); /* 플롯 영역 지우기 */ //Area.1 :X+ Y+ X1 =OriginX + 2; Y1 =W[1] + 1; X2 =W[2] - 1; Y2 =원점Y - 2; CommandToTFT( "BOXF(" + 문자열(X1) + "," + 문자열(Y1) + "," + 문자열(X2) + "," + 문자열(Y2) + "," + 문자열(색상) + ");" ); //영역2 :X- Y+ X1 =W[0] + 1; Y1 =W[1] + 1; X2 =원점X - 2; Y2 =원점Y - 2; CommandToTFT( "BOXF(" + 문자열(X1) + "," + 문자열(Y1) + "," + 문자열(X2) + "," + 문자열(Y2) + "," + 문자열(색상) + ");" ); //영역3 :X- Y- X1 =W[0] + 1; Y1 =원점Y + 2; X2 =원점X - 2; Y2 =W[3] - 1; CommandToTFT( "BOXF(" + 문자열(X1) + "," + 문자열(Y1) + "," + 문자열(X2) + "," + 문자열(Y2) + "," + 문자열(색상) + ");" ); //Area.4 :X+ Y- X1 =OriginX + 2; Y1 =원점Y + 2; X2 =W[2] - 1; Y2 =W[3] - 1; CommandToTFT( "BOXF(" + 문자열(X1) + "," + 문자열(Y1) + "," + 문자열(X2) + "," + 문자열(Y2) + "," + 문자열(색상) + ");" );} /* ########### Cartesian_ClearPlotAreas() 끝 ########### *//* ############# ################################################ *//* # ################################################ Cartesian_Line(Xp, Yp, X, Y, 설명자, 색상) Makeblock Me TFT LCD의 데카르트 선 함수 입력 매개변수:(int) Xp, Yp_____:이전 플롯 좌표 - y 값 대 x (int) X, Y_______:현재 플롯 좌표 - y 값 대 x (String) Descriptor :Setup Descriptor - Cartesian_Setup()에 의해 반환됨 (int) Color______:(x,y)에 사용할 마킹 색상 CommandToTFT() 외부 함수를 사용합니다.############# ################################### */void Cartesian_Line(float Xp, float Yp, float X, float Y , String Descriptor, int Color){ /* Descriptor에서 값 추출 */ /* L[0] L[1] L[2] L[3] W[0] W[1] W[2] W[3] */ /* Xmin Xmax Ymin Ymax Window_X1 Window_Y1 Window_X2 Window_Y2 */ float L[4 ]; 정수 W[4]; /* 디스크립터에 저장된 값 */ int j=0; /* 카운터 */ 문자열 D_Str =""; for (int i=1; i<=(Descriptor.length()-1); i++) if ( Descriptor[i] ==',' ) { if (j<4) L[j]=D_Str.toFloat( ); 그렇지 않으면 W[j-4]=D_Str.toInt(); D_Str=""; j++; } else D_Str +=설명자[i]; /* 원점 */ int OriginX =(W[0]+1) + (int)( ( (W[2]-1) - (W[0]+1) ) * abs(L[0]) / ( abs(L[1])+abs(L[0])) ); int OriginY =(W[1]+1) + (int)( ( (W[3]-1) - (W[1]+1) ) * abs(L[3]) / (abs(L[3] ])+abs(L[2])) ); 정수 XminPx =W[0] + 1; 정수 XmaxPx =W[2] - 1; 정수 YmaxPx =W[1] + 1; 정수 YminPx =W[3] - 1; (Y>L[3]) Y=L[3]인 경우; (Y
=(OriginX-2) ) &&( DispXp <=(OriginX+2) ) ) || ( ( DispYp>
=(OriginY-2) ) &&( DispYp <=(OriginY+2) ) ) || ( ( DispX>=(OriginX-2) ) &&( DispX <=(OriginX+2) ) ) || ( ( DispY>=(OriginY-2) ) &&( DispY <=(OriginY+2) ) ) )) CommandToTFT( "PL(" + 문자열(DispXp) + "," + 문자열(DispYp) + "," + 문자열(DispX) + "," + 문자열(DispY) + "," + 문자열(색상 ) + ");" );}/* ########### Cartesian_Line() 끝 ########### *//* ######## ######################################### *//* ####### ######################################## readFrequency(_DI_FrequencyCounter_Pin, _ReadingSpeed) 주파수 읽기 함수 입력 매개변수:(int) _DI_FrequencyCounter_Pin :읽을 디지털 핀(float) _ReadingSpeed____________:0...10 사이의 사용자 정의 읽기 속도(참고.1) 참고.1:_ReadingSpeed는 변경 사항을 계산할 시간을 지정하는 값입니다. 0(영), 음수 또는 10보다 큰 값일 수 없습니다. _ReadingSpeed가 변경되면 1초를 이 값으로 나누어 필요한 카운팅 시간을 계산합니다. 예를 들어; - _ReadingSpeed =0.1 -> 입력은 10초 동안 카운트(=1/0.1) - _ReadingSpeed =0.5 -> 입력은 2초 동안 카운트(=1/0.5) - _ReadingSpeed =2.0 -> 입력은 0.5 동안 카운트 초(=1/2) - _ReadingSpeed =4.0 -> 입력은 0.25초 동안 계산됩니다(=1/4) 중요하게, _ReadingSpeed의 증가는 계산 오류가 증가하기 때문에 특히 낮은 주파수(일반적으로 100Hz 미만)에서 단점이라는 점에 유의하십시오. 빈도를 줄이면 최대 20%~40%입니다.###################################### ######## */int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed){ pinMode(_DI_FrequencyCounter_Pin,INPUT); 바이트 _DigitalRead, _DigitalRead_Previous =0; 부호 없는 긴 _Time =0, _Time_Init; float _Frequency =0; if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1); else { _Time_Init =마이크로(); do { _DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if ( (_DigitalRead_Previous==1) &&(_DigitalRead==0) ) _Frequency++; _DigitalRead_Previous =_DigitalRead; _시간 =마이크로(); } 동안 ( _Time <(_Time_Init + (1000000/_ReadingSpeed)) ); } return (_ReadingSpeed * _Frequency);}/* ########### End of readFrequency() ########### *//* ############################################## *//* ############################################### controllerPID(RangeMin, RangeMax, _E, _Eprev, _dT, _Kp, _Ki, _Kd) PID Controller Function Input Parameters:(float) RangeMin:Minimum limit for output (float) RangeMax:Maximum limit for output (float) _E_____:Current error signal (float) _Eprev :Previous error signal (float) _dT____:Time difference as seconds (float) _Kp____:Proportional coefficient (float) _Ki____:Integral coefficient (float) _Kp____:Derivative coefficient Adjustment procedure:1. Set Kp=0, Ki=0, Kd=0. 2. Start to increase Kp until the system oscillates at fixed period (Pc) and note critical gain Kc =Kp. 3. Adjust final coefficients as follows. for P-control only :Kp =0.50*Kc for PI-control only :Kp =0.45*Kc, Ki =1.2/Pc for PID-control :Kp =0.60*Kc, Ki =2.0/Pc, Kd=Pc/8 4. Fine tuning could be done by slightly changing each coefficient.############################################### */ float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd){ float P, I, D; /* Base Formula:U =_Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */ P =_Kp * _E; /* Proportional Component */ I =_Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; /* Integral Component */ D =_Kp * _Kd * (_E-_Eprev) / _dT; /* Derivative Component */ return (P+I+D);}/* ########### End of controllerPID() ########### *//* ############################################## *//* ############################################### Setup############################################### */void setup(){ Serial1.begin(9600); Serial1.println("CLS(0);");delay(20); analogReadResolution(12); pinMode(_chDirection,INPUT); // Direction selector reading pinMode(_chMotorCmdCCW,OUTPUT); // PWM output to motor for counter-clockwise turn pinMode(_chMotorCmdCW,OUTPUT); // PWM output to motor for clockwise turn // Initial killing the PWM outputs to motor analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); // Initial reading for direction selection Direction=digitalRead(_chDirection); // HIGH=CCW, LOW=CW prevDirection=Direction; // The section below prepares TFT LCD // Cartesian_Setup(Xmin, Xmax, Ymin, Ymax, Window_X1, Window_Y1, Window_X2, Window_Y2, MinDashQty, ColorF, ColorX, ColorY) Cartesian_SetupDetails =Cartesian_Setup(0, _Tmax, _minRPM, _maxRPM, 20, 20, 220, 120, 10, 0, 7, 7); CommandToTFT("DS12(250,10,'Dir:CW '," + String(_WHITE) + ");"); CommandToTFT("DS12(250,25,'____ Set'," + String(_YELLOW) + ");"); CommandToTFT("DS12(250,40,'____ RPM'," + String(_GREEN) + ");"); /* Alarm Values */ CommandToTFT("DS12(250,55,'AHH:" + String(RAHH) + "'," + String(_WHITE) + ");"); CommandToTFT("DS12(250,70,'AH :" + String(RAH) + "'," + String(_WHITE) + ");"); CommandToTFT("DS12(250,85,'AL :" + String(RAL) + "'," + String(_WHITE) + ");"); CommandToTFT("DS12(250,100,'ALL:"+ String(RALL) + "'," + String(_WHITE) + ");"); /* Alarm Window */ CommandToTFT("BOX(240,55,319,115," + String(_WHITE) + ");"); /* Alarm Lamps */ CommandToTFT("BOX(240,55,248,70," + String(_WHITE) + ");"); CommandToTFT("BOX(240,70,248,85," + String(_WHITE) + ");"); CommandToTFT("BOX(240,85,248,100," + String(_WHITE) + ");"); CommandToTFT("BOX(240,100,248,115," + String(_WHITE) + ");");}/* ############################################### Loop############################################### */void loop(){ // Initialization Time:Necessary for PID controller. int InitTime =micros(); // X-Axis Auto-Reset for Graphing if ( Seconds> 90.0 ) { Seconds =0.0; Cartesian_ClearPlotAreas(Cartesian_SetupDetails,0); } // Reading Inputs /* Controller Coefficients */ Kp =Kpmax * (float)analogRead(_chKp) / 4095; Ki =Kimax * (float)analogRead(_chKi) / 4095; Kd =Kdmax * (float)analogRead(_chKd) / 4095; /* Direction Selector */ Direction =digitalRead(_chDirection); /* HIGH=CCW, LOW=CW */ /* Actual RPM and RPM Setpoint Note that maximum selectable RPM is 5000. */ RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; RPMset =5000 * (float)analogRead(_chSpeedSet) / 4095; // Calculations and Actions /* Error Signal, PID Controller Output and Final Output (PWM) to Motor */ E =RPMset - RPM; float cPID =controllerPID(E, Eprev, dT, Kp, Ki, Kd); if ( RPMset ==0 ) OutputRPM =0; else OutputRPM =OutputRPM + cPID; if ( OutputRPM <_minRPM ) OutputRPM =_minRPM; if ( OutputRPM> _maxRPM ) OutputRPM =_maxRPM; /* Changing Direction when inverted Note that no any graphical indication is performed on this function. */ if ( Direction !=prevDirection ) { /* Killing both of the PWM outputs to motor */ analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); /* Wait until motor speed decreases */ do { RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; } while ( RPM> _minRPM ); } // Writing Outputs if (Direction==HIGH) analogWrite(_chMotorCmdCCW,(int)(255*OutputRPM/_maxRPM)); else analogWrite(_chMotorCmdCW, (int)(255*OutputRPM/_maxRPM)); // Graphing /* Indicating Direction */ if (Direction==HIGH) CommandToTFT("DS12(280,10,'CCW '," + String(_WHITE) + ");"); else CommandToTFT("DS12(280,10,'CW '," + String(_WHITE) + ");"); /* Plotting Curve */ Cartesian_Line(prevSeconds, prevRPMset, Seconds, RPMset, Cartesian_SetupDetails, _YELLOW); Cartesian_Line(prevSeconds, prevRPM, Seconds, RPM, Cartesian_SetupDetails, _GREEN); /* Indicating values of RPM Setpoint, PID Controller Coefficients, Error Signal, PID Controller Output and Final RPM Output (PWM) */ CommandToTFT( "DS12(20,150,'Set:" + String(RPMset) + " rpm " + "RPM:" + String(RPM) + " rpm '," + String(_WHITE) + ");"); CommandToTFT( "DS12(20,170,'Kp=" + String(Kp) + " " + "Ki=" + String(Ki) + " " + "Kd=" + String(Kd) + " " + "dT=" + String(dT*1000) + " ms '," + String(_WHITE) + ");"); CommandToTFT( "DS12(20,190,'e=" + String(E) + " " + "cPID=" + String(cPID) + " " + "RPMout=" + String(OutputRPM) + " '," + String(_WHITE) + ");"); /* Resetting Alarm Lamps */ CommandToTFT("BOXF(241,56,247,69," + String(_BLACK) + ");"); CommandToTFT("BOXF(241,71,247,84," + String(_BLACK) + ");"); CommandToTFT("BOXF(241,86,247,99," + String(_BLACK) + ");"); CommandToTFT("BOXF(241,101,247,114," + String(_BLACK) + ");"); /* Activating Necessary Alarm Lamps */ if (RPM>=RAHH) CommandToTFT("BOXF(241,56,247,69," + String(_RED) + ");"); if ((RPM>=RAH)&&(RPMRALL)&&(RPM<=RAL)) CommandToTFT("BOXF(241,86,247,99," + String(_RED) + ");"); if (RPM<=RALL) CommandToTFT("BOXF(241,101,247,114," + String(_RED) + ");"); // Storing Values generated on previous cycle Eprev =E; prevRPMset =RPMset; prevRPM =RPM; prevSeconds =Seconds; prevDirection =Direction; // Calculating control application cycle time and passed Seconds dT =float ( micros() - InitTime ) / 1000000.0; Seconds+=dT; } Code without Graphical Touch-UpsArduino
/* ############################################### I/O Assignments############################################### */int _chSpeedSet =A0, // Speed setpoint _chKp =A1, // Proportional coefficient reading for PID controller _chKi =A2, // Integral coefficient reading for PID controller _chKd =A3, // Derivative coefficient reading for PID controller _chMotorCmdCCW =3, // PWM output to motor for counter-clockwise turn _chMotorCmdCW =2, // PWM output to motor for clockwise turn _chSpeedRead =24, // Speed reading _chDirection =25; // Direction selector reading/* ############################################### Other Constants ############################################### */#define _minRPM 0 // Minimum RPM to initiate direction changing#define _maxRPM 6000 // Maximum RPM limit#define _DiscSlots 20 // Qty of slots on Index Disc/* ############################################### Global Variables############################################### */boolean Direction, prevDirection;float RPM=0.0, RPMset=0.0, OutputRPM=0.0, Kp=0.0, Ki=0.0, Kd=0.0, Kpmax=2.0, Kimax=1.0, Kdmax=1.0, E=0.0, Eprev=0.0, dT=1.0;/* ############################################### readFrequency(_DI_FrequencyCounter_Pin, _ReadingSpeed) Frequency Reading Function Input Parameters:(int) _DI_FrequencyCounter_Pin :Digital pin to be read (float) _ReadingSpeed____________:Custom reading speed between 0...10 (Note.1) Note.1:_ReadingSpeed is a value to specify how long shall the changes be counted. It cannot be 0(zero), negative values or a value greater than 10. When _ReadingSpeed changed, 1 second shall be divided by this value to calculate required counting duration. For example; - _ReadingSpeed =0.1 -> input shall be counted during 10 seconds (=1/0.1) - _ReadingSpeed =0.5 -> input shall be counted during 2 seconds (=1/0.5) - _ReadingSpeed =2.0 -> input shall be counted during 0.5 seconds (=1/2) - _ReadingSpeed =4.0 -> input shall be counted during 0.25 seconds (=1/4) Importantly note that, increasing of _ReadingSpeed is a disadvantage especially on lower frequencies (generally below 100 Hz) since counting error increases up to 20%~40% by decreasing frequency.############################################### */int readFrequency(int _DI_FrequencyCounter_Pin, float _ReadingSpeed){ pinMode(_DI_FrequencyCounter_Pin,INPUT); byte _DigitalRead, _DigitalRead_Previous =0; unsigned long _Time =0, _Time_Init; float _Frequency =0; if ( (_ReadingSpeed<=0) || (_ReadingSpeed>10) ) return (-1); else { _Time_Init =micros(); do { _DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if ( (_DigitalRead_Previous==1) &&(_DigitalRead==0) ) _Frequency++; _DigitalRead_Previous =_DigitalRead; _Time =micros(); } while ( _Time <(_Time_Init + (1000000/_ReadingSpeed)) ); } return (_ReadingSpeed * _Frequency);}/* ########### End of readFrequency() ########### *//* ############################################## *//* ############################################### controllerPID(RangeMin, RangeMax, _E, _Eprev, _dT, _Kp, _Ki, _Kd) PID Controller Function Input Parameters:(float) RangeMin:Minimum limit for output (float) RangeMax:Maximum limit for output (float) _E_____:Current error signal (float) _Eprev :Previous error signal (float) _dT____:Time difference as seconds (float) _Kp____:Proportional coefficient (float) _Ki____:Integral coefficient (float) _Kp____:Derivative coefficient Adjustment procedure:1. Set Kp=0, Ki=0, Kd=0. 2. Start to increase Kp until the system oscillates at fixed period (Pc) and note critical gain Kc =Kp. 3. Adjust final coefficients as follows. for P-control only :Kp =0.50*Kc for PI-control only :Kp =0.45*Kc, Ki =1.2/Pc for PID-control :Kp =0.60*Kc, Ki =2.0/Pc, Kd=Pc/8 4. Fine tuning could be done by slightly changing each coefficient.############################################### */ float controllerPID(float _E, float _Eprev, float _dT, float _Kp, float _Ki, float _Kd){ float P, I, D; /* Base Formula:U =_Kp * ( _E + 0.5*(1/_Ki)*(_E+_Eprev)*_dT + _Kd*(_E-_Eprev)/_dT ); */ P =_Kp * _E; /* Proportional Component */ I =_Kp * 0.5 * _Ki * (_E+_Eprev) * _dT; /* Integral Component */ D =_Kp * _Kd * (_E-_Eprev) / _dT; /* Derivative Component */ return (P+I+D);}/* ########### End of controllerPID() ########### *//* ############################################## *//* ############################################### Setup############################################### */void setup(){ analogReadResolution(12); pinMode(_chDirection,INPUT); // Direction selector reading pinMode(_chMotorCmdCCW,OUTPUT); // PWM output to motor for counter-clockwise turn pinMode(_chMotorCmdCW,OUTPUT); // PWM output to motor for clockwise turn // Initial killing the PWM outputs to motor analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); // Initial reading for direction selection Direction=digitalRead(_chDirection); // HIGH=CCW, LOW=CW prevDirection=Direction;}/* ############################################### Loop############################################### */void loop(){ // Initialization Time:Necessary for PID controller. int InitTime =micros(); // Reading Inputs /* Controller Coefficients */ Kp =Kpmax * (float)analogRead(_chKp) / 4095; Ki =Kimax * (float)analogRead(_chKi) / 4095; Kd =Kdmax * (float)analogRead(_chKd) / 4095; /* Direction Selector */ Direction =digitalRead(_chDirection); /* HIGH=CCW, LOW=CW */ /* Actual RPM and RPM Setpoint Note that maximum selectable RPM is 5000. */ RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; RPMset =5000 * (float)analogRead(_chSpeedSet) / 4095; // Calculations and Actions /* Error Signal, PID Controller Output and Final Output (PWM) to Motor */ E =RPMset - RPM; float cPID =controllerPID(E, Eprev, dT, Kp, Ki, Kd); if ( RPMset ==0 ) OutputRPM =0; else OutputRPM =OutputRPM + cPID; if ( OutputRPM <_minRPM ) OutputRPM =_minRPM; if ( OutputRPM> _maxRPM ) OutputRPM =_maxRPM; /* Changing Direction when inverted */ if ( Direction !=prevDirection ) { /* Killing both of the PWM outputs to motor */ analogWrite(_chMotorCmdCCW,0); analogWrite(_chMotorCmdCW,0); /* Wait until motor speed decreases */ do { RPM =60 * (float)readFrequency(_chSpeedRead,4) / _DiscSlots; } while ( RPM> _minRPM ); } // Writing Outputs if (Direction==HIGH) analogWrite(_chMotorCmdCCW,(int)(255*OutputRPM/_maxRPM)); else analogWrite(_chMotorCmdCW, (int)(255*OutputRPM/_maxRPM)); // Storing Values generated on previous cycle Eprev =E; prevDirection =Direction; // Calculating control application cycle time and passed Seconds dT =float ( micros() - InitTime ) / 1000000.0;}
섹션> 회로도
It's a prototype to explain DC motor speed control by using PID controller, and what should be considered for reversing.