Nocuta NF-A8 5V PWM

- 보드 : P-NUCLEO-WB55

- 개발 툴 : STM32CubeMX, True Studio

- 팬 : 녹투아 NF-A8 5V PWM

- 팬 작동 전압 : 5V 

- 팬 소비 전력 : 0.75W

- 팬 Logic Level : PWM (Input) - Logic Low 최대 전압 0.8V / Logic High 최대 전압 5.25V

- 팬 PWM 주파수 : 25kHz (허용범위 21kHz ~ 28kHz)

- 팬 듀티 사이클 : 0 ~ 100%

- 팬 전원 : 18650 배터리

- 부스트 컨버터 : TI TPS61322 (18650 (2.7V~4.2V) -> TPS61322 (5V) -> NF-A8 5V PWM)

- 팬 제어 참고 자료

https://noctua.at/pub/media/wysiwyg/Noctua_PWM_specifications_white_paper.pdf

https://www.youtube.com/watch?v=gKHww3qJbs8


[연결]

[작동 방식]

<PWM>

Noctua PWM specifications white paper - PWM

- PWM을 이용해 팬 속도 조절

- 팬 속도는 듀티 사이클에 비례 (0~100%)

- 팬의 PWM 핀에 입력되는 PWM 신호가 0 V일 때 (듀티 사이클 0 %), 팬이 정지하고

- PWM 신호가 5 V일 때 (듀티 사이클 100%), 팬은 최고 속도로 회전한다

- 한 번의 PWM 신호 입력으로 팬 RPM이 고정되는 것이 아니라 지속적으로 PWM 신호를 입력해줘야 한다

 

<RPM>

Noctua PWM specifications white paper - RPM

- 팬의 Tachometer 출력 신호는 팬의 현재 RPM을 알아내는데 사용

- 출력 신호는 Hz 이고 팬 속도는 일반적으로 RPM (Revolutions Per Minute)으로 표기된다

- RPM으로 변환하기 위해 출력 신호를 60배 증가 시키고 팬이 회전당 두 개의 임펄스를 출력하므로 2로 나누어 준다

- fan speed [RPM] = frequency [Hz] * 60 / 2

- 86 [Hz] * 60 / 2 = 2580 [rpm]

Noctua PWM specifications white paper - Tachometer 핀 연결 설정

- 녹투아 팬에서 Tachometer 신호를 읽어들이기 위해선 위의 그림과 같은 연결이 필요하다

- 5V 팬을 사용하므로 Link에 5mA 미만의 전류가 흐르게 해야한다

- 그림과 다르게 Vcc 5V를 사용하고 1.2k 저항을 사용한 결과 정상 작동 확인


[CubeMX 설정]

<PWM 설정>

PWM 설정

- PWM 출력에 사용되는 클럭은 해당 타이머가 속하는 APB의 타이머 클럭을 기반으로 한다

- (위 설정의 경우, TIM2는 APB1에 속하고 APB1의 타이머 클럭은 32MHz 이다)

- PWM 주파수는 Timer Clock / (Prescaler+1)(Counter Period+1) 로 결정된다

- (Prescaler, Counter Period 는 실제 주파수 계산 때 +1이 되므로 원하는 값에 -1을 함)

- 따라서 위 설정에서 PWM 주파수는 32 Mhz / ((10-1)+1)((128-1)+1) = 25kHz 가 된다

- 듀티 사이클 (%) =  (사용자 입력값 / Counter Period) * 100

- Pulse : Counter Compare Register (CCR) 초기화 값

- Counter Mode : 카운트 증가 방향

- PWM mode 1 : 업카운팅 모드에서 period (CNT) < pulse (CCR)일 때, 타이머 채널 활성화 이외엔 비활성화

- 카운터 모드가 업이므로 timer counter register (CNT)는 1씩 증가하고 pulse (CCR) 값보다 작을 때는 타이머 채널이 활성화(CH Polarity : High 이므로 High 출력) pulse 값 이상일 땐 비활성화된다

