-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
258 lines (258 loc) · 136 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[802.11无线网络]]></title>
<url>%2Fposts%2F8965%2F</url>
<content type="text"><![CDATA[有线->无线优点: 移动性 弹性(快速部署) 成本 无线网络特点:对有线网络的互补而非取代 MAC:介质访问控制 指派给802.11网络卡的MAC地址与以太网来自同一地址库,对外面的网络而言,没有区别。802.11的MAC地址也会出现在ARP列表中 只要时802网络就必然会同时具备MAC与PHY两种元件,MAC用于决定如何访问介质与传输介质的规则,PHY负责传送与接收的细节。 CSMA/CD:载波侦听/碰撞检测 CSMA/CA:载波侦听/冲突避免 虚拟载波侦听由网络分配矢量( Network Allocation Vector,简称 NAV)所提供 LLC:逻辑链路控制层 802.11两种物理层:调频展频FHSS 直接序列展频DSSS BSS:Basic Service Set,基本服务集。802.11 网络基本组件,每个 BSS 包含一组逻辑上彼此关联的工作站。 DS:分布式系统(Distribution System),用来串联接入点的一组服务。逻辑上包含有线骨干网络和桥接功能。 MIC:消息完整性校验码(message integrity code)。针对一组欲保护数据计算出的散列值,用以防止数据遭篡改。 IV:初始化向量(Initialization Vector),加密标头中公开的密钥材料。 ICV:完整性校验值(Integrity Check Value),数据帧的校验码,用于防止数据遭篡改。 Michael:数据完整性的校验算法,由 TKIP 规范。 TLS:传输层安全协议(TLS)是确保互联网上通信应用和其用户隐私的协议(RFC 2246)。当服务器和客户机进行通信,TLS 确保没有第三方能窃听或盗取信息。TLS 是安全套接字层(SSL)的后继协议。 无线局域网的基本构成单位是基本业务集 BSS,它提供一个覆盖区域,使 BSS 中的站点保存充分连接。 BSS 支持两种拓扑结构:独立基本服务集 IBSS:称为特别网络(ad hoc network)。是一个独立的 BSS,没有中枢链路基础结构,一个最小的 IBSS 最少包含两个工作站( STA),工作站之间可以直接通信。这种操作模式能够形成灵活的组网机制,且不需要事先规划。 基础结构型基本服务集 Infostructure BSS: STA 必须与 AP 建立关联,才能取得网络服务。 多个 BSS 可以构成一个扩展网络(ESS 网络)。 ESS 内部的 STA 可以互相通信,但是 STA通信是必须通过 AP 进行。连接多个 AP(BSS 的组件)称为分布系统( DS)。 无线介质本身也可以做为传输系统。此种无线传输系统(wireless distribution system,简称 WDS)的配置通常称为「无线桥接器」 (wireless bridge)配置,因为它允许网络工程师在链路层连接两个局域网络。无线桥接器可用来快速连接不同的网段,十分适合访问供应商( access provider)使用。 TPC 是在 802.11h 所定义的新服务。 欧洲标准要求作业于 5 GHz 频段的工作站必须能够控制电波的传输功率,避免干扰其他同样使用 5 GHz 频段的用户。传输功率控制也有助于避免干扰其他无线局域网络。传输距离是传输功率的函数;工作站的传输功率愈高,传输距离就愈远,也就愈容易干扰邻近的网络。如果可以 将传输功率调到“刚刚好”( just right),就可以避免干扰到邻近的工作站。 动态选频(DFS),开发的目的主要是为了在欧洲地区避免干扰 5GHz 频段的雷达系统。 就 802.11 而言,基站之间可能出现三种转换: 不转换 BSS 转换 ESS 转换 (未支持) 和其他链路层协议不同,802.11 采用正面回应机制。所有传送出去的帧都必须得到回应,只要任何一个环节失败,该帧即视为已经丢掉。 在无线网络中,由隐藏节点所导致的碰撞问题相当难以监听,因为无线收发器通常是半双工工作模式, 即无法同时收发数据。 为了防止碰撞发生, 802.11 允许工作站使用请求发送( RTS)和允许发送(CTS)帧来清空传送区域。由于 RTS 与 CTS 帧会延长数据交易过程,因此 RTS帧、 CTS 帧、数据帧以及最后的应答帧均被视为相同基本连接的一部分。 DCF 是标准 CSMA/CA 访问机制的基础。和以太网一样,在传送数据之前,它会先检查无线链路是否处于空闲状态。为了避免冲突发生,当某个传送者占据频道时,工作站会随机为每个帧选定一段延后时间。在某些情况之下, DCF 可利用 CTS/RTS 空闲技术,进一步减少碰撞发生的可能性。点协调功能提供的是免竞争服务。 称为点协调者的特殊工作站可以确保不必通过竞争即可使用介质。点协调者位于基站,因此只有基础型网络才会使用 PCF。为了赋予比标准竞争式还高的优先性,PCF 允许工作站经过一段较短的时间即可传送帧。PCF 在实际上并不常见有些应用需要尽力传达更高一级的服务质量,却又不需要用到 PCF 那么严格的管控。 HCF允许工作站维护多组服务队列,针对需要更高服务品质的应用,则提拔更多的介质访问机会。HCF 尚未完全标准化,不过最终将成为802.11 标准的一部分。 802.11 会用到四种不同的帧间隔: SIFS <PIFS <DIFS <EIFS 定期发送 Beacon,主要是用来声明某个网络的存在。STA 根据 Beacon 得知某个网络存在,从而调整加入该网络所必须的参数。 控制帧和管理帧: 控制帧主要用于协助数据帧的传递,可用于管理无线媒介的访问、提供MAC层的可靠性。以下只讲帧类型,不讲帧结构。1.1 RTS帧:用来取得媒介的控制权,用于传送分段帧,分段由网卡驱动程序中的RTS threshold阀值确定。1.2 CTS帧:用于回复RTS帧,没有RTS就没有CTS。1.3 ACK帧:MAC及任何数据的传送(包括一般传送,RTS/CTS交换之前的帧、帧片段)所需要的肯定确认。1.4 PS-POLL帧:移动式工作站从省电模式苏醒后,回向AP传送一个PS-POLL帧用于获得缓存帧。 管理帧:802.11的管理过程分为3步,首先移动式工作站找出可供访问的兼容无线网络,其次网络系统对移动式工作站进行验证,最后移动式工作站与AP之间建立关联。帧主体有固定字段(长度固定的字段)和信息元素(长度不定的数据块) 管理帧的类型3.1 Beacon(信标)帧:主要用于声明某个网络的存在3.2 Probe Request 帧:移动式工作站用于扫描所在区域内有哪些802.11网络。3.3 Probe Resonse帧:如果Probe Request所检查的网络与之兼容,该网络用Probe Request响应。3.4 IBSS ATIM帧:IBSS without AP,无法依赖AP缓存帧,IBSS工作站为处于休眠状态的接收者缓存帧,就会在传递期间送出一个ATIM帧通知对方有消息待传。3.5 Disassociation帧与Deauthentication帧:用于结束一段认证关系3.6 Association Request帧:移动式工作站找到兼容网络并通过身份验证,便发送Association Request帧以试图加入网络。3.7 Reassociation Request 帧:位于相同扩展服务区域,但在不同基本服务区域间游走的移动式工作站,再次使用分布式系统时,必须与网络重新关联。3.8 Association Response帧与Reassociation Response帧:3.9 Authentication帧:3.10 Aciton 帧:用于触发测量动作。 802.11 工作站可以关闭无线电波收发器,并且定期进入休眠状态,以维持最长的电池使用时间。在这段期间,基站会为每部处于休眠状态的工作站暂存帧。若有暂存帧,基站会在后续的 Beacon 帧中告知工作站。由省电状态唤醒的工作站可以使用 PS-Poll 帧取得这些暂存帧。 ACK 或 CTS 之类的应答帧必须以基本速率组合所包含的速率传送, 但不能高于这次传输所使用的起始帧。应答帧必须使用与起始帧相同的调制方式(DSSS、CCK或OFDM)。 无线基站的核心,其实就是桥接器,负责在无线与有线介质之间转换帧。 WEP 用来保护数据的 RC4 密码锁属于对称性密钥串流密码锁 无线加密的多种方法及其区别(WEP WPA TKIP EAP)无线网络的安全性由认证和加密来保证。认证允许只有被许可的用户才能连接到无线网络;加密的目的是提供数据的保密性和完整性(数据在传输过程中不会被篡改)。802.11标准最初只定义了两种认证方法: 开放系统认证(Open System Authentication) 共享密钥认证(Shared Key Authentication)以及一种加密方法: 有线等效保密(Wired Equivalent Privacy – WEP)对于开放系统认证,在设置时也可以启用WEP,此时,WEP用于在传输数据时加密,对认证没有任何作用。对于共享密钥认证,必须启用WEP,WEP不仅用于认证,也用于在传输数据时加密。WEP使用对称加密算法(即发送方和接收方的密钥是一致的),WEP使用40位或104位密钥和24位初始化向量(Initialization Vector – IV,随机数)来加密数据。注:使用初始化变量(IV)的目的是避免在加密的信息中出现相同的数据。例如:在数据传输中,源地址总是相同的,如果只是单纯的加密(WEP使用静态密码),这样在加密的信息中会出现相同的数据,有可能被恶意地破解。由于初始化变量(IV)是随机数,可以避免这种情况的出现。在配置无线网络的安全性时,一般将40位/104位密钥写成密钥长度:64位(40+24)/128位(104+24)由于WEP有一些严重缺陷,如初始化向量的范围有限,而且是使用明文传送……,802.11使用802.1x来进行认证、授权和密钥管理,另外,IEEE开始制订802.11i标准,用于增强无线网络的安全性。同时,Wi-Fi联盟与IEEE一起开发了Wi-Fi受保护的访问(Wi-Fi Protected Access – WPA)以解决WEP的缺陷WPAWPA不同于WEP,WPA同时提供认证(基于802.1x可扩展认证协议 – Extensible Authentiation Protocl - EAP的认证)和加密(临时密钥完整性协议 – Temporal Key Integrity Protocol – TKIP)。WPA的认证 – 802.1x802.1x最初设计用于有线网络,但对无线网络也适用。802.1x使用认证服务器在无线网卡和无线访问点(AP)之间提供基于端口的访问控制和相互认证。802.1x体系结构包括下列3部分:(1)Supplicant:要访问网络的设备,通常是802.11客户端(2)Authenticator:客户端和认证服务器的中间设备,在客户端和认证服务器之间传递信息。对于无线网络来说通常为无线访问点(AP)。(3)Authentication Server(认证服务器):对Supplicant进行实际身份验证的设备,通常是远程认证拨号用户服务(Remote Authentication Dial-In User Service - RADIUS)服务器韩梅梅预约好了去拜访某公司老总,李雷也要去。到了公司门口,保安要求两人出示身份证明,保安通过电话与老总联系后,老总确认后通知可以让韩梅梅进来,李雷不能进去。保安根据老总的指示,对韩梅梅放行,拒绝李雷进入。韩梅梅和李雷相当于802.1x体系中的supplicant,保安相当于authenticator,只起一个传送信息的作用,而不是进行实际的身份验证,老总相当于authentication server,进行实际的身份验证。Supplicant、authenticator、authentication server三者之间需要通信。Supplicant和authenticator之间使用EAPoL通信,authenticator将EAP封装在其他高层协议(如RADIUS)中与authentciation server通信或authenticator将EAPoL转换为其他认证协议(如RADIUS)传递给authentication server。EAP和EAPoL我对EAP和EAPoL的区别一直很模糊。这次好好看了些资料,总结如下:EAP可扩展认证协议(EAP)是一个认证框架,而不是一种特定的认证机制,EAP提供一些公共的功能,并且允许协商认证机制(EAP方法)。EAP规定如何传输和使用由EAP方法产生的密钥材料(如密钥、证书等等)和参数。无线网络中常用的EAP方法(认证机制)包括EAP-TLS、EAP-SIM、EAP-AKA、LEAP和EAP-TTLS。EAPoL(EAP over LAN)EAP只定义了消息格式,每种使用EAP的协议必须定义一种将EAP消息封装到该协议消息中的方法。802.1x中定义了将EAP消息封装到802中的方法,这种方法称为EAP over LAN – EAPoL。所以EAPoL实际上是一种传送机制。实际的认证方法是EAP方法来指定。802.1x认证的核心是authenticator上端口的打开与关闭。对于合法用户(认证通过的用户)接入时,端口打开,可以自由访问网络;对于没有用户接入或非法用户(认证未通过的用户)接入时,端口关闭,这样就不能访问网络。 802.1x认证过程中还使用了动态密钥以加密数据。WPA的加密 – TKIP临时密钥完整性协议(Temporal Key Integrity Protocol – TKIP)也是对称加密方法,使用RC4算法,TKIP使用128位临时密钥和48位初始化向量(IV)总结WPA的优点:(1)WPA利用802.1x认证提供强力的访问控制;(2)TKIP使用动态密钥WPA缺点:(1)由于TKIP使用RC4算法,安全隐患(2)复杂的认证和加密导致性能降低]]></content>
<tags>
<tag>无线</tag>
</tags>
</entry>
<entry>
<title><![CDATA[AP和STA模式]]></title>
<url>%2Fposts%2F2039%2F</url>
<content type="text"><![CDATA[wifi模块包括两种工作模式AP和STA。 AP:(Access Point)无线接入点。 STA:(Station)每一个连接到无线网络中的中断都可称为一个站点。 工作在AP模式 工作在AP模式下,手机、PAD、电脑等设备可以直接连上模块,可以很方便对用户设备进行控制 工作在STA模式 这是一种基木的组网方式,由一个AP和许多STA组成,如下图。其特点是AP处于中心地位,STA之间的相互通信都通过AP转发完成。该模式下,WIFI模块工作在STA(CLIENT)模式。通过适当的设置,COM的数据与WIFI的网路数据相互转换。 AP+STA 感觉就是把路由当作中继使用了。]]></content>
<tags>
<tag>AP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[点运算符和箭头运算符的区别]]></title>
<url>%2Fposts%2F48012%2F</url>
<content type="text"><![CDATA[写链表的时候突然想到.和->的区别是什么,之前纠结了很久,但是因为不用C语言也就没去查找。 点运算符“.”应用于实际的对象,箭头运算符“->”与一个指针对象的指针一起使用。 对于一个结构体: 123struct A{ int a;}; 如果有一个变量A n,那么使用其中的成员元素时: n.a = 1; 但如果用指针的方法访问,A *n,那么使用他的元素时也要用指针 (*n).a = 1; 或者直接: n->a = 1; 所以,箭头的左边必须为指针,点号的左边必须为实体。]]></content>
<tags>
<tag>C语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[错题(1)]]></title>
<url>%2Fposts%2F61141%2F</url>
<content type="text"><![CDATA[题目:输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组。 开始没用指针做,觉得还挺简单的。后来发现参考程序是用指针写的,就想着也用指针写一下,然后就发现了一堆错误。。 第一次代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <stdio.h>void input(int a[]);void output(int a[]);void max_min(int a[]);int main(){ int a[5]; input(a); max_min(a); output(a); return 0;} void input(int a[]){ for( int i = 0; i < 5; i++ ){ scanf( "%d", &a[i] ); }}void output(int *a){ int *p; for( p = a; p < a+5; p++ ){ printf( "%d,", *p ); } }void max_min(int a[]){ int *p; int *max, *min, *t1, *t2; max = min = a; for( p = a; p < a+5; p++ ){ if( *max < *p ){ max = p; } if( *min > *p ){ min = p; } } if( *a != *max ){ *t1 = *a; *a = *max; *max = *t1; } if( *(a+4) != *min ){ *t1 = *(a+4); *(a+4) = *min; *min = *t1; } } 运行程序发现,输出结果错误。注释掉 max_min 函数的后面两个if语句,打印此时的 max 和 min 发现最大值和最小值无误。所以问题在于后面的两个if语句中。观察代码发现,因为我输入的5个数是1,2,3,4,5.所以此时max指向的内存地址为a+5的地址,而min指向的地址为a的地址,而我直接赋值 a 就导致了前面定义的所有指向a内存空间的指针里的值发生了改变,这时 min 指向 a ,所以 min的值也发生了改变。 第二次代码: 1234567891011121314151617181920212223242526void max_min(int a[]){ int *p; int *max, *min, *t1 ; max = min = a; int k, l; for( p = a; p < a+5; p++ ){ if( *max < *p ){ max = p; } if( *min > *p ){ min = p; } } k = *max; l = *min; if( *a != k ){ *t1 = *a; *a = k; *max = *t1; } if( *(a+4) != l ){ *t1 = *(a+4); *(a+4) = l; *min = *t1; } } 运行程序还是错误,仔细观察代码发现,在函数中只是声明了 *t1 这个指针,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间,指针变量并未被初始化为指向任何现有的内存空间,又因为他是自动变量,所以无法被初始化。然后将t1指向a的内存空间,犯了第一次的错误。。 第三次代码: 12345678910111213141516171819202122232425void max_min(int a[]){ int *p; int *max, *min; max = min = a; int j, k, l; for( p = a; p < a+5; p++ ){ if( *max < *p ){ max = p; } if( *min > *p ){ min = p; } } k = *max; l = *min; if( *a != k ){ j = *a; *a = k; *max = j; } if( *(a+4) != l ){ j = *(a+4); *(a+4) = l; *min = j; } 成功完成题目。。]]></content>
<tags>
<tag>C语言</tag>
<tag>错题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C语言关键字]]></title>
<url>%2Fposts%2F41598%2F</url>
<content type="text"><![CDATA[C语言最早有32个关键字。其中auto,register,extern,static这4个提供4种存储类别。四种存储类型中有两种存储期:自动存储期和静态存储期。 其中auto和register对应自动存储期,具有自动存储期的变量在进入声明该变量的程序块是被建立,在该程序块活动的时候存在,退出该程序块时撤销。实际上,自动变量是一个局部变量,其作用域为包含他的代码块。代码块是被包含在花括号中的一段代码。 自动变量通常存储在栈中。这意味着执行代码块时,其中的变量依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量。 extern,static对应静态存储期,静态存储在整个程序执行期都存在。分为两块参考:.date 已初始化的全局变量或静态变量 .bss 存放未初始化或初始值为0的全局或者静态变量。静态变量的初值是在编译时就进行了初始化,用static修饰的变量赋过数值的话就保存为他的初值,如果没有初始化的话就赋值为零,且整个程序只初始化一次。 new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间(free store)或堆(heap)。该内存池同用于静态变量和自动变量的内存是分开的。new和delete让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的声明周期不完全收程序或函数的生命时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的,单new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。 autoC语言和C++中auto关键字的使用有很大区别。在C语言中使用auto关键字声明一个变量为自动变量,是C语言中应用最广泛的一种类型,在函数内定义变量时,如果没有被声明为其他类型的变量都是自动变量,也就是说,省去类型说明符auto的都是自动变量。当省略数据类型,只使用auto修饰变量,在C语言中默认变量为int型 register如果一个变量被register来修辞,就意味着,该变量会作为一个寄存器变量,让该变量的访问速度达到最快。比如:一个程序逻辑中有一个很大的循环,循环中有几个变量要频繁进行操作,这些变量可以声明为register类型。 寄存器变量:寄存器变量是指一个变量直接引用寄存器,也就是对变量名的操作的结果是直接对寄存器进行访问。寄存器是CPU的亲信,CPU操作的每个操作数和操作结果,都由寄存器来暂时保存,最后才写入到内存或从内存中读出。也就是说,变量的值通常保存在内存中,CPU对变量进行读取先是将变量的值从内存中读取到寄存器中,然后进行运算,运算完将结果写回到内存中。 在使用寄存器变量时,请注意: 待声明为寄存器变量类型应该是CPU寄存器所能接受的类型,意味着寄存器变量是单个变量,变量长度应该小于等于寄存器长度 不能对寄存器变量使用取地址符“&”,因为该变量没有内存地址 尽量在大量频繁的操作时使用寄存器变量,且声明的变量个数应该尽量的少 static静态全局变量:当进程中一个全局变量被static修饰后(被声明为static),则该全局变量被称为静态全局变量。它的作用域是仅在它的源文件内,其它源文件都无法访问它。 全局变量:隐式被static修饰的全局变量,作用域也是仅在它的源文件内,不能被其它源文件访问。但与静态全局变量(显式被static修饰的全局变量)不同的是全局变量在其它源文件中可以通过extern声明后访问,而静态全局变量则无法访问。 静态局部变量:局部变量被static修饰后,则称为静态局部变量。静态局部变量则被存放在data段内(定义时,如果用户没有初始化,编译器会自动将其初始化为0),而且整个进程周期中,只定义和初始化一次,每次调用局部函数时,静态局部变量都会维持最后一次修改的值,作用域是局部代码段。 static函数:被关键字static修饰的函数。static函数作用域是源文件,即其他源文件无法调用该函数,类似的private函数。 代码: 1234567891011121314151617181920#include <stdio.h>int a=1;int f(int c){ static int a=2; c++;return (a++)+c; }main(){ int i,k=0; for(i=0;i<2;i++) { int a=0; k+=f(a); } k+=a; printf("%d\n",k); } 此时,变量a作为全局变量、局部静态变量和局部变量存在。但a作为不同变量存在时作用域也不同,并且没有联系。(ps: a++:先执行表达式后再自增,执行表达式时使用的是a的原值.++a:先自增再执行表达示,执行表达式时使用的是自增后的a。)然而,实际开发中,这么写真的不会被打死吗。。 externextern声明变量:表明该变量在其他源文件里已经被定义,此处需要使用。extern声明的变量必须是在其他源文件内的非静态的全局变量。 volatilevolatile字面意思是易挥发,易变化的意思,它修辞的变量表示该变量的值很容易由于外部因素发生改变,强烈请求编译器要老老实实的在每次对变量进行访问时去内存里读取。 12345int a = 10;int b = a;int c = a; 编译器扫描了代码发现上面,第一行代码在将10赋给了整形变量a,之后a变量的值没有再发生改变。在后面第二行中,将a变量里的值取出来赋给b变量。在第三行代码里将a变量的值赋给c的时候,因为CPU访问内存速度较慢,编译器为了提高效率,直接将10赋给了c。上述代码如果运行在多线程中,在一个线程上下文中没有改变它的值,但是我们不能保证变量的值没有被其它线程改变。为了防止在类似的情况下,编译器玩省事,可以将这些变量声明为volatile,这样,不管它的值有没有变化,每次对其值进行访问时,都会从内存里,寄存器里读取,从而保证数据的一致,做到表里如一。]]></content>
<tags>
<tag>C语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[尾调用和尾递归]]></title>
<url>%2Fposts%2F5759%2F</url>
<content type="text"><![CDATA[尾调用尾调用是指一个函数的最后一个动作是一个函数调用的情况,即这个调用的返回值直接被当前函数返回。调用函数foo之后,还有别的操作,所以不属于尾调用,即使语义一样。调用后也有别的操作,所以不属于尾调用,即使写在同一行。 栈帧栈帧将栈分割成几个记录块,每一块大小不相同。这些记录块是编译器用来实现函数调用的数据结构,用于记录每次函数调用所涉及的相关信息的记录单元。栈帧是函数的执行环境,它包括函数的参数,函数的局部变量,函数执行完之后要返回的位置等。 尾调用的优化函数调用会在栈上形成一个栈帧,如果函数A调用函数B,那么在A的栈帧下方,还会形成B的栈帧。等到B函数的返回,B的栈帧才会消失。如果函数B又调用函数C,那么B的下方又会形成C的栈帧。以此类推,所有的栈帧堆叠起来,就形成一个“调用栈”。 由于尾调用是外层函数的最后一步操作,尾调用返回后,外层函数也就返回了。执行尾调用的时候,外层函数栈帧中保存的调用位置、内部变量等信息都不会再用到了,所以,可以用内层函数(即尾调用函数)的栈帧覆盖外层函数的栈帧,而不是在外层函数栈帧下面再新开一个,这样可以节省占空间。这就叫尾调用优化。 尾递归若一个函数在尾位置调用自身,就是尾递归。尾递归是递归的一种特殊情况。每一次递归时return的内容不再是一个表达式,而是这个函数本身。这样使用一个栈帧就可以了,这个函数有着良好的栈帧复用性。]]></content>
<tags>
<tag>C语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C语言显示拓展的ascii码]]></title>
<url>%2Fposts%2F8475%2F</url>
<content type="text"><![CDATA[今天做题时,发现原本应该显示图案变成了乱码。 源码: 12345678910#include "stdio.h"int main(void){char a=176,b=219;printf("%c%c%c%c%c\n",b,a,a,a,b);printf("%c%c%c%c%c\n",a,b,a,b,a);printf("%c%c%c%c%c\n",a,a,b,a,a);printf("%c%c%c%c%c\n",a,b,a,b,a);printf("%c%c%c%c%c\n",b,a,a,a,b);} 网上查了一下原因,176的16进制是B0,219的16进制是DB,0xB0DB是“佰”字的内码,所以输出的就是“佰”了。主要原因是文件信息的代码页不同,我们所使用的操作系统中文状态下的代码页,要显示扩展的ASCII码需要在437 OEM-美国这个下面显示,这样就可以显示出你所希望的。 修改方法: 1.右击运行界面上边框,选择默认值一项 2.选中使用旧版控制台,重启窗口 3.修改默认代码页,936(ANSI/OEM-简体中文GBK)为437 OEM-美国 4.关闭后重新运行一下即可 修改后:]]></content>
<tags>
<tag>C语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[打印]]></title>
<url>%2Fposts%2F36499%2F</url>
<content type="text"><![CDATA[123456789101112131415161718192021222324#include <stdio.h>int main(){ printf ( "\n\ .::::.\n\ .::::::::./\n\ ::::::::::: FUCK YOU\n\ ..:::::::::::'\n\ '::::::::::::'\n\ .::::::::::\n\ '::::::::::::::..\n\ ..::::::::::::.\n\ ``::::::::::::::::\n\ ::::``:::::::::' .:::.\n\ ::::' ':::::' .::::::::.\n\ .::::' :::: .:::::::'::::.\n\ .:::' ::::: .:::::::::' ':::::.\n\ .::' :::::.:::::::::' ':::::.\n\ .::' ::::::::::::::' ``::::.\n\ ...::: ::::::::::::' ``::.\n\ ```` ':. ':::::::::' ::::..\n\ '.:::::' ':'````..\n"); return 0; } 以上是一段打印的代码,两点需要注意: 转义字符 在 C 语言中,用双引号括起来的内容我们称之为字符串,也就是我们平时所说的文本。 字符串可以由可见字符和转义字符组成,可见字符就是你输入什么,显示出来就是什么。 而你如果想将一个字符串分为两行来显示,那么你就需要使用到转义字符。 转义字符一般是表示特殊含义的非可见字符,以反斜杠开头: 反斜杠 在字符串中反斜杠 + 字符是转义字符,表示特殊含义。 但反斜杠如果后边不带任何字符(直接换行),表示我们希望 C 语言将该行以及下一行看做是一个整体。]]></content>
<tags>
<tag>C语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[输入捕获]]></title>
<url>%2Fposts%2F36750%2F</url>
<content type="text"><![CDATA[stm32的输入捕获:通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。 步骤1:设置输入捕获滤波器(通道1为例)输入捕获寄存器IC1F[3:0],这个用来设置输入采样频率和数字滤波器长度。其中,fCK_INT是定时器的输入频率(TIMxCLK),一般为72MHz,而fDTS则是根据TIMx_CR1的CKD[1:0]的设置来确定的,如果CDK[1:0]设置为00,那么fDTS=fCK_INT。N值就是滤波长度,举个简单的例子:假设IC1F[3:0]=0011,并设置IC1映射到通道1上,且为上升沿触发,那么在捕获到上升沿的时候,在以fCK_INT的频率,连续采到8次通道1的电平,如果都是高电平,则说明却是一个有效的触发,就会触发输入捕获中断。这样就可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤波的效果。 步骤2:设置输入捕获极性(通道1为例)CC1P:输入/捕获1输出极性 CC1通道配置为输出: 0:OC1高电平有效 1:OC1低电平有效 CC1通道配置为输入: 该位选择是IC1还是IC1的相反信号作为触发或捕获信号。 0:不反相:捕获发生在IC1的上升沿:当用作外部触发器时,IC1不反相。 1:反相: 捕获发生在IC1的下降沿:当用作外部触发器时,IC1反相。 步骤三:设置输入捕获映射通道(通道1为例) CC1S[1:0]:捕获/比较1选择 这2位定义通道的方向(输入/输出),及输入脚的选择: 00:CC1通道被配置为输出 01:CC1通道被配置为输入,IC1映射在TI1上 10:CC1通道被配置为输入,IC1映射在TI2上 11:CC1通道被配置为输入,IC1映射在TRC上。此模式仅工作在内部触发器输入被选中时(由TIMx_SMCR寄存器的TS位选择) CC1S仅在通道关闭时(TIMx_CCER寄存器的CC1E=‘0’)才是可写的。 步骤四:设置输入捕获分频器(通道1为例) IC1PSC[1:0]输入/捕获1预分频器 这2位定义了CC1输入(IC1)的预分频系数,一旦CC1E=‘0’时(TIMx_CCER寄存器中),则预分频器复位。(分别位1、2、4、8触发一次捕获) CC1E:输入/捕获1输出使能 CC1通道配置为输出 0:关闭——OC1禁止输出 1:开启——OC1信号输出到对应的输出引脚 CC1通道配置为输入 该位决定了计数器的值是否能捕获TIMx_CCR1寄存器 0:禁止 1:使能 步骤五:捕获到有效信号可以开启中断]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[C语言学习随笔(1)]]></title>
<url>%2Fposts%2F48322%2F</url>
<content type="text"><![CDATA[用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) 1#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL UL(表示无符号长整型),如果不写UL后缀,系统默认为:int, 即,有符号整数。 1.数值常数有:整型常数、浮点常数;2.只有数值常数才有后缀说明;3.数值常数后缀不区分字母大小写。(1)整型常数的表示形式有:十进制形式、以0开头的八进制形式、以0x开头的十六进制形式,无二进制形式。整型常数默认是signed int的。对整型常数进行类型转换的后缀只有:u或U(unsigned)、l或L(long)、u/U与l/L的组合(如:ul、lu、Lu等)。例:100u; -123u; 0x123l;(2)浮点常数的表示形式有:科学计数形式和小数点形式。浮点常数默认是double的。对浮点常数进行类型转换的后缀只有:f或F(单精度浮点数)、l或L(长双精度浮点数)。(注:因浮点型常数总是有符号的,故没有u或U后缀)。例:1.23e5f; 1.23l; -123.45f; 数据类型 C 中的类型可分为以下几种: 三元运算符耗时比if-else少原博 原理: CPU处理模式: CPU是通过流水线处理来获得高性能的。所谓流水线,简单来说就是当CPU在处理当前指令的时候,后面已经有N条指令在后面排队等待你去执行了。这样,当你要执行下一条指令的时候,你不用再去找那条指令,它已经乖乖跟在前一条指令的屁股后面等你去执行了。 if…else…处理模式: 那问题就在于,后面的指令需要确认一个排队顺序。如果程序员也是简单的编写流水线式的代码,对于CPU来说指令排序很容易。但是if…else…就不一样了。if…else…简单来说就是:当我满足条件,那我就执行A,如果我不满足条件,我就执行B。但是对于给指令排队的CPU来说,它还没执行到判断条件这一步,不知道你满不满足呀!这样它就不好给指令排队了。假设它按照你满足条件,把A指令排在你后面。那么当执行到最后发现你不满足条件,那么就得把之前排好的队列清空,重新给你把B指令排到后面给你执行。这种预测错误的惩罚将会导致CPU处理时间更长。假设预测准确的话,每次调用函数大概13个时钟周期,而只要预测错误,可能就需要大约44个时钟周期了。 三元运算处理模式: 对于三元运算符,它的处理方法就不同了。x=y>0?A:B;当CPU要给这条指令排队时,它会将两种结果都进行排队,也就是表达式A和表达式B都会被CPU进行处理,算出结果。计算对CPU来说反而是它更喜欢的事情,你只要能排队排好了,让它能流水线模式来执行,对于它来说总体速度反而更快。CPU会将两种结果A和B都给计算出来(这跟if…else…不同,其只会计算一种结果),最后再判断y>0?。如果y>0,则选择A,抛弃B; 否则就选择B,抛弃A。 iostream与iostream.h的区别详细解析原帖 有两种用法:A:include<iostream.h>include<fstream.h> B: includeinclude A是标准用法,B是老式用法。如果用了,则一定要引入命名空间,即”using namespace std;”.如果用了<iostream.h>,则不要引入命名空间,否则会引起编译错误,提示找不到命名空间 题目:输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。 代码: 12345678910111213141516171819202122#include <iostream>#include <stdio.h>#include <string.h>int main(){ using namespace std; char a[20]; gets(a); int i, num=0, cha=0, space=0, total; total = strlen(a); for( i=0; a[i]!='\0'; i++ ){ if( a[i]>='0' && a[i]<='9' ) num++; else if( (a[i]>='a' && a[i]<='z') || (a[i]>='A' && a[i]<='Z') ) cha++; else if( a[i]==' ' ) space++; } cout << "数字:" << num << endl; cout << "字母:" << cha << endl; cout << "空格:" << space << endl; cout << "其他:" << total-num-cha-space << endl; return 0;} 1)iostream和iostream.h头文件的问题 2)C语言不存在“string”数据类型,字符串就是一串以NUL字节结尾的字符。(’\n’) 3) 代码里的数字不是真的数字,而是‘0’~‘9’的字符 4)测试代码时,发现虽然定义的数组长度只有20,但输入超过20个字符时测试结果无误,猜测是因为数组是传址的缘故(C语言是不会检查你下标是否越界的。数组在内存中是一段连续的空间,当你使用下标 0 访问,访问到的是第一个元素,使用 1 访问得到第二个,如此这般。你定义一个3长度的数组,却访问到第9个单位去,这个地方已经不属于你定义的范围了。如果你修改的这个范围外的元素有在其它地方被使用到,就可能发生错误。) 题目:两个乒乓球队进行比赛,各出三人。甲队为a,b,c三人,乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比,c说他不和x,z比,请编程序找出三队赛手的名单。 代码(我): 1234567891011121314151617181920#include <iostream>int main(){ using namespace std; char i, j, k; for( i='x'; i<='z'; i++ ){ for( j='x'; j<='z'; j++ ){ for( k='x'; k<='z'; k++ ){ if( i!=j && i!=k && j!=k ){ if( i!='x' && k!='x' && k!='z' ){ cout<<'a'<<':'<<i<<endl; cout<<'b'<<':'<<j<<endl; cout<<'c'<<':'<<k<<endl; } } } } } return 0;} 代码(答案): 1234567891011121314151617#include <stdio.h>main(){char i,j,k;/*i是a的对手,j是b的对手,k是c的对手*/for(i='x';i<='z';i++) for(j='x';j<='z';j++) { if(i!=j) for(k='x';k<='z';k++) { if(i!=k&&j!=k) { if(i!='x'&&k!='x'&&k!='z') printf("order is a--%c\tb--%c\tc--%c\n",i,j,k); } } }} 比较了两种代码,我写的被完虐。答案的代码进行比较的次数比我的少了一个量级,看了一下两次运行的时间,一个0.38秒,一个0.01821秒,差距大的不正常。把IO流改成格式化输出,0.38秒变成0.01855秒,恢复正常。在数据量较大的时候,两种比较的差距才会出现。IO流比格式化输出耗时。 题目:利用递归函数调用方式,将所输入的5个字符,以相反顺序打印出来。 这道题刚开始不知道用递归怎么做,因为感觉传入数组递归的话不太好写终止语句。看了下答案才发现这道题的目的是为了让我们更好的理解递归的本质,递归的时候,每次调用函数,计算机都会为这个函数分配新的空间,这就是说,当被调函数返回的时候,调用函数中的变量依然会保持原先的值,且从后往前执行,也就是调用了栈。 最后,栈,递归,尾递归。 C语言并不执行数组下标的有效性检查。你觉得为什么这个明显的安全手段会从语言中省略? 在C语言中数组就是指针,它只保存地址。这就造成无法检查是否越界,但也给指针和数组的交互操作提供极大的便利。 为什么C语言中a[3]==3[a]? C规定,[]这个符号的作用就是a[b]==*(a+b),ab部分前后。 题目:取一个整数a从右端开始的4~7位 答案的代码 123456789main(){ unsigned a,b,c,d; scanf("%o",&a); b=a>>4; c=~(~0<<4); d=b&c; printf("%o\n%o\n",a,d);} 但运行源码后发现不对,看了程序分析后,先使a右移4位,设置一个低4位全为1,其余全为0的数。可用~(~0<<4)。将上面二者进行&运算。思路是对的,但是这应该放在2进制的代码中,当输入的是代码中的8进制时,因为一个8进制位可以转化为3个2进制位,所以最后取得不是输入数字的4~7位而是转化成2进制数时的4~7位,在转化成8进制后就只剩下1~2位数了。]]></content>
<tags>
<tag>C语言</tag>
</tags>
</entry>
<entry>
<title><![CDATA[PWM输出]]></title>
<url>%2Fposts%2F7876%2F</url>
<content type="text"><![CDATA[PWM在电力电子技术中占据着重要的地位,被广泛的应用在逆变电路中。 STM32中PWM工作过程 在调用PWM时,首先要使能TIM3外设的时钟,并对TIM3通道相应的GPIO引脚做相应的配置。配置好GPIO后,还要时基初始化、输出模式初始化和装载捕获/比较寄存器的数值。 时基配置: TIM_Period:定时周期,实质是存储到重载寄存器TIMx_ARR大的数值。 TIM_Prescaler:对定时器时钟TIMxCLK的预分频值,分频后作为脉冲计数器的驱动时钟。 TIM_ClockDivision:时钟分频因子。怎么又出现一个配置时钟分频的呢?要注意这个TIM_ClockDivision与TIM_Prescaler不同。TIM_Prescaler预分频是对TIMxCLK进行分频,分频后的时钟被输出到脉冲计数器中,而TIM_ClockDivision虽然也是对TIMxCLK进行分频,但被输出到定时器的ETPR数字滤波器部分,会影响滤波器的采样频率。ETPR数字滤波器的作用是对外部时钟TIMxETR进行滤波。本实验中无意义。 TIM_CounterMode:本成员配置的为脉冲计数器的计数模式。(向上、向下、中央对齐) 输出模式: 通过定时器的输出模式由TIM_OCInitTypeDef类型结构体的成员配置。 TIM_OCMode:输出模式配置,主要使用的为PWM1和PWM2模式。 PWM1模式:向上计数时,当TIMx_CNT<TIMx_CCRn时,通道n输出为有效电平,否则为无效电平;向下计数时,当TIMx_CNT>TIMxCCRn时通道n为无效电平,否则为有效电平。PWM2模式与PWM1模式相反。其中有效电平和无效电平是不固定的,也就是需要(TIM_OCPolarite)配置的。 TIM_OCPolarite:有效电平极性。 CCR1:捕获比较(值)寄存器(x=1,2,3,4):设置比较值。CCMR1: OC1M[2:0]位: 对于PWM方式下,用于设置PWM模式1【110】或者PWM模式2【111】CCER:CC1P位:输入/捕获1输出极性。0:高电平有效,1:低电平有效。CCER:CC1E位:输入/捕获1输出使能。0:关闭,1:打开。 代码 1234567891011121314151617181920212223242526272829303132333435363738void TIM1_PWM_Init(u16 arr,u16 psc){ GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外设时钟使能 //设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 80K TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高 TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能 TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能 TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器,允许或禁止在定时器工作时向ARR的缓冲器中写入新值,以便在更新事件发生时载入覆盖以前的值 TIM_Cmd(TIM1, ENABLE); //使能TIM1 } 预装载用于精确波形控制,PWM输出,一般用不到精确波形控制。故开不开都问题不大。在定时器的输出比较模式下,TIMx_CCRx寄存器能够在任何时候通过软件进行更新以控制波形,这个通过软件写入控制波形的值是立即生效呢还是在定时器发生下一次更新事件时被更新的,是由TIM_OCxPreloadConfig(TIMx, TIM_OCPreload_Enable)这条语句决定的!Enable就是下一次更新事件时被更新;Disable是立即生效 如果是高级定时器,则还需要配置:刹车和死区寄存器(TIMx_BDTR),该寄存器,我们只需要关注最高位:MOE位,要想高级定时器的PWM正常输出,则必须设置MOE位为1,否则不会有输出。注意:通用定时器不需要配置这个。 主函数 1234567891011121314151617 int main(void) { u16 led0pwmval=0; u8 dir=1; delay_init(); //延时函数初始化 LED_Init(); //初始化与LED连接的硬件接口 TIM1_PWM_Init(899,0);//不分频。PWM频率=72000/(899+1)=80Khz while(1) { delay_ms(10); if(dir)led0pwmval++; else led0pwmval--; if(led0pwmval>300)dir=0; if(led0pwmval==0)dir=1; TIM_SetCompare1(TIM1,led0pwmval); } } 虽然TIM1计数器中的计数值要与76比较,但是这一个周期的时间十分短暂,导致低电平时间过短,达到人眼无法分辨的频率,所以我们觉得LED一直是亮着的,这个时候决定LED变化的只有主函数中最后一段代码中的函数TIM_SetCompare1(TIM1,led0pwmval)中的led0pwmval这个参数决定的占空比,从而影响电压进而影响电流大小,最后导致了LED的亮度的改变。 关于STM32影子寄存器和预装载寄存器和TIM_ARRPreloadConfig1、有影子寄存器的有3个:分频寄存器PSC,自动重装载ARR,自动捕获CCRx,注意,PSC,ARR,CCRx不是影子寄存器,而是它们对应的“预装载寄存器”; 2、影子寄存器才是真正起作用的寄存器,但是ST没有提供这个寄存器出来,只是提供出与之相对应的预装载寄存器,分别为“PSC,ARR,CCRx” 3、我们用户能接触到,能修改或读取的都是预装载寄存器,ST只是把它们开放出来(影子寄存器并没有开放给用户),其实就是ARR寄存器,如:TIM1->ARR 4、从预装载寄存器ARR传送到影子寄存器,有两种方式,一种是立刻更新,一种是等触发事件之后更新;这两种方式主要取决于寄存器TIMx->CR1中的“APRE”位; 4.1 , APRE=0,当ARR值被修改时,同时马上更新影子寄存器的值; 4.2 , APRE=1,当ARR值被修改时,必须在下一次事件UEV发生后才能更新影子寄存器的值; 5、怎么样马上立刻更改影子寄存器的值,而不是下一个事件;方法如下: 5.1 、将ARPE=0,TIM_ARRPreloadConfig(ch1_Master_Tim, DISABLE ); 5.2 在ARPE=1,TIM_ARRPreloadConfig(ch1_Master_Tim, ENABLE); 我们更改完预装载寄存器后,立刻设置UEV事件,即更改EGR寄存的UG位,如下: TIM1->ARR = period-1; //设置周期 TIM1->CCR1 = period>>1; //设置占空比 50% TIM_GenerateEventTIM1,TIM_EventSource_Update); //主动发生UEV事件,UG=1]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[死区时间]]></title>
<url>%2Fposts%2F57562%2F</url>
<content type="text"><![CDATA[在H桥、三相桥的PWM驱动电路中,上下两个桥臂的PWM驱动信号是互补的,即上下桥臂轮流导通,但实际上为了防止出现上下两个臂同时导通(会短路),在上下两臂切换时留一段时间,上下臂都施加关闭信号,这个上下臂都断的时间就是死区时间。高级定时器可以配置出输出互补的PWM信号,并且在这个PWM信号中加入死区时间,为电机的控制提供了极大的便利。 关于死区时间,看到有这两种理解,一种是电机电感需要释放能量,一种是功率器件以及电路的延迟需要等待。个人理解他们都是死区时间的考虑因素。 电机的各相输入的控制信号之间设定的互锁时间,留一段时间让储能电感放电,以免造成短路。 PWM输出时的Dead Zone(死区)作用是在电平翻转时插入一个时间间隔,更具体的理解是,通常大功率电机、变频器等,末端都是由大功率管、IGBT等元件组成的H桥或3相桥。每个桥的上半桥和下半桥是绝对不能同时导通的,但高速的PWM驱动信号在达到功率元件的控制端时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。 在保证不出现短路的情况下,死区时间越短越好。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[stm32定时器]]></title>
<url>%2Fposts%2F50823%2F</url>
<content type="text"><![CDATA[stm32一共有8个16位的定时器,其中TIM6、TIM7是基本定时器,TIM2~5是通用定时器,TIM1和TIM8是高级定时器。这些定时器使STM32具有定时、信号频率测量、信号的PWM测量、PWM输出、三相6步电机控制及编码接口等功能,都是专门为工业控制领域量身定做的。 基本定时器基本定时器只具备最基本的定时功能,就是累加的时钟脉冲超过预定时,能触发中断或DMA请求。由于在芯片内部与DAC外设相连,可以通过接触输出驱动DAC,也可以作为其他通用定时器的时钟基准。 工作时,脉冲计数器TIMx_CNT由时钟触发进行计数,当TIMx_CNT的计数值等于重载寄存器TIMx_ARR中保存的数值N时,产生溢出事件,可触发中断或DMA请求。然后TIMx_CNT的值重新被置为0,重新向上计数。 通用定时器(Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk)除了基本的定时时,它主要用于测量脉冲的频率、脉冲宽与PWM脉冲的场合,还具有编码器的接口。 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为: ① 输入捕获 ② 输出比较 ③ PWM 生成(边缘或中间对齐模式) ④ 单脉冲模式输出 捕获/比较寄存器通用定时器的基本计时功能与基本定时器的工作方式一样,同样把时钟源经过预分频器输出到脉冲计数器TIMx_CNT累加,溢出时就产生中断或DMA请求。而通用定时器比基本定时器多出的强大功能,就是因为通用定时器多出了一种捕获/比较寄存器TIMx——CCR,它在输入时被用于捕获输入脉冲在电平翻转时脉冲计数器TIMx_CNT的当前计数值,从而实现脉冲的频率测量。在输出时被用来存储一个脉冲数值,把这个数值用于与脉冲计数器TIMx_CNT的当前计数值进行比较,根据比较结果进行不同的电平输出。 PWM输出过程分析通用定时器可以利用GPIO引脚进行脉冲输出,在配置为比较输出、PWM输出功能时,捕获/比较寄存器TIMx_CCR被用作比较功能。 若配置脉冲计数器TIMx_CNT为向上计数,而重载寄存器TIMx_ARR被配置为N,即TIMx_CNT的当前计数值数值X在TIMxCLK时钟源的驱动下不断累积,当TIMx_CNT的数值X大于N时,会重置TIMx_CNT数值为0并重新计数。而在TIMx_CNT计数的同时,TIMx_CNT的计数值X会与比较寄存器TIMx_CCR预先存储的数值A进行比较。当脉冲计数器的数值X小于计较寄存器的值A时,输出高电平(或低电平)。相反的,X大于A时,输出低电平(或高电平)。如此循环,得到的输出脉冲周期就为重载寄存器存储的数值(N+1)乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器TIMx_CCR的值A乘以触发脉冲的时钟周期,即输出PWM的占空比为A/(N+1)。 PWM输入过程分析当定时器被配置为输入功能时,可以用于检测输入到GPIO引脚的信号,此时捕获/比较寄存器TIMx_CCR被用作捕获功能。 要测量的PWM脉冲通过GPIO引脚输入到定时器的脉冲检测通道,其时序为图中TI1。把脉冲计数器配置为向上计数,重载寄存器的N值配置为足够大。在输入脉冲TI1的上升沿到达时,触发IC1和IC2的输入捕获中断,这时把脉冲计数器TIMx_CNT的计数值复位为0,于是TIMx_CNT的计数值X在TIMx_CLK的驱动下从0开始累加直到TI1出现下降沿,触发IC2捕获事件,此时捕获寄存器把脉冲计数器的当前值2存储起来(TIMx_CCR2),而脉冲计数器继续累加,直到TI1出现第二个上升沿,触发IC1的捕获事件,此时TIMx_CNT的当前计数值4被保存到TIMx_CCR1。 TIMx_CCR1(加1)的值乘以TIMxCLK的周期即为PWM输入脉冲周期,TIMx_CCR2(加1)的值乘以TIMxCLK的周期即为PWM的高电平时间,进而可以计算出PWM脉冲的频率、占空比。 定时器的时钟源从时钟源方面来说,通用定时器比基本定时器多了一个选择,它可以使用外部脉冲作为定时器的时钟源。 计数器时钟可以由下列时钟源提供:内部时钟(CK_INT)外部时钟模式1:外部输入脚(TIx)外部时钟模式2:外部触发输入(ETR)内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。 使用外部时钟源时,要使用寄存器进行触发边沿、滤波器带宽的配置。如果选择内部时钟源的话则与基本定时器一样,也为TIMxCLK。但要注意的是,所有定时器使用内部时钟时,定时器的时钟源都被称为TIMxCLK,但TIMxCLK的时钟源并不是完全一样的。 TIM2~7也就是基本定时器和通用定时器,TIMxCLK的时钟源是APB1预分频器的输出。当APB1的分频系数为1时,则TIM2~7的TIMxCLK直接等于该APB1预分频器的输出,而APB1的分频系数不为1时,TIM2~7的TIMxCLK则为APB1的2倍。 在常见配置中,AHB=72MHz,而APB1预分频器的分频系数被配置为2,则PCLK1刚好达到最大值32MHz,而此时APB1的分频系数不为1,则TIM2~7的时钟TIMxCLK=(AHB/2)*2=72MHz。 而对于TIM1和TIM8这两个高级定时器,TIMxCLK的时钟来源则是APB2预分频器的输出,同样它也根据分频系数分成2中情况。 常见的配置中AHB=72MHz,APB2预分频器的分频系数被配置为1,此时PCLK2刚好达到最大值72MHz,而TIMxCLK则直接等于APB2分频器的输出,即TIM1和TIM8的时钟TIMxCLK=AHB=72MHz。 虽然这种配置下最终TIMxCLK的时钟频率相等,但必须清楚他们的时钟来源时有区别的。还要强调的是:TIMxCLK是定时器内部的时钟源,但在时钟输出到脉冲计时器前,还经过了一个预分频器PSC,最终用于驱动脉冲计数器的时钟根据预分频器PSC的配置而定。 定时器中断实现步骤1234567891011使能定时器时钟。 RCC_APB1PeriphClockCmd();初始化定时器,配置ARR,PSC。 TIM_TimeBaseInit();开启定时器中断,配置NVIC。 void TIM_ITConfig(); NVIC_Init();使能定时器。 TIM_Cmd();编写中断服务函数。 TIMx_IRQHandler(); 高级定时器TIM1和TIM8是两个高级定时器,他们具有基本、通用定时器的所有功能,还具有三相6步电机的接口、刹车功能及用于PWM驱动电路的死区时间控制等,使他非常适用于电机控制。相比于通用定时器,高级定时器多出了BRK、DTG两个结构,因而具有死区时间的控制功能。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[190413]]></title>
<url>%2Fposts%2F28547%2F</url>
<content type="text"><![CDATA[不管怎么说现在学习的热情已经越来越低了,看了下时间,大概坚持了一个月的时间,自觉性就不再了。我觉得规律是一个很重要的原因。之前完全靠自觉,没有定制完备的作息。而这两天中午莫名的失眠,直接导致了晚上学习时精力的不足,加上看的书籍过于晦涩,于是很难集中注意力在书上。可我又是一个不喜欢让太过死板的制度限制住的人,就简单制定一下每天的目标,而不是具体的每个小时要做的事吧。 首先是周一到周五,由于白天有课,学习的时间只有到晚饭后了。每天看一讲视频和对应的章节的书,然后写一下相应的代码与总结。由于stm32的视频快看完了,以及我的几本C的书籍难度超微有点大,后续的视频改成C的。先看C的视频再去啃书。最后每天研究一题嵌入式的面试题吧。 然后是周末的时间,因为死宅睡醒就是中午了,吃完午饭后可以看会书,看到想睡就睡一觉。醒来后研究一下平时的课程到吃晚饭,然后重复工作日的作息。]]></content>
<tags>
<tag>hide</tag>
</tags>
</entry>
<entry>
<title><![CDATA[看门狗]]></title>
<url>%2Fposts%2F59401%2F</url>
<content type="text"><![CDATA[为什么要看门狗在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。 看门狗解决的问题是什么在启动正常运行的时候,系统不能复位。在系统跑飞(程序异常执行)的情况,系统复位,程序重新执行。 独立看门狗独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它仍有效。独立看门狗适合应用于需要看门狗作为一个在主程序之外能够完全独立工作,并且对时间精度要求低的场合。 独立看门狗功能描述在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗。此时计数器开始从其复位值0xFFF递减,当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)。无论何时,只要在键值寄存器IWDG_KR中写入0xAAAA(通常说的喂狗), 自动重装载寄存器IWDG_RLR的值就会重新加载到计数器,从而避免看门狗复位。如果程序异常,就无法正常喂狗,从而系统复位。 溢出时间计算:Tout=((4×2^prer) ×rlr) /40 (M3) 123456void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//取消写保护:0x5555使能void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PRvoid IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLRvoid IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KRvoid IWDG_Enable(void);//使能看门狗:写0xCCCC到KRFlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);//状态:重装载/预分频 更新 窗口看门狗窗口看门狗由从APB1时钟分频后得到时钟驱动。通过可配置的时间窗口来检测应用程序非正常的过迟或过早操作。窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序。 之所以称为窗口就是因为其喂狗时间是一个有上下限的范围内(窗口),你可以通过设定相关寄存器,设定其上限时间(下限固定)。喂狗的时间不能过早也不能过晚。 而独立看门狗限制喂狗时间在0-x内,x由相关寄存器决定。喂狗的时间不能过晚。 STM32F的窗口看门狗中有一个7位的递减计数器T[6:0],它会在出现下述2种情况之一时产生看门狗复位:1 当喂狗的时候如果计数器的值大于某一设定数值W[6:0]时,此设定数值在WWDG_CFR寄存器定义。当计数器的数值从0x40减到0x3F时【T6位跳变到0】 。2 如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),它可以用于喂狗以避免WWDG复位。 超时时间:Twwdg = ( 4096 2 ^ WDGTB ( T [5:0] + 1)) / Fpclk1 WDGTB:WWDG的预分频系数 Fpclk1:APB1的时钟频率 T [5:0] : 窗口看门狗计数器底6位 为什么要窗口看门狗?对于一般的看门狗,程序可以在它产生复位前的任意时刻刷新看门狗,但这有一个隐患,有可能程序跑乱了又跑回到正常的地方,或跑乱的程序正好执行了刷新看门狗操作,这样的情况下一般的看门狗就检测不出来了;如果使用窗口看门狗,程序员可以根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗也不会滞后刷新看门狗,这样可以检测出程序没有按照正常的路径运行非正常地跳过了某些程序段的情况。 123456789101112131415//使能看门狗时钟: RCC_APB1PeriphClockCmd();//设置分频系数: WWDG_SetPrescaler();//设置上窗口值: WWDG_SetWindowValue();//开启提前唤醒中断并分组(可选): WWDG_EnableIT(); NVIC_Init();//使能看门狗: WWDG_Enable();//喂狗: WWDG_SetCounter();//编写中断服务函数 WWDG_IRQHandler();]]></content>
<tags>
<tag>stm32</tag>
<tag>嵌入式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[stm32的printf函数]]></title>
<url>%2Fposts%2F11595%2F</url>
<content type="text"><![CDATA[在之前的USART实验中调用了printf函数来打印发送的信息。printf()函数用法和在纯C语言编程中看起来好像没有区别,但实际上在调用printf()函数的时候,我们需要把printf()重新定向到串口中。重定向是指用户可以自己重新写C的库函数,当连接器检查到用户编写了与C库函数相同的名字的函数时,优先采用用户编写的函数,这样用户就可以实现对库的修改了。 为了实现重定向printf()函数,我们需要重写fputc()这个C标准库函数,因为printf()在C标准函数中实际是一个宏,最终是调用了fputc()这个函数。 123456int fputc(int ch, FILE *f){ while((USART1->SR&0X40)==0);//循环发送,直到发送完毕 USART1->DR = (u8) ch; return ch;} 重定向时,我们把fputc()的形参ch,作为串口将要发送的数据,也就是说,当使用printf()时,他先调用这个fputc()函数,然后将ch的值赋值给USART1的数据寄存器,触发串口向PC发送一个相应的数据。(当我们需要发送数据时,内核或DMA外设把数据从内存写入发送数据寄存器TDR后,发送控制寄存器将适时的自动把数据从TDR加载到发送移位寄存器,然后通过串口线Tx,把数据一位一位的发送出去,当数据从TDR转移到移位寄存器时,会产生发送寄存器TDR已空事件TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件TC,这些事件可以在状态寄存器中查询到。) 若使用C标准输出库函数,需要在main.c文件中把stdio.h这个头文件包含进来,还要在编译器中设置一个选项UseMicroLIB (使用微库),这个微库是Keil MDK为嵌入式应用量身定做的C库,我们要先具有库才能重定向,勾选使用后,就可以使用printf()这个函数了。 除了重定向的方法,我们还可以自己编写格式输入输出函数,功能和重定向之后的printf()函数类似。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[c与指针读书笔记(1)]]></title>
<url>%2Fposts%2F54171%2F</url>
<content type="text"><![CDATA[注释在C语言中有两种注释方式: 一种是以/*开始、以*/结束的块注释(block comment), 另一种是以//开始、以换行符结束的单行注释(line comment)。 预处理器仅通过检查注释的结束符来检测注释中的字符,因此,不能嵌套块注释。在有些语言中,注释有时用于把一段代码“注释掉”,也就是使这段代码在程序中不起作用,但并不是将其真正从源文件中删除。在C语言中,如果用/*和*/来注释掉原先就有注释的代码,会出现问题。要从逻辑上删除一段C代码,更好的办法是使用#if指令。 123#if 0 statements#endif 在#if和#endif之间的程序段就可以有效的从程序中去除了,这是一种更为安全的方法。 预处理123456#include <stdio.h>#include <stdlib.h>#include <string.h> #define MAX_COLS 20 //所能处理的最大列号 #define MAX_INPUT 1000 //每个输入行的最大长度 这5行就是预处理指令,因为他们是由预处理器解释的。预处理器读入源码,根据预处理指令对其修改,然后把修改过的源码交给编译器。]]></content>
<tags>
<tag>c语言</tag>
<tag>c与指针</tag>
</tags>
</entry>
<entry>
<title><![CDATA[串行通信原理梳理]]></title>
<url>%2Fposts%2F24857%2F</url>
<content type="text"><![CDATA[前言虽然之前在一些课程中已经学习过关于串行通信原理的知识,但一直没有总结,很多知识也都忘记了。今天就重新梳理一下。 妈蛋,发送端的跳线帽坏了,害我白浪费好长的时间排错。 清明放假(荒废)3天。。 背景知识处理器与外部设备通信的两种方式并行通信传输原理:数据各个位同时传输。优点:速度快缺点:占用引脚资源多 串行通信传输原理:数据按位顺序传输。优点:占用引脚资源少缺点:速度相对较慢 按照数据传送方向,分为单工:数据传输只支持数据在一个方向上传输半双工:允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。 串行通信的通信方式同步通信:带时钟同步信号传输。 -SPI(全双工),IIC(半双工)通信接口异步通信:不带时钟同步信号。 -UART(通用异步收发器),单总线(USART:通用同步异步收发器,USART可用作UART使用) UART异步通信方式特点全双工异步通信。分数波特率发生器系统,提供精确的波特率。-发送和接受共用的可编程波特率,最高可达4.5Mbits/s可编程的数据字长度(8位或者9位);可配置的停止位(支持1或者2位停止位);可配置的使用DMA多缓冲器通信。单独的发送器和接收器使能位。检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志多个带标志的中断源。触发中断。其他:校验控制,四个错误检测标志。 STM32串口异步通信需要定义的参数: 起始位 数据位(8位或者9位) 奇偶校验位(第9位) 停止位(1,15,2位) 波特率设置 通过对时钟的控制可以改变波特率。在配置波特率时,向USART_BRR波特率寄存器写入参数,修改串口时钟的分频值USARTDIV。USART_BRR包括2部分(整数部分和小数部分)。 对于USART1,由于挂载在APB2总线上,所以他的时钟源为fPCLK2;而USART2,USART3挂载在APB1上,时钟源为fPCLK1。 直通线与交叉线串口线主要分为2种:直通线与交叉线。假如PC与板子之间要实现全双工串口通信,必然是PC的Tx针脚要连接到板子的Rx脚,PC的Rx针脚要连接到板子的Tx脚。由于板子和电脑的串口接法是相同的,就要使用交叉线来连接了。设计板子时尽量采用与PC相同的标准串口接法。 常用的串口相关寄存器USART_SR状态寄存器USART_DR数据寄存器USART_BRR波特率寄存器 当我们需要发送数据时,内核或DMA外设把数据从内存写入发送数据寄存器TDR后,发送控制寄存器将适时的自动把数据从TDR加载到发送移位寄存器,然后通过串口线Tx,把数据一位一位的发送出去,当数据从TDR转移到移位寄存器时,会产生发送寄存器TDR已空事件TXE,当数据从移位寄存器全部发送出去时,会产生数据发送完成事件TC,这些事件可以在状态寄存器中查询到。 串口通信串口操作相关库函数void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能void USART_Cmd();//使能串口void USART_ITConfig();//使能相关中断 void USART_SendData();//发送数据到串口,DRuint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据 FlagStatus USART_GetFlagStatus();//获取状态标志位void USART_ClearFlag();//清除状态标志位ITStatus USART_GetITStatus();//获取中断状态标志位void USART_ClearITPendingBit();//清除中断状态标志位 串口配置一般步骤硬件连接PA9,PA10(串口1)连接到了USB串口电路。 串口配置的一般步骤1串口时钟使能,GPIO时钟使能:RCC_APB2PeriphClockCmd();2串口复位:USART_DeInit(); 这一步不是必须的3GPIO端口模式设置:GPIO_Init(); 模式设置为GPIO_Mode_AF_PP4串口参数初始化:USART_Init(); 硬件流:功能表现为:当外设硬件处于准备好的状态时,硬件启动自动控制,不需要软件再进行干预。 5开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤) NVIC_Init(); USART_ITConfig();6使能串口:USART_Cmd(); (使用外设时,不仅要使用其时钟,还要调用此函数使能外设)7编写中断处理函数:USARTx_IRQHandler();8串口数据收发:void USART_SendData();//发送数据到串口,DRuint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据9串口传输状态获取:FlagStatus USART_GetFlagStatus(USART_TypeDef USARTx, uint16_t USART_FLAG);void USART_ClearITPendingBit(USART_TypeDef USARTx, uint16_t USART_IT); USART代码分析头文件1234567#define USART_REC_LEN 200 //定义最大接收字节数 200#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收 extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 extern u16 USART_RX_STA; //接收状态标记 //如果想串口中断接收,请不要注释以下宏定义void uart_init(u32 bound); 串口1中断服务程序1234567891011121314151617181920212223242526272829303132void USART1_IRQHandler(void) //串口1中断服务程序{ u8 Res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntEnter(); #endif if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据 if((USART_RX_STA&0x8000)==0)//接收未完成 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//接收缓冲 USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntExit(); #endif} USART_RX_STA接收状态标记,bit15 接收完成标志,bit14 接收到0x0d,bit13~0,接收到的有效字节数目。 程序要求,发送的字符是以回车换行结束(0x0D,0x0A)。 当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2 个字节组成:0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到 0X0D,那么在接收数据超过 USART_REC_LEN 的时候,则会丢弃前面的数据,重新接收。 计算机向串口发送一串字符,一般不止一个,例如发送”abcdefg回车“。那么串口中断函数会执行9次,回车要执行两次串口中断。当串口中断函数第一次执行时,USART1->DR里面装的是字符a,下面以串口第一次执行来分析这个串口中断函数。if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)这是判断读数据寄存器是否空,因为接受到了a,所以不是空的,这个判断成立。Res =USART_ReceiveData(USART1);//(USART1->DR); 既然接受到了字符a,那么就要把他读取出来。 if((USART_RX_STA&0x8000)==0) 因为现在接受的是第一个字符,所以接收肯定没有完成,USART_RX_STA还是它的初始化值,于是第15位还是0,这个判断语句成立。于是要执行下面这句话 if(USART_RX_STA&0x4000) USART_RX_STA的第14位仍然是0,所以这个判断不成立,所以会执行下面这句话。if(Res==0x0d) 当然这个判断也不成立,所以要执行下面这句话。USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;计算出接收的是第几个字符,然后装到缓存里面。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[EXTI外部中断]]></title>
<url>%2Fposts%2F13433%2F</url>
<content type="text"><![CDATA[EXTI(External Intertupt)就是指外部中断,通过GPIO检测输入脉冲,引起中断事件,打断原来的代码执行流程,进入中断服务函数中进行处理,处理完后再回到中断之前的函数执行。CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。STM32F103系列上面,又只有60个可屏蔽中断。 stm32的所有GPIO都引入到EXTI外部中断线上,使得所有的GPIO都能作为外部中断的输入源。STM32的中断控制器支持19个外部中断/事件请求:线0~15:对应外部IO口的输入中断。线16:连接到PVD输出。线17:连接到RTC闹钟事件。线18:连接到USB唤醒事件。每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。PAx~PGx端口的中断事件都连接到EXTIx,即同一时刻EXTIx只能响应一个端口的事件触发,不能同一时间响应所有的GPIO端口的事件,但可以分时复用。 AFIOAFIO指GPIO端口的复用功能,当把GPIO用作EXTI外部中断或使用重映射功能时,必须开启AFIO时钟,而再使用默认复用功能的时候,不必开启AFIO时钟。 EXTI初始化配置1234567typedef struct{ uint32_t EXTI_Line; //指定要配置的中断线 EXTIMode_TypeDef EXTI_Mode; //模式:事件 OR中断 EXTITrigger_TypeDef EXTI_Trigger;//触发方式:上升沿/下降沿/双沿触发 FunctionalState EXTI_LineCmd; //使能 OR失能}EXTI_InitTypeDef; 12345EXTI_InitStructure.EXTI_Line=EXTI_Line2; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure); 编写中断服务函数stm32f10x_it.c文件是专门用来存放中断服务函数的。文件中默认只有几个关于系统异常的中断服务函数,且都是空函数,在需要的时候自行编写。(不可以自定义函数名,中断服务函数名必须与启动文件startup_stm32f10x_hd.s中的中断向量表定义一致) 1234567891011121314151617181920212223242526272829303132333435DCD EXTI0_IRQHandler ; EXTI Line 0DCD EXTI1_IRQHandler ; EXTI Line 1DCD EXTI2_IRQHandler ; EXTI Line 2DCD EXTI3_IRQHandler ; EXTI Line 3DCD EXTI4_IRQHandler ; EXTI Line 4DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7DCD ADC1_2_IRQHandler ; ADC1 & ADC2DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TXDCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0DCD CAN1_RX1_IRQHandler ; CAN1 RX1DCD CAN1_SCE_IRQHandler ; CAN1 SCEDCD EXTI9_5_IRQHandler ; EXTI Line 9..5DCD TIM1_BRK_IRQHandler ; TIM1 BreakDCD TIM1_UP_IRQHandler ; TIM1 UpdateDCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and CommutationDCD TIM1_CC_IRQHandler ; TIM1 Capture CompareDCD TIM2_IRQHandler ; TIM2DCD TIM3_IRQHandler ; TIM3DCD TIM4_IRQHandler ; TIM4DCD I2C1_EV_IRQHandler ; I2C1 EventDCD I2C1_ER_IRQHandler ; I2C1 ErrorDCD I2C2_EV_IRQHandler ; I2C2 EventDCD I2C2_ER_IRQHandler ; I2C2 ErrorDCD SPI1_IRQHandler ; SPI1DCD SPI2_IRQHandler ; SPI2DCD USART1_IRQHandler ; USART1DCD USART2_IRQHandler ; USART2DCD USART3_IRQHandler ; USART3DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 中断线在5之后就不能像0~4那样只有单独一个函数名,必须写成EXTI9_5_IRQHandler 和EXTI15_10_IRQHandler。 12345678void EXTI9_5_IRQHandler (void){ if (LED1==0) { LED1 = !LED1; EXTI_ClearITPendingBit(EXTI_Line5); }}]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[NVIC中断优先级管理]]></title>
<url>%2Fposts%2F32250%2F</url>
<content type="text"><![CDATA[stm32对Cortex内核中断向量表进行重新编排,把编号从-3到6的中断向量定义为系统异常,编号为负的内核异常不能被设置优先级,如-3复位(Reset),-2不可屏蔽中断(NMI),-1硬错误(Hardfault)。编号从7开始为外部中断,优先级可自行设置。 由于中断太多,配置困难,因此需要中断控制寄存器NVIC。NVIC属于Cortex内核器件,不可屏蔽中断和外部中断都由它处理,systick不是由他控制的。 中断管理方法首先,对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值。由于NVIC只能配置16种中断向量的优先级,也就是说抢占优先级和响应优先级的数量由一个4位的数字来决定,把这个4位数字的位数配置成抢占优先级和响应优先级有5组分配式。分组配置是在寄存器SCB->AIRCR中配置。一般情况下,系统代码执行过程中,只设置一次中断优先级分组,比如分组2,设置好分组之后一般不会再改变分组。随意改变分组会导致中断管理混乱,程序出现意想不到的执行结果。 中断优先级分组函数12345void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup){ assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup)); SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;} 1NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 中断优先级设置分组设置好之后,设置单个中断的抢占优先级和响应优先级 中断设置相关寄存器__IO uint8_t IP[240]; //中断优先级控制的寄存器组 __IO uint32_t ISER[8]; //中断使能寄存器组。用来使能中断。 __IO uint32_t ICER[8]; //中断失能寄存器组。作用:用来失能中断。 __IO uint32_t ISPR[8]; //中断挂起寄存器组。作用:用来挂起中断。 __IO uint32_t ICPR[8]; //中断解挂寄存器组。作用:用来解挂中断。 __IO uint32_t IABR[8]; //中断激活标志位寄存器组。作用:只读,通过它可以知道当前在执行的中断是哪一个,如果对应位为1,说明该中断正在执行。 MDK中NVIC寄存器结构体12345678910111213141516typedef struct{ __IO uint32_t ISER[8]; uint32_t RESERVED0[24]; __IO uint32_t ICER[8]; uint32_t RSERVED1[24]; __IO uint32_t ISPR[8]; uint32_t RESERVED2[24]; __IO uint32_t ICPR[8]; uint32_t RESERVED3[24]; __IO uint32_t IABR[8]; uint32_t RESERVED4[56]; __IO uint8_t IP[240]; uint32_t RESERVED5[644]; __O uint32_t STIR; } NVIC_Type; 中断优先级控制的寄存器组:IP[240] 。作用:设置每个中断优先级。全称是:Interrupt Priority Registers。240个8位寄存器,每个中断使用一个寄存器来确定优先级。STM32F10x系列一共60个可屏蔽中断,使用IP[59]~IP[0]。每个IP寄存器的高4位用来设置抢占和响应优先级(根据分组),低4位没有用到。 中断使能寄存器组:ISER[8]。作用:用来使能中断。32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ISER[0]和ISER[1]。ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~27对应中断32~59; 中断失能寄存器组:ICER[8]。作用:用来失能中断。32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ICER[0]和ICER[1]。ICER[0]的bit0~bit31分别对应中断0~31。ICER[1]的bit0~27对应中断32~59;配置方法跟ISER一样。 中断参数初始化函数void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct); 1234567typedef struct{ uint8_t NVIC_IRQChannel; //设置中断通道 uint8_t NVIC_IRQChannelPreemptionPriority;//设置响应优先级 uint8_t NVIC_IRQChannelSubPriority; //设置抢占优先级 FunctionalState NVIC_IRQChannelCmd; //使能/使能} NVIC_InitTypeDef; 123456NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位2NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化NVIC寄存器]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[systick滴答定时器]]></title>
<url>%2Fposts%2F36388%2F</url>
<content type="text"><![CDATA[Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,即一次最多可以计数2的24次方个时钟脉冲,这个脉冲计数值被保存到SysTick 当前值寄存器 STK_VAL中,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。当然要使用Ststick进行工作必须要进行Systick配置。他的控制配置很简单,只有3个控制位和1个标志位,都在STK_CTRL中。 在所有CM3产品中,Systick定时器的处理方式都是相同的。不过,STCLK的具体的来源则由芯片设计者决定,因此不同产品之间的时钟频率可能大不相同。如果使用中发现延时不一致,问题一般都是因为不同内核时钟不一样而已。修改ticks值即可。 Systick有4个寄存器 CTRL SysTick 控制和状态寄存器 LOAD SysTick 自动重装载除值寄存器 VAL SysTick 当前值寄存器 CALIB SysTick 校准值寄存器 固件库中的Systick相关函数: 1.SysTick_CLKSourceConfig() //Systick时钟源选择 misc.c文件中 12345678910111213void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource){ /* Check the parameters */ assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource)); if (SysTick_CLKSource == SysTick_CLKSource_HCLK) { SysTick->CTRL |= SysTick_CLKSource_HCLK; } else { SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; }} 选择时钟源,外部时钟源(STCLK,等于HCLK/8),另一个是内核时钟(FCLK,等于HCLK)。 SysTick_Config(uint32_t ticks) //初始化systick,时钟为HCLK,并开启中断 //core_cm3.h/core_cm4.h文件中 1234567891011121314151617181920212223/** * @brief Initialize and start the SysTick counter and its interrupt. * @param ticks number of ticks between two interrupts * @return 1 = failed, 0 = successful * Initialise the system tick timer and its interrupt and start the * system tick timer / counter in free running mode to generate * periodical interrupts. */static __INLINE uint32_t SysTick_Config(uint32_t ticks){ if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ /* set reload register */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set Priority for Cortex-M0 System Interrupts */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); SysTick->VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */} ticks:两个中断之间的脉冲数,即相隔多少个时钟周期会引起一次中断 SysTick_LOAD_RELOAD_Msk:SysTick上限,大于这个值返回1(failed)。 SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;-1是因为向下计数至0。 systick定时器有两个可选的时钟源,一个是外部时钟源(STCLK,等于HCLK/8),另一个是内核时钟(FCLK,等于HCLK)。假若你选择内核时钟,并将HCLK频率设置为72MHz的话,系统时钟周期为1/(72M);systick有一个24位的递减计数器,每个系统时钟周期计数器值减一,那么当计数器减到零时,时间经过了:系统时钟周期计数器初值。当你将计数器初值设为72000时(有些例程里面设为71999,其实没什么影响,误差极小),当计数器值减到0时经过了1/(72M)72000=0.001s,即1ms。 Systick中断服务函数: void SysTick_Handler(void); 1234567void SysTick_Handler(void){ if (TimingDelay != 0x00) { TimingDelay--; }} 最后就是原子给的delay.h头文件里的函数了,刚开始时就对这个头文件很感兴趣了,觉得这是一个应用范围很广的东西,原来是用systick写的。因为还没学os,忽略。 12345678910111213141516171819202122232425void delay_init(){#if SYSTEM_SUPPORT_OS //如果需要支持OS. u32 reload;#endif SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8 fac_us=SystemCoreClock/8000000; //为系统时钟的1/8 #if SYSTEM_SUPPORT_OS //如果需要支持OS. reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为M reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间 //reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右 fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位 SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断 SysTick->LOAD=reload; //每1/delay_ostickspersec秒中断一次 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK #else fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数 #endif} #if SYSTEM_SUPPORT_OS //如果需要支持OS.//延时nus//nus为要延时的us数. 初始化的语句,即sysclk=72,fac_us=9(无单位)。外部晶振为8MHZ, systick的时钟为外部时钟的1/8,所以systick(系统时钟)为9MHZ。系统运行9M次耗时1s,因此运行9次耗时1us。fac_us没有单位,它只是在重装初值时起到作用,比如Systick->LOAD=nus*fac_us. 12345678910111213141516171819void delay_us(u32 nus){ u32 temp; SysTick->LOAD=nus*fac_us; //时间加载 SysTick->VAL=0x00; //清空计数器 SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器 SysTick->VAL =0X00; //清空计数器 }//延时nms//注意nms的范围//SysTick->LOAD为24位寄存器,所以,最大延时为://nms<=0xffffff*8*1000/SYSCLK//SYSCLK单位为Hz,nms单位为ms//对72M条件下,nms<=1864 bit0位为使能位,bit16位为计数标志countflagwhile(temp&0x01&&!(temp&(1<<16)));//当使能位没有被置0并且countflag为0(没数完), 然后一直执行DO里的语句,用来延时。 利用systick进行时间测量 当我们开启systick定时器后,定时器开始工作,我们可以定义一个变量a来对中断次数进行记录,在定时器进入中断时这个变量就a++,当我们关闭定时器后,将变量的数值乘以定时器的中断周期就是测量的时间。一般利用该功能测量程序的时间,特别是涉及算法的程序,对优化算法有非常大的帮助。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[STM32时钟系统和SystemInit函数解读]]></title>
<url>%2Fposts%2F18156%2F</url>
<content type="text"><![CDATA[STM32系统时钟总结1 STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL。 ①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。 ③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。WDG ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。 2 系统时钟SYSCLK可来源于三个时钟源: ①、HSI振荡器时钟 ②、HSE振荡器时钟 ③、PLL时钟 3 STM32可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。 4 任何一个外设在使用之前,必须首先使能其相应的时钟。 内部时钟是由芯片内部RC震荡器产生得的,起针较快,所以时钟在芯片刚上电的时候,默认使用内部高速时钟。而外部时钟信号是由外部晶振输入的,在精度和稳定性上都有很大优势,所以上电之后在通过软件配置,转而用外部时钟信号。 RCC相关头文件和固件库源文件1.时钟使能配置 RCC_LSEConfig() 、RCC_HSEConfig()、 RCC_HSICmd() 、 RCC_LSICmd() 、 RCC_PLLCmd() …… 2.时钟源相关配置: RCC_PLLConfig ()、 RCC_SYSCLKConfig() 、 RCC_RTCCLKConfig() … 3.分频系数选择配置: RCC_HCLKConfig() 、 RCC_PCLK1Config() 、 RCC_PCLK2Config()… 4.外设时钟使能: RCC_APB1PeriphClockCmd(): //APB1线上外设时钟使能 RCC_APB2PeriphClockCmd(); //APB2线上外设时钟使能 RCC_AHBPeriphClockCmd**(); //AHB线上外设时钟使能 5. 其他外设时钟配置: RCC_ADCCLKConfig (); RCC_RTCCLKConfig(); 6.状态参数获取参数: RCC_GetClocksFreq(); RCC_GetSYSCLKSource(); RCC_GetFlagStatus() 7.RCC中断相关函数: RCC_ITConfig() 、 RCC_GetITStatus() 、 RCC_ClearITPendingBit()… SystemInit函数在startup_stm32f10x_hd.s启动文件中,有一段启动代码 123456789Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT __main IMPORT SystemInit LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP 当芯片被复位时,将开始运行这一段代码,运行过程是先调用SystemInit()函数,在进入C语言中的“__main”函数执行(不是main),这是一个C标准库的初始化函数,执行这个函数后,最终跳转到用户文件的“main”函数入口,开始运行主函数。 SystemInit()函数,定义在system_stm32f10x.c中,他的作用是设置系统时钟SYSCLK 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758void SystemInit (void){ /* Reset the RCC clock configuration to the default reset state(for debug purpose) */ /* Set HSION bit 打开内部8MHz震荡器*/ RCC->CR |= (uint32_t)0x00000001; /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */#ifndef STM32F10X_CL RCC->CFGR &= (uint32_t)0xF8FF0000;#else RCC->CFGR &= (uint32_t)0xF0FF0000;#endif /* STM32F10X_CL */ /* Reset HSEON, CSSON and PLLON bits */ RCC->CR &= (uint32_t)0xFEF6FFFF; /* Reset HSEBYP bit */ RCC->CR &= (uint32_t)0xFFFBFFFF; /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */ RCC->CFGR &= (uint32_t)0xFF80FFFF;#ifdef STM32F10X_CL /* Reset PLL2ON and PLL3ON bits */ RCC->CR &= (uint32_t)0xEBFFFFFF; /* Disable all interrupts and clear pending bits */ RCC->CIR = 0x00FF0000; /* Reset CFGR2 register */ RCC->CFGR2 = 0x00000000;#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL) /* Disable all interrupts and clear pending bits */ RCC->CIR = 0x009F0000; /* Reset CFGR2 register */ RCC->CFGR2 = 0x00000000; #else /* Disable all interrupts and clear pending bits */ RCC->CIR = 0x009F0000;#endif /* STM32F10X_CL */ #if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL) #ifdef DATA_IN_ExtSRAM SystemInit_ExtMemCtl(); #endif /* DATA_IN_ExtSRAM */#endif /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */ /* Configure the Flash Latency cycles and enable prefetch buffer */ SetSysClock();#ifdef VECT_TAB_SRAM SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */#else SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */#endif } SetSysClock()函数 123456789101112131415static void SetSysClock(void){#ifdef SYSCLK_FREQ_HSE SetSysClockToHSE();#elif defined SYSCLK_FREQ_24MHz SetSysClockTo24();#elif defined SYSCLK_FREQ_36MHz SetSysClockTo36();#elif defined SYSCLK_FREQ_48MHz SetSysClockTo48();#elif defined SYSCLK_FREQ_56MHz SetSysClockTo56(); #elif defined SYSCLK_FREQ_72MHz SetSysClockTo72();#endif 因为前面定义默认时钟为72MHz,所以SetSysClockTo72() 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495static void SetSysClockTo72(void){ __IO uint32_t StartUpCounter = 0, HSEStatus = 0; /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/ /* Enable HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* Wait till HSE is ready and if Time out is reached exit */ do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } if (HSEStatus == (uint32_t)0x01) { /* Enable Prefetch Buffer */ FLASH->ACR |= FLASH_ACR_PRFTBE; /* Flash 2 wait state cpu比flash快,等待flash */ FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; /* HCLK = SYSCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK2 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; /* PCLK1 = HCLK/2 */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;#ifdef STM32F10X_CL /* Configure PLLs ------------------------------------------------------*/ /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */ /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */ RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL | RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC); RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 | RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5); /* Enable PLL2 */ RCC->CR |= RCC_CR_PLL2ON; /* Wait till PLL2 is ready */ while((RCC->CR & RCC_CR_PLL2RDY) == 0) { } /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLMULL9); #else /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);#endif /* STM32F10X_CL */ /* Enable PLL */ RCC->CR |= RCC_CR_PLLON; /* Wait till PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) { } /* Select PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; /* Wait till PLL is used as system clock source */ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { } } else { /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */ }}#endif 对照中文参考手册和时钟框图还挺好理解的。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[GPIO_KEY实验笔记]]></title>
<url>%2Fposts%2F11950%2F</url>
<content type="text"><![CDATA[本来觉得这次实验比较简单,就不写了,然而好像太高估自己了。。 首先是对比自己写的代码和给的源码,发现源码比我多了一行 1GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试 注释前后,对实验好像没影响。看了一下网上的说法,大概是这样的: STM32F10x系列的MCU复位后,PA13/14/15 & PB3/4默认配置为JTAG功能。有时我们为了充分利用MCU I/O口的资源,会把这些端口设置为普通I/O口。 而KEY0的IO口恰好是PA15。 然后是KEY_Scan()这个函数的写法,源码实在是太简洁而严谨了,感觉自己还差的很多啊。还有就是关于static这个关键字,让我发现之前关于C语言的一些深入的知识已经不太记得了,需要重新系统的学习一遍。 123456789101112131415u8 KEY_Scan(u8 mode){ static u8 key_up = 1; if (mode) key_up = 1; if (key_up && (KEY1==0 || KEY0==0 || WK_UP==1)) { delay_ms(10); key_up = 0; if (KEY1==0) return KEY1_ON; else if (KEY0==0) return KEY0_ON; else if (WK_UP==1) return WKUP_ON; } else if (KEY1==1 && KEY0==1 && WK_UP==0) key_up = 1; return 0;}]]></content>
<tags>
<tag>stm32</tag>
<tag>笔记</tag>
</tags>
</entry>
<entry>
<title><![CDATA[延时消抖]]></title>
<url>%2Fposts%2F48277%2F</url>
<content type="text"><![CDATA[Key_Scan()函数中有一段代码 1234567891011121314if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON){ /*延时消抖*/ Delay(10000); if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON) { while (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON); return KEY_ON; } else return KEY_OFF;}elsereturn KEY_OFF; 好奇什么是延时消抖,为什么有2条重复的代码if (GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON) 百度百科说:按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。 所以,Key_Scan()才要读取2次GPIO_ReadInputDataBit读取的数据。键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。 抖动时间:抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。参照这个老哥的说法nickqiang,这个参数应该挺准确的 然并软,这样的消抖在实际的工程应用并无价值,在实际的工程应用中,是使用一个周期中断函数,每隔一段时间调用该函数来检测设备所有按键,把按下的键用不同的全局变量记录下来。在检测时也是使用短暂延时来消抖(滤波)。 像上面那样消抖,可以达到去抖的目的,但是实现方式太过暴力,在延时的时候一直占用cpu的资源,如果在延时的时候,有其他外部中断或者抢占事件,系统完全没有响应的。]]></content>
<tags>
<tag>stm32</tag>
<tag>笔记</tag>
</tags>
</entry>
<entry>
<title><![CDATA[stm32蜂鸣器实验总结]]></title>
<url>%2Fposts%2F31496%2F</url>
<content type="text"><![CDATA[由于家境贫寒只买得起mini版的缘故,关于蜂鸣器的实验只能另外买蜂鸣器外设来做实验了。也因此蜂鸣器所使用的IO口可以自定义,并不一定只能用PB.8来做实验。实验中出现了一些小问题,记录下来,以便之后的复习。 首先是在写beef.c的时候,变量的声明放在了可执行语句的后面。 12345678910111213141516#include "beep.h"#include "stm32f10x.h"void BEEP_Init(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_ResetBits(GPIOB,GPIO_Pin_8); } 使用MDK调试的时候,出现以下错误: error: #268: declaration may not appear after executablestatement 修改为: 12345678910111213141516#include "beep.h"#include "stm32f10x.h"void BEEP_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_ResetBits(GPIOB,GPIO_Pin_8); } 编译通过。 第二个错误是误把RCC_APB2PeriphClockCmd写成RCC_APB2PeriphResetCmd,由于这两个函数前后一样,且RCC_APB2PeriphResetCmd函数在更显眼的地方,以后要注意。 接下来是关于蜂鸣器实验的一些总结。 蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电。STM32 开发板板载的蜂鸣器是电磁式的有源蜂鸣器。这里的有源不是指电源的“源”,而是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路,一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供2~5Khz 左右的方波驱动,才能发声。 STM32 的单个 IO 最大可以提供 25mA 电流(来自数据手册),而蜂鸣器的驱动电流是 30mA 左右,两者十分相近,但是全盘考虑,STM32 整个芯片的电流,最大也就 150mA,如果用 IO 口直接驱动蜂鸣器,其他地方用电就得省着点了…所以,我们不用 STM32 的 IO 直接驱动蜂鸣器,而是通过三极管扩流后再驱动蜂鸣器,这样STM32 的 IO 只需要提供不到 1mA 的电流就足够了。 蜂鸣器与 STM32 连接原理图 : 图中我们用到一个 NPN 三极管(S8050)来驱动蜂鸣器,R38 主要用于防止蜂鸣器的误发声。当 PB.8 输出高电平的时候,蜂鸣器将发声,当 PB.8 输出低电平的时候,蜂鸣器停止发声。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[stm32的3种开发方式]]></title>
<url>%2Fposts%2F31616%2F</url>
<content type="text"><![CDATA[前言stm32有3种不同的开发方式,库函数开发,基于寄存器开发与位带操作。3种方式各有优劣,这里我简单的写一下自己的看法吧。 在51单片机的程序开发中,我们直接配置寄存器。因为51的功能相对比较简单,对应的寄存器也比较少,所以可以通过配置寄存器的方式开发。到了stm32中,虽然原理相同,但功能多了非常多,寄存器也相对的多了,这时想象51一样记住每个寄存器是不可能的。这时ST公司提供了库函数,开发者只需调用这些函数接口来配置寄存器。 而这3种方式,本质上都是通过不同的途径,最终实现对相应寄存器的配置。 基于寄存器开发直接配置寄存器,可以更直观的了解配置的是那些寄存器以及是如何配置的,同时运行程序是占用资源也更少,代码更加简洁。 1234567891011121314void LED_Init(void){ RCC->APB2ENR|=1<<2; RCC->APB2ENR|=1<<5; //GPIOA.8 GPIOA->CRH&=0XFFFFFFF0; GPIOA->CRH|=0X00000003; GPIOA->ODR|=1<<8; //GPIOD.2 GPIOD->CRL&=0XFFFFF0FF; GPIOD->CRL|=0X00000300; GPIOD->ODR|=1<<2;} 这样的方式是内核执行效率最高的方式,然而由于寄存器数量和复杂度的增加,直接配置寄存器的开发速度和程序可读性就下降。 库函数开发库的本质就是建立一个新的软件抽象层,分层使得问题变得简单,屏蔽了底层实现方式的差异,让软件开发变成简单的调用函数。 从内核的执行效率来看,首先库函数在被调用的时候,要耗费调用的时间,在函数内部,把输入参数转换成直接写入到寄存器的值也耗费了一些运算时间。优点是可以快速上手stm32微控制器,交流方便,查错简单。 123456789101112131415161718void LED_Init (void){ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD,ENABLE); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_8); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOD,&GPIO_InitStructure); GPIO_SetBits(GPIOD,GPIO_Pin_2); } 位带操作(记得引入sys.h头文件)原理把每个比特膨胀为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。 优点对硬件I/O密集型的底层程序提供了很大方便,使代码更加简洁。化简跳转的判断。在多任务中实现共享资源在任务间的“互锁”访问。 支持区域其中一个是 SRAM 区的最低 1MB 范围,0x20000000 ‐ 0x200FFFFF(SRAM 区中的最低 1MB) 第二个则是片内外设区的最低 1MB范围,0x40000000 ‐ 0x400FFFFF(片上外设区中最低 1MB) 123456789101112131415161718#define LED0 PAout(8) // PA8#define LED1 PDout(2) // PD2 int main(void){ Stm32_Clock_Init(9); //系统时钟设置 delay_init(72); //延时初始化 LED_Init(); //初始化与LED连接的硬件接口 while(1) { LED0=0; LED1=1; delay_ms(300); LED0=1; LED1=0; delay_ms(300); } } 最后随着处理器性能的不断提升,库函数开发会成为趋势,但寄存器可以对原理有更深入的了解。库和寄存器交叉学习,开发用库,然后慢慢深入,加深理解。]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hexo博客的玄学加速]]></title>
<url>%2Fposts%2F50535%2F</url>
<content type="text"><![CDATA[前言利用 InstantClick、hexo-service-worker、hexo-filter-optimize 加速博客 InstantClickInstantClick是一个JavaScript库,可以通过预加载显着加快网站的速度。先下载压缩版的 instantclick.min.js(浏览器右键保存为),再放到~/next/source/js/src/下,然后添加代码: 12<script type="text/javascript" src= "/js/src/instantclick.min.js" data-no-instant></script><script data-no-instant>InstantClick.init();</script> 去除顶部加载条: 在next/source/css/_custom/custom.styl文件添加以下代码: 123#instantclick { display: none;} hexo-service-workerhexo-service-worker 是一个 hexo 用来让博客拥有 Service Worker 功能的插件,能够默认的把站点中 public 内的所有静态资源包括 html 文件缓存起来,达到离线可访问的效果 安装1npm i hexo-service-worker --save 用法安装插件后,直接配置 _config.yml 文件如下就可以了: 1234567# offline config passed to sw-precache.service_worker: maximumFileSizeToCacheInBytes: 5242880 staticFileGlobs: - public/**/*.{js,html,css,png,jpg,gif,svg,eot,ttf,woff,woff2} stripPrefix: public verbose: true hexo-filter-optimize安装1npm install hexo-filter-optimize --save 用法安装插件后,直接配置 _config.yml 文件如下就可以了: 12345678910111213141516171819202122232425filter_optimize: enable: true # remove static resource query string # - like `?v=1.0.0` remove_query_string: true # remove the surrounding comments in each of the bundled files remove_comments: false css: enable: true # bundle loaded css file into the one bundle: true # use a script block to load css elements dynamically delivery: true # make specific css content inline into the html page # - only support the full path # - default is ['css/main.css'] inlines: excludes: js: # bundle loaded js file into the one bundle: true excludes: # set the priority of this plugin, # lower means it will be executed first, default is 10 priority: 12 修复网页图标不显示在主题配置文件中,让fontawesome 使用 cdn 即可,搜索fontawesome,修改如下: 123# Internal version: 4.6.2 # See: http://fontawesome.io/ fontawesome: http://maxcdn.bootstrapcdn.com/font-awesome/4.6.2/css/font-awesome.min.css]]></content>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title><![CDATA[GPIO_Init源码及注释]]></title>
<url>%2Fposts%2F53676%2F</url>
<content type="text"><![CDATA[GPIO_Init源码及注释 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)//把GPIOx的地址转换为GPIO_TypeDef结构体指针类型{ uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00; uint32_t tmpreg = 0x00, pinmask = 0x00; /* 断言,用于检查输入的参数是否正确 */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode)); assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); /*---------------------------- GPIO的模式配置 -----------------------*//*把输入参数GPIO_Mode的低四位暂存在currentmode*/ currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);/*判断是否为输出模式,若是输出模式,可输入参数中输出模式的bit4位都是1typedef enum{ GPIO_Mode_AIN = 0x0, GPIO_Mode_IN_FLOATING = 0x04, GPIO_Mode_IPD = 0x28, GPIO_Mode_IPU = 0x48, GPIO_Mode_Out_OD = 0x14, GPIO_Mode_Out_PP = 0x10, GPIO_Mode_AF_OD = 0x1C, GPIO_Mode_AF_PP = 0x18}GPIOMode_TypeDef;*/ if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) { /* 检查输入参数 */ assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); /* 输出模式,所以要配置GPIO的速率(00(输入模式) 01(10MHz)) 10(2MHz) 11(20MHz) */ currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed; }//GPIO_Mode的低四位分别为0000,0100,1000,1100.对速率无影响/*---------------------------- 配置GPIO的CRL寄存器 ------------------------*/ /* 判断要配置的是否为pin0~pin7*//*#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected*/#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected*/#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected*/#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected*/#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected*/#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected*/#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */ */ if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) { /*备份原CRL寄存器的值*/ tmpreg = GPIOx->CRL; /*循环,一个循环设置一个寄存器位*/ for (pinpos = 0x00; pinpos < 0x08; pinpos++) { /*pos的值为1左移pinpos位*/ pos = ((uint32_t)0x01) << pinpos; /* 令pos与输入参数GPIO_PIN做位与运算,为下面的判断做准备 */ currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; /*判断,若currentpin等于pos,说明GPIO_PIN参数中含的第pos个引脚需要配置*/ if (currentpin == pos) { /*pos的值左移2位(乘以4),因为寄存器中4个寄存器位配置一个引脚*/ pos = pinpos << 2; /*以下两个句子,把控制这个引脚的4个寄存器位清零,其他寄存器位不变*/ /* Clear the corresponding low control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* 向寄存器写入将要配置的引脚模式 */ tmpreg |= (currentmode << pos); /* 复位GPIO引脚的输入输出默认值 */ /* 判断是否为下拉输入模式 */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { /*下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置零*/ GPIOx->BRR = (((uint32_t)0x01) << pinpos); } else { /* 判断是否为上拉输入模式 */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { /*上拉输入模式,引脚默认置1,对BSRR寄存器写1可对引脚置1*/ GPIOx->BSRR = (((uint32_t)0x01) << pinpos); } } } } /*把前面处理后的暂存值写入CRL寄存器之中*/ GPIOx->CRL = tmpreg; }/*---------------------------- GPIO CRH Configuration ------------------------*/ /* Configure the eight high port pins */ if (GPIO_InitStruct->GPIO_Pin > 0x00FF) { tmpreg = GPIOx->CRH; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = (((uint32_t)0x01) << (pinpos + 0x08)); /* Get the port pins position */ currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos); if (currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding high control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08)); } /* Set the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08)); } } } GPIOx->CRH = tmpreg; }}]]></content>
<tags>
<tag>C语言</tag>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[stm32 I/O口的八种工作模式]]></title>
<url>%2Fposts%2F33448%2F</url>
<content type="text"><![CDATA[stm32 I/O口有8种工作模式,分别为4种输入模式和4种输出模式: 4种输入模式: 1234(1)GPIO_Mode_AIN 模拟输入 (2)GPIO_Mode_IN_FLOATING 浮空输入 (3)GPIO_Mode_IPD 下拉输入 (4)GPIO_Mode_IPU 上拉输入 4种输出模式: 1234(5)GPIO_Mode_Out_OD 开漏输出 (6)GPIO_Mode_Out_PP 推挽输出 (7)GPIO_Mode_AF_OD 复用开漏输出 (8)GPIO_Mode_AF_PP 复用推挽输出 那么这8种工作模式具体的含义是什么呢? 浮空,顾名思义就是浮在空中,上面用绳子一拉就上去了,下面用绳子一拉就沉下去了. 开漏,就等于输出口接了个NPN三极管,并且只接了e,b. c极 是开路的,你可以接一个电阻到3.3V,也可以接一个电阻到5V,这样,在输出1的时候,就可以是5V电压,也可以是3.3V电压了.但是不接电阻上拉的时候,这个输出高就不能实现了. 推挽,就是有推有拉,任何时候IO口的电平都是确定的,不需要外接上拉或者下拉电阻. 推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。输出高电平的时候,P-MOS管导通。低电平时,N-MOS管导通。两个管子轮流导通,一个负责灌电流,一个负责拉电流 ,使其负载能力和开关速度都比普通的方式有很大的提高。推挽输出的供电平为0伏,高电平为3.3伏。 开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。如果我们控制输出为0,低电平,则N-MOS管导通,使输出接地。若控制输出为1,则既不输出高电平,也不输出低电平,为高阻态,需外接一个上拉电阻。他具有“线与”特性,即很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部上拉电阻所接电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整个线路都为低电平,0伏。开漏形式的电路有以下几个特点: 利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经R pull-up ,MOSFET到GND。IC内部仅需很下的栅极驱动电流。 一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻值决定了逻辑电平转换的沿的速度 。阻值越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。) OPEN-DRAIN提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。 可以将多个开漏输出的Pin,连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系。这也是I2C,SMBus等总线判断总线占用状态的原理。 补充:什么是“线与”?: 在一个结点(线)上, 连接一个上拉电阻到电源 VCC 或 VDD 和 n 个 NPN 或 NMOS 晶体管的集电极 C 或漏极 D, 这些晶体管的发射极 E 或源极 S 都接到地线上, 只要有一个晶体管饱和, 这个结点(线)就被拉到地线电平上. 因为这些晶体管的基极注入电流(NPN)或栅极加上高电平(NMOS),晶体管就会饱和, 所以这些基极或栅极对这个结点(线)的关系是或非 NOR 逻辑. 如果这个结点后面加一个反相器, 就是或 OR 逻辑. 其实可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为0,只有都为高电平时,与的结果才为逻辑1。 推挽输出模式一般应用在输出电平为0和3.3伏的场合。而开漏输出模式一般应用在电平不匹配的场合,如需要输出5伏的电压。 浮空输入模式在芯片内部既没有接上拉,也没有接下拉电阻,经由触发器输入。配置成这个模式直接用电压表测量其引脚电压为1点几伏,是个不确定值。由于其输入阻抗较大,一般把这种模式用于标准的通信协议如I2C,USART的接收端。由于浮空输入一般多用于外部按键输入,浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的。 上拉输入/下拉输入/模拟输入:这几个概念很好理解,从字面便能轻易读懂。 若配置为上拉输入模式,默认情况下(GPIO引脚无输入),读取的GPIO引脚数据为1,高电平。下拉输入模式则相反,在默认情况下其引脚数据为0,低电平。 模拟输入模式则关闭了施密特触发器,不接上下拉电阻,经由另一线路把电压信号传送到片上外设模块。如传送至ADC模块,由ADC模块采集电压信号。所以采用ADC外设的时候,必须使用模拟输入模式。 复用开漏输出、复用推挽输出:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)最后总结下使用情况:在STM32中选用IO模式(1) 浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1(2)带上拉输入_IPU——IO内部上拉电阻输入(3)带下拉输入_IPD—— IO内部下拉电阻输入(4) 模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)STM32设置实例:(1)模拟I2C使用开漏输出_OUT_OD,接上拉电阻,能够正确输出0和1;读值时先GPIO_SetBits(GPIOB, GPIO_Pin_0);拉高,然后可以读IO的值;使用GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0);(2)如果是无上拉电阻,IO默认是高电平;需要读取IO的值,可以使用带上拉输入_IPU和浮空输入_IN_FLOATING和开漏输出_OUT_OD;]]></content>
<tags>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[断言]]></title>
<url>%2Fposts%2F58240%2F</url>
<content type="text"><![CDATA[ 阅读GPIO_Init源码时,发现断言函数 assert,如下: 1234/* Check the parameters */assert_param(IS_GPIO_ALL_PERIPH(GPIOx));assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); 为了能深入的理解断言在程序运行中的作用,我尝试对断言进行总结。在使用C语言编写工程代码时,我们总会对某种假设条件进行检查,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。然而assert并不仅仅是一个报错函数,事实上它还是一个宏。 断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言,而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题可以用断言来进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。 原型定义:void assert( int expression ); assert宏的原型定义在<assert.h>中,其作用是先计算表达式 expression ,如果expression的值为假(即为0),那么它首先打印一条出错信息,然后通过调用abort 来终止程序运行。下面来看看一段代码: 1234567891011#include <stdio.h>#include <assert.h>int main( void ){ int i; i=1; assert(i++); printf("%d\n",i); return 0;} 因为我们给定的i初始值为1,所以使用assert(i++);语句的时候不会出现错误,进而执行了i++,所以其后的打印语句输出值为2。如果我们把i的初始值改为0,那么就回出现如下错误。Assertion failed: i++, file E:\fdsa\assert2.cpp, line 8Press any key to continue 断言语句不是永远会执行,可以屏蔽也可以启用,这就要求assert不管是在屏蔽还是启用的情况下都不能对我们本身代码的功能有所影响,这样的话刚才我们在代码中使用了一句assert(i++);是不妥的,因为我们一旦禁用了assert,i++的语句就得不到执行,对于接下来i值的使用就会出现问题了,所以对于这样的语句我们应该是要分开来实现,写出如下两句来替代, assert(i); i++;,所以这就对于断言的使用有了相应的要求,那么我们一般在什么情况下使用断言呢?主要体现在一下几个方面: 1.可以在预计正常情况下程序不会到达的地方放置断言。(如assert (0);) 2.使用断言测试方法执行的前置条件和后置条件 。 3.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如某个变量的变化范围) 对于上面的前置条件和后置条件可能有的读者还不是很了解,那么看看下面的解释你就明白了。 1.前置条件断言:代码执行之前必须具备的特性 2.后置条件断言:代码执行之后必须具备的特性 3.前后不变断言:代码执行前后不能变化的特性 当然在使用的断言的过程中会有一些我们应该注意的事项和养成一些良好的习惯,如: 1.每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,我们就无法直观的判断是哪个条件失败 2.不能使用改变环境的语句,就像我们上面的代码改变了i变量,在实际编写代码的过程中是不能这样做的 3.assert和后面的语句应空一行,以形成逻辑和视觉上的一致感,也算是一种良好的编程习惯吧,让编写的代码有一种视觉上的美感 4.有的地方,assert不能代替条件过滤 5.放在函数参数的入口处检查传入参数的合法性 6.断言语句不可以有任何边界效应 7.频繁的调用会极大的影响程序的性能,增加额外的开销。所以在调试结束后,可以通过在包含#include 的语句之前插入 #define NDEBUG 来禁用assert调用。 现在回到GPIO_Init源码中,通过以下2段代码我们可以更好的理解断言在实际开发中的应用 1assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); 1234567#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \ ((PERIPH) == GPIOB) || \ ((PERIPH) == GPIOC) || \ ((PERIPH) == GPIOD) || \ ((PERIPH) == GPIOE) || \ ((PERIPH) == GPIOF) || \ ((PERIPH) == GPIOG)) 由第二段代码的含参宏,确定传入的参数是否符合要求,这种用法为上述的前置条件断言。]]></content>
<tags>
<tag>C语言</tag>
<tag>stm32</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2Fposts%2F16107%2F</url>
<content type="text"><![CDATA[前言本来想把这个初始化的界面删了,后来想想算了,干脆用这个界面来记录建立博客的经过,方便以后参考。 由于这两天的建站,严重影响自学的进度,就主要记录一些采过的坑吧。 参考教程基础使用Hexo+Github一步步搭建属于自己的博客(基础) 使用Hexo+Github一步步搭建属于自己的博客(进阶) 进阶打造个性超赞博客Hexo+NexT+GitHubPages的超深度优化 hexo的next主题个性化配置教程 内容博文压缩教程在站点的根目录下执行以下命令: 12$ npm install gulp -g$ npm install gulp-minify-css gulp-uglify gulp-htmlmin gulp-htmlclean gulp --save 进入博客根目录,新建gulpfile.js文件,内容如下: 123456789101112131415161718192021222324252627282930313233var gulp = require('gulp');var minifycss = require('gulp-minify-css');var uglify = require('gulp-uglify');var htmlmin = require('gulp-htmlmin');var htmlclean = require('gulp-htmlclean');// 压缩 public 目录 cssgulp.task('minify-css', function() { return gulp.src('./public/**/*.css') .pipe(minifycss()) .pipe(gulp.dest('./public'));});// 压缩 public 目录 htmlgulp.task('minify-html', function() { return gulp.src('./public/**/*.html') .pipe(htmlclean()) .pipe(htmlmin({ removeComments: true, minifyJS: true, minifyCSS: true, minifyURLs: true, })) .pipe(gulp.dest('./public'))});// 压缩 public/js 目录 jsgulp.task('minify-js', function() { return gulp.src('./public/**/*.js') .pipe(uglify()) .pipe(gulp.dest('./public'));});// 执行 gulp 命令时执行的任务gulp.task('default', [ 'minify-html','minify-css','minify-js']); 生成博文是执行 hexo g && gulp 就会根据 gulpfile.js 中的配置,对 public 目录中的静态资源文件进行压缩。 填坑然而,运行 hexo g && gulp 时,会出现以下错误: 123456789101112131415assert.js:350 throw err; ^AssertionError [ERR_ASSERTION]: Task function must be specified at Gulp.set [as _setTask] (D:\nodejs\blog\node_modules\undertaker\lib\set-task.js:10:3) at Gulp.task (D:\nodejs\blog\node_modules\undertaker\lib\task.js:13:8) at Object.<anonymous> (D:\nodejs\blog\gulpfile.js:31:6) at Module._compile (internal/modules/cjs/loader.js:701:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10) at Module.load (internal/modules/cjs/loader.js:600:32) at tryModuleLoad (internal/modules/cjs/loader.js:539:12) at Function.Module._load (internal/modules/cjs/loader.js:531:3) at Module.require (internal/modules/cjs/loader.js:637:17) at require (internal/modules/cjs/helpers.js:22:18) gulp项目需要全局安装gulp和项目内安装gulp,通过 gulp -v 查看全局gulp 和本地项目的gulp版本: 123$ gulp -v[22:12:41] CLI version 2.0.1[22:12:41] Local version 4.0.0 查看项目里的D:\nodejs\blog\package.json文件,可以看到gulp版本 12345678910111213"devDependencies": { "babel-core": "^6.26.3", "babel-preset-es2015": "^6.24.1", "del": "^4.0.0", "gulp": "^4.0.1", "gulp-babel": "^8.0.0", "gulp-clean-css": "^4.0.0", "gulp-htmlclean": "^2.7.22", "gulp-htmlmin": "^5.0.1", "gulp-imagemin": "^5.0.3", "gulp-uglify": "^3.0.2", "run-sequence": "^2.2.1" } 将 "gulp": "^4.0.1",改成 "gulp": "^2.0.1",发现出现错误Unsupported gulp version 将版本号从gulp2改成改成gulp3"gulp": "^2.0.1",成功。 鼠标飘字特效教程新建一个js:*/themes/next/source/js/src/jiazhiguan.js 12345678910111213141516171819202122232425262728/* 鼠标飘字*/var a_idx = 0;jQuery(document).ready(function($) {$("body").click(function(e) {var a = new Array("富强", "民主", "文明", "和谐", "自由", "平等", "公正" ,"法治", "爱国", "敬业", "诚信", "友善");var $i = $("<span/>").text(a[a_idx]);a_idx = (a_idx + 1) % a.length;var x = e.pageX,y = e.pageY;$i.css({"z-index": 999999999999999999999999999999999999999999999999999999999999999999999,"top": y - 20,"left": x,"position": "absolute","font-weight": "bold","color": "#a40000"});$("body").append($i);$i.animate({"top": y - 180,"opacity": 0},1500,function() {$i.remove();});});}); 然后在Blog\themes\next\layout\_layout.swig文件的最下方,</body>前面添加<script type="text/javascript" src="/js/src/jiazhiguan.js"></script>。 填坑由于ANSI编码无中文字符,所以出现乱码。将编码从ANSI改成utf-8编码。 用记事本打开这个1.html,把这文档另存到原来的目录,覆盖自己,只是编码要从ANSI改成utf-8。完成。 文章加密访问打开themes->next->layout->_partials->head.swig文件,在pace段后面插入这样一段代码: 1234567891011121314<script> (function () { if ('{{ page.password }}') { if (prompt('请输入文章密码') !== '{{ page.password }}') { alert('密码错误!'); if (history.length === 1) { location.replace("http://xxxxxxx.xxx"); // 这里替换成你的首页 } else { history.back(); } } } })();</script> 然后在文章上写成类似这样: 1234567title: 断言tags: - C语言 - stm32abbrlink: 58240date: 2019-03-17 23:10:42password: ******** 添加网页标题崩溃欺骗搞怪特效创建js文件 在 next\source\js\src 文件夹下创建 crash_cheat.js,添加代码如下: 1234567891011121314151617<!--崩溃欺骗--> var OriginTitle = document.title; var titleTime; document.addEventListener('visibilitychange', function () { if (document.hidden) { $('[rel="icon"]').attr('href', "/img/TEP.ico"); document.title = '╭(°A°`)╮ 页面崩溃啦 ~'; clearTimeout(titleTime); } else { $('[rel="icon"]').attr('href', "/favicon.ico"); document.title = '(ฅ>ω<*ฅ) 噫又好了~' + OriginTitle; titleTime = setTimeout(function () { document.title = OriginTitle; }, 2000); } }); 引用在 next\layout\_layout.swig 文件中,添加引用(注:在swig末尾添加): 12<!--崩溃欺骗--><script type="text/javascript" src="/js/src/crash_cheat.js"></script> 引用本地图片插件安装与配置1npm install hexo-asset-image --save 打开/node_modules/hexo-asset-image/index.js,将内容更换为下面的代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061'use strict';var cheerio = require('cheerio');// http://stackoverflow.com/questions/14480345/how-to-get-the-nth-occurrence-in-a-stringfunction getPosition(str, m, i) { return str.split(m, i).join(m).length;}var version = String(hexo.version).split('.');hexo.extend.filter.register('after_post_render', function(data){ var config = hexo.config; if(config.post_asset_folder){ var link = data.permalink; if(version.length > 0 && Number(version[0]) == 3) var beginPos = getPosition(link, '/', 1) + 1; else var beginPos = getPosition(link, '/', 3) + 1; // In hexo 3.1.1, the permalink of "about" page is like ".../about/index.html". var endPos = link.lastIndexOf('/') + 1; link = link.substring(beginPos, endPos); var toprocess = ['excerpt', 'more', 'content']; for(var i = 0; i < toprocess.length; i++){ var key = toprocess[i]; var $ = cheerio.load(data[key], { ignoreWhitespace: false, xmlMode: false, lowerCaseTags: false, decodeEntities: false }); $('img').each(function(){ if ($(this).attr('src')){ // For windows style path, we replace '\' to '/'. var src = $(this).attr('src').replace('\\', '/'); if(!/http[s]*.*|\/\/.*/.test(src) && !/^\s*\//.test(src)) { // For "about" page, the first part of "src" can't be removed. // In addition, to support multi-level local directory. var linkArray = link.split('/').filter(function(elem){ return elem != ''; }); var srcArray = src.split('/').filter(function(elem){ return elem != '' && elem != '.'; }); if(srcArray.length > 1) srcArray.shift(); src = srcArray.join('/'); $(this).attr('src', config.root + link + src); console.info&&console.info("update link as:-->"+config.root + link + src); } }else{ console.info&&console.info("no src attr, skipped..."); console.info&&console.info($(this)); } }); data[key] = $.html(); } }}); 打开_config.yml文件,修改下述内容 1post_asset_folder: true 填坑打开_config.yml将url修改为下述内容 123# URL## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'url: https://wangzipai.github.io 参考hexo引用本地图片无法显示]]></content>
<tags>
<tag>hexo</tag>
</tags>
</entry>
</search>