OpenEdv-开源电子网

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

学习正点原子精英STM32版的学习笔记

  [复制链接]

2

主题

17

帖子

0

精华

初级会员

Rank: 2

积分
58
金钱
58
注册时间
2018-5-18
在线时间
14 小时
发表于 2018-6-25 20:01:29 来自手机 | 显示全部楼层
喜欢楼主的更新,我会继续跟帖的,希望楼主更新分享下去。
正点原子逻辑分析仪DL16劲爆上市
回复 支持 反对

使用道具 举报

2

主题

17

帖子

0

精华

初级会员

Rank: 2

积分
58
金钱
58
注册时间
2018-5-18
在线时间
14 小时
发表于 2018-6-25 20:18:03 | 显示全部楼层
warship 发表于 2018-6-22 18:22
装载的时间周期非常短,
只是数个指令周期而已,基本在us就完成了。
但装载定时器周期时,

你装载初值为三,计数到零的时候是3-1,2-1,1-0,刚好三个周期,到零的瞬间就中断了,为什么要减1
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-6-25 20:25:50 | 显示全部楼层
探路阳 发表于 2018-6-25 20:18
你装载初值为三,计数到零的时候是3-1,2-1,1-0,刚好三个周期,到零的瞬间就中断了,为什么要减1

到0不叫溢出,0是正常值,从00到FF才是真正意义上的溢出
想计三个脉冲,就得装载2。2-1-1-1=FF才会溢出

我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-26 09:41:03 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-6-26 10:09 编辑
探路阳 发表于 2018-6-25 20:18
你装载初值为三,计数到零的时候是3-1,2-1,1-0,刚好三个周期,到零的瞬间就中断了,为什么要减1

warship说的没错,SysTick的VAL是从LOAD减计数到0后定时到。
也就是像for循环:
如果需要循环3次:
for( int i = 2; i <= 0; i++)一样的道理。
2-----1------0-----溢出,刚好3次。---------------------------------
补充:
查看了一下文档,似乎没有关于溢出的描述,不过从库函数SysTick_Config()的设计看确实有-1的操作,我的理解是重装载需要一个SysTick周期,所以-1.
刚才看了编程文档,提到2点:如果需要每N周期得到一个时刻则需要N-1;如果需要在N周期后发生中断,则LOAD为N。
不知所然。



回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-26 16:11:45 | 显示全部楼层
学习完SysTick后开始学习中断,在SysTick中也是使用到中断的。
先来看简单的概念,这是基础。
Cortex-M3内核支持256个中断,其中内部中断16个,外部中断IRQs共计240个。这些中断可以编程为256级优先级。
所谓的外部中断不仅仅是GPIO中断。
而STM32仅仅支持CM3所支持的一部分中断:一共支持84个中断,16级可编程优先级。其中内部中断还是16个,这个应该是基础的重要中断了,系统级,可能也不会让用户使用。
外部中断支持68个。
具体到了精英版的STM32F103芯片则只是支持STM32的部分中断,其他的是高级别芯片F107的东西。
F103支持16个内部中断,这个没变,外部中断有60个。
想想一开始不用管理240号人,只有60个人,还是比较合适学习的。
------------------------------------------
STM32的中断级别分两种,是两种的组合:抢占优先级和响应优先级。有些资料叫做主优先级和次优先级。
所谓抢占优先级就是当发生高优先级中断时可以把正在执行的低优先级中断先挂起,挂起后立刻处理发生的高优先级中断。当高优先级中断处理完毕后再来继续处理之前的低优先级中断。
而响应优先级的处理顺序只有在同级别的抢占优先级中断间才有比较的必要,当中断的抢占优先级不同时首先比较的是抢占优先级,谁管响应优先级呢。
同一抢占优先级不同响应优先级的中断如何比较呢?其实很简单,先来先得,同时发生时高相应级别的先得。也就是说如果已经在处理低响应优先级的中断时发生了高响应优先级中断,则需要处理完正在处理的中断才能处理高级响应级别的中断,不能像抢占一样被挂起。也就是说响应优先级体现了公平性,而抢占优先级体现了特权。
----------------------------------------------
西门子200、1200的中断可以看作是同一抢占优先级的不同响应优先级中断。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-26 16:24:17 | 显示全部楼层
优先级用数字表示,值越小优先级越高。
STM32用4位来表示优先级,这4位包换了抢占和响应优先级。提供了5种优先级的预定表示方案,叫做优先级组0~4,不同的方案组意味着抢占和响应优先级可表示的级别多少。
比如,选择组2时就是2位表示抢占2位表示响应,各有4级(0、1、2、3级)。如果是组0则全部用来表示响应优先级,大家都是同一抢占级别,响应级别有16级(0...15)。
使用哪组方案整个程序中只能出现一次选择,不能出现第二次,否则系统会糊涂的。
一般在main()中使用中断前设置,使用NVIC_PriorityGroupConfig()函数。


回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-26 22:58:02 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-6-27 14:12 编辑

NVIC是嵌套向量中断控制器,也许是学习STM32最复杂的控制器了。
目前全面了解是不太可能的,先如此吧。
只需要按照一定的流程来设计就可以了:中断编号决定中断源,中断抢占、响应优先级,中断使能。
这些是中断初始化函数所需要的信息,和GPIO使能初始化类似,使用结构体来设置这些参数,然后让NVIC_Init()根据这些参数来设置中断。
----------------------
目前有几个地方记住:SCB中断AIRCR中的8...10位决定采用哪个组的方案来解释中断优先级寄存器IP中的第4...7位数据,IP是中断优先级寄存器,有好多个组,是32位寄存器的分段表示,每个寄存器8位,4...7位即为高4位。
中断的操作有使能、失能,不是通常的某个位的01状态,而是用两位表示,一位表示使能,一位表示失能。它位于2个不同的寄存器里面,构成一对关于使能失能的寄存器。使能失能寄存器位只有1有效,0是无效的,也就是说0既不使能也不失能。怎么控制?
中断的操作还有挂起还解挂,也是一对寄存器位,和使能失能类似,当然功能是挂起解挂。
----------------这些寄存器功能复杂,目前记住使用NVIC_PriorityGroupConfig()来设置中断组类型和使用NVIC_Init来初始化某个中断即可。



