第3章 第一个样例程序及工程组织 - 图文

更新时间:2024-04-24 08:00:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第3章

第一个样例程序及工程组织

本章导读:本章是全书的重点和难点之一,主要内容有①以小灯闪烁工程为例,阐述第一个C语言工程的框架、组织结构与编程规范,给出底层驱动构件的封装示范,完成CodeWarrior环境下第一个Kinetis工程的入门;②给出通用输入/输出(General Purpose Input/Output, GPIO )基本概念及使用方法,阐述GPIO模块编程要点及驱动构件设计方法;③阐述开发人员编写源程序文件的基本规范及组织方法;④给出CodeWarrior环境下工程文件的打开、编译、链接、机器码下载的过程;⑤剖析机器码文件以达到透彻理解第一个样例程序的执行过程;⑥阐述工程组织体系下的文件夹及文件的组成,说明哪些文件对K60芯片是固定不变的,哪些文件是编译或链接过程中产生的,给出这些文件的基本用法。

3.1 GPIO模块的驱动构件设计

3.1.1 GPIO的基础知识

1. I/O接口的概念

I/O接口,即输入/输出接口,是微控制器同外界进行交互的重要通道。接口的英文是Port, 也可以翻译为“端口”;另一个英文单词是Interface,也翻译为接口。从中文字面看,接口与端口似乎有点区别,但在嵌入式系统屮它们的含义是相同的。有时1/0引脚称为接口(Interface),而把用于对1/0引脚进行编程的寄存器称为端口(Port),实际上它们是紧密相连的。因此,不必深究它们之间的区别。有些书中甚至直接称I/O接口(端口)为I/O 口。在嵌入式系统中,接口千变万化,种类繁多,有显而易见的人机交互接口,如操纵杆、键盘、显示器;也有无人介入的接口,如网络接口、机器设备接口等。

2. 通用I/O (GPIO)

所谓通用I/O,也记为GPIO (General Purpose I/O),即基本的输入/输出,有时也称并行I/O,或普通1/0,它是I/O的最基本形式。本书中使用正逻辑,电源(Vcc)代表高电平,对应数字信 号“1”;地(GND)代表低电平,对应数字信号“0”。作为通用输入引脚,MCU内部程序可以通过端口寄存器读取该引脚,以确定该引脚是“1”(高电平)或“0”(低电平),

即开关量输入。

作为通用输出引脚,MCU内部程序通过端口寄存器向该引脚输出“1”(高电平)或“0”(低电 平),即开关量输出。大多数通用I/O引脚可以通过编程来设定其工作方式为输入或输出,称之为双向通用I/O。

3.上拉/下拉电阻与输入引脚的基本接法

芯片输入引脚的外部有三种不同的连接方式:带上拉电阻的连接、带下拉电阻的连接和“悬 空”连接。通俗地说,若MCU的某个引脚通过一个电阻接到电源(Vcc)上,这个电阻被称为“上拉电阻”;与之相对应,若MCU的某个引脚通过一个电阻接到地(GND)上,则相应的电阻 被称为“ 下拉电阻”。这种做法使得悬空的芯片引脚被上拉电阻或下拉电阻初始化为高电平或低电平。根据实际情况,上拉电阻与下拉电阻可以取值在1~10 kΩ之间,其阻值大小与静态电流 及系统功耗有关。

图3-1给出了一个MCU的输入引脚的三种外部连接方式,假设MCU内部没有上拉或下拉电阻,图中的引脚I3上的开关K3采用悬空方式连接就不合适,因为K3断开时,引脚I3的电平不确定。在图3-1中,R1>>R2, R3<

图3-1 I/O口输入电路

4.输出引脚的基本接法

作为通用输出引脚,MCU内部程序向该引脚输出高电平或低电平来驱动器件工作,即开关量输出。在图3-2中,输出引脚01和02釆用了不同的方式驱动外部器件。

输出引脚O1直接驱动发光二极管LED,当O1引脚输出高电平时,LED不亮;当O1引脚输出低电平时,LED点亮。这种接法的驱动电流一般为2?10 mA。

输出引脚O2通过一个NPN三极管驱动蜂鸣器,当02引脚输出高电平时,三极管导通,蜂鸣器响;

当02引脚输出低电平时,三极管截 止,蜂鸣器不响。这种接法可以用 图3-2 I/O口输出电路 O2引脚上的几个mA的控制电流驱动高达100 mA的驱动电流。

若负载需要更大的驱动电流,就必须釆用另外的驱动电路, 但对于MCU编程来说,没有任何影响。

3.1.2 GPIO模块概要与编程药店

这是本书首次接触到Kinetis微控制器寄存器的功能与编程。对于模块及相应寄存器功能的介绍,也就是用户手册文档的翻译工作,其英文资料和屮文翻译放在本书网上光盘的“..\\WYH-K60-BookA-CD(V1.0)\\分章阅读资料”,也会综合出现在另一本著作《Kinetis微控制器体系结构与开发实践——基于ARM Cortex-M4内核》中。本书作为教材,为节省篇幅,并更好地适合于教学需要,暂不给出模块及相应寄存器功能的详细介绍,只给出模块概要与编程要点,以及模块驱动构件设计和驱动构件测试实例,这些构件可以直接应用于实际产品的研发中,也可封装到实时操作系统中供任务调用。

1.GPIO模块概要

K60的大部分引脚具有多重功能,可以通过编程来设定使用其中一种功能。144引脚封装的K60芯片具有5个GP10口,分别为A 口、B 口、C 口、D 口和E 口,共含100个引脚。每个口实际所含的引脚数不同,下面给出各口可作为GP10功能的引脚数目及引脚名称。

A口有26个引脚,分别为PTA0?PTA19、PTA24?PTA29; B口有20个引脚,分別为PTB0?PTB11、PTB16?PTB23; C口有20个引脚,分别为PTC0?PTC19; D口有16个引脚,分别为PTD0?PTD15;

E口有18个引脚,分別为PTE0?PTE12、PTE24?PTE28。

每个GPIO 口均包含6个寄存器,表3-1给出了一个GPIO 口的6个寄存器功能的简要说明,其屮x表示A、B、C、D、E屮的任意一个。各寄存器均为32位,复位值均为0x00000000。

表3-1 GPIO寄存器

2. GPIO模块编程要点

GPIO模块的基本编程步骤如下。

