当前文档版本为 v0.5.0,您可以访问当前页面的 开发中 版本以获取最近可能的更新。

SoC App 开发指南

本文介绍 PAN1080 SDK APP 开发的基础知识,如何新建一个简单的 App 工程,并举例说明如何在 App 工程中操作 PAN1080 EVB 开发板的外设模块。

1 概述

PAN1080 SDK 基于 Zephyr OS 开发,其构建系统(Build System)使用 CMake

01_SDK/zephyr目录下存放有 Zephyr OS 的核心代码、内核配置信息、SoC 定义、开发板定义以及 Panchip 提供的例程等。

Zephyr 目录结构如下所示:

  • Zephyr 的核心代码位于01_SDK/zephyr目录下,其中包含一些重要的文件:

    • CMakeLists.txt:CMake 构建系统的顶层文件,包含构建 Zephyr 核心代码所需的 CMake 信息。

    • Kconfig:Kconfig 顶层文件,其直接引用另一个顶层文件Kconfig.zephyr

    • west.yml:west manifest 文件,其中列出了 west 工具能够识别和管理的外部模块目录。

  • Zephyr 的核心代码还包括如下这些顶层目录(每个顶层目录下还包括一个或多个子目录):

    • arch:存放芯片 CPU 架构级信息,由于 PAN1080 是 ARM 架构,因此其中只保留了 ARM 架构信息。

    • soc:存放芯片 SoC 级信息及一些默认配置。

    • boards:存放板级信息,比如 PAN1080 各种 EVB 开发板。

    • doc:Zephyr 官方文档,其内容与 Zephyr文档官网一致。

    • drivers:存放设备驱动代码。

    • dts:存放 DeviceTree(芯片硬件初始化配置) 信息。

    • include:存放 Zephyr 公开的 API 头文件。

    • kernel:存放 Zephyr OS 内核代码。

    • lib:存放库文件,包含一个简单的标准C语言库等。

    • misc:其他一些不便分类的文件。

    • samples_panchip:存放 Panchip 提供的 PAN1080 官方例程。

    • scripts:存放编译测试相关的脚本文件。

    • cmake:存放 CMake 构建 Zephyr App 所需的相关配置和脚本文件。

    • subsys:Zephyr 子系统,包括:USB Device Stack(USB 设备栈)、File System(文件系统)、Bluetooth Host and Controller(低功耗蓝牙Host端与Controller端)等实现代码。

    • share:存放一些额外的架构无关的数据,目前包含 Zephyr CMake Package。

一个最简单的 APP 工程文件结构如下所示:

   <work_dir>/my_app
   ├── CMakeLists.txt
   ├── prj.conf
   └── src
       └── main.c

其中:

  • CMakeLists.txt: 构建系统从此文件中查找待编译 APP 源码,并将 APP 目录与 Zephyr 链接起来,使得 APP 可以编译、配置、使用 Zephyr 提供的各种功能。

  • 配置文件: APP 需要提供一个 Kconfig 配置文件(通常称为prj.conf),其中包含一个或多个 Zephyr 内核配置信息,这些配置与特定的芯片(SoC)配置、特定的开发板(Board)配置等配置信息合并,生成最终的配置文件(.config文件与autoconf.h文件)。

  • APP 源码文件: APP 需要包含一个或多个C语言或汇编源码文件,这些文件通常位于名为src的目录下。

  • <work_dir>:App 工程所在的目录,一般放在 samples_panchip 目录内。

    :从文件结构上来说,构建系统允许我们将自己的 APP 工程创建在任意目录下;只要配置正确,命令行方式的构建系统可以在编译 APP 工程的时候,自动识别到 SDK 中 Zephyr 的路径;但目前 ZAL 工具只支持从 samples_panchiptests_panchip 两个目录中搜索 App 工程。

APP 工程创建后,我们即可以使用 ZAL 工具(或直接使用west build CLI 命令)触发编译操作(其内部是调用 CMake),编译过程中会自动生成单独的 Build(构建)目录,其中存放编译输出的所有文件。

下面介绍如何创建、构建和运行一个自定义的 APP。

2 确认开发环境

参考 SDK 快速入门 文档,确认软硬件开发环境,可以正常编译、下载和调试程序。

3 参考相关例程

