开发者学堂课程【极简开发 - 平头哥 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.启动流程介绍
启动分为两块,左边的非线程环境、右边的线程环境。左边现场没跑起来,右边现场跑起来。
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
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中指定的链接脚本
可能在 Solution 组件定义链接脚本,Board 组件定义链接脚本,Chip 组件定义链接脚本。
使用逻辑先看 Solution 组件中是否有指定,如果有指定马上使用,如果没有指定继续在 Board 组件查看是否指定如果有指定马上使用,如果没有指定继续在 Chip 组件查看是否有指定,如果找到使用,没有报错。
比较灵活,芯片厂商 Chip 组件按自己常用的方法提供链接脚本,芯片可能做成10块板子,10块板子可能是10种应用场景,10种应用场景需要的链接脚本不一样。板子的厂商基于 Chip 给的修改,生成新的基于板子的链接脚本。有的板子有 psram,有的板子 flash 为一兆,有的两兆。板子链接脚本长度定义不一样,根据板子具体情况定义链接脚本。
有 psram flash 的板子开发的 Solution 五花八门,一个 Solution一定要运行 flash,另一个 Solution 运行 psram,板子的链接脚本无法统一。根据 Solution 的情况定义链接的脚本,提供三级的链接脚本,方便不同层次的软件开发者使用链接脚本。
启动、链接,最小系统已经有雏形。
3.打印功能移植
移植 CSI UART 驱动接口 csi_uart_putc,开发者仅需实现 CSI UART 驱动整个打印功能实现。
调用关系,用户程序调到 ulog 组件,调到c库接口,调到 YoC 设备框架,最后调到设备驱动层。
4.CSI 驱动移植
YoC 更多开发,开发 CSI 驱动接口,提供几大类接口驱动,引脚类、安全类、定时器类、存储类、系统类,接口与 YoC 用户芯片厂商的合作伙伴一起结合智慧定义。定义过程中参考其它优秀的操作系统接口设计,融合成 CSI 接口。按照 CSI 接口实现 UART、IIC、QSPI 驱动,驱动好后对整个平台芯片的 SDK 开发相对完整完成。
CSI 驱动移植-事件模型
不同的硬件中断的类型、名字有差别,抽象出通用的事件。比如发送成功、接受成功等通用事件抽象出来供用户程序调用。
流程如下
中断进入 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 启动第一个任务。
任务切换流程介绍
横轴表示时间,纵轴表示优先级,不仅仅表示中断的优先级,是泛的概念,将中断、任务、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/
点击上传组件跳出对话框,csi_riscv_dummy 组件、版本号填好,点确定。前提经过1520认证测试,经过企业账号的申请。
答疑
链接脚本指定,可以通过数组指定。但通过数组指定没办法将内存100%利用完,定义数组10K或20K相对绝对的值,可能在某种场景超过值,要扩大。通过上下浮动,伸缩的方式将 DSRAM 里剩余的内存变成堆,将 DSRAM 内存100%利用。数组的方式更让人接收、理解,掌握了伸缩上下浮动的方式可能觉得此方式更好。
TSPend 是特殊的中断,用做 task 切换 switch,特点优先级低于普通中断。TSPend 需要芯片 CPU 支持可嵌套的中断控制器才能有 TSPend 属性,否则无法判断优先级。
测试通过 CSI 自动化测试,支持一个测试版、一个内测版、两个线接起来,通过脚本自动化方式,将芯片功能测试。可通过脚本的控制把脚本都测一遍。测试用例的开发不仅是平头哥,用户和伙伴也在贡献测试用例。接下来会推出更详细的测试用例,不仅可测试 CSI 驱动,某种意义可用来做芯片验证。开发芯片对 CSI 驱动,CSI 测试的测试集可对芯片做相对全面的测试,提供专门的测试集。







