예열 및 디스플레이 표시

재료별로 재료명, 목표온도, 목표속도를 디스플레이에 표시하는 것을 완성하였습니다. 이 단계에서는 목표온도가 설정되면, 현재온도와 비교하고 차이가 있으면 예열하는 것을 구현 하는 것이 목표입니다.

※ 지금부터는 전체코드를 계속해서 보여주는 것은 코드가 길기 때문에 작성된 코드에서 추가될 부분만 설명드리겠습니다.

예열이 되게 하는 방법은 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 이라고 설정하였습니다.
이 좌표 자체는 낮은 온도에서 높은 온도로 예열하는 경우에는 문제가 되지 않습니다.
반대로 높은 온도에서 낮은 온도로 냉각할 경우 아래와 문제가 발생합니다.

../../_images/Step4_1.gif

위의 그림은 105도에서 97도까지 냉각될 때, 현재 코드로 문제가 발생하는 부분입니다.
자리수가 달라서 100에서 99로 변경될 때 맨 뒤의 0이 지워지지 않습니다.
이 문제를 해결하는 방법은 여러가지가 있지만, 여기에서는 간단하게 해결해보겠습니다.
아마 여유가 있으시면, 해결방법을 스스로 찾아보시기 바립니다.

../../_images/Step4_2.gif
우리는 위와 같이 문제를 해결해보려 합니다. 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}