SDK 中提供了一些例程(位于01_SDK/zephyr/samples_panchip目录),可以直接编译下载到 EVB 开发板上执行,包括多线程打印消息、LED 闪灯、以及蓝牙相关例程等等。

在进行开发之前,建议先看一下相关的例程和文档,熟悉 Zephyr OS 的基本框架;然后实际将这些例程编译烧录至 EVB 开发板中查看运行效果,同时进一步熟悉开发环境的使用。

image

SDK 例程目录

另外,SDK 中也提供了一些 Zephyr 的测试用例(位于01_SDK/zephyr/tests_panchip目录),用于测试外设 Driver、OS Kernel 等功能是否正常。阅读这些测试用例代码,也有助于熟悉 Zephyr OS 提供的各种 API 的使用方法。

image

SDK 测试用例目录

4 新建一个 App 工程

创建一个 APP 最简单的方式,是从 01_SDK/zephyr/samples_panchip 目录下,Copy 一个例程,并将其中的非必要文件(如README.rstsample.yaml)删除即可。

假设我们希望创建一个名为my_led_blink的 App 工程,使用 PWM 的方式将 EVB 开发板上的 LED 灯点亮并令其闪烁,要求:

  1. 首先,LED 灯在每 100ms 的时间内,亮 20ms、灭 80ms,持续 3s

  2. 然后,LED 灯在每 100ms 的时间内,亮 80ms、灭 20ms,持续 3s

  3. 重复上述闪烁规则

  4. 每次闪烁频率切换的时候,均向 UART 串口打印闪烁频率信息

下面我们详细讲解如何新建一个工程来实现上述需求。

4.1 从例程中拷贝一份相似的工程

例程blinky(位置:01_SDK/zephyr/samples_panchip/basic/blinky目录)演示了如何使用 GPIO 的方式点亮 LED 并令其每隔 1s 闪烁一次。

