【果壳】D1h开发板评测——裸机与应用篇

裸机篇

环境搭建

编译安装xfel

git clone https://github.com/xboot/xfel.git
cd xfel
make
sudo make install

测试

xfel version
AWUSBFEX ID=0x00185900(D1/F133) dflag=0x44 dlength=0x08 scratchpad=0x00045000
#表明安装成功并检测到xfel设备

riscv裸机开发工具链

wget https://gitlab.com/weidongshan/Toolchain/-/raw/master/riscv64-glibc-gcc-thead_20200702.tar.xz
mkdir toolchain
tar xf riscv64-glibc-gcc-thead_20200702.tar.xz -C toolchian/

参考代码

git clone https://github.com/bigmagic123/d1-nezha-baremeta.git

目录结构和配置工具

ccdroid@ubuntu:~/d1-baremetal$ tree -L 2
.
├── d1-nezha-baremeta
│   ├── docs
│   ├── LICENSE
│   ├── opensbi
│   ├── README.md
│   ├── spl
│   ├── src
│   └── tools
├── env.sh
└── toolchian
    ├── riscv64-glibc-gcc-thead_20200702
    ├── riscv64-glibc-gcc-thead_20200702.tar.xz
    └── xfel

env.sh

export BPJ_DIR=`pwd`
export B_CROSS_COMPILE=$BPJ_DIR/toolchian/riscv64-glibc-gcc-thead_20200702/bin
export PATH=$PATH:$B_CROSS_COMPILE
alias xfel='$BPJ_DIR/toolchian/xfel'

用source env.sh加载env.sh,便于开发学习

系统启动研究

从RISCV生态的角度上来看,D1开发板确实是一块不错的可以研究很深的开发板。对于研究D1的底层裸机开发,首先需要知道可以玩那些东西,也可以对RISCV相关的软件生态有比较透彻的理解,本文会从spl阶段到opensbi阶段以及后续阶段做一个简单的分析。

D1上电后启动的第一个程序

D1上电后,首先启动一个(Boot ROM)BROM。根据芯片手册的描述,该BROM的启动地址是0地址处开始启动。

一共是48KB的内存空间用于运行BROM,那么这个BROM做了哪些事情?首先它根据efuse和GPIO选择了启动的媒体类型。支持的启动方式有

  • SD card
  • eMMC
  • SPI NOR Flash
  • SPI NAND Flash

并且可以根据GPIO的选择和Efuse的选择决定启动的模式。同时也支持USB的启动方式,这就为fel启动方式做了很好铺垫。

总的说来,BROM就是从其他的介质中读取SPL,然后放到SRAM中执行,同时也通过FEL运行环境。

sdk中启动代码

tina-d1-open/lichee/brandy-2.0
./build.sh

不难看出,在tina-d1-open的源代码下有lichee的代码。另外brandy-2.0下有splopensbiu-boot的代码。通过对spl代码的研究,主要从流程上可以知道,spl的代码运行在sram中。

spl阶段

从内存的布局上来说,主要分为下面几部分

过查看,可以看到SRAM为32KB,但是实际编译出来的估计大小在32KB~64KB,远大于32KB,这样怀疑的可能性是SRAM可能大于32KB或者利用了DSP0 IRAM的空间。在编译的过程中,发现SPL的固件的头部一段区域,也就是0x00020000地址开始处的一段空间,是初始化的参数,SPL可以根据这个参数选择初始化的串口编号,初始化的DDR参数等等。在这里面编译的串口并非开发板的参数的串口参数,后面在制作固件的时候,会将头部的信息替换。

通过反汇编来查看基本的信息:

从不同的模式下启动,有着不同的引导程序。

通过随便反汇编一个程序,可找到入口地址

riscv64-linux-gnu-objdump -D boot0_nand.elf > 1.txt

不难看到第一阶段的启动实际上在SRAM A1中。

为什么会有

boot0_nand
boot0_spinor
boot0_sdcard

应该是支持这三种启动模式,分别到不同的地方去取固件。

