/*@ 20/05/26
* - CUBEMX 설정 Active, Passive 모드 두 가지 상황으로 분리
* - [동작] 부분 액티브, 패시브 모드에서의 동작 상황 설명 추가
* - 액티브 모드 코드 및 설명 재수정 (타이머 인터럽트 제거)
* - 패시브 모드 코드 및 설명 수정 (타이머 인터럽트 추가)
*
*@ 20/05/21
* - CUBE MX 타이머 인터럽트 설정 추가
* - 타이머 인터럽트를 사용한 PMS7003 데이터 수신으로 변경
* - 수신 완료 콜백 함수 코드 수정
* - 타이머 부분 추가
* - 액티브 모드 부분 코드 및 설명 수정
* - 코드 링크 추가
*/
- VCC : 5V (4.5~5.5V 내부의 팬이 5V로 동작되어야 하기 때문)
- 통신 방식 : UART
- DATA : 3.3V (MCU가 5V로 작동한다면 R/X, T/X핀에 Level Shifter 연결 필요 (5V->3.3V))
- 인터페이스 레벨 : L<0.8, H>2.7 (3.3V)
- 측정 범위 : 0.3~1.0 / 1.0~2.5 / 2.5~10 (㎛)
- 검출 효율 : 50% (0.3㎛), 98%(>=0.5㎛)
- 유효 범위 (pm2.5 기준) : 0~500 ㎍/㎥
- 최대 범위 (pm2.5 기준) : >=1000 ㎍/㎥
- 해상도 : 1 ㎍/㎥
[하드웨어 연결]
주의점
- SET, RESET 핀은 내부 풀업 되있으므로 사용하지 않는다면 연결해선 안된다
- PIN7, 8은 연결해선 안된다
연결
- 손쉬운 연결을 위해 PMS7003 어댑터 보드, 1.27mm-2.00mm-2.54mm 보드 및 1.25mm 커넥터를 사용
- UART TX -> PMS7003 RX, UART RX -> PMS7003 TX 에 연결
- 데이터 라인의 전압이 3.3V을 필요로 하므로 5V로 작동하는 MCU를 사용할 경우, TX, RX 라인에 Level Shifter 를 사용해 데이터 라인의 전압을 변환시킬 필요가 있다
- ex) MCU UART TX(5V) -> 5V to 3.3V Level Shifter -> PMS7003 RX(3.3V)
- PMS7003 TX(3.3V) -> 3.3V to 5V Level Shifter -> MCU UART RX
[CUBE MX 설정]
- Active, Passive 모드에 따라 UART DMA, Timer 다르게 설정
<공통>
- UART 설정
- 보드 레이트 : 9600 bps
- 데이터 길이 : 8 비트
- 패리티 비트 : X
- 스탑 비트 : 1
- 인터럽트 활성화
<Active Mode>
- UART DMA - Rx Circular 모드로 설정
- 타이머 인터럽트 사용 X
<Passive Mode>
- DMA 미사용, 인터럽트를 통해 송수신 (DMA 사용 설정 되있어도 상관 X)
- 타이머 인터럽트 설정
- 타이머 주기 : 해당 타이머 인터럽트가 속하는 APBx 타이머 클럭 속도 / (Prescaler + 1) + (Counter Period + 1)
- WB55 : TIM16 - APB2 (Timer Clock=32Mhz)
- 32,000,000 / {((32,000 -1) + 1)) + ((5,000 - 1) + 1)} = 0.2 Hz = 5 Sec (T=1/f)
- 5초 주기로 타이머 인터럽트 작동
[동작]
- 두개의 디지털 출력 옵션 : 액티브(디폴트 설정), 패시브 모드
- 액티브 모드 : PMS7003 센서는 지속적으로 측정한 센서값을 호스트로 자동 전송하고 호스트는 DMA Rx를 Circular 모드로 설정함으로써 PMS7003 데이터 수신 마지막에 자동으로 transfer count를 리셋하고 소스 버퍼의 첫번째 바이트부터 다시 수신을 시작한다
- 패시브 모드 : PMS7003에 Change Mode 명령어+패시브 모드 명령어를 전송해 PMS7003을 패시브 모드로 변경하고 데이터를 읽어올 땐 Passive Read 명령어를 보낸 뒤 UART 수신 명령어를 사용해 센서 데이터를 수신한다
- 커맨드 명령어를 통해 슬립, 웨이크업 기능을 사용할 수 있다
- 팬 성능으로 인해 센서가 슬립 모드에서 깨어나고 적어도 30초 지난 뒤에야 안정된 데이터를 얻는게 가능
<액티브 모드>
- 액티브 모드에서 센서는 자동으로 시리얼 데이터를 호스트에 보냄
- 액티브 모드는 두개의 서브 모드로 분리 : Stable 모드, Fast 모드
- 농도 변화가 작으면 센서는 2.3s 간격의 스테이블 모드로 작동
- 농도 변화가 크다면 센서는 200~800ms 간격의 패스트 모드로 자동 변경 (농도가 높을 수록 더 짧은 간격을 가짐)
- 호스트(MCU)로 전송하는 데이터의 길이 : 32Bytes (각 8bit의 크기를 가진 32개의 배열)
(00~31 중 사용할 부분만 정리)
00 : 스타트 캐릭터1 : 0x42 고정
01 : 스타트 캐릭터2 : 0x4d 고정
(고정값이므로 UART 통신이 문제없이 이뤄지고 있는지 확인하는데 사용할 수 있다)
10 : 데이터4 상위 8비트 : PM1.0에서의 먼지 농도 ㎍/㎥(대기 환경 하)
11 : 데이터4 하위 8비트
12 : 데이터5 상위 8비트 : PM2.5에서의 먼지 농도 ㎍/㎥(대기 환경 하)
13 : 데이터5 하위 8비트
14 : 데이터6 상위 8비트 : PM10에서의 먼지 농도 ㎍/㎥(대기 환경 하)
15 : 데이터6 하위 8비트
16 : 데이터7 상위 8비트 : 0.1L의 공기 중 지름 0.3um 이상의 입자의 갯수를 나타냄
17 : 데이터7 하위 8비트
18 : 데이터8 상위 8비트 : 0.1L의 공기 중 지름 0.5um 이상의 입자의 갯수를 나타냄
19 : 데이터8 하위 8비트
20 : 데이터9 상위 8비트 : 0.1L의 공기 중 지름 1.0um 이상의 입자의 갯수를 나타냄
21 : 데이터9 하위 8비트
22 : 데이터10 상위 8비트 : 0.1L의 공기 중 지름 2.5um 이상의 입자의 갯수를 나타냄
23 : 데이터10 하위 8비트
24 : 데이터11 상위 8비트 : 0.1L 공기 중 지름 5.0um 이상을 갖는 입자의 갯수를 나타냄
25 : 데이터11 하위 8비트
26 : 데이터12 상위 8비트 : 0.1L 공기 중 지름 10um 이상을 갖는 입자의 갯수를 나타냄
27 : 데이터12 하위 8비트
30 : 데이터와 체크 상위 8비트 : check code = 스타트 캐릭터1+스타트 캐릭터2+...+데이터13 하위 8비트
31 : 데이터와 체크 하위 8비트
*데이터1~데이터3의 먼지 농도는 공장 환경 하에서 사용이 권장되기에 제외
<패시브 모드>
- 디폴트 보드 레이트 : 9600bps, 체크 비트 : none, 스탑 비트 : 1 비트
- 각 8비트(1바이트)의 값을 가지는 총 7개의 배열을 전송해야 한다
- 1,2번 배열의 값은 고정인 상태로 명령어(CMD)와 그 명령어에 따른 필요 데이터를 데이터1,2 배열에 입력하고 Verify Byte 1,2에 Start Byte1~Data2 까지의 배열의 합을 상위, 하위 각 8비트 나누어 저장한 뒤 센서에 전송
- 패시브 모드에서 센서로부터 읽어오는 데이터의 크기와 내용은 액티브 모드와 동일하다
스타트 바이트 1 : 0x42 (고정)
스타트 바이트 2 : 0x4d (고정)
명령어 : CMD
데이터1 : 데이터 H
데이터2 : 데이터 L
확인 바이트1 : LRC H 확인 바이트를 제외한 모든 바이트 합 (상위8비트)
확인 바이트2 : LRC L (하위 8비트)
명령어
0xe2 : 패시브 모드에서 읽기, 32바이트 응답
0xe1 : 00 - Passive 모드로 변경
: 01 - Active 모드로 변경
0xe4 : 00 - Sleep 설정
: 01 - Wakeup 설정
[코드]
<Active, Passive 모드 공통>
- 전역변수
/* USER CODE BEGIN PV */
uint8_t pms7003_Buffer[32]; //PMS7003 센서로부터 받아 올 32바이트 데이터를 저장하는 배열
uint8_t pms7003_send_buffer[7]={0x42,0x4d,0}; //센서에 명령어를 보내는데 사용될 데이터 배열
/* USER CODE END PV */
- 측정값을 수신할 데이터 배열, 명령어를 전송할 배열 및 사용에 필요한 변수 선언
- 센서로부터 읽어 온 데이터를 출력하는 함수
void print_PMS7003(void)
{
/*
combine_value : 8비트씩 나눠진 데이터를 하나로 합치는데 사용할 변수
check_byte_receive : 센서로부터 수신 받은 체크 코드(30,31)를 하나로 합치는데 사용할 변수
check_byte_calculate : 센서로부터 수신 받은 0~29 까지의 데이터를 합산해 체크 코드와 비교
*/
uint16_t combine_value, check_byte_receive, check_byte_calculate=0;
check_byte_receive=pms7003_Buffer[30]<<8|pms7003_Buffer[31];
for(uint8_t i=0;i<30;i++)
{
check_byte_calculate+=pms7003_Buffer[i];
}
//센서로부터 수신 받은 체크 코드(30,31)와 수신 받은 0~29까지의 데이터 합산이 일치할 때만 출력
if(check_byte_receive==check_byte_calculate)
{
printf("PM1.0 : %d ",(combine_value=(pms7003_Buffer[10]<<8)|pms7003_Buffer[11]));
printf("PM2.5 : %d ",(combine_value=(pms7003_Buffer[12]<<8)|pms7003_Buffer[13]));
printf("PM10 : %d ",(combine_value=(pms7003_Buffer[14]<<8)|pms7003_Buffer[15]));
printf("0.3um : %d ",(combine_value=(pms7003_Buffer[16]<<8)|pms7003_Buffer[17]));
printf("0.5um : %d ",(combine_value=(pms7003_Buffer[18]<<8)|pms7003_Buffer[19]));
printf("1.0um : %d ",(combine_value=(pms7003_Buffer[20]<<8)|pms7003_Buffer[21]));
printf("2.5um : %d ",(combine_value=(pms7003_Buffer[22]<<8)|pms7003_Buffer[23]));
printf("5.0um : %d ",(combine_value=(pms7003_Buffer[24]<<8)|pms7003_Buffer[25]));
printf("10.0um : %d\n",(combine_value=(pms7003_Buffer[26]<<8)|pms7003_Buffer[27]));
}
else //체크 코드가 일치하지 않는 경우
{
}
}
- 상, 하나로 나뉘어 저장된 데이터(각 8bit)를 하나로 합친 뒤(16bit) 출력
- 명령어 전송 함수
void write_PMS7003(char* cmd)
{
//1~5번째까지의 배열의 합을 필요로 하는 6, 7번째 배열인 verify byte1, 2에 값을 저장하기 위해 사용할 변수
uint16_t verify_byte=0;
//사용자가 입력한 명령어(문자열)을 비교하여 해당하는 명령어에 따라
//센서로 전송할 데이터 배열을 채운다
if(strcmp(cmd,"Read")==0)
{
pms7003_send_buffer[2]=0xe2;
pms7003_send_buffer[3]=0x00;
pms7003_send_buffer[4]=0x00;
}
else if(strcmp(cmd,"Passive")==0)
{
pms7003_send_buffer[2]=0xe1;
pms7003_send_buffer[3]=0x00;
pms7003_send_buffer[4]=0x00;
}
else if(strcmp(cmd,"Active")==0)
{
pms7003_send_buffer[2]=0xe1;
pms7003_send_buffer[3]=0x00;
pms7003_send_buffer[4]=0x01;
}
else if(strcmp(cmd,"Sleep")==0)
{
pms7003_send_buffer[2]=0xe4;
pms7003_send_buffer[3]=0x00;
pms7003_send_buffer[4]=0x00;
}
else if(strcmp(cmd,"WakeUp")==0)
{
pms7003_send_buffer[2]=0xe4;
pms7003_send_buffer[3]=0x00;
pms7003_send_buffer[4]=0x01;
}
for(uint8_t i=0;i<5;i++)
{
verify_byte+=pms7003_send_buffer[i]; //verify byte1,2=배열 1~5(buffer[0]~[4])까지의 합
}
pms7003_send_buffer[5]=verify_byte>>8; //배열 1~5까지의 합을 상하로 나누어 저장
pms7003_send_buffer[6]=verify_byte;
//센서와의 통신에 사용할 UART 라인이 준비 상태가 될 때까지 대기
while(HAL_UART_GetState(&hlpuart1)!=HAL_UART_STATE_READY)
{
}
//인터럽트를 이용해 센서에 명령어 전송
if(HAL_UART_Transmit_IT(&hlpuart1,(uint8_t*)pms7003_send_buffer,7)!=HAL_OK)
{
}
//만약 사용자가 입력한 명령어가 Read라면 센서로부터 측정 데이터를 받아오는 함수를 실행한다
if(strcmp(cmd,"Read")==0)
{
//UART가 준비 상태가 될 때까지 대기
while(HAL_UART_GetState(&hlpuart1)!=HAL_UART_STATE_READY)
{
}
//인터럽트를 통해 센서로부터 데이터 수신
if(HAL_UART_Receive_IT(&hlpuart1,pms7003_Buffer, 32)!=HAL_OK)
{
}
}
}
- 센서에 명령을 내리기 위한 데이터 배열을 전송하는 함수
- 1,2번째 배열은 각 0x42, 0x4d로 고정값을 갖고 6,7번째 배열엔 1~4번째 배열의 합이 상,하 8비트로 나뉘어 저장되어야 한다
- 명령어가 "Read"일 경우, 마지막에 인터럽트를 이용한 UART 수신을 통해 센서로부터 데이터를 받아온다 (패시브 모드에서 수신하는 데이터도 액티브 모드와 동일한 데이터 순서, 길이를 갖는다)
<Active Mode>
- 메인 함수
int main(void)
{
...
/* USER CODE BEGIN 2 */
if(HAL_UART_Receive_DMA(&hlpuart1,pms7003_Buffer,32)!=HAL_OK)
{
//Execution fail
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
- DMA를 통한 UART 수신 함수 실행
- UART DMA - Rx를 Circular 모드로 설정했음으로 센서가 데이터를 전송할 때마다 수신이 자동 시작된다
- 수신 완료 후 호출되는 UART 콜백 함수
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==LPUART1)
{
print_PMS7003();
}
}
- USER CODE 4 부분은 main.c 내 하단에 위치
- UART 데이터 수신이 완료되면 HAL_UART_RxCpltCallback() 함수가 자동으로 호출된다
- 콜백 함수는 자동 생성되지 않으므로 main.c 내에 직접 작성할 필요가 있다
- 결과
사용 코드 : https://github.com/lee35210/STM32WB_PMS7003/tree/active
<Passive Mode>
- 타이머 초기화 함수
static void MX_TIM16_Init(void)
{
/* USER CODE BEGIN TIM16_Init 0 */
/* USER CODE END TIM16_Init 0 */
/* USER CODE BEGIN TIM16_Init 1 */
/* USER CODE END TIM16_Init 1 */
htim16.Instance = TIM16;
htim16.Init.Prescaler = 32000-1;
htim16.Init.CounterMode = TIM_COUNTERMODE_UP;
htim16.Init.Period = 5000-1;
htim16.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim16.Init.RepetitionCounter = 0;
htim16.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim16) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM16_Init 2 */
HAL_TIM_Base_Start_IT(&htim16);
/* USER CODE END TIM16_Init 2 */
}
- CUBEMX main.c 하단에 자동 생성되는 타이머 초기화 함수
- 함수 내 하단 사용자 코드란에 HAL_TIM_Base_Start_IT(&타이머 이름) 을 입력해야 타이머 인터럽트가 시작된다
- 메인 함수
int main(void)
{
...
/* USER CODE BEGIN 2 */
//PMS7003 Passive Mode로 설정
write_PMS7003("Passive");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/* USER CODE BEGIN 4 */
/**
* @brief Tx Transfer completed callback
* @param huart: UART handle.
* @note This example shows a simple way to report end of DMA Tx transfer, and
* you can add your own implementation.
* @retval None
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==LPUART1)
{
}
}
/**
* @brief Rx Transfer completed callback
* @param UartHandle: UART handle
* @note This example shows a simple way to report end of IT Rx transfer, and
* you can add your own implementation.
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==LPUART1)
{
print_PMS7003();
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM16)
{
write_PMS7003("Read");
}
}
- 설정한 타이머 주기마다 HAL_TIM_PeriodElapsedCallback() 함수가 호출되고 PMS7003에 Read 명령어를 전송한다
- 각종 명령어를 전송한 직후에 HAL_UART_TxCpltCallback() 함수가 호출된다
- PMS7003으로부터 데이터 수신 완료 후, HAL_UART_RxCpltCallback() 함수가 호출되고 수신한 데이터를 출력한다
- 결과
사용 코드 : https://github.com/lee35210/STM32WB_PMS7003/tree/passive
<Sleep, Wakeup>
- 슬립 모드에선 팬의 작동이 정지되고 센서로부터의 데이터 수신이 불가능해진다
- 예시
int main(void)
{
...
/* USER CODE BEGIN 2 */
uint8_t sleep_mode=0; (슬립 모드 확인하는데 사용할 변수, 1=슬립 모드 상태)
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if((receive_complete==0)&&(sleep_mode==0)) //슬립 모드 상태일 때는 데이터 수신 실행 X
{
while(HAL_UART_GetState(&hlpuart1)!=HAL_UART_STATE_READY)
{
}
HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)pms7003_Buffer,32);
}
if(receive_complete==1)
{
print_PMS7003();
}
if(30<i&&i<40)
{
if(sleep_mode==0)
{
printf("Sleep\r\n");
write_PMS7003("Sleep");
sleep_mode=1;
}
}
else if(40<=i)
{
printf("WakeUp\r\n");
write_PMS7003("WakeUp");
sleep_mode=0;
i=0;
}
i++;
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
- 주기적으로 슬립, 웨이크업 반복
- 슬립 모드 직후, 센서로부터 받아 온 데이터에는 비어있는 부분들이 존재하지만 센서 작동 시간이 늘어감에 따라 측정값들이 채워지고 안정화되어 간다
'STM32' 카테고리의 다른 글
STM32 - SPI통신 MAX7219 7-segment 모듈 (2) | 2019.12.25 |
---|---|
STM32 - I2C 통신 MLX90614 적외선 비접촉 온도 측정 센서 (2) | 2019.12.10 |
P-NUCLEO-WB55 보드 무선 통신 사용 설정 (1) | 2019.10.16 |
STM32 - UART 통신 기반 SenseAir S8 LP CO2 감지 센서 (0) | 2019.10.11 |
STM32 - I2C 통신을 이용한 SSD1306 128x64 OLED 제어 (4) | 2019.09.17 |