回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-6-27 10:44:52 | 显示全部楼层
xiatianyun 发表于 2018-6-26 22:58
NVIC是嵌套向量中断控制器,也许是学习STM32最复杂的控制器了。
目前全面了解是不太可能的,先如此吧。
...

学习了,加油!!!
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-27 22:30:40 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-6-28 12:51 编辑

继续学习中断。
中断有很多种,GPIO这些外部I/O口产生的中断是最常用的中断。这些中断是外部中断中的一种,也就是说外部中断不仅仅只是GPIO产生的中断。
F103ZET的GPIO一共7组每组16个Pin,共计112个Pin,这些口都可以中断。不过STM32可没有这么多中断资源可以用,怎么管理呢?就用分时来控制,来看看是如何管理的。
线0~线15这16个中断线用于GPIO口的中断,所谓中断线可以理解为中断源,不过不是源头而已,这么理解好了:所谓线其实是控制中断源选择的线路开关通道,通过不同的开关来选择不同的最终中断源。就像路由器一样。
EXTI0~EXTI15就是线0~线15.
那这16条线如何管理112个口中断呢?
这7组GPIO中每组同编号名的Pin共同组成一个线组,一共是16线组,每组7个Pin,刚好对应16线。
也即PA0、PB0、PC0、...、PG0组成EXTI0
...
PA15、PB15、PC15、...、PG15组成EXTI15
可以看出EXTI的编号对应Pin的编号。
每条线中的7个Pin只能分时占用线资源,不能同时中断。换句话说即使同时满足的中断条件也不能同时执行中断。
不过,不同组是可以同时中断的。这要看中断优先级。
-------------------------
使用GPIO_EXTILineConfig()函数来设置哪个端口被当作外部中断线用,参数有两个,一个是端口资源一个是Pin资源,这个和GPIO初始化时使用的宏是不一样的,GPIO初始化的端口和引脚宏是关于地址的,而这里的端口和引脚宏是关于数值的,完全是寄存器中的值。
这个函数在stm32f10x_gpio.c中定义。通过该函数完成了某个端口pin和某条线的结合,这样该线就是该pin的中断源了,操作该EXTI就是操作该Pin中断源了。
由于线和pin是固定的映射关系,所以并不需要线作为函数参数。
还有,中断需要使能的总线是RCC_APB2Periph_AFIO 。
------------------------------------
接着需要确定该线(或者说该pin)采用哪种中断方式:
上升沿、下降沿、边沿触发。
是事件还是中断。一直以为事件就是中断,看了还不一样。至于有何不同只能以后学习知道了。
使用EXTI_Init()来初始化外部中断,使用结构数据做参数,该结构除了上述中断方式外还有是使能该中断还是失能该中断。失能、使能使用的是同一函数,只是参数有区别。
当然了,结构里面还有使用那条线的数据。
也就是说,如果该条线所映射的所有GPIO如果都采用同一中断方式的话就只需执行一次初始化了。
所不同的是如果需要另一个同线GPIO中断的话需要再次关联到另一个GPIO。关联后以前关联的GPIO就不能再执行中断了。
------------------------------------
中断函数是否每条线都有独立的一个服务函数可用呢?
不是,一共只有7个中断函数可以供这16条线用。
其中,EXTI0~EXTI4是用独立中断函数的,而EXTI5~EXTI9这5条线共用一个中断函数,EXTI10~EXTI15 共用一个中断函数。
如何在共用中断函数内判断到底是哪条线中断呢?只有通过查询某个状态来确定。还没学。
中断发生后会自动设置该线的中断标识,在中断即将结束必须手动清除该标识。这点有点乱。为什么需要手动清除呢,都自动设置了就不能自动清除吗?






回复 支持 反对

使用道具 举报

6

主题

462

帖子

0

精华

高级会员

Rank: 4

积分
904
金钱
904
注册时间
2017-12-15
在线时间
111 小时
发表于 2018-6-27 22:55:04 | 显示全部楼层
xiatianyun 发表于 2018-6-27 22:30
继续学习中断。
中断有很多种,GPIO这些外部I/O口产生的中断是最常用的中断。这些中断是外部中断中的一种 ...

您刚才说到,“外部中断不仅仅只是GPIO产生的中断”,那么还有什么是外部中断呢?
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-6-27 22:55:05 | 显示全部楼层
本帖最后由 warship 于 2018-6-27 22:56 编辑
xiatianyun 发表于 2018-6-27 22:30
其中,EXTI0~EXTI4是用独立中断函数的,而EXTI5~EXTI9这5条线共用一个中断函数,EXTI10~EXTI15 共用一个中断函数。
如何在共用中断函数内判断到底是哪条线中断呢?只有通过查询某个状态来确定。还没学。

用这一句就可知是否是你所期望的中断线发生中断了:
if(EXTI_GetITStatus(中断线号)!=RESET)
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-6-27 23:13:24 | 显示全部楼层
本帖最后由 warship 于 2018-6-28 00:02 编辑
a496298685 发表于 2018-6-27 22:55
您刚才说到,“外部中断不仅仅只是GPIO产生的中断”,那么还有什么是外部中断呢?

