OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 11757|回复: 19

谈一个IO口操作需要注意的问题

[复制链接]

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
发表于 2012-3-10 13:14:32 | 显示全部楼层 |阅读模式

在学习STM32的GPIO的时候,看过参考手册,
印象中有个地方提到,在操作某几个IO引脚的时候使用BSRR和BRR寄存器会比直接操作ODR好,
(当然,原子哥提供的宏对于操作单个IO引脚也是非常方便的)

然后我就好奇,想了一下为什么,印象中还有中断这两个字眼,
我想应该是这样的:

比如:
我的PA0 - PA7 连着一个8位总线的IC,PA8 - PA15 又连着另一个IC,
然后,当我的程序需要向IC送数据u8 a的时候,我该怎么办?
对于PA0 - PA7, 我的第一反应是这样子,
GPIOA->ODR = ((GPIOA->ODR & 0xff00) | (u16)a);
乍一看,代码很完美,写了PA0 - PA7,又不影响PA8 - PA15,呵呵,我最初确实是这么想的

再看看代码,代码右边有读ODR的操作,这个就是问题的关键所在,请看分析:
假设我的主函数需要向低8位写数据,用了刚才的代码,代码右边有读ODR的操作,
从读ODR到写ODR的过程中会有运算,一定会有时间差的,
如果在这个时间段内,发生了中断
,中断向PA8 - PA15的外设送数据,也用了我刚才的那种方式,
这样,PA8 - PA15上的数据就改变了,
然后中段返回了,
想一想,这个时候,主函数刚才读到的ODR还会是当前ODR的值吗?
主函数的代码操作确实没有改变读到的 ODR 的值的高8位,但是之前读的ODR已经不是现在的ODR了!!!
然后主函数写了ODR,就相当于,中断刚刚写的高8位的值,又被主函数改回去了!!!
这样做会有什么后果?我也不知道,用老外的话说,it depends on how unlucky you are !

那么有没有更好的方案呢?
酝酿当中。。。如果网友有自己的想法,希望网友补充。

https://github.com/roxma
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-10 13:28:43 | 显示全部楼层
继续补充:
对于写BSRR和BRR的操作,就没有这个隐患,因为它不需要读ODR寄存器,一次性写入,无时间差,
比如说,如果我需要操作低8位写一个数据u8 a,注意对于a的数据类型是8位的才可以这样写,这个涉及到C语言的数据类型转换。
BSRR = ( (u16)a ) |   (((u16)(~a)) << 8) ;
https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-10 14:01:24 | 显示全部楼层
有时候需要考虑通用性的问题,所以我经常喜欢用宏或者内联函数的方式来实现这些寄存器操作,我说过,我的逻辑与或非等等计算一直不好。。。
对于几个连续的引脚和芯片外部的设备相连的时候,
#define WritePins(port, LSB, width, val) \
port->BSRR =  ( (u16)val * LSB) & ( (((u16)LSB) <<width) - LSB )   |   (   (u32) (     ( ( (u16)val * LSB)  ^  ( (((u16)LSB) <<width) - LSB ) )   \
 &     ( (((u16)LSB) <<width) - LSB )   )   )      << 16

