K210 Standalone SDK,用C点亮野火K210

         本文旨在记录我在裸机开发K210时的一些经历和遇到的问题。

        用canMV IDE开发K210时,我发现官方API提供的图像处理不能完全满足我的需求,但用micropython的for循环来实现计算在性能上显然是不现实的。一拍脑门,我就打算试试用C来开发。

主要资料

来自GitHub用户Kendryte · GitHub发布的:

SDK源码:https://github.com/kendryte/kendryte-freertos-sdk

嘉楠官方例程:https://github.com/kendryte/kendryte-standalone-demo

canmv源码:https://github.com/kendryte/canmv(也可在野火K210网盘资料中找到)

野火K210网盘资料:https://pan.baidu.com/s/17poZ0Vq_4zEq-n3oljPGaQ?pwd=txcd,其中能找到一份Standalone SDK的API手册。

K210 standalone C开发_k210 c语言sdk-CSDN博客

环境搭建

 关于环境,我参考了 SDK源码:https://github.com/kendryte/kendryte-freertos-sdk

根据README中的教程我完成了环境的搭建,在此不多赘述。

点亮LCD屏幕

        我买到的开发板,使用的是st7789,很遗憾在官方例程中使用的是nt35310,我只能在canmv源码中寻找我需要的驱动

        我在cammv的源码中注意到了这些文件:st7789.c、st7789.h、lcd.h、mcu_lcd.c、rgb_lcd.c。为了满足它们的依赖,我还找到了:sipeed_spi.h、sipeed_type.h、sipeed_spi.c(这三者是cammv对底层spi.h的封装)、global_config.h(这个很明显是全局配置文件,有坑的是,这个文件初始是不存在于项目中的,只有当项目构建时才会动态生成,为此我在project文件夹中随意找了个项目,并用cmake工具尝试构建了一些,当然没有成功,但是成功生成了global_config.h文件)。

        然后我对部分文件进行了一些修改,再在自己的代码中封装了一个lcd_init()。

#include <fpioa.h>
#include <bsp.h>
#include <dmac.h>

#include "lcd.h"

#define CAM_WIDTH_PIXEL 320
#define CAM_HIGHT_PIXEL 240
#define DISPLAY_BUFFER_SIZE (CAM_WIDTH_PIXEL * CAM_HIGHT_PIXEL * 2)

lcd_t *lcd = &lcd_mcu;

void lcd_init()
{
    lcd_para_t *lcd_para = lcd->lcd_para;
    lcd_para->oct = true;
    lcd_para->freq = 20000000;
    lcd_para->rst_pin = 29;
    lcd_para->cs_pin = 28;
    lcd_para->dcx_pin = 27;
    lcd_para->clk_pin = 26;

    sysctl_set_spi0_dvp_data(1);
    
    fpioa_set_function(27, FUNC_GPIOHS0 + DCX_GPIONUM);
    fpioa_set_function(28, FUNC_SPI0_SS3);
    fpioa_set_function(26, FUNC_SPI0_SCLK);
    fpioa_set_function(29, FUNC_GPIOHS0 + RST_GPIONUM);

    fpioa_set_function(30, FUNC_GPIOHS0 + RD_GPIONUM);
    gpiohs_set_drive_mode(RD_GPIONUM, GPIO_DM_OUTPUT);
    gpiohs_set_pin(RD_GPIONUM, GPIO_PV_HIGH); 

    lcd->init(lcd_para);
    lcd->set_direction(DIR_YX_LRDU);
    lcd->clear(RED);
}

        注意调用fpioa_set_function时要确认自己板子的引脚,必要的话可以向客服请求原理图。

        在编译时,如果没有对lcd.c进行修改,可能会报出maixpy_sdcard_loading和dual_func未定义。对前者,我选择把相关代码全部删除,因为我暂时还没有写关于SD卡的代码,spi不会冲突。对后者,可以在main文件中添加以下代码(该代码可以参考canmv源码中的maixpy_main.c):

typedef int (*dual_func_t)(int);
volatile dual_func_t dual_func;

int core1_function(void *ctx)
{
    uint64_t core = current_coreid();
    while (1)
    {
        if (dual_func)
        {
            dual_func(core);
            dual_func = NULL;
        }
        else
        {
            msleep(10);
        }
    }
}