当前xfel在d1上支持了ddr初始化,下载到SRAM和DDR3等操作,并支持运行程序。非常的强大,后面做裸机开发会经常用到,那么在spl里做了哪些事情?首先SPL是运行在SRAM中的程序,这段程序受到SRAM尺寸大小的影响,并不会做的很复杂。主要功能来说:

  • 1.通过引脚判断是否启动JTAG
  • 2.初始化DDR
  • 3.使能MMU
  • 4.根据SD CARD、SPI NAND FLASH 、SPI NOR FLASH判断初始化那种外设。
  • 5.根据opensbi/rtos/uboot,将其搬运到DDR中执行,然后程序运行在DDR中。

opensbi阶段

此时程序就运行在DDR中了,对于开发RISCV的人来说,opensbi并不陌生,一方面这个是后台常驻程序,提供S-mode和M-mode的转换层,另外也起到引导下一阶段程序的目的。这个d1下个阶段指的是uboot。然后opensbi就常驻在M-mode下了。作为独立的程序,我也在裸机层面去编译下载opensbi。这个阶段编译出来的固件,会放到

brandy-2.0/opensbi/build/platform/thead/c910/firmware

文件夹中,然后通过下面的分析得到启动地址。

找到fw_jump.elf

实际的入口应该从0x40000000处入手。

spl研究

为了可以使已有的所有SPL的设计统一,也为了简化添加适用于新板子的设计,专门设计一个通用的SPL框架。在SPL框架下,一个板子的所有代码都能够被重用。代码复制和链接不再是必要的。

Generic SPL framework

To unify all existing implementations for a secondary program loader (SPL)
and to allow simply adding of new implementations this generic SPL framework
has been created. With this framework almost all source files for a board
can be reused. No code duplication or symlinking is necessary anymore.

两个点:

1、为了统一所有现有实现第二段的程序加载程序(SPL-Secondary Program Loader)
2、允许简单地添加新的实现

BootROM是一级启动程序

BootROM 的代码除了去初始化硬件环境以外,还需要去外部存储器上面,将接下来可执行的程序读到内存来执行

既然是读到内存执行,那么这个内存可以不可以是我们板载的 DDR 呢?

理论上是可以的,但是,SoC 厂家设计的 DDR 控制器呢,一般会支持很多种类型的 DDR 设备,并且会提供兼容性列表,SoC 厂家怎么可能知道用户 PCB 上到底用了哪种内存呢?

所以,直接把外部可执行程序读到 DDR 显然是不太友好的,一般来说呢,SoC 都会做一个内部的小容量的 SRAM (又是成本),BootROM 将外部的可执行程序从存储器中读出来,放到 SRAM 去执行;

那么 BootROM 从具体哪个存储器读出二进制文件呢?SoC 厂家一般会支持多种启动方式,比如从 eMMC 读取,从 SDCard 读取,从 Nand Flash 读取等等;上电的时候,需要告诉它,它需要从什么样的外设来读取后面的启动二进制文件;

一般的设计思路是,做一组 Bootstrap Pin,上电的时候呢?BootROM 去采集这几个 IO 的电平,来确认要从什么样的外部存储器来加载后续的可执行文件;

比如呢,2 个 IO,2’b00 表示从 Nand 启动,2’b01 表示从 eMMC 启动,2’b10 表示从 SDCard 启动等等;

当 BootROM 读到这些值后,就会去初始化对应的外设,然后来读取后面要执行的代码;这些 IO 一般来说,会做成板载的拨码开关,用于调整芯片的启动方式。读取烧写的二进制的时候呢,需要注意一些细节,比如,SoC 厂家告诉你,你需要先把 SDCard 初始化称为某种文件系统,然后把东西放进去才有效,之类的;因为文件系统是组织文件的方式,并不是裸分区;你按照 A 文件系统的方式放进去,然后 SoC 的 BootROM 也按照 A 文件系统的方式读出来,才能够达成一致;

spl在整个嵌入式软件中的作用