我们以此例程为基础进行修改:

  1. 拷贝一份 blinky 并将其重命名为 my_led_blink

    image

    拷贝一个现有的例程

    打开CMakeList.txt,将第 5 行project参数修改为新的项目名称 my_led_blink

    # SPDX-License-Identifier: Apache-2.0
    
    cmake_minimum_required(VERSION 3.20.0)
    find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
    project(my_led_blink)
    
    target_sources(app PRIVATE src/main.c)
    

    :本 App 工程目录中,必要的文件为:

    • src\main.c: 主程序代码

    • CMakeList.txt: cmake 文件

    • prj.conf: 项目配置文件

    其它两个文件不是必须的(在本示例中我们可以直接将其删除):

    • README.rst: 例程说明文件

    • sample.yaml: 用于 Zephyr 单元测试的信息描述文件

  2. 确认 EVB 硬件接线:

    1. 使用 USB 线,将 PC USB 与 EVB Type-C USB(USB->UART)相连,用于供电及串口 Log 打印

    2. 将 JLink SWD 正确连接至 EVB 板(P46ICEKP47ICED

    3. 使用跳线帽将 P16 引脚与 RGB 灯的蓝色通道(RGB-B)引脚相连

  3. 打开 ZAL 工具,在 Project List 中找到新建的工程:

    image

    ZAL 选择新建的工程配置

    如果在新增 App 工程目录之前,已经打开了 ZAL 工具,则在选择新的 App 工程之前,应先点击一下 ZAL 工具 Config 列表右面的 U 按钮,更新 Project 列表。

  4. 点击build按钮,编译成功后会显示生成名为zephyr.elf的输出文件,同时显示当前工程的 FLASH 和 SRAM 占用信息:

    image

    ZAL Build 新建的工程

  5. 点击Flash按钮,将程序烧录至芯片Flash中:

    image

    ZAL 烧录 新建的工程

  6. 烧录成功后,即可观察到 EVB 板上的蓝色 LED 灯以 1Hz 的频率不断闪烁:

  7. 最后,点击Open IDE按钮,启动VS Code,后续代码修改与编译烧录调试均可在 VS Code 环境下进行:

    image

    启动 VS Code

4.2 硬件资源规划

4.2.1 时钟与电源配置

在实际项目中我们需要确定 SoC 时钟源、内部各模块的时钟配置、电源选择等事项。但这里我们直接使用 EVB 板开发,因此暂不关注这些内容,使用默认配置参数即可。

EVB 板的电源选择相关,请参考 PAN108x EVB 硬件资源介绍 文档。

4.2.2 PWM 模块配置

我们使用 PAN1080 SoC 的 PWM 模块控制 EVB 板上的 LED 灯,为此我们需要规划:

  • 使用哪个 PWM 模块的哪个输出通道(PAN1080 SoC 中内置了 3 个硬件 PWM 模块,分别为PWM0PWM1PWM2,每个模块均有 8 个通道)

  • 将 PWM 波形输出到 SoC 的哪个引脚

需要注意的是,由于 PAN1080 SoC 的每个引脚最多只能配置成预设的 8 个功能之一,因此我们在规划引脚资源的时候,要查阅各个引脚的 PINMUX 定义,从中找到合适的引脚使用。

  1. 我们可以从 PAN1080 Development Kit 中的如下几个文件的任意一个中找到 PAN1080 SoC 的 PINMUX 引脚定义:

    • 01_SDK/zephyr/dts/arm/panchip/pan1080/pan1080xx1_pinctrl.dtsi (32-Pin 封装引脚定义)

    • 01_SDK/zephyr/dts/arm/panchip/pan1080/pan1080xx5_pinctrl.dtsi (64-Pin 封装引脚定义)

    • 01_SDK/zephyr/include/drivers/pinmux/pinmux_pan1080.h

    • 04_DOC/06_others/PAN108x-Datasheet.pdf04_DOC/06_others/PAN1080x-产品说明书.pdf

  2. 这里,我们直接从 VS Code 中打开pan1080xx5_pinctrl.dtsi文件(按快捷键 Ctrl+P,输入文件名即可)

  3. 由于 EVB 板上的蓝色 LED 灯已经与P16引脚相连,我们先查找P16引脚的 PINMUX 定义,看是否有 PWM 功能;查阅后发现其正好有一个pwm0_ch6 (PWM0 Channel 6)的定义,可以供我们使用:

    image

    打开 pan1080xx5_pinctrl.dtsi 文件

  4. 为使规划的硬件资源生效,我们需要修改 SDK 中的板级 DeviceTree 文件。

    从 VS Code 中打开名为pan108xxb5_evb.dts的文件(按快捷键 Ctrl+P,输入文件名即可):

    image

    打开 pan108xxb5_evb.dts 文件

    此文件中描述了关于 PAN108X-XB5 EVB 的所有板级硬件配置信息,这些配置保证了我们可以使用 EVB 开发板成功运行 SDK 中的所有例程(samples_panchip)和测试用例(tests_panchip)。

    关于Zephyr DeviceTree(.dts/.dtsi)的更多细节,请参考:

  5. 我们暂不关心除PWM以外的模块配置,可以看到,pan108xxb5_evb.dts文件第 172~175 行描述了一个PWM0模块的配置:

    ...
    
    &pwm0 {
    	pinctrl-0 = <&p1_0_pwm0_ch4 &p1_1_pwm0_ch5 &p1_6_pwm0_ch6>;
    	status = "okay";
    };
    
    ...
    

    其含义如下:

    • 第一行&pwm0表示,当前配置文件中对pwm0中属性值的修改会更新默认的(名为pan1080.dtsi的)配置文件中的值,亦即,若本次更新的属性不在默认配置文件中,则为此属性赋予一个值;若本次更新的属性已经在默认配置文件中已经有一个值,则覆盖此属性的默认值;

    • 第二行pinctrl-0表示,修改当前pwm0模块的 PINMUX 配置,其中尖括号中:

      • &p1_0_pwm0_ch4表示将芯片P10引脚的 PINMUX 切换为PWM0 Channel 4功能;

      • &p1_1_pwm0_ch5表示将芯片P11引脚的 PINMUX 切换为PWM0 Channel 5功能;

      • &p1_6_pwm0_ch6表示将芯片P16引脚的 PINMUX 切换为PWM0 Channel 6功能;

    • 第三行status = "okay"表示,在系统初始化过程中,使能 DeviceTree 中对当前PWM模块的各项参数配置;

  6. 由于我们的规划是使用PWM0 Channel 6从芯片P16引脚输出方波,而此处已经配置好,因此 DTS 文件中我们无需做任何改动。

4.2.3 UART 模块配置

我们希望使用 PAN1080 SoC 的UART模块输出 Log 日志到 PC,为此我们需要明确:

  • 使用哪个 UART 模块(PAN1080 SoC 中内置了 2 个 UART 模块,分别为UART0UART1

  • UART Tx/Rx 分别映射到 SoC 的哪个引脚

  • 如何与 PC 通信

PAN1080 EVB 底板中提供了一个 USB 转 UART 的模块电路,其可以很方便地通过跳线帽与 PAN1080 SoC 的P00UART0 TX)、P01UART0 RX)相连,这里我们就使用这种方式与 PC 通信。

  1. 使用跳线帽将 EVB 底板(左侧)的 USB 转 UART 模块的 2 个通信排针连接至 PAN1080 SoC:

    • 将 TX0 引脚连接至 P00

    • 将 RX0 引脚连接至 P01

  2. 再次在 VS Code 中打开板级 DeviceTree 文件pan108xxb5_evb.dts,我们可以看到其中已经有了一些与UART 有关的配置:

    ...
    
    	chosen {
    		zephyr,console = &uart0;
    		zephyr,shell-uart = &uart0;
    		zephyr,bt-mon-uart = &uart0;
    		zephyr,bt-c2h-uart = &uart0;
            ...
    	};
    
    	soc {
    		pin-controller@40030000 {
    			/* port, pin, pinmux_name, pinmux_sel [, flag1, ... ] */
    			DT_PAN_PINS(p0, 1, uart0_rx, PAN1080_PIN_FUNC_P01_UART0_RX, input-enable);
    		};
    	};
    
    ...
    
    &uart0 {
    	current-speed = <921600>;
    	pinctrl-0 = <&p0_0_uart0_tx &p0_1_uart0_rx>;
    	status = "okay";
    };
    
    ...
    

    其含义如下:

    • chosen节点描述了 Zephyr OS 用到的硬件模块与实际硬件的映射关系:

      • zephyr,console = &uart0表示将 Zephyr Console 模块配置为使用UART0通信,我们此次向 UART 打印 LED 状态消息即使用此方式;

      • zephyr,shell-uart = &uart0zephyr,bt-mon-uart = &uart0zephyr,bt-c2h-uart = &uart0均与本文内容无关,这里不做详细介绍,只需了解我们将 Zephyr 所有用到的串口均默认映射到了PAN1080 SoC 的UART0模块即可;

    • soc节点描述了芯片级的硬件配置信息:

      • pin-controller@40030000是一个子节点,代表 PAN1080 SoC 的 PINMUX(MFP)模块;

      • DT_PAN_PINS(p0, 1, uart0_rx, PAN1080_PIN_FUNC_P00_UART0_RX, input-enable)用来辅助配置UART0的 PINMUX 参数;其中,此配置的前 4 个参数表示我们将芯片P01引脚的 PINMUX 切换为 UART0 RX 功能,最后一个参数表示辅助配置参数,这里input-enable表示打开此引脚的数字信号输入功能;

        注1:除input-enable参数外,此处还可以配置bias-pull-up以使用当前引脚的内部上拉电阻,或配置bias-pull-down以使用当前引脚的内部下拉电阻;

        注2:此处的 PINMUX 配置不是必须的,只有在需要打开某个引脚数字信号输入功能或内部上拉/下拉电阻功能的时候,才应该在此处进行配置,并且配置的引脚功能应当与对应外设模块中的pinxtrl-0中的配置一致,例如:

        • 我们在 DeviceTree 的uart0节点中将Tx功能配置到了P00引脚,将Rx功能配置到了P01引脚,因此我们需要在soc/pin-controller节点中将P01引脚的数字信号输入功能打开,但无需配置数字信号输出引脚P06

        • 如果我们使用I2C模块,则由于 I2C 协议要求有上拉电阻,因此我们可以配置bias-pull-up参数以使用 SoC 内部上拉电阻;如果我们将I2C模块配置为 Slave,则由于SCLSDA两根线均为输入信号,因此还需要配置input-enable参数以打开这两个引脚的数字信号输入功能;

    • &uart0表示,修改uart0节点中的属性,且其中的修改会更新默认配置文件(名为pan1080.dtsi)中的值:

      • current-speed = <921600>表示,波特率配置为921600;

      • pinctrl-0 = <&p0_0_uart0_tx &p0_1_uart0_rx>表示:

        • 将芯片P00引脚的 PINMUX 切换为 UART1 TX 功能;

        • 将芯片P01引脚的 PINMUX 切换为 UART1 RX 功能(与soc/pin-controller节点中的配置一致);

      • status = "okay"表示,在系统初始化过程中,使能 DeviceTree 中对当前UART模块的各项参数配置;

  3. 从 dts 文件中可知,SDK 中对于 UART 的配置已经与我们在 EVB 板中的接线一致,因此无需再做修改。

