YoC 上的必备工序(二)| 学习笔记

简介: 快速学习 YoC 上的必备工序。

开发者学堂课程【极简开发 - 平头哥 YoC 平台如何帮助开发者快速入门  AIoT :YoC 上的必备工序(二)】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址:https://developer.aliyun.com/learning/course/735/detail/13099


YoC 上的必备工序(二)

三.YoC芯片 SDK 对接

芯片 SDK 对接关键点

1.启动流程

2.链接脚本

3.打印功能移植

4.CSI 驱动移植

5.RTOS 移植

五点做好芯片启动起来,更多交给 CSI 驱动移植,串口、SPI 驱动到移植。

1.启动流程介绍

image.png

启动分为两块,左边的非线程环境、右边的线程环境。左边现场没跑起来,右边现场跑起来。

startup.S 每个体系不一样是启动文件,初始化 CPU 必要的控制进程器。进入 pre_main/entry_c 接口,将 aos 内核启动,三个步骤。第一 init,创建 task_new_ext 线程,第三个 start。进入start 进行任务的切换,真正进入 application entry 函数。函数功能调用用户的 main 函数,用户的 main 函数由用户自己编程的。提供范本的初始化流程有 posix、cxx、board、cmd cli 初始化,后面有很多始终的初始化,用户自己启动。

启动文件-异常向量表

最容易出错的最小系统的开发,考验基础知识,涉及链接汇编的知识。

section.vectors

.align 2

.globl _Vectors

_Vectors:

.long g_top_irqstack

.long Reset Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long Default_Handler

.long PendSV_Handler

.long SysTick_Handler

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

. long do_irq

以上为异常向量表,以汇编的形式定义,上边定义通用的 CPU 保留的异常向量,后面是中断的向量。

字段说明

.section .vectors,指定向量表为 .vectors 段,不写可能存在数据段,强行指定 .vectors 段为了链接时可以指定到程序段的最头上,需要借助 .vectors 段的。如果是通用的找不到向量表,无法指定到程序段的开头。

.align 2,4字节对齐

_Vectors,向量表符号名等于 int Vectors,int a,a是符号的名字。

.globl_Vectors,标记为全局符号,全局可以访问的符号表

.long g_top_irgstack,表(数组)的元素0存放栈指针,将栈指针 top 符号数字,填充到数组0元素。启动 CPU 自动拉 sp,sp 指向 top 栈点。

启动文件-启动汇编

.text

.align 2

_start:

.align 2

.globl Reset Handler

.type Reset Handler,ofunction

Reset_Handler:

movs r0,#0x0

msr control, ro

ldr r0,=g_top_irqstack

mov sp,r0

#ifndef_NO_SYSTEM_INIT

ldr r0,=SystemInit

blx r0

#endif

ldr r0,=pre_main

blx r0

.size Reset_Handler,.-Reset_Handler

_exit:

b _exit

启动汇编 text、globl 等是重复的。

字段说明

movs r0,#0x0 msr control, r0 控制寄存器初始化

ldr r0, =g_top_irqstack mov sp, r0 设置栈指针 SP

Idrr,=Systemlnit blx r0 跳转至系统初始化

ldr r0,=pre_main blx r0 跳转至 pre_main

控制寄存器初始化每一个系统结构,平头哥1906、1902的 CPU 都有自己的控制寄存器,基本可以通用。

设置栈指针初始化,从新将栈指针指向为 g_top_irqstack。

跳转至 pre_main,系统初始化针对芯片做复用关系基本的初始化,pre_main 真正进入到相对有逻辑的应用C函数。

异常向量表、启动汇编两部分写好后,C语言没问题。

2.链接脚本-内存空间定义

链接脚本 YoC 提供模板,可根据模板更改,可通过脚本自己编写。

MEMORY

{

ISRAM : ORIGIN = 0x00000000,LENGTH = 0x20000

DSRAM :ORIGIN = 0x20000000,LENGTH = 0x20000

}

ISRAM: DSRAM: 内存区域名,可被链接脚本使用。

ORIGIN=0x00000000 该内存区域从0x00000000开始

LENGTH=0x20000 该内存区域大小是0x20000

ORIGIN = 0x00000000,LENGTH = 0x20000 大小28K,命名为 ISRAM。ORIGIN = 0x20000000,LENGTH = 0x20000 28K,命名为 DSRAM,ISRAM DSRAM 底下的脚本会用到。代码段放到 ISRAM 里,数据段放到 DSRAM 里。开发者根据自己芯片的划分定义 MEMORY 区域名,有的芯片增加 flash 字段。

链接脚本-堆空间指定1

_min_heap_size=0x200;

PROVIDE (_ram_end =0x20020000);

PROVIDE (_heap_end=ram_end);

_min_heap_size 支持内存动态分配,首先告诉堆的起始地

址和结束地址。链接脚本里通过 _heap_end=ram_end 指定堆的起始地址和结束地址。

_min_heap_size 保证 heap 空间最小是0x200

堆的过程中始终保证堆的大小是0x200,如果小于0x200会报错。

_ram_end 内存结束地址是0x20020000

_ram_end 指定符号是0x20020000

_heap_end=_ram_end 堆结束地址是 _ram_end

堆的结束等于内存结束

_heap_start 在哪里?

脚本链接-堆空间指定2

image.png

DSRAM 划分如上,划分.data 段、_ram_code(有些代码放到 ram 上跑)全局变量段 bss、_user_heap_stack 段。_user_heap_stack 段分为两段,stack 栈,剩下是为堆。heap_end 等于0×20020000,_heap_start 在栈的结束地址。总128K,data 段占1K,_ram_code 占1K,bss 占1K,栈占1K,去掉4K,剩下的是堆。如果所有四个变为2K,那么堆变为120K,所以_heap_start 上下浮动。

_ebss=.;

_bss_end _=.;

_end=.;

end=.;

}>REGION_BSS AT>REGION_BSS

._user_heap_stack(NOLOAD):{

.=ALIGN(0x4);

*(.stack*)

=ALIGN(0x4);

_heap_start=.;

.+=_min_heap_size;

.=ALIGN(0x4);

}> REGION_BSS AT>REGION_BSS

}

开发者可根据例子修改,_heap_start 指向绝对地址,主要保证起始地址与其它段无冲突,没有问题。

链接脚本-XIP

XIP 可能导致程序跑飞、跑挂,很难跟踪问题,平时注意脚本描述。

XIP/非XIP

程序直接在 Flash 闪存内运行,不把代码读到系统 RAM 中,话该种模式称为 XIP;反之为非 XIP 模式 XIP 模式下,需要在 package.yaml 定义 CONFIG XIP

XIP 模式

.data :{

.=ALIGN(0x4);

_sdata=.;

_data_start__=.;

Data_start=.;

*(.got.plt)

*(.got)

*(.gnu.linkonce.r*)

*(.data)

*(.data*)

*(.data1)

*(.data.*)

_edata=.;

_data_end__=.;

.=ALIGN(0x4);

}>REGION_DATA AT>REGION_RODATA

XIP 模式需要指定下载地址,指定 LMA。LMA指定到 AT>REGION_RODATA,放在片上 flash 里。

非 XIP 模式

.data :{

.=ALIGN(0x4);

_sdata=.;

_data_start__=.;

Data_start=.;

*(.got.plt)

*(.got)

*(.gnu.linkonce.r*)

*(.data)

*(.data*)

*(.data1)

*(.data.*)

_edata=.;

_data_end__=.;

.=ALIGN(0x4);

}>REGION_DATA

非 XIP 模式整个程序运行到 yaml 不需要特别指定下载地址,LMA 跟随执行地址分配。

XIP 程序段编译

Program Headers:

Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align

LOAD 0x000400 0x00000000 0x00000000 0x09d74 0x09d7

4 R E x400

LOAD 0x00a400 0x20000000 0x00009d74 0x00834 0x00834 RWE 0x400

LOAD 6x00ac38 0x20000838 0x20000838 0x00000 0x03388 RW 0x400

Section to Segment mapping:

Segment Sections...

00 .text .eh_frame.rodata

01 .data ._ram_code

02 .bss ._user_heap_stack

VirtAddr PhysAddr 是执行地址和下载地址名称。

非 XIP 程序段编译

Program Headers:

Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align

LOAD 0x000400 0x20000000 0x00000000 0x098dc 0x0cc68 RWE 0x400

Section to Segment mapping:

Segment Sections...

00 .text .eh_frame.rodata.data ._ram_code .bss ._user_heap_stack

非 XIP 有一行,XIP 有三行。非 XIP 模式执行地址与下载地址一致,XIP 下载地址与执行地址不同。

程序访问全局变量,edata=1,1的地址在 VirtAddr取。

data 在 flash 里,XIP 模式下数据要拷贝到 VirtAddr,取出a的值为正确的值。最小系统的 green up 没有将数据段拷贝到真正执行地址导致错误。

链接脚本-指定链接脚本

指定在 package.yaml 中

package.yaml 字段: Id_script:./gcc_flash.lds

覆盖式指定,优先使用solution中指定的链接脚本

image.png

可能在 Solution 组件定义链接脚本,Board 组件定义链接脚本,Chip 组件定义链接脚本。

使用逻辑先看 Solution 组件中是否有指定,如果有指定马上使用,如果没有指定继续在 Board 组件查看是否指定如果有指定马上使用,如果没有指定继续在 Chip 组件查看是否有指定,如果找到使用,没有报错。

比较灵活,芯片厂商 Chip 组件按自己常用的方法提供链接脚本,芯片可能做成10块板子,10块板子可能是10种应用场景,10种应用场景需要的链接脚本不一样。板子的厂商基于 Chip 给的修改,生成新的基于板子的链接脚本。有的板子有 psram,有的板子 flash 为一兆,有的两兆。板子链接脚本长度定义不一样,根据板子具体情况定义链接脚本。

有 psram flash 的板子开发的 Solution 五花八门,一个 Solution一定要运行 flash,另一个 Solution 运行 psram,板子的链接脚本无法统一。根据 Solution 的情况定义链接的脚本,提供三级的链接脚本,方便不同层次的软件开发者使用链接脚本。

启动、链接,最小系统已经有雏形。

3.打印功能移植

image.png

移植 CSI UART 驱动接口 csi_uart_putc,开发者仅需实现 CSI UART 驱动整个打印功能实现。

调用关系,用户程序调到 ulog 组件,调到c库接口,调到 YoC 设备框架,最后调到设备驱动层。

4.CSI 驱动移植

image.png

YoC 更多开发,开发 CSI 驱动接口,提供几大类接口驱动,引脚类、安全类、定时器类、存储类、系统类,接口与 YoC 用户芯片厂商的合作伙伴一起结合智慧定义。定义过程中参考其它优秀的操作系统接口设计,融合成 CSI 接口。按照 CSI 接口实现 UART、IIC、QSPI 驱动,驱动好后对整个平台芯片的 SDK 开发相对完整完成。

CSI 驱动移植-事件模型

不同的硬件中断的类型、名字有差别,抽象出通用的事件。比如发送成功、接受成功等通用事件抽象出来供用户程序调用。

流程如下

image.png

中断进入 Default_IRQHa,各芯片、ARCH 的实现不一,处理现场保存恢复、嵌套操作。进入下一层做中断分发,分发到 uart_irqhandler 或 spi_irqhandler,调用用户传入的 uart 函数。uart 发送完成,用户接收发送完成的事件发给信号量,告诉另一个线程,串口数据接收完成快去处理。信号量的发送,消息队列的发送,其它全局信息的处理,用户可添加自己的代码。隔离为两层上面是用户 callback,下面是驱动。

对接时可观察,可将上层应用代码统一。CSI 接口提供规范系统芯片接口,让应用程序统一,提供 CSI 标准测试用例。通过 CSI 测试程序验证芯片的功能,目前有源源不断的开发者贡献测试的 keys。CSI 接口不断积累测试 keys 变多,让接口更稳定。

5.RTOS 移植

RTOS 常规意义不需要移植,很多厂商移植的代码已经 reading 好,直接用。YoC 只需将 CPU 型号填好,YoC tools CDK 工具自动选好移植文件。平头哥有自己的 RISC-V,每个厂商基于 RISC-V,甚至自己开发 CPU 架构实现芯片,导致 RTOS 移植代码不统一。客户做自己的 RISC-V,可能移植与标准移植不统一。

移植两个文件

port_c.c

port_s.s

其它边边角角的移植量非常小

RTOS 移植-port_c.c

void *cpu_task_stack_init(cpu_stack_t*stack_base,size _t stack _size,

void *arg, task_entry_t entry)

任务栈初始化,参数将栈值传进,栈值大小、任务 task 参数、task 函数地址传进,函数将任务栈初始化。

参数/返回值 说明

stack_base 栈基地址

stack_size 栈大小(单位:word)

arg 线程函数参数

entry 线程函数

返回值 使用完后的栈基地址

RISC-V 移植是将通用的寄存器ao、a1寄存器初始化成用户传进的值,线程调起来可以直接跳转。

RTOS 移植-port_s.s

关中断函数

size_t cpu_intrpt_save(void);

参数/返回值 说明

返回值 CPSR 寄存器的值

关中断关掉将CPSR 寄存器的值返回,用户保存。用户保存后恢复中断,值恢复做到开关中断的效果。

开中断

void cpu_intrpt_restore(size_t cpsr);

参数/返回值 说明

cpsr CPSR 寄存器的值

开中断、关中断不是强行的开强行的关,当执行函数时当前中断开要关掉,当前中断是关的还是关掉,保持不变。寄存器返回出来,最终通过 restore 函数恢复。单纯的强行关闭强行开启,应用可能有莫名其妙的问题。

void cpu_task_switch(void); 线程下切换任务

void cpu_intrpt_switch(void); 中断中切换任务

内核移植任务切换的函数,线程下切换任务一个线程调用信号量发送,最底层调用 cpu_task_switch。中断里调用信号量发送,最底层调用 cpu_intrpt_switch。两个切换任务有的一样有些有区别,可参考YoC 具体属性。

void cpu_first_task_start(void);

启动第一个任务

启动流程有 aos_start,最终调 cpu_first_task_start 启动第一个任务。

任务切换流程介绍

image.png

横轴表示时间,纵轴表示优先级,不仅仅表示中断的优先级,是泛的概念,将中断、任务、TSPend融合在一起,表示优先级的数轴。Task 优先级最低、然后 TSPend 优先级、Intr 优先级、然后SysTick优先级。Intr 优先级和 SysTick优先级可以对调,有些系统希望 SysTick 设置低一点,Intr 优先级高一点。以 SysTick 高于Intr 优先级为例。

刚开始零点时 TaskA 运行,发一个信号量,调用 cpu_task_switch,平头哥 RISC-V CPU 里触发 TSPend 特殊中断,直接进入 TSPend 处理函数。TSPend 处理函数保存 TaskA 的线程,恢复 TaskB 线程,等待 TSPend 中断返回后线程变为 TaskB,完成线程的任务切换,借助 TSPend 完成。当前任务运行在 TaskB,普通中断产生,中断的优先级别高于 Task,立马响应串口中断。运行时没有等到串口中断返回,更高优先级 SysTick 产生。SysTick 优先级高于普通中断,抢占普通中断进入 SysTick 中断。SysTick 中断TaskB 时间片用完,切换到 TaskA,触发到 TSPend。触发以后不会马上响应 TSPend 中断,TSPend 中断优先级最低,回到串口中断,串口中断完后立马响应 TSPend 中断。TSPend 中断保护 TaskB 线程,恢复 TaskA 的线程。回到 TaskA。

线程态下与中断态下任务切换实现机制。可以根据平头哥190、290 CPU 里 Priority 代码对照图看流程。

RTOS(Rhino)移植-调试技巧

问题:Rhino是否在运行?

方法:g_sys_stat==RHINO_RUNNING?

问题:判断任务是否在切换

方法:cpu_task_switch和cpu_intrpt_switch是否被调用

问题:Tick中断是否正常

方法:krhino_tick_proc是否被调用

问题:查看当前task信息

方法:查看g_active_task[0],0不是线程为0而是多核情况下 cpu 0。多核可看 g_active_task[1]、task[2]。

四个关键点掌握后基本可以判断操作系统是否正常,甚至不需要通过打印判断操作系统是否正常。

Build

芯片开发完后 Build 粗略图,make 一下,出现分区的信息、编译的信息。

make

Build Solution by

scons: Reading SConscript files .….

scons: done reading SConscript files.

scons: Building targets ...

AR yoc_sdk/lib/libcsi.a

CC out/newlib/libc/stdio/vasprintf.o

CC out/newlib/libc/stdio/_csky_printf.o

CC out/newlib/libc/stdio/printf.o

ranlib yoc_sdk/lib/libcsi.a

......

......

LINK out/riscv_dummy_demo/yoc.elf

Generating generated/data/prim

INSTALL yoc.elf

scons: done building targets.

YoC SDK Done

[INFO] Create bin files

imtb,0,0,0x10000000,0x00001000,0x10001000,imtb tee,1,1,0x10001000,0x00005000,0x10006000

boot.1,0,0x10006000,0x00006000,0x1000c000,boot

prim,1,1,0x1000c000,0x00028000,0x10034000,prim misc,0,0,0x10034000,0x0000aa00,0x1003ea00 kv,0,0x1003ea00,0x00000c00,0x1003f600 Ipm,0,0,0x1003f600,0x00000200,0x1003f800 otp,0,0,0x4003f800,0x00000800,0x40040000

boot, 6 bytes

prim, 34488 bytes

imtb, 4096 bytes

 

四.芯片组件上传到 OCC

OCC 网站

http://occ.t-head.cn/

image.png

点击上传组件跳出对话框,csi_riscv_dummy 组件、版本号填好,点确定。前提经过1520认证测试,经过企业账号的申请。

答疑

链接脚本指定,可以通过数组指定。但通过数组指定没办法将内存100%利用完,定义数组10K或20K相对绝对的值,可能在某种场景超过值,要扩大。通过上下浮动,伸缩的方式将 DSRAM 里剩余的内存变成堆,将  DSRAM 内存100%利用。数组的方式更让人接收、理解,掌握了伸缩上下浮动的方式可能觉得此方式更好。

TSPend 是特殊的中断,用做 task 切换 switch,特点优先级低于普通中断。TSPend 需要芯片 CPU 支持可嵌套的中断控制器才能有 TSPend 属性,否则无法判断优先级。

测试通过 CSI 自动化测试,支持一个测试版、一个内测版、两个线接起来,通过脚本自动化方式,将芯片功能测试。可通过脚本的控制把脚本都测一遍。测试用例的开发不仅是平头哥,用户和伙伴也在贡献测试用例。接下来会推出更详细的测试用例,不仅可测试 CSI 驱动,某种意义可用来做芯片验证。开发芯片对 CSI 驱动,CSI 测试的测试集可对芯片做相对全面的测试,提供专门的测试集。

相关文章
|
存储 C语言
大端存储和小端存储
1.大小端字节序 2.大端存储 3.小端存储 4.为什么会有大小端存储模式之分? 5.如何判断当前机器是大端存储还是小端存储 方法1 方法2
4252 0
|
Linux 芯片
Linux内核学习(六):linux kernel的Kconfig分析
Linux内核学习(六):linux kernel的Kconfig分析
1221 0
|
负载均衡 调度 芯片
SMP多核启动(一):spin-table
SMP多核启动(一):spin-table
644 0
|
存储 算法 机器人
卡尔曼滤波 KF | 扩展卡尔曼滤波 EKF (思路流程和计算公式)
本文分析卡尔曼滤波和扩展卡尔曼滤波,包括:思路流程、计算公式、简单案例等。滤波算法,在很多场景都有应用,感觉理解其思路和计算过程比较重要。
3799 0
|
网络安全 PHP 数据安全/隐私保护
云主机搭建WordPress
本文介绍了云主机的优点,以及如何快速注册云主机账户,然后介绍了如何使用CyberPanel来创建云主机并启动WordPress网站。第一步:访问官网注册账号,然后激活账号,绑定结算方式。第二步:使用CyberPanel搭建WordPress,首先是启动CyberPanel应用,接着选择云主机规格后启动创建。第三步:连接CyberPanel控制面板,创建WordPress网站…
304 1
云主机搭建WordPress
|
Java API 开发工具
java实现chatGPT SDK
构建了一个Java ChatGPT-SDK,用于封装OpenAI接口,支持多种服务调用链路,特别是会话模型。SDK采用工厂模式,提供会话服务的创建,利用OkHttp3和Retrofit2处理HTTP请求,包括请求拦截设置apiKey。核心接口包括IOpenAiApi和OpenAiSession,后者实现会话交互,支持流式响应。测试代码展示了如何使用SDK进行聊天交互。
473 2
|
算法 程序员 编译器
【cmake 踩坑记录】CMake文件安装深入解析:EXCLUDE的奥秘与替代方案
【cmake 踩坑记录】CMake文件安装深入解析:EXCLUDE的奥秘与替代方案
626 0
|
编译器 Linux 开发者
【cmake 交叉编译配置设置】CMAKE_TOOLCHAIN_FILE:跨平台编译的秘密武器
【cmake 交叉编译配置设置】CMAKE_TOOLCHAIN_FILE:跨平台编译的秘密武器
2062 0
【Jlink】使用Jlink RTT工具打印日志
RTT( Real Time Terminal)是SEGGER公司新出的可以在嵌入式应用中与用户进行交互的实时终端。J-Link驱动4.90之后的版本都支持RTT。RTT既可以从MCU上输出信息、也可以向应用程序发送信息,由于其高速的特性,所以不影响MCU的实时性。实现原理: 固件代码将要输出的log数据按照RTT的格式写到确定地址的内存中去,然后RTT通过swd口读取对应内存地址的数据,并显示到PC终端上。
1308 0
|
存储 缓存 网络协议
【先码后看】DHCP 扩展选项大全
动态主机配置协议 (Dynamic Host Configuration Protocol,DHCP) [1] 提供了一个框架,用于将配置消息传递给 TCP/IP 网络上的主机。配置参数和其他控制消息携带在标签数据项中,这些数据项存储在 DHCP 消息的“选项/options”字段中。数据项本身也称为“选项/options”。
1988 0
【先码后看】DHCP 扩展选项大全