IAR 8051 嵌入式开发概述

节选自IAR C/C++ Compiler User Guide ,47 ~ 58 页

使用IAR 构建工具开发嵌入式软件

通常,嵌入式软件是为特定的微处理器编写的。程序的主要流程被设计成一个无限循环,等待外部的事件发生,然后做出处理。程序位于单片机的ROM 区域(注:一般单片机都是哈佛架构,与PC 不同),在系统复位后开始执行。编写嵌入式软件时,必须综合考虑多种硬件和软件方面的因素。作为辅助,开发工具提供了编译器选项、扩展关键字、#pragma 预编译指令等。

存储映射和分区

嵌入式系统一般包含多种不同类型的存储区,比如片内RAM、扩展DRAM 或SRAM、ROM、EEPROM,以及flash 存储区。

注:有些单片机是不带RAM 的。单片机使用的哈弗架构,执行程序的时候RAM 并不是必须的,程序内部的变量有些时候只用寄存器就足够了,比如AVR 这种通用寄存器比较多的类型。而PC 则需要先把程序代码和数据全部加载到RAM 之后才能执行。

作为嵌入式软件开发者,必须理解这些存储区的不同特性和功能。举例来说,片内RAM 一般比其他类型的存储要快,对于时序要求高的应用,把经常访问的变量放在片内RAM 里会更有效率。而一些配置信息可能使用不频繁,但是需要在掉电后保持它们的值,这时候就应该把它们放进EEPROM 或者flash 存储。

为了实现高效的存储分配,编译器提供了一些机制,用来指示应该把函数代码和数据放在什么地方,参见控制数据和函数的存储位置 @ 原文259 页。根据特定的链接器配置文件,链接器控制代码和数据区段的存储,参见安排代码和数据 - 链接器配置文件 @ 原文122 页。

与外设单元的通讯

如果有外部器件连接到单片机上,可能就需要初始化并控制信号接口。比如,使用SPI 的CE 引脚,以及监听并处理外部中断信号。通常,这些工作都是在运行时完成的,通过使用单片机的特殊功能寄存器(SFR)实现。这些寄存器一般都位于特定的地址上,包含一些用于配置芯片功能的数据位。

这些寄存器和单片机标准外设的信息在每个器件特定的I/O 头文件中定义,扩展名是*.h*,参见器件支持 @ 原文43页,或者上一篇blog。关于使用的例子,参见访问特殊功能寄存器 @ 原文273 页。

事件处理

在嵌入式系统中,中断 是一种实时处理内外事件的方式,比如检测一个按钮按下的事件,以及串口通信的事件。一般来说,当中断被启用后,一旦触发,单片机就会立即从正在执行的代码中跳出,开始执行中断处理的程序流程。

注:这说的是正在执行main 函数的时候发生了中断。在执行中断处理流程的过程中也可以继续发生中断,这时候要怎么处理就会复杂很多。此外,中断也可以在完全没有执行代码的时候发生,比如单片机休眠后中断唤醒。总之,基本上就是任何时候,而这种灵活性也相应的导致了复杂性。

编译器提供了一些语法,用来管理硬件和软件中断,以及编写自定义的中断处理程序(注:也就是中断服务程序,ISR,向中断服务的流程),参见中断原语、并行以及操作系统相关的编程 @ 原文97 页。

系统启动

在所有的嵌入式系统中,系统启动代码都是用来在main 函执行前初始化系统的程序,这包括对硬件和软件两方面的初始化。

嵌入式软件开发者必须保证启动代码位于特定的地址上,或者有向量表中的指针指向它(注:指复位中断)。这意味着启动代码以及向量表必须放在非易失性存储中,比如ROM、EEPROM、或者flash。

注:说什么废话……一般预定义的启动代码编译之后会自动加进程序里,基本上就几行汇编,哪怕项目里没有驱动代码的文件。大部分时候不用操心这个,也别想着一定要把功能性的初始化代码放进去,那些启动代码的作用是,没有它们,程序本身都跑不动。

C/C++ 应用程序进一步,还需要初始化所有全局变量,这些工作是由链接器和系统启动代码共同处理的。参见应用程序的执行 - 概述 @ 原文52 页,就在这篇后面。

实时操作系统(RTOS)

很多情况下,由一个main 函数构成的嵌入式程序就是唯一在系统上跑的软件。然而,使用RTOS 会有一些好处。

注:RTOS 最基本的功能就是任务调度,类似于有几个灯要定时交替亮这种,大部分时候有这么个调度框架就够用了。

