-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPID__.ino
233 lines (188 loc) · 9.63 KB
/
PID__.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include <Servo.h>
/////////////////////////////
// Configurable parameters //
/////////////////////////////
// Arduino pin assignment
#define PIN_LED 9 // [1234] LED를 아두이노 GPIO 9번 핀에 연결
#define PIN_SERVO 10 // [3069] 서보모터를 아두이노 GPIO 10번 핀에 연결
#define PIN_IR A0 // [3070] 적외선 센서를 아두이노 아날로그 A0핀에 연결
// Framework setting
#define _DIST_TARGET 200 // [2635] 목표하는 위치
#define _DIST_MIN 100 //[3050] 거리의 최솟값이 100mm
#define _DIST_MAX 410 //[3068] 거리의 최대값 410mm
// Distance sensor
#define _DIST_ALPHA 0.0 //[3054] 센서 보정정도
// [3131] EMA필터값
// Servo ran
#define _DUTY_MIN 1400 // [3052] 서보의 최소각도를 microseconds로 표현
#define _DUTY_NEU 1600 // [3070] 레일플레이트 중립위치를 만드는 서보 duty값
#define _DUTY_MAX 1800 //[3062] 서보의 최대각도의 microseconds의 값
// Servo speed control
#define _SERVO_ANGLE 30 // [3131] 최대 가동범위에 따른 목표 서보 회전각
#define _SERVO_SPEED 3000 //[3064]서보 속도를 30으로
// Event periods
#define _INTERVAL_DIST 20 // [2635] 적외선센서 업데이트 주기
#define _INTERVAL_SERVO 20 // [2635] 서보 업데이트 주기
#define _INTERVAL_SERIAL 100 // [3070] 시리얼 플로터 갱신 속도
// PID parameters
#define _KP 2.0 // [2635] P 이득 비율(?)
#define _KI 0.0 // [2635] I 이득 비율
#define _KD 25.0 // [2635] D 이득 비율
//#define _INTERVAL_DIST 30 // DELAY_MICROS * samples_num^2 의 값이 최종 거리측정 인터벌임. 넉넉하게 30ms 잡음.
#define DELAY_MICROS 1500 // 필터에 넣을 샘플값을 측정하는 딜레이(고정값!)
#define EMA_ALPHA 0.35 // EMA 필터 값을 결정하는 ALPHA 값. 작성자가 생각하는 최적값임.
float ema_dist=0; // EMA 필터에 사용할 변수
float filtered_dist; // 최종 측정된 거리값을 넣을 변수. loop()안에 filtered_dist = filtered_ir_distance(); 형태로 사용하면 됨.
float samples_num = 3; // 스파이크 제거를 위한 부분필터에 샘플을 몇개 측정할 것인지. 3개로 충분함! 가능하면 수정하지 말 것.
//////////////////////
// global variables //
//////////////////////
// Servo instance
Servo myservo; // [3070] 서보 객체 생성
// Distance sensor
float dist_target; // [1234] location to send the ball
float dist_raw, dist_ema; // [3070] 거리와 노이즈 필터 적용 후 거리를 저장하기 위한 변수
// Event periods
unsigned long last_sampling_time_dist, last_sampling_time_servo,
last_sampling_time_serial;
// [2635] 각 이벤트별 업데이트를 위해 측정하는 시간
//[3054] 마지막으로 측정한 거리, 마지막으로 측정한 서보 각도
bool event_dist, event_servo, event_serial; // [2635] 이벤트별 이번 루프에서 업데이트 여부
// Servo speed control
int duty_chg_per_interval; // [3055] 서보속도 제어를 위한 변수 선언
int duty_target, duty_curr; // [3131] 목표duty, 현재duty
// PID variables
float error_curr, error_prev, control, pterm, dterm, iterm; // [3070] PID 제어를 위한 현재 오차, 이전오차, 컨트롤(?), p 값, d 값, i 값 변수 선언
void setup() {
// initialize GPIO pins for LED and attach servo
pinMode(PIN_LED, OUTPUT); //[3062] 핀 LED 활성화
myservo.attach(PIN_SERVO); // [3070] 서보 구동을 위한 서보 초기화
// initialize global variables
duty_target, duty_curr = _DUTY_MIN; // [3055] duty_target, duty_curr 초기화
last_sampling_time_dist, last_sampling_time_servo, last_sampling_time_serial = 0;
// [3055] 샘플링 타임 변수 초기화
error_prev, error_curr = _DUTY_NEU; //error 값 초기화???
dist_raw, dist_ema = _DIST_MIN; // [3055] dist 변수 초기화
pterm = dterm = iterm = 0; // [2635] pid 제어값에서 우선 p 만 사용하도록
// move servo to neutral position
myservo.writeMicroseconds(_DUTY_NEU);//[2635]
// initialize serial port
Serial.begin(57600); //[2635]
// convert angle speed into duty change per interval.
duty_chg_per_interval = (_DUTY_MAX - _DUTY_MIN)/(float)(_SERVO_ANGLE) * (_SERVO_SPEED /1000.0)*_INTERVAL_SERVO;
// [3131] 설정한 서보 스피드에 따른 duty change per interval 값을 변환
}
void loop() {
// [3055] indentation 수정(space 4칸)
/////////////////////
// Event generator //
/////////////////////
unsigned long time_curr = millis(); // [3070] 이벤트 업데이트 주기 계산을 위한 현재 시간
// [3070] 이벤트 주기가 돌아올때까지 현재시간과 비교하며 기다리도록 함.
if(time_curr >= last_sampling_time_dist + _INTERVAL_DIST) {
last_sampling_time_dist += _INTERVAL_DIST;
event_dist = true;
}
if(time_curr >= last_sampling_time_servo + _INTERVAL_SERVO) {
last_sampling_time_servo += _INTERVAL_SERVO;
event_servo = true;
}
if(time_curr >= last_sampling_time_serial + _INTERVAL_SERIAL) {
last_sampling_time_serial += _INTERVAL_SERIAL;
event_serial = true;
}
////////////////////
// Event handlers //
////////////////////
if(event_dist) {
event_dist = false; // [2635] 업데이트 대기distance
// get a distance reading from the distance sensor
dist_raw = filtered_ir_distance(); // [2635] 센서 값 받아와서 변수에 저장
//dist_ema = _DIST_ALPHA * dist_raw + (1 - _DIST_ALPHA) * dist_ema;
// [3055] dist_ema 설정
//filtered_dist = filtered_ir_distance();
// PID control logic
error_curr = _DIST_TARGET - dist_raw; // [2635] 오차 계산
pterm = _KP * error_curr; // [2635] p값은 오차
dterm = _KD * (error_curr - error_prev);
//control = _KP * pterm + _KI * iterm + _KD * dterm; // [2635] control 값, i와 d는 현재 0
control = pterm + dterm;
//duty_target = f(duty_neutral, control)
//duty_target = _DUTY_NEU * (1 + control); // [3070] control 값이 다 합해서 1이 되도록 되어있다면, 중립 위치에 컨트롤 값 만큼의 비율을 더해 목표위치를 정한다.
// [2635] NEU 에다가 위아래로 control 에 맞게 조절한 값?
duty_target = _DUTY_NEU + control;
// keep duty_target value within the range of [_DUTY_MIN, _DUTY_MAX]
if(duty_target < _DUTY_MIN) // [2635] 양극값 넘어가는 경우 극값으로 제한
{
duty_target = _DUTY_MIN;
}
if(duty_target > _DUTY_MAX)
{
duty_target = _DUTY_MAX;
}
error_prev = error_curr;
}
if(event_servo) {
event_servo = false; // [2635] 업데이트 대기
// adjust duty_curr toward duty_target by duty_chg_per_interval
if(duty_target > duty_curr) { // [3070]
duty_curr += duty_chg_per_interval;
if(duty_curr > duty_target) duty_curr = duty_target;
}
else {
duty_curr -= duty_chg_per_interval;
if(duty_curr < duty_target) duty_curr = duty_target;
}
// update servo position
myservo.writeMicroseconds((int)duty_curr); // [3070]
event_servo = false; // [3055] 모든 작업이 끝나면 이벤트를 다시 false로
}
if(event_serial) {
event_serial = false; // [3070] 이벤트 주기가 왔다면 다시 false로 만들고 이벤트를 수행
Serial.print("dist_ir:");
Serial.print(dist_raw);
Serial.print(",pterm:");
Serial.print(map(pterm,-1000,1000,510,610));
Serial.print(",dterm:");
Serial.print(map(dterm,-1000,1000,510,610));
Serial.print(",duty_target:");
Serial.print(map(duty_target,1000,2000,410,510));
Serial.print(",duty_curr:");
Serial.print(map(duty_curr,1000,2000,410,510));
Serial.println(",Min:100,Low:200,dist_target:255,High:310,Max:410");
event_serial = false; // [3055] event_serial false로 변경
}
}
float ir_distance(void){ // return value unit: mm
float val; // [3055] 변수 val 선언
float volt = float(analogRead(PIN_IR)); // [3055] volt변수에 적외선 센서 측정값 입력
val = ((6762.0/(volt - 9.0)) - 4.0) * 10.0; // [3055] volt 값 보정
return val; // [3055] val 리턴
}
float under_noise_filter(void){ // 아래로 떨어지는 형태의 스파이크를 제거해주는 필터
int currReading;
int largestReading = 0;
for (int i = 0; i < samples_num; i++) {
currReading = ir_distance();
if (currReading > largestReading) { largestReading = currReading; }
// Delay a short time before taking another reading
delayMicroseconds(DELAY_MICROS);
}
return largestReading;
}
float filtered_ir_distance(void){ // 아래로 떨어지는 형태의 스파이크를 제거 후, 위로 치솟는 스파이크를 제거하고 EMA필터를 적용함.
// under_noise_filter를 통과한 값을 upper_nosie_filter에 넣어 최종 값이 나옴.
int currReading;
int lowestReading = 1024;
for (int i = 0; i < samples_num; i++) {
currReading = under_noise_filter();
if (currReading < lowestReading) { lowestReading = currReading; }
}
// eam 필터 추가
ema_dist = EMA_ALPHA*lowestReading + (1-EMA_ALPHA)*ema_dist;
return ema_dist;
}
//===================================================
/* float ir_distance_filtered(void){ // return value unit: mm
return ir_distance(); // for now, just use ir_distance() without noise filter.
} */