- period (CNT)는 1씩 증가하다 Counter Period(AutoReload Register(ARR))+1 을 초과할 때 0이 된다

- (위의 설정에선 128을 넘어서면 다시 0부터 1씩 증가)

 

<Tachometer Input 핀 설정>

Tachometer 핀에 연결될 GPIO 핀 설정

- Tachometer 신호를 입력받는데 사용할 GPIO 핀을 GPIO_Input 으로 설정한다 (사용하지 않는 핀들 중에 선택)

 

<타이머 설정 (PWM 측정용)>

- PWM 실시간 측정용 타이머 설정

- 타이머 주기 = 32 MHz / {((32000-1)+1)*((1000-1)+1)} = 1 sec

- (사용 타이머가 속하는 APBx 타이머 클럭 (MHz) / {(Prescaler+1)(Counter Period+1)})

 

<버튼 설정 (필수 X, 듀티 사이클 조절하는데 사용)>

보드 탑재 버튼 설정

- 보드에 탑재되어있는 버튼들을 사용해 PWM 듀티 사이클 조절

- 사용할 버튼을 GPIO_EXTIx 로 변경해 인터럽트 모드 사용

- 풀업 설정 및 폴링 엣지에서 인터럽트가 발생하게 설정

- (내부 풀업으로 인해 버튼에 연결된 핀은 High 상태를 유지하다 버튼이 눌리는 순간 Low로 바뀐다)

 

<인터럽트 우선 순위 설정>

- SysTick 핸들러 내에서 증가하는 카운터에 의존하는 모든 딜레이와 타임아웃 HAL 함수들을 다른 인터럽트 내에서 사용할 경우, SysTick 인터럽트 우선 순위를 다른 인터럽트 함수보다 높게 설정해야 한다 (낮은 순자)

- (우선 순위 안 바꿀 경우, 버튼 및 타이머 인터럽트 콜백에서 HAL_GetTick() 정상 작동 X)

- 기본적으로 우선 순위가 0으로 설정되므로 다른 인터럽트들의 우선 순위를 낮춰서 사용 (숫자 ↑ 우선 순위 ↓)

- (https://stackoverflow.com/questions/53899882/hal-delay-stuck-in-a-infinite-loop 참고해 설정)

 

[코드]

<듀티 사이클 조절 함수 (버튼 인터럽트 콜백 함수)>

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	//count : 버튼이 눌려있던 시간을 저장하는 변수
	//lower_limit : 버튼이 눌러졌다고 인식되기 위해 필요한 최소 시간(ms)
	int count=0, lower_limit=80;
    
	//타이머-채널에 설정된 듀티 사이클을 저장하는 변수 선언 및 현재 듀티 사이클 읽어와서 저장
	uint8_t current_dutyCycle=HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);

	//사용자가 누른 버튼이 1번 버튼일 때
	if(GPIO_Pin==B1_Pin)
	{
		//버튼 디바운스 함수를 통해 사용자가 버튼을 누르고 있던 시간을 측정    
		count=button_debounce(B1_GPIO_Port,B1_Pin);

		//하한을 넘어가는 시간동안 버튼을 누르고 있었다면 듀티 사이클 조절 실행
		if(lower_limit < count)
		{
			if(10<=current_dutyCycle)
			{
				//현재 설정된 듀티 사이클을 저장한 변수에 -10값을 새로운 듀티 사이클로 설정
				__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,dutyCycle-10);
				//위의 HAL 드라이버 대신 직접 타이머-채널 레지스터에 접근해 듀티 사이클 설정도 가능
//				TIM2->CCR1-=10;
			}
			else
			{
				__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,0);
//				TIM2->CCR1=0;
			}
		}
	}
	//사용자가 누른 버튼이 2번 버튼일 때
	else if(GPIO_Pin==B2_Pin)
	{
		count=button_debounce(B2_GPIO_Port,B2_Pin);

		if(lower_limit < count)
		{
			if(120<=current_dutyCycle)
			{
				__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,128);
//				TIM2->CCR1=128;
			}
			else
			{
				__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,dutyCycle+10);
//				TIM2->CCR1+=10;
			}
		}
	}
    //사용자가 누른 버튼이 3번 버튼일 때
	else if(GPIO_Pin==B3_Pin)
	{
		count=button_debounce(B3_GPIO_Port,B3_Pin);
		if(lower_limit < count)
		{
			//RPM 측정 함수 호출        
			rpm_calculation(&htim2,TIM_CHANNEL_1);
		}
	}
	else
	{

	}
	printf("PWM : %ld\r\n",HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1));
