
UART(Universal Asynchronous Receiver/Transmitter)는 널리 사용되는 시리얼 통신 프로토콜입니다.
UART(Universal Asynchronous Receiver/Transmitter)는 피어 투 피어(Peer-to-Peer, P2P) 하드웨어 통신 프로토콜입니다.
이번 레슨에서는 UART 드라이버를 인터럽트 기반 방식으로 사용하는 방법을 배웁니다.
이 레슨의 실습 섹션에서는 UART를 통해 명령어를 전송하여 보드의 LED를 제어합니다.
비동기 모드(Interrupt-driven)로 UART를 통해 데이터를 송수신하는 방법을 학습합니다.
UART API를 사용하여 UART 주변 장치를 설정하는 방법을 학습합니다.
UART 드라이버 API를 사용하여 UART 기능을 실습하고 검토합니다.
TX (Transmit) : 데이터를 전송하는 핀
RX (Receive) : 데이터를 수신하는 핀
RTS (Request to Send) : 데이터를 송신할 준비가 되었음을 나타냄
CTS (Clear to Send) : 데이터를 전송해도 되는지 확인
UART는 간단하고 널리 사용되는 직렬 통신 프로토콜로, 다음과 같은 주요 특징을 가집니다:
시작/데이터/종료 비트를 통해 데이터를 직렬로 전송.
TX와 RX 핀의 크로스 커플링으로 데이터 송수신.
선택적으로 패리티 비트와 하드웨어 흐름 제어 사용 가능.
Zephyr에서 UART 주변 장치를 사용하는 방법은 세 가지가 있습니다:
prf.conf 파일에 다음을 추가
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y (비동기 API 사용을 위해 반드시 필요)
UART 드라이버를 사용하기 위해 다음 헤더 파일을 포함
#include <zephyr/drivers/uart.h>
DEVICE_DT_GET() 매크로를 사용하여 UART 디바이스 구조체를 가져옴
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0));
if (!device_is_ready(uart)) {
return;
}
UART 설정(보드레이트, 패리티 비트 등)은 정적(빌드 타임) 또는 동적(런타임)으로 변경 가능.
동적 설정을 위해 uart_config 구조체를 생성
const struct uart_config uart_cfg = {
.baudrate = 115200,
.parity = UART_CFG_PARITY_NONE,
.stop_bits = UART_CFG_STOP_BITS_1,
.data_bits = UART_CFG_DATA_BITS_8,
.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
};
설정을 적용하려면 uart_configure() 함수 호출
int err = uart_configure(uart, &uart_cfg);
if (err == -ENOSYS) {
return -ENOSYS;
}
UART 이벤트를 처리하기 위한 콜백 함수를 정의:
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data) {
switch (evt->type) {
case UART_TX_DONE:
// 데이터 전송 완료 처리
break;
case UART_RX_RDY:
// 데이터 수신 처리
break;
default:
break;
}
}
uart_callback_set() 함수를 사용해 콜백 함수 등록
int err = uart_callback_set(uart, uart_cb, NULL);
if (err) {
return err;
}
UART로 들어오는 데이터를 저장할 버퍼 선언
static uint8_t rx_buf[10] = {0}; // 10바이트 크기의 수신 버퍼
uart_rx_enable()를 호출해 수신 시작
uart_rx_enable(uart, rx_buf, sizeof(rx_buf), 100);
콜백 함수에서 UART_RX_RDY 이벤트를 통해 데이터 접근
evt->data.rx.len // 수신된 데이터 길이
evt->data.rx.offset // 버퍼 내 데이터가 저장된 위치
evt->rx.buf[offset] // 수신된 실제 데이터
수신 버퍼가 가득 차면 기본적으로 수신이 멈춤
UART_RX_DISABLED 이벤트에서 uart_rx_enable()을 다시 호출해 연속 수신 활성화
case UART_RX_DISABLED:
uart_rx_enable(dev, rx_buf, sizeof(rx_buf), 100);
break;
전송할 데이터를 저장할 버퍼 선언
static uint8_t tx_buf[] = {"nRF Connect SDK Fundamentals Course \n\r"};
uart_tx() 함수 호출
int err = uart_tx(uart, tx_buf, sizeof(tx_buf), SYS_FOREVER_US);
if (err) {
return err;
}
UART_TX_DONE 이벤트를 통해 전송 완료 후 작업 수행
case UART_TX_DONE:
// 필요한 작업 수행
break;
prj.conf
#
# Copyright (c) 2016 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
CONFIG_GPIO=y
# STEP 2 - Enable the serial driver in asynchronous mode
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y
CMake
#
# Copyright (c) 2016 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(nrf_connect_sdk_fundamentals)
target_sources(app PRIVATE src/main.c)
main.c
/*
* Intel Corporation의 저작권(c) 2016
*
* SPDX-License-Identifier: Apache-2.0
*/
/* UART를 통해 LED 제어. 키보드에서 1-3을 누르면 개발 키트의 LED 1-3이 전환됩니다. */
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
/* STEP 3 - main.c에 UART 드라이버 헤더 파일 포함 */
#include <zephyr/drivers/uart.h>
/* 1000밀리초 = 1초 */
#define SLEEP_TIME_MS 1000
/* STEP 10.1.1 - 수신 버퍼 크기 정의 */
#define RECEIVE_BUFF_SIZE 10
/* STEP 10.2 - 수신 타임아웃 기간 정의 */
#define RECEIVE_TIMEOUT 100
/* STEP 5.1 - gpio_dt_spec를 사용하여 LED의 디바이스 포인터 가져오기 */
/* nRF7002dk에는 LED가 2개만 있으므로, 사용하는 DK에 따라 컴파일 시간 조건을 반영합니다. */
#if defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP) || defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP_NS)
static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios);
#else
static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios);
static const struct gpio_dt_spec led2 = GPIO_DT_SPEC_GET(DT_ALIAS(led2), gpios);
#endif
/* STEP 4.1 - UART 하드웨어의 디바이스 포인터 가져오기 */
#if defined(CONFIG_BOARD_NRF54L15DK_NRF54L15_CPUAPP) || defined(CONFIG_BOARD_NRF54L15DK_NRF54L15_CPUAPP_NS)
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart20));
#else
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0));
#endif
/* STEP 9.1 - 전송 버퍼 정의, UART를 통해 전송될 데이터를 저장할 버퍼 */
static uint8_t tx_buf[] = {
#if defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP) || defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP_NS)
"nRF Connect SDK 기본 과정\r\n"
"개발 키트의 LED 1-2를 전환하려면 키보드에서 1-2를 누르세요.\r\n"};
#elif defined(CONFIG_BOARD_NRF54L15DK_NRF54L15_CPUAPP) || defined(CONFIG_BOARD_NRF54L15DK_NRF54L15_CPUAPP_NS)
"nRF Connect SDK 기본 과정\r\n"
"개발 키트의 LED 0-2를 전환하려면 키보드에서 0-2를 누르세요.\r\n"};
#else
"nRF Connect SDK 기본 과정\r\n"
"개발 키트의 LED 1-3을 전환하려면 키보드에서 1-3을 누르세요.\r\n"};
#endif
/* STEP 10.1.2 - 수신 버퍼 정의 */
static uint8_t rx_buf[RECEIVE_BUFF_SIZE] = {0};
/* STEP 7 - UART 콜백 함수 정의 */
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
switch (evt->type) {
case UART_RX_RDY:
#if defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP) || defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP_NS)
if ((evt->data.rx.len) == 1) {
if (evt->data.rx.buf[evt->data.rx.offset] == '1') {
gpio_pin_toggle_dt(&led0);
} else if (evt->data.rx.buf[evt->data.rx.offset] == '2') {
gpio_pin_toggle_dt(&led1);
}
}
#elif defined(CONFIG_BOARD_NRF54L15DK_NRF54L15_CPUAPP) || defined(CONFIG_BOARD_NRF54L15DK_NRF54L15_CPUAPP_NS)
if ((evt->data.rx.len) == 1) {
if (evt->data.rx.buf[evt->data.rx.offset] == '0') {
gpio_pin_toggle_dt(&led0);
} else if (evt->data.rx.buf[evt->data.rx.offset] == '1') {
gpio_pin_toggle_dt(&led1);
} else if (evt->data.rx.buf[evt->data.rx.offset] == '2') {
gpio_pin_toggle_dt(&led2);
}
}
#else
if ((evt->data.rx.len) == 1) {
if (evt->data.rx.buf[evt->data.rx.offset] == '1') {
gpio_pin_toggle_dt(&led0);
} else if (evt->data.rx.buf[evt->data.rx.offset] == '2') {
gpio_pin_toggle_dt(&led1);
} else if (evt->data.rx.buf[evt->data.rx.offset] == '3') {
gpio_pin_toggle_dt(&led2);
}
}
#endif
break;
case UART_RX_DISABLED:
uart_rx_enable(dev, rx_buf, sizeof rx_buf, RECEIVE_TIMEOUT);
break;
default:
break;
}
}
int main(void)
{
int ret;
/* STEP 4.2 - UART 디바이스가 준비되었는지 확인 */
if (!device_is_ready(uart)) {
printk("UART 디바이스가 준비되지 않았습니다.\n");
return 1;
}
/* STEP 5.2 - LED 디바이스가 준비되었는지 확인 */
if (!device_is_ready(led0.port)) {
printk("GPIO 디바이스가 준비되지 않았습니다.\n");
return 1;
}
/* STEP 6 - LED GPIO 구성 */
#if defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP) || \
defined(CONFIG_BOARD_NRF7002DK_NRF5340_CPUAPP_NS)
ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 1;
}
ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 1;
}
#else
ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 1;
}
ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 1;
}
ret = gpio_pin_configure_dt(&led2, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return 1;
}
#endif
/* STEP 8 - UART 콜백 함수 등록 */
ret = uart_callback_set(uart, uart_cb, NULL);
if (ret) {
return 1;
}
/* STEP 9.2 - uart_tx()를 호출하여 UART를 통해 데이터 전송 */
ret = uart_tx(uart, tx_buf, sizeof(tx_buf), SYS_FOREVER_MS);
if (ret) {
return 1;
}
/* STEP 10.3 - uart_rx_enable()을 호출하여 데이터 수신 시작 */
ret = uart_rx_enable(uart, rx_buf, sizeof rx_buf, RECEIVE_TIMEOUT);
if (ret) {
return 1;
}
while (1) {
k_msleep(SLEEP_TIME_MS);
}
}