鱼C论坛

 找回密码
 立即注册
查看: 6413|回复: 17

[技术交流] AVR单片机IO口的位操作(类似于DSP)---鱼C首发

[复制链接]
回帖奖励 220 鱼币 回复本帖可获得 10 鱼币奖励! 每人限 1 次
发表于 2015-11-2 21:47:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 沉思的牛 于 2015-11-3 11:24 编辑

我写这篇帖子的目的是让大家更好的理解指针,位域,共用体,希望大家把它们应用得更好;
所有源代码我会发出来,再后面的就不全部贴出来了;

用过51单片机的同学都知道可以用sbit定义一个位变量,从而操作一个管脚。
比如:
  1. sbit LED0 = P0^0; //定义一个位变量,操作一个管脚
复制代码


再比如DSP的操作方式:
  1. //设置GPIOD5管脚为高电平
  2. GpioDataRegs.GPDDAT.bit.GPIOD5 = 1;
复制代码
  

以下我们单片机用ATMEGA128A,开发环境用Atmel Studio6.0为例子。

在AVR开发中有个头文件里面有个BIT()的宏定义,但是用这个需要不停地取反,或等操作,也不是很爽。
其实AVR的IO口是可以位寻址的,DataSheet里面有说到(汇编语言);

现在我们想把这种操作方式变成DSP这样,
既可以操作一个管脚,又可以操作整个端口。是不是爽歪歪 131.jpg


从DSP的操作方式可以看到,其实它就是用了 位域共用体
我想初学C语言的同学都有一个疑问,位域和共用体特么到底用来干什么 6.png ,其实我是学了DSP过后才发现位域和共用体这么流弊~! 27.png

/**********************************华丽的分割线*******************************/

在写程序之前我们必须要弄懂的一个问题:
        我们是怎么利用C语言来进行对AVR IO口进行操作的?
        没错! 那就是万能的指针。

我们先从官方的代码分析
  1. #define DDRA    _SFR_IO8(0x1A)   //先打开DDRA端口的定义看看,居然还有宏定义,再继续翻 _SFR_IO8

  2. //打开看到居然还有宏定义,再继续看_MMIO_BYTE,还有__SFR_OFFSET是什么鬼?(最后再说)
  3. #define _SFR_IO8(io_addr)   _MMIO_BYTE((io_addr) + __SFR_OFFSET)

  4. #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))  这个*号当然就是解引用,就是对这个地址指向的内存操作;


  1. // __SFR_OFFSET官方定义,其实就是一个地址的偏移,保证兼容性
  2. #ifndef __SFR_OFFSET
  3. /* Define as 0 before including this file for compatibility with old asm
  4.    sources that don't subtract __SFR_OFFSET from symbolic I/O addresses.  */
  5. #  if __AVR_ARCH__ >= 100
  6. #    define __SFR_OFFSET 0x00
  7. #  else
  8. #    define __SFR_OFFSET 0x20
  9. #  endif
  10. #endif
复制代码


最后说一下volatile关键字,其实就是为了不让编译器优化volatile修饰的代码,具体解释去百度。



既然知道了为什么良辰不介意陪大家玩玩~ u=3839753832,1303918913&fm=15&gp=0.jpg
DDRA的地址 = 0x1A
PORTA的地址 = 0x1B



  1. //我们写这段代码来控制PA口
  2. (*(volatile unsigned char *)(0x1A)) = 0xff;   //相当于DDRA=0xff;
  3. (*(volatile unsigned char *)(0x1B)) = 0x55;  //相当于PORTA = 0x55;     
复制代码

在你的PA口接上LED看看效果,是不是能控制了!如果能,并且你已经理解的话我们继续往下!~

/**********************************华丽的分割线*******************************/


总体思想如下:
现在我们就来详细的讲解位域和共用体的用法:
我们可以用位域把IO端口分成8个域,每个域占1个位;
然后在用一个位域把IO端口分成1个域,占8个位。
最后把这两个放到共用体来操作。

详细的内存结构如图:
附件下载: AVR_GPIO内存图.pdf (88.26 KB, 下载次数: 7)
AVR_GPIO.jpg

/**********************************华丽的分割线*******************************/

这些代码都是AVR_GPIO.h文件里面的;
内存结构都有了,接下来我们就要按照这个结构来写程序了!
我们应该从右往左写过去:
1.根据内存结构图我们先建立两个位域:
  1. //这个位域用来进行位操作,看内存结构图,我想你已经知道是哪个了
  2. struct GpioBits
  3. {
  4.         volatile unsigned char Pin0 : 1;    //每个管脚占一位,就可以操作每个管脚了
  5.         volatile unsigned char Pin1 : 1;
  6.         volatile unsigned char Pin2 : 1;
  7.         volatile unsigned char Pin3 : 1;
  8.         volatile unsigned char Pin4 : 1;
  9.         volatile unsigned char Pin5 : 1;
  10.         volatile unsigned char Pin6 : 1;
  11.         volatile unsigned char Pin7 : 1;
  12. };
复制代码

  1. //这个位域用来操作整个端口
  2. struct GpioByte
  3. {
  4.         volatile unsigned char Byte : 8 ; //这个位域占8位,方便操作一个字节,一个端口8个管脚
  5. };
复制代码



2.位域有了,看内存结构图,接下来应该是建立共用体了。
  1. //共用体用来选择位操作和字节操作
  2. union GpioByteOrBitSelect
  3. {
  4.         volatile struct GpioByte *All;    //操作整个端口(字节)
  5.         volatile struct GpioBits *Bit;    //操作位
  6. };
复制代码



3.现在就是要建立最大的那一个结构体了,包含所有的IO口,我们先键立PA口的为例子,后面的大家自己建立;
我会把源代码发布出来。
  1. //这个就是最后的结构体类型定义了,定义了所有IO口
  2. struct GpioRegisters
  3. {
  4.         volatile union GpioByteOrBitSelect *GpioDDRA;     //我们先以PA口作为例子
  5.         volatile union GpioByteOrBitSelect *GpioPORTA;
  6.         volatile union GpioByteOrBitSelect *GpioPINA;

  7.         //其他的io各位自己定义,我源代码里面也有
  8.        
  9. };
复制代码


这些都是AVR_GPIO.C文件里面的
4.上面我们都是类型定义,最重要的下面,要建立内存实体,就是变量;
现在是最关键的一步,就是要让共用体里面的指针要指向io口的地址

  1. //以下初始化为io口地址,强转为位域指针类型
  2. //建立GpioByteOrBitSelect类型的共用体实体为gpioSelectDDRA,然后赋初值为DDRA的内存地址
  3. //后面的都是类似
  4. volatile union GpioByteOrBitSelect gpioSelectDDRA={(struct GpioByte *)(GPIO_DDRA_ADDRESS+__SFR_OFFSET)};   
  5. volatile union GpioByteOrBitSelect gpioSelectPORTA={(struct GpioByte *)(GPIO_PORTA_ADDRESS+__SFR_OFFSET)};
  6. volatile union GpioByteOrBitSelect gpioSelectPINA={(struct GpioByte *)(GPIO_PINA_ADDRESS+__SFR_OFFSET)};
复制代码


io地址定义
  1. //关于IO地址定义
  2. #define GPIO_DDRA_ADDRESS    0x1a
  3. #define GPIO_PORTA_ADDRESS   0x1b
  4. #define GPIO_PINA_ADDRESS    0x19
复制代码


5.然后建立GpioRegisters结构体的内存实体(变量)

  1. volatile struct GpioRegisters gpios ={
  2.          &gpioSelectDDRA,      //指向gpioSelectDDRA共用体,看我们的内存结构图
  3.          &gpioSelectPORTA,    //指向gpioSelectPORTA共用体
  4.          &gpioSelectPINA       //指向gpioSelectPINA共用体

  5.         //后面的IO口自己添加
  6. };
复制代码


6.最后方便我们使用起来看起都是指针操作,我们再建立一个GpioRegisters结构体指针;
其实你不用指针也可以,我有强迫症,要全部看起来用->指针符号才爽!!
  1. //这个指针当然要指向GpioRegisters定义的gpios实体
  2. volatile struct GpioRegisters *Gpios  = &gpios;
复制代码


7.最后在编译一下吧;

然后测试一下:
Gpios->GpioDDRA->All->Byte = 0xff;
Gpios->GpioPORTA->All->Byte = 0xff;
Gpios->GpioPORTA->Bit->Pin0 = 0;


/**********************************华丽的分割线*******************************/

最后祝你成功;
当然我们这样弄肯定是有利有弊的:
好处:我们使用io口更方便了。
弊端:我们建立的指针实体会占用一定的内存空间,不过对于RAM大一点的MCU来说也没有什么了。
/**********************************华丽的分割线*******************************/


源代码附上:
AVR_GPIO_DEMO.rar (157.99 KB, 下载次数: 24)

如果各位觉得还不够爽,后期我再加录个视频。

评分

参与人数 1荣誉 +10 鱼币 +10 贡献 +10 收起 理由
康小泡 + 10 + 10 + 10 支持楼主!

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2015-11-4 21:51:59 | 显示全部楼层

回帖奖励 +10 鱼币

文章写的非常的NICE,不错不错~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-11-4 22:04:44 | 显示全部楼层
竹林小溪 发表于 2015-11-4 21:51
文章写的非常的NICE,不错不错~

谢谢支持哦~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-11-5 09:55:11 | 显示全部楼层

回帖奖励 +10 鱼币

是单片机耶,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-11-5 10:49:37 | 显示全部楼层

必须是单片机!
这算是单片机IO口的高级应用吧
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-11-5 17:10:43 | 显示全部楼层

回帖奖励 +10 鱼币

我目前只见过一个共同体的应用,似乎是VC的串口通讯。。。反正没咋懂。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-11-7 14:37:17 | 显示全部楼层
wangchunchun 发表于 2015-11-5 17:10
我目前只见过一个共同体的应用,似乎是VC的串口通讯。。。反正没咋懂。

你要是把我这个看懂了,你就能熟练的应用共用体和位域了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-11-7 22:19:44 | 显示全部楼层

回帖奖励 +10 鱼币

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2015-11-10 14:38:33 | 显示全部楼层
沉思的牛 发表于 2015-11-7 14:37
你要是把我这个看懂了,你就能熟练的应用共用体和位域了

嗯,谢谢,楼主,依然看懂。:victory:
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-11-10 17:07:30 | 显示全部楼层
wangchunchun 发表于 2015-11-10 14:38
嗯,谢谢,楼主,依然看懂。

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2016-7-20 23:32:12 | 显示全部楼层

回帖奖励 +10 鱼币

学习了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2016-7-20 23:32:45 | 显示全部楼层
谢谢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2016-11-15 22:13:37 | 显示全部楼层

回帖奖励 +10 鱼币

来个视频把
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

头像被屏蔽
发表于 2018-9-20 21:02:54 | 显示全部楼层

回帖奖励 +10 鱼币

提示: 作者被禁止或删除 内容自动屏蔽
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-11-13 18:50:14 | 显示全部楼层

回帖奖励 +10 鱼币

硬件好难的
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-11-22 17:30:50 | 显示全部楼层

回帖奖励 +10 鱼币

膜拜大佬
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-8-26 18:27:28 | 显示全部楼层

回帖奖励 +10 鱼币

强啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-9-2 18:00:50 | 显示全部楼层

回帖奖励 +10 鱼币

牛人, 初学msp430f5529开发板,用你这个位域,卡在GPIO_P1DIR_ADDRESS + __SFR_OFFSET这一句。
提示'__SFR_OFFSET' undeclared here (not in a function)。能详细解释一下吗?谢谢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-18 17:04

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表