- 차저는 세가지 충전 단계를 갖는다 - 완방된 배터리를 복구하기 위한 프리차지, 벅 차지를 안전하게 공급하는 패스트차지 정전류,
안전하게 풀 용량에 도달하기 위한 볼티지 레귤레이션
- 차저는 온도 센싱 스탠다드, 오버 볼티지 프로텍션(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 계산
- SPI 통신 사용, 명령어를 전송해 디스플레이를 제어하거나 RGB 데이터를 전송해 디스플레이 표현
- Clock Polarity : 0 (SCL 비활성화 상태에서 LOW 유지)
- Clock Phase : 0 (SCL 첫번째 엣지에서 데이터 캡쳐, 두번째 엣지에서 출력)
- CPOL=0, CPHA=0 이므로 SCL의 Rising Edge에서 SDA 라인의 데이터가 캡쳐되고 Falling Edge에서 데이터 출력
- D/C : LOW일 때 전송되는 데이터는 명령어,
HIGH일 때 전송되는 데이터는 명령어 레지스터에 저장될 매개변수 (명령어와 연이어 전송될 경우)
or 디스플레이 데이터 램에 저장될 RGB 데이터 배열
- 데이터 8 비트씩, MSB부터 전송
- 전송할 데이터의 종류에 따라(명령어=0, 디스플레이 데이터/명령어 매개변수=1) D/C 라인을 Low or High로 설정한다
- C/S 라인이 LOW 상태가 된 후에 데이터 전송이 이루어져야만 한다
- 데이터 전송이 완료된 이후엔 C/S 라인을 다시 High로 설정한다
- ST7735에 데이터를 쓸 때 : SCL 주기 최소 66ns (15.15Mhz 이하)
- ST7735로부터 데이터를 읽을 때 SCL 주기 최소 150ns (6.67Mhz 이하)
<Color Depth>
- ST7735는 디스플레이를 표현하는데 있어서 4k (RGB 4-4-4 bit), 65k (RGB 5-6-5 bit), 262k (6-6-6 bit) 총 세가지 방식의 컬러 심도를 가지고 있다
- 전송한 RGB 데이터는 ST7735 디스플레이 데이터 램(132x162x18-bit 그래픽 타입 static 램)에 저장된다
- 전원 인가 직후 기본 값은 262k (18 bit per pixel)
- 기본 컬러 심도 설정은 18 bit이므로 12, 16bit 컬러 심도를 사용하기 위해선 먼저 컬러 심도 변경에 필요한 명령어와 명령어 매개변수를 전송해 컬러 심도 설정을 변경해야한다 (3Ah(명령어(COLMOD)) + D2~D0(매개변수) 총 2byte)
- 12 bit, 16 bit 포맷 둘 다 RGB 데이터 비트가 빈틈없이 연속적으로 이어진다
- 12 bit 포맷은 RGB(4-4-4)의 형태이므로 3byte가 두 픽셀의 RGB값을 가진다
- 16 bit 포맷은 RGB(5-6-5)의 형태이므로 2byte가 한 픽셀의 RGB값을 가진다
- 전원 인가 직후, 하드웨어 리셋시 기본적으로 설정되어 있는 컬러 심도는 18 bit RGB 6-6-6 형태
- 12 bit, 16 bit 컬러 심도와는 다르게 RGB 비트가 빈틈없이 연이어 전송되는 형태가 아니고 6bit 색상 데이터(R/G/B)+2bit로 구성된 1 byte 데이터 세 개(R+G+B)가 하나의 픽셀을 나타낸다
- 전송되는 RGB 각 1 byte의 MSB인 D7(Bit 7)부터 D2(Bit 2)까지 색상값을 입력하면 된다 (D1, D0 사용 X, 전송 O)
<어드레스 카운터>
- 어드레스 카운터는 읽기, 쓰기를 위한 디스플레이 데이터 램의 주소를 설정한다
- 데이터는 드라이버의 램 행렬(132x162x18-bit)에 픽셀 단위로 쓰여진다
- 램의 위치는 어드레스 포인터에 의해 지정된다
- 주소 범위는 X=0~131, Y=0~161 이고 범위 밖에 주소는 허용되지 않는다
- 램에 디스플레이 데이터를 쓰기 전에 데이터가 기록될 창(window)이 정의되어야 한다
- 창은 시작 주소를 지정하는 XS, YS와 마지막 주소를 지정하는 XE, YE 커맨드 레지스터를 통해 프로그래밍 할 수 있다
- 예를 들어, 전체 디스플레이 컨텐츠가 기록된다면 윈도우는 다음 값으로 정의 할 수 있다
XS=0, YS=0, XE=127, YE=161 (128x162)
- 수직 어드레싱 모드(MV=1), Y-주소는 각 바이트 이후에 증가하고 마지막 Y-주소(Y=YE) 이후에 Y는 YS로 랩 어라운드(어드레스 최대 번지 다음에 제로 번지가 연속함)하고, X는 다음 열을 어드레싱하기 위해 증가
- 수평 어드레싱 모드(V=0), X-주소는 각 바이트 이후에 증가하고 마지막 X-주소(X=XE) 이후에 X는 XS로 랩 어라운드하고 Y는 다음 행을 어드레싱하기 위해 증가한다
- 모든 마지막 주소(X=XE, Y=YE) 이후에 어드레스 포인터는 X=XS, Y=YS 주소로 랩 어라운드 한다
- 다양한 디스플레이 아키텍처를 다루기 위한 유연성을 위해 "CASET, RASET, MADCTL" 명령어는 X-주소, Y-주소의 반전을 허용하는 MX, MY 플래그를 정의한다
- MX, MY 그리고 MV(열<->행 교환)가 변경되면 데이터 버스트가 디스플레이 램에 다시 쓰여진다
[CUBEMX 설정]
- ST7735에 데이터를 전송하기만 하므로 Trasmit Only Master 로 설정 (Master MOSI -> ST7735 SDA)
- 데이터는 8 bit씩 MSB부터 전송
- 데이터시트 상 SCL Write 주기 최소값은 66ns (15.15Mhz 이하)
- 사용하는 SPI가 속하는 APB의 클럭 속도를 설정한 뒤, SPI 프리스케일러 설정을 통해 SCL 설정
- 16Mhz 설정의 경우, 62.5ns로 최소값 보다는 낮지만 정상 작동 확인
- CPOL=Low(=0), CPHA=1 Edge(=0)
- SCL은 비활성화시 Low를 유지하고 통신 시작시 SCL 첫번째 엣지(Rising)에서 SDA 라인의 데이터가 캡쳐되고 SCL의 두번째 엣지(Falling)에서 캡쳐된 데이터가 출력된다
- 인터럽트와 DMA를 활용해 데이터 전송
- C/S (Chip Select, =SS (Slave Select), D/C (Data/Command), Reset 핀 할당 및 Output 으로 설정
(사용하지 않는 GPIO 핀들 사용)
- C/S 핀은 SPI 통신 비활성화 상태에선 High 상태를 유지하고 통신 때만 Low로 전환해 사용
- D/C 핀은 명령어를 전송할 땐 Low 상태여야하고 명령어 매개변수 or 디스플레이 데이터를 전송할 땐 High 상태여야 한다
- Reset 핀은 H/W 리셋에 사용되는 핀으로 기본적으론 High 상태를 유지하고 있어야 한다
[코드]
(Write 기능들만 구현)
<초기화>
*기본 설정
- 전원 인가시 디스플레이는
- Sleep In 상태이므로 Sleep Out 명령어를 전송해 Sleep 모드에서 빠져나온다
- Display Off 상태이므로 Display On 명령어 전송해 디스플레이를 활성화 시킨다
- 행렬 시작 주소 (0, 0), 마지막 주소 (161, 131) 이므로 디스플레이 창은 (0~161, 0~131)이 된다
*화면 반전
- 사용한 LCD의 경우, 위 사진에서 (0, 0)으로 표시한 부분이 어드레스 포인터의 시작점이다 (행,열은 사진 방향과 동일)
- 80x160 해상도에서 80에 해당하는 부분은 26~105까지의 주소를 사용한다 (반전 설정 상관없이 26~105)
- 160에 해당하는 부분은 1~160까지의 주소를 사용한다 (디스플레이 해상도가 128x160이 아닌 132x162로 추정)
- 행과 열의 방향은 사진과 동일하고 RGB 데이터는 열부터 순차적으로 쓰여진다
- 어드레스 포인터는 열의 마지막 부분에서 열의 시작 지점으로 돌아가고 행 주소는 1 증가한다
- 오른쪽 사진처럼 사용하기 위해 Memory Data Access Control (MADCTL) 레지스터에서 MV 비트를 1로 설정해 행과 열을 서로 바꾸고 MX 비트를 1로 설정해 열 주소 순서를 반전시켰다
*RGB 출력 반전
- 사용한 LCD 모듈의 경우, Memory Data Access Control(MADCTL) 레지스터의 RGB 비트 설정이 0임에도 불구하고 BGR 순으로 반대로 디스플레이에 데이터가 표현됨. 따라서 MADCTL 레지스터의 RGB 순서 설정 비트를 1로 변경함으로 전송한 RGB 데이터 순서대로 디스플레이 출력이 되게끔 설정.
- RGB BIT 설정으로 인해 변경된 RGB 출력 순서는 좌우, 상하 반전을 하더라도 변하지 않는다
*디스플레이 반전
- 사용한 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
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 통신 비활성화)로 설정하고 전송에 사용된 디스플레이 데이터의 메모리 할당을 해제함