比如说,高优先级任务的执行时序不会被程序其他部分的低优先级任务影响。这通常可以让程序的行为更有确定性,并且可以更有效率的使用CPU、让CPU 在空闲时进入休眠模式,从而可能减少单片机的电量消耗。

使用RTOS 可以让程序更易读、易维护,很多时候还能减少代码体积(注:因为你可能会自己写一堆比较笨拙的代码来实现和RTOS 相似的功能。)。应用代码可以被干净的分离成几个任务,相互间可以相对独立。这可以让团队合作更容易实现,同时开发任务也可以简单的分离成多个任务,由多名开发者或团队同时推进。

最后,RTOS 可以给应用程序创造一个相对干净的开发接口,降低硬件依赖性,可以更容易的将代码移植到不同的硬件平台。

构建流程 - 概述

这一节将介绍程序的构建流程,介绍相关的构建工具——编译器、汇编器(注:就是汇编代码的编译器)和链接器,以及它们的协作过程,从源代码开始,到生成可执行文件。为了更熟悉实践中的过程,你还应该实际运行一两个IAR 提供的示例项目。

注:这部分可以去看看其他描述编译、链接过程的资料,应该有本书名字就叫”程序员的基本修养“,看看也没坏处,如果有时间的话。

编译过程

IDE 中有两个工具,也就是IAR C/C++ 编译器和IAR 汇编器,它们用于将源代码编译成目标文件,这是一种中间产物。两种工具生成的都是IAR UBROF 格式的可重定位目标文件。有需要的话,编译器也可以用来生成汇编源文件,你可以直接修改汇编文件,然后再用汇编器编译成目标文件。关于汇编器的更多信息,参见IAR Assembler User Guide for 8051

编译过程的图示如下:

在这里插入图片描述

编译过后,你可以将任意数量的模块打包成一个库,每个模块就是一个目标文件。使用库的好处是,构成库的每个模块只会在被直接或间接使用时才会链接到应用程序中(注:静态链接)。或者,在创建库之后,也可以使用IAR XAR Library Builder 或IAR XLIB Librarian(注:大概是用来生成IAR 特别的两种库文件格式)。

注:一般很少有必要生成个静态库之后再用,尤其是嵌入式环境,以前的话顶多能省点编译时间吧。如果把代码编译成静态库再用,万一硬件配置改变了,可能还得重新把库编译一遍,灵活性太差了。像arduino 的生态里,基本上库都是按源代码分发,然后就地编译进程序里。

链接过程

目标文件或库文件形式的可重定位模块,被IAR 编译器和汇编器生成后,是不能直接原样执行的。要变成一个可执行文件,首先要经过链接。

IAR 环境使用的链接器是IAR XLINK 链接器。通常,链接器需要以下信息作为输入:

  • 几个目标文件,也可以是库文件;
  • 标准库,包含运行时环境、编译器支持代码和标准库函数及其实现(注:启动代码应该就是所谓的运行时环境的一部分);
  • 程序入口点标签(编译时自动生成);
  • 链接器配置文件,描述代码和数据在目标系统中的存储位置;
  • 关于输出文件格式的信息;

IAR XLINK 链接器可以根据配置生成输出,可以根据需要选择不同的输出格式。你可能会想把输出加载到调试器,也就是说你需要输出调试信息。或者也可能像把输出送给编程器,这时调试信息就是不需要的。使用-F 参数可以配置调试器的输出格式。

链接过程的图示如下:

在这里插入图片描述

链接过程中,链接器可能会在stderrstdout 生成错误信息和日志信息。查看日志信息有助于理解程序的链接过程到底发生了什么,比如说,为什么一个模块会被移除。

更多信息参见IAR Linker and Library Tools Reference Guide

链接之后

IAR XLINK 链接器会生成一个绝对地址目标文件,如果你没有选择别的格式。链接完成后,这个目标文件可以被用于:

  • 加载到IAR C-SPY 调试器或任何其他兼容的外部调试器;
  • 加载到编程器中给目标硬件编程;

输出文件的可能用法如下图:

在这里插入图片描述

应用程序的执行 - 概述

这一部分介绍了嵌入式程序的执行过程,分为三个阶段:

  • 初始化阶段;
  • 执行阶段;
  • 终止阶段;

初始化阶段

初始化阶段在程序启动后,main 函数执行前,也就是CPU 复位后。简单起见,初始化阶段可被分为:

  • 硬件初始化:
    最基本的工作是初始化栈,或者说栈指针(注:如果不知道的话——函数调用返回点、局部变量之类的都在栈里)。硬件初始化通常由系统初始化代码完成,也就是cstartup.s51。同时如果有需要的话,还可以执行一个自定义的底层函数。这可能包括了复位或启动硬件的其他部分、设置CPU 的状态等,用来作为C/C++ 软件系统初始化的准备。
  • C/C++ 软件系统初始化:
    通常,这部分包括初始化每个C/C++ 全局或静态变量或其他符号,确保在main 函数执行前,它们能有一个正确的值(注:未经初始化的内存中,所有值都是随机的)。
  • 应用初始化
    这部分完全取决你的具体应用。可能包括设置和启动RTOS 核心以及启动初始任务。对于完全裸机的应用,这部分可能包括设置各种中断、初始化通讯接口、器件等。(注:这部分一般都是放进main 函数里的。如果用的是Arduino,那就setup 函数及之前的框架初始化代码。

对于一个基于ROM 或者flash 的系统(注:也就是单片机的哈佛架构),所有的常量和函数代码直接放在ROM 中。而所有放在RAM 中的符号(注:只包括全局和静态的量)都必须在main 函数执行前被初始化。链接器在程序执行前已经把RAM 分割成多个区域,分别用于变量、栈、堆以及其他。

以下图示简要介绍了初始化阶段的流程,关于初始化流程各阶段的更多信息,参见系统启动和终止 @ 原文164 页。关于对数据的初始化,参见初始化和系统启动 @ 原文123 页。

1. 复位后

首先系统启动代码执行硬件初始化,比如根据预先划分的栈区域,让栈指针指向正确的位置。

在这里插入图片描述

2. RAM 初始化

将所有RAM 区域用0 填充。

在这里插入图片描述

3. 变量初始化

执行所有全局和静态量的初始化,把它们的数据从ROM 复制到对应RAM 地址。

在这里插入图片描述

4. 调用main 函数

在这里插入图片描述

执行阶段

嵌入式软件主要流程的实现一般是一个循环,然后要么由中断驱动,要么用轮询,从而实现与外部的交互,以及处理内部事件。如果是一个中断驱动的程序,相关的中断配置一般会在main 函数的开头完成。

对于一个有实时性要求的系统,多任务操作系统提供的框架可能是必要的。这意味着你的程序软件应该作为一个实时操作系统的一部分。这种情况下,RTOS 本身以及要执行的各种任务必须要在main 函数中初始化并启动。

终止阶段

通常,嵌入式应用一经启动,永远不会主动终止。如果需要的话,你必须让终止行为明确且合适。

要让一个应用受控的终止,要么调用标准库函数,比如exit_Exitabort,要么直接从main 函数中退出。一旦从main 函数退出,exit 函数会被自动调用,随后所有全局或静态的C++ 对象会被析构,所有打开的文件也会被关闭(注:有点神秘的说法)。

注:以前听说过,好像有什么单片机,它的main 函数外面默认包了一层无限循环,就是说从main 退出之后只会再次回到开头初始化的部分。

当然,如果程序逻辑有问题,程序也可能以不受控的形式终止,也就是一场系统崩溃。更多信息参见系统终止 @ 原文166 页。

注:为了保证哪怕在完全未知的情况也不至于因为失控而造成严重后果,可以使用看门狗自动复位,或者自动执行正确的终止流程。相关的高可靠设计方法的书估计起码能填满一个书架,算上讲反面案例的故事书的话就更多了。当然更简单的还是,有点b 数,别去掺和那些很可能搞出人命的事。

构建应用程序 - 概述

在命令行环境下,要将源代码文件myfile.c 编译成目标文件myfile.r15,可以使用如下命令:

icc8051 myfile.c

这条命令使用默认设置生成了目标文件,实际中你必须明确一些重要的参数,参见基本项目配置 @ 原文56 页,就在后面。

调用链接器可以使用如下命令:

xlink myfile.r51 myfile2.r51 -o a.d51 -f my_configfile.xcl -r

这行示例给链接器输入了两个目标文件,即myfile.r51myfile2.r51my_configfile.xcl 是链接器配置文件。-o 参数设置了输出文件的文件名。-r 参数用来设置输出文件的格式是UBROF,这是可以用C-SPY 工具调试的文件格式。默认情况下程序的入口点标签是_program_start,可以使用-s 参数调整。

构建项目的时候,IDE 环境下会在Build 消息窗口输出大量的构建信息。这些信息可能会派上用场,比如,可以据此生成构建项目的命令行脚本。要启用这个功能,右键点击Build 消息窗口,在弹出菜单中选择All

基本项目配置

这一节将会简要介绍项目的基础配置,从而让编译器和链接器能够根据项目的目标器件生成最佳的程序。在IDE 或命令行环境下你都可以设置这些参数。

基本上,你可能需要设置以下方面的参数:

  • CPU 核心;
  • 数据模式;
  • 代码模式;
  • 调用约定;
  • DPTR 配置;
  • 优化设置;
  • 运行时环境,参见设置运行时环境 @ 原文149 页;
  • 自定义XLINK 链接器配置,参见链接应用程序 章节;

除了这些以外,还有很多其他选项,可以通过精细的调整让结果更佳。关于所有可用的选项的信息,参见编译器选项 以及IDE 项目管理和构建指引 章节。

处理器配置

要让编译器能生成最优化的代码,你应该告诉它你使用的8051 处理器类型。

CPU 核心

编译器支持经典的Intel 8051 微处理器核心、美信(Dallas)DS80C390 核心、Mentor Graphics M8051W/M8051EW 核心,以及相似的其他器件。

命令行环境可以使用参数--core={plain | extended1 | extended2} 选择所使用的CPU 核心类型。这个选项反映了目标单片机的寻址能力。更多信息,参见基本项目设置 - 硬件存储配置 @ 原文61 页。

IDE 环境下,可以在Project > Options > General Options > Target 菜单下,从Core 选项的下拉菜单中选择正确的选项。与核心相关的其他默认配置随后会自动完成,用于链接器和调试器的器件配置文件也会自动选择。

数据模式

8051 系列微处理器的一个特点就是在存储访问能力方面的取舍,可选择的范围从smalllarge,代表着低开销的小存储区域访问方法以及开销大的多的可以访问外部存储区域的方法。更多信息参见理解存储架构 章节。

编译器选项--data_model 可以设置全局和静态变量的默认存储类型和位置,这同时隐含了存储访问方法的设置。

数据存储 章节涵盖了这方面更多的细节,同时还包括了如何在代码中覆盖单个变量的默认设置。

代码模式

编译器支持在文件或单个函数的层面上设置默认的函数位置,这同时隐含了函数调用约定的默认配置。代码模式的更多细节参见函数 章节。

调用约定

用编译器选项--calling_convention 可以控制编译器处理局部数据存储的方式。这可以控制编译器是否要按照默认,使用栈模式,或者用overlay 模式来处理局部数据,以及栈或者overlay frame 的位置(注:暂时没搜到overlay 模式的详细信息,也不知道要怎么翻译,可能就是额外准备个内存区,每一层函数要用的地方都提前划分好。默认的栈模式指的就是把函数调用信息和局部数据都一层一层放到栈里)。也就是在设置,要把局部变量和函数参数放到栈里还是一个overlay 存储区域,以及要把栈或者这个overlay 存储区域放在什么地方。更多信息参见选择调用约定 @ 原文198 页。

DPTR 配置

一些器件可以使用最多8 个数据指针,使用它们可以提高执行一些库函数代码的效率。

使用编译器选项--dptr 可以设置可用的数据指针数量、每个指针的位数(注:比如一般数据指针是16 位的,但是也有更宽的,比如24 位)、以及where the data pointers should be located(注:没理解这句是在说什么)。数据指针的寄存器地址是在编译器配置文件中指定的,也可以在IDE 的项目设置中分别指定。

对速度和体积的优化

编译器可以对代码进行多种优化,包括:

  • 死代码删除:自动删除完全没有用到的代码;
  • 常量传播:将代码中用常量进行的赋值等操作直接用常量替换;
  • 内联:去除函数调用,直接把函数内容原地展开;
  • 公共子表达式删除:当多个表达式或表达式的一部分计算结果一致时,用一个变量替换这些表达式;
  • precision reduction: 不知道,没搜到。可能是多字节变量运算的时候,如果有可能,把它们当更窄的数据类型处理。

编译器也可以对循环做优化,比如把循环展开并删除循环变量(注:就是把循环变成写死的一大串代码)。

你可以在多个优化等级中择其一,在最高优化等级时,你可选择三种不同的优化目标——体积、速度和平衡。大部分优化可以让程序跑的更快,同时体积更小,但是当这种最优结果无法实现时,编译器会根据优化目标来选择要执行的优化。

注:比如内联和循环展开优化,基本上肯定会导致体积膨胀。

优化等级和优化目标可以用在整个程序上,也可以在单个文件甚至单个函数的等级分别指定。此外,一些优化是可以单独被关闭的,比如函数内联。

更多关于编译器优化的以及高效率编码技巧的信息,参见高效嵌入式应用编码 章节。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例 基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例 基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例 基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例基于51单片机开发的小程序,学习资料,用于学习51单片机嵌入式开发,应用案例

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值