(1)通过设置“数据方向寄存器”来指定相应引脚为输入或输出功能。若指定位为0,

则为输入;若指定位为1,则为输出。

(2)若是输出引脚,则通过设置“数据输出寄存器”来指定相应引脚输出低电平或高电平,对应值为0或1。亦可通过“输出置位寄存器”、“输出清位寄存器”、“输出取反寄存器”改变引脚状态,参见表3-1中关于寄存器的说明。

(3)若是输入引脚,则通过“数据输入寄存器”获得引脚的状态。若指定位为0,表示当前该引脚上为低电平;若为1,则为高电平。

3. 置位与清位的宏函数

设置寄存器某一位为1,称为置位;设置寄存器某一位为0,称为清位。这在底层驱动编程时经常用到。置位与清位的基本原则是:当对寄存器的某一位进行置位或清位操作时,不能干扰该寄存器的其他位,否则,可能会出现意想不到的错误。

综合利用“<<”、“>>”、“丨”、“&”、“?”等位运算符,可以实现置位与清位,且不影响其他位的功能。

下面以8位寄存器为例进行说明,其方法适用于各种位数的寄存器。设R为8位寄存器,下面说明将R的某一位置位与清位,而不干预其他位的编程方法:

(1)置位。要将R的第3位置1,其他位不变,可以这样做:R|=(l<<3),其中“1<<3”的 结果是“0b00001000”,R |= (1<<3)也就是R=R|0b00001000,任何数和0相或不变,任何数和1 相或为1,这样达到对R的第3位置1,但不影响其他位的目的。

(2)清位。要将R的第2位清0,其他位不变,可以这样做:R&=?(1<<2),其屮“?(1<<2)” 的结果是“0blllll0ll”,R&=?(1<<2)也就是R=R&0b11111011,任何数和1相与不变,任何数和0相与为0,这样达到对R的第2位清0,但不影响其他位的0的。

(3)获得某一位的状态。(R>>4)&1,是获得R第4位的状态,“R>>4”是将R右移4位, 将R的第4位移至第0位,即最后1位,再和1相与,也就是和0b00000001相与,保留R最后1位的值,以此得到第4位的状态值。

为了方便使用,把这种方法改为带参数的“宏函数”,并且简明定义,放在公共头文件(common.h)中。使用该“宏”的文件,可以包含“common.h”文件。

这样就可以使用BSET、BCLR、BGET这些容易理解与记忆的标识,进行寄存器的置位、清位及获得寄存器某一位状态的操作。

4.开关量输入与输出的编程方法

设x 口的“数据方向寄存器”为GPIOx_PDDR、“数据输出寄存器”为GPIOx_PDOR、“数据输入寄存器”为GPIOx_PDIR。

1 )幵关量输出的编程方法

首先初始化端口的引脚数据方向为输出,然后运用该引脚的数据寄存器进行数据输出。如:若要x口的第3引脚输出高电平,可编写如下代码:

2)开关量输入的编程方法

首先初始化端口的引脚数据方向为输入,然后运用该引脚将外界数据输入给对应数据寄存器中。如:若要获得x口的第3引脚的输入数据,可编写如下代码:

3.1.3 GPIO驱动构件设计

对底层构件进行设计时,最关键的工作是对构件的共性与个性进行分析,杣取出构件的属性和对外接口函数。应尽量做到:当一个底层构件应用到不同系统时,仅需要修改构件的头文件,而对于构件的源程序文件则不必修改或改动很小。

GPIO引脚可以被定义成输入、输出两种情况:若是输入,程序需要获得引脚的状态(逻辑1或0);若是输出,程序可以设置引脚状态(逻辑1或0)。MCU的GPIO模块分为许多端口, 每个端口有若干引脚。为了实现对所有GPIO的引脚统一编程,本实例设计了 GPIO构件(由 hw_gpio.h、hw_gpio.c两个文件组成),如要使用GPIO构件,只需要将这两个文件加入到所建工程中,由此方便了对GPIO的编程操作。实际上,把构件封装好之后再使用构件时,仅需看头文件中的相关函数接口说明即可。

这是本书的第一个底层驱动构件,请注意以下编程要点。

(1)在驱动文件中要做好必要的注释,包括文件说明、函数头注释、行注释、边注释等,请读者注意体会。

(2)在编写驱动构件时,考虑到可移植性,原则上不使用全局变量。

(3)遵循统一的驱动构件命令规范,包括文件、函数、变量的命名规范等。

(4)驱动构件头文件(.h)屮仅包含对外接口函数的声明,对外接口函数及内部函数的实现在构件源程序文件(.c)中实现。同时应注意,头文件声明对外接口函数的顺序与源程序文件实现对外接口函数的顺序应保持一致,源程序文件中内部函数的声明放在接口函数的前面,其实现代码放在对外接口函数的后面。

1. GPIO 构件头文件(hw_gpio.h)

在GPIO驱动构件的头文件(hw_gpiah)中包含的内容有①防止重复包含的条件编译代码结 构“#ifndef ...#define ...#endif”;②用宏定义方式统一了使用的端口名称:PORT_A、PORT_B、 PORT_C、PORT_D、PORT_E ;③声明了 5个对外服务函数接口;加载所有端口时钟函数 (hw_gpio_enable_port)、端口引脚初始化函数(hw_gpio_init)、获取引脚状态函数(hw_gpio_get)、 设定引脚状态函数(hw_gpio_set)、反转引脚状态函数(hw_gpio_reverse)。

原则上,在进行应用程序开发时,可以只看头文件,就能应用GPIO编程。

2. GPIO构件源程序文件(hw_gpio.c)

GPIO驱动构件的源程序文件中定义的对外接U函数,主要是对相关寄存器进行配置,从而完成构件的基本功能。构件内部使用的函数也在构件源程序文件中定义。

3.2 CodeWarrior开发环境简介

嵌入式软件开发有别于桌面软件开发的一个显著的特点在于,它需要一个交叉编译和调试环境,即工程的编辑和编译所使用的软件通常在pc上运行,而编译生成的嵌入式软件的机器码文件则需要通过写入工具下载到目标机(如K60)上执行。由于主机和目标机的体系结构存在差异, 从而增加了嵌入式软件开发的难度,因此,选择好的开发套件将有助于学习与开发。本书以飞思卡尔公司的 CodeWarrior Development Studio for Microcontroller v10.l 集成开发环境(以下简称CW vl0.l)作为嵌入式软件集成开发平台,在苏州大学飞思卡尔嵌入式中心设计的Kinetis写入器及K60核心板(见2.4.4节)的硬件基础上进行开发应用。本书中提供的所有样例程序均在该平台 下调试通过。