外部中断线有19条,除了GPIO的这16条外,
还有RTC,USB,PVD等3条
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-6-27 23:17:07 | 显示全部楼层
xiatianyun 发表于 2018-6-27 22:30
继续学习中断。
中断有很多种,GPIO这些外部I/O口产生的中断是最常用的中断。这些中断是外部中断中的一种 ...

之所以系统不自动清除中断标识?
我想:其中的原因之一是有这种多条中断线共用一个中断服务函数的情况,
系统不知道你处理了哪一条中断线,
无法帮你主动清除,
索性把控制权都交给你了。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165352
金钱
165352
注册时间
2010-12-1
在线时间
2108 小时
发表于 2018-6-28 01:36:03 | 显示全部楼层
厉害了,楼主加油!
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-28 16:25:35 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-6-28 16:42 编辑

多谢楼上几位。
中断发生时如果中断服务被执行,系统自动把发生中断的挂起位设置为SET。
由于除了0~4线外的几条中断线的中断向量是同一个,所以需要判断到底是那条线发生中断,只有确认是所需中断线时才能处理所需程序,如果不是则不执行该段程序。
在中断函数的最后,由于是由多条线共用的中断函数,退出前需要手动清除挂起位。
可以相像中断函数内一定存在多条分支,分支结束即为该中断线服务函数的退出,如果由系统清除挂起状态的话只能在整个函数的结尾处一次全部清除所有相关的中断线挂起位,这样有些中断还没有来得及处理就被清除了。
(我的理解:由于中断有优先级,不同优先级的中断可能使用了同一中断函数,清除后就不能在高优先级中断处理后来处理低优先级中断了。)
也可以相像即使是同一中断在上次没有处理完前是不能被打断的,因为该中断已经被挂起了。

------------------------这样,一个模式化的EXTI中断函数是:


[mw_shl_code=c,true]void EXTI0_IRQHandler(void)
{
//确认是否产生了 EXTI_Line0 中断
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
//
//具体程序。
//
//清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
}
}[/mw_shl_code]

不过,这些中断函数的声明又是在哪里被声明的呢?
以前的SysTick中断函数SysTick_Handler()是在stm32f10x_it.h中声明的,同一文件没有找到啊。

回复 支持 反对

使用道具 举报

头像被屏蔽

0

主题

8

帖子

0

精华

禁止访问

积分
49
金钱
49
注册时间
2018-6-28
在线时间
112 小时
发表于 2018-6-28 16:34:07 | 显示全部楼层
哇 真的学习到了
回复 支持 反对

使用道具 举报

6

主题

126

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
207
金钱
207
注册时间
2018-4-18
在线时间
10 小时
发表于 2018-6-28 16:35:12 | 显示全部楼层
真的好厉害  保持下去
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-28 16:45:07 | 显示全部楼层

大声说出来,让别人知道你在做什么,自己才能有学习的动力。
回复 支持 9 反对 0

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-28 16:59:07 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-6-28 17:37 编辑

通过stm32f10x_it.h最后的说明知道必须和startup_stm32f10x.hd.s中的中断函数名一致,所以...
在启动文件中就规定好了。[mw_shl_code=c,true]; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC
                DCD     EXTI0_IRQHandler           ; EXTI Line 0
                DCD     EXTI1_IRQHandler           ; EXTI Line 1
                DCD     EXTI2_IRQHandler           ; EXTI Line 2
                DCD     EXTI3_IRQHandler           ; EXTI Line 3
                DCD     EXTI4_IRQHandler           ; EXTI Line 4
                .......
....................................
                DCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5
                ......................
............................................
                DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
                DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line[/mw_shl_code]

也找到了SysTick_Handler(),不过SysTick_Handler()在其他文件中有声明。
看来可以自己在根据名字来声明。

------------------------
进一步查看stm32f10x.h中断编号的枚举定义,发现除了EXTI0~4外,其他的只有EXTI9_5_IRQn和EXTI15_10_IRQn,并不是独立编号的。
如果我需要使用线5中断,要如何处理呢?
比如如何清除挂起呢?使用EXTI9_5_IRQn一次就清除了5条线了啊?不明白了。---------------------------
补充:
把中断编号和中断线弄混了,虽然知道但还是会搞糊涂。
中断线的定义在stm32f10x_exit.h里面。


回复 支持 反对

使用道具 举报

1

主题

10

帖子

0

精华

初级会员

Rank: 2

积分
169
金钱
169
注册时间
2018-6-18
在线时间
25 小时
发表于 2018-6-28 18:15:09 | 显示全部楼层
Mark一下
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-6-29 09:42:05 来自手机 | 显示全部楼层
辛亏有ASSERT断言,不然学习就辛苦了。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-1 16:14:37 | 显示全部楼层
在学习外部中断。
想模仿教程中的按键中断使LED点亮、熄灭。
遇到几个问题:如果像教程中的做法,中断后进入中断函数检测按键防抖,则需要等待一段时间,无论是否是因为抖动按下的按键还是真正按下按键都需要等待。这个我想和中断的本质有些不符。
中断是因为发生了某些突发事件需要紧急处理才设置的机制,如果过多占用系统处理时间则不能及时处理其他突发事件了。
所以中断函数最好是越短越好。
如果取消防抖那么确实也存在问题,不能解决按键抖动问题。在实际使用开发板时也深有体会。
要怎样才能用好中断呢?
现在的方案大体就是:
1、像教程一样的做法,中断函数中通过延时等待来再次检测是否按下按键。
2、取消防抖处理。
3、另想它法。


