[AVR单片机]

AVR单片机IO口的位操作(类似于DSP)

[复制链接]
3107|8
手机看帖
扫描二维码
随时随地手机跟帖
沉思的牛|  楼主 | 2015-11-3 11:11 | 显示全部楼层 |阅读模式
本帖最后由 沉思的牛 于 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)) 黄色部分是传进去的参数,从这个字面意思可以知道是内存的地址,
                                                                                         //其实传进去的就是io口的地址;
(*(volatile uint8_t *)(mem_addr))  再来看红色这部分是什么意思: 可以看出是类型强转,把这个地址值强转为指针类型;

(*(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)

相关帖子

huangxz| | 2015-11-3 13:50 | 显示全部楼层
搞的那么麻烦,还不如直接用汇编来的快啊.
sbi,cbi直接搞定了.

使用特权

评论回复
沉思的牛|  楼主 | 2015-11-3 14:26 | 显示全部楼层
huangxz 发表于 2015-11-3 13:50
搞的那么麻烦,还不如直接用汇编来的快啊.
sbi,cbi直接搞定了.

建立库文件确实麻烦了一点!
不过感觉这样用C语言写的时候还可以。

使用特权

评论回复
erian| | 2015-12-5 13:10 | 显示全部楼层
huangxz 发表于 2015-11-3 13:50
搞的那么麻烦,还不如直接用汇编来的快啊.
sbi,cbi直接搞定了.

这绝对不是麻不麻烦的问题。
这是对语言的理解,和熟练。

顶楼主。

使用特权

评论回复
沉思的牛|  楼主 | 2015-12-6 09:41 | 显示全部楼层
erian 发表于 2015-12-5 13:10
这绝对不是麻不麻烦的问题。
这是对语言的理解,和熟练。

谢谢支持!
写这个库有助于C语言的理解和提高!

使用特权

评论回复
ccxlslr| | 2015-12-13 20:21 | 显示全部楼层
位域是C语言的概念,与汇编操作的位寻址没有直接的关系。不能乱扯。至于编译器是否编译成位操作也是不一定的。至于可以像Keil51的写法。有些编译器也是可以的。
位域也可以对内存操作。很多时候,位域也是用与或非来完成的,可以多看看汇编就清楚了。本质上是差不多的。位域对不同的编译器,对位的处理方式并不相同,不能保证跨平台的移植。(不是说不能用,有些编译器,也是用位域来实现的)
想简单操作。可以定义宏也是个不错的选择,可移植性也不错。如:
#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))       ///< 位取反,直接操作指定的位

使用特权

评论回复
沉思的牛|  楼主 | 2015-12-15 14:52 | 显示全部楼层
ccxlslr 发表于 2015-12-13 20:21
位域是C语言的概念,与汇编操作的位寻址没有直接的关系。不能乱扯。至于编译器是否编译成位操作也是不一定 ...

是的,位域和位寻址是不同的概念;在这里只是提一下。
在VS2010中反汇编看位域其实就是位操作

使用特权

评论回复
xtuwz| | 2017-9-27 17:05 | 显示全部楼层
谢谢啦,正需要,用来写上位机可以参考么

使用特权

评论回复
buaahjm| | 2020-2-11 23:21 | 显示全部楼层
厉害了

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

3

主题

63

帖子

4

粉丝