写得可能比较复杂,不太好懂,
比如向PA0 - PA7 写数据a可以这样做:
WritePins(GPIOA, Bit(0), 8, a);
(注:Bit宏的定义可以看这里http://www.openedv.com/posts/list/3134.htm)
其实是这样的,
右边的表达式中,左边黄色括号内得到的是要置1的几个IO位,
右边黄色括号内得到的是 要清0的IO位,因为最后是要写到寄存器BSRR的,所以可以看到里面有左移16位的操作

关于这个宏的效率,我没测试,我这里只是提供一种方案而已,一般 参数 LSB 和 width 都是常数,编译器可以自动优化,但是优化到什么程度? 我也不清楚,测试了才知道



https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-10 14:32:52 | 显示全部楼层

楼上的代码的功能,通过内联的方式测试通过:
finline void WritePins(GPIO_TypeDef* port,u16 LSB,u16 width,u16 val)
{
 port->BSRR = (( (u16)val * LSB) & ( (((u16)LSB) <<width) - LSB ) )  
     | ( ((u32)( ( ( (val * LSB)  ^  ( ((LSB) <<width) - LSB ) ) )
      & ( ( (( LSB) <<width) - LSB ) ) ))      << 16 ) ;
}

测试代码:
 WritePins(GPIOA, Pin(0), 8, BByte(10010110));
 WritePins(GPIOA, Pin(8), 8, BByte(10111010));

 WritePins(GPIOB, Pin(3), 8, BByte(10100101));



 

stm32_cppTest.zip

1.43 MB, 下载次数: 992

https://github.com/roxma
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165524
金钱
165524
注册时间
2010-12-1
在线时间
2116 小时
发表于 2012-3-10 18:46:00 | 显示全部楼层
有点复杂.
你说的问题确实存在,但是没必要这么做.
设置某个IO又不想影响其他IO,直接或就可以了,如果再去读出来,就有点脱裤子放屁了.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

12

主题

216

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
313
金钱
313
注册时间
2011-4-7
在线时间
3 小时
发表于 2012-3-11 16:18:23 | 显示全部楼层
mark
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-11 16:22:01 | 显示全部楼层
回复【5楼】正点原子:
---------------------------------
"设置某个IO又不想影响其他IO,直接或就可以了"

不明白,为什么直接或就可以了?
https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-11 17:07:12 | 显示全部楼层

GPIOA->ODR = ((GPIOA->ODR & 0xff00) | (u16)a);

其实可以近似看成

GPIOA->ODR &= 0xff00;
GPIOA->ODR |= a;


但是从反汇编的情况看来(其实我只认得STR(写store RAM)和LDR(读load RAM)指令。。。),是不一样的

这种只读了一次,也只写了一次,

而这种,读->写,然后又读,然后又写。有一个读应该是可以优化掉的(这个应该是官方把寄存器的类型定义成不可优化的了,,,所以编译器没做优化)
,但是编译器是不会把两个写给优化掉。

两种方式都有我在楼主位提到的问题,
因为第二种方式有两次写操作,而第一次写操作的结果并不是我想要的,并且和第二次写存在着一定的时间差,
结果就是会导致有窄脉冲的出现(软件仿真的结果也是这样的。)
,所以我没有采取第二种方式。

https://github.com/roxma
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165524
金钱
165524
注册时间
2010-12-1
在线时间
2116 小时
发表于 2012-3-11 18:02:01 | 显示全部楼层
回复【7楼】Pony279:
---------------------------------
比如要设置PA7输出高。
我直接GPIOA->ODR|=1<<7;
就可以了,不用去读其他IO状态的。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-11 18:07:33 | 显示全部楼层
回复【9楼】正点原子:
---------------------------------
但是GPIOA->ODR|=1<<7;
其实是
GPIOA->ODR = GPIOA->ODR | ( 1<<7) ;
呀,因为任何运算都还是得在通用寄存器里完成的啊
https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-3-11 18:09:57 | 显示全部楼层
它们的汇编代码是一样的:


https://github.com/roxma
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165524
金钱
165524
注册时间
2010-12-1
在线时间
2116 小时
发表于 2012-3-11 23:22:12 | 显示全部楼层
回复【10楼】Pony279:
---------------------------------
原来如此.呵呵.学习了.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
21
金钱
21
注册时间
2012-4-7
在线时间
0 小时
发表于 2012-4-7 21:35:40 | 显示全部楼层
回复【11楼】Pony279:
---------------------------------
很欣赏楼主较真的精神,我还没有研究过32的汇编,但是似乎记得学51汇编是只有累加器中才可以进行逻辑运算。
我还看了楼主的别的贴感觉很有帮助,我本身也是一名大学生,现在大一,花在额外学东西做东西的精力比较大,比较感兴趣,但是对于基础理论也是尽力抓,至于在时间分配上不知楼主能否给点建议,不胜感激。
回复 支持 反对

使用道具 举报

2

主题

1446

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
2249
金钱
2249
注册时间
2010-12-16
在线时间
203 小时
发表于 2012-4-7 22:20:39 | 显示全部楼层
这个要看CPU架构和编译器处理方式吧,处理器的外设一般不会设计成带运算功能,很多单片机的说明书都着重提到了“读-修改-写”,即“Read-Modify-Write”的具体操作,特别是一些允许被多个访问源修改的寄存器。

为了避免不可预料的操作,对这些寄存器都会有特别的说明。
比如对于原子操作位和中断,可以看stm32RM在8.1.2的描述。这个描述的另一个意思,就是如果对ODR进行位的原子操作,是需要禁止中断的。
另一个表现是一些含有多个标志位的寄存器,因为寄存器可以被硬件和CPU修改,为了避免不可预料的操作,一般设计成写“1”清除标志位,而不是写“0”清除标志位,因为可能在“读-修改-写”过程中有新的标志位写入寄存器。另一种设计是标志位的清除操作是使用各自的方式,比如STM32的串口某些标志位可以通过一些操作序列来清除,而不必直接操作标志位寄存器。
技术讨论请发帖 , 需要我回复请点左下的 < 回复 > 让系统通知我 . 本人不通过其他方式返回任何参数.
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-4-7 23:08:30 | 显示全部楼层
回复【13楼】lyepng:
---------------------------------
“很欣赏楼主较真的精神,我还没有研究过32的汇编,”
我对汇编并不熟,只是稍有了解,汇编不是必须掌握的,但是了解一点的话会对写程序有帮助。


“现在大一,花在额外学东西做东西的精力比较大,比较感兴趣,但是对于基础理论也是尽力抓,至于在时间分配上不知楼主能否给点建议,不胜感激。”
确实需要比较多精力的,我觉得,如果对自己的专业感兴趣的话,应该把主要的时间花在理论的学习上,
如果自己没有扎实的基础的话,感觉很多时候都只是在重复别人做过的工作
个人见解,我也只是大二的,呵呵,共勉啊~
https://github.com/roxma
回复 支持 反对

使用道具 举报

36

主题

1105

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
2201
金钱
2201
注册时间
2012-2-8
在线时间
35 小时
 楼主| 发表于 2012-4-7 23:19:34 | 显示全部楼层
回复【14楼】shihantu:
---------------------------------

呵呵,举一反三呀,

我提的这个问题,确实不仅仅限于IO操作才需要注意的。
https://github.com/roxma
回复 支持 反对

使用道具 举报

58

主题

288

帖子

1

精华

高级会员

Rank: 4

积分
814
金钱
814
注册时间
2012-3-29
在线时间
81 小时
发表于 2012-4-10 00:42:57 | 显示全部楼层
回复【2楼】Pony279:
---------------------------------
应该左移16位吧
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
21
金钱
21
注册时间
2014-9-13
在线时间
0 小时
发表于 2014-9-13 16:47:49 | 显示全部楼层
回复【15楼】Pony279:
---------------------------------
我现在小学,没看过stm32.
你在深圳?大一?深大的?扯淡还差不多。
回复 支持 反对

使用道具 举报

27

主题

259

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
387
金钱
387
注册时间
2014-8-19
在线时间
0 小时
发表于 2014-9-17 10:21:16 | 显示全部楼层
回复【13楼】lyepng:
---------------------------------
你大一都研究这么深?服
回复 支持 反对

使用道具 举报

9

主题

507

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3347
金钱
3347
注册时间
2013-4-10
在线时间
333 小时
发表于 2018-1-27 12:44:34 | 显示全部楼层
学习了, 都是高手啊
回复 支持 反对

使用道具 举报

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

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-8 17:29

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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