回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-1 21:49:39 | 显示全部楼层
xiatianyun 发表于 2018-6-29 09:42
辛亏有ASSERT断言,不然学习就辛苦了。

什么意思?
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-1 22:02:08 | 显示全部楼层

就是不用刻意记参数取值,使用时查看assert断言嘛。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-1 22:02:55 | 显示全部楼层
遇到巨坑了,我写的中断试验代码不能进入中断。。。。。。。。。。
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-1 22:07:50 | 显示全部楼层
xiatianyun 发表于 2018-7-1 22:02
遇到巨坑了,我写的中断试验代码不能进入中断。。。。。。。。。。

是不是没有开AFIO时钟
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-1 22:18:12 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-1 22:25 编辑
warship 发表于 2018-7-1 22:07
是不是没有开AFIO时钟

开了的。
[mw_shl_code=c,true]// 板载按键外部中断初始化。
// KEY0/KEY1/WKUP三个按键的中断初始化。
// 分两步:1.设置外部中断线。2.设置中断线的NVIC优先级并使能中断。
void keyExti_Init(void)
{
    EXTI_InitTypeDef EXTI_InitStruct;  // 外部中断初始化参数结构。
    NVIC_InitTypeDef NVIC_InitStruct;  // 中断向量控制器初始化参数结构。

    // 复用功能时钟使能
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


    // KEY0 中断设置:
    // KEY0:接到PE4端口,上拉输入;
    // 设置PE4为线4的中断源。
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
    //中断线4、下降沿触发。
    EXTI_InitStruct.EXTI_Line = EXTI_Line4;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_Init(&EXTI_InitStruct);

    // KEY1 中断设置:
    // KEY1: 接到PE3端口,上拉输入;
    // 设置PE3为线3的中断源。
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);
    // 中断线3、下降沿触发。
    EXTI_InitStruct.EXTI_Line = EXTI_Line3;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_Init(&EXTI_InitStruct);

    // WKUP 中断设置:
    // WKUP: 接到PA0端口,下拉输入;
    // 设置PA0为线0的中断源。
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
    // 中断线0、上升沿触发。
    EXTI_InitStruct.EXTI_Line = EXTI_Line0;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_Init(&EXTI_InitStruct);

    // 设置NVIC中断优先级及使能中断。
    // KEY0所在的外部中断通道为EXTI4_IRQn。
    NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
    // 抢占优先级为2.
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x02;
    // 响应优先级为0.
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
    // 使能外部中断通道。
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    // 根据初始化参数结构信息初始化NVIC.
    NVIC_Init(&NVIC_InitStruct);


    // KEY1所在的外部中断通道为EXTI3_IRQn。
    NVIC_InitStruct.NVIC_IRQChannel = EXTI3_IRQn;
    // 抢占优先级为2.
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x02;
    // 响应优先级为1.
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x01;
    // 使能外部中断通道。
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    // 根据初始化参数结构信息初始化NVIC.
    NVIC_Init(&NVIC_InitStruct);

    // WKUP所在的外部中断通道为EXTI0_IRQn。
    NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
    // 抢占优先级为2.
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x02;
    // 响应优先级为3.
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x03;
    // 使能外部中断通道。
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    // 根据初始化参数结构信息初始化NVIC.
    NVIC_Init(&NVIC_InitStruct);
   
    EXTI_ClearITPendingBit(EXTI_Line4);  //清除LINE4上的中断标志位  
    EXTI_ClearITPendingBit(EXTI_Line3);  //清除LINE3上的中断标志位
    EXTI_ClearITPendingBit(EXTI_Line0);  //清除LINE0上的中断标志位      
}[/mw_shl_code]

key初始化在main()中完成。
现在不能进入中断,搜索了论坛贴,也不得要领。
我都怀疑中断函数名有问题,可是把原子例程的中断函数名复制过来看是一模一样的。
用Debug设置运行到中断函数里面来,结果就是不能运行进入中断函数。
初始化函数最后三行清除中断标志位是怀疑一开始就中断挂起时我加的。


回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-1 22:46:22 | 显示全部楼层
xiatianyun 发表于 2018-7-1 22:18
开了的。
[mw_shl_code=c,true]// 板载按键外部中断初始化。
// KEY0/KEY1/WKUP三个按键的中断初始化。 ...

在系统时钟配置完成后,有没有配置NVIC系统?

void NVIC_Configuration(void)
{

        #ifdef  VECT_TAB_RAM  
          /* Set the Vector Table base location at 0x20000000 */
          NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
        #else  /* VECT_TAB_FLASH  */
          /* Set the Vector Table base location at 0x08000000 */
          NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);   
        #endif
//-----------------------------------------------------
  //设置NVIC中断分组2:   即设定2位抢占优先级(从0到3),2位响应优先级(从0到3)
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         
//-----------------------------------------------------

}
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-1 22:53:53 | 显示全部楼层
warship 发表于 2018-7-1 22:46
在系统时钟配置完成后,有没有配置NVIC系统?

void NVIC_Configuration(void)

NVIC_PriorityGroupConfig()在进入main()时就设置了的。
其他的设置向量表什么的还没有学习,就用缺省的了。
不会是这里有问题吧?
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-1 23:04:21 | 显示全部楼层
xiatianyun 发表于 2018-7-1 22:53
NVIC_PriorityGroupConfig()在进入main()时就设置了的。
其他的设置向量表什么的还没有学习,就用缺省的 ...

按理说,向量表用缺省的应该不会有什么问题的,
我是经常在RAM中调试程序,所以常将向量表装入RAM中的。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-1 23:30:14 | 显示全部楼层
排除故障方法建议:
1不要开那么的中断,只编写一个典型的最简系统,便于查找定位问题。
2 或者直接移植原子的外部中断实验程序到你的板上看能否工作,再对照检查问题。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