0、上电后,BootROM 开始执行,初始化时钟,关闭看门狗,关 Cache,关中断等等,根据 Bootstrap Pin 来确定启动设备,初始化外设;
1、使用外设驱动,从存储器读取 SPL;
---------------- 以上部分是 SoC 厂家的事情,下面是用户要做的事情 ----------------

2、SPL 被读到 SRAM 执行,此刻,控制权以及移交到我们的 SPL 了;

3、SPL 初始化外部 DDR;

4、SPL 使用驱动从外部存储器读取 u-boot 并放到 DDR;

5、跳转到 DDR 中的 u-boot 执行;

6、加载内核;

这下回到起点:ROM->SPL->uboot.img,这个SPL架构将可以编译产生一个uboot-spl.bin。即BL1的代码。也就是说SPL结构其实做的工作就是uboot的BL1阶段的工作。

这里就不要把BL1和安全启动的BL1搞混淆,这是因为安全启动的粒度是更细的。

编译

进入spl,目录执行make即可

目录如下:

ccdroid@ubuntu:~/d1-baremetal/spl$ ls
arch  autoconf.mk  board  common  drivers  fes  include  Makefile  mk  nboot  README  tools
ccdroid@ubuntu:~/d1-baremetal/spl$ ls nboot/
boot0.lds                  boot0_sdcard.elf             boot0_spinor_sun20iw1p1.bin  load_image_spinor
boot0_nand.bin             boot0_sdcard.map             libsun20iw1p1_nand.o         main
boot0_nand.elf             boot0_sdcard_sun20iw1p1.bin  libsun20iw1p1_sdcard.o       make_download.sh
boot0_nand.map             boot0_spinor.bin             libsun20iw1p1_spinor.o       Makefile
boot0_nand_sun20iw1p1.bin  boot0_spinor.elf             load_image_mmc
boot0_sdcard.bin           boot0_spinor.map             load_image_nand

其中boot0_sdcard_sun20iw1p1.bin便是spl的可执行文件

运行

xfel write 0x20000 boot0_sdcard_sun20iw1p1.bin
xfel exec 0x20000

执行效果:

ccdroid@ubuntu:~/d1-baremetal/d1-nezha-baremeta/spl/nboot$ xfel write 0x20000 boot0_sdcard_sun20iw1p1.bin
100% [================================================] 64.000 KB, 333.636 KB/s
ccdroid@ubuntu:~/d1-baremetal/d1-nezha-baremeta/spl/nboot$ xfel exec 0x20000

开发板运行效果:

[18075]HELLO! BOOT0 is starting!
[18078]BOOT0 commit : 896f97a
[18081]set pll start
[18083]periph0 has been enabled
[18086]set pll end
[18088][pmu]: bus read error
[18091]board init ok
[18093]ZQ value = 0x2e***********
[18096]get_pmu_exist() = -1

opensbi研究

编译

cd opensbi/
ccdroid@ubuntu:~/d1-baremetal/d1-nezha-baremeta/opensbi$ ls
build_c906.sh  CONTRIBUTORS.md  docs    firmware  lib       platform   scripts            sun20iw2p1.config
build.sh       COPYING.BSD      env.sh  include   Makefile  README.md  sun20iw1p1.config  ThirdPartyNotices.md
# 修改env.sh,B_CROSS_COMPIL替代原有的目录,这个在顶层env.sh中配置过
vim env.sh
	export CROSS_COMPILE=$B_CROSS_COMPILE/riscv64-unknown-linux-gnu-  PLATFORM_RISCV_ISA=rv64gcxthead FW_JUMP_ADDR=0x40200000 FW_TEXT_START=0x40000000
source env.sh
#编译
make PLATFORM=thead/c910

生成的可执行文件在build/platform/thead/c910/firmware/fw_jump.bin

运行

先reset xfel

xfel reset

在按住fel按键,再开机

xfel version
	AWUSBFEX ID=0x00185900(D1/F133) dflag=0x44 dlength=0x08 scratchpad=0x00045000
cd build/platform/thead/c910/firmware
xfel ddr d1
#开发板输出
	DRAM only have internal ZQ!!
	get_pmu_exist() = 4294967295
	ddr_efuse_type: 0x0
	[AUTO DEBUG] single rank and full DQ!
	ddr_efuse_type: 0x0
	[AUTO DEBUG] rank 0 row = 15
	[AUTO DEBUG] rank 0 bank = 8
    [AUTO DEBUG] rank 0 page size = 2 KB
    DRAM BOOT DRIVE INFO: %s
    DRAM CLK = 792 MHz
    DRAM Type = 3 (2:DDR2,3:DDR3)
    DRAMC ZQ value: 0x7b7bfb
    DRAM ODT value: 0x42.
    ddr_efuse_type: 0x0
    DRAM SIZE =512 M
    DRAM simple test OK.
xfel write 0x40000000 fw_jump.bin
	100% [================================================] 60.555 KB, 338.921 KB/s
xfel.exe exec 0x40000000
#开发板输出
    OpenSBI v0.6
       ____                    _____ ____ _____
      / __ \                  / ____|  _ \_   _|
     | |  | |_ __   ___ _ __ | (___ | |_) || |
     | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | |
     | |__| | |_) |  __/ | | |____) | |_) || |_
      \____/| .__/ \___|_| |_|_____/|____/_____|
            | |
            |_|

    Platform Name          : T-HEAD Xuantie Platform
    Platform HART Features : RV64ACDFIMSUVX
    Platform Max HARTs     : 1
    Current Hart           : 0
    Firmware Base          : 0x40000400
    Firmware Size          : 75 KB
    Runtime SBI Version    : 0.2

    MIDELEG : 0x0000000000000222
    MEDELEG : 0x000000000000b1ff
    PMP0    : 0x0000000040000000-0x000000004001ffff (A)
    PMP1    : 0x0000000040000000-0x000000007fffffff (A,R,W,X)
    PMP2    : 0x0000000080000000-0x00000000bfffffff (A,R,W,X)
    PMP3    : 0x0000000000020000-0x0000000000027fff (A,R,W,X)
    PMP4    : 0x0000000000000000-0x000000003fffffff (A,R,W)

rt-thread研究

RT-Thread 是一款主要由中国开源社区主导开发的开源实时操作系统。实时线程操作系统不仅仅是一个单一的实时操作系统内核,它也是一个完整的应用系统,包含了实时、嵌入式系统相关的各个组件:TCP/IP协议栈,libc接口,图形用户界面等。

官网的介绍如下:

这里使用bigmagic123提供的代码,该rt-thread运行在M-model下,代码路径:

git clone https://github.com/bigmagic123/d1-nezha-rtthread.git

依赖:

sudo apt-get install scons

配置RT-Thread编译

修改bsp/d1-nezha下面的rtconfig.py文件

vim bsp/d1-nezha/rtconfig.py

编译

进入d1-nezha-rtthread\bsp\d1-nezha

cd bsp\d1-nezha
scons -c
    scons: Reading SConscript files ...
    scons: done reading SConscript files.
    scons: Cleaning targets ...
    Removed cconfig.h
    scons: done cleaning targets.
scons

运行

xfel ddr d1
xfel write 0x40000000 rtthread.bin
xfel exec 0x40000000

可以看到串口打印如下的信息:

heap: [0x4004bb1c - 0x4324bb1c]

 \ | /
- RT -     Thread Operating System
 / | \     4.1.0 build Dec 19 2021 18:26:36
 2006 - 2021 Copyright by rt-thread team
file system initialization done!
Hello RISC-V!
msh />

使能LVGL的功能

需要使用rt-thread的pkg功能在Linux上使用scons --menuconfig

> RT-Thread online packages     
    > multimedia packages   
        > LVGL: powerful and easy-to-use embedded GUI library

打开图形化界面

[*] LVGL (official): powerful and easy-to-use embedded GUI library                                                    
[ ] LittlevGL2RTT (legacy): The LittlevGL GUI Lib for RT-Thread    
[*] Enable LVGL music player demo for RT-Thread

