RISC-V嵌入式开发准备篇2:嵌入式开发的特点介绍_半斗米的博客-程序员宝宝

技术标签: RISC-V  嵌入式硬件  

原文出处:https://mp.weixin.qq.com/s/ljYZwMj3JaPN29dTAXA3bQ

随着国内第一本RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》 正式上市,越来越多的爱好者开始使用开源的蜂鸟E203 RISC-V处理核,很多初学者留言询问有关RISC-V工具链使用的问题,因此本公众号将开始陆续发表若干篇有关RISC-V软件工具链使用的文章,包括:

本文为RISC-V嵌入式开发准备篇2:嵌入式开发的特点介绍。

本文的目的是对嵌入式开发的特点进行简单的科普与回顾,为后续详细介绍“RISC-V GCC工具链”和“RISC-V汇编语言程序设计”打下基础。注:本文力求通俗易懂,主要面向初学者,对嵌入式开发有所了解的读者可以忽略此文。

在本号上次发表的文章《编译过程简介》中介绍过,嵌入式系统的程序编译过程和开发有其特殊性,譬如:

  • 嵌入式系统需要使用交叉编译与远程调试的方法进行开发。
  • 需要自己定义引导程序。
  • 需要注意减少代码体积(Code Size)。
  • 需要移植printf从而使得嵌入式系统也能够打印输入。
  • 使用Newlib作为C运行库。
  • 每个特定的嵌入式系统都需要配套的板级支持包。
    下文将分别予以介绍。

1 交叉编译和远程调试

在本号上次发表的文章《编译过程简介》中介绍了如何在Linux系统的PC电脑上开发一个Hello World程序,对其进行编译,然后运行在此电脑上。在这种方式下,我们使用PC电脑上的编译器编译出该PC电脑本身可执行的程序,这种编译方式称之为本地编译。

嵌入式平台上往往资源有限,嵌入式系统(譬如常见ARM MCU或8051单片机)的存储器容量通常只在几KB到几MB之间,且只有闪存而没有硬盘这种大容量存储设备,在这种资源有限的环境中,不可能将编译器等开发工具安装在嵌入式设备中,所以无法直接在嵌入式设备中进行软件开发。因此,嵌入式平台的软件一般在主机PC上进行开发和编译,然后将编译好的二进制代码下载至目标嵌入式系统平台上运行,这种编译方式属于交叉编译。

交叉编译可以简单理解为,在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序,譬如,在x86平台的PC电脑上编写程序并编译成能运行在ARM平台的程序,编译得到的程序在x86平台上不能运行,必须放到ARM平台上才能运行。

与交叉编译同理,在嵌入式平台上往往也无法运行完整的调试器,因此当运行于嵌入式平台上的程序出现问题时,需要借助主机PC平台上的调试器来对嵌入式平台进行调试。这种调试方式属于远程调试。

常见的交叉编译和远程调试工具是GCC和GDB。在本号上次发表的文章《编译过程简介》中介绍了如何使用Linux自带的GCC本地编译一个Hello World程序并运行。但是,GCC不仅能作为本地编译器,还能作为交叉编译器;同理GDB不仅可以作为本地调试器,还可以作为远程调试器。

当作为交叉编译器之时,GCC通常有不同的命名,譬如:

  • arm-none-eabi-gcc和arm-none-eabi-gdb是面向裸机(Bare-Metal)ARM平台的交叉编译器和远程调试器。
    • 所谓裸机(Bare-Metal)是嵌入式领域的一个常见形态,表示不运行操作系统的系统
  • 而riscv-none-embed-gcc和riscv-none-embed-gdb是面向裸机RISC-V平台的交叉编译器和远程调试器。
    • 本号后续发文《RISC-V GCC工具链的介绍》将介绍RISC-V GCC工具链的更多信息。

2 移植newlib或newlib-nano作为C运行库

newlib是一个面向嵌入式系统的C运行库。相对于本号上次发表的文章《编译过程简介》中介绍的glibc,newlib实现了大部分的功能函数,但体积却小很多。newlib独特的体系结构将功能实现与具体的操作系统分层,使之能够很好地进行配置以满足嵌入式系统的要求。由于专为嵌入式系统设计,newlib具有可移植性强、轻量级、速度快、功能完备等特点,已广泛应用于各种嵌入式系统中。

