RVB2601应用开发实战系列一: Helloworld最小系统

简介: RVB2601开发板是基于CH2601芯片设计的生态开发板,其具有丰富的外设功能和联网功能,可以开发设计出很多有趣的应用。为了开发者更好的了解如何在CH2601上开发应用,本文介绍了如何移植对接CH2601芯片到YoC最小系统,开发第一个我的helloworld程序。
  1. 引言

RVB2601开发板是基于CH2601芯片设计的生态开发板,其具有丰富的外设功能和联网功能,可以开发设计出很多有趣的应用。为了开发者更好的了解如何在CH2601上开发应用,本文介绍了如何移植对接CH2601芯片到YoC最小系统,开发第一个我的helloworld程序。

整个开发移植工作,我们都全部基于剑池CDK集成开发环境进行开发。剑池CDK以极简开发为理念,是专业为IoT应用开发打造的集成开发环境。它在不改变用户开发习惯的基础上,全面接入云端开发资源,结合 图形化的OSTracer、Profiling等调试分析工具,加速用户产品开发。想要了解更多剑池CDK开发信息,请前往平头哥芯片开发社区里集成开发环境获取更多。

建议在在看本文之前,先详细看下RVB2601开发板快速上手教程。本例程名为ch2601_helloworld_demo,可以通过CDK直接从OCC拉取。

  1. 最小系统移植适配

YoC最小系统包括对AliOS Things内核的移植,涉及到任务切换时的处理器上下文保存和恢复 ,中断事件处理,时钟心跳初始化等。利用一个任务不断周期性的打印"Helloworld"来演示最小系统移植成功。

2.1 适配YoC 内核
进入ch2601_helloworld目录,打开工程文件,所有的组件代码都位于packages节点下, 点击packages下的 rhino_arch 包。该组件包含了ARM、CSKY、RISCV等架构下的任务调度的代码,假如架构相同,则直接使用包内代码,若不存在,需要按照接口,将port_s.S、port_c.c等代码实现。具体目录结构如下图:
image.png

由于CH2601使用了RISC-V 32bit处理器, 我们使用rv32_32gpr的具体实现,根据Kernel的对接分为以下几个部分

2.1.1 任务切换相关
cpu_intrpt_switch
该功能函数定义在rhino_arch/src/riscv/rv32_32gpr/port_c.S里, 主要用户触发软中断,切换任务。用户可以通过该接口来实现任务切换。

cpu_intrpt_switch:

li      t0, 0xE080100C
lb      t1, (t0)
li      t2, 0x01
or      t1, t1, t2
sb      t1, (t0)
ret

tspend_handler

该功能函数定义在rhino_arch/src/riscv/rv32_32gpr/port_c.S里, 作为tspend中断的处理函数接口,主要用于保存当前的任务上下文,切换将要运行的下一个任务后,恢复下一个任务上下文。

tspend_handler:

addi    sp, sp, -124
sw      x1, 0(sp)
sw      x3, 4(sp)
sw      x4, 8(sp)
sw      x5, 12(sp)
sw      x6, 16(sp)
sw      x7, 20(sp)
sw      x8, 24(sp)
sw      x9, 28(sp)
sw      x10, 32(sp)
sw      x11, 36(sp)
sw      x12, 40(sp)
sw      x13, 44(sp)
sw      x14, 48(sp)
sw      x15, 52(sp)
sw      x16, 56(sp)
sw      x17, 60(sp)
sw      x18, 64(sp)
sw      x19, 68(sp)
sw      x20, 72(sp)
sw      x21, 76(sp)
sw      x22, 80(sp)
sw      x23, 84(sp)
sw      x24, 88(sp)
sw      x25, 92(sp)
sw      x26, 96(sp)
sw      x27, 100(sp)
sw      x28, 104(sp)
sw      x29, 108(sp)
sw      x30, 112(sp)
sw      x31, 116(sp)
csrr    t0, mepc
sw      t0, 120(sp)
la      a1, g_active_task
lw      a1, (a1)
sw      sp, (a1)
li      t0, 0xE000E100
lw      t1, (t0)
li      t2, 0xFEFFFFFF
and     t1, t1, t2
sw      t1, (t0)

