本章以终端内部使用的MCU型号为ARM Cortex-M0+内核的KL36芯片(对应金葫芦IoT-GEC的A系列)为例阐述UE程序的执行过程,其他型号MCU的过程大同小异。
终端用户程序编辑、编译使用NXP的Kinetis Design Studio(KDS)集成开发环境,目前使用的版本为3.2.0。
1.下载KDS集成开发环境
KDS 集成开发环境可从苏州大学嵌入式学习社区网站下载,下载网址为 https://sumcu.suda.edu.cn/ ,单击“资料下载”→“工具”→“开发环境下载”,单击“kinetis-design-studio_3.2.0”即可下载,也可以从NXP官网下载( https:// www.nxp.com/cn),但需要注册。
2.如何快速使用KDS集成开发环境
附录B.1给出了KDS集成开发环境的快速指南。集成开发环境属于基本工具,对于工具的使用,我们采用快速指南之途径。原则上,它们并不属于知识要素范畴,但是学习工具的使用往往会花费大量的时间。一些公司的说明书冗长、凝练不足,有时还不得要领。在使用中,读者应该总结体会,并形成自己的文档。为此,本书针对所用工具尽可能地提炼必要的步骤,使读者可以简捷地按照快速指南来完成主要的操作,以节省在学习工具方面花的时间。另外,还要多说几句,读者原来已经熟悉的集成开发环境及编程工具,实际上均大同小异,利用快速指南可以在一个较短的时间内完成基本切换,不必为此纠结。
3.如何下载终端用户程序
3.5节已经给出下载与测试终端用户程序的基本方法,即通过串口方式进行,使用本系统提供的PC软件“AHL-UserPrgUpdate.exe”,在有SWD编程器的情况下,也可通过SWD编程器下载机器码,附录B.2给出了SWD-Programmer快速指南。
面向窄带物联网的通用嵌入式计算机(GEC)内部程序分为固化驻留在内部的基本输入/输出系统(Basic Input and Output System,BIOS)和用户自行编程的用户(User)程序。系统上电复位后,首先执行BIOS,然后转向用户程序。BIOS的主要功能如下。
(1)初始化系统时钟。本系统初始化内核时钟为48 MHz,总线时钟为24 MHz,同时也开启了看门狗。
(2)初始化内核定时器SysTick。开启SysTick定时中断,定时间隔为500 ms。SysTick定时中断处理程序进行计时,并提供两个时间操作API函数:sysTimeGet、sysTimeSet,用户程序可直接通过这两个API函数获取与设置系统时间。
(3)初始化所使用的串口。本系统使用 UART1 作为通信模组与芯片板连接的串口,并且可通过该串口进行远程更新程序,上电复位后,初始化UART1为无校验、115200 bps,使能UART1接收中断及总中断;本系统使用UART2为用户程序更新软件所使用的串口,上电复位后,初始化UART2为无校验、115200 bps,使能UART2接收中断及总中断,以便支持用户程序的更新与基本调试,通过TTL-USB串口线将UE与工具计算机相连,使用“…\04-Soft\01-UE\AHL-UserPrgUpdate.exe”软件即可实现用户程序的更新。需要注意的是,用户程序不得再使用UART1和UART2,否则可能无法更新用户程序。
(4)为用户程序提供应用程序编程接口。BIOS 为用户程序提供应用程序编程接口(Application Programming Interface,API),这是BIOS的最重要功能,可以把大部分硬件底层驱动和NB-IoT通信构件UECom以机器码的形式放入BIOS,用户程序可通过API方式直接调用,无须用户编写,从而缩短了应用程序开发时间。具体的对外接口将在第11~13章中阐述。
(5)判断用户程序是否需要更新。若无须更新,则将中断向量表复制至 RAM 区后,立即转向用户程序执行;若需更新,则等待接收上位机送来的更新命令,同时绿灯 闪烁。
用户程序是真正的用户二次编程模板,可调用驻留在BIOS内的基本输入/输出构件API实现大部分功能。
良好的工程结构是编程的重要一环。建立一个组织合理、易于理解的工程结构需要较深入的思考与斟酌。
所谓的工程结构或工程框架,就是指工程内文件夹的命名、文件的存放位置、文件内容等相关规则。软件工程框架是整个工程的脊梁,其主要任务不是完成一个单独的模块功能,而是指出工程应该包含哪些文件夹,这些文件夹里面应该放置什么文件,各个文件的内容又是如何定位的,等等。
根据这些基本原则,本书设计了终端(UE)的工程结构,如图4-1所示。
图4-1 AHL-IoT工程结构树形模板
工程结构树形模板中的前两个文件Binaries、Includes是编译链接自动产生的,下面以编号01~06开头的文件夹为源代码文件夹,Debug文件夹也是编译链接自动产生的,内部含有机器码文件、.lst文件、.map文件等。
1.源代码文件夹
合理设置文件夹可使“物有所归”,条理清晰。用户程序的源代码文件可分类安排在文档、内核、MCU、用户板、软件构件、无操作系统源程序六个文件夹中。
(1)文档文件夹:01_Doc。软件工程明确指出,文档与程序一样重要,这里把文档放入工程结构中,其目的是让文档与源程序密切联系在一起。在编程过程中需及时记录有关信息。
(2)内核文件夹:02_Core。ARM Cortex-M0+被称为“内核”,比CPU的概念更宽一些,与内核相关的文件放在此文件夹中,如core_cm0plus.h、core_cmFunc.h、core_cmInstr.h,这些文件是由ARM公司提供给MCU厂家的。
(3)MCU文件夹:03_MCU。把链接文件、MCU的启动文件、MCU底层驱动(MCU基础构件)放入这个文件夹中,分别建立 linker_file、startup、MCU_drivers 三个下级文件夹。
(4)用户板文件夹:04_UserBoard。开发者选好一款MCU后,要做成产品总就要设计自己的硬件板,这就是用户板。这个板上可能有LCD、传感器、开关等,这些硬件必须由软件驱动才能工作。驱动这些硬件的软件构件称为应用构件,应用构件大多需要调用MCU基础构件。应用构件被放置在04_UserBoard文件夹中。
(5)软件构件文件夹:05_SoftComponet。与硬件不直接相关的软件构件放在该文件夹中。
(6)无操作系统源程序文件夹:06_NosPrg。该文件夹放置总头文件(includes.h)、中断服务例程源程序文件(isr.c)、主程序文件(main.c)等与具体应用相关的文件,这些文件是工程开发人员进行编程的主要对象。总头文件includes.h是isr.c及main.c使用的头文件,包含用到的构件、全局变量声明、常数宏定义等。isr.c 是中断处理函数所在的文件。main.c是应用程序的启动总入口,main函数就是在该文件中实现的。main函数中包含了一个永久的主循环,对具体事务过程的操作几乎都添加在该主循环中。
2.Debug文件夹
Debug 文件夹中的文件是由工程编译链接自动产生的 ,包含编译链接生成的.elf 文件、.hex 文件、.lst 文件、.map 文件。若编译链接后,Debug 文件夹中只有.elf 文件,没有.lst、.hex、.map文件,则可参考附录B.1进行集成开发环境的设置,增加编译链接过程产生.lst、.hex、.map文件,再重新编译链接即可。
(1).elf文件。.elf(Executable and Linking Format)即可执行链接格式,最初是由UNIX系统实验室(UNIX System Laboratories,USL)作为应用程序二进制接口(Application Binary Interface,ABI)的一部分而制订和发布的。其最大特点是具有比较广泛的适用性,通用的二进制接口定义使其可以平滑地移植到多种不同的操作环境上。可使用 UltraEdit 工具查看.elf文件内容。
(2).hex文件。.hex(Intel HEX)是由一行行符合Intel HEX文件格式的文本所构成的ASCII文本文件,在Intel HEX文件中,每一行包含一个HEX记录,这些记录由对应机器语言码(含常量数据)的十六进制编码数字组成。在KDS集成开发环境下,直接双击并按“F5”键刷新后可查看该文件。
(3).lst 文件。.lst 文件包含了函数编译后机器码与源代码的对应关系,可用于程序分析。
(4).map文件。.map文件包含了查看程序、堆栈设置、全局变量、常量等存放的地址信息。.map 文件中指定的地址在一定程度上是动态分配的(由编译器决定),工程中有任何修改,这些地址都可能发生变动。
User_GEC_Basic的启动过程有些复杂,详见附录B.3,当需要深入理解启动过程时,可查阅该附录。
User_GEC_Basic程序的执行可分两条“线路”:main函数(主循环)及中断处理程序。在AHL-IoT-GEC工程中的06_NosPrg文件夹有三个文件,即includes.h、main.c和isr.c,这三个文件可实现User_GEC_Basic主循环及中断处理程序。
1.理解总头文件(includes.h)
includes.h 文件称为应用程序总头文件,是全局变量声明的地方,它还包含了 main.c和isr.c文件中使用到的所有构件的头文件、全局使用的宏常数以及自定义数据类型。
特别需要理解的是,在重复包含includes.h文件时,为了防止全局变量被重复声明,在文件中引入了宏G_VAR_PREFIX和GLOBAL_VAR,给出了“全局变量一处声明多处使用的处理方法代码段”,并且规定,在随后的所有全局变量声明 中,一律带上G_VAR_PREFIX这个宏作为前导。如果在 main.c 文件中宏定义 GLOBAL_VAR 且包含 includes.h,那么宏G_VAR_PREFIX 就被替换为“空”,即全局变量在 main.c 文件中声明。在 isr.c 文件包含includes.h后,由于isr.c文件没有宏定义GLOBAL_VAR,因此isr.c文件中全局变量的声明会自动带上extern前缀,这样就可避免全局变量重复定义问题,即isr.c文件中不会再为每个全局变量声明一次,这就是“全局变量一处声明多处使用”的含义。
includes.h文件中“全局变量一处声明多处使用”的代码如下:
2.理解主流程(main.c文件)
在MCU完成系统启动过程后,系统将进入main函数继续执行。main函数包含了所有的主流程操作,是终端(UE)编程的主要内容。main.c中主要包含两大部分:一是初始化部分,包括声明主函数使用的变量、初始化驱动构件函数指针、为有关变量赋初值、初始化外设模块、使能模块中断等;二是主循环体部分,负责响应各个中断。下面给出了main函数的执行框架。
初始化部分完成了工程中所有上电后只需执行一次的操作,为主循环体代码的执行提供了基础。主循环体部分中的代码内容需要不断重复执行,响应中断的发生。main.c 文件主要实现的功能如下:终端(UE)上电后LCD会提示产品型号、BIOS版本、芯片温度、eSIM卡的IMSI、基站信号强度、LBS位置信息等,在成功连接网络后会显示时间;每间隔一段时间,终端(UE)通过信息邮局(MPO)向指定 IP 和端口号的服务器发送数据,并等待接收数据,进行相关的数据处理;当触摸按键时,LCD显示按键次数,每按下三次,终端(UE)将进行数据的发送与接收;使用AHL-UserPrgUpdate可更新终端用户程序。
3.理解中断处理程序(isr.c文件)的功能
另一条运行路线是中断处理程序。当某个中断产生时,执行相应的中断处理程序。终端驻留程序BIOS在User_GEC_Basic程序运行时可为其提供系统时间的查询和配置、串口更新与函数调用等功能,这些功能代码是在BIOS中的Systick、UART2和SVC中断等中断服务例程中实现的,表4-1列举了BIOS中驻留的中断服务例程。
表4-1 BISO中驻留的中断服务例程
若想要 User_GEC_Basic“继承”BIOS 系统时间的查询和配置、串口更新等功能,那么BIOS和User_GEC_Basic就需要使用相同的Systick、UART2和SVC等中断服务例程。因此,在BIOS跳转到User_GEC_Basic执行之前,需要首先将BIOS的中断向量表复制到RAM区,以便User_GEC_Basic使用。其实现方法如下。
(1)在BIOS的链接文件ld中定义一段RAM空间地址uservectors,用来复制BIOS的中断向量表。定义的 RAM 段起点处为“__VECTOR_RAM__”,定义长度为“__RAM_VECTOR_TABLE_SIZE”。
(2)先将BIOS的中断向量表(物理地址0x0000000开始)复制到步骤(1)中定义的RAM空间地址uservectors中(其首地址为__VECTOR_RAM__),然后设置User_GEC_Basic中断向量表的偏移寄存器SCB->VTOR为__VECTOR_RAM__,即可实现User_GEC_Basic中断向量表的重定位。
此时,BIOS和User_GEC_Basic使用的是相同的中断向量表,即Systick和UART2的中断服务例程指向原BIOS处,从而使User_GEC_Basic“继承”终端程序驻留程序的Systick中断和UART2中断等。
而对于User_GEC_Basic,其中断向量表放置在RAM中,因而可以通过修改该中断向量表中的内容来动态地配置各个中断服务例程,例如,在User_GEC_Basic中通过setVector函数可实现中断服务例程的注册操作。这样,User_GEC_Basic就“继承”了终端BIOS的Systick和UART2等中断处理例程,而其他中断可使用setVector函数进行动态注册。
User_GEC_Basic共有两个中断处理例程,这两个中断处理例程包含在06_NosPrg文件夹下的isr.c文件中,分别是LPTMR0 中断和TSI0 中断。
(1)LPTMR0中断:每500 ms执行一次中断,完成计时操作。
(2)TSI0中断:当TSI触摸键被触摸时触发该中断,可通过TSI触摸键实现不同的功能,是与外界进行交互的接口之一。
用户程序在执行过程中会响应7个中断,其中,从BIOS中“继承”了5个中断,另外两个在User_GEC_Basic中实现。中断处理例程与主流程相互配合、各负其责,共同完成工程所需的各种功能。