由于嵌入式操作系统和底层硬件的多样性,为了能够将C/C++语言所需要的库函数实现与具体的操作系统和底层硬件进行分层,newlib的所有库函数都建立在20个桩函数的基础上,这20个桩函数完成具体操作系统和底层硬件相关的功能:

  • I/O和文件系统访问(open、close、read、write、lseek、stat、fstat、fcntl、link、unlink、rename);
  • 扩大内存堆的需求(sbrk);
  • 获得当前系统的日期和时间(gettimeofday、times);
  • 各种类型的任务管理函数(execve、fork、getpid、kill、wait、_exit);
    这20个桩函数在语义、语法上与POSIX(Portable Operating System Interface of UNIX)标准下对应的20个同名系统调用完全兼容。

所以,如果需要移植newlib至某个目标嵌入式平台,成功移植的关键是在目标平台下找到能够与newlib桩函数衔接的功能函数或者实现这些桩函数。本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将介绍蜂鸟E200的HBird-E-SDK平台如何实现移植实现newlib的桩函数。

注意:newlib的一个特殊版本newlib-nano版本进一步为嵌入式平台减少了代码体积(Code Size),因为newlib-nano提供了更加精简版本的malloc和printf函数的实现,并且对库函数使用GCC的-Os(侧重代码体积的优化)选项进行编译优化。

3 嵌入式引导程序和中断异常处理

在本号上次发表的文章《编译过程简介》中介绍了如何在Linux系统的PC电脑上开发一个Hello World程序,对其进行编译,然后运行在此电脑上。在这种方式下,程序员仅仅只需要关注Hello World程序本身,程序的主体由main函数组织而成,程序员可以无需关注Linux操作系统在运行该程序的main函数之前和之后需要做什么。事实上,在Linux操作系统中运行应用程序(譬如简单的Hello World)时,操作系统需要动态地创建一个进程、为其分配内存空间、创建并运行该进程的引导程序,然后才会开始执行该程序的main函数,待其运行结束之后,操作系统还要清除并释放其内存空间、注销该进程等。

从上述过程中可以看出,程序的引导和清除这些“脏活累活”都是由Linux这样的操作系统来负责进行。但是在嵌入式系统中,程序员除了开发以main函数为主体的功能程序之外,还需要关注如下两个方面:

  • 引导程序:
    • 嵌入式系统上电后需要对系统硬件和软件运行环境进行初始化,这些工作往往由用汇编语言编写的引导程序完成。
    • 引导程序是嵌入式系统上电后运行的第一段软件代码。引导程序对于嵌入式系统非常关键,引导程序所执行的操作依赖于所开发的嵌入式系统的软硬件特性,一般流程包括:初始化硬件、设置异常和中断向量表、把程序拷贝到片上SRAM中、完成代码的重映射等,最后跳转到main函数入口。
    • 本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将结合HBird-E-SDK平台的引导程序实例了解引导程序的更多细节。
  • 中断异常处理
    • 中断和异常是嵌入式系统非常重要的一个环节,因此,嵌入式系统软件还必须正确地配置中断和异常处理函数。有关RISC-V架构的中断和异常的详细信息,请参见RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》 中第13章内容《不得不说的故事——中断和异常》。
    • 本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将结合HBird-E-SDK程序实例了解如何配置中断和异常处理函数。

4 嵌入式系统链接脚本

在本号上次发表的文章《编译过程简介》中介绍了如何在Linux系统的PC电脑上开发一个Hello World程序,对其进行编译,然后运行在此电脑上。在这种方式下,程序员也无需关心编译过程中的“链接”这一步骤所使用的链接脚本,无需为程序分配具体的内存空间。

但是在嵌入式系统中,程序员除了开发以main函数为主体的功能程序之外,还需要关注“链接脚本”为程序分配合适的存储器空间,譬如程序段放在什么区间、数据段放在什么区间等等。

本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将结合HBird-E-SDK的“链接脚本”实例了解更多细节。

5 减少代码体积

嵌入式平台上往往存储器资源有限,嵌入式系统(譬如常见的ARM MCU或8051单片机)的存储器容量通常只在几KB到几MB之间,且只有闪存而没有硬盘这种大容量存储设备,在这种资源有限的环境中,程序的代码体积(Code Size)显得尤其重要,因此,有效地降低降低代码体积(Code Size)是嵌入式软件开发必须要考虑的问题,常见的方法如:

  • 使用newlib-nano作为C运行库以取得较小代码体积(Code Size)的C库函数。
  • 尽量少使用C语言的大型库函数,譬如在正式发行版本的程序中避免使用printf和scanf等函数。
  • 如果在开发的过程中一定需要使用printf函数,可以使用某些自己实现的阉割版printf函数(而不是C运行库中提供的printf函数)以生成较小的代码体积。
  • 除此之外,在C/C++语言的语法和程序开发方面也有众多技巧以取得更小的代码体积(Code Size)。
  • 本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将结合HBird-E-SDK平台实例了解更多“减少代码体积”的实现细节。