选择上述即可。

source ~/.env/env.sh
pkgs --update

更新完成pkgs,重新生成 rtthread.bin,在lcd屏幕上就能看到lvgl显示了。

riscv向量编程

RISCV V扩展即向量指令扩展(RVV),这部分作为研究AI加速计算领域有着非常关键的作用。既然的D1支持了rvv扩展(0.7.1,最新的版本已经0.10版本),那么就实际的从底层原理角度分析一下使用的流程。利用了多媒体加速指令集,可以让计算变得更加的高效,同时并行计算的特性使得同时多次计算一组数字成为可能,类似于arm的NEON等等,那么RISCV又该如何去开启和使用V扩展指令,让计算变得更加高效呢?

下面会通过一个裸机代码入手,结合实战去展示riscv rvv的使用。

https://github.com/bigmagic123/d1-nezha-baremeta/tree/main/src/2.vector_example

机器模式处理器状态寄存器(MSTATUS)

机器模式状态处理寄存器可以查看玄铁C910的用户手册,开启的V扩展的位是[23:24]位,如果不设置这两位,那么使用V扩展指令的时候,会出现指令未定义的异常。

这里需要注意的是,RISCV的各家的VS标志并不是一定是这两位,比如sifive会定义在

但是无论怎么说,都需要设置机器状态控制器去开启v扩展指令的支持。

/* Enable FPU and accelerator if present */
li t0, MSTATUS_FS | MSTATUS_XS | (0x01800000)
csrs mstatus, t0

在启动代码中,通过0x01800000设置mstatus开启V扩展支持。

编译选项支持V扩展

默认情况下,平头哥提供的交叉编译工具链已支持了V扩展的编译。只需要在编译选项中开启即可。

从传递给riscv 的gcc的选项来看,带有v扩展即可。

-march是指定了riscv的模块化的指令集选项,可以通过选项指定目标RISC-V支持的模块化的指令集的组合。比如下面几种组合。

rv32i[m][a][f[d]][c]
rv32g[c]
rv64i[m][a][f[d]][c]
rv64g[c]

往往也会结合-mabi进行使用。-mabi决定了RISCV目标支持的ABI函数调用的规程。

RISCV向量计算的原理

在riscv的V扩展中,一共定义了32个寄存器,v0~v31,这32个寄存器,每个长度都是VLEN长度。在玄铁C906定义长度为128位。

而在V扩展的操作中,需要扩展下面的寄存器组。

下面来具体分析一些每个寄存器的作用。

vstart

矢量起始位置寄存器指定了执行矢量指令时起始元素位置,每条矢量指令执行后 VSTART 会被清零。

该寄存器只有在处理器进入陷阱或者中断状态时,才会被硬件写入。

所以的向量指令都会从vstart中给定的元素编号开始执行,支持完成后,自动变为0。

为什么会有这个寄存器,原因是在V扩展指令中,每个寄存器是可以分割与合并的,并不是单独操作。

vxsat

这个是向量定点的饱和标志位,该位指示定点指令是否必须使输出值饱和,以此适应目标格式。

vxrm

向量定点舍入模式寄存器,指定了定点指令采用的舍入模式。

vl

矢量长度寄存器指定了矢量指令更新目的寄存器的范围,矢量指令更新目的寄存器中元素序号小于 VL 的元素,清零目的寄存器中元素序号大于等于 VL 的元素。特别的,当 VSTART>=VL 或 VL 为 0 时,目的寄存器的所有元素不 被更新。该寄存器是任意模式下的只读寄存器,但是 vsetvli、vsetvl 以及 fault-only-first 指令能够更新该寄存器的值。

该寄存器的值是通过vsetvli/vsetvl指令自动设置的。

vtype

VTYPE 寄存器指定了矢量寄存器组的数据类型以及矢量寄存器的元素组成。

通过C910的数据手册,可看出

向量长度寄存器VLENB

