Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Drag & Drop Files, Choose Files to Upload 您最多可以上传 2 个文件。
  • 简体中文
    Eng
    简体中文
  • 练习 1 – 通过 UART 控制 LED

    通过 UART 控制 LED

    在本练习中,您将学习如何通过 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=y
    Kconfig

    正如我们之前提到的,第一行通常通过板卡的设备树默认启用。然而,第二行对于启用串行驱动程序的异步 API 至关重要。

    3. 在 main.c 中包含 UART 驱动程序的头文件。

    #include <zephyr/drivers/uart.h>
    C

    4. 获取 UART 硬件的设备指针并验证其是否就绪。

    4.1 获取 UART 硬件的设备指针。

    请记住,在 nRF Connect SDK 中,任何外设都作为 struct device 类型的指针进行访问。

    const struct device *uart= DEVICE_DT_GET(DT_NODELABEL(uart0));
    C
    const struct device *uart= DEVICE_DT_GET(DT_NODELABEL(uart30));
    C
    const 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 ;
    	}
    C

    5. 通过 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);
    
    C
    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);
    C

    5.2 验证 LED 设备是否就绪。

    由于所有 LED 都与同一个端口 &gpio0 相关联,我们只需要检查其中一个设备即可。

    	if (!device_is_ready(led0.port)){
    		printk("GPIO device is not ready\r\n");
    		return 1;
    	}
    C

    6. 配置 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 ;
    	}
    C
    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 ;
    	}
    C

    7. 定义 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;
    	}
    }
    C
    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);
    			}
    		}
    		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;
    		}
    C

    9. 现在让我们通过 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"};
    C
    static 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"};
    C
    static 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"};
    C

    9.2 通过调用 uart_tx() 通过 UART 发送数据。由于我们不使用流控制,请传递 SYS_FOREVER_US 以禁用传输超时。这与接收超时无关。

    	ret = uart_tx(uart, tx_buf, sizeof(tx_buf), SYS_FOREVER_US);
    	if (ret) {
    		return 1;
    	}
    C

    10. 让我们开始接收数据!

    10.1 定义接收缓冲区。对于这个通过 UART 控制 LED 的简单练习,我们将定义一个大小为 10 且类型为 uint8_t 的小缓冲区。

    10.1.1 定义接收缓冲区的大小。

    #define RECEIVE_BUFF_SIZE 10
    C

    10.1.2 定义接收缓冲区并将其成员初始化为零。

    static uint8_t rx_buf[RECEIVE_BUFF_SIZE] = {0};
    C

    10.2 将接收超时周期定义为 100 微秒。这意味着当接收到一个字节且在 100 微秒内处于空闲状态(没有新数据)时,将生成 UART_RX_RDY 事件。如果您要在应用程序中使用接收超时,请选择符合您应用程序要求的值。

    #define RECEIVE_TIMEOUT 100
    C

    10.3 通过调用 uart_rx_enable() 开始接收,并向其传递接收缓冲区的地址。

    	ret = uart_rx_enable(uart ,rx_buf,sizeof rx_buf,RECEIVE_TIMEOUT);
    	if (ret) {
    		return 1;
    	}
    C

    11. 构建应用程序并将其烧录到您的开发板上。

    12. 查看配置冲突。

    现在我们已经构建了应用程序,您可以返回 prj.conf 文件,并注意到我们添加的第一行 CONFIG_SERIAL 下方有一条蓝色波浪线。这表示配置冲突,通常是因为 Kconfig 符号在活动构建上下文中被设置了多次。

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

    返回 nRF Connect 窗口。

    1. 选择应用程序的构建上下文 (l5_e1),然后在 Details 视图下,展开 Kconfig。在这里我们可以看到组成最终构建的所有文件。
    2. 打开与您正在使用的构建目标相匹配的文件(在本例中为 nRF52840 DK)
    3. 这是板卡目标定义中的 Kconfig 文件, zephyr/boards/nordic/
    4. 在这里我们看到 CONFIG_SERIAL 已在板卡的设备树中启用。

    13. 查看设备树可视化编辑器 (Devicetree Visual Editor)。

    让我们还通过使用 Devicetree Visual Editor 检查 UART 外设是否已在设备树中启用。

    13.1 打开设备树可视化编辑器。

    1. 选择应用程序镜像 (l5_e1)
    2. 在 ACTIONS 视图下,单击 Devicetree。
    3. 或者,单击左侧边栏中的图标以打开设备树可视化编辑器窗口。

    这将打开设备树可视化编辑器,如下图所示

    • Context Files:列出活动构建上下文的设备树文件(显示在 Build Contexts View 中)。
    • Context Overview:列出活动构建上下文的设备树信息。
    • Nodes:列出所选设备树文件的 所有 可用节点。此处对节点和引脚的所有更改都会自动镜像到活动设备树文件的代码中。节点也表示在图形化的 Editor View 中,该视图允许您检查和修改节点属性及引脚分配。图形化编辑器视图在 nRF Connect SDK 中级课程的第 3 课 中有介绍。

    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 kit
    Terminal
    *** Booting nRF Connect SDK ***
    *** Using Zephyr OS ***
    Press 1-2 on your keyboard to toggle LED 1-2 on your development kit
    Terminal
    *** Booting nRF Connect SDK ***
    *** Using Zephyr OS ***
    Press 0-2 on your keyboard to toggle LED 0-2 on your development kit
    Terminal

    我们使用的是设备树中设置的默认波特率 (115200) 和 UART 设置。如果您想更改该设置,可以按照 之前 说明的使用 UART API 进行操作。

    在串口模拟器中输入 1 到 3 的数字(对于 nRF54 DK 为 0 到 2)将切换开发板上相应的 LED 状态。

    此练习的解决方案可以在 GitHub 仓库 l5/l5_e1_sol 中找到。

    Switch language?

    Progress is tracked separately for each language. Switching will continue from your progress in that language or start fresh if you haven't begun.

    Your current progress is saved, and you can switch back anytime.

    Register an account
    Already have an account? Log in
    (All fields are required unless specified optional)

    • 8 or more characters
    • Upper and lower case letters
    • At least one number or special character

    Forgot your password?
    Enter the email associated with your account, and we will send you a link to reset your password.