减小代码体积(Code Size)的方法很多,本文在此不做一一赘述,请初学的读者自行查阅相关资料进行学习。

6 支持printf函数

在本号上次发表的文章《编译过程简介 》中介绍了如何在Linux系统的PC电脑上开发一个Hello World程序,程序中使用C语言的标准库函数printf打印了一个“Hello World”字符串。该程序在Linux系统里面运行的时候字符串被成功的输出到了Linux的终端界面上。在这个过程中,程序员无需关心Linux系统到底是如何将printf函数的字符串输出到Linux终端上的。事实上,如《编译过程简介》中所述,在Linux本地编译的程序会链接使用Linux系统的C运行库glibc,而glibc充当了应用程序和Linux操作系统之间的接口,glibc提供的 printf 函数就会调用如sys_write等操作系统的底层系统调用函数,从而能够将“字符串”输出到Linux终端上。

从上述过程中可以看出,由于有glibc的支持,所以printf函数能够在Linux系统中正确的进行输出。但是在嵌入式系统中,printf的输出却不那么容易了,基于如下几个原因:

  • 嵌入式系统使用newlib作为C运行库,而newlib的C运行库所提供的printf函数最终依赖于如本文中所介绍的newlib桩函数write,因此必须实现此write函数才能够正确的执行printf函数。
  • 嵌入式系统往往没有“显示终端”存在,譬如常见的单片机其作为一个黑盒子一般的芯片,根本没有显示终端。因此,为了能够支持显示输出,通常需要借助单片机芯片的UART接口将printf函数的输出重新定向到主机PC的COM口上,然后借助主机PC的串口调试助手显示出输出信息。同理,对于scanf输入函数,也需要通过主机PC的串口调试助手获取输入然后通过主机PC的COM口发送给单片机芯片的UART接口。
  • 从以上两点可以看出,嵌入式平台的UART接口非常重要,往往扮演了输出管道的角色,为了能够将printf函数的输出定向到UART接口,需要实现newlib的桩函数write,使其通过编程UART的相关寄存器将字符通过UART接口输出。

本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将结合HBird-E-SDK平台移植printf函数的实例了解更多细节。

7 提供板级支持包

对于特定的嵌入式硬件平台,为了方便用户在硬件平台上开发嵌入式程序,硬件平台一般会提供板级支持包(Board Support Package,BSP)。板级支持包所包含的内容没有绝对的标准,通常说来,其必须包含如下内容:

  • 底层硬件设备的地址分配信息
  • 底层硬件设备的驱动函数
  • 系统的引导程序
  • 中断和异常处理服务程序
  • 系统的链接脚本
  • 如果使用newlib作为C运行库,一般还提供newlib桩函数的实现。

由于板级支持包往往会将很多底层的基础设施和移植工作搭建好,因此应用程序开发人员通常都无需关心本文第1.2节至第1.6节中描述的内容,能够从底层细节中被解放出来避免重复建设而出错。本号后续发文《基于HBird-E-SDK平台的软件开发与运行》将结合HBird-E-SDK平台的BSP实例了解更多细节。

更多信息

感兴趣的读者可以通过下面二维码关注公众号“硅农亚历山大”,了解Verilog、IC设计、CPU、RISC-V和人工智能AI相关的更多设计技巧和经验分享,注意:由于干货太多,请自备茶水。

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zoomdy/article/details/83614703

智能推荐

boost:any_春泥面包的博客-程序员宝宝

引文:http://club.topsage.com/thread-2276552-1-1.html引文讲解的非常详细,这里做一下学习记录。Any库支持类型安全地存储和获取任意类型的值。当你需要一个可变的类型时,有三种可能的解决方案:无限制的类型,如 void*. 这种方法不可能是类型安全的,应该象逃避灾难一样避免它。可变的类型,即支持多种类型的存储和获取的类型。支持转换的类型,如字符串类型与

CVX踩过的坑_汇源果汁121的博客-程序员宝宝

CVX踩过的坑公式错误很多时候如果报错是一个非法的操作,而且是跟凸或凹,仿射相关的错误,那应该是公式推导或者说是代码过程中写错了常用公式替代^2可以用square替代,各种小技巧有待探索mosek1、“引用了不存在的字段‘sol’“ 。。。cvx end那报错后续查看了一下数值,可能存在进行尺度变换的时候将数值放大到了CVX没法求解的程度,可以适当缩小尺度变化的大小求解1、最优解不符合限制条件这个问题很玄学,没想到解决方法2、各种求解器不同点有点大...

CF946G Almost Increasing Array 题解 线段树优化dp_FelFa_1414666的博客-程序员宝宝