该寄存器用于表示矢量寄存器的数据位宽,以实际位宽除以 8 得到的字节数体现。C906 矢量寄存器为 128 位,因此 VLENB 值固定为 16。该寄存器位长是 64 位,用户模式只读。

通过实例分析RISCV V扩展的运作机制

下面一个rvv实际的函数

void test_v(void)
{
  float a[]={1.0,2.0,3.0,4.0};
  float b[]={1.0,2.0,3.0,4.0};
  float c[]={0.0,0.0,0.0,0.0};
  int len=4;
  int i=0;
  //inline assembly for RVV 0.7.1
  //for(i=0; i<len; i++){c[i]=a[i]+b[i];}
  asm volatile(
               "mv         t4,   %[LEN]       \n\t"
               "mv         t1,   %[PA]        \n\t"
               "mv         t2,   %[PB]        \n\t"
               "mv         t3,   %[PC]        \n\t"
               "LOOP1:                        \n\t"
               "vsetvli    t0,   t4,   e32,m1 \n\t"
               "sub        t4,   t4,   t0     \n\t"
               "slli       t0,   t0,   2      \n\t" //Multiply number done by 4 bytes
               "vle.v      v0,   (t1)         \n\t"
               "add        t1,   t1,   t0     \n\t"
               "vle.v      v1,   (t2)         \n\t"
               "add        t2,   t2,   t0     \n\t"
               "vfadd.vv   v2,   v0,   v1     \n\t"
               "vse.v      v2,   (t3)         \n\t"
               "add        t3,   t3,   t0     \n\t"
               "bnez       t4,   LOOP1        \n\t"
               :
               :[LEN]"r"(len), [PA]"r"(a),[PB]"r"(b),[PC]"r"(c)
               :"cc","memory", "t0", "t1", "t2", "t3", "t4",
                "v0", "v1", "v2"
               );

              for(i=0; i<len; i++){
                            printf("\n");
                            printf("%f\n",c[i]);
                            printf("\n");
               }
}

这里采用的是内联汇编,可以更加深入的分析RVV的运作机制和底层原理。

在riscv中,内联汇编的写法

asm volatile("nop");

这样编译器在编译后会生成可以执行的汇编代码。

该函数的功能

for(i=0; i<len; i++){c[i]=a[i]+b[i];}

通过上述分析,通过向量计算,可以一次性计算出上面四次循环加法。

vsetvli    t0,   t4,   e32,m1

vsetvli表示设置每个向量的长度,t4的值表示的是len,也就是4。

e32表示每个元素为32位,m1表示使用1倍数量的向量寄存器。

该条指令相当于把一个向量寄存器(128位)分成四等分,这是一条设置指令,设置vl寄存器。返回值为t0,这里由于是刚好装下4条32位的数字,所以返回值为4。

sub        t4,   t4,   t0

通过查看数组是否计算完成,来进行循环计算,这里t4为0了。

slli       t0,   t0,   2

往左移动两位,也就是将t0乘以4。这里计算的目的是如果存在很长的数组,可以偏移t0个字节从而指向数组的下个地址。

vle.v      v0,   (t1)

填充向量寄存器(t1)为a数组,一条指令将数据放到向量寄存器v0中。

add        t1,   t1,   t0

将a数组的起始元素加上16字节(4个元素)的偏移。

vle.v      v1,   (t2)

填充b数组的数组到向量寄存器v1中。

add        t2,   t2,   t0

将数组b的元素的起始地址偏移16字节,也就是4个元素。

vfadd.vv   v2,   v0,   v1

执行向量加法,将向量的结果保存到向量寄存器v2中。

vse.v      v2,   (t3)

将向量寄存器中值写回到c数组中。

add        t3,   t3,   t0

将数组c的元素指针偏移4个元素。

bnez       t4,   LOOP1

直到计算的len长度为0,此时跳出循环计算。

由于此时计算只有4字节,所以一次循环就计算完成了,不用多次计算。

采用向量寄存器的计算,可以把四次循环计算用一次计算就完成。当然这种如果大量计算时,才能体现出更大的优势。

最后的结果如下:

通过对数组的计算

  float a[]={1.0,2.0,3.0,4.0};
  float b[]={1.0,2.0,3.0,4.0};
  float c[]={0.0,0.0,0.0,0.0};

最后c数组的结果

float c[]={2.0,4.0,6.0,8.0};

其理论数据和实际数据一样。

RVV使用体验

刚接触到riscv 的 V扩展编程时,很多概念都理解的很模糊,感觉十分的困难,通过一段时间梳理之后,发现和以前mips上接触的mxu或者arm的neno使用上大多数是一样的,就需要去设置使用寄存器的长度,当然这些底层函数如果进行一层封装后,再给用户使用,那才是比较方便的,但是本文只是介绍底层实现的原理,并不多介绍使用的细节。

RVV还有一个特性就是寄存器的扩充,比如D1采用的玄铁C906的核,支持的是32个128位的向量寄存器,也可以将两个或多个向量寄存器拼成一个来使用。这样寄存器的长度更加长,能够同时做到并行计算也就更多。这取决于如何做向量的优化设计。

应用篇

环境准备

工具链准备

在env.sh中加入工具链路径,用source env.sh加载便可生效

vim env.sh
PRE_COMPILER=$TOP_DIR/prebuilt/gcc/linux-x86/riscv/toolchain-thead-glibc/riscv64-glibc-gcc-thead_20200702/bin/riscv64-unknown-linux-gnu-

代码编写

在顶层目录创建app,用来构建应用代码

mkdir app

以后应用代码在app目录下完成

hello world

在app目录下创建helloworld目录

mkdir helloworld

c代码编写

cd helloworld
vim hello.c
#include <stdio.h>
int main(int argc, char const **argv)
{
    printf("Hello World\n");
    return 0;
}

Makefile编写

vim Makefile
MAIN_SRC=hello.c
SRCS+=$(MAIN_SRC)
TARGET=$(patsubst %.c,%,$(MAIN_SRC))
$(TARGET):$(SRCS)
    $$GCC -o $@ $^

clean:
    rm -rf $(TARGET) *.o

编译

make之后生成hello,用file helloreadelf -h hello查看

file hello
hello: ELF 64-bit LSB  executable, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 4.15.0, not stripped
ccdroid@ubuntu:~/d1h/tina-d1-h/app/helloworld$ readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           <unknown>: 0xf3
  Version:                           0x1
  Entry point address:               0x103a0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          9624 (bytes into file)
  Flags:                             0x5
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         33
  Section header string table index: 32

Machine: : 0xf3

可见工具并没有识别出riscv架构,应该是工具的适配还没有做好

运行

将生成的hello文件通过adb传输到开发板运行

#开发主机端
adb push hello /root
#开发板端
cd root
chmod +x hello
./hello
	Hello World

基本配置和开发环境正常

gstreamer硬解码

注意:(tinav2不能实现,废弃)

D1的tina系统支持libcedar的openmax接口 使得gstreamer可以用gst-omx插件调用libcedar进行视频硬解码
再加上tina支持了gst-aw插件 提供了gst的一个元件sunxifbsink 就是一个可以进行硬件转换YV12->RGB的硬件图层插件 即DE的应用
这样一来 D1使用gst进行播放视频 效果会非常流畅

配置流程

make menuconifg

libcedarx

Multimedia

gst-omx

gstreamer1-libs

gst1-libav

这个不是用来软解视频的 而是用来软解音频

gstreamer1-plugins-aw

gstreamer1-plugins-bad

gstreamer1-plugins-base

gstreamer1-plugins-good

gstreamer1-utils

编译

make

pack rootfs文件大小超标l

mbr size = 252
mbr magic softw411
disk name=boot-resource
disk name=env
disk name=env-redund
disk name=boot
disk name=rootfs
ERROR: dl file rootfs.fex size too large
ERROR: filename = rootfs.fex
ERROR: dl_file_size = 42240 sector
ERROR: part_size = 40824 sector
update_for_part_info -1
ERROR: update mbr file fail

更改系统分区大小