__task_switch_nosave:

la      a0, g_preferred_ready_task
la      a1, g_active_task
lw      a2, (a0)
sw      a2, (a1)
lw      sp, (a2)
/* Run in machine mode */
li      t0, MSTATUS_PRV1
csrs    mstatus, t0
lw      t0, 120(sp)
csrw    mepc, t0
lw      x1, 0(sp)
lw      x3, 4(sp)
lw      x4, 8(sp)
lw      x5, 12(sp)
lw      x6, 16(sp)
lw      x7, 20(sp)
lw      x8, 24(sp)
lw      x9, 28(sp)
lw      x10, 32(sp)
lw      x11, 36(sp)
lw      x12, 40(sp)
lw      x13, 44(sp)
lw      x14, 48(sp)
lw      x15, 52(sp)
lw      x16, 56(sp)
lw      x17, 60(sp)
lw      x18, 64(sp)
lw      x19, 68(sp)
lw      x20, 72(sp)
lw      x21, 76(sp)
lw      x22, 80(sp)
lw      x23, 84(sp)
lw      x24, 88(sp)
lw      x25, 92(sp)
lw      x26, 96(sp)
lw      x27, 100(sp)
lw      x28, 104(sp)
lw      x29, 108(sp)
lw      x30, 112(sp)
lw      x31, 116(sp)
addi    sp, sp, 124
mret

2.1.2 第一个任务初始化

cpu_first_task_start
该功能函数定义在rhino_arch/src/riscv/rv32_32gpr/port_c.S里, 作为第一个任务启动接口。用户通过调用该接口来实现第一个任务的启动。

cpu_first_task_start:

j       __task_switch_nosave

cpu_task_stack_init

该功能函数定义在rhino_arch/src/riscv/rv32_32gpr/port_c.c里, 用于初始化第一个任务的的上下文,用户可以通过调用该接口来实现第一个任务的执行入口,输入参数等。

void cpu_task_stack_init(cpu_stack_t stack_base, size_t stack_size,

                      void *arg, task_entry_t entry)

{

cpu_stack_t *stk;
register int *gp asm("x3");
uint32_t temp = (uint32_t)(stack_base + stack_size);
temp &= 0xFFFFFFF8UL;
stk = (cpu_stack_t *)temp;
*(--stk) = (uint32_t)entry;                   /* PC            */
*(--stk) = (uint32_t)0x31313131L;             /* X31           */
*(--stk) = (uint32_t)0x30303030L;             /* X30           */
*(--stk) = (uint32_t)0x29292929L;             /* X29           */
*(--stk) = (uint32_t)0x28282828L;             /* X28           */
*(--stk) = (uint32_t)0x27272727L;             /* X27           */
*(--stk) = (uint32_t)0x26262626L;             /* X26           */
*(--stk) = (uint32_t)0x25252525L;             /* X25           */
*(--stk) = (uint32_t)0x24242424L;             /* X24           */
*(--stk) = (uint32_t)0x23232323L;             /* X23           */
*(--stk) = (uint32_t)0x22222222L;             /* X22           */
*(--stk) = (uint32_t)0x21212121L;             /* X21           */
*(--stk) = (uint32_t)0x20202020L;             /* X20           */
*(--stk) = (uint32_t)0x19191919L;             /* X19           */
*(--stk) = (uint32_t)0x18181818L;             /* X18           */
*(--stk) = (uint32_t)0x17171717L;             /* X17           */
*(--stk) = (uint32_t)0x16161616L;             /* X16           */
*(--stk) = (uint32_t)0x15151515L;             /* X15           */
*(--stk) = (uint32_t)0x14141414L;             /* X14           */
*(--stk) = (uint32_t)0x13131313L;             /* X13           */
*(--stk) = (uint32_t)0x12121212L;             /* X12           */
*(--stk) = (uint32_t)0x11111111L;             /* X11           */
*(--stk) = (uint32_t)arg;                     /* X10           */
*(--stk) = (uint32_t)0x09090909L;             /* X9            */
*(--stk) = (uint32_t)0x08080808L;             /* X8            */
*(--stk) = (uint32_t)0x07070707L;             /* X7            */
*(--stk) = (uint32_t)0x06060606L;             /* X6            */
*(--stk) = (uint32_t)0x05050505L;             /* X5            */
*(--stk) = (uint32_t)0x04040404L;             /* X4            */
*(--stk) = (uint32_t)gp;                      /* X3            */
*(--stk) = (uint32_t)krhino_task_deathbed;    /* X1            */
return stk;

}

2.1.3 内核心跳时钟初始化
内核心跳时钟主要用于系统时钟的计时,系统任务的切换等。我们可以采用一个普通的定时器来做为系统心跳时钟。

SystemInit
该功能函数定义在chip_ch2601/sys/system.c, 实现对整个系统的进行初始化,包括对系统内核时钟,CACHE初始化等。

void SystemInit(void)
{

enable_theadisaee();
cache_init();
section_init();
interrupt_init();
soc_set_sys_freq(CPU_196_608MHZ);
csi_etb_init();
sys_dma_init();
csi_tick_init();

ifdef CONFIG_XIP

sys_spiflash_init();

endif

bootrom_uart_uninit();

}

csi_tick_init
该功能函数在chip_ch2601/sys/tick.c,实现内核心跳的初始化,通过回调函数tick_event_cb 对系统时钟进行技术,同时通过调用krhino_tick_proc实现对系统任务的调度。

csi_error_t csi_tick_init(void)
{

csi_error_t ret;
csi_tick = 0U;
ret = csi_timer_init(&tick_timer, CONFIG_TICK_TIMER_IDX);
if (ret == CSI_OK) {
    ret = csi_timer_attach_callback(&tick_timer, tick_event_cb, NULL);
    if (ret == CSI_OK) {
        ret = csi_timer_start(&tick_timer, (1000000U / CONFIG_SYSTICK_HZ));
    }
}
return ret;

}
void csi_tick_increase(void)
{

csi_tick++;

}
static void tick_event_cb(csi_timer_t timer_handle, void arg)
{

csi_tick_increase();

if defined(CONFIG_KERNEL_RHINO)

krhino_tick_proc();

elif defined(CONFIG_KERNEL_FREERTOS)

xPortSysTickHandler();

elif defined(CONFIG_KERNEL_UCOS)

OSTimeTick();

endif

}

2.1.4 内核初始化
在任务启动前,需要对内核做初始化,最后调用aos_start来启动第一个任务。

aos_init
该功能函数位于aos/src/main.c, 用于初始化内核,启动第一个任务。

int pre_main(void)
{

/* kernel init */
aos_init();

ifdef CONFIG_OS_TRACE

trace_init_data();

endif

/* init task */
aos_task_new_ext(&app_task_handle, "app_task", application_task_entry,
                 NULL, INIT_TASK_STACK_SIZE, AOS_DEFAULT_APP_PRI);
/* kernel start */
aos_start();
return 0;

}

aos_start
该功能函数用于启动内核,运行第一个任务。

至此,YoC内核部分适配结束,编译通过后就可以进行Helloworld应用程序开发了。

2.2 开发helloworld程序
2.2.1 串口初始化
在app/src/init/init.c里完成board初始化函数里完成串口的初始化。

void board_yoc_init()
{

board_init();
// uart_csky_register(CONSOLE_UART_IDX);
console_init(CONSOLE_UART_IDX, 115200, 128);
ulog_init();
aos_set_log_level(AOS_LL_DEBUG);

LOGI(TAG, "Build:%s,%s",__DATE__, __TIME__);
board_cli_init();

}

console_init
该功能函数用于串口的初始化。

ulog_init
该功能函数用于打印功能的初始化。

2.2.2 打印Helloworld
最后在main函数里实现helloworld的循环打印。

int main(void)
{

board_yoc_init();
LOGD(TAG, "%s\n", aos_get_app_version());
while (1) {
    LOGD(TAG, "Hello world! YoC");
    sample_test();
    aos_msleep(1000);
}
return 0;

}

2.3. 编译运行
编译通过后,下载到RVB2601开发板后复位运行(具体下载运行操作可以参考RVB2601开发板快速上手教程),看到串口窗口出现一下打印,说明移植成功。
image.png

  1. 总结