//		printf("PWM : %ld\r\n",TIM2->CCR1);
}

- 버튼 입력을 통해 PWM 듀티 사이클을 조절하므로 HAL_GPIO_EXTI_Callback() (GPIO 인터럽트 콜백 함수) 사용

- HAL_TIM_ReadCapturedValue() 함수를 통해 변경하려는 PWM 채널의 현재 듀티 사이클 읽어옴

- ((&htim2, TIM_CHANNEL_1) : Timer 2의 채널 1)

- B1 (버튼1) : 듀티 사이클 감소, B2 (버튼2) : 듀티 사이클 증가, B2 (버튼3) : RPM 측정

- 콜백 함수가 호출되고 전달된 매개변수 GPIO_Pin을 통해 인터럽트가 발생한 GPIO 핀이 어떤 핀인지 확인

- if~else 문을 통해 핀에 따라 듀티 사이클 감소, 증가 or RPM 측정 실행

- button_debounce() 함수를 통해 버튼이 몇 ms동안 눌려졌는지 측정하고 그 값을 count 변수에 저장

- 설정한 하한(lower_limit 변수)을 넘어가는 시간만큼(ms, count변수) 버튼이 눌렸을 경우에만 듀티 사이클 조절 실행

- __HAL_TIM_SET_COMPARE() 함수를 통해 듀티 사이클을 변경하려는 타이머, 채널 및 듀티 사이클 값을 입력

- (HAL_TIM_ReadCapturedValue() 함수를 통해 읽어 온 현재 듀티 사이클 값을 기반으로 ±10 가감)

- 듀티 사이클 최대값은 CubeMX에서 설정한 Counter Period 값 +1 이다

- HAL_TIM_ReadCapturedValue(), __HAL_TIM_SET_COMPARE() 같은 HAL 드라이버를 사용하지 않고 TIMx->CCRn 로 직접 레지스터에 접근해 현재 설정된 듀티 사이클을 읽어오거나 설정할 수도 있다

 

<버튼 Debounce 함수>

/* @ 버튼 디바운스 함수
 *  gpio_port : 버튼과 연결된 GPIO 핀이 속한 GPIO Port
 *  gpio_pin : 버튼과 연결된 GPIO 핀
 */
int button_debounce(GPIO_TypeDef* gpio_port, uint16_t gpio_pin)
{
	//count : 버튼을 누르고 있던 시간을 저장할 변수
	//start_time, end_time : 버튼을 누르고 있던 시간을 측정하기 위해 사용할 변수
	int count, start_time, end_time;

	//버튼과 연결된 GPIO 핀 상태를 저장할 변수 선언 및 현재 핀 상태 저장
	GPIO_PinState pin_stat=HAL_GPIO_ReadPin(gpio_port,gpio_pin);

	//HAL_GetTick() 함수를 통해 측정 시작하는 시간을 start_time 변수에 저장
	start_time=HAL_GetTick();
	while(pin_stat==GPIO_PIN_RESET)
	{
		//버튼 GPIO 핀 상태 읽어와 저장    
		pin_stat=HAL_GPIO_ReadPin(gpio_port,gpio_pin);
	}
	//버튼에서 손을 떼 측정이 종료된 시간을 end_time 변수에 저장
	end_time=HAL_GetTick();
    
	//종료 시간-시작 시간으로 버튼이 눌려있던 시간을 계산해 저장 후 반환
	count=end_time-start_time;
	return count;
}

