$19 정도에 미세 먼지 센서 SDS011을 구입했습니다.
이 센서는 미세먼지 PM2.5와 PM10을 측정하여 serial로 전달해줍니다. 통신 파라미터는 다음과 같습니다.
통신 속도 : 9600bps
패리티 : 없음
데이터 비트 : 8
스톱비트 : 1
SDS011이 전달하는 정보는 다음과 같습니다.
1초에 한 번, 길이가 10바이트로 고정된 패킷이 옵니다. 이렇게 고정된 길이의 패킷이 오는 것은 HAL 드라이버의 UART 인터럽트를 적용하기에 딱 좋은 상황입니다. 그런데 실제로 10자를 단위로 인터럽트가 걸리도록 설정해 보았는데, 무엇이 잘못되었는지 콜백 함수인 HAL_UART_RxCpltCallback() 함수를 호출하지 않습니다. 1자를 단위로 인터럽트가 발생하도록 하면 잘 동작하는데, 2자 이상 받은 후에 인터럽트가 발생하도록 하면 콜백 함수를 호출하지 않습니다. 한 글자씩 받다가 10자가 되면 main() 함수의 while() 문에서 처리하도록 프로그래밍하겠습니다.
앞의 글 DHT22(AM2302) 온도 습도 사용하기에서 만든 프로젝트를 확장해서 MGG19264 그래픽 lcd에 미세먼지 정보를 출력해 보겠습니다.
1. 프로젝트 만들기
STM32CubeMX에서 앞의 글에서 만든 STM32F130DTH22.ioc를 연 후에, USART1을 사용하도록 다음과 같이 변경합니다.
[USART1]의 [Mode]를 Asyncronous로 지정했고, [Parameter Settings]에서 Baud Rate를 9600으로 지정했습니다. [NVIC Settings]에서 USART1 global interrupt를 Enable 시켰습니다.
STM32CubeMX의 맨 위 [File] 메뉴에서 [Save Project As] 항목을 클릭하여 다음과 같이 변경하여 저장합니다.
프로젝트 명이 다음의 그림과 같이 바뀐 것을 확인하고, [GENERATE CODE] 버튼을 눌러 코드를를 생산합니다. 본 글에서는 프로젝트명을 STM32F103AIR로 정했습니다.
2. 파일 복사 및 코드 추가하기
앞의 글DHT22(AM2302) 온도 습도 사용하기에서 만든 프로젝트로부터 다음의 파일들을 복사합니다.
[Inc] 폴더 안의 헤더 파일 4개(AM2302.h, GlcdDisplay.h, M19264Display.h, ST7565Font.h)를 새 프로젝트의 [Inc] 폴더로, [Src] 폴더 안의 소스프로그램 4개(AM2302.c, GlcdDisplay.c, M19264Display.c, ST7565Font.c)를 새 프로젝트의 [Src] 폴더로 각각 복사합니다.
main.h, main.c, stm32f1xx_it.c 등 3 개의 파일을 수정할 예정입니다.
1)main.h 파일에 다음의 내용들을 추가합니다.
/* USER CODE BEGIN EFP */ extern uint8_t intFlag; extern uint8_t SDS011Msg[]; extern uint8_t SDS011idx; /* USER CODE END EFP */
/* USER CODE BEGIN Private defines */ #define SDS011 huart1 #define SDS011_MESSAGE_LENGTH 10 #define NO_INTERRUPT 0 #define INT_TIM2_AM2302 (1 << 0) #define INT_UART_SDS011 (1 << 1) #define TEMP_SECOND_COL (M19264_RESOLUTION_X / 2) // column position for temp #define TEMP_VALUE_COL 40 // column position for temp value #define QUEUE_BUFFER_LENGTH 1024 // queue size /* USER CODE END Private defines */
인터럽트를 처리하기 위한 전역 변수 intFlag을 선언했고, 매크로 들을 정의했습니다
2)main.c 파일에 다음 내용들을 입력합니다. 주석은 입력할 위치를 알리기 위해 같이 나열합니다.
/* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "M19264Display.h" #include "GlcdDisplay.h" #include "AM2302.h" /* USER CODE END Includes */ /* Private macro ----------------------------------------------------------------*/ /* USER CODE BEGIN PM */ #define PM25_GOOD 150 #define PM25_COMMON 350 #define PM25_BAD 750 #define PM10_GOOD 300 #define PM10_COMMON 800 #define PM10_BAD 1000 /* USER CODE END PM */
/* USER CODE BEGIN PV */ uint8_t intFlag; uint8_t SDS011Msg[10] = {0,}; uint8_t SDS011idx = 0; /* USER CODE END PV */
/* USER CODE BEGIN PFP */ uint8_t DisplayChar(uint8_t ch); /* USER CODE END PFP */
main() 함수 내에 다음과 같은 코드를 입력합니다.
/* USER CODE BEGIN 1 */ uint8_t ret; AM2302DATA data; int16_t pm25, pm10; char* pState[] = {"좋음", "보통", "나쁨", "매우나쁨"}; char *p1, *p2; /* USER CODE END 1 */
HAL_Delay(2000); intFlag = NO_INTERRUPT; HAL_TIM_Base_Start_IT(&htim2); GlcdInitialize(); HAL_UART_Receive_IT(&SDS011, SDS011Msg, 1); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { if(intFlag & INT_TIM2_AM2302) { intFlag &= ~INT_TIM2_AM2302; ret = AM2302ReadData(&data); if(ret == AM2302_SUCCESS) { GlcdGraphicGotoxy(0, 0); printf("¿Âµµ:%3d.%d\n", data.temp / 10, data.temp % 10); GlcdGraphicGotoxy(TEMP_SECOND_COL, 0); printf("½Àµµ:%3d.%d\n", data.humid / 10, data.humid % 10); } else { GlcdGraphicClear(); printf("Read Error(%d)\n", ret); } } if(intFlag & INT_UART_SDS011) { intFlag &= ~INT_UART_SDS011; SDS011idx = 0; pm25 = SDS011Msg[3] * 256 + SDS011Msg[2]; pm10 = SDS011Msg[5] * 256 + SDS011Msg[4]; for(int i = 0;i < 10;i++) SDS011Msg[i] = 0; GlcdGraphicGotoxy(0, 16); printf("PM25:%3d.%d\n", pm25 / 10, pm25 % 10); GlcdGraphicGotoxy(TEMP_SECOND_COL, 16); printf("PM10:%3d.%d\n", pm10 / 10, pm10 % 10); p1 = pState[pm25 <= PM25_GOOD ? 0:(pm25 <= PM25_COMMON ? 1:(pm25 <= PM25_BAD ? 2:3))]; p2 = pState[pm10 <= PM10_GOOD ? 0:(pm25 <= PM10_COMMON ? 1:(pm25 <= PM10_BAD ? 2:3))]; GlcdGraphicGotoxy(TEMP_VALUE_COL - 3 * ASCII_FONTSIZE_X, 32); printf("%8s\n", p1); GlcdGraphicGotoxy(TEMP_SECOND_COL + TEMP_VALUE_COL - 3 * ASCII_FONTSIZE_X, 32); printf("%8s\n", p2); } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }
변수 pm25는 초미세먼지 (2.5um의 농도 * 10)의 값을 갖고 있는 변수이고, pm10은 미세먼지 (10um의 농도 * 10)의 값을 갖고 있는 변수입니다. 각각의 농도에 따라 미세농도의 예보 기준에 의하여 "좋음", "보통", "나쁨", "매우나쁨"으로 판정을 내립니다. 미세먼지 예보 기준은 서울시 미세먼지 정보센터에서 찾아왔습니다.
3)st32f1xx_it.c 파일을 열어 USART1의 인터럽트 핸들러 USART1_IRQHandler()이 다음과 같도록 입력합니다.
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ if ((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)) { SDS011Msg[SDS011idx] = (uint8_t)(SDS011.Instance->DR & (uint8_t)0x00FF); if(SDS011idx < SDS011_MESSAGE_LENGTH - 1) SDS011idx++; else { intFlag |= INT_UART_SDS011; } } __HAL_UART_CLEAR_PEFLAG(&huart1); /* clear event flag */ return; /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }
main() 함수에서 SDS011이 연결된 USART1로 데이터가 들어오면 USART1 인터럽트가 발생하도록 처리했습니다. USART1인터럽트가 발생하면 인터럽트 핸들러 USART1_IRQHandler() 함수가 호출됩니다. 이 핸들러에서는 새로 들어온 데이터를 SDS011Msg[SDS011idx]에 넣습니다. SDS011idx 변수는 (현재까지 들어온 데이터 수 - 1)의 값을 가지고 있습니다. 이 변수의 값이 9보다 작으면 SDS011idx의 값을 1증가 시키고 인터럽트 핸들러를 마칩니다. 이 변수의 값이 9 이상이면 10개의 데이터가 다 들어온 것이므로 main() 함수에서 처리하도록 infFlag의 값을 설정하고 인터럽트 핸들러를 마칩니다. SDS011로부터 도착한 정보는 위에서 입력한 main() 함수의 while() 문 안의 if(intFlag & INT_UART_SDS011) 문 안에서 처리합니다.
동작하는 사진입니다.
소스 프로그램을 zip 파일로 압축하여 올립니다.
'STM32F103' 카테고리의 다른 글
STM32F103으로 ESP8266 제어하기 - USART 프로그래밍 (0) | 2018.12.25 |
---|---|
미세먼지 센서 SDS011 사용하기(개선편) (0) | 2018.12.18 |
DHT22(AM2302) 온도 습도 센서 사용하기 (2) | 2018.12.11 |
ST7565P GLCD 제어하기(제3편) - printf() 사용하기 (10) | 2018.12.10 |
ST7565P GLCD 제어하기(제2편) - 제어함수 만들기 및 사용하기 (0) | 2018.11.28 |