6

主题

126

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
207
金钱
207
注册时间
2018-4-18
在线时间
10 小时
发表于 2018-7-2 16:25:57 | 显示全部楼层
很厉害 坚持下去
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-2 16:53:31 | 显示全部楼层
终于找到问题所在了。
我把原子教程中断试验5下载后以及把我得程序下载后对比debug,通过查看中文说明手册,把EXTI寄存器和EXTI_InitStruct变量作为检查对象发现了问题:
首先是看AFIO是否激活为1,发现原子程序是为1。
我的AFIO也是为1的,这点没有问题。
然后看EXTI_InitStruct变量参数:原子的初始化(声明变量后)还没有赋值时EXTI_LineCmd就是0x03,也就是ENABLE,而我的是0x00,即DISABLE。
所以原子的没有给EXTI_LineCmd赋值就能够通过EXTI_Init()初始化成功,而我的不成功。
解决问题就是添加一条语句:
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
[mw_shl_code=c,true]// KEY0 中断设置:
    // KEY0:接到PE4端口,上拉输入;
    // 设置PE4为线4的中断源。
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);
    //中断线4、下降沿触发。
    EXTI_InitStruct.EXTI_Line = EXTI_Line4;
    EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStruct.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStruct);[/mw_shl_code]
---------------------------------
这是什么原因呢?


回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-2 19:11:51 | 显示全部楼层
xiatianyun 发表于 2018-7-2 16:53
终于找到问题所在了。
我把原子教程中断试验5下载后以及把我得程序下载后对比debug,通过查看中文说明手册 ...

没有细看你的源代码,没有看出你漏掉了这一句。

正常情况下都是需要这一句的。
这句是清除中断屏蔽对应位的,
是不是原子在其它地方开启了?
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-2 20:34:20 | 显示全部楼层
暂时不知道教程里面的程序为什么会在初始化EXTI_InitStructure时就会使EXTI_InitStructur.EXTI_LineCmd=ENABLE。
不过通过这个坑,我发现有几个地方需要认真学习体会。
使用按键作为外部中断时,如果不作按键初始化(key_Init()),我发现还是可以触发中断的,不过有些无语:手指划过按键都会触发。
不过这种无序的触发在WKUP上没有发生,我分析是因为WKUP是下拉输入,而中断又设置成上升沿触发,不太容易受到电磁干扰。
而KEY0和KEY1是上拉输入,中断设置成下降沿触发,比较容易受电磁干扰。
至于为什么这样会容易受到电磁干扰,我不知道。
--------------------------
还有,如果不执行Key_Init(),执行读相应端口数据时返回值也是有的。比如:GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4),就会返回1,也就是上拉的1信号。
所以,怎样才能设计成只有相应总线时钟使能后才会执行读端口程序呢?
---------------------------



回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-2 20:49:02 | 显示全部楼层
本帖最后由 warship 于 2018-7-2 20:50 编辑
xiatianyun 发表于 2018-7-2 20:34
暂时不知道教程里面的程序为什么会在初始化EXTI_InitStructure时就会使EXTI_InitStructur.EXTI_LineCmd=ENA ...

我看原子的实验5 有这一句哦。
//外部中断0服务程序
void EXTIX_Init(void)
{

         EXTI_InitTypeDef EXTI_InitStructure;
         NVIC_InitTypeDef NVIC_InitStructure;

    KEY_Init();         //        按键端口初始化

          RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);        //使能复用功能时钟

    //GPIOE.2 中断线以及中断初始化配置   下降沿触发
          GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);

          EXTI_InitStructure.EXTI_Line=EXTI_Line2;        //KEY2
          EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;        
          EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
          EXTI_InitStructure.EXTI_LineCmd = ENABLE;
          EXTI_Init(&EXTI_InitStructure);                 //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器    ......
    以上片断倒数第二句
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-2 20:53:31 | 显示全部楼层
至于其它后续的没有这一句,
是因为都借用的是同一个结构体变量,
只须赋值一次,没有变化的话,后面可共用。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-2 21:51:12 | 显示全部楼层
warship 发表于 2018-7-2 20:49
我看原子的实验5 有这一句哦。
//外部中断0服务程序
void EXTIX_Init(void)

我的和你的不一样,我的是精英版:
void EXTIX_Init(void)
{

           EXTI_InitTypeDef EXTI_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;

    KEY_Init();         //        按键端口初始化

          RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);        //使能复用功能时钟



   //GPIOE.3          中断线以及中断初始化配置 下降沿触发 //KEY1
          GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
          EXTI_InitStructure.EXTI_Line=EXTI_Line3;
          EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;       
          EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
          EXTI_Init(&EXTI_InitStructure);                  //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器

   //GPIOE.4          中断线以及中断初始化配置  下降沿触发        //KEY0
          GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
          EXTI_InitStructure.EXTI_Line=EXTI_Line4;
          EXTI_Init(&EXTI_InitStructure);                  //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器


   //GPIOA.0          中断线以及中断初始化配置 上升沿触发 PA0  WK_UP
          GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);

          EXTI_InitStructure.EXTI_Line=EXTI_Line0;
          EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
          EXTI_Init(&EXTI_InitStructure);                //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-3 10:45:48 | 显示全部楼层