codeforces 传送门 & 洛谷传送门Description有一个长度为 nnn 的整数序列,每次操作可以把一个元素修改为任意整数。求最少要几次操作,使得序列能满足:删除一个元素后是严格单调增的。2≤n≤2×1052\le n\le 2\times 10^52≤n≤2×1051≤ai≤1091\le a_i\le 10^91≤ai​≤109Solution要求最小修改次数,我们不妨考虑求最大保留元素个数。这样其实类似于求最长上升子序列,但是因为要求严格单增且必须是整数,所

addition过程 sgnb_5G ENDC架构中PSCell更换流程_weixin_39737764的博客-程序员宝宝

继续协议学习,应该说NSA的各个方面的规范文本及实现都要比SA过程复杂的多。今天的topic是anchor MeNB不变的情况下,SgNB cell的变换流程。此场景也是在ENDC网络下经常遇到的情况,处于ENDC双连接情况的UE在移动过程中不但要测量MeNB邻区的信号(以备MeNB cell之间的切换),同时也需要测量除当前双连接之外其他邻接SgNB cell的信号强度,以备更换到信号更好的双连...

ipadpro2020和ipadair3参数对比哪个好_妙龙的博客-程序员宝宝_air3和pro2020的参数对比

2020年的ipadpro配备了A12Z这款性能强大的仿生处理器。相比air3提升的不止一点半点。我们在使用的时候可以体验到不同于以往数码产品的流畅感。可以说ipadpro的性能足以和市面上常见的笔记本一较高下,这是air3所不具备的。ipadpro2020更多使用感受和评价: https://www.apple.com.cnipadair3更多使用感受和评价: https://www.apple.com.cn还有就是它采用了后置双摄的设计,两颗摄像头的设计在iPad上面还是第一次使用。因为ipa

二维离散傅里叶变换与逆变换的原理与实现(Matlab)_闵叶灵的博客-程序员宝宝_二维离散傅里叶逆变换

前言在野外数据采集中,虽然单个仪器采集的是一维信号,但是当把多台仪器数据汇总并生成做二维剖面的图像时,噪声可不只有一维的,更有x,y两个方差同时存在的"二维噪声"!我们已经知道一维噪声可以用一维傅里叶变换到频域滤波,同理二维噪声也可以用二维傅里叶变换到"频率滤波"。二维傅里叶正变换的原理笔者很讨厌一上来就看到一连串复杂的公式!因此当我看懂一个原理后,我就会用最好理解的方式来重述它,毕竟我更偏...

随便推点

EA15的使用_原始猿miser的博客-程序员宝宝_ea15

EA(version15)的使用EA的使用之UML图UML功能模型用例图用例图的制作对象模型动态模型功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入EA的使用之UML图本节是EA中关于UML图的制作UMLUML图称为统一建

使用CURL断点续传时遇到的数据类型问题_hhello的博客-程序员宝宝

使用vc2008+CURL开发 HTTP 文件下载时,发现只要设置了断点续传,就无法执行下载,向CURL注册的下载回调函数根本就不会被调用。其实是CURLOPT_RESUME_FROM_LARGE的参数类型没有正确设置。

eclipse配置c/c++开发环境————(2020.3.23学习笔记)_曾胖神父的博客-程序员宝宝_eclipse c++

配置前提:已经安装eclipse,并且已经配置好java开发环境。这个前提满足之后就可以开始给eclipse配置c/c++开发环境了,给eclipse配置c/c++开发环境具体可分为两步第一步:安装CDT插件1:eclipse菜单 -> Help -> Install New Software… -> Work with (Add…)Name:CDTLocation:...

sd 卡驱动在2.6内核的编写.sd/mmc/sdio kernel,sd/mmc/sdio 内核_weixin_30917213的博客-程序员宝宝

sd卡驱动主要参照已有的文件即可,2410,9260都挺好。其实写驱动主要是搞清楚工作流程即可。我这里写一些心得与大家分享下,基于2.6.24:1、主要的结构体:static const struct mmc_host_ops my_mci_ops = { .request = my_mci_request, //命令数据请求 .set_ios...

Java基础学习总结(21)——常用正则表达式列表_科技D人生的博客-程序员宝宝

很多不太懂正则的朋友,在遇到需要用正则校验数据时,往往是在网上去找很久,结果找来的还是不很符合要求。所以我最近把开发中常用的一些正则表达式整理了一下,包括校验数字、字符、一些特殊的需求等等。给自己留个底,也给朋友们做个参考。一、校验数字的表达式数字:^[0-9]*$n位的数字:^\d{n}$至少n位的数字:^\d{n,}$m-n位的数字:^\d{m,n}$零和非零开头的数字

推荐文章

热门文章

相关标签