然后记得在主函数中要调用
    fpioa_init();
    dmac_init();
    register_core1(core1_function, NULL);
这样就可以得到一个纯色的lcd屏啦。

摄像头接收

        我买到的摄像头是ov2640,很幸运,该驱动在官方例程中就能找到。当然在canmv源码中也能找到相关驱动,但它的依赖关系就复杂了,因此我不推荐使用。

        参照例程代码、官方手册示例(在野火的资料里找到的)和博主,我完成了自己的代码。

        首先是回调函数:

void on_irq_dvp(void*ctx)
{
    if(dvp_get_interrupt(DVP_STS_FRAME_FINISH))
    {
        g_dvp_buff = (g_dvp_buff == g_lcd_gram0) ? g_lcd_gram1 : g_lcd_gram0;
        dvp_set_display_addr((uint32_t)g_dvp_buff);
        dvp_clear_interrupt(DVP_STS_FRAME_FINISH);
        g_dvp_finish_flag = true;
    } 
    else
    {
        if(!g_dvp_finish_flag)
            dvp_start_convert();
        dvp_clear_interrupt(DVP_STS_FRAME_START);
    }
    return 0;
}

        然后是初始化(相关引脚的重映射没有写在该函数中):

#include <dvp.h>
#include <plic.h>
#include <iomem.h>

#include "ov2640.h"

extern uint16_t *g_lcd_display_buff;
volatile bool g_dvp_finish_flag = false;
uint16_t *g_lcd_gram0;
uint16_t *g_lcd_gram1;

void dvp_cam_init(void)
{    
    sysctl_set_spi0_dvp_data(1);
    /* DVP初始化,设置sccb的寄存器长度为8bit */
    dvp_init(8);
    /* 设置输入时钟为24000000*/
    dvp_set_xclk_rate(24000000);
    /* 使能突发传输模式 */
    dvp_enable_burst();
    /* 关闭AI输出模式,使能显示模式 */
    dvp_set_output_enable(DVP_OUTPUT_AI, 0);
    dvp_set_output_enable(DVP_OUTPUT_DISPLAY, 1);
    /* 设置输出格式为RGB */
    dvp_set_image_format(DVP_CFG_RGB_FORMAT);
    /* 设置输出像素大小为320*240 */
    dvp_set_image_size(CAM_WIDTH_PIXEL, CAM_HIGHT_PIXEL);
    
    /* 设置DVP的显示地址参数和中断 */
    g_lcd_gram0 = (uint16_t *)iomem_malloc(DISPLAY_BUFFER_SIZE);
    g_lcd_gram1 = (uint16_t *)iomem_malloc(DISPLAY_BUFFER_SIZE);

    g_dvp_buff = g_lcd_gram0;
    dvp_set_display_addr((uint32_t)g_dvp_buff);
    dvp_config_interrupt(DVP_CFG_START_INT_ENABLE | DVP_CFG_FINISH_INT_ENABLE, 0);
    dvp_disable_auto();

    plic_set_priority(IRQN_DVP_INTERRUPT, 1);
    plic_irq_register(IRQN_DVP_INTERRUPT, on_irq_dvp, NULL);
    plic_irq_enable(IRQN_DVP_INTERRUPT);

    ov2640_init();

    dvp_clear_interrupt(DVP_STS_FRAME_START | DVP_STS_FRAME_FINISH);
    dvp_config_interrupt(DVP_CFG_START_INT_ENABLE | DVP_CFG_FINISH_INT_ENABLE, 1);
}

        在此,我要提一嘴我遇到的一个坑了,就是volatile这个关键字。在最初我没有给g_dvp_finish_flag变量加上volatile关键字,结果发现每次按下reset按钮后,屏幕就会显示一张图片,然后卡死。经调试,发现是由于缓存一致性冲突引起的,K210的cpu核心有一级缓存,中断函数只改变了内存中的变量,而cpu核心在先前已经从内存读取了这个变量,因此cpu默认从缓存中读取,而缓存中的变量并没有从内存更新。

摄像头显示

        照理来说,既然lcd驱动完成了,摄像头读取也完成了,那么显示应当是很简单的事情了,只要在主循环中调用函数就行了。但是我对canmv源码中的图片显示函数并不满意,因为直接复制下来的代码,是会把传输得到的图像数据复制一份再发送到lcd屏幕中。我认为,这复制一份的行为,是完全没有必要的,因此首先要对lcd驱动进行一番修改。

        在lcd_mcu.c中:

volatile bool g_lcd_draw_picture_finish_flag = true;
static uint32_t g_pixs_draw_pic_size = 0;
static uint32_t g_pixs_draw_pic_half_size = 0;

static void mcu_lcd_draw_picture(uint16_t x1, uint16_t y1, uint16_t width, uint16_t height, uint8_t *ptr)
{
    g_lcd_draw_picture_finish_flag = false;
    g_lcd_display_buff = (uint16_t *)ptr;
    lcd_set_area(x1, y1, x1 + width - 1, y1 + height - 1);
    g_pixs_draw_pic_size = width * height;
    if(g_pixs_draw_pic_size % 2)
    {
        tft_write_half(g_lcd_display_buff, g_pixs_draw_pic_size);
    }
    else
    {
        tft_write_word((uint32_t *)g_lcd_display_buff, g_pixs_draw_pic_size / 2);
    }
    g_lcd_draw_picture_finish_flag = true;
}

然后在main.c中定义以下引脚重映射函数和电压域分配函数:        

static void io_mux_init(void)
{

    /* Init DVP IO map and function settings */
    fpioa_set_function(18, FUNC_CMOS_VSYNC);
    fpioa_set_function(19, FUNC_CMOS_RST);
    fpioa_set_function(20, FUNC_CMOS_HREF);
    fpioa_set_function(21, FUNC_CMOS_PWDN);
    fpioa_set_function(22, FUNC_CMOS_XCLK);
    fpioa_set_function(23, FUNC_CMOS_PCLK);

    fpioa_set_function(25, FUNC_SCCB_SCLK);
    fpioa_set_function(24, FUNC_SCCB_SDA);

    /* Init SPI IO map and function settings */
    fpioa_set_function(27, FUNC_GPIOHS0 + DCX_GPIONUM);
    fpioa_set_function(28, FUNC_SPI0_SS3);
    fpioa_set_function(26, FUNC_SPI0_SCLK);
    fpioa_set_function(29, FUNC_GPIOHS0 + RST_GPIONUM);

    sysctl_set_spi0_dvp_data(1);
}

static void io_set_power(void)
{
    sysctl_set_power_mode(SYSCTL_POWER_BANK3, SYSCTL_POWER_V18);
    sysctl_set_power_mode(SYSCTL_POWER_BANK4, SYSCTL_POWER_V18);
}

最后是main()函数:

int main(void)
{
    sysctl_pll_set_freq(SYSCTL_PLL0, 800000000UL);
    sysctl_pll_set_freq(SYSCTL_PLL1, 160000000UL);
    sysctl_pll_set_freq(SYSCTL_PLL2, 45158400UL);

    fpioa_init();
    dmac_init();
    plic_init();
    sysctl_enable_irq();
    io_mux_init();
    io_set_power();

    register_core1(core1_function, NULL);

    dvp_cam_init();
    lcd_init();
    while(1)
    {
        while(g_dvp_finish_flag == 0)
            ;
        g_dvp_finish_flag = 0;
        lcd->draw_picture(0, 0, 320, 240, (uint8_t*)((g_dvp_buff == g_lcd_gram0) ? g_lcd_gram1 : g_lcd_gram0));
    }
}

 这下就大功告成啦。

接下来我就可以对读到的摄像头数据做任何我想做的事情了。

使用Arduino开发K210

        在完成了以上工作之后,我突然在嘉楠官网发现了这个:开始 — Arduino-K210 文档 (canaan-creative.com)

        于是我去尝试了一下,下载下来后,我翻看了一下源码,在驱动中,我发现了大量注释调的函数,似乎是还在施工中。

        然后我悲伤地发现他没有我买到的版型,但我在board_config.md中找到了添加版型的教程,然后按照从客服那里要到的原理图我,我添加了我的版型。

        接着我用官方的例程进行了测试比如test_rotation.ino、screen_display.ino,发现lcd屏可以正常打开,但摄像头驱动有问题,原因暂时未知,还有待排查。过程中我还下载了一个依赖库Adafruit-GFX,移出了一些不兼容的库。

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值