这几天碰到不能发帖求助的问题,新发帖无法显示滑动验证。
需要怎么设置呢?我的是Chrome浏览器。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-3 11:01:15 | 显示全部楼层
硬件的中断只需要满足条件就会触发,如果需要除了触发条件外还附带一些其他条件的话就无法满足。比如按键中断,如果需要防抖滤波的话就需要检测端口脉冲在一定时间内是否连续,不连续就认为是抖动信号。不加处理的话每个脉冲信号都会引发中断,这样可能不是我们需要的。
当然,按键中断防抖处理可以采取一些办法,我这几天就想办法。
不过我认为如果是按键输入的话还是采用查询的方式而不是中断方式比较合理。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-4 00:16:59 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-5 17:07 编辑

这几天学习外部中断,才发现还有几个专题没有学习:端口复用和重映射、位带操作。简单来说端口复用就是一个Pin既可以当普通GPIO用也可以被配置成另一种功能,当然同时只能用一个功能有效。就像键盘按键同一个键一般也是有两种按键键值。
复用的功能一般来说是内核外设,比如串口、CAN等等。(GPIO是片上外设。)
如果这些外设功能引脚都外引出的话芯片引脚就太多了。
并且在实际使用中也不会所有的引脚都用上,平衡起见就做了复用处理。
而端口重映射其实也是关于内核外设功能引脚如何配置到实际引脚上的。如果一个外设功能引脚所在缺省复用引脚和一些设计有冲突那么可以考虑用其他引脚来复用,这就叫重映射。
不过需要注意的是,无论是复用还是重映射都不是想用哪个pin就用那个pin的,都是定义好了的,需要查手册。
有一套复用和重映射编程顺序需要遵守,这个还没有试验。
这里带出辅助功能时钟AFIO,如果需要重映射就需要使能AFIO时钟。
AFIO时钟在操作如下功能时需要使能:复用功能重映射、外部中断线、事件输出。注意是复用功能重映射,不是复用功能,复用功能不需要开启AFIO时钟。
之前的外部中断就是首先使能AFIO时钟的。
--------------------------------------------------
位带操作在LED试验中用到了,不过一直没有引起注意,后来在中断试验环节出问题查看教程才发现有更简洁的操作。
这个概念看了很久才明白。
一开始以为是定义了一些宏来映射实际地址字节的bit位,后来发现不是。
又以为是定义了一些宏来把字节地址的bit位扩展为一个32位字地址空间,操作这个映射地址来转换操作这个位,存在个转换过程。
看完相关章节也没有发现如何转换操作。
我才明白这个不是软件转换过程。
这是硬件所具备的映射:操作映射地址自动操作位,不需要额外的转换。
那么就来看看是如何映射的,其实和寄存器映射也没什么不同。
首先要知道不是所有的地址空间都可以被映射的,只有SRAM的低1MB和外设低1MB空间实现映射。
我看了内存映射章节发现不论是SRAM的低1MB还是外设低1MB不是都可以提供给用户使用的,F103ZET只有64kBSRAM。而外设空间也只有部分提供使用,低1MB涵盖了APB1/APB2/AHB总线地址。
映射到哪里呢?就是不提供使用的保留区地址:SRAM是0x2200,0000开始的32MB空间,外设是0x4200,0000开始的32MB地址空间。
也就是说操作这些地址其实只能实现操作供使用空间的位。
一个位映射为32位字,对了,STM32一个字是4个字节,和以前所学不同。
一个位要么是1要么是0,映射后的字值只有低0位有效,所以,0xffff,ffff和0x0000,0001没有不同。
一个字节就要映射到8*4=32个字节空间去。太浪费了,这也回答了这是系统硬件提供的映射,如果是用户自定义的操作就1个bit映射到1个字节好了。
知道了这些,映射关系就好理解了。
不过似乎库里面没有关于位带映射的宏定义,在教程里面有原子提供的sys.h文件定义了这些常用位带映射。
-------------------
在中文参考手册里面倒是发现官方的说法不是位映射到32位字地址,而是反过来,是32位的字地址映射到位上来。哈。。。。。





回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-4 13:29:11 | 显示全部楼层
为什么官方库没有位带映射方面的头文件呢?
我想估计是因为SRAM的位带操作不常用,而且这时用户使用的区域,没必要由官方来定义。
而外设区的位带比较特殊,一般也就GPIO相关操作需要用到位带,而且也不是所有外设的寄存器位都可以毫无压力地做位带操作,有些寄存器如果做独立的位操作可能比较危险,当然,纯属我得看法。这样比较常用的位带操作就是关于GPIO的ODR和IDR寄存器,也就是GPIO的写和读操作。
官方一看,得,你们来吧。。。。。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-4 14:47:28 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-4 17:24 编辑

来看看原子提供的例程sys.h文件关于位带操作的宏定义。
主要是GPIO端口的读写位操作实现。
读写GPIO端口是通过ODR和IDR寄存器来实现的。
所以,需要映射的寄存器就是ODR和IDR。
库里面关于GPIO寄存器的分装是用结构来实现的,并没有独立映射某个寄存器。
不过库里面映射了GPIOA~GPIOG,可以看作是GPIO的首地址,而GPIO寄存器地址是这些首地址的偏移。
所以有如下关于ODR和IDR寄存器地址的宏定义:
[mw_shl_code=c,true]//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C
......
#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 [/mw_shl_code]
映射完寄存器地址后来看看是怎么和位带区进行映射的:
外设位带区开始地址是0x4200,0000,如果需要映射的是A.n,则公式是:
AliasAddr=0x42000000+ (A‐0x40000000)*32 + n*4
这里,0x4000,0000是外设低1MB开始地址,也即外设可以映射位带的开始地址。0x4000,0000映射到0x4200,0000。