1. CodeWarrior功能和特点

CodeWarrior?(以下简称CW)集成开发环境是飞思卡尔公司推出的面向嵌入式应用开发的商业软件工具,功能强大。作为一个完整的集成开发环境,CodeWarrior集成了方便开发人员使用的编辑器、源码浏览器、搜索引擎、构造系统、调试器和工程管理器等功能模块,提供了高度可视化操作及自动创建复杂嵌入式系统应用的功能,为飞思卡尔嵌入式产品开发提供了便利。2012年初,飞思卡尔官方推荐使用CW v10.1进行K60及其他MCU的开发。

CW vl0.l有两种默认的授权版本,即试用版和特别版。安装试用版后,CW vl0.l有30天的试用期限,授权期过后,如要继续使用,需要中请license,否则仅能作为特别版使用。特别版无时间限制,但是限制生成的机器代码大小,对于本书说明的Kinetis系列MCU限制生成的机器码大小为128 KB。

2. CodeWarrior安装与配置

飞思卡尔公司为其网上注册用户在官方网站(www.freescale.com/cwmcul0)提供了Code Warrior安装文件的下载链接,下载安装文件后执行,即可根据提示进行安装。需要注意的是,CW v10.l基于Eclipse开放集成开发环境,有适用于Windows版和Linux版两个平台的版本,本书中使用Windows平台卜的CW vl0.l。CW vl0.l环境的运行界面如图3-3所示。

下载调试程序时还需安装下载调试器的驱动程序。CW vl0.l安装后的程序文件夹中已包含了 Kinetis写入器的驱动程序文件,将写入器接到PC时,Windows会提示发现新硬件,使用系统自动安装向导进行自动安装即可。

图3-3 CWvlO.l运行界面

关于CWvl0.l的简要使用方法,参见网上光盘的“.AWYH-K60-BookA-CD(V1.0)\\整体

资料\\ CW_vl0.1 简明操作 v 1.1.pdf”。

3.3 嵌入式设计编码基本规范

在嵌入式软件设计过程中,遵循一定的规范有重要意义:统一的规范对于开发人员进行设计有一定的指导作用;开发人员在熟悉了规范框架后,很容易对基于同一规范框架下的其他工程进行理解,可提高阅读代码的效率;编码规范的设计以简单、实用、可靠为原则,编码规范一经确定,须注意统一使用。

为了与RTOS无缝衔接,即有RTOS与无RTOS系统的底层驱动构件做到一致,本规范将对编码风格做一定的约定,供编程人员参考。涉及内容包括对基本数据类型、自定义结构体类型、变量、函数等命名的规范及嵌入式驱动的编程规范。

(1)编码过程中要添加足够的注释,包括文件和函数的头注释、代码块的行注释、代码语句的旁注释等,修改代码时需同时更新注释,保持代码与注释的一致。注释统一使用“//”方式, 不再使用“/* */”方式。

(2)合理使用缩进和空行,保持程序代码结构整齐、淸晰。 (3)对于程序屮使用的自定义标识符(变量名、函数名等),统一使用下划线分隔内 部单词。

3.3.1 硬件驱动构件文件

构件化底层驱动函数工作是对寄存器操作的基本封装。在无操作系统下,可直接调用底层驱动函数进行编程。

1. 文件命名

(1)底层驱动构件文件名统一使用前缀“hw_”,表示为硬件底层驱动文件。

(2)底层驱动构件文件成对出现,各构件有一个头文件(.h)和一个实现代码文件(.c)。头文件屮包括文件包含、宏定义、类型定义、函数接口声明等内容,实现代码文件屮包含变量定义、 函数实现等内容。

(3)依据构件名为驱动构件命名。 (4)文件名中全部使用小写字符。

例如,GPIO功能模块的底层驱动构件文件名为“hw_gpio.h”、“hw一gpioi.c”。

2. 文件头注释

在底层驱动文件的开始部分加入文件注释,对本文件进行简要描述。

(1)统一注释项目为:“文件名称”、“功能概要”、“版权所有”、“版本更新”等。 (2)对于代码的修改,要及时在文件头注释中说明。

3.3.2数据类型

1.基本数据类型

嵌入式程序设计与一般的程序设计有所不同,与嵌入式程序屮打交道的大多数都是底层硬件的存储中.元或是寄存器,所以在编写程序代码时,使用的基本数据类型多以字节、字、双字为单位。因此,从使用的方便性和可移植性考虑,通常需要重新定义几种基木的数据类型。另外需要注意,对于不同的编译器,为基本数据类型分配的比特数不同,需根据具体编译器的配置进行数据类型的定义。

例如,在定义嵌入式基本数据类型的头文件(如common.h)时,应包含至少如下类型定义。

对数据类型的命名参考样例屮的命名方式,使用“uint8”、“uint16”、“uint32”、“int8”、“int16”、 “int32”等短命名方式。在不同的操作系统下开发时,若原有定义的嵌入式基本数据类型名称与样例中不一致,可保留其定义,避免繁重且高风险的工作量。但对于新添加的程序,要遵循本规范,保持代码风格的一致性,以便于使用和阅读

另外,在此说明不优化变量修饰符(volatile)的作用。volatile告诉编译器不优化类型变量是随时可能发生变化的,因此,在每次使用它的时候必须从定义该变量分配的存储空间中取值,而不能通过编译器将这个访问过程优化。观察芯片寄存器头文件,读者就会发现,在指定寄存器地址时,就会用到不使用优化类型变量的设计。

2.结构体数据类型

在程序编写过程中,经常耑要使用结构体类型,结构体数据类型在定义时应遵循以下样例规范。

说明:

?对结构体类型及字段应有简明清晰的注释说明;

?结构体类型本身的命名应以简单明了、见名知义为原则;

?结构体名称使用小写字母命名(〈defined_struct_name>),定义结构体类型时,全部使用大写字母命名();

?对结构体内部字段使用全部的大写字母命名;

?定义类型时,同时定义类型木身和指向该类型的指针类型。

3.3.3 函数

为嵌入式设备编写设备驱动程序,涉及驱动接口函数和自定义函数。在调用函数时,应从相应头文件中复制函数头,然后在参数声明的位置上替换指定参数值。

1. 底层驱动函数

规范:

?底层驱动函数名统一使用前缀“hw—”,表示该函数为硬件底层驱动函数; ?使用“hw_” +构件名+函数操作的命名方式,函数名所有字符均为小写;

?使用函数返回值返冋执行后的单个结果值,通过传入指针参数所指向的存储块返冋多个结果值;

?底层驱动函数中一般不处理结构体数据类型;

?对函数进行详细注释,如样例所示,注释项目包括“函数名称”、“函数返回”、“参数说 明”、“功能概要”等。

2. 自定义函数

在嵌入式编程时,有时需要自定义一些函数,完成指定功能。对于自定义函数,参数数量及类型可根据实际需要设定,函数名应以简明清晰、见名知义为命名原则。

例如,使用显示终端显示操作目录的函数可有如下声明。

规范:

?函数名适当使用前缀,标识驱动使用对象,便于分组管现; ?全部使用小写字母。

3.3.4源码文件夹结构

源码文件夹(Source)中包含所有的工程源代码文件,下设hwComponent、swComponent、

GPIOapplication、MCUabout、FunctionProgram 子文件夹和 main.c、includes.Iin isr.c 三个源码文 件,现在对各文件夹及文件进行说明。

1. 子文件夹

^i件夹下包含底层构件的驱动文件,每个构件对应一个以构件名命令的 子文件夹,如“GPIO”。文件夹命名规范为

?若构件名为常用的缩写方式,则使用大写的缩写字符; ?若构件名中用中.词表示,则单词首字母人写,其余小写;

?多个单词可参考驼峰标识法,即连续的多个单词首字母大写,其余小写,但命名不宜过长;

?构件底层驱动文件命名如前文所述。

若是使用已有的底层驱动构件进行设计,则在该文件夹下的文件夹均应来fl构件库,从而避 免重复开发,提卨开发效率。若是进行底层驱动构件开发,也应将底层驱动构件文件夹保存在此 文件火下,经过测试可靠后可添加到构件库屮,供应用开发使用。

2. 子文件夹

子文件夹下包含构件化设备抽象驱动文件。每个构件对应一个子文件夹。 文件夹命名应规范与“hwComponent”屮说明一致。

由于基于GPIO底层构件的应用较多,故专设T文件火用于保存基于GPIO 构件常用的抽象驱动文件,可包含KB (键盘)、LCD (LCD液晶显示)、LED (LED数码管显示)、 Light (小灯显示)等。构件文件夹及驱动文件命名规范与“hwComponent”中说明一致。

3. 子文件夹

子文件夹下保存了与MCU配置运行相关的构件文件:定义公共类型和公共函数的common.h、common.c文件;定义系统初始化过程执行函数的sysinit.h、sysinit.c函数;定义屮断向量表的vector.h、vector.c文件;定义芯片寄存器映射的MK60N512VMD100.h文件。对于指定的一款芯片,这个文件夹下的文件一经测试无误,一般就不做更改。

4. 子文件夹

包含用户自足义专用功能函数所在的程序文件。例如,若添加软件延时函数delay,则可编 写delay.c文件并添加到此文件夹下。

5. main.c、includes.h 及 isr.c 文件

main.c:用户应用程序主文件。用户的主要程序在该文件下编写。

includes.h:丄程包含的头文件定义。在丄程中添加新的构件文件时,需要在该文件中添加包含。在主函数所在的main.c文件中包含木文件。

isr.c:在该文件中泣义丨:程使用的中断服务函数(Interrupt Service Routine,ISR)。需要注意 的是,在本文件屮仅定义屮断服务函数实现源码,添加屮断时,还需要在MCUabout文件夹下的 vector.h屮添加屮断服务函数的声明并替换屮断向量表中的相应表项。

在图3-4中,显示了 CWvlO.l环境下K60工程的规范工程组织结构。CWvlO.l可维护文件的存储结构与工程逻辑结构一致。

3.4第一个C语言工程:控制小灯闪烁

本书用K60控制发光二极管指示灯的例子开始我们的程序之旅,程序中使用了GPIO构件來 编写指示灯程序。当指示灯两端引脚上有足够高的正向压降时,它就会发光。在木书的工程实例中,灯的正端引脚接K60的普通I/O 口,负端引脚通过电阻接地。当在I/O引脚上输出高或低电平时,指示灯就会亮或暗。

3.4.1 Light构件设计

控制指示灯的亮或暗,通过调用GPIO构件完成。设有两盏灯,分别为运行指示灯1 (Light_Run 1)和运行指示灯 2(Light_Run2)。它们所接 MCU的GPIO端口名为Light_Run_PORT,

具体的端口号和引脚号,只要在Light.h屮给出宏定义即可。

1. Light构件的头文件light.h

2. Light构件的程序文件light.c

3.4.2 Light构件测试工程主程序

在includes.h文件中需要包含GPIO.h,这样在该工程屮就可以调用GPIO构件的接口函数。首先调用gpio_init函数,初始化所需的每一盏指示灯,然后通过gpio_reverse函数将引脚电平取反,就能够在程序运行时,较明显地看到指示灯闪烁的现象。main.c的源代码如下。

3.4.3在CW环境下导入样例工程

本书提供了经过验证的Light工程的源文件,读者可在CW vl0.l环境下导入该工程,观察工程组织及源码、下载程序到芯片、在芯片上运行程序并观察现象。

1. 导入已有工程

首先应确保工程文件夹位于全英文路径下,选择“文件(File)” 一 “导入(Import...)”,出 现导入文件对话框,选择“常规”分组中的“现有项目到工程空间中”,单击“下一步”。在导入项目对话框中,选中工程所在文件夹(如“D:\\cw_workspace_k60\\Light”),在“项目”面板中会出现开发环境自动检测到的项目,勾选之后单击“完成”。

也可在Windows系统环境中将工程文件夹用鼠标拖至CW vl0.l的主面板上,CW vl0.l将自动导入工程到当前工作空间中并打开。

2. 设定为Flash工程

打开工程后默认情况下,当前工程为运行在RAM中的程序,断电之后程序未在芯片内部保存。应设定打开工程创建后生成可独立运行在目标板的程序,选择“项目”一“构建配置”一“设置为活跃”,选择“MK60N512VMD100_INTERNAL_FLASH”。亦可在工程浏览器屮的跟文件夹下右键单击,在弹出菜单中选择“构建配置”一“设置为活跃”,选择“MK60N512VMD100_INTERNAL FLASH”。

3. 编译创建机器码文件

选择“项目”一“清理...”,弹出项目清理对话框,勾选当前项目,并勾选“立即开始构建”,单击“确定”按钮,即清除最近一次工程创建的机器码文件,并生成新的机器码文件。

至此,工程被编译,并创建好在芯片上可执行的机器码文件,存放为工程中/MK60 N512VMD100_INTERNAL_FLASH/文件夹下的“S19”文件(“afx”、“hex”文件亦是可写入的机器码文件)。

4. 下载程序到芯片

单击工具栏中的“下载”工具图标,选中“把文件烧录到芯片”,在编程下载对话框中选择运行配置为 “K60_MK60N512VMD100_INTERNEL_FLASH_PnE OSJTAG”;选择 Flash 配置为“MK60N512VMD100”,其他选项不做修改。

以Light工程为例,选择“Workspace...”,从工程组织结构下选择“MK60N512 VMD100_INTERNEL—FLASH” 分组下 “Source” 的 “Light.afx”(或 Light.afx.sl9、Light.afx. hex 任一亦可)文件,载入机器码文件。

选择“擦除并编程”,将机器码文件下载到MCU中。

使用CWvl0.l的详细过程参见..\\WYH-K60-BookA-CD(V1.0)\\整体资料\\ CWvl0.l简明操作vl.l.pdf”文档说明。

3.5理解第一个C工程

本节以Light工程为例,讲述CW环境下K60工程的组织及执行过程。

嵌入式系统工程包含若干文件,如程序文件、头文件、与编译调试相关的信息文件、工程说明文件以及工程目标代码文件等。工程文件的合理组织对一个嵌入式系统工程尤为重要,它不但会提高项目的开发效率,同时也会降低项目的维护难度。

嵌入式系统工程的文件组织方法以硬件对象为核心来展开,系统中每个对象应包含相关的头文件、程序文件及说明文件等。以硬件对象的方式来组织文件,会使得工程结构清晰,错误定位方便,后期维护容易,这也体现了嵌入式系统软件工程的基本思想。

3.5.1 CW开发环境下工程文件组织框架

图3-4 CW工程文件组织的树型结构

图3-4给出了用1/0 口控制小灯闪烁工程的树形结构模板,该模板是苏州大学飞思卡尔嵌入式研发中心专为K60开发设计的工程模板。与CW vl0.l提供的Demo工程模板相比,该模板简洁易懂,去掉了一些初学者不易理解或不必要的文件,同时应用底层驱动构件化的思想改进了程序结构,目的是引导读者进行规范的文件组织与编程。

图3-4展示的树形结构分为“工程配置文件” 、“输出文件夹” 、“源程序文件夹” 3 大部分。其中

“工程配置文夹”包含与调试相关的配置文件、链接文件以及启动代码文件,对于一般?

的开发过程不需要改动;

“输出文件夹”中保存的是源码工程经过编译链接之后生成的文件,其中S19文件或?

afx 文件为生成的程序机器码文件,可下载到目标板上运行;

“源程序文件夹”?包含通用函数、驱动构件文件、主程序文件以及屮断服务例程文件等,这些文件是工程开发人员进行编程的主要对象。

3.5.2 文件说明

1.中断向量及服务例程文件

位于文件夹下的vector.h和vector.c保存了中断向量表的信息。Vector.c

文件中定义了中断向量表的存储结构,无须用户更改。vectonh中包含了对中断向量表

中断服务例程的声明及映射关系。

若开发人员需要添加一个自定义的中断服务例程。可先在isr.c (位于文件夹下)编写中断服务例程函数的实现代码,再在vector.h文件中声明自定义中断服务例程函数,并修改对应中断向量表项的宏定义为中断服务例程函数名。关于中断编程将在第4章阐述。

需要注意的是,屮断向量表的前两项分别为指向堆栈初始化指针和上电启动函数的指针。在系统上电后,首先查询屮断向量表的第一个表项(_BOOT_STACK_ADDRESS)并设定为堆栈指针,然后从第二个表项中取出上电启动函数(_startup_)的指针,执行上电启动函数,开始启动过程。

2.启动文件

位于文件夹下的 crt0.s 和 start.c 是 CW vl0.l 环境下 K60 工程启动执行文件。

crt0.s文件采用ARM汇编语言编写,在文件屮定义了上电启动执行过程的_startup_函数,该函数完成:复位所有的通用寄存器(r0?r12)、关闭总屮断,调用start函数。

在start.c文件中定义了 start函数,该函数依次完成的功能是:关闭看门狗、复制中断向量表到RAM中、系统初始化、调用主函数main。函数具体如下。

(1)关闭看门狗。看门狗的使用在嵌入式设计中非常重要,它可以在芯片代码跑飞或死机的情况下复位芯片。嵌入式产品往往24小时全天候运行,为了保证程序一直正常运行,必须在产品正式发布时启动看门狗功能。而在程序调试阶段,为了保证程序执行流程,往往先关闭看门狗中断。若在应用程序中启动看门狗功能,就必须在看门狗计时器溢出之前“喂狗”将看门狗计时器刷新使其重新开始计时,否则将引起看门狗溢出复位。关于看门狗的编程可参见第14章。

(2)复制中断向量表到RAM中。代码在RAM中执行的效率比在Flash中执行高,通常ARM 芯片都会将屮断向量表复制到RAM屮。未初始化的数据段(BSS)应该清0。其屮涉及链接文件中定义的变量,包括Flash中断向量表地址_VECTOR_TABLE、RAM中断向量表地址_VECTOR_RAM。这些变量定义在链接文件(512KB_flash.lcf)中。

(3)系统初始化设置。在系统初始化函数sysinit在文件夹下的system.c 文件中实现,主要功能是配置系统时钟。

芯片主时钟是利用MCG模块中的PLL模块,通过将电路板上的50 MHz有源晶振信号处理得到的。本书样例工程使用的内核时钟是96 MHz,相应的总线时钟频率为内核时钟频率

的1/2 (48 MHz), FlexBus时钟频率为内核时钟频率的1/2 (48 MHz), Flash时钟频率为内核时钟频率的1/2 (24 MHz)。另外,sysinit.c文件中定义了内核时钟频率变量core_clk_khz和总线时钟频率变量periph_clk_khz,对于需要使用频率参数的构件,可在构件源程序文件屮声明引用。例如,在串口驱动构件的源程序文件hw_uart..c中有

在工程中提供了 50 MHz、96 MHz、100 MHz和48 MHz内核时钟可供选配,由sysinit.h文件(位于文件夹下)屮的宏定义设定。本书将在第14章屮详细阐述K60的时钟系统。

(4)调用main函数。调用main.c文件中定义的主函数main。

用户一般在main函数中设计程序代码,系统从上电开始执行一系列的初始化配置操作之后,调用main函数,开始执行用户程序。

上述两个文件的内容因具体的目标系统不同而有所区别。但使用工程模板创建新工程时,在一般情况下不需要修改。

3. 包含头文件与主程序文件

包含头文件(includes.h)包含主文件(main.c)用到的头文件、外部函数引用、常量、全局变量定义以及外部变量引用,是与主程序文件mainx对应的头文件。main.c文件是工程任务的核心文件,main函数即在该文件中实现。在main函数中包含了一个主循环,对具体事务过程的操作几乎都是添加在该主循环中。

4. 芯片相关文件

位于文件夹下的MK60N512VMD100.h文件定义了 K60N512芯片专用的寄存器映射。需要注意的是,针对某一款芯片进行开发时,对应有专用的寄存器映射文件,这一般由芯片开发人员进行设计,应用开发人员不必修改。 链接文件

1 ) LCF文件

链接命令文件(Linker Command File, LCF)屮指定了工程编译过程屮各数据段在存储空间中的位置分配。下面以样例工程中使用的512KB_flash.lcf文件为例进行说明。

在MEMORY段定义中

?定义程序存储区(ROM)起始地址为0x00000000,长度为0x00080000 (512KB);

?定义RAM存储区起始地址为0xlFFF0410,长度为0x0001FBF0 (128 KB-0x410B,其中保留0x410长度的存储空间用于在上电启动后存放复制的中断向量表)。

在SECTIONS段定义中,定义了若干数据段在存储空间中的分配。 (1)中断向量表在RAM中的起始地址为0xlFFF0000。

(2)存放在程序存储区中的.text段,存放中断向量表(%vectortable))、程序操作码(*(.text))、常量(*(.rodata))等内容。

(3)存放在RAM 屮的.data_bss 段,存放:*(.data)、*(.sdata)、*(.relocate_code)、*(.relocate_const)、 %relocate_data)、*(.test)、*(.sbss)、*(SCOMMON)、*(.bss)、*(COMMON)、堆空间(_HEAP)、 栈空间(__SP)等数据段及_BOOT_STACK_ADDRESS地址(上电启动初始化堆栈信息首地址)。分配各数据段的内容将在编译产生的xMAP文件中指定,xMAP文件中定义了工程的实体项目(函数、变量、常量等)在存储空间中的分配情况。

LCF文件中的\可看做一个地址变量,每当分配数据段后,表示的地址将跳过分配数据段的地址区间递增,指向下一段可能分配的数据段的起始。

在LCF文件中未指定各数据段的固定长度(除固定分配的堆空间4 KB、栈空间1 KB),各数据段空间根据工程代码的实际编译结果动态分配。但观察LCF文件屮内容可知,在此分配的各数据段是连续存放的。

2 ) xMAP文件

生成工程后链接函数、变量、常量等实体的信息可从xMAP文件中查看。例如

表示main函数所在的地址为0000090C,长度为0000003A,该函数来自于main.obj模块。

长度定义为0的实体,不是映像文件的内容,由于这些函数不在应用工程屮使用,因此,在编译的时候被移除(链接器执行了死区清理将它们优化掉了)。例如

“@xxx”表示的内容是由编译器内部产生的,编译器在优化代码时可能产生这样的信息,可不考虑它们。例如

在文件块结束的部分还出现了存储器映射信息。

“v_addr” 表示 “virtual address” 虚拟地址,“p_addr” 表示 “physical address” 实际地址。通常p一addr与v_addr相同,只有在启动时从ROM (Flash)中复制数据(或代码)到RAM,才会出现不同的情况,若要进行深入分析,还需要查看LCF文件。

需要注意的是,在xMAP文件中指定的地址在一定程度上是动态分配的(由编译器决定), 因此,若工程有任何修改,这些地址都可能发生变动。

分析样例工程Light的xMAP文件。

上文中介绍上电启动执行函数的存储地址如下所示。

通过工程的LCF和xMAP文件可以了解到程序及数据在芯片内部的存储情况。另外,当使用Flash在线编程功能时,还为选择用户可用存储资源提供了参考。

关于LCF文件及Kinetis编译工具的详细说明,参见文档“..\\WYH-K60-BookA-CD (V1.0)\\ 分章阅读材料 \\Ch03-DOC\\MCU_Kinetis_Compiler.pdf”。

6.机器码文件

在编译链接过程中,CWvl0.l会产生机器码文件(.afx),这是可写入Flash中的文件,但在CW vl0.l环境中无法查看。若在项目配置时选择编译链接时产生.S19文件(样例工程已配置生成S19文件),就可生成以S记录格式表示的.afx.S19文件,可在CWvl0.l环境中查看该文件内容。S记录格式是飞思卡尔公司的十六进制目标代码文件,它将目标程序和数据以ASCII码格式表示,可直接显示和打印。

S格式文件中的每一行称为一个S记录(如表3-2所示),目标文件由若干行S记录构成,每行S记录可以用CR/LF/NUL结尾。一行S记录由下列五部分组成。每字节数据被编码成2个十六进制字符,第一个字符代表数据的高四位,第二个字符代表数据的低4位。

1 )记录类型

S19文件中的记录有8种类型:S0、SI、S2、S3、S5、S7、S8、S9,这是为了满足不同的编码、解码及传送方式的需求,用2字符表示。

表3-2 S记录格式

S0—S格式文件的第一个记录,表示文件名(含路径),存储地址部分没有使用,以0000占位。此行(S0开头的记录)表示程序的开始,不写入存储器中。

S1—该类型记录包含代码/数据内容以及2个字节的记录内容在存储器中的首地址。 S2—该类型记录包含要写到Flash的扩展地址处的代码/数据内容以及3个字节的记录内容在存储器屮的首地址。

S3—该类型记录包含要写到Flash的扩展地址处的代码/数据内容以及4个字节的记录内容在存储器中的首地址。

S5——标记本文件的Sl、S2、S3记录的个数,此记录不是一个S文件所必须的。

S7—对应S3记录的结束记录,4字节地址,对应8个字符,表示程序的开始执行地址,代码/数据部分未被使用。此行表示程序的结朿,无须下载到MCU。

S8—对应S2记录的结朿记录,3字节地址,对应6个字符,表示程序的开始执行地址,代码/数据部分未被使用。此行表示程序的结束,无须下载到MCU。

S9—对应S1记录的结束记录,2字节地址,对应4个字符,表示程序的开始执行地址, 代码/数据部分未被使用。此行表示程序的结束,无须下载到MCU。

每个S记录块都使用唯一的终止记录。

2)记录长度

表示该行记录中内容的长度,以字节为单位,包含记录起始地址字段、记录代码/数据字段和记录校验和字段。1字节表示。

3)记录首地址

地址字段表示该条记录中的有效内容连续存放在存储器中的首地址,根据记录类型的不同,该字段的长度可以是2字节(SI、S9)、3字节(S2、S8)或4字节(S3、S7)。

4)记录代码/数据

记录代码/数据字段表示的就是实际的目标代码或数据,这部分内容将被下载到目标芯片的存 储器中并运行。该段内容的数据长度可由“记录长度”字段的值减去“记录首地址”字段的长度与“记录校验和”字段的长度算得。

5)记录校验和

记录校验和字段长度为1字节,是“记录长度”、“记录首地址”、“记录代码/数据”三个字段 所有字节之和的反码的低8位,用于校验记录有效性。计算方法为校验和=0xFF-(记录长度+存储地址+代码/数据)

注意:位校验和不是字符的校验和,而是实际二进制数的校验和。 以Light工程中的Light.afx.S19文件为例。

第1行为S0记录,其中,“S0”是记录类型说明;“03”是记录长度字段内容,表示本条记录的长度为3字节;“0000”是地址字段内容,在首记录中以“0000”填充;“FC”是记录校验字段内容,计算过程为:(0xFF-0x03-0x00-0x00) =0xFC

第2行为S3记录,其屮,“S3”是记录类型说明;“FD”是记录长度字段内容,表示本条记录的长度为0xFD (十进制253 )字节;“ 00000000 ”是本记录的存储的首地址;“9018FF1FC904000041050000...”为存放在存储区中的机器操作码;记录末端的“07”是本条记录的校验和字段内容。该行记录表示将“9018FF1FC904000041050000...”等机器操作码存放在以 “00000000”为首地址的存储区中。需要注意的是,S19文件中记录的数据内容是以“小端”的方式组织的,这与MCU内核对数据的存储方式有关。在这种格式中,字数据的低字节存储在低地址中,而字数据的高字节则存放在高地址中。例如,S3记录中数据字段的一个单元的数据内容为“3D 05 00 00”,实际表示的数据内容为“00 00 05 3D”。对于S记录的地址字段,由于本身并不写入MCU,所以表示的内容无须进行转换。

最后1行为S7记录,其屮,“S7”是记录类型说明;“05”是记录长度字段内容,表示本条记录的计数长度为5字节;“000004C8”对应芯片启动函数的地址;“2E”为本条记录的校验和字段内容。

在CW vl0.l环境下打开工程中的某个源程序文件,在源程序窗口单击鼠标右键,从弹出的菜单中选取Disassemble即可查看该文件的列表文件。在列表文件中可以得到源程序的

反汇编语句及对应的操作码,使用这些操作码就可以在S19文件中对执行语句进行定位。

综合分析工程的xMAP文件、LCF文件、S19文件和反汇编文件,可以将程序执行过程与对生成的机器码定位。

例1:分析Light工程生成的S19文件可知,写入Flash起始部分的是中断向量表,中断向量表的第一条记录应为初始化堆栈指针,对应Light.afx.S19文件屮第一条记录屮解析的数据“9018FF1F”(对应地址为1F FF 18 90,此地址映射到RAM存储区的最前端),屮断向量表屮的第二条记录应为系统上电启动函数的地址,对应记录为“C9040000”(对应地址为00 00 04 C9, 此地址映射到Flash存储区,可在S19文件中找到对应记录)。

例2: LCF文件中关于main函数的记录为

说明:main函数的入口地址为0000090C,长度为0000003A。查看S19文件中的相关记录即 可找到main函数的机器码如下所示。

7. 反汇编信息文件

将工程编译后,可查看源程序文件的反汇编代码,方法为:在源代码文件的编辑界面单击右键,在弹出菜单中选择“Disassemble”,即可在编辑界面中看到反汇编文件的内容。反汇编文件可列出C语言源程序对应的汇编语言代码和机器码信息,与S19文件对应。

3.5.3芯片上电启动执行过程

芯片上电启动后,首先查询保存在Flash存储区首端的屮断向量表,取出第一个表项的内容 设定为堆栈初始化指针,取出第二个表项的内容作为启动函数(_startup_,位于crt0.s)的入口地址。在_startup_函数中,将通用寄存器清0,之后调用start函数(位于start.c)。在start函数中,执行关闭看门狗(wdog_disable)、通用启动例程(common_startup)、系统初始化(sysinit)、调用主函数(main)。其中,在common_startup函数中复制中断向量表到RAM中,在sysinit函数中主要完成芯片时钟系统的配置。main函数(位于main.c)为开发人员自定义的执行程序函数。

3.6在CW环境下创建一个新的工程

本书中提供了所有基本模块的驱动构件样例工程,在样例工程中包含了驱动构件源程序和基本用法,读者可以以样例程序为模板建立新的工程。釆用修改现有工程的方式新建工程比较简单,无需配置,且不容易出错。工程的文件夹名、工程名和生成机器码文件的文件名均可在建立新工程之后自定义修改,本节将对导入样例工程后可进行的操作进行简要说明。

1. 修改工程名

在CWvl0.l左侧的工程导航器面板中,鼠标右键单击工程的根文件夹,选择“重命名”,在“重命名资源”对话框中输出新的工程名,单击“确认”按钮即可。选中资源浏览器中工程的根文件夹后,按下“F2”键,亦可激活“重命名资源”对话框。

2. 修改生成机器码文件的文件名

右击工程的根文件夹,选择“属性”,出现工程属性对话框,选择“C/C++构建”一“设置”,选择“构建工件”标签,在“工件名称” 一栏中设定生成机器码文件的文件名。

3.编辑工程源代码

添加底层构件时,应从构件库中将指定驱动构件程序文件夹复制到“hwComponent”文件夹 下,然后在集成开发环境下添加对新构件的路径引用;对于抽象构件,应复制到“swComponent” 文件夹K。由于基于GPIO的抽象构件应用较多,故专设GPIOapplication文件夹存放,例如指示灯构件Light、键盘构件KB等。CWvl0.l自动维护文件系统的文件夹结构与集成开发环境下工程文件夹结构保持一致。

添加对驱动构件程序的引用时,除在“Source”文件夹下的“includes.h”下添加构件头文件外,还需要在对构件有引用关系的文件或文件夹中手动添加对新加入文件的引用路径。在有引用关系的文件或文件夹上右击,选择“属性” 一“C/C++常规”一“路径和符号”,在“包含目录”中添加引用文件路径。

4. 跟踪宏、函数定义

若要查看工程中对已定义宏和函数的调用,可将鼠标移至对象上,就会弹出对象的简要定义信息。若要查看完整的定义源码,可将编辑界面的光标移至跟踪对象上,使用键盘上的“F3”键跟踪对象的定义,或者单击鼠标右键,在弹出菜单中选择“Open Declaretion\即可将当前编辑区域显示内容切换到对象定义的源码部分。

关于CWvl0.l环境的详细说明,读者可自行查看CWvl0.l环境的帮助信息。CWvl0.l环境提供了非常丰富的帮助信息,若能够有效地利用这个资源,读者将更好地理解嵌入式系统的开发过程。

3.7本章小结

本章作为全书的重点和难点之一,给出了Kinetis系列微控制器的C语言工程编程框架,对第一个C语言入门工程进行了较为详尽的阐述。透彻理解工程的组织原则、组织方式及执行过程,对后续的学习将有很大的铺蛰作用。

(1)给出了封装底层驱动构件的基本方法:将硬件模块基本的功能抽象出来,遵循一定的代码设计规范,合理设计底层驱动构件函数接口。以GPIO底层构件的驱动设计为例,抽象山GPIO模块的引脚初始化、读端口寄存器位、写端口寄存器位、将端口寄存器位状态反转4个基本驱动函数。在编写GPIO底层驱动程序的过程中,使用了编写驱动程序时常用的寄存器位操作宏函数:BSET、BCLR和BGET,可以确保在对寄存器的某一位进行读写时,不影响其他位。

(2)遵循统一的嵌入式设计编码规范是提高可移植性、开发效率、软件稳定性和可维护性的 有力保证。驱动程序的调用接口应遵循统一规范,便于工程在不同平台间移植。编码时,要在程序中合理地添加注释,提高程序的可读性,且便于维护。对文件、变量、函数、

数据结构等命名要遵循统一的规范,特别地,对于底层驱动函数的命名,要添加“hw_<构件名>”前缀,如“hw_gpio_init”,这样的设计是考虑到将底层驱动程序加入操作系统的驱动体系时,与设备的抽象驱动程序相区別。

(3)以“第一个C语言工程:控制小灯闪烁”为例,讲述了设计底层驱动构件程序的方法。在样例工程中,设计了基于GPIO的抽象构件“Light”,并针对该构件设计了测试样例工程。Light构件封装了指示灯初始化(light_init)、设定指示灯亮/灭状态(light_control)、指示灯状态切换(light_change)三个接口函数供开发人员调用。在light构件的头文件light.h屮还定义了指示灯的端口名和引脚名,若需要变更指示灯控制引脚,仅需修改它们在头文件中的定义,可不改动主函数中的内容,提高了程序的可移植性。读者应尽快熟悉基于样例工程进行开发的过程,包括导入工程、编译链接生成机器码文件、下载机器码文件到芯片中等步骤。

(4)透彻理解工程框架对实际开发具有重要作用。工程框架中,应着重理解“源程序文件夹()”下子文件夹及文件的组织和作用。一般情况下,不宜变动工程框架。编译过程中产生的文件可供分析程序使用。与中断向量相关的有三个文件为vector.h、vector.c和isr.c,分别包含中断向量表项的宏定义、中断向量表数据结构和中断服务例程。定义芯片启动过程的两个启动文件为crt0.s和static.c,在这两个文件屮定义了芯片上电启动的执行程序。开发人员编写源代码 最常用的两个文件includes.h和main.c,分别包含了应用程序屮使用到的所有包含头文件和main 函数。芯片专用的寄存器定义一般是由芯片研发单位提供,进行应用开发时不必变动。分析链接文件(LCF、xMap)和机器码文件(S19)可以获得程序在芯片中的存储信息,并了解芯片内部的工作过程。利用反汇编分析程序,也是学习嵌入式系统开发的基本功之一。对于这些基本知识, 若读者能够充分理解,将对今后的嵌入式开发学习过程有很大的帮助。

(5)简要总结样例工程的执行过程如下:芯片上电启动后,首先查询存放于Flash起始位置的中断向量表中第一个表项,执行对堆栈进行初始化过程。查询第二个表项,执行crt0.s文件中定义的_startup函数,初始化寄存器、并关中断后调用start.c文件中定义的start函数。在start函数中进行关闭看门狗、复制中断向量表到RAM、系统初始化后,开始执行主函数main。

网上光盘的“..\\WYH-K60-BookA-CD(V1.0)\\分章阅读资料\\Ch03-DOC”文件夹下给出了本章 阅读材料,见表3-3。

表3-3第3章网上光盘阅读材料

习 题

1. 在第一个样例程序的工程组织图中,哪些文件是由用户编写的?哪些是由开发环境编译链接产生的?

2. 简述第一个样例程序的执行过程。

3. 用记事本打开样例工程的机器码文件Light.afx.sl9,解释S记录的含义,并在其中找到main函数的入口地址和全部main函数的操作码。

4. 修改样例程序,循环点亮-熄灭SD-K60核心板上的另外两盏小灯。

5. 参考Light构件设计一个“Button”构件,实现对拨码开关功能的封装,在编码过程

中遵循嵌入式设计编码基木规范。

本文来源:https://www.bwwdw.com/article/8rrp.html

Top