RVB2601最小系统hellworld主要实现对YoC系统的内核适配,具备RTOS的基本能力,实现简单的串口打印。后续还有更精彩的实战案例,敬请期待。

相关文章
|
前端开发
AJAX请求 状态pending
AJAX请求 状态pending
1032 0
AJAX请求 状态pending
|
3月前
|
人工智能 JavaScript Linux
🚀 恢复 Cursor 缺失的功能!一键生成你的专属 AI 编程助手配置
🚀 恢复Cursor被移除的规则生成功能!`generate-cursor-rule`一键安装,AI智能分析项目,自动生成专属`.cursorrules`文件,支持全平台,零配置使用,提升开发效率,团队协作更统一。告别手动编写,让AI助手更懂你的代码风格!
319 9
|
6月前
|
SQL 人工智能 自然语言处理
阿里云 CIO 蒋林泉:AI 大模型时代,我们如何用 RIDE 实现 RaaS 的首次落地?
本文整理自阿里云智能集团 CIO 蒋林泉在 AICon 2025 深圳的演讲,分享了阿里云在大模型应用落地中的实践经验。通过多个数字人项目案例,探讨了企业在 AI 应用中的组织转型、业务识别、产品定义与工程落地等关键环节,并提出了 RIDE 方法论(重组、识别、定义、执行),助力企业实现 AI 有效落地。
|
9月前
|
人工智能 自然语言处理 前端开发
上线几天,轻松斩获10k,开源通用AI智能体Suna:一句话自动处理Excel/爬数据/写报告,程序员私人助理诞生!
Suna是由Kortix推出的全球首个开源通用型AI Agent,可通过自然语言对话自动完成浏览器操作、数据分析、系统管理等复杂任务。它具有“执行力”,能像人类员工一样理解指令并操作数字工具,支持自托管保障数据安全,适用于市场分析、学术研究、企业办公等场景。Suna的核心优势在于实现“语言→行动”的转化,适合需要实际操作的任务,如爬虫、报表生成和网站部署。项目地址为:https://github.com/kortix-ai/suna。
936 0
|
人工智能 自然语言处理 数据挖掘
从行业痛点到AI前沿:揭秘AGI时代企业培训的终极之选
近几年接触到的各类培训合作方越来越多,从国际咨询巨头、互联网科技培训平台,到本土独角兽型的专业培训公司;从专攻新技术与创新场景的培训团队,到深谙传统行业痛点的咨询顾问。作为一名在央企、国企、上市公司人力资源培训条线深耕多年的HR负责人,深知在这片竞争激烈的培训服务蓝海中,寻找高质、高效的合作伙伴并不简单,因为企业培训的逻辑正在悄然改变。
|
运维 程序员 数据库
如何用TCC方案轻松实现分布式事务一致性
TCC(Try-Confirm-Cancel)是一种分布式事务解决方案,将事务拆分为尝试、确认和取消三步,确保在分布式系统中实现操作的原子性。它旨在处理分布式环境中的数据一致性问题,通过预检查和资源预留来降低失败风险。TCC方案具有高可靠性和灵活性,但也增加了系统复杂性并可能导致性能影响。它需要为每个服务实现Try、Confirm和Cancel接口,并在回滚时确保资源正确释放。虽然有挑战,TCC在复杂的分布式系统中仍被广泛应用。
980 5
|
并行计算 API 流计算
Flink之处理函数 (ProcessFunction)1
Flink之处理函数 (ProcessFunction)
785 0
|
Ubuntu
vscode配置clang-format自动格式化代码
vscode配置clang-format自动格式化代码
3810 0
|
消息中间件 Linux 芯片
RT-Thread快速入门-体验RT-Thread
RT-Thread快速入门-体验RT-Thread
480 0
RT-Thread快速入门-体验RT-Thread
|
开发框架 移动开发 虚拟化
uniapp打包之配置MacOS虚拟机生成iOS打包证书
uniapp是一款跨端开发框架,可用于快速开发iOS、Android、H5等多端应用。本文将详细介绍如何实现uniapp开发的iOS应用打包。
715 0