Arduino代码机制-IO
2016-02-20 19:24
603 查看
AVR单片机中有三种存储器,Flash, RAM, EEPROM。这篇讨论的是RAM。
RAM地址是16位的,最多能寻址64k字节的地址空间。要进行RAM扩展的话,最多能扩展多大内存呢?
16位地址寻址能力是64k字节,其中已经编址了0x2200的地址空间,只能扩展剩余的地址空间,因此最多能扩展的内存为0xffff - 0x21ff = 0xde00的内存。如果使用64kB的存储器来扩展的话,那么存储器的高字节部分将会访问不到。
第一个问题,将端口C全部设置为输出的话,直接对寄存器操作代码将会很简单,而且效率很高:
如果使用arduino的函数的话,将会调用8遍pinMode:
再考虑到pinMode函数里还需要读取Flash等等操作,效率就低了很多。因此有时候直接操作寄存器还是必要的。
在上面给寄存器DDRC赋值的操作是不是很方便?但是为何给寄存器名字赋值就是给这个寄存器赋值了呢?而我们应该给这个寄存器的那一块内存赋值才行。我们应该关心一下DDRC到底是什么。
如果知道DDRC的地址的话,假如是addr,那么给DDRC赋值就可以这样:
好了,如果知道每一个寄存器的地址,再将寄存器的名字宏定义为*addr,就像这样:
那么开发者就可以用寄存器名字来进行寄存器的读取操作了。
实际上的就是这样的,但要复杂一些。
AVR的单片机存在两种编址方式,一种是从通用寄存器开始编址,就像上面说的那样,那么这时候I/O寄存器的地址就是从0x20开始。另一种是通用寄存器不编址,而从I/O寄存器开始编址,这时候I/O寄存器的地址就是从0开始。不同型号单片机采用这两种编址方式之一。为了适应着两种情况,定义了寄存器起始地址宏:
在预编译时期,根据不同型号的单片机确定起始地址。
这样,我们就能认为所有单片机的I/O寄存器都是从0开始编址,寄存器的真实地址为(addr + 起始地址)。
又定义了读取内存内容的宏:
分别用来读取一个字节、一个字、两个字。
又定义了宏SFR_IO,这个宏有两个作用,计算真实地址和取真实地址中内容:
在将寄存器名称定义为SFR_IO和地址,就行了。例如:
经过几次宏定义,就能够简单的操作寄存器啦。
源代码参考:
hardware\tools\avr\avr\include\avr\io.h
hardware\tools\avr\avr\include\avr\sfr_defs.h
RAM编址
AVR单片机的RAM编址是这样的,最先的是32个通用寄存器,然后是64个I/O寄存器,然后是扩展I/O寄存器,最后才是Internal SRAM。以ATmega2560为例,内存为8192字节。通用寄存器地址范围是0x00到0x1f,I/O寄存器地址范围是0x20到0x5f,扩展I/O寄存器地址范围是0x60到0x1ff,内存地址范围是0x200到0x2200。如图所示。图为ATmega2560的datasheet中内容。RAM地址是16位的,最多能寻址64k字节的地址空间。要进行RAM扩展的话,最多能扩展多大内存呢?
16位地址寻址能力是64k字节,其中已经编址了0x2200的地址空间,只能扩展剩余的地址空间,因此最多能扩展的内存为0xffff - 0x21ff = 0xde00的内存。如果使用64kB的存储器来扩展的话,那么存储器的高字节部分将会访问不到。
I/O寄存器
尽管Arduino提供了大量的函数和宏,而且大部分效率很高且使用安全,使得开发者不需要关心底层的硬件。但也会有一些特定的情况,Arduino做的不好的。例如要将某个端口全部设置为输出,例如用某个端口并行传输数据。这时候,直接对寄存器操作就比用arduino函数效率高得多。第一个问题,将端口C全部设置为输出的话,直接对寄存器操作代码将会很简单,而且效率很高:
DDRC = 0xFF;
如果使用arduino的函数的话,将会调用8遍pinMode:
pinMode(37, OUTPUT); pinMode(36, OUTPUT); pinMode(35, OUTPUT); pinMode(34, OUTPUT); pinMode(33, OUTPUT); pinMode(32, OUTPUT); pinMode(31, OUTPUT); pinMode(30, OUTPUT);
再考虑到pinMode函数里还需要读取Flash等等操作,效率就低了很多。因此有时候直接操作寄存器还是必要的。
在上面给寄存器DDRC赋值的操作是不是很方便?但是为何给寄存器名字赋值就是给这个寄存器赋值了呢?而我们应该给这个寄存器的那一块内存赋值才行。我们应该关心一下DDRC到底是什么。
如果知道DDRC的地址的话,假如是addr,那么给DDRC赋值就可以这样:
*addr = 0xff;
好了,如果知道每一个寄存器的地址,再将寄存器的名字宏定义为*addr,就像这样:
#define /*寄存器名称*/ *(/*地址*/)
那么开发者就可以用寄存器名字来进行寄存器的读取操作了。
实际上的就是这样的,但要复杂一些。
AVR的单片机存在两种编址方式,一种是从通用寄存器开始编址,就像上面说的那样,那么这时候I/O寄存器的地址就是从0x20开始。另一种是通用寄存器不编址,而从I/O寄存器开始编址,这时候I/O寄存器的地址就是从0开始。不同型号单片机采用这两种编址方式之一。为了适应着两种情况,定义了寄存器起始地址宏:
#ifndef __SFR_OFFSET # if __AVR_ARCH__ >= 100 # define __SFR_OFFSET 0x00 # else # define __SFR_OFFSET 0x20 # endif #endif
在预编译时期,根据不同型号的单片机确定起始地址。
这样,我们就能认为所有单片机的I/O寄存器都是从0开始编址,寄存器的真实地址为(addr + 起始地址)。
又定义了读取内存内容的宏:
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr)) #define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr)) #define _MMIO_DWORD(mem_addr) (*(volatile uint32_t *)(mem_addr))
分别用来读取一个字节、一个字、两个字。
又定义了宏SFR_IO,这个宏有两个作用,计算真实地址和取真实地址中内容:
#if _SFR_ASM_COMPAT #define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET) #define _SFR_IO16(io_addr) ((io_addr) + __SFR_OFFSET) #else #define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET) #define _SFR_IO16(io_addr) _MMIO_WORD((io_addr) + __SFR_OFFSET) #endif
在将寄存器名称定义为SFR_IO和地址,就行了。例如:
#define PINA _SFR_IO8(0X00)
经过几次宏定义,就能够简单的操作寄存器啦。
源代码参考:
hardware\tools\avr\avr\include\avr\io.h
hardware\tools\avr\avr\include\avr\sfr_defs.h
相关文章推荐
- android小问题--------------------SQLiteDatabase.insert(table, nullColumnHack, values)参数
- scrapy 爬网站 显示 Filtered offsite request to 错误.
- 交换Button中图片与文字的左右位置
- UILabel在Autolayout中的多行显示/动态高度
- 循环中 break 与 continue 的区别
- 分享一款很好的视频处理软件 会声会影X8(huishenghuiyingx8-trial_x64)
- UITableView
- CodeForces 622A Infinite Sequence
- EasyUI系列学习(九)-Panel(面板)
- uiautomatorviewer获取当前屏幕中的各个元素信息包括类名
- 1041. Be Unique (20)
- 【UER #1】跳蚤OS(Trie)
- php中$_REQUEST、$_POST、$_GET的区别和联系小结
- 求php中的request详细用法
- CodeForces 626A Robot Sequence
- RequireJS 与 SeaJS 的异同
- the behavior of the UICollectionViewFlowLayout is not defined because:
- POJ 2299 Ultra-QuickSort(树状数组+离散化)
- 制作 Nine-Patch 图片
- UIScrollView