TI bq21040 Charging Profile With Thermal Regulation (데이터시트 p.10)
- 차저는 세가지 충전 단계를 갖는다 - 완방된 배터리를 복구하기 위한 프리차지, 벅 차지를 안전하게 공급하는 패스트차지 정전류,
안전하게 풀 용량에 도달하기 위한 볼티지 레귤레이션
- 차저는 온도 센싱 스탠다드, 오버 볼티지 프로텍션(OVP), DPM-IN, 세이프티 타이머, ISET 쇼트 보호 등의 안전 기능 세트를 가지고 있다 - 만약 배터리 전압이 LOWV 쓰레스홀드(2.5V) 보다 낮다면 배터리는 방전된 것으로 여겨지고 프리컨디셔닝 사이클이 시작된다. 이 페이즈에서 배터리로 흘러가는 전류량은 프리 차지 전류라고 불리고 패스트 차지 전류의 20%로 고정되어 있다 - 배터리 전압이 VLOWV 쓰레스홀드까지 충전되면 패스트차지가 시작되고 패스트차지 전류가 적용된다
- 패스트차지 정전류는 ISET 터미널을 사용해 프로그램된다 - 충전의 대부분은 정전류에 의해 이뤄진다 - IC내의 전력 소모는 배터리 전압이 최저일 때 극대가 된다 - 만약 IC가 125℃에 도달해 IC가 써멀 레귤레이션에 들어가면 타이머 클럭이 절반으로 느려지고온도를 더 이상 상승시키지 않기 위해 필요한만큼 충전 전류를 줄인다 - 배터리 셀이 레귤레이션 전압만큼 충전되면 전압 루프 제어를 취하고 전류가 종단 쓰레스홀드에 가까워질 때까지 배터리를 레귤레이션 전압으로 유지한다 - 종단 전류는 패스트차지 전류의 10%로 설정된다 - CHG 터미널은 첫번째 충전 사이클일 때만 LOW (LED ON)이고 충전 전류에 대한 종단이 활성, 비활성이든 관계없이 종단 쓰레스홀드에 도달하면 턴 오프된다 - (데이터시트 p.8 내용 발췌)
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 계산
- 사용한 LCD 모듈의 경우, Memory Data Access Control(MADCTL) 레지스터의 RGB 비트 설정이 0임에도 불구하고 BGR 순으로 반대로 디스플레이에 데이터가 표현됨. 따라서 MADCTL 레지스터의 RGB 순서 설정 비트를 1로 변경함으로 전송한 RGB 데이터 순서대로 디스플레이 출력이 되게끔 설정.
- RGB BIT 설정으로 인해 변경된 RGB 출력 순서는 좌우, 상하 반전을 하더라도 변하지 않는다
*디스플레이 반전
Display Inversion On 명령어
RGB 픽셀 0xFC, 0x00, 0x00 전송, MADCTL RGB BIT=1 / (왼쪽) Inversion Off (오른쪽) Inversion On
- 사용한 LCD 모듈의 경우, Inversion Off 일 때 RGB값 0x00에서 최대 밝기, 0xFC에서 최소 밝기가 된다
- 예) R=0xFC, G=0x00, B=0x00 이라면 해당 픽셀은 Red=off / Green, Blue=최대 밝기가 된다
- Inversion On 명령어를 전송해 전송하는 R/G/B 값의 크기가 디스플레이 픽셀 밝기에 그대로 반영되게 한다
- st7735_write_cmd(), st7735_write_cmd_param() 함수를 ST7735에 명령어와 명령어+매개변수를 전송하는 기본 툴로 사용
- Sleep Out -> Display On -> Memory Data Access Control (행열 교환 및 열 방향 반전) -> Address Pointer 설정 (Window) -> Display Inversion On 순서로 ST7735 드라이버 초기화 (데이터 시트에 초기화 과정 없기에 임의 설정)
- 열과 행의 주소는 각각 2 byte의 길이를 가지는데 열과 행의 주소 둘 다 255를 넘어가지 않으므로 명령어 매개변수의 두번째, 네번째 배열에 원하는 주소를 입력해 전송
- st7735_write_cmd(), st7735_write_cmd_param() 함수는 우선적으로 SPI 사용 가능 때까지 while()문을 통해 대기
- SPI가 Ready 상태가 되면 CS(Low, SPI 통신 활성화) -> D/C (Data/Command) -> IT 통해 SPI 통신 개시 -> 통신 완료까지 while()문 사용해 대기 -> CS(High, SPI 통신 비활성화)
- 전달받은 문자열의 첫번째 문자부터 순차적으로 하나씩 st7735_write_char() 함수에 전달
- 매 전송 이후, 폰트 가로 너비만큼 열 주소를 증가
- 열의 남은 공간이 폰트의 가로 너비 보다 작을 경우 전송 중단
(필요한 경우 else 란에 row_start+폰트 높이, col_start=0 을 입력해 다음 행의 0번째 열부터 나머지 문자가 출력되게끔 설정)
*stm32wbxx_it.c
stm32wbxx_it.c 위치 (CUBEMX 기준)
void DMA1_Channel1_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
/* USER CODE END DMA1_Channel1_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi1_tx);
/* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
if(HAL_DMA_GetState(&hdma_spi1_tx)==HAL_DMA_STATE_READY)
{
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, SET);
free(hspi1.pTxBuffPtr);
}
/* USER CODE END DMA1_Channel1_IRQn 1 */
}
- stm32wbxx_it.c 소스 파일에 위치한 DMA 인터럽트 핸들러
- 사용한 SPI의 TX에 할당한 DMA 채널을 찾아 코드 입력
- DMA를 통한 전송이 완료되어 DMA 채널이 Ready 상태라면 CS 핀을 High (SPI 통신 비활성화)로 설정하고 전송에 사용된 디스플레이 데이터의 메모리 할당을 해제함