4.3 修改代码

下面我们演示如何在 VS Code 环境下写 App 代码,实现预期功能。

4.3.1 使用PWM

  1. Zephyr 提供了标准的 PWM API 接口(头文件位置:01_SDK/zephyr/include/drivers/pwm.h),为了使用这些接口,我们需要先使能 Zephyr PWM Driver,方法是在 App 目录的prj.conf,增加一个 Config 项:

    CONFIG_PWM=y
    

    增加后文件内容如图所示:

    image

    在prj.conf文件中使能PWM Driver

  2. 接着,我们在main.c中,删除一些无关的代码,并增加 PWM 相关操作代码:

    /*
     * Copyright (c) 2021-2022 Shanghai Panchip Microelectronics Co.,Ltd.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr.h>
    #include <device.h>
    #include <devicetree.h>
    #include <drivers/gpio.h>
    #include <drivers/pwm.h>
    
    void main(void)
    {
    	const struct device *pwm_dev;
    	bool flag = true;
    
    	pwm_dev = DEVICE_DT_GET(DT_NODELABEL(pwm0));
    	if (pwm_dev == NULL) {
    		return;
    	}
    
    	while (1) {
    		if (flag == true) {
    			flag = !flag;
    			/* LED Blink, turn on 20ms and turn off 80ms in every 100ms */
    			pwm_pin_set_usec(pwm_dev, 6, 100000, 20000, PWM_POLARITY_NORMAL);
    		} else {
    			flag = !flag;
    			/* LED Blink, turn on 80ms and turn off 20ms in every 100ms */
    			pwm_pin_set_usec(pwm_dev, 6, 100000, 80000, PWM_POLARITY_NORMAL);
    		}
    		k_msleep(3000);
    	}
    }
    
  3. 点击 VS Code 的Terminal菜单,选择Run Build Task...(或直接快捷键Ctrl + Shift + B),依次执行 Build 和 Flash 命令,将程序烧录至芯片中,即可观察到 LED 灯闪烁,且每 3s 切换一次闪烁频率。

4.3.2 使用 UART

我们可以直接使用 zephyr 提供的 printk 函数,将 Log 信息输出至串口,修改while(1)循环内的代码如下:

	while (1) {
		if (flag == true) {
			flag = !flag;
			/* LED Blink, turn on 20ms and turn off 80ms in every 100ms */
			if (pwm_pin_set_usec(pwm_dev, 6, 100000, 20000, PWM_POLARITY_NORMAL) == 0) {
				printk("LED Blink, turn-on 20ms and turn-off 80ms in every 100ms..");
			} else {
				printk("error occurs\n");
			}
		} else {
			flag = !flag;
			/* LED Blink, turn on 80ms and turn off 20ms in every 100ms */
			if (pwm_pin_set_usec(pwm_dev, 6, 100000, 80000, PWM_POLARITY_NORMAL) == 0) {
				printk("LED Blink, turn-on 80ms and turn-off 20ms in every 100ms..");
			} else {
				printk("error occurs\n");
			}
		}
		k_msleep(3000);
	}

重新编译烧录后,在 PC 中使用串口工具(如 SecureCRT)即可看到每 3s 打印一次消息:

image

修改后的 App Log 打印

5 更多相关文档

  1. Zephyr Devicetree 与 Kconfig 配置指南:介绍 Zephyr Kconfig 配置的相关技巧

  2. Zephyr DeviceTree APIs:Zephyr官方对于DeviceTree接口的详细介绍

  3. Zephyr Peripheral APIs:Zephyr官方提供的外设Driver API接口详细介绍