(A-0x4000,0000)意味着A和起始地址的偏移,(A-0x4000,0000)*32是A之前寄存器所映射的位带区所占空间字节数,也即A映射到位带区的开始偏移地址。
n*4就是第nbit映射的偏移地址(1bit-->4byte)。bit0就意味着是A-0x4000,0000。也即n的值是0开始,bit0~bit7,当然n也可以超过7,一样计算。
这个公式计算了A.n这个位在外设位带区的映射地址。
*32就是<<5,而*4就是<<2。
所以,映射就是:
[size=14.6667px]AliasAddr=0x42000000+ ((A‐0x40000000) << 5 + n << 2)
看看如何把SRAM位带也用同一个映射表示。
他们的共同点都是位带区位于偏移0x0200,0000开始地址,所以先取得高4位,然后+0x200,0000.
(A & 0xF000,0000) + 0x200,0000 就是位带基址。
SRAM开始地址为0x2000,0000,所以首偏移可以用(A - (A & 0xF000,0000))表示。不过sys.h的做法是简单地(A & 0xFFFFF),也就是直接屏蔽了高12位的所有针对位段的偏移地址,只对低20位有效。
也就是A的地址如果除了高4位为位段外其他高8位有效的话这个定义就失效了。比如0x2010,0000,高12位为0x201,映射失效,只能是0x200x,xxxx开始的地址有效。外设地址是0x400x,xxxx有效。
不过GPIO的地址一般都是0x400x,xxxx。比如GPIOA的ODR寄存器是0x4001080C。
简单粗暴。
这样就有了宏定义:
[mw_shl_code=c,true]//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)) [/mw_shl_code]
第二个MEM_ADDR是把刚才的数值转换为指向unsigned long的指针,并定义为指针所指单元的数据值(取值)。第三个BIT_ADDR是实际需要使用的宏定义,使用BIT_ADDR(A,n)就得到了指向位带的指针,对该位带指针操作也就操作了位带映射的寄存器位或是内存某个位。
BIT_ADD()用在表达式左边是写操作,用在右边是读操作。
之后为了简单使用,又直接定义了读写GPIO端口的宏,比如
[mw_shl_code=c,true]//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 [/mw_shl_code]
这样读写就很明确了,看名字就可以了。
PAout(2) = 1;
sb = PAin(2);
--------------
不过对于ODR来说是可读可写的,所以PAout(2)既可以在=左边也可以在右边。



[size=14.6667px]



回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-4 18:02:40 | 显示全部楼层
个人感觉除了在自身取非操作比较简洁外,和原来的写法没什么不一样的。
比如如果要LED0取反,原来很麻烦,需要记住原来的状态或者用库函数读,读出后取反在回写。现在好了:
LED0 = !LED0;
回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-4 20:35:01 | 显示全部楼层
xiatianyun 发表于 2018-7-4 00:16
这几天学习外部中断,才发现还有几个专题没有学习:端口复用和重映射、位带操作。简单来说端口复用就是一个 ...

学习了,
其实我感觉官方的这种“32位的字地址映射到位上来”说法最准确。
这个才是STM32的设计师们真正所做的工作。
其实,32位的字地址是不存在的,物理上完全是不存在的。
他们借用这个正常情况下不存在的地址来做文章:
凡是读写这块物理不存在地址时,就把它转换成对相关位的操作。

如果反过来,则是我们要做的工作,我们是想对位进行操作,在知道设计者所定规则的前提下,去计算一个虚拟的地址。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-6 16:44:27 | 显示全部楼层
C语言中的整数类型。
C是和硬件紧密联系的开发语言,这就导致C中的一些数据类型因为历史的原因需要和具体硬件配合才能确定其长度。
最明显的就是整数类型了。
int是基本数据类型,和硬件相关。
在8位机中int就是8位长度,在16位机中就是16位长度,而在32位机中就是32位的长度,当然,如果不错的话在64位机中就是64位长度。
同样,type固定是8位长度,这是由历史原因决定的,大家认同了就是标准。
而1word则在8位机中是8位(比较少见),16位机中就是2byte16bits,比如西门子200PLC和300PLC。在32位机中1word就是4byte32位长度,64位机中是64位8byte。stm32是32位机,1word就是32位。PLC中会同时遇到数据类型和word一起使用的编程情况,很常见。而stm32则经常用到的是数据类型。这样有必要了解下stm32中的int类型长度。
int是基本类型,加上限定符可以有short int / unsigned int 这类。
short一般是16位长度的int,不过也只规定了不小于16位,而long int则不小于32位,且规定:short长度不大于int,而int长度不大于long.
64位需要用long long类型,这是c99的内容。
可见,stm32地址数值4G最多表示为32位,一个unsigned int就可以了。
而在位带操作中用了unsigned long来表示,这是为了通用性,一般现在的计算机表示地址是64位的。这里stm32中long 和int其实都是32位类型。
所以把位带操作中宏定义unsigned long 改为unsigned int是一样。
其他语言大多和机器特性无关,也不会关心一个int到底在机器中占多大的空间。

回复 支持 反对

使用道具 举报

31

主题

1951

帖子

3

精华

论坛元老

Rank: 8Rank: 8

积分
4418
金钱
4418
注册时间
2018-5-11
在线时间
923 小时
发表于 2018-7-6 18:40:54 | 显示全部楼层
xiatianyun 发表于 2018-7-6 16:44
C语言中的整数类型。
C是和硬件紧密联系的开发语言,这就导致C中的一些数据类型因为历史的原因需要和具体 ...

学习了,我基本上都是习惯用u8/u16/u32, 这样就不会有歧义了。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-9 22:54:29 | 显示全部楼层
最近在学习串口通讯,巨累。
回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-11 17:09:15 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-11 17:39 编辑

