이전 포스팅이 궁금하다면?
개발환경 구성부터 LED 제어 스위치 입력 제어까지 다뤄봤다. (후 블로그 쓰는 게 쉬운 일이 아니네 ㅎㅎ) 이번 포스팅에서는 UART를 사용해 볼 거다. LED 제어, 스위치 입력 UART 순서로 포스팅을 하는 이유는 이 3가지를 원활하게 다룰 줄 알아야 우리가 사용하고자 하는 센서들의 상태를 눈으로 확인하기 편하기 때문이다. 예를 들어 온습도 센서를 동작시킨다고 가정해 보자. 센서를 동작시키는 코드를 모두 작성하고 동작까지 성공했다면 온도와 습도 값을 어떻게 사람의 눈으로 확인할 것인가? 이때 UART를 이용해서 센서에서 감지한 온도와 습도 값을 실시간으로 PC에 전송해 확인할 수 있다.
UART란?
UART(Universal Asynchronous Receiver/Transmitter), 범용 비 동기화 송수신기다. 1:1 양방향 통신이 가능하고 보통 RS-232, RS-485, RS-422로 변환되어 사용된다. 자세한 내용은 아래 위키백과 참조하기 바란다.(사실 위키백과도 별 내용이 없다.)
https://ko.wikipedia.org/wiki/UART
그럼 바로 구현에 들어가 보자.
STM에서 UART를 구현하는 방법은 크게 2가지가 있다. HAL 드라이버를 사용하는 방법과 LL드라이버를 사용하는 방법. 이건 다른 기능도 마찬가지다. HAL드라이버는 많이 들어봐서 아는데 LL드라이버? 이건 뭐지?
ST가 음.. 그러니까 CubeMX라는 걸 내놓기 전에는 각 MCU에 맞는 드라이버와 소스코드를 홈페이지에 제공을 했었다. 그때는 HAL과 같은 구조가 아니었다. 이때 사용된 드라이버와 유사한 것이 LL 드라이버다. Low Layer의 약자로 HAL 드라이버보다 가볍고 빠르다는 장점이 있다. 이런 장점은 하드웨어 간 이식성이 떨어진다는 단점이 되기도 한다. 흠.. HAL을 쓰던 LL을 쓰던 그건 본인이 시스템에 맞춰 선택하면 되고, 본 포스팅에서는 우선 두 가지 사용법 모두 포스팅하도록 하겠다.
순서로는 HAL 드라이버로 데이터 송신(Transmitter), HAL 드라이버로 데이터 수신(Receiver), LL 드라이버로 데이터 송신, LL 드라이버로 데이터 수신 이렇게 진행해 보도록 하겠다!
참고로 ST 개발 보드 거의 대부분은 보드 안에 ST-LINK를 포함하고 있다. USB로 보드와 PC만 연결하면 터미널 프로그램에 가상 컴포트가 잡히는 걸 확인할 수 있다.
이렇게, 즉 별도의 UART케이블을 연결하지 않아도 디버깅과 UART 기능을 모두 사용할 수 있다. 참. 편리하다! 그럼 HAL 드라이버로 데이터를 송신하는 방법부터 알아보도록 하겠다. 우선 하드웨어 구성 먼저 살펴보자.
CubeMX를 열어보면
위쪽에 PB7번과 PB6번이 UART rx, UART tx로 설정되어있는 걸 확인할 수 있다. 통신 속도와 설정도 확인해 보자. 왼쪽 메뉴에서 Connectivity -> USART1을 선택해 보자.
설정을 확인해 보면 통신속도인 Baud Rate는 115200 Bits/s, 데이터 길이는 8 bits, Parity는 None, Stop Bits는 1로 설정되어 있다. 이 부분은 일반적으로 설정된 값이기 때문에 특별히 건드릴 부분은 없고 속도를 조절하고 싶다면 Baud Rate 부분만 조절하면 된다. UART의 하드웨어 레벨의 프로토콜 관련해서는 따로 포스팅하도록 하겠다! 아무튼 여기서 중요한 건 속도가 115200 Bits/s라는 것, 속도가 맞지 않으면 데이터가 깨진다.
회로도에서 연결을 살펴보자.
이렇게 MCU와 ST-LINK의 회로에 UART가 연결되어 있는 걸 확인할 수 있다.
1. HAL 드라이버로 데이터 송신
HAL 드라이버를 이용한 데이터 송신은 간단하다. 먼저 application.c 파일에 uart 핸들러 변수인 huart1을 extern 한다.
그다음 오른쪽 그림의 12째 줄과 같이 보내고자 하는 데이터를 만들어준다. 14번째 줄에서 HAL_UART_Transmit() 함수를 호출해 전달 인자 값만 채워주면 된다.
함수 원형을 살펴보면 uart 핸들러 변수, 전송하고자 하는 데이터, 데이터의 사이즈, 데이터 전송 타임아웃 값 순서로 매개변수가 선언되어 있다. 여기에 원하는 데이터를 넣어주면 된다.
uart핸들러 부분에 &huart1을 넣어주고 문자열 str을 uint8_t* 형 변환해 넣어주고, 문자열의 길이 값이 9(strlen(str)을 사용해도 됨.), 마지막으로 타임아웃 값을 1000(1초)을 넣어줬다.
실행해 보자. HAL_Delay(1000)을 코드 16번째 줄에 추가해서 1초에 한번 전송하도록 했다. 터미널은 테라텀을 사용했다.
2. LL 드라이버로 데이터 송신
LL 드라이버를 이용한 데이터 송신은 CubeMX에서 일부 설정을 변경해줘야 한다. 상단의 Project Manager로 들어간다.
여기서 왼쪽 하단에 보이는 Advanced Settings으로 들어간다.
중간부에 Driver Selector가 보인다. 이 부분이 활성화된 기능의 Driver를 선택하는 부분이다 default는 모두 HAL도 되어있다. 여기서 LL로 변경해 주면 된다.
그리고 Ctrl+S를 눌러 저장을 하면 코드를 생성할 것인가를 묻는다. Yes를 누르면 끝. 다음에 뜨는 창도 Yes를 누르면 코드 창으로 다시 돌아간다.
왼쪽에 프로젝트 구성을 보면 LL 드라이버가 생성된 걸 확인할 수 있다.
그럼 이제 LL 드라이버로 아까랑 똑같이 "Hello ST\r\n" 문자열을 전송해 보자!
20번째 줄에 보이는 것과 같이 함수를 하나 만들어줘야 한다. 매개변수는 문자열이나 데이터를 받을 수 있는 포인터 변수로 만든다.
LL_USART_IsActivaFlag_TXE(USART1) 함수는 데이터 전송이 완료되었는지 확인하는 함수다. HAL 드라이버와는 다르게 LL 드라이버는 문자열 데이터를 한 번에 보내는 게 아니라 문자 하나씩을 보내는 방식이기 때문에 Tx 버퍼에 있는 데이터 전송을 완료했는지 확인하고 다음 데이터를 전송해야 한다. 이렇게 구성하면 위 동영상과 같은 동작을 보일 것이다.
여기까지 HAL과 LL 드라이버로 데이터를 송신하는 방법에 대해 알아봤다. 생각보다 어렵지 않다! 보내는 건... 데이터를 수신하는 게 좀 까다롭다. 데이터 수신을 알아보기 전에 printf를 이용해서 데이터를 송신하는 방법을 알아보자!
3. printf로 데이터 송신하기
HAL, LL 드라이버 모두 유사한 방법으로 구현한다.
먼저 printf를 사용하기 위해 C 표준 해더 파일 'stdio.h'를 추가하고 21번째 줄에 보이는 함수를 작성한다. 그리고 15번째 줄에 보이는 것과 같이 일반적으로 printf를 사용하는 방식으로 사용하면 된다.
CubeIDE 설정을 일부 변경해줘야 하는데 그건 아래 그림을 참고!
HAL 드라이버도 동일하다 int __io_putchar(int ch) 함수의 내용만 HAL 드라이버로 변경된다.
이렇게, 그럼 mcu에서 uart로 데이터를 전송하는 방법에 대해서는 다 알아본 것 같다. 이제부터 데이터를 수신하는 방법에 대해 알아보자!
4. LL 드라이버 데이터 수신
설정을 HAL로 바꿨다가 다시 LL로 바꾸기 귀찮아서 LL 드라이버 먼저 설명한다! 아.. 인터럽트 설정을 안 해놨네. 후 다시 CubeMX로 이동!
좌측에 Connectivity -> USART1에서 NVIC Settings를 누르고 아래 보이는 Enabled의 체크박스를 선택하고 Ctrl+S를 눌러 코드를 생성한다. 다시 코드 창으로 이동!
Core -> Src에 stm32 l4 xx_it.c파일로 이동해 코드를 내리다 보면 void USART1_IRQHandler(void) 함수가 보일 것이다. 외부에서 UART 데이터가 들어오면 인터럽트가 발생되면서 호출되는 함수다. 그럼 이제 코드를 작성해 보자.
우선 인터럽트를 enable 시켜줘야 한다. uart 수신 데이터를 저장할 배열을 하나 만들어주고, 카운터를 하나 만들어준다. 이건 필수! 그럼 이 두 변수를 stm32l4xx_it.c에 extern 한다.
여기까지 했으면 USART1_IRQHandler(void)로 이동!
225번째 줄에서 uart 데이터 수신 버퍼의 상태를 확인하는 변수를 호출해 상태를 확인한다. 수신된 데이터가 있으면 불러와서 uartBuff에 저장한다. uartCnt를 통해 들어온 순서대로 저장.(중간에 주석 처리한 코드 LL_USART_ClearFlag_RXNE(USART1) 은 활성화된 flag를 초기화시켜 주는 함수다. MCU HAL 드라이버에 따라 없는 경우도 있다. 그래서 주석 처리함)
동작을 통해 확인해 보자.
데이터가 차곡차곡 쌓이는 걸 확인할 수 있다! 문자열을 한 번에 보내도 순차적으로 들어오는 걸 확인할 수 있다. 그럼 이제 HAL 드라이버로 데이터를 수신해 보자.
5. HAL 드라이버에서 데이터 수신
HAL 드라이버에서 데이터 수신을 알아보자.(휴 다시 HAL로 변경 ㅎㅎ)
기존설정은 그대로 두고 드라이버만 HAL로 변경하면 된다.
HAL_UART_Receive_IT 함수를 호출하고 각 매개변수에 맞는 값을 전달한다. 아래와 같이.
이렇게 하고 gpio exti callback 함수를 만들어 준 것과 같이 callback 함수를 하나 선언해 준다.
callback 함수는 프로젝트의 Drivers->STM32L4xx_HAL_Driver->Src에 선언되어 있는 걸 확인할 수 있다. 위 그림처럼 코드를 작성한다. if(huart->Instance == USART1)은 여러 개의 usart 입력을 받을 경우 구분을 위해 작성한 코드다. 하나의 usart를 사용해도 필요한 부분이다. 문자열을 보내보자.
위 그림과 같이 10개의 문자를 갖는 문자열을 전송했다.
break가 걸리고 문자열이 들어온 걸 확인할 수 있다. HAL_UART_Receive_IT의 매개변수 Size에 전달한 값만큼 데이터가 들어와야 callback 함수가 호출된다. 이 말은 길이가 변하지 않는 데이터를 수신할 경우에는 상관없지만, 길이가 가변적으로 변하는 데이터를 수신할 때는 데이터 처리가 굉장히 까다로워진다. 그래서 보통 size를 1로 해서 하나의 데이터가 들어올 때마다 계속 callback이 걸리게 처리하는데 나는 개인적으로 이런 부분이 마음에 들지 않아 LL드라이버를 사용하거나, HAL드라이버를 사용하지 않고 직접 구현한다.
후 힘들었다. 여기까지 uart에 대해 알아봤다. 하.. 나중에 시간이 조금 생기면 DMA를 활용하는 방법도 포스팅해 보도록 하겠다.
- 끝 -
다음 포스팅이 궁긍하다면?
'Hardware&Firmware > STmicroelectronic(STM)' 카테고리의 다른 글
[STM32] 10. HTS221, 온습도 센서 제어하기(B-L475E-IOT01A1 개발보드 활용하기) (0) | 2022.04.12 |
---|---|
[STM32] 9. B-L475E-IOT01A1 개발보드 드라이버 활용방법(B-L475E-IOT01A1 개발보드 활용하기) (0) | 2022.04.11 |
[STM32] 7. Interrupt (B-L475E-IOT01A1 개발보드 활용하기) (0) | 2022.04.07 |
[STM32] 6. GPIO 제어하기-입력(B-L475E-IOT01A1 개발보드 활용하기) (17) | 2022.04.05 |
[STM32] 5. GPIO 제어하기-출력(B-L475E-IOT01A1 개발보드 활용하기) (0) | 2022.04.04 |