예열 및 디스플레이 표시
재료별로 재료명, 목표온도, 목표속도를 디스플레이에 표시하는 것을 완성하였습니다. 이 단계에서는 목표온도가 설정되면, 현재온도와 비교하고 차이가 있으면 예열하는 것을 구현 하는 것이 목표입니다.
※ 지금부터는 전체코드를 계속해서 보여주는 것은 코드가 길기 때문에 작성된 코드에서 추가될 부분만 설명드리겠습니다.
예열이 되게 하는 방법은 PID 제어를 적용하여, 온도유지를 원활하게 하도록 하겠습니다.
PID 제어의 코드는 온도 챕터 에 설명되어 있습니다.
1float Kp=36, Ki=2.5, Kd=0.625, dT = 0.05;
2int16_t error, previousError = 0; // 오차 변수
3float integral, derivaitve = 0; // 적분 미분 변수
4
5/*
6 * pid 계산을 하고, 결과값을 HEATER_EN 핀에 적용
7 */
8void getPIDoutput(int targetTemp, int actualValue)
9{
10 float outputValue;
11
12 //설정온도에 도달하기 10도 전일때부터 PID 제어 시작
13 if(curTemp < setTemperature - 10)
14 {
15 outputValue = 255;
16 }
17 else
18 {
19 // 에러 값 저장
20 error = targetTemp - actualValue;
21
22 // 적분 값 저장
23 integral = integral + (float)error*dT;
24
25 // 미분 값 저장
26 derivaitve = ((float)error - (float)previousError)/dT;
27 previousError = error;
28
29 // PID 계산
30 outputValue = Kp*error + Ki*integral + Kp*derivaitve;
31
32 // output 값이 0~255 범위를 벗어나면 최대, 최소 값을 대신 저장
33 if(outputValue > 255)
34 {
35 outputValue = 255;
36 }
37 else if(outputValue <0)
38 {
39 outputValue = 0;
40 }
41
42 // outputValue 가 0이라면 예열이 되지 않음으로, isHeating 변수를 false로 저장
43 if(outputValue == 0)
44 {
45 isHeating = false;
46 }
47 else
48 {
49 isHeating = true;
50 }
51 }
52 // 계산된 결과값을 디지털 9번핀에 analogWrite 로 입력
53 analogWrite(HEATER_EN,outputValue);
54}
코드 자체는 온도 챕터에서 배웠던 것과 유사합니다.
추가해야될 코드는 예열온도를 디스플레이에 표시하는 것 입니다.
targetTemp가 변경될 때 마다 integral 값은 0으로 리셋해주어야 정확한 값을 얻을 수 있습니다.
설정 온도를 표시하는 것은 큰 문제가 없습니다만, 현재 온도를 표시하는 것은 문제가 있습니다.
이전 단계에서 현재 온도를 표시하는 좌표를 0,16 이라고 설정하였습니다.
이 좌표 자체는 낮은 온도에서 높은 온도로 예열하는 경우에는 문제가 되지 않습니다.
반대로 높은 온도에서 낮은 온도로 냉각할 경우 아래와 문제가 발생합니다.
위의 그림은 105도에서 97도까지 냉각될 때, 현재 코드로 문제가 발생하는 부분입니다.
자리수가 달라서 100에서 99로 변경될 때 맨 뒤의 0이 지워지지 않습니다.
이 문제를 해결하는 방법은 여러가지가 있지만, 여기에서는 간단하게 해결해보겠습니다.
아마 여유가 있으시면, 해결방법을 스스로 찾아보시기 바립니다.
우리는 위와 같이 문제를 해결해보려 합니다. 2자리수일 때 앞에 숫자 0을 붙여주는 방법입니다.
이를 한번에 해결해주는 함수도 있지만, 간단하게 하지 않고, 위 문제를 해결하는 코드를 작성해봅니다.
숫자가 100 이하일 경우, 10 이하일 경우에 따라 문자열에 저장하기전 0을 추가해주는 방법입니다.
1// result 는 int형 변수
2// strToShow 는 String형 변수
3if(result < 10)
4{
5 strToShow = "00" + String(result);
6}
7else if(result < 100)
8{
9 strToShow = "0" + String(result);
10}
11else
12{
13 strToShow = String(result);
14}
15
16// 디스플레이에 strToShow 를 표시
17showTextToScreen(0,16,strToShow);
result < 10 를 확인하는 조건을 먼저 확인을 해야 합니다. result < 100 의 조건에는 result < 10 이어도 참이 될 수 있기 때문입니다.
String(result) 는 result 의 값을 문자열로 변경해주는 함수입니다.
이 코드는 getTemperature 함수에 포함되야 합니다.
이번 단계에서 PID 제어와 디스플레이에 현재온도를 표시해주는 코드를 추가하여 전체 코드를 작성합니다.
1#include "ssd1306.h" // 라이브러리 포함
2
3#define BTN_A 8 // A버튼
4#define BTN_B 7 // B버튼
5#define BTN_C 11 // C버튼
6#define BTN_D 12 // D버튼
7
8#define MOTOR_EN 5 // 모터 활성화 핀
9#define MOTOR_DIR 6 // 모터 방향 핀
10#define MOTOR_SPEED 10 // 모터 속도 핀
11
12#define HEATER_EN 9 // 열선 핀
13
14#define TEMP_IN A0 // 온도 읽는 핀
15
16#define VALUE_TEMPTB 0 // 온도 테이블의 신호 값 인덱스
17#define CELSIUS_TEMPTB 1 // 온도 테이블의 온도 값 인덱스
18
19#define MATERIAL_COUNT 3 // material 구조체 배열의 갯수
20
21String strToShow; // 디스플레이에 보여줄 문자열을 저장하는 변수
22
23bool isHeating = false; // 온도가 목표보다 높은지 확인하는 bool 변수
24bool isNeedUpdateScreen = true; // 업데이트가 필요한지 확인하는 bool 변수 생성
25bool isPressedC_BTN, isPressedD_BTN; // 버튼이 눌러져 있는지 확인하는 bool 변수 생성
26
27int curTemp = 0; // 현재온도
28
29long timeInterval = millis(); // 50ms 에 1회씩 작동하기 위한 시간 변수
30
31int material_index = 0; // 재료 구조체 배열의 위치를 가리키는 인덱스
32int setTemperature = 0; // 설정된 온도
33
34// 온도, 속도, 재료명을 포함한 구조체 선언
35struct setting {
36 int temperature;
37 char* materialName;
38};
39
40struct setting materials[] = {{0, "OFF"}, {60, "PCL"}, {200, "PLA"}};
41
42/*
43 * 온도 테이블 배열
44 * 첫번째 항목은 신호 값, 두번째 항목은 온도 값
45 */
46int temptable[23][2] = {
47 {1023,0},
48 {1022,10},
49 {1020,20},
50 {1016,30},
51 {1011,40},
52 {1009,50},
53 {1006,60},
54 {1004,70},
55 {1000,80},
56 {990,90},
57 {983,100},
58 {976,110},
59 {972,120},
60 {964,130},
61 {955,140},
62 {942,150},
63 {929,160},
64 {910,170},
65 {895,180},
66 {864,190},
67 {839,200},
68 {800,210},
69 {744,220}
70};
71
72/*
73 * 입력받은 문자를 디스플레이 좌표에 표시
74 */
75void showTextToScreen(int x, int y, String text)
76{
77 text = text + "\n";
78 char ch[10];
79 text.toCharArray(ch,text.length());
80 ssd1306_printFixedN(x, y, ch, STYLE_NORMAL, FONT_SIZE_2X);
81}
82
83/*
84 * 온도를 읽고, 정확한 온도로 계산 후 결과 값을 화면에 표시하고 반환하는 함수
85 * VALUE_TEMPTB = 0, CELSIUS_TEMPTB = 1 으로 온도표의 각 항목을 지시함
86 */
87int getTemperature()
88{
89 float ratioTemp;
90 float tempADU = analogRead(A0);
91 int result;
92
93 for(int i=1; i<23; i++){
94 if(tempADU >= temptable[i][VALUE_TEMPTB])
95 {
96 ratioTemp = (tempADU - temptable[i][VALUE_TEMPTB])/(temptable[i-1][VALUE_TEMPTB] - temptable[i][VALUE_TEMPTB]);
97
98 result = temptable[i][CELSIUS_TEMPTB] - ratioTemp*(temptable[i][CELSIUS_TEMPTB] - temptable[i-1][CELSIUS_TEMPTB]);
99
100 if(result < 10)
101 {
102 strToShow = "00" + String(result);
103 }
104 else if(result < 100)
105 {
106 strToShow = "0" + String(result);
107 }
108 else
109 {
110 strToShow = String(result);
111 }
112
113 showTextToScreen(0,16,strToShow);
114
115 return result;
116 }
117 }
118
119 return ;
120}
121
122float Kp=36, Ki=2.5, Kd=0.625, dT = 0.05;
123int16_t error, previousError = 0; // 오차 변수
124float integral, derivaitve = 0; // 적분 미분 변수
125
126/*
127* pid 계산을 하고, 결과값을 HEATER_EN 핀에 적용
128*/
129void getPIDoutput(int targetTemp, int actualValue)
130{
131 float outputValue;
132
133 //설정온도에 도달하기 10도 전일때부터 PID 제어 시작
134 if(curTemp < setTemperature - 10)
135 {
136 outputValue = 255;
137 }
138 else
139 {
140 // 에러 값 저장
141 error = targetTemp - actualValue;
142
143 // 적분 값 저장
144 integral = integral + (float)error*dT;
145
146 // 미분 값 저장
147 derivaitve = ((float)error - (float)previousError)/dT;
148 previousError = error;
149
150 // PID 계산
151 outputValue = Kp*error + Ki*integral + Kp*derivaitve;
152
153 // output 값이 0~255 범위를 벗어나면 최대, 최소 값을 대신 저장
154 if(outputValue > 255)
155 {
156 outputValue = 255;
157 }
158 else if(outputValue <0)
159 {
160 outputValue = 0;
161 }
162
163 // outputValue 가 0이라면 예열이 되지 않음으로, isHeating 변수를 false로 저장
164 if(outputValue == 0)
165 {
166 isHeating = false;
167 }
168 else
169 {
170 isHeating = true;
171 }
172 }
173 // 계산된 결과값을 디지털 9번핀에 analogWrite 로 입력
174 analogWrite(HEATER_EN,outputValue);
175}
176
177/*
178 * 버튼이 눌러졌는지 확인하고, 해당버튼에 맞는 코드 실행
179 */
180void checkBtnPressed()
181{
182 // C버튼이 눌리고, D버튼이 눌리지 않았을 경우 실행
183 if(!digitalRead(BTN_C) && digitalRead(BTN_D))
184 {
185 // C 버튼이 눌러져 있으면(isPressedC_BTN 가 true), 아래 코드 건너뜀
186 if(!isPressedC_BTN)
187 {
188 // 구조체 배열의 인덱스로 사용될 변수 값 1 증가
189 material_index++;
190 if(material_index > MATERIAL_COUNT-1)
191 {
192 material_index = 0;
193 }
194
195 // 디스플레이 업데이트를 할 수 있도록 변수 변경
196 isNeedUpdateScreen = true;
197
198 // 버튼 상태 변수 변경
199 isPressedC_BTN = true;
200
201 // 적분 값 리셋
202 integral = 0;
203 }
204 }
205 // D버튼이 눌리고, C버튼이 눌리지 않았을 경우 실행
206 else if(digitalRead(BTN_C) && !digitalRead(BTN_D))
207 {
208 // D 버튼이 눌러져 있으면(isPressedD_BTN 가 true), 아래 코드 건너뜀
209 if(!isPressedD_BTN)
210 {
211 // 구조체 배열의 인덱스로 사용될 변수 값 1 감소
212 material_index--;
213 if(material_index < 0)
214 {
215 material_index = MATERIAL_COUNT-1;
216 }
217
218 // 디스플레이 업데이트를 할 수 있도록 변수 변경
219 isNeedUpdateScreen = true;
220
221 // 버튼 상태 변수 변경
222 isPressedD_BTN = true;
223
224 // 적분 값 리셋
225 integral = 0;
226 }
227 }
228 else
229 {
230 // C, D 버튼 모두 눌리지 않을 경우, 변수 값 변경
231 isPressedC_BTN = false;
232 isPressedD_BTN = false;
233 }
234
235}
236
237/*
238 * 화면에 표시될 재료명, 목표속도, 목표온도를 업데이트
239 */
240void updateMaterial(int index)
241{
242 // isNeedUpdate 변수가 false 이면 함수를 종료함
243 if(!isNeedUpdateScreen) return;
244
245 // 목표 온도, 속도 항목에 현재 인덱스의 구조체 값을 저장
246 setTemperature = materials[index].temperature;
247 setMotorSpeed = materials[index].motorSpeed;
248
249 // 화면 클리어
250 ssd1306_clearScreen();
251
252 // 화면에 목표 온도, 재료 명, 속도를 표시
253 // 재료명은 좌표 0,0 에 표시
254 // 목표 온도는 좌표 52, 16 에 표시
255 strToShow = materials[index].materialName;
256 showTextToScreen(0,0, strToShow);
257
258 strToShow = "/" + String(setTemperature);
259 showTextToScreen(52,16,strToShow);
260
261 // 화면을 계속해서 업데이트 하지 않도록 방지하는 bool 변수 변경
262 isNeedUpdateScreen = false;
263}
264
265void setup()
266{
267 pinMode(BTN_A, INPUT_PULLUP);
268 pinMode(BTN_B, INPUT_PULLUP);
269 pinMode(BTN_C, INPUT_PULLUP);
270 pinMode(BTN_D, INPUT_PULLUP);
271
272 pinMode(MOTOR_EN, OUTPUT);
273 pinMode(MOTOR_DIR, OUTPUT);
274 pinMode(MOTOR_SPEED, OUTPUT);
275
276 pinMode(HEATER_EN, OUTPUT);
277
278 digitalWrite(MOTOR_EN, HIGH); // 모터 활성화
279
280 ssd1306_128x32_i2c_init(); // 32로 변경
281 ssd1306_fillScreen(0x00); // 화면 초기화
282 ssd1306_setFixedFont(ssd1306xled_font6x8); // 폰트 설정
283 ssd1306_flipHorizontal(1); // x 화면 대칭 회전
284 ssd1306_flipVertical(1); // y 화면 대칭 회전
285}
286
287void loop()
288{
289 if(millis() - timeInterval > 50)
290 {
291 curTemp = getTemperature();
292 updateMaterial(material_index);
293 checkBtnPressed();
294 getPIDoutput(setTemperature, curTemp);
295
296 timeInterval = millis();
297 }
298}