搜索
bottom↓
回复: 43

厉害了Word M0像M3位带一样操作M0的GPIO实现方法 原创可否申精

[复制链接]

出0入0汤圆

发表于 2017-7-11 16:43:31 | 显示全部楼层 |阅读模式
本帖最后由 lanlibo 于 2017-7-11 16:58 编辑

题目:厉害了Word M0 像M3位带一样操作M0的GPIO实现方法原创可否申精
老司机请调到实现方法!

老规矩,先解释一下位带(引用:http://blog.csdn.net/gaojinshan/article/details/11479929
    支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在 CM3 中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。
       位带操作的概念其实 30 年前就有了,那还是8051 单片机开创的先河,如今,CM3 将此能力进化,这里的位带操作是 8051 位寻址区的威力大幅加强版。
       CM3 使用如下术语来表示位带存储的相关地址:
              位带区:支持位带操作的地址区
              位带别名:对别名地址的访问最终作用到位带区的访问上(这中途有一个地址映射过程)
       在位带区中,每个比特都映射到别名地址区的一个字——这是只有 LSB 有效的字。当一个别名地址被访问时,会先把该地址变换成位带地址。对于读操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回。对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程。



       支持位带操作的两个内存区的范围是:
              0x2000_0000‐0x200F_FFFF(SRAM 区中的最低 1MB)
              0x4000_0000‐0x400F_FFFF(片上外设区中的最低 1MB)
       对 SRAM 位带区的某个比特,记它所在字节地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
              AliasAddr=0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32+n*4
       对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
              AliasAddr=0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32+n*4
       上式中,“*4”表示一个字为 4 个字节,“*8”表示一个字节中有 8 个比特。

       这里再不嫌啰嗦地举一个例子:
       1. 在地址 0x20000000 处写入 0x3355AACC
       2. 读取地址0x22000008。本次读访问将读取 0x20000000,并提取比特 2,值为 1。
       3. 往地址 0x22000008 处写 0。本次操作将被映射成对地址 0x20000000 的“读-改-写”操作(原子的),把比特2 清 0。
       4. 现在再读取 0x20000000,将返回 0x3355AAC8(bit[2]已清零)。
       位带别名区的字只有 LSB 有意义。另外,在访问位带别名区时,不管使用哪一种长度的数据传送指令(字/半字/字节),都把地址对齐到字的边界上,否则会产生不可预料的结果。

[cpp] view plain copy


  • ///////////////////////////////////////////////////////////////  
  • //位带操作,实现51类似的GPIO控制功能  
  • //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).  
  • //IO口操作宏定义  
  • #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))   
  • #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))   
  • #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))   
  • //IO口地址映射  
  • #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C   
  • #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C   
  • #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C   
  • #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C   
  • #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C   
  • #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C      
  • #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C      
  •   
  • #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808   
  • #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08   
  • #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008   
  • #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408   
  • #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808   
  • #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08   
  • #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08   
  •    
  • //IO口操作,只对单一的IO口!  
  • //确保n的值小于16!  
  • #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出   
  • #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入   
  •   
  • #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出   
  • #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入   
  •   
  • #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出   
  • #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入   
  •   
  • #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出   
  • #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入   
  •   
  • #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出   
  • #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入  
  •   
  • #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出   
  • #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入  
  •   
  • #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出   
  • #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入  
实现方法:
不废话上代码!
/***************************************************************************/
typedef struct
{
__IO uint16_t Pin0:1;
__IO uint16_t Pin1:1;
__IO uint16_t Pin2:1;
__IO uint16_t Pin3:1;
__IO uint16_t Pin4:1;
__IO uint16_t Pin5:1;
__IO uint16_t Pin6:1;
__IO uint16_t Pin7:1;
__IO uint16_t Pin8:1;
__IO uint16_t Pin9:1;
__IO uint16_t Pin10:1;
__IO uint16_t Pin11:1;
__IO uint16_t Pin12:1;
__IO uint16_t Pin13:1;
__IO uint16_t Pin14:1;
__IO uint16_t Pin15:1;
}PINIO_T;


#define CLI()      __set_PRIMASK(1)  
#define SEI()      __set_PRIMASK(0)


#define _PAout          ((PINIO_T*) &(GPIOA->ODR))
#define _PBout          ((PINIO_T*) &(GPIOB->ODR))
#define _PCout          ((PINIO_T*) &(GPIOC->ODR))
#define _PAin          ((PINIO_T*) &(GPIOA->IDR))
#define _PBin          ((PINIO_T*) &(GPIOB->IDR))
#define _PCin          ((PINIO_T*) &(GPIOC->IDR))


#define PAout(s)   _PAout->Pin##s
#define PBout(s)   _PBout->Pin##s
#define PCout(s)   _PCout->Pin##s


#define PAin(s)   _PAin->Pin##s
#define PBin(s)   _PBin->Pin##s
#define PCin(s)   _PCin->Pin##s

/***************************************************************************/
每次就这些利用C语言位域封装 SMT32F0芯片的IO操作 :
再来一个应用的代码
/***************************************************************************/
if(link_state==0)
        {
                TIM1->CCR1=100;
                PBout(0)=0;
                PBout(1)=0;
                PBout(2)=0;
                l1=0;
        }else         if(link_state==1)
        {
                //TIM1->CCR1=60;
                l1++;
                if(l1>200000){l1=0;TIM1->CCR1=60;}
    PBout(0)=1;
                PBout(1)=0;
                PBout(2)=0;
        }else        if(link_state==2)
        {
                TIM1->CCR1=60;
    PBout(0)=1;
                PBout(1)=1;
                PBout(2)=0;
                l1=0;
        }else        if(link_state==3)
        {
                TIM1->CCR1=100;
                PBout(0)=0;
                PBout(1)=0;
                PBout(2)=1;
                l1=0;
                if(PBin(12)==0)link_state=0;
               
        }
/***************************************************************************/
是不是和M3的一样

单注意 PAin(s)  里的S[size=14.1176px]只能是0-15 的常量数字 不能说变量!!!
[size=14.1176px]

[size=14.1176px]以上内容绝对原创;如有雷同 哈哈
[size=14.1176px]













阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2017-7-11 16:51:05 | 显示全部楼层
楼主你是如何自带背景的

出40入42汤圆

发表于 2017-7-11 16:51:56 | 显示全部楼层
二楼道出了我的心声

出0入57汤圆

发表于 2017-7-11 16:52:31 | 显示全部楼层
加不加精不知道,楼主的ID反正挺危险的了。

出40入42汤圆

发表于 2017-7-11 16:53:09 | 显示全部楼层

测试一下背景功能,嗯,新技能Get

出0入0汤圆

 楼主| 发表于 2017-7-11 16:53:14 | 显示全部楼层
leafstamen 发表于 2017-7-11 16:52
加不加精不知道,楼主的ID反正挺危险的了。

ID不符合么

出0入0汤圆

 楼主| 发表于 2017-7-11 16:53:42 | 显示全部楼层
amazing030 发表于 2017-7-11 16:51
楼主你是如何自带背景的

无意中点出来的

出0入0汤圆

发表于 2017-7-11 16:55:31 | 显示全部楼层
本帖最后由 ywlzh 于 2017-7-11 16:57 编辑

http://blog.csdn.net/ZPJ599707567/article/details/50503810  

楼主 是不知道这个世界上 还有一个浏览器叫谷歌吗? 层出不穷。

ps:你背景看的头疼

出0入57汤圆

发表于 2017-7-11 16:55:38 | 显示全部楼层

不让带背景,不让大字号。

出0入0汤圆

 楼主| 发表于 2017-7-11 16:59:50 | 显示全部楼层
ywlzh 发表于 2017-7-11 16:55
http://blog.csdn.net/ZPJ599707567/article/details/50503810  

楼主 是不知道这个世界上 还有一个浏览器 ...

来来来 你给我弄一个    PAin##s()  的链接

出0入0汤圆

发表于 2017-7-11 17:06:18 | 显示全部楼层
lanlibo 发表于 2017-7-11 16:59
来来来 你给我弄一个    PAin##s()  的链接

你是说 这一块 的代码吗?

/***************************************************************************/
typedef struct
{
__IO uint16_t Pin0:1;
__IO uint16_t Pin1:1;
__IO uint16_t Pin2:1;
__IO uint16_t Pin3:1;
__IO uint16_t Pin4:1;
__IO uint16_t Pin5:1;
__IO uint16_t Pin6:1;
__IO uint16_t Pin7:1;
__IO uint16_t Pin8:1;
__IO uint16_t Pin9:1;
__IO uint16_t Pin10:1;
__IO uint16_t Pin11:1;
__IO uint16_t Pin12:1;
__IO uint16_t Pin13:1;
__IO uint16_t Pin14:1;
__IO uint16_t Pin15:1;
}PINIO_T;


#define CLI()      __set_PRIMASK(1)  
#define SEI()      __set_PRIMASK(0)


#define _PAout          ((PINIO_T*) &(GPIOA->ODR))
#define _PBout          ((PINIO_T*) &(GPIOB->ODR))
#define _PCout          ((PINIO_T*) &(GPIOC->ODR))
#define _PAin          ((PINIO_T*) &(GPIOA->IDR))
#define _PBin          ((PINIO_T*) &(GPIOB->IDR))
#define _PCin          ((PINIO_T*) &(GPIOC->IDR))


#define PAout(s)   _PAout->Pin##s
#define PBout(s)   _PBout->Pin##s
#define PCout(s)   _PCout->Pin##s


#define PAin(s)   _PAin->Pin##s
#define PBin(s)   _PBin->Pin##s
#define PCin(s)   _PCin->Pin##s


出0入0汤圆

 楼主| 发表于 2017-7-11 17:07:44 | 显示全部楼层
ywlzh 发表于 2017-7-11 17:06
你是说 这一块 的代码吗?

/*********************************************************************** ...

算了算了 我就是 把这些封装 让大家方便用

出0入0汤圆

发表于 2017-7-11 17:15:16 | 显示全部楼层
lanlibo 发表于 2017-7-11 17:07
算了算了 我就是 把这些封装 让大家方便用

很到位呀

出0入0汤圆

发表于 2017-7-11 17:21:32 | 显示全部楼层
M3位带最大的好处是解决了临界段问题。用一条指令修改指定的bit。

但是搂着这个办法,最终汇编结果出来的时候,还是会被编译成。读出整个16bit寄存器的值,然后再执行与或操作,最后重新写入一个16bit的寄存器。
如果在第二步或第三步过程中出现中断。那么将出现临界段问题!

出0入0汤圆

 楼主| 发表于 2017-7-11 17:37:47 | 显示全部楼层
chaled 发表于 2017-7-11 17:21
M3位带最大的好处是解决了临界段问题。用一条指令修改指定的bit。

但是搂着这个办法,最终汇编结果出来的 ...

这个么 请参照  C语言 位域(位段)的相关知识   把 GPIO 地址封装成立 位域 和你自己软件操作某个位差不多 只是简化了

出0入0汤圆

 楼主| 发表于 2017-7-11 17:41:30 | 显示全部楼层
chaled 发表于 2017-7-11 17:21
M3位带最大的好处是解决了临界段问题。用一条指令修改指定的bit。

但是搂着这个办法,最终汇编结果出来的 ...

另外这个是指针操作 是读这个地址的内容

出0入0汤圆

 楼主| 发表于 2017-7-11 17:49:13 | 显示全部楼层
lianglee 发表于 2017-7-11 17:44
楼主,你说的是这样么?
我的STM8S都可以。

可以 单建议 Set_Bit(P##X##_DDR,##B##);Set_Bit(P##X##_CR1,##B##);Clr_Bit(P##X##_CR2,##B##)
加个大括号 {Set_Bit(P##X##_DDR,##B##);Set_Bit(P##X##_CR1,##B##);Clr_Bit(P##X##_CR2,##B##)}

出0入0汤圆

 楼主| 发表于 2017-7-11 17:50:56 | 显示全部楼层
lianglee 发表于 2017-7-11 17:44
楼主,你说的是这样么?
我的STM8S都可以。

/***************************************************************************/
typedef struct
{
__IO uint16_t Pin0:1;
__IO uint16_t Pin1:1;
__IO uint16_t Pin2:1;
__IO uint16_t Pin3:1;
__IO uint16_t Pin4:1;
__IO uint16_t Pin5:1;
__IO uint16_t Pin6:1;
__IO uint16_t Pin7:1;
__IO uint16_t Pin8:1;
__IO uint16_t Pin9:1;
__IO uint16_t Pin10:1;
__IO uint16_t Pin11:1;
__IO uint16_t Pin12:1;
__IO uint16_t Pin13:1;
__IO uint16_t Pin14:1;
__IO uint16_t Pin15:1;
}PINIO_T;

#define _PAout          ((PINIO_T*) &(GPIOA->ODR))
#define _PBout          ((PINIO_T*) &(GPIOB->ODR))
#define _PCout          ((PINIO_T*) &(GPIOC->ODR))
#define _PAin          ((PINIO_T*) &(GPIOA->IDR))
#define _PBin          ((PINIO_T*) &(GPIOB->IDR))
#define _PCin          ((PINIO_T*) &(GPIOC->IDR))


#define PAout(s)   _PAout->Pin##s
#define PBout(s)   _PBout->Pin##s
#define PCout(s)   _PCout->Pin##s


#define PAin(s)   _PAin->Pin##s
#define PBin(s)   _PBin->Pin##s
#define PCin(s)   _PCin->Pin##s


出0入0汤圆

发表于 2017-7-11 17:53:44 来自手机 | 显示全部楼层
你们还没操作过lpc单片机的端口,b和w寄存器才真的容易。

出0入0汤圆

 楼主| 发表于 2017-7-11 17:55:21 | 显示全部楼层
lianglee 发表于 2017-7-11 17:51
大括号还是然并卵的。
要加就加do while

循环一次 来条判断一回  学习了

出0入0汤圆

 楼主| 发表于 2017-7-11 17:56:18 | 显示全部楼层
XA144F 发表于 2017-7-11 17:53
你们还没操作过lpc单片机的端口,b和w寄存器才真的容易。

是啊 STM32 F0   就是.....

出20入25汤圆

发表于 2017-7-11 18:27:31 来自手机 | 显示全部楼层
楼主还是写的比较详细,支持一下

出10入10汤圆

发表于 2017-7-11 18:38:15 | 显示全部楼层
图文并茂,学习一下

出0入0汤圆

发表于 2017-7-11 18:56:42 来自手机 | 显示全部楼层
路过支持

出0入0汤圆

发表于 2017-7-11 19:04:30 来自手机 | 显示全部楼层
本帖最后由 myrgb 于 2017-7-12 09:58 编辑

位带的方法使用起来方便,就是需要封装下

出0入0汤圆

发表于 2017-7-11 20:06:40 | 显示全部楼层
楼主写的不错,正好要用位域的东东

出0入42汤圆

发表于 2017-7-11 21:34:37 | 显示全部楼层
lanlibo 发表于 2017-7-11 17:37
这个么 请参照  C语言 位域(位段)的相关知识   把 GPIO 地址封装成立 位域 和你自己软件操作某个位差不 ...

LZ, 14楼想表达的意思是,即使用了位段,编译器仍然会将指令分解成“读-修改-写”的操作,如果在读完后写之前进入了中断,这个中断恰好修改了相关端口的ODR值,退出中断后继续按照原来的写入,就出错了。

出0入42汤圆

发表于 2017-7-11 22:07:16 | 显示全部楼层
用LZ的代码建了个简单的工程,验证了一下:




确实还是遵循的“读-修改-写”的步骤,并没有出现和“位段区”有关的操作,此“位段”非彼“位段”哦

当然,这样写可以使代码简洁明了,前提是,必须保证没有临界区的问题,或者在操作之前关闭全局中断,操作结束后再打开全局中断。。这就显得繁琐了

所以,对于M0,正确的做法是根据使用的芯片灵活决定用何种方式实现位操作。比如,STM32的M0,GPIO提供了BSRR寄存器,就是专门用来做位操作的。

本帖子中包含更多资源

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

x

出0入42汤圆

发表于 2017-7-11 22:10:15 | 显示全部楼层
补上通过操作BSRR寄存器实现位操作的代码:



本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2017-7-12 09:44:50 | 显示全部楼层
mark,之前就已经在使用了

出0入0汤圆

发表于 2017-7-12 11:12:35 | 显示全部楼层
画皮难画骨,真的只是“像”,如果用BRR/BSRR实现,我觉得还行。

出0入0汤圆

 楼主| 发表于 2017-7-12 13:06:44 | 显示全部楼层
wshtyr 发表于 2017-7-11 22:07
用LZ的代码建了个简单的工程,验证了一下:

我想知道中断发生 了 不是有 现场保护么 压栈出栈

出0入0汤圆

 楼主| 发表于 2017-7-12 13:08:06 | 显示全部楼层
styleno1 发表于 2017-7-12 11:12
画皮难画骨,真的只是“像”,如果用BRR/BSRR实现,我觉得还行。

对只是像  时序快或严格时序 还是用寄存器

出0入0汤圆

 楼主| 发表于 2017-7-12 13:10:14 | 显示全部楼层
wshtyr 发表于 2017-7-11 22:07
用LZ的代码建了个简单的工程,验证了一下:

但是LZ毕竟小白一个多谢指点

出0入0汤圆

发表于 2017-7-19 14:18:40 | 显示全部楼层
有点意思,最开始接触是看到了TI 的C2000系列控制器中引入的位域结构体,不知道汇编实现是如何的

出0入0汤圆

发表于 2017-7-19 15:18:51 | 显示全部楼层
fm007 发表于 2017-7-19 14:18
有点意思,最开始接触是看到了TI 的C2000系列控制器中引入的位域结构体,不知道汇编实现是如何的 ...

你的头像内容,是几个意思?





出0入0汤圆

发表于 2017-7-20 22:52:56 来自手机 | 显示全部楼层
其实cm3的位带操作还是有问题的,它是通过总线完成的,同样有读修改写这样的操作,虽然保证了指令代码逻辑上是原子操作的,但是却无法保证片上外设修改对应寄存器,即总线的读修改写操作时目标寄存器被外设修改

出0入0汤圆

发表于 2017-7-20 22:54:21 来自手机 | 显示全部楼层
这个问题几年前在本论坛有人提过,还是个清华的,最后他在st的论坛获得了答案

出0入0汤圆

发表于 2017-7-20 22:55:35 来自手机 | 显示全部楼层
cm3的位带操作确实比51灵活,但是51是真正的原子操作

出0入0汤圆

发表于 2017-7-21 11:35:45 | 显示全部楼层
习惯了51的 sbit SPK  = P1^3;这种方式简洁明了,,对于这样   #define _PAin   ((PINIO_T*) &(GPIOA->IDR))   冗长摸脚布的,浪费键盘浪费视力,,,

出0入0汤圆

发表于 2017-7-21 12:29:36 | 显示全部楼层
路过学习下。

出0入0汤圆

发表于 2018-8-10 16:25:30 | 显示全部楼层
楼主,想问下,不是说n要在0-7之间么,怎么后面调用时变成了0-15之间呢?
另外有个疑问:比如位带区地址是0x40000000,bit位是8,那么经这个公式计算,位带别名区是0x42000020,
位带区是0x40000001,bit位是0,经计算这个的位带别名区地址也是0x42000020,这不就冲突了?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 23:04

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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