本帖最后由 沉思的牛 于 2015-11-3 11:20 编辑
不知道大家在实际开发过程中是怎么使用io口的,希望大家踊跃讨论。
所有源代码我会发出来,后面的就不全部贴出来了;
用过51单片机的同学都知道可以用sbit定义一个位变量,从而操作一个管脚。
比如:
- sbit LED0 = P0^0; //定义一个位变量,操作一个管脚
复制代码再比如DSP的操作方式:
- //设置GPIOD5管脚为高电平
- GpioDataRegs.GPDDAT.bit.GPIOD5 = 1;
复制代码
以下我们单片机用ATMEGA128A,开发环境用Atmel Studio6.0为例子。
在AVR开发中有个头文件里面有个BIT()的宏定义,但是用这个需要不停地取反,或等操作,也不是很爽。
其实AVR的IO口是可以位寻址的,DataSheet里面有说到(汇编语言);
现在我们想把这种操作方式变成DSP这样,既可以操作一个管脚,又可以操作整个端口。
是不是爽歪歪
从DSP的操作方式可以看到,其实它就是用了
位域 和
共用体,
(TI DSP是通过内存映射,我们暂且不谈)
初学C语言的同学都有一个疑问,位域和共用体特么到底用来干什么
其实我是学了DSP过后才发现位域和共用体这么流弊~!
。
/**********************************华丽的分割线*******************************/
在写程序之前我们必须要弄懂的一个问题:
我们是怎么利用C语言来进行对AVR IO口进行操作的?
没错! 那就是万能的指针。
我们先从官方的代码分析
- #define DDRA _SFR_IO8(0x1A) //先打开DDRA端口的定义看看,居然还有宏定义,再继续翻 _SFR_IO8
- //打开看到居然还有宏定义,再继续看_MMIO_BYTE,还有__SFR_OFFSET是什么鬼?(最后再说)
- #define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
- #define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr)) //_MMIO_BYTE宏的定义终于找到了
复制代码我们来分析一下上面的代码
(*(volatile uint8_t *)(
mem_addr)) 黄 {MOD}部分是传进去的参数,从这个字面意思可以知道是内存的地址,
//其实传进去的就是io口的地址;
(*
(volatile uint8_t *)(mem_addr)) 再来看红 {MOD}这部分是什么意思: 可以看出是类型强转,把这个地址值强转为指针类型;
(*(volatile
uint8_t *)(mem_addr)) //类型重定义typedef unsigned char uint8_t;可以看到uint8_t其实就是unsigned char
(
*(volatile uint8_t *)(mem_addr)) 这个*号当然就是解引用,就是对这个地址指向的内存操作;
__SFR_OFFSET官方定义,其实就是一个地址的偏移,保证兼容性
- // __SFR_OFFSET官方定义,其实就是一个地址的偏移,保证兼容性
- #ifndef __SFR_OFFSET
- /* Define as 0 before including this file for compatibility with old asm
- sources that don't subtract __SFR_OFFSET from symbolic I/O addresses. */
- # if __AVR_ARCH__ >= 100
- # define __SFR_OFFSET 0x00
- # else
- # define __SFR_OFFSET 0x20
- # endif
- #endif
复制代码
最后说一下volatile关键字,其实就是为了不让编译器优化volatile修饰的代码,具体解释去百度。
既然知道了为什么良辰不介意陪大家玩玩~
DDRA的地址 = 0x1A
PORTA的地址 = 0x1B
不要问我是怎么知道它的地址的!~
- //我们写这段代码来控制PA口
- (*(volatile unsigned char *)(0x1A)) = 0xff; //相当于DDRA=0xff;
- (*(volatile unsigned char *)(0x1B)) = 0x55; //相当于PORTA = 0x55;
复制代码
在你的PA口接上LED看看效果,是不是能控制了!如果能,并且你已经理解的话我们继续往下!~
/**********************************华丽的分割线*******************************/
总体思想如下:
现在我们就来详细的讲解位域和共用体的用法:
我们可以用位域把IO端口分成8个域,每个域占1个位;
然后在用一个位域把IO端口分成1个域,占8个位。
最后把这两个放到共用体来操作。
详细的内存结构如图:
附件下载:
AVR_GPIO内存图.pdf
(88.26 KB, 下载次数: 22)
2015-11-3 10:50 上传
点击文件名下载附件
AVR_GPIO
/**********************************华丽的分割线*******************************/
以下这些代码都是AVR_GPIO.h文件里面的:
内存结构都有了,接下来我们就要按照这个结构来写程序了!
我们应该从右往左写过去:
1.根据内存结构图我们先建立两个位域:
- //这个位域用来进行位操作,看内存结构图,我想你已经知道是哪个了
- struct GpioBits
- {
- volatile unsigned char Pin0 : 1; //每个管脚占一位,就可以操作每个管脚了
- volatile unsigned char Pin1 : 1;
- volatile unsigned char Pin2 : 1;
- volatile unsigned char Pin3 : 1;
- volatile unsigned char Pin4 : 1;
- volatile unsigned char Pin5 : 1;
- volatile unsigned char Pin6 : 1;
- volatile unsigned char Pin7 : 1;
- };
复制代码- //这个位域用来操作整个端口
- struct GpioByte
- {
- volatile unsigned char Byte : 8 ; //这个位域占8位,方便操作一个字节,一个端口8个管脚
- };
复制代码
2.位域有了,看内存结构图,接下来应该是建立共用体了。
- //共用体用来选择位操作和字节操作
- union GpioByteOrBitSelect
- {
- volatile struct GpioByte *All; //操作整个端口(字节)
- volatile struct GpioBits *Bit; //操作位
- };
复制代码
3.现在就是要建立最大的那一个结构体了,包含所有的IO口,我们先键立PA口的为例子,后面的大家自己建立;
我会把源代码发布出来。
- //这个就是最后的结构体类型定义了,定义了所有IO口
- struct GpioRegisters
- {
- volatile union GpioByteOrBitSelect *GpioDDRA; //我们先以PA口作为例子
- volatile union GpioByteOrBitSelect *GpioPORTA;
- volatile union GpioByteOrBitSelect *GpioPINA;
- //其他的io各位自己定义,我源代码里面也有
-
- };
复制代码
以下这些都是AVR_GPIO.C文件里面的:
4.上面我们都是类型定义,最重要的下面,要建立内存实体,就是变量;
现在是最关键的一步,就是要让共用体里面的指针要指向io口的地址
- //以下初始化为io口地址,强转为位域指针类型
- //建立GpioByteOrBitSelect类型的共用体实体为gpioSelectDDRA,然后赋初值为DDRA的内存地址
- //后面的都是类似
- volatile union GpioByteOrBitSelect gpioSelectDDRA={(struct GpioByte *)(GPIO_DDRA_ADDRESS+__SFR_OFFSET)};
- volatile union GpioByteOrBitSelect gpioSelectPORTA={(struct GpioByte *)(GPIO_PORTA_ADDRESS+__SFR_OFFSET)};
- volatile union GpioByteOrBitSelect gpioSelectPINA={(struct GpioByte *)(GPIO_PINA_ADDRESS+__SFR_OFFSET)};
复制代码
IO口地址宏定义
- #define GPIO_DDRA_ADDRESS 0x1a
- #define GPIO_PORTA_ADDRESS 0x1b
- #define GPIO_PINA_ADDRESS 0x19
复制代码
5.然后建立GpioRegisters结构体的内存实体(变量)
- volatile struct GpioRegisters gpios ={
- &gpioSelectDDRA, //指向gpioSelectDDRA共用体,看我们的内存结构图
- &gpioSelectPORTA, //指向gpioSelectPORTA共用体
- &gpioSelectPINA //指向gpioSelectPINA共用体
- //后面的IO口自己添加
- };
复制代码6.最后方便我们使用起来看起都是指针操作,我们再建立一个GpioRegisters结构体指针;
其实你不用指针也可以,强迫症!要全部看起来用->指针符号才爽!!
- //这个指针当然要指向GpioRegisters定义的gpios实体
- volatile struct GpioRegisters *Gpios = &gpios;
复制代码7.最后在编译一下吧;
然后测试一下:
- Gpios->GpioDDRA->All->Byte = 0xff;
- Gpios->GpioPORTA->All->Byte = 0xff;
- Gpios->GpioPORTA->Bit->Pin0 = 0;
复制代码
/**********************************华丽的分割线*******************************/
最后祝你成功;
当然我们这样弄肯定是有利有弊的:
利弊大家来讨论吧!~
/**********************************华丽的分割线*******************************/
源代码附上:
AVR_GPIO_DEMO.rar
(157.99 KB, 下载次数: 55)
2015-11-3 10:57 上传
点击文件名下载附件
sbi,cbi直接搞定了.
建立库文件确实麻烦了一点!
不过感觉这样用C语言写的时候还可以。
这绝对不是麻不麻烦的问题。
这是对语言的理解,和熟练。
顶楼主。
位域也可以对内存操作。很多时候,位域也是用与或非来完成的,可以多看看汇编就清楚了。本质上是差不多的。位域对不同的编译器,对位的处理方式并不相同,不能保证跨平台的移植。(不是说不能用,有些编译器,也是用位域来实现的)
想简单操作。可以定义宏也是个不错的选择,可移植性也不错。如:
#define B_V(BIT) (1 << (BIT)) ///< 将指定位置1
#define B_SET(LVALUE, BIT) (LVALUE |= B_V(BIT)) ///< 位置1,直接操作指定的位
#define B_CLR(LVALUE, BIT) (LVALUE &= ~B_V(BIT)) ///< 位清0,直接操作指定的位
#define B_REV(LVALUE, BIT) (LVALUE ^= B_V(BIT)) ///< 位取反,直接操作指定的位
是的,位域和位寻址是不同的概念;在这里只是提一下。
在VS2010中反汇编看位域其实就是位操作
一周热门 更多>