/*@ 20/05/26

 * - CUBEMX 설정 Active, Passive 모드 두 가지 상황으로 분리

 * - [동작] 부분 액티브, 패시브 모드에서의 동작 상황 설명 추가

 * - 액티브 모드 코드 및 설명 재수정 (타이머 인터럽트 제거)

 * - 패시브 모드 코드 및 설명 수정 (타이머 인터럽트 추가)

 *

 *@ 20/05/21 

 * - CUBE MX 타이머 인터럽트 설정 추가

 * - 타이머 인터럽트를 사용한 PMS7003 데이터 수신으로 변경

 * - 수신 완료 콜백 함수 코드 수정

 * - 타이머 부분 추가

 * - 액티브 모드 부분 코드 및 설명 수정

 * - 코드 링크 추가

 */

 

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 ㎍/㎥

[하드웨어 연결]

핀 설정
어댑터 보드
1.27mm / 2.00mm / 2.54mm 보드

주의점

  • 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
  • 인터럽트 활성화

UART 설정
인터럽트 설정

<Active Mode>

- UART DMA - Rx Circular 모드로 설정

- 타이머 인터럽트 사용 X

UART DMA Rx - Circular 모드로 설정 (Tx - Normal)

<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 */
}
  • 주기적으로 슬립, 웨이크업 반복

출력 결과

  • 슬립 모드 직후, 센서로부터 받아 온 데이터에는 비어있는 부분들이 존재하지만 센서 작동 시간이 늘어감에 따라 측정값들이 채워지고 안정화되어 간다

+ Recent posts