K210内置了丰富的加速器,包括神经网络处理器 (KPU),AES(高级加密加速器),APU 麦克风阵列语音数据加速计算处理器,现场可编程 IO 阵列 (FPIOA),数字摄像头接口 (DVP),相对于软件可以极大的提高 AES 运算速度,快速傅里叶变换加速器 (FFT),安全散列算法加速器 (SHA256)。
本文介绍内置的卷积人工神经网络硬件加速器 KPU;
KPU 是通用的神经网络处理器,它可以在低功耗的情况下实现卷积神经网络计算,实时获取被检测目
标的大小、坐标和种类,对人脸或者物体进行检测和分类。使用 kpu 时,必须结合 model compiler。
KPU 是通用神经网络处理器,内置卷积、批归一化、激活、池化运算单元,可以对人脸或物体进行实
时检测,具体特性如下:
• 支持主流训练框架按照特定限制规则训练出来的定点化模型
• 对网络层数无直接限制,支持每层卷积神经网络参数单独配置,包括输入输出通道数目、输入输
出行宽列高
• 支持两种卷积内核 1x1 和 3x3
• 支持任意形式的激活函数
• 实时工作时最大支持神经网络参数大小为 5.5MiB 到 5.9MiB
• 非实时工作时最大支持网络参数大小为(Flash 容量-软件体积)
KPU 的内部结构如下图所示:
对应的头文件 kpu.h
为用户提供以下接口:
• kpu_task_init (0.6.0 以后不再支持,请使用kpu_single_task_init):初始化kpu 任务句柄,该函数具体实现在model compiler 生成的gencode_output.c 中。
• kpu_run (0.6.0 以后不再支持,请使用kpu_start):启动KPU,进行AI 运算。
• kpu_get_output_buf (0.6.0 以后不再支持):获取KPU 输出结果的缓存。
• kpu_release_output_buf (0.6.0 以后不再支持):释放KPU 输出结果缓存。
• kpu_start:启动KPU,进行AI 运算。
• kpu_single_task_init:初始化kpu 任务句柄。
• kpu_single_task_deinit:注销kpu 任务。
• kpu_model_load_from_buffer:解析kmodel 并初始化kpu 句柄。
• kpu_load_kmodel:加载kmodel,需要与nncase 配合使用。
• kpu_model_free:释放kpu 资源。
• kpu_get_output:获取KPU 最终处理的结果。
• kpu_run_kmodel:运行kmodel。
本实验代码较多,这里只贴出main.c的内容,其余内容稍后上传到gitee上
#include
#include
#include
#include
#include "dvp.h"
#include "fpioa.h"
#include "lcd.h"
#include "ov2640.h"
#include "ov9655.h"
#include "plic.h"
#include "sysctl.h"
#include "uarths.h"
#include "st7789.h"
#include "dvp_cam.h"
#include "utils.h"
#include "kpu.h"
#include "l_conv.h"
#include "sleep.h"
#include "encoding.h"
#include "gpiohs.h"
#include "pin_config.h"
#include "dvp_cam.h"
int key_flag = 0;
gpio_pin_value_t key_state = 1;
volatile uint8_t g_ai_done_flag;
uint8_t g_ai_buf_out[320 * 240 * 3] __attribute__((aligned(128)));
/* KPU完成 */
static int kpu_done(void *ctx)
{
g_ai_done_flag = 1;
return 0;
}
// 卷积 池化 批归一化 激活 输出偏置
float conv_data[9*3*3] ={
//origin
//R
0,0,0,0,1,0,0,0,0,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//G
0,0,0,0,0,0,0,0,0,
0,0,0,0,1,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//B
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
0,0,0,0,1,0,0,0,0,
};
int demo_index=0;
const float conv_data_demo[4][9*3*3] ={
{ //origin
//R
0,0,0,0,1,0,0,0,0,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//G
0,0,0,0,0,0,0,0,0,
0,0,0,0,1,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//B
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
0,0,0,0,1,0,0,0,0,},
{ //edge
//R
-1,-1,-1,-1,8,-1,-1,-1,-1,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//G
0,0,0,0,0,0,0,0,0,
-1,-1,-1,-1,8,-1,-1,-1,-1,
0,0,0,0,0,0,0,0,0,
//B
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
-1,-1,-1,-1,8,-1,-1,-1,-1,},
{ //sharp
//R
-1,-1,-1,-1,9,-1,-1,-1,-1,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//G
0,0,0,0,0,0,0,0,0,
-1,-1,-1,-1,9,-1,-1,-1,-1,
0,0,0,0,0,0,0,0,0,
//B
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
-1,-1,-1,-1,9,-1,-1,-1,-1,},
{ //relievo
//R
2,0,0,0,-1,0,0,0,-1,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
//G
0,0,0,0,0,0,0,0,0,
2,0,0,0,-1,0,0,0,-1,
0,0,0,0,0,0,0,0,0,
//B
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
2,0,0,0,-1,0,0,0,-1,},
};
/**
* Function hardware_init
* @brief 硬件初始化,绑定GPIO口
* @param[in] void
* @param[out] void
* @retval void
* @par History 无
*/
void hardware_init(void)
{
/* 按键 */
fpioa_set_function(PIN_KEYPAD_MIDDLE, FUNC_KEYPAD_MIDDLE);
/* LCD */
fpioa_set_function(PIN_LCD_CS, FUNC_LCD_CS);
fpioa_set_function(PIN_LCD_RST, FUNC_LCD_RST);
fpioa_set_function(PIN_LCD_RS, FUNC_LCD_RS);
fpioa_set_function(PIN_LCD_WR, FUNC_LCD_WR);
// DVP camera
fpioa_set_function(PIN_DVP_RST, FUNC_CMOS_RST);
fpioa_set_function(PIN_DVP_PWDN, FUNC_CMOS_PWDN);
fpioa_set_function(PIN_DVP_XCLK, FUNC_CMOS_XCLK);
fpioa_set_function(PIN_DVP_VSYNC, FUNC_CMOS_VSYNC);
fpioa_set_function(PIN_DVP_HSYNC, FUNC_CMOS_HREF);
fpioa_set_function(PIN_DVP_PCLK, FUNC_CMOS_PCLK);
fpioa_set_function(PIN_DVP_SCL, FUNC_SCCB_SCLK);
fpioa_set_function(PIN_DVP_SDA, FUNC_SCCB_SDA);
// 使能SPI0和DVP
sysctl_set_spi0_dvp_data(1);
}
/**
* Function io_set_power
* @brief 设置bank6/bank7电源域1.8V
* @param[in] void
* @param[out] void
* @retval void
* @par History 无
*/
static void io_set_power(void)
{
/* Set dvp and spi pin to 1.8V */
sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
}
/* 转化图像数据格式,因为摄像头输出到AI的是RGB888格式,而显示屏需要RGB565格式 */
void rgb888_to_565(uint8_t *src_r, uint8_t *src_g, uint8_t *src_b, uint16_t *dst, uint32_t len)
{
uint32_t i;
for (i = 0; i < len; i += 2)
{
dst[i] = (((uint16_t)(src_r[i + 1] >> 3)) << 11) +
(((uint16_t)src_g[i + 1] >> 2) << 5) +
(((uint16_t)src_b[i + 1]) >> 3);
dst[i + 1] = (((uint16_t)(src_r[i] >> 3)) << 11) +
(((uint16_t)src_g[i] >> 2) << 5) +
(((uint16_t)src_b[i]) >> 3);
}
}
/* 在原来图像的基础上增加数据(字符) */
void lcd_ram_cpyimg(char *lcd, int lcdw, char *img, int imgw, int imgh, int x, int y)
{
int i;
for (i = 0; i < imgh; i++)
{
memcpy(lcd + lcdw * 2 * (y + i) + x * 2, img + imgw * 2 * i, imgw * 2);
}
return;
}
/* 左上角显示模式 */
void draw_text(void)
{
char string_buf[8 * 16 * 2 * 16]; //16个字符
char title[20];
switch (demo_index)
{
case 0:
sprintf(title, " origin ");
lcd_ram_draw_string(title, (uint32_t *)string_buf, BLUE, BLACK);
lcd_ram_cpyimg((char *)g_display_buf, 320, string_buf, strlen(title) * 8, 16, 0, 0);
break;
case 1:
sprintf(title, " edge ");
lcd_ram_draw_string(title, (uint32_t *)string_buf, BLUE, BLACK);
lcd_ram_cpyimg((char *)g_display_buf, 320, string_buf, strlen(title) * 8, 16, 0, 0);
break;
case 2:
sprintf(title, " sharp ");
lcd_ram_draw_string(title, (uint32_t *)string_buf, BLUE, BLACK);
lcd_ram_cpyimg((char *)g_display_buf, 320, string_buf, strlen(title) * 8, 16, 0, 0);
break;
case 3:
sprintf(title, "relievos");
lcd_ram_draw_string(title, (uint32_t *)string_buf, BLUE, BLACK);
lcd_ram_cpyimg((char *)g_display_buf, 320, string_buf, strlen(title) * 8, 16, 0, 0);
break;
default:
break;
}
}
/* 按键中断回调 */
int key_irq_cb(void *ctx)
{
key_flag = 1;
key_state = gpiohs_get_pin(KEYPAD_MIDDLE_GPIONUM);
return 0;
}
/* 初始化按键 */
void init_key(void)
{
// 设置按键的GPIO模式为上拉输入
gpiohs_set_drive_mode(KEYPAD_MIDDLE_GPIONUM, GPIO_DM_INPUT_PULL_UP);
// 设置按键的GPIO电平触发模式为上升沿和下降沿
gpiohs_set_pin_edge(KEYPAD_MIDDLE_GPIONUM, GPIO_PE_BOTH);
// 设置按键GPIO口的中断回调
gpiohs_irq_register(KEYPAD_MIDDLE_GPIONUM, 1, key_irq_cb, NULL);
}
/**
* Function main
* @brief 主函数,程序的入口
* @param[in] void
* @param[out] void
* @retval void
* @par History 无
*/
int main(void)
{
hardware_init();
io_set_power();
/* 设置系统时钟和DVP时钟 */
sysctl_pll_set_freq(SYSCTL_PLL0, 800000000UL);
sysctl_pll_set_freq(SYSCTL_PLL1, 300000000UL);
sysctl_pll_set_freq(SYSCTL_PLL2, 45158400UL);
uarths_init();
/* 系统中断初始化 */
plic_init();
/* 使能系统全局中断 */
sysctl_enable_irq();
/* 初始化显示屏,并显示一秒图片 */
printf("LCD init\r\n");
lcd_init();
lcd_draw_picture_half(0, 0, 320, 240, gImage_logo);
sleep(1);
/* ov摄像头初始化 */
int OV_type;
OV_type=OVxxxx_read_id();
/* 初始化摄像头 */
if(OV_type == OV_9655)
{
ov9655_init();
}
else if(OV_type == OV_2640)
{
ov2640_init();
}
else
{
return 0;//打不开摄像头,结束
}
/* 按键初始化*/
init_key();
/* kpu初始化 */
kpu_task_t task;
conv_init(&task, CONV_3_3, conv_data);
printf("KPU TASK INIT, FREE MEM: %ld\r\n", get_free_heap_size());
printf("Please press the keypad to switch mode\r\n");
while (1)
{
while (g_dvp_finish_flag == 0)
;
/* 开始运算 */
conv_run(&task, g_ai_buf_in, g_ai_buf_out, kpu_done);
while (!g_ai_done_flag)
;
g_ai_done_flag = 0;
g_dvp_finish_flag = 0;
/* 转化成LCD支持的RGB565格式 */
rgb888_to_565(g_ai_buf_out, g_ai_buf_out + 320 * 240, g_ai_buf_out + 320 * 240 * 2,
(uint16_t *)g_display_buf, 320 * 240);
/* 左上角写字母提示是哪个模式 */
draw_text();
/* 显示图像 */
lcd_draw_picture(0, 0, 320, 240, g_display_buf);
if (key_flag) //使用按键选择的卷积核
{
if (key_state == 0) //按下
{
msleep(20); //延迟去抖
key_flag = 0;
demo_index = (demo_index + 1) % 4;
memcpy((void *)conv_data, (void *)(conv_data_demo[demo_index]),
3 * 3 * 3 * 3 * sizeof(float));
conv_init(&task, CONV_3_3, conv_data);
}
else //弹起
{
msleep(20); //延迟去抖
key_flag = 0;
}
}
}
return 0;
}
代码完成后,进行编译
cd build
cmake .. -DPROJ=kpu -G "MinGW Makefiles"
make
编译完成后,在build文件夹下会生成kpu.bin文件。
使用type-C数据线连接电脑与K210开发板,打开kflash,选择对应的设备,再将程序固件烧录到K210开发板上。
实验结果:烧录固件完成后,系统会自动弹出一个终端窗口,并且打印一些初始化的信息,此时我们看显示器的已经显示了摄像头当前采集的画面,而且左上角还有一个‘origin’的单词,当我们按下keypad中间的键时,模式切换,LCD显示的画面会变化,除了原始画面,还有其他三种模式可以显示,每按一次keypad都可以切换一次模式。
K210芯片内置了卷积人工神经网络硬件加速器KPU,它可以在低功耗的情况下实现卷积神经网络计算,实时获取被检测目
标的大小、坐标和种类,对人脸或者物体进行检测和分类;