写点什么

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

发布于: 2 小时前

RVB2601 应用开发实战系列一: Helloworld 最小系统关键词:RVB2601、RISC-V 开发板、例程,低功耗,玄铁 E906,RISC-V MCU,上手,好用,控制,WiFi&BLE


1.引言

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


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


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


2.最小系统移植适配

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


2.1 适配 YoC 内核

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


由于 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 开发板快速上手教程),看到串口窗口出现一下打印,说明移植成功。


3. 总结

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


本文转自平头哥芯片开放社区(occ),更多详情请点击https://occ.t-head.cn/store/board?channelName=1

用户头像

还未添加个人签名 2021.08.24 加入

还未添加个人简介

评论

发布
暂无评论
RVB2601 应用开发实战系列一: Helloworld 最小系统