- 버튼 디바운스 함수

- Nordic사의 App button 에서 따옴

- HAL_GetTick() 함수를 사용해 버튼이 눌린 시각과 버튼에서 손을 뗀 시각을 저장한 후 이를 이용해 버튼이 눌려져있던 시간을 측정

- 버튼 인터럽트 발생 직후에 호출되어 버튼에 연결된 GPIO 핀이 RESET(Low) 상태일 동안 while()문이 반복

- GPIO 핀이 SET(High) 상태가 되면 while()문 종료 및 눌린 상태로 존재하던 시간 계산 후 반환

 

<RPM 측정 함수>

void rpm_calculation(TIM_HandleTypeDef *tim_pwm, uint32_t tim_channel)
{
	//start_time : 측정 시작 시간 저장
	//end_time : 측정 종료 시간 저장
	int start_time=0, end_time=0, rpm=0;
	//측정된 주기를 바탕으로 계산한 주파수를 저장할 변수
	long double freq=0;

	//현재 듀티 사이클을 읽어와 저장
	uint8_t current_dutyCycle=HAL_TIM_ReadCapturedValue(tim_pwm, tim_channel);

	if(current_dutyCycle==0)
	{
		printf("RPM : 0\r\n");
		return;
	}

	GPIO_PinState pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);

	//현재 Tachometer 핀이 Low 상태일 때
	if(HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin)==GPIO_PIN_RESET)
	{
		while(pin_stat==GPIO_PIN_RESET)
		{
			pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);
		}
		//측정 시작 시간 저장
		start_time=HAL_GetTick();
		while(pin_stat==GPIO_PIN_SET)
		{
			pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);
		}
		while(pin_stat==GPIO_PIN_RESET)
		{
			pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);
		}
		//측정 종료 시간 저장
		end_time=HAL_GetTick();
	}
	//현재 Tachometer 핀이 High 상태일 때
	else if(HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin)==GPIO_PIN_SET)
	{
		while(pin_stat==GPIO_PIN_SET)
		{
			pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);
		}
		//측정 시작 시간 저장
		start_time=HAL_GetTick();
		while(pin_stat==GPIO_PIN_RESET)
		{
			pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);
		}
		while(pin_stat==GPIO_PIN_SET)
		{
			pin_stat=HAL_GPIO_ReadPin(FAN_IN_GPIO_Port,FAN_IN_Pin);
		}
		//측정 종료 시간 저장
		end_time=HAL_GetTick();
	}
	else
	{
    
	}
	//측정된 주기를 바탕으로 주파수 계산 (측정된 시간은 ms 단위이므로 *1000)
	freq=(1.0/(end_time-start_time))*1000;
	printf("Freq : %Lf\r\n",freq);
    
	//변환된 주파수를 주어진 RPM 계산 공식에 대입해 현재 RPM 계산
	rpm=(int)((freq*60.0/2.0));
	printf("RPM : %d\r\n",rpm);
}

- 팬의 Tachometer 핀에 연결된 GPIO 핀 (Input)의 상태를 측정해 RPM 계산

- 먼저 현재 듀티 사이클을 읽어와 0일 경우, 함수 종료 (RPM이 0일 때, Tachometer 핀은 Low 고정이므로)

- 팬 Tachometer 핀에 연결된 GPIO 핀 (Input)의 현재 상태를 측정한 뒤 두 가지 상황으로 나누어 진행

- 1. Tachometer 핀이 Reset (Low) 상태일 때

   - Tachometer 핀이 Low 상태 중간부터 측정이 시작됐다고 가정해 일단 Low 상태가 끝날 때까지 while()문으로 대기

   - 핀이 High로 전환되어 첫번째 while()문이 종료된 직후, HAL_GetTick() 함수를 사용해 Low 상태가 끝나고

     High 상태가 시작되는 시간을 start_time 변수에 저장 (ms 단위)

   - while()문을 통해 Tachometer 핀 High -> Low 한 주기가 지난 뒤, HAL_GetTick() 를 사용해 end_time 변수에

     한 주기가 끝나는 시간을 저장

- 2. Tachometer 핀이 Set (High) 상태일 때

   - Tachometer 핀이 High 상태 중간부터 측정이 시작됐다고 가정해 일단 High 상태가 끝날 때까지 while()문으로 대기

   - 핀이 Low로 전환되어 첫번째 while()문이 종료된 직후, HAL_GetTick() 함수를 사용해 High 상태가 끝나고

     Low 상태가 시작되는 시간을 start_time 변수에 저장 (ms 단위)

   - while()문을 통해 Tachometer 핀 Low -> High 한 주기가 지난 뒤, HAL_GetTick() 를 사용해 end_time 변수에

     한 주기가 끝나는 시간을 저장

- end_time-start_time을 통해 한 주기를 계산하고 이 값을 주파수로 변환한 뒤(f=1/T) 주어진 공식을 사용해 RPM 계산

 

<타이머 콜백 함수>

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	rpm_calculation(&htim2,TIM_CHANNEL_1);
}

- 타이머 인터럽트가 발생됐을 때 실행되는 콜백 함수

- rpm_calculation() 함수를 사용해 설정한 시간 간격(1초)마다 현재 팬의 RPM 측정

 

<main 함수>

int main(void)
{
  ...
  /* USER CODE BEGIN 2 */

  printf("Start\r\n");
  HAL_TIM_Base_Start_IT(&htim16);
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

  /* USER CODE END 2 */
  ...
  while(1)
  {
  ...
  }
}

- 타이머 인터럽트(PWM 측정용으로 사용할) 시작 함수와 PWM 시작 함수 실행

 

<작동 결과>

- (세 번째 스크린샷의 dutycycle 표기는 오기입. 듀티사이클이 아닌 Counter Compare Register (CCR) 값)

[작동 테스트]

- 사용한 녹투아 NF-A8 5V PWM 의 경우, 듀티 사이클 0에서 정지, 듀티 사이클 6.25% (8/128)에서 팬 작동 시작

- 이때 측정되는 RPM은 192~200 RPM 사이, 듀티 사이클 10.15% (13/128)까지 동일 범위의 RPM 유지

 

<듀티 사이클 변화에 따른 RPM 변화 과정>

듀티 사이클 증가 직후, Tachometer 신호 변화 추이

- (위에서부터 각각 27.65ms / 26.75ms / 25.27ms 주기)
- 듀티 사이클 변경 직후, RPM이 해당 듀티 사이클에 해당하는 RPM으로 변화하기 전까지는 조금 시간이 걸린다

- (RPM 변경 완료까지 대략 18주기 정도 걸리는 것으로 측정)

 

<듀티 사이클 99%, 100%>

듀티 사이클 99.22% 신호 (127/128)
듀티 사이클 99.22% 에서의 Tachometer 신호
듀티 사이클 100%에서의 듀티 사이클 (128/128) 및 Tachometer 신호

- Counter Compare Register (CCR) 최대값(또한, Timer Counter Register(CNT)의 최대값)은 CubeMX 타이머 PWM에서 설정한 Counter Period (AutoReload Register) 값+1이다

- (Counter Period (AutoReload Register) = 128-1 로 설정된 상태)

- (위 두 그래프 CCR1=127, 마지막 그래프 CCR1=128인 상태)

 

<고정 듀티 사이클에서 RPM 변화>

- 듀티 사이클이 고정된 상태라도 RPM은 완벽히 고정되지 않고 일정 간격 내에서 계속 변화한다

+ Recent posts