在本练习中,您将学习如何通过 UART 控制开发板上的 LED。我们将利用默认连接到 Nordic 开发板接口 MCU 的 UART 控制器,以实现通过 USB 提供 UART 通信。

我们将把 UART 设置为异步模式,以便在每次有新数据时调用回调函数。我们将接收缓冲区大小设置为 10 字节,接收超时设置为 100 微秒。这意味着当接收到一个字节且在 100 微秒后处于空闲状态(没有新数据),或者接收到 10 个字节时,将生成 UART_RX_RDY 事件。
在本练习中,我们将简单练习 UART 驱动程序 章节中涵盖的步骤,以异步方式与 UART 硬件进行交互。
此练习不支持 Thingy (Thingy:53, Thingy:91, Thingy:91 X)。
1. 在 本课程的 GitHub 仓库 中,打开此练习的基础代码,位于 l5/l5_e1。
此代码基于 blinky 示例。
2. 通过在 prj.conf 中添加以下两行来启用串行驱动程序(UART 驱动程序)。
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=yKconfig正如我们之前提到的,第一行通常通过板卡的设备树默认启用。然而,第二行对于启用串行驱动程序的异步 API 至关重要。
3. 在 main.c 中包含 UART 驱动程序的头文件。
#include <zephyr/drivers/uart.h>C4. 获取 UART 硬件的设备指针并验证其是否就绪。
4.1 获取 UART 硬件的设备指针。
请记住,在 nRF Connect SDK 中,任何外设都作为 struct device 类型的指针进行访问。
const struct device *uart= DEVICE_DT_GET(DT_NODELABEL(uart0));Cconst struct device *uart= DEVICE_DT_GET(DT_NODELABEL(uart30));Cconst struct device *uart= DEVICE_DT_GET(DT_NODELABEL(uart20));C在 nRF54 系列设备上,外设具有两位数字。在本练习中,我们将使用 &uart20 实例。
4.2 验证 UART 设备是否就绪。
if (!device_is_ready(uart)){
printk("UART device not ready\r\n");
return 1 ;
}C5. 通过 gpio_dt_spec 获取 LED 的设备指针并验证其是否就绪。
5.1 获取 LED 的设备指针
由于我们使用 UART 来控制开发板上的 LED (1-3)(在 nRF54 DK 上为 LED 0-2),我们也需要获取这些 LED 的设备指针。正如我们在第 2 课中看到的,这被封装在特定于 API 的结构 gpio_dt_spec 中,并且可以使用 GPIO_DT_SPEC_GET() 检索。
nRF7002 DK 只有 2 个 LED,因此如果您使用此板卡,此步骤需要不同的代码片段。
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);
Cstatic 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);C5.2 验证 LED 设备是否就绪。
由于所有 LED 都与同一个端口 &gpio0 相关联,我们只需要检查其中一个设备即可。
if (!device_is_ready(led0.port)){
printk("GPIO device is not ready\r\n");
return 1;
}C6. 配置 LED 的 GPIO
由于 nRF7002 DK 只有 2 个 LED,此步骤需要排除第三个 LED 的不同代码片段。
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 ;
}Cret = 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 ;
}C7. 定义 UART 的应用程序回调函数。对于我们简单的 LED 应用程序,我们只对与通过 UART 接收数据相关的 UART_RX_RDY 和 UART_RX_DISABLED 事件感兴趣。对于您自己的应用程序,这可能会有所不同,正如我们在 UART 驱动程序章节中讨论的那样。
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
switch (evt->type) {
case UART_RX_RDY:
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);
}
}
break;
case UART_RX_DISABLED:
uart_rx_enable(dev, rx_buf, sizeof rx_buf, RECEIVE_TIMEOUT);
break;
default:
break;
}
}Cstatic void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
switch (evt->type) {
case UART_RX_RDY:
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);
}
}
break;
case UART_RX_DISABLED:
uart_rx_enable(dev, rx_buf, sizeof rx_buf, RECEIVE_TIMEOUT);
break;
default:
break;
}
}C由于 nRF54 系列开发板上的 LED 是从 0 开始索引的,我们需要检查数字 ‘0’、’1′ 和 ‘2’。
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
switch (evt->type) {
case UART_RX_RDY:
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);
}
}
break;
case UART_RX_DISABLED:
uart_rx_enable(dev, rx_buf, sizeof rx_buf, RECEIVE_TIMEOUT);
break;
default:
break;
}
}C使用 if((evt->data.rx.len) == 1),我们假设终端处于字符模式,这是 nRF Connect for VS Code (nRF Terminal) 和 PuTTY 中的默认终端。这两个终端默认不需要按回车键发送数据,因此不会发送额外的字节。
另一方面,如果您使用的是“行模式”下的串口终端,则需要按回车键发送数据,这通常会发送两个额外的字节 “/r/n”(取决于终端配置)。因此,您需要更改条件以匹配您发送的数据长度。
8. 通过调用函数 uart_callback_set() 注册 UART 回调函数。
ret = uart_callback_set(uart, uart_cb, NULL);
if (ret) {
return 1;
}C9. 现在让我们通过 UART 发送一些数据。
9.1 定义传输缓冲区,这是一个用于保存要通过 UART 发送的数据的缓冲区。
static uint8_t tx_buf[] = {"nRF Connect SDK Fundamentals Course\r\n"
"Press 1-3 on your keyboard to toggle LEDS 1-3 on your development kit\r\n"};Cstatic uint8_t tx_buf[] = {"nRF Connect SDK Fundamentals Course\r\n"
"Press 1-2 on your keyboard to toggle LEDS 1-2 on your development kit\r\n"};Cstatic uint8_t tx_buf[] = {"nRF Connect SDK Fundamentals Course\r\n"
"Press 0-2 on your keyboard to toggle LEDS 0-2 on your development kit\r\n"};C9.2 通过调用 uart_tx() 通过 UART 发送数据。由于我们不使用流控制,请传递 SYS_FOREVER_US 以禁用传输超时。这与接收超时无关。
ret = uart_tx(uart, tx_buf, sizeof(tx_buf), SYS_FOREVER_US);
if (ret) {
return 1;
}C10. 让我们开始接收数据!
10.1 定义接收缓冲区。对于这个通过 UART 控制 LED 的简单练习,我们将定义一个大小为 10 且类型为 uint8_t 的小缓冲区。
10.1.1 定义接收缓冲区的大小。
#define RECEIVE_BUFF_SIZE 10C10.1.2 定义接收缓冲区并将其成员初始化为零。
static uint8_t rx_buf[RECEIVE_BUFF_SIZE] = {0};C10.2 将接收超时周期定义为 100 微秒。这意味着当接收到一个字节且在 100 微秒内处于空闲状态(没有新数据)时,将生成 UART_RX_RDY 事件。如果您要在应用程序中使用接收超时,请选择符合您应用程序要求的值。
#define RECEIVE_TIMEOUT 100C10.3 通过调用 uart_rx_enable() 开始接收,并向其传递接收缓冲区的地址。
ret = uart_rx_enable(uart ,rx_buf,sizeof rx_buf,RECEIVE_TIMEOUT);
if (ret) {
return 1;
}C11. 构建应用程序并将其烧录到您的开发板上。
12. 查看配置冲突。
现在我们已经构建了应用程序,您可以返回 prj.conf 文件,并注意到我们添加的第一行 CONFIG_SERIAL 下方有一条蓝色波浪线。这表示配置冲突,通常是因为 Kconfig 符号在活动构建上下文中被设置了多次。

