第四章为面向接口的编程,本文内容包括:4.1 平台技术、4.2 开关量信号。
本章导读:
在结构化程序设计中,由于高层模块依赖底层模块,通常一个变化会引出另外的问题发生改变,则变化的代价就会急剧上升。所以在引入接口时,一个重要的经济考量是软件的不可预测性,因为需求和技术都在以不可预测的方式变化,其目的就是为了降低依赖。
4.1 平台技术
>>> 4.1.1 创新的窘境
虽然嵌入式系统和通用计算机系统同源,由于应用领域和研发人员的不同,嵌入式系统很早就走向了相对独立的发展道路。通用计算机软件帮助人们解决了各种繁杂的问题,随着需求的提升,所面临的问题越来越复杂,软件领域的大师们对这些问题进行了深入研究和实践,于是诞生了科学的软件工程理论,无需多言通用计算机软件的发展是我们有目共睹的。
再回过头来看嵌入式系统的发展,其需求相对来说较为简单,比如,通过热电阻传感器测温、上下限报警与继电器的动作,因此嵌入式系统的应用开发似乎没有必要使用复杂的软件工程方法,于是通用计算机系统和嵌入式系统走上了不同的发展道路。
当嵌入式系统发展到今天,所面对的问题也日益变得复杂起来,而编程模式却没有多大的进步,这就是所面临的困境。相信大家都或多或少地感觉到了,嵌入式系统行业的环境已经开始发生了根本的改变,智能硬件和工业互联网等都让人始料不及,危机感油然而生。
尽管企业投入巨资不遗余力地组建了庞大的开发团队,当产品开发完成后,从原材料BOM 与制造成本角度来看,毛利还算不错。当扣除研发投入和合理的营销成本后,企业的利润所剩无几,即便这样员工依然还是感到不满意,这就是传统企业管理者的窘境。
虽然ZLG 投入了大量的人力资源,但重复劳动所造成的损耗以亿元计。上千种MCU,由于缺乏平台化的技术,即便相同的外围器件,几乎都要重新编写相应的代码和文档并进行测试,所有的应用软件很难做到完美地复用。
在开发同一系列高中低三个层次的产品时,通常会遇到这样的问题,主芯片可能使用ARM9、双核A9 和DSP,其操作系统分别为μC/OS-II、Linux 和SysBIOS。不仅驱动代码不兼容,而且应用层代码也不一样,显然浪费在这上面的开销是不可想象的。
>>> 4.1.2 AWorks
在MCU 产业快速发展的今天,不同的MCU 外设的差异千差万别。AWorks 对同一类外设进行了抽象,并设计了相应的标准接口与对应的中间层,使得不同厂商、型号的MCU 外设都能以标准的接口操作。
对于上层操作系统而言,对各个外设都需要编写驱动。在编写特定操作系统下的驱动时,必须熟悉特定的驱动平台与操作系统调用,以至于程序员不得不花费大量的时间学习与此相关的知识。对于同一个外设而言,如果要支持多个操作系统,则需要编写多个驱动,其实驱动底层对硬件的操作是有相通之处的,AWorks 的出现便是解决这里提到的问题。
1. AWorks 的特点
如图4.1 所示是AWorks 的标识符,这是ZLG 经过十多年时间积累开发的IoT 物联网生态系统,成功地应用到ZLG 的示波器、功率计、功率分析仪、电压监测仪、电能质量分析仪、数据记录仪与工业通讯等系列高性能仪器和工业IoT 产品中。
图4.1 AWorks
虽然AWorks 内嵌了操作系统,但AWorks 中的操作系统如同一个驱动代码一样,仅仅是一个可以根据需要任意更换的组件。操作系统适配器直接驻留在操作系统接口之上,主要用于屏蔽各类操作系统和硬件接口的差异,从而增强了AWorks 的可移植性和可维护性,详见图4.2。
图4.2 AMetal 与AWorks 的关系
AWorks 的核心是制定了统一的接口规范,可以“按需定制”提供各种各样的高质量组件,以及图形化配置工具,则开发者无需关心与MCU、外围器件、OS有关的技术细节。如果要对底层硬件外设进行操作,也可以直接调用底层接口。
其目的是不依赖于具体的底层硬件,实现“一次编程、终生使用”和“跨平台”,通过高度复用应用软件组件,从而将程序员从苦海中释放出来,使之聚焦于更有竞争力的需求分析、算法和用户体验等业务逻辑上。行业合作伙伴可以在该平台上开发各种应用,通过有线接入和无线接入收集、管理和处理数据。
由于AWorks 提供的所有组件均使用静态内存,不使用malloc()等动态内存分配函数,彻底避免了内存泄漏。且程序编译完成后,即可知道系统运行需要占用多大的内存。AWorks是轻量级的实时系统,所有组件对初始化都进行了优化,系统能以极短的时间启动,因此在绝大部分项目中,启动时间<1s。显然系统运行时的实时性,确定性得到了最大的保障。
2. AMetal 的特点
AMetal 是AWorks 的子集,它是以API 的形式提供的,但不依赖AWorks。即:
-
将外设操作标准化,无需再次开发上层软件、驱动;
-
AMetal 作为AWorks 标准化的外设功能补充,可以直接调用;
-
可作为独立发布的软件包。
其特点为:
-
能独立运行的AMetal,提供工程模板与demo,在此基础上开发应用程序;
-
不依赖操作系统服务,将外设的所有特性开放出来;
-
封装时将效率和变化部分放在第一位,用户不看手册也能使用。
AMetal 在嵌入式软件开发中的位置详见图4.3,它位于底层硬件和上层软件之间,可以被应用程序与操作系统直接调用。AMetal 包括两部分,即AMetal API 和AMetal 组件。
图4.3 AMetal 在嵌入式软件开发中位置
AMetal API 重在抽象MCU 本身的功能部件,比如,GPIO、SPI、UART、I2C 等,程序员再也不用查看芯片手册就能编写外围器件驱动程序。AMetal 组件重在抽象外围扩展接口器件,比如,E2PROM、SPI NOR Flash、PCF85063 RTC、zigbee、BLE、键盘扫描与LED显示器电路等,程序员再也不用看原理图和编写外围器件驱动程序。
AMetal 提供的不仅仅是某个MCU 的软件包,而是一套接口规范,因此只要遵循AMetal 标准化接口函数规约,无论所用的MCU 是ARM还是DSP 或其它的内核,则应用软件均可实现复用。
如图4.4 所示的AMetal 框架分为硬件层、驱动层和标准接口层,上层软件根据需求调用合适的API 接口。三层都有对应的头文件和用户配置文件,供用户引用相应的接口。虽然各大半导体公司针对各自的MCU,提供了类似AMetal 这样的裸机API,但每个新的MCU,其API 的差异很大,因此很难做到复用应用软件。
图4.4 AMetal 框架
这里以GPIO 为例,给出相应的的文件结构图,详见图4.5,AMetal 其相应的文件有:HW 层文件、驱动层文件和用户配置文件。通常情况下,HW 层提供了直接操作硬件寄存器的接口,接口实现简洁,往往以内联函数的形式放在.h 文件中,因此,HW 层通常只包含.h文件,但当某些硬件功能设置较为复杂时,也会提供对应的非内联函数,存放在.c文件中。驱动层作为中间层,其使用HW 层接口,实现了标准接口层中定义的接口,以便用户使用标准API 访问GPIO。用户配置文件完成了相应驱动的配置,如引脚数目等。
图4.5 GPIO 文件结构
标准接口层对常见外设的操作进行了抽象,提出了一套标准API 接口,可以保证在不同的硬件上,标准API 的行为都是一样的。用户使用一个GPIO 的过程:先调用驱动初始化函数,后续在编写应用程序时仅需直接调用标准接口函数即可。可见,应用程序基于标准API 实现的,标准API 与硬件平台无关,使得应用程序可以轻松的在不同的硬件平台上运行。
4.2 开关量信号
>>> 4.2.1 I/O 输入输出
嵌入式系统的主要功能就是要实现对现实事件的监控,如同没有配置显示器、打印机或键盘的台式计算机一样,嵌入式系统必须具备输入输出(I/O)数字信号和模拟信号的能力。LPC824 系列MCU 可用的GPIO 数目,具体取决于芯片的封装类型,详见表4.1。
表4.1 GPIO 引脚
其特性为:
(1)每个GPIO 引脚均可通过软件配置为输入或输出;
(2)复位时所有GPIO引脚默认为输入;
(3)引脚中断寄存器允许单独设置;
(4)可以独立配置每个引脚的置高和置低。
当LPC824 系列MCU 的I/O 口作为数字功能时,可配置为上拉/下拉、开漏和迟滞模式。在输出模式下,无论配置为哪种模式,I/O 口都可输出高/低电平。在输入模式下,且引脚悬空时,I/O 口设置为不同模式时,其情况为:
(1)设置为高阻模式时,读取引脚的电平状态不确定;
(2)设置为上拉模式时,读取引脚的电平状态为高电平;
(3)设置为下拉模式时,读取引脚的电平状态为低电平;
(4)设置为中继模式时,如果引脚配置为输入且不被外部驱动,那么它可以令输入引脚保持上一种已知状态。
在默认情况下,除I2C 总线P0_10 和P0_11 没有可编程的上拉/下拉电阻和中继模式外,其它所有GPIO 的上拉电阻都被使能。在默认情况下,每个GPIO 仅分配给一个且仅一个外部引脚,外部引脚随后便由其固定引脚GPIO 功能来标识。但通过开关矩阵可将内部功能分配给除电源和接地引脚外的任意外部引脚,这些功能称为可转移功能。而晶体振荡器引脚(XTALIN/XTALOUT)或模拟比较器输入等某些功能仅可分配给具有适当电气特性的特定外部引脚,这些功能称为固定引脚功能。如果某个固定引脚功能未被使用,则可将其替换为任意可转移功能。对于固定引脚模拟功能,开关矩阵可使能模拟输入或输出并禁用数字端口。
>>> 4.2.2 新建工程
在编程之前,必须先建立工程,然后才能将程序下载到开发板上运行。由于AMetal 已经提供了模板工程,所以“新建工程”只需拷贝一下即可。
模板工程就是位于projects_keil5applications 目录下的 template 文件夹。新建工程即将该文件夹重新复制一份,命名为led_blinking。接着打开led_blinking 文件夹,将工程文件命名为led_blinking,然后双击led_blinking.uvprojx 工程文件打开工程,更详细的操作详见配套开发资料中的《快速入门手册》。
打开工程后,虽然在工程视图的左侧有很多分组(user_config 和user_code 等),每个分组下都有相应的文件。但先不用理会,只需要关心user_code 分组下的main.c 文件,就在该文件中的am_main()函数中添加应用程序。当MCU 无事可做时,不能让它闲下来,因为am_main()函数结束标志整个应用程序结束,从而导致MCU 跑飞。因此,am_main()函数中通常都存在一个while(1)死循环。当工程建好后,即可编程了。
>>> 4.2.3 输出控制
如图4.6 所示为AM824-Core 板载的2 个LED发光二极管,与MCU 的I/O 引脚通过J9 和J10 相连,其中的LED0 通过J9 与MCU 的PIO0_20 相连,LED1 通过J10 与MCU 的PIO0_21 相连。当I/O 输出低电平0 时,由于LED 阳极加了3.3V 电压(高电平1),因而形成了电位差,所以有电流流动,则LED 发光二极管导通,即LED 发光;当I/O 输出高电平1 时,由于无法形成电位差,则LED二极管不导通,即LED 熄灭。
图4.6 板载LED 电路
电阻R3、R4 的作用是防止产生过电流而烧坏LED,这是由电源电压和LED 的额定电流决定的LED 电学特性而接入的,当LED 的电压超过1.5V 时,电流将急剧增加,所以必须避免出现这样的情况。在数字电路中,当输出为高电位时,则电流流到负载上;当输出为低电位时,则从负载一侧吸入电流。前者的电流叫作源电流,后者叫作吸收电流。显然LED只有点亮、熄灭和翻转3 种操作,可以直接调用接口函数实现,led.h 接口文件的内容详见程序清单4.1(各接口具体的实现在第4 章中会详细介绍)。
程序清单4.1 led.h 接口
其中,led_id 是LED 的编号,AM824-Core 板载LED 的编号分别为0 和1。事实上程序设计的依赖倒置原则不一定要包含函数指针或抽象类型数据,当调用led_on()和led_off()点亮或熄灭LED 时,就使用了依赖倒置原则。它本来可以与I/O 的内存映射直接交互,却将直接访问抽象成了接口。那么针对接口的编程不妨就从点亮LED 开始,详见程序清单4.2。
程序清单4.2 点亮LED 范例程序
LED 闪烁就是让LED 不停的亮灭,因为计算机指令的执行速度非常快,其执行时间是微秒级的,所以在微秒之间点亮和熄灭LED,眼睛是看不到闪烁现象的。如果想让人眼看到LED 闪烁,就必须将LED 点亮和熄灭的停顿时间扩大近秒级别。如何实现停顿呢?点亮LED 后,先不要熄灭LED,而是先延时一会儿,让“点亮LED 后,再保持一段时间”,然后再熄灭LED。在实验之前,我们需要延时函数,它在C 语言中是怎么实现呢?很简单,就是让MCU 执行一些没有任何实际意义的空循环指令,进而等效于延时。延时范例程序详见程序清单4.3,该程序中,MCU 大约就要执行1000000 条空指令。
程序清单4.3 延时范例程序
这样的延时函数好用吗?由于MCU 运行速率不同,因而导致实际的延时结果不同。如果要求不同的延时时间,则又需要不同的延时函数。显然,该延时函数不仅通用性太差,而且延时时间也不精确。幸运的是,AMetal 提供了使用定时器实现的高精度标准延时函数,主要包含了2 个延时函数,其函数原型(am_delay.h)如下:
按照前面的思路,在点亮灯之后,延时一段时间,让“亮灯状态”保持一段时间,再熄灭LED 灯,再延时一段时间,让“熄灭状态”保持一段时间,详见程序清单4.4。
程序清单4.4 单个LED 闪烁范例程序(1)
其实LED 不停地闪烁就是让一个I/O 不断翻转的过程,而AMetal 软件包针对LED 提供了翻转LED 状态的函数led_toggle(),优化后的代码详见程序清单4.5。
程序清单4.5 单个LED 闪烁范例程序(2)
MiniPort-LED 模块集成了8 个LED,均为低电平有效,分别通过PIO0_8~PIO0_15 控制,其中的R1~R8 为LED 的限流电阻,详见图4.7。
图4.7 LED 模块电路
LED 模块通过MiniPort B(排母)与AM824-Core相连,同时将其余不使用的I/O 通过MiniPort A(排针)引出,实现模块的横向堆叠,其对应AM824-Core 的MiniPort 接口J4 的功能定义详见图4.8。
图4.8 LED 模块实物与接口定义图
下面将以LED 流水灯为例说明通用函数接口的应用开发方法。人们时常看到户外动画广告,一会儿从左到右显示,一会儿又从右到左显示,这就是流水灯效果。现在用LED 流水灯来模拟户外动画广告,使LED 顺时针旋转循环流动。假设上电初始化,将所有的GPIO 都配置为输出,且初始化为高电平,即所有的LED 全部熄灭。首先点亮LED0,延时后熄灭LED0;接着点亮LED1,延时后熄灭LED1......;然后点亮LED7,延时后熄灭LED7;接着点亮LED0,延时后熄灭LED0……,周而复始形成了循环的圆环,详见程序清单4.6。
程序清单4.6 LED 流水灯范例程序(1)
流水灯实验使用的是MiniPort-LED 的8 个LED,其对应的控制引脚与AM824-Core 上两个LED 对应的控制引脚是不同的。当使用MiniPort-LED 时,需要将led.h 文件中的宏USE_MINIPORT_LED 对应的值修改为1,表明使用MiniPort-LED。该宏的默认值为0,使用AM824-Core 板载的两个LED 灯。
显然,LED 流水灯是一个典型的“首尾相接”算法,循环队列与环形缓冲区等都属于同一类问题,这是一种常用的软件设计模式,优化后的代码详见程序清单4.7。
程序清单4.7 LED 流水灯范例程序(2)
如图4.9 所示为无源蜂鸣器电路原理图,只要短接J7_1 与J7_2,则蜂鸣器接入PIO0_24。当PIO0_24 输出低电平时,则三极管导通,向蜂鸣器供电;当PIO0_24 输出高电平时,则三极管截止,停止向蜂鸣器供电。因此只需要轮流切换PIO0_24 的电平状态,就可以控制蜂鸣器的“通电”和“断电”,即以一定的频率翻转PIO0_24 的输出电平。其实接通和断开“一段时间”的总和就是蜂鸣器的振荡周期,再稍作转换就能够得到确定的音频脉冲频率参数。从而产生机械振动音,只要频率在人耳听觉范围内,即可听到蜂鸣器发声。
图4.9 蜂鸣器电路图
显然,通过翻转引脚电平和延时,也可以让蜂鸣器发出固定频率的声音。假如使蜂鸣器发出1KHz 频率的声音,1KHz 频率对应的周期为:T=1/1000(s)=1(ms),由于一个周期是低电平(接通)时间和高电平(断开)时间的总和,因此在一个周期内,高、低电平保持的的时间分别为500us。由此可见,要使蜂鸣器不间断地发声,只要以500us 的时间间隔不断的翻转引脚的输出电平即可。当需要控制蜂鸣器时,可直接调用蜂鸣器接口,buzzer.h文件内容详见程序清单4.8 buzzer.h 接口。
程序清单4.8 buzzer.h 接口
buzzer_init()会将发声频率设置为默认值:1KHz。如需修改发声频率为其它值,如:2.5KHz,则应调用发声频率设置函数,即“buzzer_freq_set(2500);”,详见程序清单4.9。
程序清单4.9 蜂鸣器发声范例程序
-
接口编程
+关注
关注
0文章
9浏览量
8732 -
开关量信号
+关注
关注
0文章
24浏览量
2887
原文标题:周立功:面向接口的编程——平台技术、开关量信号
文章出处:【微信号:Zlgmcu7890,微信公众号:周立功单片机】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论