所谓GPIO,就是通用型输入输出(General Purpose Input/Output),GPIO试验是单片机引脚的基本输入输出功能。现在来实现这样一个场景:四个按键作为开关、四个LED作为响应,每个开关控制一盏灯,按一次开灯,再按一次关灯。再接入一个蜂鸣和另外两个按键作为音量加和音量减,音量加每按一次蜂鸣器音量增加5%,音量减每按一次音量减少5%。

一、仿真电路图设计

1、最小系统板

单片机只是一个芯片,它想要跑起来肯定还需要若干外围电路。

①电源:在Protues仿真中芯片的VCC和GND被略掉了,VCC就是接电源的正极,GND就是接地(电源负极),AT89C51需要电压为5V的电源供电,这款单片机的引脚输出高电平为5V左右、低电平为0V左右。

②振荡电路:振荡电路以晶振为核心,搭配一些电容组成。单片机的XTAL1、XTAL2就是外接晶振用的。在Protues仿真电路中,也可以不外接晶振,更快捷的方法是双击AT89C51芯片直接设置振荡频率,但为了和真实的电路保持一致,上图中还是接入了晶振,振荡频率设置为11.0592MHz。这个振荡频率和我们经常提到的电脑CPU频率本质上是一回事,指的是芯片运行时的时钟频率。当然,稍高档的芯片都会支持分频、倍频、超频,这个以后会讨论。

③复位电路:电路如上图所示,按下按键会使单片机复位。实际电路中具体要参考芯片手册实现。

仿真电路中即使不搭建振荡电路、复位电路,只有一个单独的AT89C51芯片,也可正常运行,因为Protues默认做了这些配置。但实际制作电路板的时候,这些器件必不可少。

上图中仔细观察会发现每个电路接口处会出现一个方形点,有红色、蓝色、灰色。其中红色方点代表高电平状态、蓝色方点代表低电平状态、灰色方点代表高阻态(高电阻状态,单片机输出哪种电平给它,或外接哪种电平给它,它就是哪种状态)。

2、LED模块

上图中LED以共阳极的接法接入电路,即LED的正极接电源,负极端接单片机。共阴极的接法是LED负极接地,正极接单片机。共阳极接法单片机输出低电平时LED亮起,输出高电平时LED熄灭。共阴极接法则相反。之所以采用了共阳极接法,是因为单片机的灌流(电流灌入单片机)能力略高于其电流输出驱动能力。在Protues中表现不太明显,即使采用共阴极接法,用单片机输出的电流仍然可以点亮LED,但在现实中使用实体单片机时,会发现单片机能直接驱动的电子器件很少很少,多是通过驱动芯片、放大电路、继电器等元件去驱动其他电子器件的。即使是灌流能力,单片机也比较弱,详细可以参考对应的芯片手册,如AT89C51是在100mA左右,电流过大会导致芯片烧毁,虽然在Protues中没有烧毁这种概念(不过话又说回来,不烧掉一堆元件,炸掉一堆管子也成不了真正的电子工程师)。双击LED可以看到,其启动电压为2.2V,电流为10mA,所以此处接了470Ω的限流电阻,保证电流能驱动起来LED,但又不至于有太大的电流输入到单片机中。

3、按键模块

按键模块比较容易理解,单片机P1默认是高电平(因为其内部有上拉电阻,把P1引脚的输出电平上拉为高电平,同理,下拉电阻会把引脚下拉成低电平。AT89C51的P0默认为高阻态,P1~P3都是默认为高电平),当按键按下时,对应的引脚接地,变成了低电平。按键抬起后,该引脚又变成了高电平。

4、蜂鸣器模块

蜂鸣器模块就加入了一个放大电路,基极电流微小的变化能引起集电极电流较大的变化,且二者符合一定的比例关系。所以P3^7引脚通过发出PWM波(PWM就是脉冲宽度调制,也就是占空比可变的脉冲波形。就是一个周期中高低电平所占的百分比可控),控制基极电流大小,进而控制集电极电流大小,再进而控制蜂鸣器的音量。Protues中蜂鸣器所需电压为12V,而单片机所需电压为5V,为便于仿真,图中直接使用了5V、12V两种电源端子,在实际应用中,一个设备不可能直接提供两种输入电压,而是提供一种输入电压,然后经过变压、分压等方式得到其他所需电压

二、软件架构设计

区区一个开关灯也需要架构设计吗?当然,软件入门之初就应该思考其架构设计,这样在以后设计大型项目或者分析复杂的大型嵌入式系统时,才能够清晰明确。

对于没有跑操作系统的单片机裸机程序设计,可以参考上图所示的两种方式。

左侧是较为简单的系统,其核心是把与操作硬件相关的驱动程序归为一层,业务程序归为另一层。驱动层向应用层提供标准的函数接口,这样即使硬件层发生变化,只修改驱动层程序就可以了,只要驱动程序提供给应用层的接口未发生变化,应用层就无需修改。同理,如果只是业务需求发生了变化,硬件未发生改动,则驱动层基本上不需要修改。除非驱动层原来提供给应用层的接口无法满足新的业务需求。

        右侧为较复杂的单片机系统,除了原有的硬件层、驱动层、应用层外,另加入了Framework层,这一层主要对标准操作做封装,向应用层提供标准的API(函数接口)、控制策略组合、各种函数组合。举个例子,当单片机接入LCD屏幕的时候,驱动层直接和LCD硬件设备打交道,控制LCD屏幕;Framework层提供在LCD上画出长方形、正方形、直线等API供应用层调用,并调用驱动层函数实现了这些标准方法。

三、Keil5新建工程

1、新建项目工程

打开keil5,选择“Project -- New uVision Project”

接下来会弹出Device选择框,选项如图所示。然后搜索下AT89C51,选中AT89C51,点击OK。

之后会弹出如图所示对话框,选择“是”。SATARTUP.A51里面有一些初始化操作,包括初始化SP、启动时对RAM数据清零等。如果删除SATARTUP.A51,单片机重启后(非断电重启),之前RAM空间的数据会保留。其实无论选择“是”或“否”,代码中都已包含该文件,这是跳入C函数之前执行的一段汇编代码,不加就用默认的启动代码;加了但是没修改这段代码,还是相当于使用默认的启动代码(这段代码刚刚添加上时和默认启动代码一致),这时加和没加都一样。如果采用汇编语言编程,建议不添加,因为需要改模块名称。如果采用C语言编程,建议添加,避免忘记某些初始化工作

2、按照架构设计在项目工程下新建分组

  之后点击三个方块摞在一起的那个图标,对刚刚新建的工程做一些配置。Project Targets是工程Target的名称,可以根据需要做出修改。Groups是源代码文件分组,可以修改,蓝色方框圈出的那个图标可以添加Groups,这里把SATARTUP.A51文件所在的文件夹名称改成了SATARTUP,新建了DRIVER分组,用于存放驱动程序,新建了APP分组,用于存放应用程序。注意,此处的分组并不是文件夹,也不会生成文件夹,而是Keil用于管理项目工程使用的分组,文件夹稍后会创建。

3、创建程序源码文件

之后该添加源文件了,点击“File -- New”创建文件

点击保存,就会有弹出框,让把文件保存到指定位置,本项目中我们可以这样存放文件

在项目目录下创建APP文件夹和DRIVER文件夹,里面分别创建source和include文件夹,其中source文件夹用于存放.c源文件,include文件夹用于存放.h头文件。上一步创建的文件是哪一类,就保存到哪个文件夹下,便于管理。如led驱动led.c文件放置到DRIVER/source文件夹下,led.h放置到DRIVER/include文件夹下。

4、程序源码文件关联到项目工程

文件创建完后,其实并没有和项目工程关联起来。这里注意,上面1、2步是创建配置项目工程,3步是创建源代码文件,现在两者都创建好了,但并未关联,需要把源文件添加到项目工程才算关联起来。添加方法为右键点击分组,点击如图所示选项,找到3步中创建的源文件,添加到对应分组。注意只添加.c文件,本项目添加完成后如下图所示。

5、添加头文件

上一步只添加了.c文件,那么.h文件是怎么和工程关联起来的呢?要知道使用一个.c文件中的方法,.h都是必不可少的。

①点击那个魔法棒图标

②选中C51选项卡

③点击蓝色框内的图标,选中你放置.h文件的文件夹,此处可以添加多个包含.h文件的文件夹,因为本项目比较简单,APP层只有一个.c文件,没有.h文件,所以这里只添加了DRIVER层的.h文件所在目录。选中后,系统已自动替换成了相对目录(头文件相对于项目所在的目录)。

6、配置编码格式

编码格式也顺便配置一下,否则在编写代码时,如果使用汉字注释会变成乱码。

四、LED驱动程序设计

AT89C51是一种8位单片机,即总线位数为8位。有四组IO口,即P0~P3,每组IO口有八个引脚,对应着一个8位寄存器,即8个bit位。每个引脚对应着一个bit位。这个bit位为0时,引脚输出低电平,这个bit位为1时,引脚输出高电平。反之亦然,外部对这个引脚输入低电平时,内部对应的bit位可以读取到0,对这个引脚输入高电平时,内部对应bit可以读取到1。对LED的操作就是想要开关它,那么这样设计它的驱动程序:

led.h文件,定义了四个函数,从上到下分别为开灯、关灯、获取灯的当前状态、操作灯。参数site表示当前灯是哪一个,on_off表示要打开还是关闭。

#ifndef _LED_H_
#define _LED_H_void led_on(unsigned char site);void led_off(unsigned char site);char get_led_status(unsigned char site);void led_operate(unsigned char site,unsigned char on_off);
#endif

led.c

#include <reg52.h>
#include "led.h"sbit led_d1 = P0^0;
sbit led_d2 = P0^1;
sbit led_d3 = P0^2;
sbit led_d4 = P0^3;void led_on(unsigned char site){switch(site){case 0: led_d1 = 0;break;case 1:led_d2 = 0;break;case 2: led_d3 = 0;break;case 3: led_d4 = 0;break;default:break;}
}void led_off(unsigned char site){switch(site){case 0: led_d1 = 1;break;case 1:led_d2 = 1;break;case 2: led_d3 = 1;break;case 3: led_d4 = 1;break;default:break;}
}char get_led_status(unsigned char site){switch(site){case 0: return led_d1;case 1:return led_d2;case 2: return led_d3;case 3: return led_d4;default:return -1;}
}
//on_off 0:开灯 1:关灯
void led_operate(unsigned char site,unsigned char on_off){if(on_off == 0){led_on(site);}else if(on_off == 1){led_off(site);}
}

五、按键驱动程序设计

目前还没有接触到中断,所以按键的状态需要程序循环扫描获得。由于按键机械特性所致,按键按下与放开时都有一个抖动过程,就是当按键按下时电平并不是瞬间达到低电平并保持不变,而是抖动之后才稳定下来的。按键释放时也是一样。

所以在程序设计中有一个去抖动操作,“如果发现按键按下(读取到低电平) → 等10毫秒 → 按键仍是按下状态(仍是低电平)”,说明按键已经稳定按下了。然后等待按键释放(再次读取到高电平),按键释放后即为完成了一次完整的按键操作,可以进行相关动作了。可以这样设计驱动程序:

key.h

#ifndef _KEY_H_
#define _KEY_H_char scan_keyboard();
#endif

key.c

#include <reg52.h>
#include "delay.h"
#include "key.h"sbit key1 = P1^0;
sbit key2 = P1^1;
sbit key3 = P1^2;
sbit key4 = P1^3;
sbit key5 = P1^4;
sbit key6 = P1^5;char scan_keyboard(){ //返回当前操作过的按键位置char site = -1;if(key1 == 0){delayms(10);if( key1 == 0){while(!key1);site = 0;}}else if(key2 == 0){delayms(10);if( key2 == 0){while(!key2);site = 1;}}else if(key3 == 0){delayms(10);if( key3 == 0){while(!key3);site = 2;}}else if(key4 == 0){delayms(10);if( key4 == 0){while(!key4);site = 3;}}else if(key5 == 0){delayms(10);if( key5 == 0){while(!key5);site = 4;}}else if(key6 == 0){delayms(10);if( key6 == 0){while(!key6);site = 5;}}return site;
}

delayms为毫秒级延时函数,其代码如下:

#ifndef _DELAY_H_
#define _DELAY_H_void delayms(unsigned int xms);
#endif
#include "delay.h"void delayms(unsigned int xms){  //毫秒级延时函数unsigned int i,j;for(i=xms;i>0;i--){for(j=110;j>0;j--);}
}

就是执行循环操作,其他什么也不做。根据晶振频率计算,晶振11.0592MHz,执行110次无其他逻辑的循环大约耗时1ms。(这个并不太准,和时钟周期、指令周期、状态周期、机器周期等都有关系,精确的计时不会使用这种,而是会使用定时器中断实现。)

六、蜂鸣器驱动程序设计

蜂鸣器的控制相对就比较简单了,使用了一个引脚,引脚高电平时蜂鸣器有电流通过,低电平时,蜂鸣器无电流通过。通过控制一个周期内高低电平的占比,就可以控制通过蜂鸣器的电流,进而控制其音量。本项目中一个周期可以设置为20节拍,每次按下音量按键时对高电平增或减一个节拍,即每次音量增减5%。

buzzer.h

#ifndef _BUZZER_H_
#define _BUZZER_H_void buzzer_open();void buzzer_off();
#endif

buzzer.c

#include <reg52.h>
#include "buzzer.h"
sbit buzzer = P3^7;
void buzzer_open(){buzzer = 1;
}
void buzzer_off(){buzzer = 0;
}

七、应用程序设计开发

前面的都搞定了,应用程序就比较容易了。裸机单片机有一个共同的特点,就是程序都是跑在一个死循环中的,没错,就是死循环。除非断电,这个死循环算是结束了,再上电,这个死循环又开始了。为什么呢?因为如果不是个死循环,单片机一个任务跑完就再也不会自动跑第二次。蜂鸣器部分其实设计的有点粗糙,按键被按下等待释放的过程中,蜂鸣器是一直在响的,因为等待按键释放时这个死循环被卡住了,相当于延长了蜂鸣器的某个节拍。不操作按键时,蜂鸣器的音量和预期一致。本此实验主要体验Keil5的使用、单片机软件架构和AT89C51的输入输出。

application.c

#include "led.h"
#include "key.h"
#include "buzzer.h"
#define MAX_VOL 20
#define MIN_VOL 1void main(){unsigned char volume = 10;//周期计数,忽略键盘扫描,用于蜂鸣器控制//如volume = 10,即20个周期有10个输出高电平unsigned char cycle = 0;while(1){unsigned char key_site = scan_keyboard(); //扫描按键状态char led_status = -1;cycle++;switch(key_site){case 0:                                      //按键一被按了一次led_status = get_led_status(0);if(led_status == 0){led_operate(0,1);               //D1状态改变}else if(led_status == 1){led_operate(0,0);               //D1状态改变}break;case 1:led_status = get_led_status(1);if(led_status == 0){led_operate(1,1);               //D2状态改变}else if(led_status == 1){led_operate(1,0);               //D2状态改变}break;case 2:led_status = get_led_status(2);if(led_status == 0){led_operate(2,1);               //D3状态改变}else if(led_status == 1){led_operate(2,0);               //D3状态改变}break;case 3:led_status = get_led_status(3);if(led_status == 0){led_operate(3,1);               //D4状态改变}else if(led_status == 1){led_operate(3,0);               //D4状态改变}break;case 4:                                      //蜂鸣器音量+if(volume < MAX_VOL){volume++;}break;case 5:                                      //蜂鸣器音量-if(volume > MIN_VOL){volume--;}break;default:break;}if(cycle >= 0){buzzer_open();}if(cycle >= volume){buzzer_off();}if(cycle >= (MAX_VOL+1)){cycle = 0;}}
}

八、项目编译并仿真

1、点击魔法棒 --- OutPut --- 创建HEX文件 

 2、点击编译,看信息确认编译是否成功,如果失败则根据信息找出原因

 3、编译成功后会在项目目录的Objects文件夹下生成.hex文件

 4、Protues如果在运行需要先停止仿真,双击AT89C51芯片,在弹出框中Program File一项选中刚才的那个.hex文件。点击“确定”,再次启动仿真,运行的就是我们刚才编写的程序了。

从51到ARM裸机开发实验(003) AT89C51 GPIO实验相关推荐

  1. 从51到ARM裸机开发实验(006)Exynos4412 GPIO实验

    本次实验基于Tiny4412开发板,开发板上有四个可编程控制的LED.四个按键.来实现这样一种场景:每个按键控制一盏LED,每按一次则对应的LED状态发生改变,按一次开灯,再按一次关灯.核心控制为三星 ...

  2. 从51到ARM裸机开发实验(002) Keil5的安装与配置

    Keil可以说是单片机开发最好用的集成开发工具了,支持从51到ARM7.ARM9.Cortex-M.Cortex-R为内核的大量ARM系列单片机.Keil分C51版.MDK版等,如果想要共存需要做一些 ...

  3. ARM裸机开发——双机异步串行通信

    写在前面  本报告因为期末将至,后续还需要完成一次课程设计,故本次实验较为简单,完成的时间也非常匆忙,故文章内容较为单薄,也可能有着更多疏忽之处,还望大家海涵. 1. 项目任务  1) 利用S3C24 ...

  4. ARM裸机开发:主频与时钟

    文章目录 ARM裸机开发:主频与时钟 一.时钟系统 1.1 外部时钟电路 1.2 7路PLL时钟源 1.3 时钟树概览 二.时钟配置 2.1 内核时钟设置 2.2 PFD时钟设置 2.3 AHB.IP ...

  5. Linux学习——总结ARM裸机开发步骤

    本文仅介绍一些基础概念. 我们常用的开发平台是在x86,这个是Intel推出的架构.在x86平台上的开发步骤,不管是什么软件,无外乎建立工程->写代码->保存->编译->运行, ...

  6. ARM裸机开发——Linux环境搭建和LED灯闪烁实验

    写在前面  本文为学校开展的嵌入式系统设计课程,本文主要是记录课程中的相关作业与学习记录,本课程采用了S3C2440A嵌入式系统开发板,由于主要以学校开展课程为主,本文内容可能有一定的课程资料辅助以及 ...

  7. 基于Cortex-A7架构的嵌入式linux ARM裸机开发<2>——LED灯闪烁(C版本)

    文章目录 一.利用汇编初始化C环境 二.C语言部分实验程序编写 三.Makefile程序编写 四.链接脚本程序编写 五.编译及烧录 利用汇编编写LED灯点亮程序我们已经实现过了,所以这里不再着重讲解基 ...

  8. ARM裸机开发篇3:ARM汇编语言程序设计

    写在前面: 本文章为<ARM Cortex-A7裸机开发篇>系列中的一篇,全系列总计11篇.笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板). 针对FS-MP ...

  9. ARM裸机开发篇2:ARM微处理器指令系统

    目录 ARM微处理器指令系统 ARM处理器寻址方式 数据处理指令寻址方式 内存访问指令寻址方式 ARM处理器指令集 数据操作指令 乘法指令 Load/Store指令 跳转指令 状态操作指令 协处理器指 ...

最新文章

  1. 微信小程序页面之间数据传递
  2. ASP.NET格式化日期
  3. 2016年云巴产品更新合集
  4. 浮动元素的display属性
  5. 使用Python和OpenCV检测图片上的条形码
  6. 智能计米器jk76怎么安装_智能电视怎么安装软件?详细教程一学就会
  7. Zend Optimizer 相关报错收集
  8. phpstorm如何进行文件或者文件夹重命名
  9. adb指令禁用软件_技巧 | adb助你华为手机免ROOT卸载预装软件
  10. ajax 刷新 保持原位置_JavaEE之Ajax第一课
  11. 京东物流将收购德邦股份66.49%股份
  12. 搜索场景下的智能实体推荐
  13. 使用JMeter建立接口测试
  14. 二、Nginx 反向代理配置初学个人理解
  15. 全网唯一, MATLAB绘制好看的弦图
  16. 百度离线地图-Vue
  17. VUE根据url下载文件
  18. 202012798范明霞的博客
  19. AtCoder Beginner Contest 266(C- G)「判凸包」「dp」「期望」「基环树」「组合数」
  20. 疑难杂症共济失调怎么治疗恢复?

热门文章

  1. 一个未经压缩的图片(bmp)的大小
  2. 电商类小程序开发审核及其他注意事项
  3. “人类先锋”点亮物联网灯塔
  4. Anaconda3 +pycharm详细安装教程(2023年)
  5. 如何在「星图地球开发者平台」搭建智慧气象综合分析和展示平台?
  6. poj2083分图形-c++ dfs
  7. java+vue家政公司服务预约网站系统springboot+vue
  8. 不管你在几线城市,这几个公众号请收好!
  9. HashMap嵌套HashMap之遍历
  10. i510400f和r53600 对比哪个好