将鼠标悬停在 Kconfig 符号上以查看配置冲突。在这里我们可以看到 CONFIG_SERIAL 在构建中被设置了多次。

返回 nRF Connect 窗口。
l5_e1),然后在 Details 视图下,展开 Kconfig。在这里我们可以看到组成最终构建的所有文件。zephyr/boards/nordic/CONFIG_SERIAL 已在板卡的设备树中启用。
13. 查看设备树可视化编辑器 (Devicetree Visual Editor)。
让我们还通过使用 Devicetree Visual Editor 检查 UART 外设是否已在设备树中启用。
13.1 打开设备树可视化编辑器。
l5_e1)
这将打开设备树可视化编辑器,如下图所示

13.2 确认 UART 外设已启用。
在 Nodes 视图中,找到 soc 菜单并单击右侧的箭头将其展开。然后向下滚动直到找到 uart0 ,并注意其复选框已勾选,表示它在构建上下文中已启用。

14. 您应该观察到开发板上的三个 LED 全部亮起。使用集成 nRF Terminal 或 PuTTY 等串口终端,您应该看到以下输出:
*** Booting nRF Connect SDK ***
*** Using Zephyr OS ***
Press 1-3 on your keyboard to toggle LED 1-3 on your development kitTerminal*** Booting nRF Connect SDK ***
*** Using Zephyr OS ***
Press 1-2 on your keyboard to toggle LED 1-2 on your development kitTerminal*** Booting nRF Connect SDK ***
*** Using Zephyr OS ***
Press 0-2 on your keyboard to toggle LED 0-2 on your development kitTerminal我们使用的是设备树中设置的默认波特率 (115200) 和 UART 设置。如果您想更改该设置,可以按照 之前 说明的使用 UART API 进行操作。
在串口模拟器中输入 1 到 3 的数字(对于 nRF54 DK 为 0 到 2)将切换开发板上相应的 LED 状态。
此练习的解决方案可以在 GitHub 仓库 l5/l5_e1_sol 中找到。