页次: 1
在入手全志T113之后,第一时间移植好了之前6ull平台的rootfs。但是在测试QT的过程中发现屏幕最右侧有一部分显示不正常,经过初步推测应该是RGB行场同步时序有问题。本以为在设备树里面稍作修改之后就能OK,但是居然前前后后一共花了至少三个星期的时间。
这里就不给分析经过了,因为是真的真的太漫长了,搞得我头都大了。真的是要吐槽一下全志的代码:
1.函数名字真的看得头疼
2.代码杂乱无章,大量使用全局变量,让分析各种没头绪。
然后在这里直接给出最终分析的结果:
1.设备树里面timing,除了width、height、lcd_x和lcd_y之外的参数全没有用上(一开始不停的源码里面找这些参数在什么地方配置的,最后发现特么的根本就没有地方使用过这些参数)
2.width和height主要是用来计算dpi使用的,我这里写的280最终计算出来的dpi大概是97左右,windows标准的是96,反正相差无几我也没有细调了,lcd_x和lcd_y在原来的代码里主要是用来计算内核里面有一张图片的缩放使用的。
3.lcd timing寄存器里面的值是在uboot里面设置好之后在kernel里面直接拿来用的。
4.uboot里面的timing计算方式有问题,应该是百问没有仔细阅读全志文档的原因造成的,这也是使得屏幕显示向左平移了几十个像素的原因
5.我忘记我要说什么了…
接下来就说怎么处理吧:
1.首先按照全志的文档把设备树里面的timing改了
2.打开 drivers/video/fbdev/sunxi/disp2/disp/de/disp_lcd.c 然后找到 s32 disp_init_lcd(struct disp_bsp_init_para* para) 函数,在前面加上一段代码
typedef struct
{
volatile unsigned int Timing0;
volatile unsigned int Timing1;
volatile unsigned int Timing2;
volatile unsigned int Timing3;
}RegTypeDef;
static void set_lcd_timings(unsigned int dwLcdRegBase, struct disp_video_timings* pTimings)
{
RegTypeDef* reg;
unsigned int dwWidth, dwHeight;
unsigned int dwHT, dwHBP;
unsigned int dwVT, dwVBP;
unsigned int dwHSPW, dwVSPW;
reg = (RegTypeDef*)(dwLcdRegBase + 0x48);
dwWidth = pTimings->x_res;
dwHSPW = pTimings->hor_sync_time;
dwHBP = pTimings->hor_back_porch + pTimings->hor_sync_time; //在官方文档里:hbp = hbp + hspw,然而在全志的代码里面又将hspw其减去,所以在这里需要加回来
dwHT = pTimings->hor_total_time;
dwHeight = pTimings->y_res;;
dwVSPW = pTimings->ver_sync_time;
dwVBP = pTimings->ver_back_porch + pTimings->ver_sync_time;
dwVT = pTimings->ver_total_time;
reg->Timing0 = ((dwWidth - 1) << 16) | (dwHeight - 1);
reg->Timing1 = ((dwHT - 1) << 16) | (dwHBP - 1);
reg->Timing2 = (((dwVT - 1) * 2) << 16) | (dwVBP - 1);
reg->Timing3 = ((dwHSPW - 1) << 16) | (dwVSPW - 1);
printk("width: %d, hspw: %d, hbp: %d, ht: %d\n", dwWidth, dwHSPW, dwHBP, dwHT);
printk("height: %d, vspw: %d, vbp: %d, vt: %d\n", dwHeight, dwVSPW, dwVBP, dwVT);
}
然后在 s32 disp_init_lcd(struct disp_bsp_init_para* para) 函数的 disp_lcd_init(lcd, lcd->disp); 后面加上
set_lcd_timings(para->reg_base[DISP_MOD_LCD0], &lcd->timings);
3.最后编译烧写进去之后重启就搞定了…
4.uboot我就懒得修改了,凑合用吧
公子·小白是一对由狗尾草智能科技推出的人工智能机器人,该系列中的产品包括公子小白、公子小白Pro、Smart Plus公子小白、公子小白成长版、公子小白青春版......这次要拆的这个机器人是公子小白系列,它在16/17年左右首发价是1880元,但这次从闲鱼淘来它,只花了60元。
公子小白机器人主打“情感社交”场景,机器人背后的狗尾草智能科技算是国内比较早一批进行AI语音交互机器人设计的公司,这要是放到现在也可以算是半个ChatGPT概念公司了。
拆开外包装,看到这个说明书,中间连接的钉子都已经生锈了,生产时间写的是2017年的2月。17年生产的东西到现在还是全新未开封啊,可见当时的备货太多了,也是奔着百万级市场去做的,但是现在公司的官网和APP都显示不可用了,微信公众号的最新推文日期已然停留在了2020年,估计已经停止官方支持了。
确实,当时这个产品是领先时代的,他们的slogan是“AI虚拟生命终将走进每一个人的生活”,翻译一下就是前些时间爆火的元宇宙概念吗?AI、智慧生命、元宇宙、ChatGPT,这些都是时下最火热的行业话题。
但回到16/17年,那时的人工智能发展尚未成熟,只能进行单论的对话,并且经常答非所问,难一点的问题都无法回答,也无法通过图灵测试,那时候我们经常把人工智能说是“人工智障”,但是如果这个产品放到现在来做,接任ChatGPT,或许会是另一番光景啊。
拆解
这个机器人的表面十分圆滑,想拆开它还要费点劲,在机器人外部找不到任何一颗裸露的螺丝钉,经常一番捣鼓才发现它的拆解之道,首先需要找到隐藏的硅胶塞把它挖出来,把钉子卸了,然后强行把头给撬下来,这样就可以看到里面的大致结构了。
首先看到主板上,主控是全志的R58,一款8核的芯片,上面固定了散热片,这颗芯片的价格差不多是10美金,折合人民币60多块钱,如果单从淘散料倒卖的角度来说,就凭这一颗主控就已经差不多回本了,那么到这里拆解就结束了。
全志R58,8核A7*1.8GHz,这差不多是十年前的芯片啊,十年前的国产8核芯片,早期很多国内的带屏智能音箱、视频电话用的都是全志这颗R58的芯片,比如小鱼在家的视频电话,小鱼在家后来被百度收购了,于是也就成了后来小度在家智能视频音箱的原型。由此可见R58性能还是很高的,也有一些安卓平板选用了R58作为主控,在此基础上应该可以升级出很多有趣的玩法。
这里有一颗AXP813,是一颗电源管理芯片,集成电源管理,音频解码器,兼容USB3.0的充电器,RTC,模数转换器和硬件DSP,内部音频模块集成动态范围控制器,和自动增益控制,可在录音时提高信噪比,降低背景噪声干扰。这颗芯片也是全志旗下的,作为电源管理芯片一般都是跟主控成套片出售。
两颗1GB 的SK 海力士的DDR3。
一颗镁光的8GB的nand flash。
这有一颗AP6255的wifi/蓝牙模组,这个模组应该是支持2.4G和5G的,16年的时候5G的路由器都不是很普及,机器人内容没有选用AP6212而是用AP6255,真的是对未来充满了期待,是奔着这个产品生命周期会延续很多年来设计的。
这里有两颗科声讯的CX208,这是两颗语音处理芯片,里面集成了语音算法。比如降噪、回声消除、声源定位、唤醒词识别等等。但是我们现在基本都不用这种外挂的语音处理芯片了,一般是把语音处理部分放到主控里去跑,有的主控里还集成了专门跑语音算法的DSP或者NPU。
每颗CX208可以连接2颗麦克风,所以我们看到上面是一个4麦克风的阵列,用的是早期的驻极体麦克风,现在的智能语音AIOT产品基本都不用这种麦克风了,基本上的都是硅麦。
主板后面接了一个屏幕,是一个方形的5寸左右的屏幕,然后用这个结构件把它框起来,这样看起来就是弧形的脸,把边角的部分挡住,就可以显示那个机器人的各种表情了。这个挡板的结构设计也很有意思。
如果设备还能运行的话,可以看到这个屏幕上会显示很多表情。因为现在这家公司的服务器都不运行了,所以都玩不了了。但是我要是这家公司最后的员工啊,我就让这个屏幕继续播放广告,会像某不退押金的共享单车一样,榨干他的最后价值。
这上面还有一个摄像头,是用来扫码和人脸试别用的,他会试别人脸的位置,跟着人动,也可以对人的表情做出判断,比如你不开心就给你讲笑话安慰你,或者是识别到前面是小朋友的时候,就播放适合小朋友的内容。当然这些都是理论功能啊,不知道这些功能有没有实现。
然后下面是一个舵机,控制值这个机器人头转动的。我们来听一下这个声音,很有机器人感觉的那个声音,很有未来感,这么多年过去了和是很有质感,一看就是贵东西啊。
然后下面这个是充电环的设计,跟airpod耳机底部的充电环是一样的。airpod耳机那个是有专利的,很多耳机厂商早年做tws耳机的时候就中过招。
以上是比较重要的几个零部件介绍,其它的还有很多就不一一介绍了,最后上一张拆解的总览图。
结语
这是一个16年时候的人工智能产品啊,当时还处于智能语音发展的早期,算法、方案、供应链成熟度都还处于一个刚刚能做产品的状态。AI对话只能试别简单的内容,只能做单轮对话不能做多轮对话,我们说那个时候的人工智能是弱人工智能。
今年随着chatGPT的大火,国内外资本纷纷入局,人工智能又被推到了一个新的高度,如果这个产品放到现在做,或许或有更好的体验和更广阔的市场。
现在的chatGPT已经可以通过图灵测试了,智商达到了一个大学生的水平,但是这些输出在2023年4月的今天,还只是停留在纸面上,不过从技术难度上来说,让它和机器人结合落地已经不存在瓶颈了,只是把输出结果再转化一次成为行代命令而且,所以,我们很期待马上到来的强人工智能时代,期待有更多改变我们生活的伟大产品。
作者@chhjnavy
本帖转载自:https://bbs.aw-ol.com/topic/3291/
------------------------------------------------------------------
------------------------------------------------------------------
1. g2d 模块概述
g2d 主要功能:
1)旋转:支持90、180、270旋转;
2)镜像反转:H / V;
3) scale:放缩
4)格式转换:yuv 转 rgb 等,多种格式相互间转换;
5)透明叠加功能:实现两个rgb图片叠加;
6)矩形填充,等诸多功能;
2. g2d 配置
1)源码目录:tina-v853-docker/kernel/linux-4.9/drivers/char/sunxi_g2d
2)make kernel_menuconfig 配置
Device Drivers > Character devices > sunxi g2d driver
按空格键选中【*】
3)Device Tree 设备树配置
sun8iw21p1.dtsi路径:
tina-v853-docker/kernel/linux-4.9/arch/arm/boot/dts/sun8iw21p1.dtsi
g2d: g2d@05410000 {
compatible = "allwinner,sunxi-g2d";
reg = <0x0 0x05410000 0x0 0xbffff>;
interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_g2d>;
iommus = <&mmu_aw 3 1>;
status = "okay";
};
status 要设定为“okay” 状态。
4)重新编译内核
mkernel
make -j1 V=s
pack
使用烧录工具PhoenixSuit 将路径:tina-v853-docker/out/v851s/lizard/openwrt/v851s_linux_lizard_uart0.img 下的img 镜像烧录到开发板。
adb shell 打开控制终端查看设备节点G2D:
5)通过G2D 设备节点,对g2d 进行操作
static int SampleG2d_G2dOpen(SAMPLE_G2D_CTX *p_g2d_ctx)
{
int ret = 0;
p_g2d_ctx->mG2dFd = open("/dev/g2d", O_RDWR, 0);
if (p_g2d_ctx->mG2dFd < 0)
{
aloge("fatal error! open /dev/g2d failed");
ret = -1;
}
return ret;
}
3. 详解 eyesee-mpp 中g2d sample 具体应用
1)eyesee-mpp 中 g2d sample 编译与执行请参考另一篇帖子
链接文本
2)g2d sample 目录
3)运用 g2d 进行rotation,scale,格式转换
具体实现:将 nv21 格式的1920x1080图转换成rgb888 格式并放缩为640x360 大小。具体用到两个功能,格式转换和放缩。
步骤如下:
1))根据1920x1080 nv21 格式以及 640x360 rgb888 格式申请虚拟地址空间以及转换成物理地址(注意:g2d 转换是在物理地址中完成的)
1920x1080 nv21 格式空间大小(输入文件):
Y 占 19201080 = 2073600 字节
UV 占 19201080 / 2 = 1036800 字节
640x360 rgb888 格式空间大小(输出文件):
RGB 占 6403603 = 691200 字节
另外:虚拟地址转换成物理地址使用如下函数:
g2d_getPhyAddrByVirAddr()
申请虚拟空间并转换成物理空间完整函数如下:
在文件 tina-v853-docker/platform/allwinner/eyesee-mpp/middleware/sun8iw21/sample/sample_g2d/sample_g2d.c/sample_g2d.c 中
static int PrepareFrmBuff(SAMPLE_G2D_CTX *p_g2d_ctx)
{
SampleG2dConfig *pConfig = NULL;
unsigned int size = 0;
pConfig = &p_g2d_ctx->mConfigPara;
p_g2d_ctx->src_frm_info.frm_width = pConfig->mSrcWidth;
p_g2d_ctx->src_frm_info.frm_height = pConfig->mSrcHeight;
p_g2d_ctx->dst_frm_info.frm_width = pConfig->mDstWidth;
p_g2d_ctx->dst_frm_info.frm_height = pConfig->mDstHeight;
size = ALIGN(p_g2d_ctx->src_frm_info.frm_width, 16)*ALIGN(p_g2d_ctx->src_frm_info.frm_height, 16);
if(pConfig->mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420 || pConfig->mPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420)
{
p_g2d_ctx->src_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);
if(NULL == p_g2d_ctx->src_frm_info.p_vir_addr[0])
{
aloge("malloc_src_frm_y_mem_failed");
return -1;
}
p_g2d_ctx->src_frm_info.p_vir_addr[1] = (void *)g2d_allocMem(size/2);
if(NULL == p_g2d_ctx->src_frm_info.p_vir_addr[1])
{
g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);
aloge("malloc_src_frm_c_mem_failed");
return -1;
}
p_g2d_ctx->src_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[0]);
p_g2d_ctx->src_frm_info.p_phy_addr[1] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->src_frm_info.p_vir_addr[1]);
}
if(pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_888)
{
size = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height * 3;
p_g2d_ctx->dst_frm_info.p_vir_addr[0] = (void *)g2d_allocMem(size);
if(NULL == p_g2d_ctx->dst_frm_info.p_vir_addr[0])
{
if(p_g2d_ctx->src_frm_info.p_vir_addr[0] != NULL)
{
g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[0]);
}
if(p_g2d_ctx->src_frm_info.p_vir_addr[1] != NULL)
{
g2d_freeMem(p_g2d_ctx->src_frm_info.p_vir_addr[1]);
}
aloge("malloc_dst_frm_y_mem_failed");
return -1;
}
p_g2d_ctx->dst_frm_info.p_phy_addr[0] = (void *)g2d_getPhyAddrByVirAddr(p_g2d_ctx->dst_frm_info.p_vir_addr[0]);
}
return 0;
}
2))通过fopen 传菜间两个文件句柄,fd_in fd_out 用来操作输入输出两个文件资源
p_g2d_ctx->fd_in = fopen(p_g2d_ctx->mConfigPara.SrcFile,"r");
if(NULL == p_g2d_ctx->fd_in)
{
aloge("open src file failed");
ret = -1;
goto _err2;
}
fseek(p_g2d_ctx->fd_in, 0, SEEK_SET);
p_g2d_ctx->fd_out = fopen(p_g2d_ctx->mConfigPara.DstFile, "wb");
if (NULL == p_g2d_ctx->fd_out)
{
aloge("open out file failed");
ret = -1;
goto _err2;
}
fseek(p_g2d_ctx->fd_out, 0, SEEK_SET);
3))读出 1920x1080 nv21 图资放入 虚拟空间
read_len = p_g2d_ctx->src_frm_info.frm_width * p_g2d_ctx->src_frm_info.frm_height;
if(pConfig->mPicFormat == MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420|| pConfig->mPicFormat == MM_PIXEL_FORMAT_YUV_SEMIPLANAR_420)
{
size1 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[0] , 1, read_len, p_g2d_ctx->fd_in);
if(size1 != read_len)
{
aloge("read_y_data_frm_src_file_invalid");
}
size2 = fread(p_g2d_ctx->src_frm_info.p_vir_addr[1], 1, read_len /2, p_g2d_ctx->fd_in);
if(size2 != read_len/2)
{
aloge("read_c_data_frm_src_file_invalid");
}
fclose(p_g2d_ctx->fd_in);
g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[0], read_len);
g2d_flushCache((void *)p_g2d_ctx->src_frm_info.p_vir_addr[1], read_len/2);
}
4))打开g2d 初始化,并开始转换
ret = SampleG2d_G2dOpen(p_g2d_ctx);
if (ret < 0)
{
aloge("fatal error! open /dev/g2d fail!");
goto _err2;
}
ret = SampleG2d_G2dConvert(p_g2d_ctx);
if (ret < 0)
{
aloge("fatal error! g2d convert fail!");
goto _close_g2d;
}
//具体转化函数:
static int SampleG2d_G2dConvert_scale(SAMPLE_G2D_CTX *p_g2d_ctx)
{
int ret = 0;
g2d_blt_h blit;
g2d_fmt_enh eSrcFormat, eDstFormat;
SampleG2dConfig *pConfig = NULL;
pConfig = &p_g2d_ctx->mConfigPara;
ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mPicFormat, &eSrcFormat);
if(ret!=SUCCESS)
{
aloge("fatal error! src pixel format[0x%x] is invalid!", pConfig->mPicFormat);
return -1;
}
ret = convert_PIXEL_FORMAT_E_to_g2d_fmt_enh(pConfig->mDstPicFormat, &eDstFormat);
if(ret!=SUCCESS)
{
aloge("fatal error! dst pixel format[0x%x] is invalid!", pConfig->mPicFormat);
return -1;
}
//config blit
memset(&blit, 0, sizeof(g2d_blt_h));
if(0 != pConfig->mDstRotate)
{
aloge("fatal_err: rotation can't be performed when do scaling");
}
blit.flag_h = G2D_BLT_NONE_H; // angle rotation used
// switch(pConfig->mDstRotate)
// {
// case 0:
// blit.flag_h = G2D_BLT_NONE_H; //G2D_ROT_0, G2D_BLT_NONE_H
// break;
// case 90:
// blit.flag_h = G2D_ROT_90;
// break;
// case 180:
// blit.flag_h = G2D_ROT_180;
// break;
// case 270:
// blit.flag_h = G2D_ROT_270;
// break;
// default:
// aloge("fatal error! rotation[%d] is invalid!", pConfig->mDstRotate);
// blit.flag_h = G2D_BLT_NONE_H;
// break;
// }
//blit.src_image_h.bbuff = 1;
//blit.src_image_h.color = 0xff;
blit.src_image_h.format = eSrcFormat;
blit.src_image_h.laddr[0] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[0];
blit.src_image_h.laddr[1] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[1];
blit.src_image_h.laddr[2] = (unsigned int)p_g2d_ctx->src_frm_info.p_phy_addr[2];
//blit.src_image_h.haddr[] =
blit.src_image_h.width = p_g2d_ctx->src_frm_info.frm_width;
blit.src_image_h.height = p_g2d_ctx->src_frm_info.frm_height;
blit.src_image_h.align[0] = 0;
blit.src_image_h.align[1] = 0;
blit.src_image_h.align[2] = 0;
blit.src_image_h.clip_rect.x = pConfig->mSrcRectX;
blit.src_image_h.clip_rect.y = pConfig->mSrcRectY;
blit.src_image_h.clip_rect.w = pConfig->mSrcRectW;
blit.src_image_h.clip_rect.h = pConfig->mSrcRectH;
blit.src_image_h.gamut = G2D_BT601;
blit.src_image_h.bpremul = 0;
//blit.src_image_h.alpha = 0xff;
blit.src_image_h.mode = G2D_PIXEL_ALPHA; //G2D_PIXEL_ALPHA, G2D_GLOBAL_ALPHA
blit.src_image_h.fd = -1;
blit.src_image_h.use_phy_addr = 1;
//blit.dst_image_h.bbuff = 1;
//blit.dst_image_h.color = 0xff;
blit.dst_image_h.format = eDstFormat;
blit.dst_image_h.laddr[0] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[0];
blit.dst_image_h.laddr[1] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[1];
blit.dst_image_h.laddr[2] = (unsigned int)p_g2d_ctx->dst_frm_info.p_phy_addr[2];
//blit.dst_image_h.haddr[] =
blit.dst_image_h.width = p_g2d_ctx->dst_frm_info.frm_width;
blit.dst_image_h.height = p_g2d_ctx->dst_frm_info.frm_height;
blit.dst_image_h.align[0] = 0;
blit.dst_image_h.align[1] = 0;
blit.dst_image_h.align[2] = 0;
blit.dst_image_h.clip_rect.x = pConfig->mDstRectX;
blit.dst_image_h.clip_rect.y = pConfig->mDstRectY;
blit.dst_image_h.clip_rect.w = pConfig->mDstRectW;
blit.dst_image_h.clip_rect.h = pConfig->mDstRectH;
blit.dst_image_h.gamut = G2D_BT601;
blit.dst_image_h.bpremul = 0;
//blit.dst_image_h.alpha = 0xff;
blit.dst_image_h.mode = G2D_PIXEL_ALPHA; //G2D_PIXEL_ALPHA, G2D_GLOBAL_ALPHA
blit.dst_image_h.fd = -1;
blit.dst_image_h.use_phy_addr = 1;
ret = ioctl(p_g2d_ctx->mG2dFd, G2D_CMD_BITBLT_H, (unsigned long)&blit);
if(ret < 0)
{
aloge("fatal error! bit-block(image) transfer failed[%d]", ret);
system("cd /sys/class/sunxi_dump;echo 0x14A8000,0x14A8100 > dump;cat dump");
}
return ret;
}
5))转化完成后将640x360 rgb888 图资通过fd_out句柄存储起来
if(pConfig->mDstPicFormat == MM_PIXEL_FORMAT_RGB_888)
{
out_len = p_g2d_ctx->dst_frm_info.frm_width * p_g2d_ctx->dst_frm_info.frm_height *3;
g2d_flushCache((void *)p_g2d_ctx->dst_frm_info.p_vir_addr[0], out_len);
fwrite(p_g2d_ctx->dst_frm_info.p_vir_addr[0], 1, out_len, p_g2d_ctx->fd_out);
}
4. 总结转化步骤
通过步骤3中的模块化分析,可以看出g2d 转化大概分为一下步骤:
1)为打开 iomen 初始化;
2)为src以及dst图资申请虚拟地址空间并转换成物理地址空间;
3)将src图资放入虚拟地址空间,然后自动映射到物理地址空间;
4)打开g2d 设备节点进行转换(最重要的一环,可以通过手册分析具体怎么转换的);
5)将转换好的dst图资保存起来;
1. MPP 模块概述
MPP 系统控制模块,根据芯片特性,完成硬件各个部件的复位、基本初始化工作,同时负责完成 MPP(Media Process Platform 媒体处理平台)系统各个业务模块的初始化、去初始化以及管MPP 系统各个业务模块的工作状态、提供当前 MPP 系统的版本信息等功能。
应用程序启动 MPP 业务前,必须完成 MPP 系统初始化工作。同理,应用程序退出 MPP 业务后,也要完成 MPP 系统去初始化工作,释放资源。
功能描述
(1)初始化 MPP 组件的运行环境,完成音频输入输出、视频输入输出等硬件设备的初始化配置。
(2)提供绑定组件的接口。
(3)提供媒体内存分配、释放、查询的接口。
主要模块缩写:
VI :视频输入模块
VO:视频输出模块
VENC:视频编码模块
VDEC:视频解码模块
AI:音频输入
AO:音频输出
AENC:音频编码模块
ADEC:音频解码模块
2. v851s MPP sample
MPP sample 源码目录:
tina-v853-docker/platform/allwinner/eyesee-mpp/middleware/sun8iw21/sample
MPP sample 配置目录(kconfig、makefile):
tina-v853-docker/openwrt/package/allwinner/eyesee-mpp/middleware/
3. 编译一个MPP 模块步骤(以 sample_g2d 为例)
1)make menuconfig 配置配置项,选中sample_g2d
路径:Allwinner > eyesee-mppp > select mpp sample > sample_g2d 空格选中变成 【*】
2)到配置目录下编译
MPP sample 配置目录(kconfig、makefile):
tina-v853-docker/openwrt/package/allwinner/eyesee-mpp/middleware/
执行:
mm -B
将会在源码目录(上面已给出)的bin 文件夹下产生可执行文件以及配置文件(如果bin 文件夹下没产生,请到源码sample 下查看)
3)将其adb push 到v851s开发板中
执行:
./sample_vi_g2d -path sample_vi_g2d.conf
g2d 具体作用,请看源码code 中的 readme.txt 以及 g2d 手册
4. 其他sample 同理
注意:
1)执行某个sample 一定要先在make menuconfig 中选中
2)其他 sample 可能有依赖文件或者lib,通过错误提示,在make menuconfig 按“/” 搜索,然后选中即可
3)想知道每个sample 的具体含义,多看readme.txt 以及 官方手册
本文转载自:https://bbs.elecfans.com/jishu_2348036_1_1.html
前言
上一篇文章使用RT-Smart的IIC驱动OLED屏幕,进行基本的字符串显示,在使用过程中对RT-Smart有了一定熟悉,准备使用SPI驱动ST7789,但SPI接口没有引出,本次使用手上已有的传感器MPU6050进行使用。
过程
本次直接开始添加离线包
在/D1S/userapps/rt-thread/bsp/allwinner/d1s下输入scons –menuconfig进行内核配置,添加MPU605使用所需的配置。
使用IIC0进行陀螺仪数据的读取,在RT-Thread Compents 下选择Device Drivers
使能I2C device drivers
在RT-Thread online packages下选择MPU6050离线包
在General Drivers Configuration下配置IIC
保存退出,更新一下,发现报错,不能更新MPU6050的离线包
解决方法,打开rt-studio随意新建一个工程,下发MPU6050的芯片包
查看离线包的json文件,如下所示:
打开自己的工程,发现使能的MPU6050后.json文件中没有进行相应的配置,将rt-studio生成的.json文件生成的内容复制到自己的工程。
在次更新,下载成功
将mpu使用的I2C设备修改为I2C0
保存,使用scons编译
将sd.bin拖到xfel同级目录下
替换掉原来的sd.bin
三、固件下载
先将SD卡格式化
然后插入开发板中,先安全FEL按键,然后按一下复位按键,使开发板进入FEL模式。
按一下复位按键,查看串口输出
输入help命令
输入mpu5xxx_test
将陀螺仪换个位置读取,数据读取成功
五、总结
本次完成了MPU6050的数据读取,成功的使用的该外设,当然是底层驱动,还为进行应用层的开发,在驱动层已经有了大致的了解,驱动使用时容易出现MenuConfig配置了但rtconfig.h不更新,每次都要手动的添加,当然在使用的过程中也遇到了很多错误,然后重新下载源码进行开发。
本文转载自:https://bbs.elecfans.com/jishu_2344522_1_1.html
前言
本次使用RT-Smart的IIC驱动OLED屏幕,进行基本的字符串显示,在使用的过程中遇到一些问题,在这里做记录分享,本次以熟悉RT-Smart使用为主。
过程
本次直接开始添加离线包,环境搭建请参考上一篇文章:https://bbs.aw-ol.com/topic/3145/
在/D1S/userapps/rt-thread/bsp/allwinner/d1s下输入scons –menuconfig进行内核配置,添加OLED使用所需的配置。
选择SSD1306离线包
使用IIC3,同时使用ssd1306 sample
使能IIC3驱动
保存退出,更新一下
修改rtconfig.h
保存,使用scons编译
将sd.bin拖到xfel同级目录下
替换掉原来的sd.bin
三、固件下载
先将SD卡格式化
然后插入开发板中,先安全FEL按键,然后按一下复位按键,使开发板进入FEL模式。
按一下复位按键,查看串口输出
输入help命令
输入ssd1306_test进行测试,报错
查看硬件原理图,PB6,PB7被占用,使用IIC0进行。
重新下载程序后,复位开发板,IIC0已经注册
输入ssd1306测试命令
查看OLED屏幕输出
硬件连接
对应芯片引脚:
对应排针:
五、总结
RT-Smart文档使用资料有点少,花费大量时间摸索完成了OLED屏幕显示,在摸索过程中也逐渐知道了外设的使用方式。接下里下一步开始驱动2.8寸的LCDTFT。
1、V853-智能交互摄像头开发板
该参赛作品基于全志V853开发板制作的一款类似眼镜外挂的小产品,可以对场景进行辅助识别,并通过云端交互实现物联网控制,进一步实现物联网与人机交互的融合。
开发板配置了摄像头和小屏幕接口,并外挂DDR3和EMMC,充分发挥了主控的编解码能力和边缘计算能力(NPU),是一个很有创意的边缘计算设计。
2、V853-DouPi方案设计
该参赛作品基于全志V853芯片设计了SCB 单板,使用了V853的音视频接口和丰富的外设接口,并外接AXP电源管理芯片。
在SCB单板上包含了一路千兆网口、一路UART、一路MIPI CSI、一路MIPI DSI、一路USB OTG、板载WiFi/蓝牙以及15个GPIO管脚输出。
3、V853-服务器管理及协处理卡
该参赛作品基于全志V853芯片设计了一个服务器的管理设备,实现服务器管理及协处理卡功能,对服务器进行管理和检测,实时反馈信息,通过物联网接入网络,可以远程在线管理。
开发板集成了1路百兆管理网口,1路UART调试功能,1路VGA显示功能,1路音频输出,1路USB功能,主板电压检测功能,1路视频检测功能,WIFI功能。
4、V853-全功能BTB学习开发板
该参赛作品基于全志V853芯片设计了一块开发板,分为核心板和底板两个部分,引用了V853所支持的大部分功能引脚,是名副其实的“全功能”开发板。
核心板采用BTB设计,并板载了eMMC、DDR、AXP电源芯片,最大支持 1G DDR3 32GB eMMC存储;核心板背面采用了3x60pin排针设计,支持自贴NAND FLASH。
底板上引出了绝大部分资源,具体包括两个百兆网口、RGB888及MIPI DSI显示接口、USB OTG、MIPI CSI摄像头、MIC、TF卡接口等其它接口。
目前开发板功能还在验证中,验证没有问题后,会公开底板配套的原理图、尺寸图、使用文档,配套的视频以及嵌入式AI开发等所有资源。
5、V853-全功能BTB学习开发板
该参赛作品基于全志V853芯片设计了一款开发板,引出console调试串口和端子外接4G+GPS模块,并集成SD CARD,千兆以太网,百兆以太网,WiFi,音频输入输出,MIPI CSI ,MIPI DSI ,按键,LED,G-SENSOR,USB及USB HUB,引出console调试串口,ADC引脚引出,2.54端子引出一路串口,两路PWM及GPIO。
6. V851s-显示终端
该参赛作品计划将V851S芯片设计成核心板和底板结构,驱动LCD显示,接收CAN和RS485通信的数据显示在LCD显示屏上。
7.V851s-多媒体开发板
该参赛作品基于全志V851s芯片设计了一个极简版本的双目摄像头开发板,可外接屏幕和以太网。
在开发板设计技巧方面,为了简化电路图采用的内部LDO输出的1.8V;为了方面模块化的特点,采用核心外设独立设计;为了便于开发与调试,采用了LCD;针对V851s自身的特点,采用MIPI高速摄像头进行设计。
8.V853-多功能串口服务器
该参赛作品基于全志V853芯片设计了一个单片机管理设备,通过以太网控制V853设备再对多台单片机进行管理,并设计了配套的上位机软件;原理图主要在官方参考设计上进行修改,通过RS232和TTL控制下位机。
9.V853-音频播放器开发板
该参赛作品基于全志V853芯片设计了一个小音频管理系统,类似于一个个人小声卡,主要用于家里音乐试听,并针对多通道功放进行音效调音,满足室内的音响效果调优,增加diy 调音乐趣
开发板将实现codec音频驱动以实现基础硬件功能。硬件调试完成后,进行驱动验证、音频链路验证,主要涉及boot 文件系统驱动等调试,alsa 驱动移植等工作。在此基础上实现调音架构,实现delay gain biquad 等模块的功能实现,并整理出调音接口。
资料获取
以上参赛方案详细设计资料均可获取:
在OTA升级过程中,添加定制化需求,实现控制变量升级。所以选择利用swupdate升级满足定制化脚本需求
该方法在831,853,t113两个平台上测试有效
因为swupdate支持脚本,所以在升级过程中,添加能够实现定制升级的脚本即可,将带有定制升级的脚本编入策略描述文件(sw-descrition)OTA升级配置文件中(sw-subimgs),在打包ota升级包时,将脚本一起打包进ota包中。在进行下面的配置后,输入升级命令时,首先开始执行脚本中的内容,满足升级条件后,再进行升级流程。下面进行详细的步骤介绍:
1.swupdate支持shellscript脚本,需要以下功能配置(默认选中)
执行:make menuconfig/make ota_menuconfig
Allwinner --->
<*> swupdate............................. software update for embedded system
Swupdate Settings --->
General Configuration --->
[*] enable pre and postinstall scripts
Image Handlers --->
[*] shellscript
2.在swupdate文件夹下创建脚本,名字随意,在脚本中写入要实现的功能。例如:目前有一个方案需要升级,但是分为A,B两种产品,对应做了不同的功能,为了防止升级混淆,可以利用脚本去控制变量升级,比如A产品方案中的env,可以添加一个变量project=a,B产品方案中的env添加一个变量project=b,那么脚本中的内容就可以判断project=a或b时,继续完成升级。env中添加内容和脚本内容举例如下:
env中:
脚本内容如下:
3.在描述文件中添加脚本功能,在sw-description中添加以下代码:
4.将脚本打包进ota包中,脚本名字可以任意起,我这里是preinstall_checkAB.sh。
5.重新编译主系统和recovery系统,烧录固件,打包ota包,再进行验证。
6.验证:当校验失败时,返回值为1,会看到如下打印,不能升级。
校验成功时,返回值为0,看到如下打印,升级成功。
1.主题
FreeRTOS_R128_如何查看并更改各CPU核的默认运行频率
2.问题背景
硬件:R128
软件:FreeRTOS
在日常开发过程中,需要评估各CPU核的默认运行频率,有时候需要降低默认运行频率来降低功耗,有时候又需要提升默认运行频率来增加算力。
那么如何从查看并更改R128上各CPU核的默认运行频率?
3.解决办法
查看各CPU核默认运行频率
默认启动各个CPU核时会打印对应CPU核的运行频率,如下图所示:
更改各CPU核默认运行频率方法
由于R128中各CPU核的默认运行频率是M33核上的代码配置的,因此只需修改M33核的代码即可。
具体步骤为:
首先通过执行crtos命令切换到rtos代码目录,rtos代码目录路径为:lichee/rtos
然后修改文件arch/arm/armv8m/sun20iw2p1/sun20i.c里设置相关时钟频率的地方
M33核
M33核相关时钟硬件如下图:
目前SDK中M33核时钟ar200a_hclk的来源为:DPLL1输出时钟经过第一个分频器输出ck1_m33时钟,然后通过第二个分频器输出sysclk钟,最后通过一个多路选择器输出ar200a_hclk。
DPLL1输出时钟的频率已经确定为1920M,因此要修改M33核的时钟,则只需要修改这2个分频器的分频值即可。
由于SDK中默认将第一个分频器的分频值定为5(此分频器的分频值取值范围为4-8),也即ck1_m33时钟频率为1920/5=384M,而第二个分频器的分频值取值范围为1-16,因此如果要修改的M33核时钟频率在24M到384M之间且可以被384M整除,则只需要修改第2个分频器的分频值,例如将M33核默认运行频率修改为128M,则只需修改宏AR200A_FREQ即可,如下所示:
diff --git a/arch/arm/armv8m/sun20iw2p1/sun20i.c b/arch/arm/armv8m/sun20iw2p1/sun20i.c
index 9b8c1cb1..c09444fc 100755
--- a/arch/arm/armv8m/sun20iw2p1/sun20i.c
+++ b/arch/arm/armv8m/sun20iw2p1/sun20i.c
@@ -445,7 +445,7 @@ static void rcosc_init(void)
#define DPLL1_FREQ (1920000000)
#define DPLL2_FREQ (1920000000)
#define DPLL3_FREQ (1600000000)
-#define AR200A_FREQ ( 192000000)
+#define AR200A_FREQ ( 128000000)
#define DEVICE_FREQ ( 192000000)
static int sys_clk_init(void)
{
若要修改的M33核时钟频率不满足上述条件,则需要同时修改函数ar200a_clk_set里设置ck1_m33时钟频率的地方,例如修改ck1_m33时钟为240M(1920/8=240M):
diff --git a/arch/arm/armv8m/sun20iw2p1/sun20i.c b/arch/arm/armv8m/sun20iw2p1/sun20i.c
index 9b8c1cb1..29b71137 100755
--- a/arch/arm/armv8m/sun20iw2p1/sun20i.c
+++ b/arch/arm/armv8m/sun20iw2p1/sun20i.c
@@ -190,7 +190,7 @@ static int ar200a_clk_set(u32 freq)
/*fixed 384M*/
//sr32(CCMU_AON_BASE+0xa4, 0, 3, 0x3);
//0x4004c4a4: 0x8080000b
- ret = hal_clk_set_rate(clk_ck1_m33, 384000000);
+ ret = hal_clk_set_rate(clk_ck1_m33, 240000000);
if (HAL_CLK_STATUS_OK != ret) {
ret = -1;
goto err2;
C906核
C906核相关时钟硬件如下图:
目前SDK中C906核时钟rv_sys_clk的来源为:DPLL1输出时钟经过第一个分频器输出ck1_906时钟,然后通过第二个分频器输出rv_sys_clk时钟。
DPLL1输出时钟的频率已经确定为1920M,因此要修改C906核的时钟,则只需要修改这2个分频器的分频值即可,第一个分频器的分频值可取2、2.5、3、4、7,第二个分频器的分频值可取1、2、4、8。
例如将C906核默认运行频率修改为320M,需要修改sun20i_boot_c906函数中2处设置频率的地方,:
diff --git a/arch/arm/armv8m/sun20iw2p1/sun20i.c b/arch/arm/armv8m/sun20iw2p1/sun20i.c
index 9b8c1cb1..5798657b 100755
--- a/arch/arm/armv8m/sun20iw2p1/sun20i.c
+++ b/arch/arm/armv8m/sun20iw2p1/sun20i.c
@@ -596,7 +596,7 @@ int sun20i_boot_c906(void)
//set clk_ck1_c906 clk to 480M
//sr32(CCMU_AON_BASE+0xa4, 4, 3, 0x1);
- ret = hal_clk_set_rate(clk_ck1_c906, 480000000);
+ ret = hal_clk_set_rate(clk_ck1_c906, 640000000);
if (HAL_CLK_STATUS_OK != ret) {
ret = -1;
goto err2;
@@ -632,7 +632,7 @@ int sun20i_boot_c906(void)
//set clk_ck_c906_div to 480000000;
//sr32(CCMU_BASE+0x064, 0, 2, 0x0);
clk_c906_div = hal_clock_get(HAL_SUNXI_CCU, CLK_RISCV_DIV);
- ret = hal_clk_set_rate(clk_c906_div, 480000000);
+ ret = hal_clk_set_rate(clk_c906_div, 320000000);
if (HAL_CLK_STATUS_OK != ret) {
ret = -1;
goto err6;
PS:由于第一个分频器无法输出320M时钟,故先配置第一个分频器输出640M的ck1_c906时钟,然后由第二个分频器再进行2分频,从而得到320M的C906核时钟。
DSP核
DSP核相关时钟硬件如下图:
目前SDK中DSP核时钟dsp_sys_clk的来源为:DPLL3输出时钟经过第一个分频器输出ck3_hifi5时钟,然后通过第二个分频器输出dsp_sys_clk时钟。
DPLL3输出时钟的频率已经确定为1600M,因此要修改DSP核的时钟,则只需要修改这2个分频器的分频值即可,第一个分频器的分频值可取3、4、5、6、7,第二个分频器的分频值可取1、2、4、8。
例如将DSP核默认运行频率修改为320M,只需修改宏DSP_CORE_CLOCK_FREQ即可,:
diff --git a/arch/arm/armv8m/sun20iw2p1/sun20i.c b/arch/arm/armv8m/sun20iw2p1/sun20i.c
index 9b8c1cb1..97d03ad7 100755
--- a/arch/arm/armv8m/sun20iw2p1/sun20i.c
+++ b/arch/arm/armv8m/sun20iw2p1/sun20i.c
@@ -703,7 +703,7 @@ err1:
#if defined(CONFIG_ARCH_ARMV8M_DEFAULT_BOOT_DSP) || defined(CONFIG_COMMAND_BOOT_DSP) \
|| defined(CONFIG_PM_SUBSYS_DSP_SUPPORT)
-#define DSP_CORE_CLOCK_FREQ (400000000)
+#define DSP_CORE_CLOCK_FREQ (320000000)
#define DSP_LDO_WORK_VOLT (1200) //400M@1.2V, 274M@1.1V
int __sun20i_boot_dsp_with_start_addr(uint32_t dsp_start_addr)
{
另外目前SDK代码中第一个分频器和第二个分频都配置成了相同的频率,若第一个分频器无法直接输出想要设置的频率,则需要分别将2个分频器配置成不同的输出频率。例如将DSP核默认运行频率修改为200M,第一个分频器最低输出时钟频率为1600/7=228.57M,无法直接输出200M,因此需要先配置第一个分频器输出400M的ck3_hifi5时钟,然后由第二个分频器进行2分频从而得到200M的dsp_sys_clk时钟,主要修改如下所示:
diff --git a/arch/arm/armv8m/sun20iw2p1/sun20i.c b/arch/arm/armv8m/sun20iw2p1/sun20i.c
index 9b8c1cb1..53f6828a 100755
--- a/arch/arm/armv8m/sun20iw2p1/sun20i.c
+++ b/arch/arm/armv8m/sun20iw2p1/sun20i.c
@@ -703,7 +703,7 @@ err1:
#if defined(CONFIG_ARCH_ARMV8M_DEFAULT_BOOT_DSP) || defined(CONFIG_COMMAND_BOOT_DSP) \
|| defined(CONFIG_PM_SUBSYS_DSP_SUPPORT)
-#define DSP_CORE_CLOCK_FREQ (400000000)
+#define DSP_CORE_CLOCK_FREQ (200000000)
#define DSP_LDO_WORK_VOLT (1200) //400M@1.2V, 274M@1.1V
int __sun20i_boot_dsp_with_start_addr(uint32_t dsp_start_addr)
{
@@ -752,7 +752,7 @@ int __sun20i_boot_dsp_with_start_addr(uint32_t dsp_start_addr)
//set clk_ck3_hifi5 clk to 400M
//sr32(CCMU_AON_BASE+0xa8, 8, 3, 0x3);
- ret = hal_clk_set_rate(clk_ck3_hifi5, DSP_CORE_CLOCK_FREQ);
+ ret = hal_clk_set_rate(clk_ck3_hifi5, 400000000);
if (HAL_CLK_STATUS_OK != ret) {
ret = -1;
goto err2;
修改完成后重新编译M33核固件,因为R128中各CPU核的默认运行频率是M33核上的代码配置的。
可先执行lunch_rtos选择对应方案的M33核,然后再执行mrtos编译以及执行pack命令打包固件。
下图是修改M33核频率为128M、C906核频率为320M、DSP核频率为320M后的效果:
全志D1S芯片介绍
全志D1S是由全志公司推出的,基于阿里平头哥的C906内核的全新一代应用处理器。
C906兼容RISC-V架构,标配内存管理单元,可运行Linux等操作系统。C906采用5级整型流水线设计,并可选性能优异的单双精度浮点和128位矢量运算单元,
适用于消费类IPC、多媒体、消费类电子等应用领域
全志D1s,又名 F133,它是今年早些时候和支持 Linux 的开发板一起推出的全志D1 RISC-V 处理器的低成本版,它和D1的主要区别在于D1s内置的RAM是 64MB DDR2。
原理图解析:
RTT D1s RDC2022纪念版开发板使用了核心板+底板的设计方式,一般核心板使用多层板设计,完成最小系统设计后,底板可以使用双面板设计,这样可以节省制作成本,也方便开发板在项目直接购核心板,自行根据项目需求设计底板,降低了硬件设计的繁琐程度。
主供电采用EA3036,提供0.9V, 1.8V, 3.3V三路电源供电,其中0.9V电压为内核电压,1.8V为全志D1S内封的DDR2内存供电,3.3V为外设接口供电。
板载了一颗贴片SD卡,四线SDIO模式,方便量产时直接SMT即可完成贴片,通过丝印查找出其型号为苏州澜智公司推出的1Gb (128MByte) SD NAND,采用SLC,稳定性比普通的TF卡高。
板载的CH340连接到的是D1s的GP8/PG9/USART3引脚,这个有印象即可,其他接口后面使用到的时候再做提及。
RT-THREAD SMART介绍
RT-Thread Smart 是基于 RT-Thread 操作系统上的混合操作系统,简称为 rt-smart,它把应用从内核中独立出来,形成独立的用户态应用程序,并具备独立的地址空间(32 位系统上是 4G 的独立地址空间)。
以下是 rt-smart 的整体结构框图,在硬件平台的基础上通过 MMU、系统调用的方式把整个系统分成了内核态及用户态。
基于RTT D1S 开发板运行RT-SMART
从上面的架构图中,我们可以看出,RM-smart层级关系是和linux内核具有一些相似之处的,都是分为用户态和内核态,我们开发一般也是从这两方面入手。
通过查阅全志D1的用户手册,我们可以知道,在默认情况下,其启动顺序为:
SMHC0 -> SPI NOR -> SPI NAND -> SMHC2
而RTT D1s开发板上的贴片SD卡连接到的正是SDIO0 (也就是SMHC0),那么我们只要把rt-smart的系统烧录到贴片SD卡里面,上电后就能够运行rt-smart操作系统了。
RT-smart操作系统在SD卡中的分区如下图:
从0处开始的8KB空间是MBR分区头,这是文件系统规定的,直接跳过即可。
从0x2000开始的是SPL ,SPL类似于arm 嵌入式开发中常用的uboot,主要起到的功能是初始化DDR2,从SD卡中把内核或下一阶段的引导程序等复制到DDR2中,然后把程序指针指向内存空间中对应的起始地址并运行。
从0xE000开始的就是OpenSBI , 设备树和RT-smart内核,这一段代码会被复制到内存中以内核态运行。
从0x800000开始的是用户态程序,用户自己编写的程序就下载到这个位置,以用户态运行。
内核态与用户态的编译
本段内容主要参考了这篇文章:快来尝鲜!!使用 D1s (RDC2022 纪念版) 运行 RT-Smart
SPL
当前阶段,RT-smart的SPL程序尚未开源,所以该部分程序我们直接在上面的帖子里翻到最后,下载附件里的二进制文件即可。最终SPL程序文件名称为:
boot0_sdcard_sun20iw1p1_f133.bin
内核程序
开发环境:
虚拟机/实体机等方式运行Ubuntu20.04
首先创建一个工作文件夹:
mkdir ~/work/rttd1s
cd ~/work/rttd1s
然后克隆用户态仓库到该目录:
git clone https://github.com/RT-Thread/userapps.git
进入该目录后,下载RT-smart内核代码,并执行环境变量配置:
cd userapps
git clone https://github.com/RT-Thread/rt-thread.git
source smart-env.sh riscv64
结果出现了报错:
打开脚本后,发现是SHELL_FOLDER这个变量获取失败了,他获取的是执行该命令时第一个参数的所在位置。我们这个脚本是通过source命令来执行的。
SHELL_FOLDER=$(cd $(dirname $0); pwd)
我们可以通过直接硬编码其位置来修复这个问题,就像下图这样:
再次执行,没有再出现错误。
配置工具链:
python3 tools/get_toolchain.py riscv64
编译内核程序
进入 rt-thread/bsp/allwinner/d1s 目录下
使用 scons --menuconfig 查看配置。smart 使用的串口为 UART3 ,对应引脚为 PG8 和 PG9。检查 UART3 是否开启,以及引脚是否正确。
> General Drivers Configuration
> General Purpose UARTs
> Enable UART3
然后检查内核是否是使用的uart3
> RT-Thread Kernel > Kernel Device Object
后面我们会需要点亮屏幕,所以在这里提前打开LCD驱动:
> General Drivers Configuration ─────
使用 scons 命令编译 内核
编译完内核之后,会自动执行打包命令 ./mksdimg.sh 打包出 sd.bin 镜像文件。该文件就是rt-smart系统的内核文件,根据上文中描述的那样,我们后面就会将其下载到SD卡的对应位置。
......
LINK rtthread.elf
riscv64-unknown-linux-musl-objcopy -O binary rtthread.elf rtthread.bin
riscv64-unknown-linux-musl-size rtthread.elf
text data bss dec hex filename
879356 60472 274136 1213964 12860c rtthread.elf
./mksdimg.sh
Allwinner TOC1 Image
Size: 1072640 bytes
Contents: 3 items
00000000:00000490 Headers
00000600:000188e0 => 40000000 opensbi
00019000:00007475 => 40200000 dtb
00020600:000e5748 => 40400000 kernel
scons: done building targets.
编译用户态程序
打包用户态程序需要用到当前目录下制作SDcard镜像的脚本,我们回到userapps主目录然后把脚本复制过去.
cd ~/work/rttd1s/userapps/
cp rt-thread/bsp/allwinner/d1s/mksdcard.sh ./mksdcard.sh
在当前目录下执行scons ,编译用户态程序,当出现提示scons: done building targets后,即为编译成功。
$ scons
.....
....
scons: done building targets
然后在当前目录下运行mkcard.sh,没有提示报错,即为成功,在当前目录下会生成sdroot.bin,这就是我们的用户态镜像,我们会将其烧录到sd卡的对应位置。
下载烧录
通过前文的努力,我们已经获得了所需的3个文件,分别是rt-smart提供的SPL,我们自己编译的内核文件和用户态文件,下面我介绍将其下载到开发板上SD卡的方法:
我们需要使用带数据传输功能的USB-TYPE-C线缆连接开发板的USB接口和电脑。这里有两种方法:
首先按住开发板上的FEL按键,然后插入电脑上电
先把线缆连接好,然后按住FEL按键,点击一次RST按键
,默认情况下会出现一个新的USB设备,但是会提示没有驱动。
然后我们需要使用zadig这个软件给该设备强制安装winusb驱动,zadig这个软件在前面的帖子里或者互联网上都可以找到。打开软件后,确认当前显示的设备是Unknown Device #1,然后安装驱动。
驱动安装完成之后,我们就可以使用xfel这个软件来下载程序了,改软件同样在上面帖子最后的附件里。
烧录的命令有三个,分别如下:
烧录boot镜像:
xfel.exe sd write 8192 boot0_sdcard_sun20iw1p1_f133.bin
烧录内核镜像:
xfel.exe sd write 57344 sd.bin
烧录用户态应用
xfel.exe sd write 8388608 sdroot.bin
我们把从Ubuntu中编译完的镜像复制到xfel同级目录,然后依次运行以上命令即可。命令中的sd write就是指烧录到贴片sd卡里,后面的数字是烧录的起始地址,就对应着SPL, 内核和用户态程序的起始地址。
PS E:\MP\RTT-D1S\RDC资料\出厂固件+工具> ./xfel.exe sd write 8192 boot0_sdcard_sun20iw1p1_f133.bin
write 48Kbytes
swap buf:00023E00,len=512, cmdlen=512
Found SD card 125173760 bytes
100% [================================================] 48.000 KB, 226.353 KB/s
PS E:\MP\RTT-D1S\RDC资料\出厂固件+工具> ./xfel.exe sd write 57344 sd.bin
align up to 8KB, write 1072640 ==> 1048Kbytes
swap buf:00023E00,len=512, cmdlen=512
Found SD card 125173760 bytes
100% [================================================] 1.023 MB, 218.882 KB/s
PS E:\MP\RTT-D1S\RDC资料\出厂固件+工具> ./xfel.exe sd write 8388608 sdroot.bin
write 8192Kbytes
swap buf:00023E00,len=512, cmdlen=512
Found SD card 125173760 bytes
100% [================================================] 8.000 MB, 217.951 KB/s
烧录完成后,我们将串口的USB接口连接到电脑,然后重新上电即可运行。
串口的软件这里推荐使用mobaXterm,打开软件后, 创建一个新的串口,波特率默认为500000。依次点击以下按钮:
然后按下重启键,就可以看到rt-smart系统已经运行起来了。
\ | /
- RT - Thread Smart Operating System
/ | \ 5.0.0 build Feb 22 2023 22:26:54
2006 - 2022 Copyright by RT-Thread team
hal_sdc_create 0
card_detect insert
Initial card success. capacity :60014MB
sdmmc bytes_per_secotr:200, sector count:7537400
found part[0], begin: 8388608, size: 58.614GB
found partition:sd0 of mbr at offset 0x0000000000004000, size:0x0000000007533400
hal_sdc_create 1
card_detect insert
Initial card failed!!
[E/drv-sdmmc] init sdmmc failed!
[E/drv-sdmmc] sdmmc_init failed!
[D/FAL] (fal_flash_init:47) Flash device | sdcard0 | addr: 0x00000000 | len: 0xa6e80000 | blk_size: 0x00000200 |initialized finish.
[I/FAL] ==================== FAL partition table ====================
[I/FAL] | name | flash_dev | offset | length |
[I/FAL] -------------------------------------------------------------
[I/FAL] | download | sdcard0 | 0x00800000 | 0x00800000 |
[I/FAL] | easyflash | sdcard0 | 0x01000000 | 0x00100000 |
[I/FAL] | filesystem | sdcard0 | 0x01100000 | 0x00c00000 |
[I/FAL] =============================================================
[I/FAL] RT-Thread Flash Abstraction Layer initialize success.
Hello RISC-V
[W/DBG] disp:[parser_disp_init_para 575]of_property_read screen1_output_type fail
msh />Mount "sd0p0" on "/" success
hal_sdc_create 1
[os E] OS_MutexCreate():42, handle 0x4056e348
card_detect insert
mmc_send_app_op_cond,109 100
Initial card failed!!
[E/drv-sdmmc] init sdmmc failed!
sdmmc bytes_per_secotr:200, sector count:0
[E/drv-sdmmc] read offset 0 over part sector 0
[E/drv-sdmmc] device read mbr 1-sector failure
msh />ls bin/
Directory bin/:
hello.elf 341224
ping.elf 347336
pong.elf 342160
umailbox.elf 357440
vi.elf 517464
webclient.elf 393032
webserver.elf 489416
msh />
而RT-smart的msh就类似于linux里的shell,使用ls命令是查看文件, 用户态程序存放在bin文件夹下。
用户程序开发
RT-smart的用户态程序是和内核分离的,我们在userapps文件夹下可以看到一个叫做apps的文件夹,里面存放的就是用户程序的代码,每个程序以文件夹的形式分开。
默认带的这些程序就是rt-smart自带的示例,用于演示相关api的使用。
比如这里的hello程序,执行后功能就是在控制台打印出一行hello, world!\n。
我们需要开发任何用户程序都是按照类似的流程做的。
我们在msh里运行bin/hello,查看一下效果:
msh />bin/hello.elf
msh />hello world!
如果我们要创建新的程序,就复制hello程序目录到当前文件夹,然后在此基础上进行开发,程序写完后依然是使用scons命令完成编译,mksdcard.sh打包镜像即可
运行LVGL
在rt-smart上运行lvgl也很方便,而且得益于rt-smart的内核态与用户态分离,我们可以很方便的借(zhao)鉴(chao)大佬们之前的研究成果。
这里非常感谢网友Rb君的努力!他的工作如下:
丝滑的在RT-Smart用户态运行LVGL
https://club.rt-thread.org/ask/article/e8bc90918bd055eb.html
具体的原理我这里不再赘述,大家可以进他的帖子看看,我这里给出最快速的体验方法:
首先克隆 lvgl用户态程序
mkdir git_projs
cd git_projs/
git clone https://github.com/Rbb666/RT-Smart-UserAPP.git
cd .. # 回到userapps目录
cp -r git_projs/RT-Smart-UserAPP/userapps/media/ ./ # 复制media文件夹到我们的userapps
然后我们需要修改scons的编译脚本,这里用任何喜欢的编辑器打开userapps目录下的SConstruct文件,翻到最后,添加一行编译media文件夹:
因为我手上只有一块不支持电容触摸的RGB-LCD屏幕,所以很遗憾,我在这里将lvgldemo 里的auto play打开,达到不需要触屏,自动播放的效果。
然后依然是使用scons编译用户程序,mksdcard.sh打包镜像,重新下载到贴片TF卡中后,即可运行lvgl程序下面是效果演示:
可以看到效果还是很不错的,480*272分辨率的屏幕能够达到60fps.
RISCV内核是一种全新开放指令集risc核心,打断了之前arm内核在相关领域内的垄断低位,几年内就推出了应用处理器级别的SOC,具有强大的性能,而且开发方式和之前基于arm的开发基本没有什么区别。
RT-smart操作系统其实我之前并未怎么了解,只知道是一种用于应用处理器(带MMU)的实时操作系统。
经过实际使用下来,我感觉用起来还是很舒服的,有别于linux,其具有启动快,中断快等优点,(arm-linux启动一般都需要数十秒,rt-smart可能只需要1秒)
使用scons开发,这一点也有别于linux的那一套makefile。需要一定的学习过程。
但是最大的问题是相关的文档还是太缺少了,光是对rt-smart这一套概念的理解就花了不少的时间,希望国内厂商以后还是能够在文档相关的工作做的更好。
本文转载自:https://bbs.aw-ol.com/topic/3005/
作者@new_bee
目录
1.芯片介绍
2.开发板介绍
3.RT-Smart用户态系统编译使用
4.感想
5.引用
1. 芯片介绍
RISC-V架构由于其精简和开源的特性,得到业界的认可,近几年可谓相当热门。操作系统方面有RT-Thread,Linux等支持。在今年早些时候,Google希望RISC-V成为Android支持的“一级”架构。
D1s 是22nm 64bit RISC-V 架构的处理器,主频720MHz。内置64M DDR2。支持RGB,MIPI等视频输出接口,ADC、DAC、I2S、PCM、DMIC、OWA 等多种音频接口。音视频编解码支持如下:
Video decoding
H.265 up to 1080p@60fps
H.264 up to 1080p@60fps
MPEG-1/2/4, JPEG, VC1 up to 1080p@60fps
Video encoding
JPEG/MJPEG up to 1080p@60fps
Supports input picture scaler up/down
芯片框图如下:
2. 开发板介绍
RDC2022 纪念版开发板是基于D1s芯片的小尺寸开发板,尺寸为5.5cm*4cm。正面包含一块D1s核心板,外围电路采用ME3116作为DCDC降压芯片,CH340N作为USB-TTL芯片,PT4103作为背光驱动,并包含一块LM4890音频功放。总体电路比较简洁。
开发板上方USB为烧录口,下方为串口,可以在开发板启动后接串口调试。
开发板背面为SD卡插槽,一个RGB888接口接显示屏,和一个触摸屏接口。该开发板还包含两组12脚的PMOD接口用于接插外设。
RGB接口按引脚定义可以接入通用RGB屏幕,我这里接的是480*272的屏幕,引脚定义如下:
3. RT-SMART用户态系统编译使用
RT-Smart 是基于 RT-Thread 操作系统上的混合操作系统,把应用从内核中独立出来,形成独立的用户态应用程序。这样更加安全。
以下编译过程参考了快来尝鲜!!使用 D1s (RDC2022 纪念版) 运行 RT-Smart,并将过程中一些小坑记录下,方便后面遇到同样问题的人。
3.1 刷机&调试环境准备
操作系统:Windows
zadig.7z
首先用typec数据线一端插入烧录口,按住FEL按钮同时另一端插入电脑,此时设备进入FEL模式。第一次插入设备管理器会发现未知设备。运行Zadig安装驱动,如下图:
之后就可以在FEL模式下刷机。
用typec数据线一端插入调试口,另一端插入电脑。电脑打开串口软件,选择对应串口,波特率设为500000,则可以通过串口进行调试。
3.2 开发编译环境准备
操作系统:Ubuntu 20.04
下载用户态代码:
git clone https://github.com/RT-Thread/userapps.git
下载内核态代码:
注:位置可以与用户态代码分开,只要设置RISCV64的工具链环境变量即可
git clone https://github.com/RT-Thread/rt-thread.git
下载RISC-V 64工具链
cd userapps
python3 tools/get_toolchain.py riscv64
配置RISC-V 64工具链环境变量
注意:按官方脚本需要执行
source smart-env.sh riscv64
这个脚本完成两个工作:
设置RTT_CC等环境变量
copy riscv的config到.config
但是某些shell下获取当前脚本目录错误,导致无法完成copy,第一次使用的话会继续用默认的arm工具链编译。
比较简单的解决办法是执行两步:
source smart-env.sh riscv64
cp configs/def_config_riscv64 .config
3.3 编译rt-thread内核
进入bsp目录
cd rt-thread/bsp/allwinner/d1s
修改编译配置
scons --menuconfig
首先打开UART3串口,路径为
General Deivers Configuration->General Purpose UARTs
选中UART3,并修改TX引脚为GPIOG08,RX引脚为GPIOG09
设置终端使用UART3,路径为
RT-Thread Kernel->Kernel Device Object
修改为uart3
这回目标是点屏,所以要打开LCD驱动,路径为
General Deivers Configuration->Enable LCD
以上完成内核配置,保存退出。
编译内核:
scons
编译成功后会生成内核文件:
烧录内核
下载xfel工具:xfel_v1.2.9.7z
将sd.bin拷贝到windows下xfel工具目录下。按住开发板FEL按钮同时另一端插入电脑。执行命令完成烧录:
xfel.exe sd write 57344 sd.bin
3.4 编译rt-smart用户态应用点屏
首先将D1s bsp目录下 mksdcard.sh 脚本拷贝到userapps目录下。
这里借用丝滑的在RT-Smart用户态运行LVGL的LVGL移植成果
下载修改代码:
https://github.com/Rbb666/RT-Smart-UserAPP.git
拷贝项目下的lvgl到userapps/apps目录下
由于一些宏定义缺失,需要将lvgl-nes/lv_rtt_port下的lv_conf.h和lv_port.h拷到对应目录下
同时,由于我没有触摸屏设备,所以注释掉lv_demo.c中的触摸屏初始化代码
在用户态代码根目录下,编译用户态应用
scons
执行脚本制作root文件系统
./mksdcard.sh
生成sdroot.bin
最后烧录文件系统
xfel.exe sd write 8388608 sdroot.bin
3.5 点屏
烧录完成后重启,就可以用调试口看到目录下有lvgl.elf。执行点亮屏幕
./lvgl.elf
4. 感想
总体而言,RISC-V的生态在厂商和开源社区的努力下逐步完善起来。芯片,开发板,操作系统都有诸多选择,工具链也可以较透明的无缝移植。
但是感触比较深的还是缺少更多的文档和入门教程,对于新人不会像树莓派等有开箱即用的效果(当然树莓派现在的性价比就不说了)。
另外,scons编译确实较方便,不过针对一些复杂的基于makefile的开源项目,移植到scons较为繁复。在用RISCV工具链编译一些基于makefile的开源项目时,也遇到了一些坑,之后会写文章分享。当然RISCV的发展不是一蹴而就的事,仍需业界和开源社区大力支持。
5. 引用
D1s
RT-Thread文档中心
快来尝鲜!!使用 D1s (RDC2022 纪念版) 运行 RT-Smart
丝滑的在RT-Smart用户态运行LVGL
目的
V85x某方案目前默认Sensor是GC2053。实际使用时若需要用到GC4663(比如wdr功能)和SC530AI(支持500W),可按如下步骤完成切换。
步骤
下面以GC4663为例,SC530AI按相应方式适配。
Step1 检查Sensor驱动是否被编译成.ko文件
GC4663 Sensor驱动文件gc4663_mipi.c和配置Sensor驱动编译的Makefile文件路径的路径:
cpp
tina\lichee\linux-4.9\drivers\media\platform\sunxi-vin\modules\sensor
修改配置Sensor驱动编译的Makefile文件:
添加后,重新mkernel即可编译出GC4663驱动的ko文件。
Step2 检查ISP效果文件是否已适配
效果文件gc4663_mipi_wdr_default_v853.h路径:
cpp
tina\external\eyesee-mpp\middleware\sun8iw21\media\LIBRARY\libisp\isp_cfg\SENSOR_H\gc4663
效果文件解析文件isp_ini_parse.c路径:
cpp
tina\external\eyesee-mpp\middleware\sun8iw21\media\LIBRARY\libisp\isp_cfg\
若没有sensor对应的效果文件,则需要先添加和适配。
适配后,通过make menuconfig修改sensor为gc4663。
Step3 检查board.dts是否已适配
板级配置文件board.dts的路径:
cpp
tina\device\config\chips\v853\configs\perf1
其中,I2C地址可以从GC4663 Sensor驱动中找到。
cpp
tina\lichee\linux-4.9\drivers\media\platform\sunxi-vin\modules\sensor\gc4663_mipi.c
Step4 修改ko自动加载脚本
先将ko拷贝到/lib/modules/下,然后加载ko。
(1)拷贝ko
modules.mk的路径:
cpp
tina\target\allwinner\v853-perf1
(2)加载ko
S00mpp的路径:
cpp
tina\target\allwinner\v853-perf1\busybox-init-base-files\etc\init.d
Step5 重新编译mpp和固件
重新编译mpp和固件的命令:
cleanmpp && mkmpp && mp
1.主题
LVGL视频播放界面实现方法
2.问题背景
使用LVGL开发且需要在UI下显示视频或者显示摄像头数据,但是不知道如何实现,要么是只显示UI,要么就只显示视频。
3.具体表现
可以看下视频播放的时候有哪些元素。
有播放按钮,进度条,设置等可交互的控件,这些都位于UI层。
UI的下方就是视频解码后的一帧数据,视频数据位于视频层。
4.问题分析
一般UI层位于视频层的上方,如果UI层没有设置透明度,那么会有一个背景色,覆盖了视频层,导致只能看到背景色和一些控制按钮。
5.根本原因
首先需要知道有图层的概念,下面的命令可以看到图层的信息:
cpp
root@TinaLinux:/# cat /sys/class/disp/disp/attr/sys
screen 0:
de_rate 300000000 hz, ref_fps:59
mgr0: 1280x800 fmt[rgb] cs[0x204] range[full] eotf[0x4] bits[8bits] err[1] force_sync[1] unblank direct_show[false] iommu[1]
dmabuf: cache[4] cache max[4] umap skip[0] umap skip max[18]
lcd output backlight( 50) fps:60.0 1280x 800
err:0 skip:67 irq:2300 vsync:0 vsync_skip:0
BUF enable ch[0] lyr[0] z[0] prem[N] a[pixel 0] fmt[ 77] fb[1280, 736; 640, 368; 640, 368] crop[ 0, 0,1280, 720] frame[ 0, 0,1280, 800] addr[fb200000,fb2e6000,fb359000] flags[0x 0] trd[0,0]
depth[ 0] BUF enable ch[1] lyr[0] z[16] prem[N] a[pixel 255] fmt[ 0] fb[1280, 800;1280, 800;1280, 800] crop[ 0, 0,1280, 800] frame[ 0, 0,1280, 800] addr[ff800000, 0, 0] flags[0x
0] trd[0,0]
在内核初始化过程中,显示驱动注册/dev/fb0,会申请UI层,可以看到ch[1] lyr[0],一般UI就一个地址addr[ff800000, 0, 0],并且也是最顶层的z[16],混合模式a[pixel 255]表示由应用控制UI层的透明度。
视频播放(使用TPlayer接口)的时候会通过中间件申请视频图层,可以看到ch[0] lyr[0],并且yuv三个地址一直在变化addr[fb200000,fb2e6000,fb359000],通常视频会放到最底层z[0]。
当应用没有透明时,因为UI层的zorder比视频层大,因此只能看到UI,看不到视频,所以应用需要“挖空”。
6.解决办法
下面代码里的屏幕,一般是指/dev/fb0,LVGL版本是8.3.2。
使用下面的代码前需要在lv_conf.h中设置参数:
cpp
#define LV_COLOR_SCREEN_TRANSP 1
cpp
/* 初始化屏幕风格 */
static lv_style_t style_scr_act;
if (style_scr_act.prop_cnt == 0) {
lv_style_init(&style_scr_act);
/* 默认不是透明的,后面按需要切换即可 */
lv_style_set_bg_opa(&style_scr_act, LV_OPA_COVER);
/* 一定要应用风格,不然也是没有效果的 */
lv_obj_add_style(lv_scr_act(), &style_scr_act, 0);
}
/* 这里根据按钮状态,切换不同风格,一种是UI能够透明看到底下的视频,一种是UI覆盖视频,视频就看不到了 */
if (lv_obj_has_state(btn, LV_STATE_CHECKED)) {
/* 这里切换为UI透明 */
lv_label_set_text(label, "Stop");
/* 这里设置屏幕是透明的 */
lv_disp_get_default()->driver->screen_transp = 1;
/* 这里设置屏幕背景是透明的 */
lv_disp_set_bg_opa(lv_disp_get_default(), LV_OPA_TRANSP);
/* 这里清空屏幕,不清空的话,可能不会生效 */
lv_memset_00(lv_disp_get_default()->driver->draw_buf->buf_act,
lv_disp_get_default()->driver->draw_buf->size
* sizeof(lv_color32_t));
/* 这里屏幕风格切换为透明的 */
lv_style_set_bg_opa(&style_scr_act, LV_OPA_TRANSP);
/* 通知风格变化,需要更新 */
lv_obj_report_style_change(&style_scr_act);
} else {
/* 这里切换为UI不透明,也就是覆盖视频 */
lv_label_set_text(label, "Play");
/* 这里设置屏幕是不透明的 */
lv_disp_get_default()->driver->screen_transp = 0;
/* 这里设置屏幕背景是不透明的 */
lv_disp_set_bg_opa(lv_disp_get_default(), LV_OPA_COVER);
/* 这里屏幕风格切换为不透明的 */
lv_style_set_bg_opa(&style_scr_act, LV_OPA_COVER);
/* 通知风格变化,需要更新 */
lv_obj_report_style_change(&style_scr_act);
}
问题一
在驱动中有个接口:
/* sel: 0:lcd0; 1:lcd1 */
static s32 LCD_set_bright(u32 sel, u32 bright)
{
sunxi_lcd_dsi_dcs_write_1para(sel,0x51,bright);
return 0;
}
操作这个接口调整亮度的话,显示是无法正常的。是否驱动在操作这个接口过程中需要等待屏幕数据发帧结束? 感觉是要等的,但是目前还没详细看代码流程。
解答:
发送命令要处于LP模式,在送帧(HP模式)的时候去切换模式肯定会导致黑屏的,需要在中断的消隐区间去设置背光,可以翻一下lcd调试说明文档,里面有esd这个章节,显示驱动中也有esd相关代码,做esd就是在中断的消隐区间去读mipi的寄存器,判断屏的状态。可以读那么就可以写了。
问题二
模仿esd程序进行中断中修改亮度操作,可以写入亮度成功,也可以读到东西,但是中断就无法重新进入了。是否有什么注意到的地方。
static s32 lcd_set_oled_bright(u32 sel,u32 bright)
{
s32 ret = 0;
u8 result[16] = {0};
s32 nump = 1;
ret = sunxi_lcd_dsi_dcs_read(0, 0x54, result,&nump);
dsi_dcs_wr_1para(sel, 0xFE, 0x00); //{ 0xFE,1,{0x00} },
dsi_dcs_wr_1para(sel, 0x51, 0x50);
printk(KERN_WARNING "wwwwwwwwwwww lcd_set_oled_bright :bright = %d read result :%02x \n",bright,result[0]);
return 0;
}
/*此处省略*/
struct __lcd_panel rm69330_139_panel = {
/* panel driver name, must mach the name of
* lcd_drv_name in sys_config.fex
*/
.name = "rm69330_139",
.func = {
.cfg_panel_info = lcd_cfg_panel_info,
.cfg_open_flow = lcd_open_flow,
.cfg_close_flow = lcd_close_flow,
.lcd_user_defined_func = lcd_user_defined_func,
.set_bright = LCD_set_bright,
.esd_check = lcd_esd_check,
.reset_panel = lcd_reset_panel,
.set_esd_info = lcd_set_esd_info,
.oled_set_bright = lcd_set_oled_bright,
},
};
#if defined(__LINUX_PLAT__)
static irqreturn_t disp_lcd_event_proc(int irq, void *parg)
#else
static irqreturn_t disp_lcd_event_proc(void *parg)
#endif
{
struct disp_device *lcd = (struct disp_device *)parg;
struct disp_lcd_private_data *lcdp = NULL;
struct disp_manager *mgr = NULL;
#if defined(SUPPORT_EINK) && defined(CONFIG_EINK_PANEL_USED)
struct disp_eink_manager *eink_manager = NULL;
#endif
u32 hwdev_index;
u32 irq_flag = 0;
unsigned int panel_extend_dirty;
unsigned int panel_extend_modify_oledbright;
unsigned long flags;
if (lcd == NULL)
return DISP_IRQ_RETURN;
hwdev_index = lcd->hwdev_index;
lcdp = disp_lcd_get_priv(lcd);
if (lcdp == NULL)
return DISP_IRQ_RETURN;
#if defined(SUPPORT_EINK) && defined(CONFIG_EINK_PANEL_USED)
eink_manager = disp_get_eink_manager(0);
if (eink_manager == NULL)
return DISP_IRQ_RETURN;
#endif
if (disp_al_lcd_query_irq
(hwdev_index, LCD_IRQ_TCON0_VBLK, &lcdp->panel_info)) {
#if defined(SUPPORT_EINK) && defined(CONFIG_EINK_PANEL_USED)
eink_display_one_frame(eink_manager);
#else
int cur_line =
disp_al_lcd_get_cur_line(hwdev_index, &lcdp->panel_info);
int start_delay =
disp_al_lcd_get_start_delay(hwdev_index, &lcdp->panel_info);
#if defined(CONFIG_DISP2_LCD_ESD_DETECT)
if (lcdp->lcd_panel_fun.esd_check &&
lcdp->lcd_panel_fun.reset_panel) {
++lcdp->esd_inf.cnt;
if (cur_line < 2 &&
!atomic_read(&lcdp->lcd_resetting) &&
lcdp->esd_inf.cnt >= lcdp->esd_inf.freq) {
disp_al_lcd_disable_irq(hwdev_index, LCD_IRQ_TCON0_VBLK,
&lcdp->panel_info);
if (!lcdp->esd_inf.esd_check_func_pos ||
lcdp->lcd_panel_fun.esd_check(lcd->disp)) {
/*request reset*/
atomic_set(&lcdp->lcd_resetting, 1);
schedule_work(&lcdp->reflush_work);
printk(KERN_WARNING "wwwwwwwwwwww lcd_esd_check disp_lcd_event_proc 1633 \n");
}
disp_al_lcd_enable_irq(hwdev_index, LCD_IRQ_TCON0_VBLK,
&lcdp->panel_info);
printk(KERN_WARNING "wwwwwwwwwwww after lcd_esd_check disp_lcd_event_proc 1635 \n");
lcdp->esd_inf.cnt = 0;
}
}
#endif
mgr = lcd->manager;
if (mgr == NULL)
{
printk(KERN_WARNING "wwwwwwwwwwww after if (mgr == NULL) 1644 \n");
return DISP_IRQ_RETURN;
}
if (cur_line <= (start_delay - lcdp->judge_line))
sync_event_proc(mgr->disp, false);
else
sync_event_proc(mgr->disp, true);
#endif
} else {
irq_flag = disp_al_lcd_query_irq(hwdev_index, LCD_IRQ_TCON0_CNTR,
&lcdp->panel_info);
irq_flag |=
disp_al_lcd_query_irq(hwdev_index, LCD_IRQ_TCON0_TRIF,
&lcdp->panel_info);
if (irq_flag == 0)
goto exit;
if (disp_al_lcd_tri_busy(hwdev_index, &lcdp->panel_info)) {
/* if lcd is still busy when tri/cnt irq coming,
* take it as failture, record failture times,
* when it reach 2 times, clear counter
*/
lcdp->tri_finish_fail++;
lcdp->tri_finish_fail = (lcdp->tri_finish_fail == 2) ?
0 : lcdp->tri_finish_fail;
} else
lcdp->tri_finish_fail = 0;
mgr = lcd->manager;
if (mgr == NULL)
return DISP_IRQ_RETURN;
#if defined(CONFIG_DISP2_LCD_ESD_DETECT)
if (lcdp->lcd_panel_fun.esd_check &&
lcdp->lcd_panel_fun.reset_panel) {
++lcdp->esd_inf.cnt;
if (!atomic_read(&lcdp->lcd_resetting) &&
lcdp->esd_inf.cnt >= lcdp->esd_inf.freq) {
if (!lcdp->esd_inf.esd_check_func_pos ||
lcdp->lcd_panel_fun.esd_check(lcd->disp)) {
/*request reset*/
atomic_set(&lcdp->lcd_resetting, 1);
schedule_work(&lcdp->reflush_work);
printk(KERN_WARNING "wwwwwwwwwwww lcd_esd_check disp_lcd_event_proc 1685 \n");
}
printk(KERN_WARNING "wwwwwwwwwwww after lcd_esd_check disp_lcd_event_proc 1688 \n");
lcdp->esd_inf.cnt = 0;
}
}
#endif
if (lcdp->tri_finish_fail == 0) {
sync_event_proc(mgr->disp, false);
disp_al_lcd_tri_start(hwdev_index, &lcdp->panel_info);
} else
sync_event_proc(mgr->disp, true);
}
spin_lock_irqsave(&lcd_data_lock, flags);
panel_extend_dirty = lcdp->panel_extend_dirty;
lcdp->panel_extend_dirty = 0;
/*这里是我添加的调整oled亮度的地方, bright的值通过ioctl修改,并 lcdp->panel_extend_modify_oledbright; 置位,让中断来处理这个亮度*/
panel_extend_modify_oledbright = lcdp->panel_extend_modify_oledbright;
lcdp->panel_extend_modify_oledbright = 0;
spin_unlock_irqrestore(&lcd_data_lock, flags);
if (panel_extend_dirty == 1)
disp_al_lcd_cfg_ext(lcd->disp, &lcdp->panel_extend_info_set);
if(panel_extend_modify_oledbright == 1)
{
if(lcdp->lcd_panel_fun.oled_set_bright)
{
u32 getbl = disp_lcd_get_bright(lcd);
lcdp->lcd_panel_fun.oled_set_bright(lcd->disp,getbl);
}
}
exit:
printk(KERN_WARNING "wwwwwwwwwwww before DISP_IRQ_RETURN 1709 \n");
/*尝试加入的打印信息, 一旦执行了亮度调整,这个中断服务不再重新进入了,但是亮度调节是成功的*/
return DISP_IRQ_RETURN;
}
解答:
在代码块2里做如下修改
#if 1
if(panel_extend_modify_oledbright == 1)
{
if(lcdp->lcd_panel_fun.oled_set_bright)
{
u32 getbl = disp_lcd_get_bright(lcd);
lcdp->lcd_panel_fun.oled_set_bright(lcd->disp,getbl);
/*加入下面这部分逻辑,让他重新触发中断即可*/
if (lcdp->tri_finish_fail == 0) {
sync_event_proc(mgr->disp, false);
disp_al_lcd_tri_start(hwdev_index, &lcdp->panel_info);
} else
sync_event_proc(mgr->disp, true);
}
}
转载地址: https://blog.csdn.net/qq_43280079/article/details/127189338
什么是MELIS?
Melis是全志科技自研的一套rtos系统。自全志科技创建以来,该系统就已经问世,并持续抗战了近十年,其主要应用其自家的F系列芯片产品上,在MP3与老人机非常流行的时代,该系统配合着其F系列的芯片,真的是能抗能打,早在2012-2013年的时候,Melis的装机量就已经过亿,但因为其属于自研系统,全志科技并没有将其开源,导致虽装机量很多,但是很少人知道。
Melis之所以能抗战那么久,或者说全志科技之所以能在那个芯片行业并不出彩的年代坚持下来,我觉得主要归功于其F1C100s/F1C200s这个代的芯片。这款芯片差不多是十年前的产品,但是因为其具有很强的编解码能力,以及异于常人的性价比,现如今依然活跃在各个微媒体市场。
Melis经过了多个迭代,现如今配合着其最新推出的F133(risc-V)芯片,推出了最新的Melis4.0系统,并使用了更多人熟悉的RT-thread内核,在GitHub上,部分进行了开源,引领广大开发爱好者参与进risc-V的生态建设中。最近想用F133做一点有趣的开发,因此也开始研究Melis4.0。
个人觉得,通过学习melis系统,可以让学习者更了解系统的构成,什么是系统调用?系统是如何加载模块的?什么是BSP?等,对于学习者来说,是非常不错的入门选择。
开发环境
Ubuntu22.04虚拟机
MQ-R(F133) 芒果派开发板
代码下载
GitHub的项目地址如下:
https://github.com/Tina-Linux/d1s-melis
仓库的简介提示,如果需要编译代码,需要手动下载一下相应的工具链并将工具链放在对应目录里。
工具链地址如下:
https://github.com/Tina-Linux/d1s-melis/releases
建议直接下载这个,因为工程中默认的也是这个
代码下载好后,解压。并将下载好并解压好的工具链放在代码的toolchain目录下。(我这里将原项目目录更名为了source,并不影响其它)
一切准备就绪后,即可开始编译。如果是新的系统,建议先安装一些依赖库,后续可能会用到。
sudo apt install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
编译
环境变量
在代码编译之前,我们可能需要设置很多环境变量(lunch 、croot、pack等),因为每一次的环境变量都是比较固定的,因此,项目中,将这些初始化环境变量的操作,都整合成了两个脚本,分别是"envsetup.sh"和“melis-env.sh”。因为melis-env.sh最终会自动调用“envsetup.sh”,因此,我们只需要执行“melis-env.sh”即可
#初始化环境变量
melis-env.sh
初始化环境变量后,我们需要通过“lunch”来选择我们需要编译的项目,lunch是通过扫描projects文件夹下的文件夹,进行识别一共有多少套方案,lunch选中后,系统的编译参数、打包参数,运行参数等,就会使用对应方案文件夹中的配置文件。(具体为什么叫lunch,原因不详,或许本应该是launch?学习英语任重道远!)
对应方案
#通过lunch来选择要编译的项目
lunch
因为我使用的是麻雀的开发板,因此我这里选择3.
编译
以上流程走完后,原工程是可以直接make的,但是第一次编译,或者选择了不同方案后编译,建议都是要clean一下,重新lunch一次,避免环境不干净。
#先clean一下
make clean
#再把方案清一下
make distclean
#重新lunch,再选择一次方案
lunch
#执行make操作
make
注意,如果你在kconfig中,增加了某些选项,但是没有在deconfig中增加对应的配置,直接make可能会报错。需要通过make menuconfig来重新生成默认的编译配置。
#通过make menuconfig选择编译参数
make menuconfig
注意:curses.h: No such file or directory ,那是因为环境没弄好,“sudo apt install libncurses5-dev” 即可
第一次编译,直接保存即可。配置默认是保存在当前目录下的 “.config”文件中,最好不要改文件名。如果不想每次编译都make menuconfig,那么就将当前的“.config”的内容,更新到对应方案目录下的deconfig文件中。比如我要改的话,那我就需要将“.config”文件中的内容,全部替换到**“projects/d1s-mq/configs/deconfig”**文件中,这样每次lunch之后,系统默认来这里来拿编译参数。
以上完成后,就可以直接make了,如果没改过任何东西的话,是可以直接编译通过的。如果不通过,可能要检查一下自己的环境是否安装完善,勤加百度,即可解决问题。
打包
编译成功后,可以通过pack,将编译好的文件,全部打包成一个.img的固件。
因为固件可能是用于卡烧录的,用于nor烧录的,用于nand烧录的,根据烧录方式的不同,需要搭配不同的参数,默认是打包生成nor烧录的固件。
#通过pack指令,打包生成固件(默认生成nor固件)
pack
#生成nand烧录固件
pack -a nand
#生成卡烧录的固件
pack -a card
最终的固件将会生成在“out/对应方案/”目录下。
注意:如果出现如下错误:
这个时候,打包是没有成功的,cd到对应目录下,查看文件夹下,又是存在这个dragon文件,有点神奇。其实是因为dragon这个程序是在32位机器上编译出来的,这是32位的程序,在64位的Ubuntu中运行需要提前安装32位的库。
请执行操作:
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 -y
sudo apt install lib32z1 -y
重新pack后,没有提示,打包成功。
注意事项:
source melis-env.sh 的操作仅在当前终端有效,新开终端,或者关闭了终端后,需要重新source一下,才能进行下面的操作。
lunch操作与source操作一样,都是只对当前终端有效,每次打开新终端,都需要进行一次source、lunch。
小贴士:
source了之后,可以使用各种快捷调整指令,方便开发
croot 回到项目根目录
ckernel 跳转至kernel文件夹
cmodoule 跳转至模块文件夹
。。。(更多操作,可以看环境脚本“envsetup.sh”中的function有哪些)
页次: 1