串口通讯。
串口通讯需要学习的东西很多,一次不能全部了解,就只能一步一步来了。
关于串口的一些物理层知识:
串行是相对与并行而言的,并行是一次发送接收多个位,比如一次发送8bit,而串行是把要通讯的数据按1bit1bit的发送和接收,也就是移位处理。
并行通讯效率高于串行,但总体通讯距离不如串行,这是由它们的物理接口决定的。
串行通讯是目前应用最广泛的通讯方式。
就物理接口而言,串行通讯大致分为RS232和RS485两种,当然这只是大致的分法,其他还有RS422等等。计算机上常用的USB也是串行通讯的一种。
常用的DB9是指9针口,只是物理接口。其实串行和几针口、接口是什么没有太大的关系,那只是工业应用上的常规做法,就像插座,可以使用家用插座也可以使用工业插座,可以是方形的也可以是圆形的,甚至可以直接连接。
但是,还是有必要知道些事实的。RS232常用DB9而485一般使用普通连接器。
从通讯双方的协调关系上又分全双工、半双工、单工几种。全双工像手机打电话,双方可以各说各的,半双工像对讲机,一方说完另一方才能说。单工就比较少见了,只许一方说另一方只能听,有意见也不能提。
当然,RS232、RS485、RS422是全双工还是半双工其实需要看具体接线和实际程序。

物理接口除了外在看得见的接口不同外,其实更重要的是使用的电压不同。
电子电路常用的是TTL电平,TTL电平表示1是用2.4~5V来表示,0使用的是0~0.5V,中间还有一段0.5~2.4V不确定,其实就是保持原来的逻辑不变。
RS232更多的是表示逻辑电平,RS232表示1是用-15~-3V,0使用的是+3~+15V,不是说RS232有30V的电压,而是说RS232表示逻辑的电平够宽泛。
如果要把电子电路的电平转换为RS232就需要转换电路。
-----------------------------------



回复 支持 反对

使用道具 举报

29

主题

338

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1181
金钱
1181
注册时间
2018-4-13
在线时间
170 小时
 楼主| 发表于 2018-7-11 17:27:08 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-11 23:36 编辑

精英版没有直接的RS232接口,是把芯片的串口经过转换成为USB接口,使用USB来模拟串口的,需要在电脑端安装RS232-USB驱动才能模拟串口。
STM32F103的有5个通讯口,其中3个是可以同步异步通讯的,有2个只能异步通讯,如果只作为串口使用的话就有5个了。
同步就是双方需要一个共用的通讯时钟信号,在此时钟的脉动下进行通讯。而异步不需要共用的时钟信号,它们的协调靠通讯数据上的额外数据来协调。当然,双方必须在一致的通讯频率下来通讯。
RS232有9根通讯线,其中就有用作同步的时钟信号线。如果用作异步的话就不需要这些信号线了,所以现在一般RS232只有3根通讯线:一根是接收信号线RXD,一根是发送信号线TXD,一根用作电平公共端。
收发独立,当然就有了全双工的可能。
-----------------------------------------
RS232虽然是全双工通讯,不过在一次收或一次发过程中是需要时间的,这个耗时主要是因为把数据从寄存器发到另一端需要时间,更何况另一端是什么设备是不知道的,可能是同样的设备也可能不是,设备是否准备好也不知道,设备硬件特性是否有延时也是不得而知的。所以一次通讯无论是收还是发都需要等待上次任务完成才能进行。比如发送一串数据,程序把数据串分割成独立字节的数据送到串口,串口是把这个独立字节数据按位依次发送出去,发送完一个字节为一次发送任务。在下个字节发送前需要等待上个字节发送完成,如果不这样则可能会忽略下个字节的发送。
这个可以用试验来验证:
串口通讯试验中,在主程序里面当接收到数据后把接收到的数据原样发送到电脑端,原程序使用循环发送接收缓冲区数据,在执行下个循环前用while来检测是否发送完毕。
[mw_shl_code=c,true]for(int t = 0; t < len; t++)
            {
                bTimer2_TxEnb = TRUE;
                USART_SendData(USART1, BUsart1_RxBuffer[t]); //把接收到的缓冲区[t]数据发送到串口1.
                while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待串口1发送单次数据(1byte)完成。               
            }[/mw_shl_code]
如果注释掉while,则程序是:
[mw_shl_code=c,true]for(int t = 0; t < len; t++)
            {
                bTimer2_TxEnb = TRUE;
                USART_SendData(USART1, BUsart1_RxBuffer[t]); //把接收到的缓冲区[t]数据发送到串口1.
                //while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待串口1发送单次数据(1byte)完成。               
            }[/mw_shl_code]
测试发现,for循环极快就结束,而电脑端只接收到一个字节的数据。
再次从电脑端发送到开发板,开发板回传数据还是只有一个字节的数据,但有趣的是并非缓冲区0的数据。
多次测试发现回传的数据很随机。为什么不是索引0的数据呢?
原因是:for的结束和发送是不同步的,当for结束时可能一次发送都还没有结束。但是,当发送开始时下个待发送数据可以进入发送数据寄存器了,所以下次for时发送的并非0索引的数据,可能是0索引数据也可能是1索引数据,整体看是随机的。(事实上更可能是接收数据的前半段的任一个)
所以,必须等待上次发送成功后才能发送下个数据。
接收数据同样的道理,不过教程使用接收中断,只有接收到数据(也即接收结束)才会触发中断处理。
发送数据也可以采用中断的做法。
不过如果是处理完一个字节就中断似乎又不太经济,会占用过多CPU资源。

回复 支持 反对

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-11-24 06:37

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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