1.3 创建工程和执行程序

你编写和执行的第一个C语言程序将告诉STM32微控制器,让它发送一条消息给PC机或笔记本电脑。

任务四 你的第一个工程

双击Keil u Vision3的图标,启动Keil uVision3程序,你会得到图1.10所示的Keil u Vision3的IDE主界面。IDE表示Integrated Development Environment,即集成开发环境。Keil提供了包括C编译器、宏汇编、连接器、库管理及一个功能强大的仿真调试器在内的完整开发方案,通过一个集成开发环境(u Visoin)将这些部分组合在一起,其软件开发逻辑结构如图1.11所示。掌握这一软件的使用,对于进行单片机或ARM系统开发者来说是十分必要的,如果你使用C语言编程,那么Keil是你的不二之选,即使不使用C语言而仅用汇编语言编程,其方便易用的集成环境、强大的软件仿真调试工具也会令你事半功倍。

图1.10 Keil uVision3的IDE主界面

图1.11 Keil软件开发逻辑结构图

集成开发环境

早期的程序设计各个阶段都要用不同的软件来进行处理,如先用字处理软件编辑源程序,然后用链接程序进行函数、模块连接,再用编译程序进行编译,开发者必须在几种软件间来回切换操作。现在的编程开发软件将编辑、编译、调试等功能集成在一个桌面环境中,这样就大大方便了用户。这就是集成开发环境(简称IDE)。IDE是一个用于程序开发的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面工具等一体化的开发软件服务套件。所有具备这一特性的软件或者软件套件(组)都可以叫做集成开发环境。比如,微软的Visual Studio.Net系列,可以称为C++、VB、C#等语言的集成开发环境。

通过用Project菜单中的New Project命令建立项目文件(工程文件),过程如下:

(1)创建HelloRobot文件夹,将提供的library文件夹,stm32f10x_heads.h文件和HelloRobot.h文件复制到此目录下。library文件夹里面的文件是STM32库文件。

(2)单击Project,会出现图1.12所示的画面,然后选择New u Vision Project,弹出Create New Project对话框,找到刚才建立好的HelloRobot目录,如图1.13所示。

图1.12 Keil uVision3工程菜单画面

图1.13 Create New Project对话框

(3)双击HelloRobot文件夹,在文件名中输入工程文件名:HelloRobot(可不用加后缀名),保存在此目录下,如图1.14所示。之后单击“保存”出现图1.15所示的窗口。

图1.14 创建HelloRobot工程

图1.15 Select Device for Target ‘Target 1’ 对话框

(4)这里要求选择芯片的类型,双击STMicroelectronics,在弹出的下拉菜单中选择STM32F103xx(与教学开发板一致),就会出现相关介绍信息。例如选择STM32F103VB,如图1.16所示。看看选择不同的STM32F103xx单片机,所显示的资源有哪些不同。

图1.16 CPU型号选择窗口

(5)按OK确定,出现图1.17所示窗口。询问是否加载启动代码,在这里选择“否”,不加载(后面将会手工装载启动代码:cortexm3_macro.s,stm32f10x_vector.s,而不用系统提供的默认启动代码)。选择“否”后将出现图1.18所示窗口,此时项目文件,即工程文件就创建好了。

图1.17 是否加载启动代码提示窗口

图1.18 目标工程窗口

启动代码

启动代码(Bootloader)是嵌入式系统启动时常见的一小段代码,类似于启动计算机时的BIOS,一般用于完成CPU的初始化工作和自检。其他常见的启动代码比如ARM9嵌入式系统中的UBoot或Vivi等。同一型号的CPU启动代码会随着开发板的设计不同而略有不同。

STM32这两个启动代码主要完成处理器的初始化工作。其中,启动文件cortexm3_macro.s的作用:定义Cortex-M3宏指令操作,这些宏指令操作可供用户在C中调用。如在C文件中有必要使用汇编指令和这些宏指令做预处理时,只要直接在C代码中使用EXPORT后面的宏指令操作就可以了(类似于在C中嵌入汇编的操作);启动文件stm32f10x_vector.s的作用:初始化堆栈,定义程序启动地址、中断向量表和中断服务程序入口地址,以及系统复位启动时,从启动代码跳转到用户main函数入口地址。

查看HelloRobot目录,你会发现HelloRobot.uv2工程文件。另外,opt文件是关于工程开发环境的参数配置和选项设置文件(Option File)。plg文件是编译日志文件(Compile Log File),存放编译器的编译结果,编译时采用的命令参数,已经编译后得到的错误和警告信息。

项目文件(工程文件)

在当今的应用软件开发中,一个软件系统是由工程文件组成,工程文件包含若干个程序文件、头文件、甚至库文件。类似一本书,有目录和各个章节;或者像一个公司,有好多部门。uv2文件是51、STM32等单片机或者ARM的Keil项目文件(工程文件),打开它就打开了这个工程,即与应用程序相关的全部文件和相应的设置。它包括的文件有头文件、源文件、汇编文件、库文件、配置文件等。这些文件的有关信息就保存在称为“工程”的文件中,每次保存工程时,这些信息都会被更新。在Keil中使用工程文件来管理构成应用程序的所有文件,而且编译生成的可执行文件是与项目文件(工程文件)同名。

任务五 你的第一个程序

项目文件创建后,这时只有一个框架,紧接着需要向项目文件中添加程序文件内容。Keil uVision支持C语言程序。可以是已经建立好的程序文件,也可以是新建的程序文件。如果是建立好了的程序文件,则直接用后面的方法添加;如果是新建立的程序文件,则先将程序文件.c存盘后再添加。

首先,先添加启动代码(汇编文件):cortexm3_macro.s,stm32f10x_vector.s。右键单击“Source Group 1”,单击“Add Files to Group‘Source Group 1’”,如图1.19所示。

图1.19 添加文件

在弹出的对话框中选择文件类型,然后选中两个启动代码,单击“Add”按钮添加进工程文件中,再单击“Close”关闭此对话框,如图1.20所示。

图1.20 添加启动代码

文件添加到项目文件中去后,这时“Source Group 1”的前面将出现一个“+”号。单击“+”,展开“Source Group 1”目录,如图1.21所示。

图1.21 加入启动文件后的工程

这时可以添加已经建立好的程序文件,如图1.22所示。如果是新建立的程序文件,则先将程序文件.c存盘后再添加。

图1.22 加入启动文件后的工程

单击按钮(或通过“File→New”操作)为该项目新建一个C语言程序文件。将该例程键入到Keil uVision IDE的编辑器中,并以文件名HelloRobot.c保存。

例程:HelloRobot.c

    #include "stm32f10x_heads.h"
    #include "HelloRobot.h"
    int main(void)
    {
      BSP_Init();                // 开发板初始化
      USART_Configuration();     // 串口1(USART1)初始化
      printf("Hello Robot!\n");
      while (1);
    }

将文件保存在项目文件夹HelloRobot中,在文件类型中填写.c(这里.c为文件扩展名,表示此文件类型为C语言源文件),如图1.23所示。

图1.23 C语言源文件保存对话框

下一步就是添加该文件到目标工程项目了,其具体添加过程如下。

(1)单击“+”,展开“Source Group 1”目录,然后右键单击“Source Group 1”,在出现的菜单下选择“Add File To Group ‘Source Group 1’”,如图1.24所示。出现Add Files to Group Source‘Group1’对话框。在该对话框中选择需要添加的程序文件:HelloRobot.c,单击Add按钮,把所选文件添加到项目文件中。一次可添加多个文件。

图1.24 添加文件

(2)程序文件添加到项目文件中去后,如图1.25所示(注意:图中显示的文件名是刚才输入的文件名)。

图1.25 添加C语言文件到目标工程中

双击源文件即可显示源文件的编辑界面,如图1.26所示。

图1.26 源文件的编辑界面

下面来产生下载需要的可执行文件。图1.27表示编译的几种方式,图1.27(a)的“Translate current file”表示仅编译当前源文件;图1.27(b)的“Build target”表示编译整个工程文件,编译时仅编译修改了的或新的源文件;图1.27(c)的“Rebuild all target files”表示重新编译整个工程文件,工程中的文件不管是"否修改,编译时都将重新编译。

图1.27 编译工程文件的几种方式

一般地,我们单击Keil uVision IDE快捷工具栏中的,编译整个工程文件,如图1.28所示。Keil的C编译器根据要生成的目标文件类型对目标工程项目中的C语言源文件进行编译。编译过程中,可以观察到源文件中有没有错误产生,如果没有错误产生,在IDE主窗口的下面出现“0 Error(s),0 Warning(s)”提示信息,表明已成功生成了可执行文件,并存储在HolloRobot目录中。

图1.28 编译过程的输出信息

查看HelloRobot目录,你会发现生成了HelloRobot.axf文件。ax(f arm excute file)是ARM芯片使用的文件格式,它包含bin代码外,还包括了调试信息。与axf文件相似,单片机系统开发经常也会用到hex文件,hex文件包括地址信息,可直接用于烧写或下载。

如要产生可执行的.hex文件,需要对目标工程“Target 1”进行编译设置,右键单击“Target 1”,选择“Option for target ‘Target 1’”。单击“Output”,选择其中的“Create HEX File”,如图1.29所示,单击OK按钮确定,关闭设置窗口。

图1.29 设置目标工程的编译输出文件类型

再次单击Keil uVision IDE快捷工具栏中的,Keil的C编译器开始根据要生成的目标文件类型对目标工程项目中的C语言源文件进行编译。这时HelloRobot目录中,你会发现生成了HelloRoBot.hex文件。

任务六 下载可执行文件到教学开发板

将ULink下载工具的一端通过USB线连接到电脑USB口上,另一端连接到教学开发板上的JTAG口上。接好后,按图1.30(a)~(c)所示过程依次配置,安装ULink驱动。

图1.30 ULink下载工具驱动的安装

将ULink和教学开发板连接好,打开教学开发板电源开关。单击Project下的Options for Target(工程属性),弹出“Options for Target”对话框。或者单击“Flash”菜单下的“Configure Flash Tools”,按照如图1.31(a)~(f)所示进行配置。

图1.31 ULink下载配置

图1.31 ULink下载配置(续)

图1.31 ULink下载配置(续)

这样ULink下载工具就配置好了。如图1.32所示,单击图标(或通过“Flash→Download”操作),程序就开始下载了,下载结束如图1.33所示。

图1.32 程序下载

图1.33 程序下载结束

下载时,先擦除上次Flash存储器中的程序,再将刚才编译好的程序下载,最后经校验无误后,下载结束。此时,按教学开发板的Reset复位键,下载的程序开始运行。

Flash存储器

Flash是存储芯片的一种,通过特定的程序可以修改里面的数据。Flash存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据,同时可以快速读取数据(NVRAM的优势)。U盘和MP3里用的就是这种存储器。嵌入式系统以前一直使用ROM(EPROM)作为它们的存储设备,20世纪90年代中期开始,Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,用做存储程序代码或者Bootloader及操作系统,现在则直接当U盘使用。

目前Flash主要有两种:NOR Flash和NAND Flash。NOR Flash的读取和我们常见的SDRAM的读取是一样的,用户可以直接运行装载在NOR Flash里面的代码,这样可以减少SRAM的容量,从而节约了成本。NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512字节,采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码,因此使用NAND Flash的嵌入式系统开发板除了使用NAND Flash以外,还做上了一块小的NOR Flash来运行启动代码。

一般小容量的用NOR Flash,因为其读取速度快,多用来存储操作系统等重要信息,而大容量的用NAND Flash,最常见的NAND Flash应用是嵌入式系统采用的DOC(Disk On Chip)和我们通常用的“闪盘”,可以在线擦除。目前市面上的Flash主要来自Intel,AMD,Fujitsu和Mxic,而生产NAND Flash的主要厂家有Samsung和Toshiba及Hynix。

SRAM存储器

SRAM是英文Static RAM的缩写,它是一种具有静止存取功能的内存,不需要刷新电路即能保存它内部存储的数据。而DRAM(Dynamic Random Access Memory)每隔一段时间,要刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,访问速度快。但是SRAM也有它的缺点,即价格高、集成度较低、功耗较大,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积。SRAM只用来存储变量数据和提供给堆栈来使用。在嵌入式系统中,Flash的容量一般要大于SRAM的容量,如STM32F103VB芯片,其Flash容量为128K,SRAM容量为20K;STM32F103VE芯片,其Flash容量为512K,SRAM容量为64K。其他,8/16位单片机、ARM9或ARM11等嵌入式系统也是如此。

当生成的可执行文件大于Flash存储空间时,则不能被下载到Flash中。如果出现类似下面的错误,则表示生成的可执行文件大于Flash存储空间:

    Error: L6406W: No space in execution regions with .ANY selector matching Section .text(***.o).

这时,可以对程序进行优化,例如,减小缓存尺寸,减少全局变量,少定义尺寸大的数组而多用指针等方法。此外,合理调整RealView MDK的编译和链接配置,也可以减小生成的可执行文件大小。比如,在链接脚本中指定代码的存储布局,将代码段、只读数据段、可读写数据段分别存放,以减小生成的可执行文件大小。常有下面3种解决方法。

(1)使用微库:在图1.31(a)所示的对话框中选中“USE MicroLib”,以使代码减少。

(2)修改链接脚本:在“Options For Target”对话框的“Linker”页面,将Use Memory Layout from Target Dialog前面的复选框勾上,如图1.34所示。然后在“Target”页面(图1.31(a)所示的对话框)中修改存储空间中只读部分(Read/Only Memory Areas)和可读写部分(Read/Write Memory Areas)的起始和大小,一般来说加大只读部分大小(该部分存放程序中的指令),而减小可读写部分的大小(该部分存放堆栈、局部变量等)。

图1.34 代码尺寸优化:配置“Linker”页面

(3)修改优化级别:在“Options For Target”对话框的“C/C++”页面,可使用编译选项“-Ospace”进行编译,将着重对空间进行优化,让编译器自动减小代码大小。另外,还可以选更高的选优化级别“Level 3(-O3)”,如图1.35所示。Level 3的优化等级最高,最适合下载到最终的产品芯片中,而Level 0为不优化,这种模式最适合调试,因为它不会优化掉代码。在学习时,使用“Level 0(-O0)”,以方便程序的调试。

图1.35 代码尺寸优化:配置C/C++页面

关于MicroLib

使用微库,将以更精简短小的C库替代标准C库,减小代码大小。MicroLib是默认C库的备选库。它主要用于内存有限的嵌入式应用程序中。这些应用程序不在操作系统中运行。

如果你发现在Keil RealView MDK中使用printf函数,不能向串口输出信息,或者今后发现可以软件仿真,不能硬件仿真,注意要在图1.31(a)中的设置对话框选中“USE MicroLib”。MicroLib提供了一个有限的stdio子系统,它仅支持未缓冲的stdin、stdout和stderr。这样,即可使用printf()来显示应用程序中的诊断消息。

要使用高级I/O函数,就必须提供自己实现的以下基本函数,以便与自己的I/O设备(如串口)配合使用。

为所有输出函数:fprintf()、printf()、fwrite()、fputs()、puts()、putc()和putchar()等需要实现fputc()函数。

为所有输入函数:fscanf()、scanf()、fread()、read()、fgets()、gets()、getc() 和getchar()等需要实现fgetc()函数。

由于MicroLib进行了高度优化,以使代码变得很小。因此,MicroLib不完全符合ISO C99库标准,仅提供有限的支持,不具备某些ISO C特性。并且其他特性具有的功能比默认C库少,MicroLib与默认C库之间的主要差异是:

(1)MicroLib不支持IEEE 754关于二进制浮点算法标准,否则会产生不可预测的输出的结果,如NaN、无穷大。

(2)MicroLib中不支持的转换为%lc、%ls和%a。

(3)MicroLib进行了高度优化,以使代码变得很小。

(4)MicroLib不支持与操作系统交互的所有函数,如abort()、exit()、atexit()、clock()、time()、system()和getenv()。不能将main()声明为带参数的,并且不能返回内容。

(5)不支持与文件指针交互的所有stdio函数,否则将返回错误。仅支持三个标准流:stdin、stdout和stderr。即不完全支持stdio,仅支持未缓冲的stdin、stdout和stderr。

(6)MicroLib不提供互斥锁来防止非线程安全的代码。

(7)MicroLib不支持宽字符或多字节字符串。如果使用这些函数,则会产生链接器错误。

(8)与stdlib不同,MicroLib不支持可选择的单或双区内存模型。MicroLib只提供双区内存模型,即单独的堆栈和堆区。

该你了!——比较一下这几种优化方法所生成的可执行文件大小

任务七 用串口调试软件查看单片机输出信息

打开串口调试软件,如图1.36所示进行设置。一般地,若台式PC机(或笔记本电脑)有串口,则选择串口“COM1”;若使用的台式PC机(或笔记本电脑)没有串口,则使用USB转串口适配器,如图1.7(b)所示。安装好对应的USB驱动程序后,在“我的电脑”上右击→属性→硬件,查看“设备管理器”中的“端口(COM和LPT)”信息,如显示“通信端口(COM5)”,则将串口号设为COM5即可。

图1.36 串口调试软件设置

单击“连接”按钮,串口连接成功,如图1.37所示。如串口没有连接成功,注意检查串口连接线是否连接正确,并关掉其他串口调试软件。

图1.37 串口连接成功

这时,你在接收区看到了什么?什么也没有!为什么呢?因为当你把执行文件成功地下载到单片机的那个时刻开始,程序就开始运行了:单片机已经向PC发送了信息。你错过了接收,再按一下教学开发板上的“Reset”按键,你将看到调试软件上的接收区显示了一条信息,如图1.38所示。

图1.38 串口调试终端显示的信息

恭喜你!

如果串口调试终端可以显示信息,则说明你的STM32开发环境都已成功建立,包括硬件连接和软件配置。

HelloRobot.c是如何工作的?

例程中前两行代码是HelloRoBot.c所包含的头文件:“stm32f10x_heads.h”和“HelloRobot.h”。这两个头文件在本书的后续章节和任务中都要用到,“stm32f10x_heads.h”文件主要包含STM32库文件的定义,它在编译过程中用来将程序需要用到的标准数据类型和一些标准函数、中断服务函数等包括进来,生成可执行代码。头文件中可以嵌套头文件,同时也可以直接定义一些常用的功能函数。

HelloRobot.h则包含了本例程中以及后面例程中都要用到的几个重要函数的定义和实现,在后面的章节中会进一步讲解,包括:

(1)数据结构的定义;

(2)系统时钟配置函数:RCC_Configuration;

(3)GPIO模式配置函数:GPIO_Configuration;

(4)中断控制配置函数:NVIC_Configuration;

(5)串口1配置函数:USART_Configuration;

(6)将C语言printf库函数输出重定向到串口输出函数:fputc;

(7)开发板初始化函数:BSP_Init;

(8)微秒延时函数:delay_nus;

(9)毫秒延时函数:delay_nms。

main函数的第一行语句是开发板初始化函数:BSP_Init,用来配置系统时钟、GPIO模式和中断控制,分别是RCC_Configuration()、GPIO_Configuration()、NVIC_Configuration()。这几个函数将在后面的章节讲解。这行语句中“//”后的是注释。注释是一行会被编译器忽视的文字,因为注释是为了给人阅读,编译器不对其进行编译。

main函数的第二行语句是串口1初始化配置函数:USART_Configuration,用来规定单片机串口是如何与PC通信的。串口配置函数将在后面的章节讲解。第三行语句是printf函数:它是单片机通过串口向PC机发送一条信息:Hello Robot!。

main函数的第四行语句是“while(1);”。编译好后的可执行文件是下载到单片机Flash存储器上的,并且是从头开始往下加载的。当你把可执行文件加载上去的时候,填满了整个Flash空间吗?当然没有!那么,当程序执行完printf函数之后,它还将向下继续执行,但后面的空间并没有存放程序代码,这时程序后会乱运行,也就发生了“跑飞”现象。加上while(1);语句,让程序一直停止在这里,就是为了防止程序跑飞。

C语言中的两种文件

在教材各章节中,会多次使用以“#”号开头的预处理命令,如包含命令#include,宏定义命令#define等。一般都放在源文件的前面,称为预处理部分。所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,将首先对源程序中的预处理部分作处理,处理完毕后再对源程序进行编译。

常用的预处理命令有:

(1)宏定义

在C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。“define”为宏定义命令,被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。

(2)文件包含

文件包含是C预处理程序的另一个重要功能。文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。

在程序设计中,文件包含是很有用的。一个大的程序可以分为多个模块,由多个程序员分别编程。有些公用的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。这样,可避免在每个文件开头都去书写那些公用量,从而节省时间,并减少出错。

包含命令中的文件名可以用双引号(“”)括起来,也可以用尖括号(< >)括起来。使用尖括号表示在包含安装软件的目录中去查找(这个目录是安装软件时,设置的安装路径),而不在源文件目录去查找,一般是安装路径(如C:\Keil MDK***)下的Include目录;使用双引号则表示首先在当前的源文件目录中查找(即当前的HelloRobot目录),若未找到才到包含安装软件的目录中去查找。用户编程时可根据自己文件所在的目录来选择某一种命令形式。如“stm32f10x_heads.h”和“HelloRobot.h”2个文件表示在当前源文件所在目录。

一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。

任务八 做完实验关断电源

把电源从教学开发板上断开很重要,原因有几点:首先,如果系统在不使用时没有消耗电能,电池可以用的更久;其次,在以后的学习中,你将在教学开发板上的面包板上搭建电路,搭建电路时,应使面包板断电。如果是在教室,老师可能会有额外的要求,如断开串口电缆,把教学开发板存放到安全的地方等。总之,你做完实验后最重要的一步是断开电源。断开电源比较容易,只要三位开关拨到左边的0位即可,如图1.9(a)所示。

提倡节约用电,实践低碳生活。

通过创建第一个程序,你可能感觉到了学习基于ARM Cortex-M3内核的STM32单片机不是很容易!确实不错,要学好单片机,还要有一定的C语言基础,嵌入式系统的开发确实是一项非常具有挑战性的任务。即使是一个简单的程序,也需要进行大量的准备,学习大量的计算机知识。万事开头难!