vim ./device/config/chips/d1-h/configs/nezha/sys_partition.fex

音频功能

录音设备查看

使用 arecord -l 命令

root@TinaLinux:/# arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: audiocodec [audiocodec], device 0: SUNXI-CODEC 2030000.codec-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: snddmic [snddmic], device 0: 2031000.dmic-dmic-hifi dmic-hifi-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 2: sndhdmi [sndhdmi], device 0: 2034000.daudio-audiohdmi-dai 20340a4.hdmiaudio-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

播放设备查看

使用 aplay -l 命令

root@MaixLinux:~# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: audiocodec [audiocodec], device 0: SUNXI-CODEC 2030000.codec-0 []
  Subdevices: 1/1
  Subdevice 0: subdevice 0
card 2: sndhdmi [sndhdmi], device 0: 2034000.daudio-audiohdmi-dai 20340a4.hdmiaudio-0 []
  Subdevices: 1/1
  Subdevice 0: subdevice 0

录音播放测试:

arecord -D hw:1,0 -f S16_LE -t wav -d 3 t.wav     #录音
Recording WAVE 't.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
aplay -D hw:0,0 t.wav                             #播放录音
Playing WAVE 't.wav' : [  387.805903] [SNDCODEC][sunxi_card_hw_params][620]:stream_flag: 0
Signed 16 bit Little Endian, Rate 8000 Hz, Mono

调试过程和工具记录

辅助命令

删除git信息

#删除git信息
find . -name .git -type d -exec rm -rf {} +

个人环境变量设置

在/home/ccdroid/d1h/tina-d1-h目录下创建env.sh

vim env.sh
TOP_DIR=`pwd`
alias cdt='cd $TOP_DIR'
alias cdu='cd $TOP_DIR/lichee/brandy-2.0/u-boot-2018'
alias cdk='cd $TOP_DIR/lichee/linux-5.4'
alias cdc='cd $TOP_DIR/device/config/chips/d1-h/configs/nezha'
alias cddk='cd $TOP_DIR/lichee/linux-5.4/arch/riscv/boot/dts/sunxi'
alias cddu='cd $TOP_DIR/lichee/brandy-2.0/u-boot-2018/arch/riscv/dts'

使用source env.sh加载便可以使用cd[x]快速切换目录

编译错误

environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799

原因是squash需要用这个日期信息来生成一个冗长的version信息,系统会从git信息中查找,但git没有安装,或者git环境变量错误。在openwrt目录下执行命令 date +%s > version.date可以解决。

解决:第一步.make distclean 第二步 date +%s > version.date 第三步:make

git报错fatal: bad default revision ‘HEAD‘

问题现象:

在本地已经有了文件夹和文件,想用git进行管理,在git初始化完毕,并且将目录文件添加到git后,运行 #git log报错,错误内容为:fatal: bad default revision ‘HEAD’。

问题原因:

没有进行过至少一次commit,所以才引发了错误。

解决办法:

执行命令,将当前目录状态保存为最初状态,命令为:

#git commit -m “Init”

相关说明:

如果是刚建的git,会提示让配置用户名和邮箱,使用以下指令初始化即可。

#git config --global user.email “用户名@qq.com”

#git config --global user.name “用户名”

运行错误

lv_examples 0
wh=480x800, vwh=480x1600, bpp=32, rotated=0

unable open evdev interface:: No such file or directory

在Linux内核中,evdev是一种输入子系统,用于处理各种事件类型的输入,如键盘、鼠标和游戏控制器等。若要在Linux内核中启用evdev,你需要确保在内核配置中启用了输入子系统和evdev驱动。

如果你需要编译一个新的内核,并启用evdev,你可以按照以下步骤操作:

  1. 下载并解压缩Linux内核源码。
  2. 配置内核,通常使用make menuconfig命令。
  3. 在“Input device support” → “Generic input layer”中选择“Event interface”。
  4. 保存配置并退出。
  5. 编译并安装内核。

如果你想在已运行的内核中动态加载evdev模块,可以使用以下命令: