AVR单片机教程——数字IO寄存器

2019-11-30来源: eefocus关键字:AVR  单片机  数字IO寄存器

前两篇教程中我们学习了LED、按键、开关的基本原理,数字输入输出的使用以及两者之间的关系。我们用到了 pin_mode 、 pin_read 和 pin_write 这三个函数,实际上它们离最底层(至少是单片机制造商允许我们接触到的最底层)就只有一步之遥了。而学单片机要是不了解一点底层,那跟Arduino玩家还有什么区别?(为防止有忠实的Arduino粉丝骂我,我得承认还是有一小部分Arduino玩家是知道本篇教程所介绍内容的。)根本不好意思说自己学过单片机好吧。这所谓的最底层,就是数字IO寄存器了。


在开始之前,你需要下载两份文档:


单片机的数据手册。官网链接极慢,我在国内平台上传了一份,在本篇教程写成之时是最新的。


开发板的原理图。本应在教程之初就放出来,但事实证明没有原理图也不影响使用。现在是肯定需要的。


等等,你可能还不知道寄存器是什么。那我们就从寄存器开始吧。


寄存器是一类CPU内部的存储器,分为通用寄存器与特殊功能寄存器(8086对特殊功能寄存器还有细分)。通用寄存器,顾名思义是通用的,可以存储操作数、运算结果、内存地址等数据,在使用C语言编程时,一般不直接接触通用寄存器,而由编译器负责安排其使用。特殊功能寄存器有特定的功能,一些作用于CPU内部,如PC存放下一条指令的地址,SP记录内存中栈顶的位置(现在无需了解这些);另一些与IO模组相连接,单片机程序通过这类寄存器来控制各种外设,我们今天要学习的数字信号IO寄存器就属于这一类,并且应该是其中最简单的了。


我们使用的单片机的型号是ATmega324PA,它有多种封装,引脚(pin)数不尽相同,但都有32个通用输入输出(GPIO)引脚。由于AVR架构是8位字长的,CPU一次处理1位数据和8位数据所需的时间是一样的,这32个引脚被组织为4个端口(port),分别是PA、PB、PC和PD。


在AVR架构tiny与mega系列的单片机中,每个端口都有3个寄存器控制数字信号IO,分别是PORTx、DDRx和PINx。这里的x是A、B、C或D,由于这4个端口在数字IO方面完全相同,就把它们合并起来讲。相应地,对于每个引脚Pxn,有PORTxn、DDxn(没有R)和PINxn三个bit控制其数字IO。


DDxn控制引脚方向:当DDxn为1时,Pxn为输出;当DDxn为0时,Pxn为输入。


当Pxn为输入时,如果PORTxn为1,则该引脚通过一个上拉电阻连接到VCC;否则引脚悬空。


当Pxn为输出时,如果PORTxn为1,引脚输出高电平;否则输出低电平。


PINxn的值为Pxn引脚的电平。如果给PINxn写入1,PORTxn的值会翻转。


还有很多细节问题,如MCUCR寄存器中PUD位的功能、复位后寄存器的值、输入输出切换的方法、读取引脚电平的延迟、未连接引脚的处理方法等,留作今天的作业,阅读数据手册I/O-Ports一章中除Alternate Port Functions一节以外的内容(一共8页不到,不多吧),找出这些问题的答案,并以此为基础回答上一篇教程最后的问题。


讲了这么多,相信你也没记住多少,而且你也不知道去哪里用这些寄存器。


要使用寄存器,你需要在C语言程序中写 #include (在创建项目时自动生成的代码中就有),然后就可以使用 PORTA 、 DDRB 、 PINC 等寄存器了。它们是宏定义,你不必去探究它们展开后是怎样的,只需知道这些宏可以读取,可以赋值,可以位操作,就像 uint8_t 类型变量一样。


但是诸如 PORTA0 和 DDB7 等宏定义却不代表寄存器上的那一位,它们实际上就是字面值常量,如 PORTAx 的意义是寄存器 PORTA 的第x位(第0位为最低位,第7位为最高位),它的值就是x。因此,直接对这些宏复制是不正确的(不仅意义不正确,编译也不会通过)。


在开发板的库函数中的 提供了包含几个用于位操作的宏函数。我们先按照手册来用,稍后来看它们是如何实现的。


我们先返璞归真一下,回到最初的例子,点亮一个LED,不过这次我们不再使用 提供的函数,而是直接操作寄存器。


先点亮红色LED吧。在原理图的第2页左上角,红色LED通过一个电阻连接到网络LED0,而在第1页中LED0连接的是单片机PC4引脚,因此我们需要让PC4引脚输出高电平。回到上面看一下三个寄存器的功能,输出高电平需要DDxn和PORTxn同时为1。这里把x和n分别用C和4带入,即我们要让DDC4和PORTC4为1。


将一个寄存器的一位置为1可以由 set_bit 实现。它需要两个参数,要操作的整型变量与表示第几位的整数。把DDC4置为1应该写 set_bit(DDRC, 4); ,4 可以用 DDC4 替换,这个定义就是这么用的。类似地也可以将PORTC4置为1。点亮红色LED的整个程序如下:


1 #include

2 #include

4 int main(void)

5 {

6     set_bit(DDRC , 4);

7     set_bit(PORTC, 4);

8 }


相信聪明的你已经知道闪烁和流水灯怎么写了。翻转输出电平可以使用 flip_bit(PORTC, 4); ,也可以使用 set_bit(PINC, 4); 。


下面来看数字输入。还是用第一个与按键相关的例子,让LED状态与按键保持一致,即按下亮起。


读取一个寄存器中的一位可以使用 read_bit。如果引脚上电平为高,read_bit 的运算结果非0(但不一定是布尔值1)。如果你没有忘记的话,按键按下时引脚电平为低,因此对读取引脚电平的结果取非才是按键是否按下。


在原理图中,按键一端连接在BTN0网络上,进而连接到单片机的PA4引脚。因此按键是否按下应该写为:!read_bit(PINA, 4) 。


在读取之前应该先把引脚配置为输入。尽管复位后默认为输入,在这个例子中没有必要向DDA4写0,但明确写出来可以让看这段代码的人(可能别人也可能是你自己)明白PA4是作输入的,这样做是一种良好的习惯。至于PORTA4,由于这一引脚在外部有连接上拉电阻,就没有必要启用内部上拉电阻了。


 1 #include

 2 #include

 3 

 4 int main(void)

 5 {

 6     reset_bit(DDRA, 4);

 7     set_bit(DDRC, 4);

 8     while (1)

 9     {

10         cond_bit(!read_bit(PINA, 4), PORTC, 4);

11     }

12 }


再结合按键动作的知识,你应该知道怎样直接通过寄存器操作来判断按键动作了吧。


顺便说一句,以上两个程序都不必在项目属性中给linker加上libee1库。虽然代码中使用了 ,但其中都是宏定义,与linker无关。


之前留了一个问题,就是位操作是如何实现的。以下为 中部分代码:


1 #define set_bit  (r, b) ((r) |=  (1u << (b)))

2 #define reset_bit(r ,b) ((r) &= ~(1u << (b)))

3 #define read_bit (r, b) ((r) &   (1u << (b)))

4 #define flip_bit (r, b) ((r) ^=  (1u << (b)))


写那么多括号是为了防止出现运算符优先级的问题。假设r就是一个寄存器,比如PORTC,b就是一个数字,比如4,也可以是一个变量,那么 (r) |= (1u << (b)) 就相当于 r = r | 1u << b (后缀u表示无符号数,位操作的运算数一般都是无符号数)。对于二进制表示下的每一位,如果不是第b位,那么位或运算符右边此位为0,运算结果等于左边,即r的这些为保持不变;对于第b位,右边此位为1,无论左边此位的值是多少,结果一定是1,即这一位被置1;这样就实现了将一位置为1的功能。


reset_bit 的实现还要绕一个弯。1u << b 是一个第b位为1,其余位为0的数,那么 ~(1u << b) ,即位与赋值号右边,是一个第b位为0而其余为都是1的数。仿照上面的分析可得,运算结果的第b位一定是0而其余位与r中原来的值相同。类似的分析也可应用于 flip_bit :两个bit进行异或运算的结果,若相同则为1,不同则为0;当一个运算数是1时,结果就是另一个运算数取反;当一个运算数是0时,结果与另一个运算数相同;因此 flip_bit 就使r的第b为取反而其他为不变。


以上是向寄存器中的位写入的操作。用于读取位的 read_bit 的原理也大致相同,用寄存器的值与 1u << b 相与,仅当第b位为1时结果是 1u << b ,这是个非零数,否则结果为0。read_bit 语句可以直接放在 if 语句的条件部分,但如果是根据其结果决定一个变量是否加1,不能直接加上其运算结果,可以转换成 bool 类型或用 if 语句判断。


这篇教程有点长。好好消化一下,然后把以前写过的程序用寄存器重新写一遍,以此巩固所学的知识。


从本教程开始至今,我们先了解了LED灯、按键、拨动开关、数字输入输出的使用方法,然后学习C语言位操作与数字IO寄存器,终于打通了一条从底层到应用的路。而网络上很多教程都是反过来讲的,即先介绍寄存器,然后直接通过寄存器来驱动LED、检测按键等,甚至有直接写诸如 DDRB |= 0x0C; 或 if (PINB & 0x40) 这样的代码的,初学者怎么看得明白?站在我的角度,我觉得以上都是常识,都不用讲,尽管我学习的时候也颇费周折(正是因为那些反过来的教程)。现在我站在初学者的角度,认为本教程的讲解顺序是更容易理解的。


我学习计算机之前,总对计算机抱有特殊的幻想,觉得它什么都能干,很神奇。现在这些想法都没有了,尤其是在学

[1] [2]
关键字:AVR  单片机  数字IO寄存器 编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic481606.html 本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:AVR单片机教程——数码管
下一篇:AVR单片机教程——数字输出

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

AVR开发 Arduino方法(四) 串行通信子系统
  Arduino UNO R3主处理器ATMega328P的串行通信子系统可以用于与计算机、外设或其他微控制器进行通信,它支持3种串行通信方式:通用同步/异步收发器,串行外设接口和两线串行接口。1. 通用同步/异步收发器  在串行通信中,波特率用来衡量传输速率的快慢,同步和异步的对象是波特率的时钟信号;同步通信的设备之间需要一条额外的时钟线,也因此同步方式可以提供更高的波特率;这里将以异步为例。  下面的示例可以使通过串口发送给Arduino的数据回显到串口监视器上: 1 // SerialEcho.ino 2 char data; 3  4 void
发表于 2019-12-05
AVR开发 Arduino方法(四) 串行通信子系统
AVR开发 Arduino方法(三) 定时/计数器子系统
Arduino UNO R3的主处理器ATMega328P拥有3个定时/计数器,它们分别是Timer0,Timer1和Timer2;它们都通过对来自内部或外部的脉冲信号进行计数的方式完成基本的定时/计数功能以及一些其他的功能。Timer0和Timer2是8位定时/计时器,Timer1是16位定时/计数器;下面以Timer2为例讨论定时/计数器子系统的典型应用,这些内容同样适用于Timer0和Timer1。1. 精准延时在前面的例子中,已经使用了一些与精准延时相关的Arduino库函数:   delay(ms):延迟一段时间  ms:延迟的时长,单位是毫秒 请注意,上面的Arduino
发表于 2019-12-05
AVR开发 Arduino方法(三) 定时/计数器子系统
AVR开发 Arduino方法(二) 中断子系统
在了解中断子系统之前,首先要了解中断的概念。你正在看书,这时电话响了,你会怎么做呢?相信大多数人会这样:先标记看到的位置,接完电话回来后继续阅读。这就是一个现实生活中中断的例子,我们把“电话响了”成为中断源。Arduino UNO R3的主处理器ATMega328P拥有26个中断源,如下表所示:向量号程序地址中断源中断定义中断服务程序名称10x0000RESET外部电平复位,上电复位,掉电检测复位,看门狗复位20x0002INT0外部中断请求0INT0_vect30x0004INT1外部中断请求1INT1_vect40x0006PCINT0引脚电平变化中断请求0PCINT0_vect50x0008PCINT1引脚
发表于 2019-12-05
AVR开发 Arduino方法(一) 端口子系统
  Arduino UNO R3的主处理器ATMega328P上有3个8位的输入/输出端口,分别是PB,PC和PD。Arduino IDE提供的Blink示例可以帮助我们了解端口的数字输出功能:  1 // Blink.ino 2 int led = 13; 3  4 void setup() { 5   pinMode(led, OUTPUT); 6 } 7  8 void loop() { 9   digitalWrite(led, HIGH);10  &nbs
发表于 2019-12-05
AVR开发 Arduino方法(一) 端口子系统
AVR开发 Arduino方法(附一) 工具链与调试技术
开“AVR Source Code”和“AVR Variables”对话框,从中可以查看程序源代码以及变量值的情况:从“AVR Source Code”对话框中找到主函数,找到setup()函数和loop()函数的调用语句,双击打上断点,运行到断点处,就可以开始调试了:(2) 使用Visual Studio进行硬件调试安装带有C++组件2012或更新版本的Visual Studio后,在http://www.visualmicro.com/page/Arduino-Visual-Studio-Downloads.aspx中下载Visual 
发表于 2019-12-05
AVR开发 Arduino方法(附一) 工具链与调试技术
AVR+FPGA实现六路闭环电流控制程序
library ieee;use ieee.std_logic_1164.all;use ieee.std_logic_arith.all;use ieee.std_logic_unsigned.all;entity XTKZQ isport(    rst,clk        :    in        std_logic;    --时钟和复位信号,复位信号由AVR初始化手动给出    --avr 读写相关信号线    ale,rd
发表于 2019-12-05
小广播
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved