如果不會暫存器開發而陷入瓶頸,那麼本文將會有較大幫助
如果
不會暫存器開發而陷入瓶
頸
,
那麼本文將會有較大幫助
//
/
插播一條:我自己在今年年初錄製了一套還比較系統的入門微控制器教程,想要的同學找我拿就行了免費的,私信我就可以
哦
~
點我頭像黑色字型加我地球呺也能領取哦。最近比較閒,帶做畢設,帶學生參加省級或以上比
賽
///
0。
緒論
對於經過系統培訓的開發
者
。
微控制器
或
So
C
的驅動開
發
,
不管是使用各種庫還是直接上寄存
器
,
都不成問
題
。
可是很多非專業但是需要幹嵌入式或微控制器工程的
人
,
比如機
械
,
車輛工
程
,
物
理
,
或是其他的一些專
業
。
有時候這些學生需要搞比
賽
,
做項
目
,
不可避免的要用到一些單片
機
(
由於種種原
因
,
現在幾乎都會首
選
STM32)。
但是缺乏系統訓練的學生往往無法看懂暫存器版本的例
程
,
或是別人的開源項
目
。
自己寫的話更是不知道如何開
始
。
或者編譯完成後總是出現莫名的問
題
。
這給大家帶來了極大的困
難
。
同時網際網路上大部分教程都是轉載來轉載
去
,
各種差異和版本都
有
。
很多人即使看過了也是一頭霧
水
。
所以應一個同學要
求
,
大概寫一下暫存器的一些操
作
。
本教程將會使
用
“
學生使用者體量龐
大
”
的
STM32F1x
x
系列的微控制器作為例
子
。
但是不必擔
心
。
所有的暫存器操作都是共通
的
。
並不會有本質的差
異
。
希望你能快速閱讀完並且理解
完
,
然後查閱自己需要的資
料
。
不管是什麼東
西
,
這種方式都是通用
的
。
嵌入式軟體開發具有一個比較龐大的知識體
系
。
限於作者的時間和水
平
,
本文不可能講太多東
西
。
但是如果你只是
因為不會暫存器開發而陷入瓶
頸
,
那麼本文將會有較大幫助
(
畢竟這個是寫文章的目
的
)。
理解本文需要一些基
礎
,
大概包
括
: c/c++,
一些數
學
,
計算機基礎知
識
。
大多數理工類專業都會有相關的課程
的
。
如果確實存疑可以即時搜
索
。
讓我們先記住開發的正確方
式
:
摘抄借
鑑
,
修改糅
合
,
以實現功能為目
的
,
以別人的程式碼為基
礎
。
只要不涉及什麼智慧財產權的
話
,
這樣做是完全沒有問題
的
。
畢竟不需要把很多基礎性的東西寫來寫
去
。
所以請大膽的找開源項
目
,
儘可能在基礎上
改
,
而不是從零開
發
。
不廢話
了
,
開始正
事
。
比如十進位制下
的
0。
1
就是二進位制的無限不迴圈小
數
。
上面那個例子也
是
。
著名
的
IEEE754 floa
t
浮點數標準導致
的
bug:
在很多語言
中
, 0。1+0。
2
≠
0。3
就是因
為
0。
1
是二進位制無限迴圈小數的原
因
。
但是儲存器位寬不能是無窮
的
。
所以產生舍入誤
差
。
在瀏覽器中按
下
F1
2
進入開發者模
式
,
嘗
試
JavaScrip
t
下的浮點數精
度
bug
所以在很多專案
中
,
為了實現當兩個值相等時觸發什麼函
數
,
往往不會直接寫相
等
,
而是兩者的差值小於多少時即生效
float
a,b;……。。
if
(
a
==
b){
/
/
這種寫法不建議
}……。。
if
(
abs(a
-
b)
0。00001
){
/
/
一般這麼寫
}
二進位制與
十
/
十六進位制的轉換
剛才那個方法就可以直接
算
。
還有其他演算法這裡不
講
。
先讓我貼一個表
格
:
BIN
DEC
HEX
0000
0001
1
1
0010
2
2
0011
3
3
0100
4
4
0101
5
5
0110
6
6
0111
7
7
1000
8
8
1001
9
9
1010
10
A
1011
11
B
1100
12
C
1101
13
D
1110
14
E
1111
15
F
BI
N
是二進
制
, DE
C
是十進
制
, HE
X
是十六進
制
。
都是英語簡
寫
。
十六進位制和二進位制可以直接換
算
。
方法是每四位二進位制看作一個十六進位制
比如
0110 1101 1011 1001
6 D B 9
轉換表背過的話讀程式碼快
些
。
因為一般情況
下
,
為了讓一行程式碼看的不至於太
長
,
人們會用十六進位制代表二進
制
。
尤其是對於擁
有
3
2
位
cortex-M
3
核心
的
STM32F
1
系列單片
機
,
一次寫一個三十二位數屬實太冗
長
。
c/c+
+
中
,
預設寫的數字都是十進
制
。
二進位制應該
是
0
b
開
頭
,
比
如
0b00101100,
而十六進位制
是
0
x
開
頭
,
比
如
0x3C。
/
/
一般這麼寫
GPIOB
->
CRL
&=
0x00440000
;
/
/
這麼寫就不太美觀了
GPIOB
->
CRL
&=
0b00000000010001000000000000000000;
數學差不多
了
。
開始正
文
。
2。 c/c+
+
語言基礎
a+b; /
/
加法
a-b; /
/
減法
a*b; /
/
乘法
a/b; /
/
除
法
,
所有計算需要注意整
型
(
整
數
)
和浮點
型
(
小
數
)
的運算區
別
。
如有疑問自行搜尋
a%b; /
/
求
模
。
就是小學學的餘
數
。 14÷4=3 。。。 2
這
裡
14mod4=2, 14/4=3。 float(14)/4=3。5f
a<
/
左
移
。
將
a
看為二進位制
數
,
整個向左移
動
b
位
。
比
如
00000110<
就
是
00011000。
當溢位的時候會發生什麼
呢
?
a>>b; /
/
右
移
。
和上面一樣的功
能
。
a&b; /
/
按位求
與
(AND),
比
如
11010010
// & 01010011
// ——————
// 01010010
a|b; /
/
按位取
或
(
OR)
~a; /
/
按位取
非
(
NOT)
!a; /
/
邏輯
非
(
注意和上面的區
別
)
c+=a; /
/
相當
於
c=c+a;
其他算符同
理
。
重點看左右移和位操
作
(
與
,
或
,
非
等
)。
3。
我們配寄存
器
(register
)
到底是在配置什麼
首
先
,
你的最終目的都是使用微控制器
的
GPIO(general pin input & output
)
讀
取
/
輸出一個高電平還是低電
平
。
不管是諸
如
I2C, SP
I
的通
信
,
還是按鍵讀
取
,
亮燈報
警
,
說到底都是高低電平的控制或探
測
。
AD
C
輸入的是模
擬
(Analog
)
信
號
,
但是會被轉化為數
字
(Digital
)
信
號
,
一樣是高低電
平
。
這裡暫且不談
外
設
,
時
鍾
,
或者一些功能的使能及配置一樣是透過暫存器
的
。
原理相
似
。
畢竟晶片積體電路也是電
路
,
而且是數字電
路
。
暫時不深
入
。
所
以
,
為了讓某一
個
Pi
n
輸出電
平
,
或者使能一個通
道
,
我們可以
用
0
或
者
1
實
現
。
但是程式碼終歸是代
碼
,
不是魔
法
。
為了使需求生
效
,
微控制器將每一個需要控制的
量
,
賦予一個
地址
。
在電路層面上實現相關的功能綁
定
。
使用者只需透過給這個地址去寫一個
值
,
就相當於控制了需要控制的東
西
。
易於計
算
,
我們
的
3
2
位處理器最大可以尋
址
4G
B
的記憶體空
間
。
注
意
:
並不是每一個地址都是有真實物理地址對應
的
。
換而言
之
,
一個地址可能指向的是真實的內
存
,
也可能並不是真實存在的內
存
。
不過訪問這個地址相當於控制了被控量一
樣
。
此時該地址可以看作控制量的一個
句
柄
(
handler)
。
“
使能
”
的意思是
enable
。
反義詞
是
“
禁用
(
disable
)”。
計算機相關的詞彙總是這麼的看不懂字面意
思
。
看英語就明白
了
。
記憶體是儲存資料的地
方
。
任何電子資訊資料都可以看
做
0
1
串
。 3
2
位機的最小儲存單元
是
DWORD
(
雙
字
, Double Word),
包
含
3
2
位元
位
。 3
2
位機的記憶體單元可以看作一個個存
儲
3
2
位元位的小倉
庫
。
為了找到所需要的數
據
,
需要給這些倉庫編
號
。
這個所謂的編號就是
地址
(Address)。
儲存的值是地址值的變數叫做
指
針
(
pointer)
。
比如一個倉庫放著一個記錄某個貨物的多個存放倉庫的編
號
,
那麼這個記憶體裡的資料就是一個指
針
。
計算機無法分辨哪寫是指
針
,
哪些是數
據
。
這需要人去完
成
。
庫函式是暫存器的封裝
。
不管
是
S
T
出
的
HA
L
硬體抽象層
庫
,
還是標準
庫
STL,
都是封裝而
已
。
本質上是宏定義替換和一些函
數
。
宏定義在編
譯
(compile
)
階段完
成
。
不佔用寶貴的儲存空
間
。
函式本身會被編譯為代
碼
,
所以會佔用空
間
。
儘量避免在庫中使用函
數
。
當然使用者程式碼肯定無所
謂
。
為什麼要用庫函式
呢
?
比如
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure。Pin=GPIO_PIN_13;
GPIO_Initure。Mode=GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
這一段代
碼
,
不加註釋你也知道他的意思是初始
化
PB1
3
為推輓輸
出
。
但是如果寫成了暫存器版本的你可能就看不懂
了
。
GPIOB->CRH&=0XFF0FFFFF;
GPIOB->CRH|=0X00300000;
GPIOB->ODR|=1<
現在會簡要介紹暫存器的配置方
法
。
你能搜到這個文章肯定是因為
你找見的開原始碼使用的是暫存器版
本
。
正常人的
話
,
能找見庫函式絕對是不會用暫存器版本的程式碼
的
。
暫存器版本的程式碼實際上也是封
裝
,
比如上述程式碼實際上也可以直接寫成
(
uint32_t *)(((((uint32_t)0x40000000) + 0x10000) + 0x0C00)+0x01)&=0XFF0FFFFF;
(
uint32_t *)(((((uint32_t)0x40000000) + 0x10000) + 0x0C00)+0x01)|=0X00300000;
(
uint32_t *)(((((uint32_t)0x40000000) + 0x10000) + 0x0C00)+0x04)|=1<
/
/
這一段程式碼可能不對
上面就是直接透過指標操作暫存器的方
式
。
暫存器宏定義和結構體相當
於
,
先找
到
GPIO
B
的開頭地
址
,
然後加上結構體的附加地址偏
移
(
去操作某一個功
能
,
比
如
CR
H
寄存
器
)。
最後給這個地址寫入一串二進位制值去控制相應的東
西
。
說到底還是去給某些位元上寫高低電
平
。
類似
於
GPIOB->CR
H
這
種
,
實際上是定義的一個結構
體
。
typedef
struct
{
__IO
uint32_t
CRL;
__IO
uint32_t
CRH;
__IO
uint32_t
IDR;
__IO
uint32_t
ODR;
__IO
uint32_t
BSRR;
__IO
uint32_t
BRR;
__IO
uint32_t
LCKR;}
GPIO_TypeDef;
透過結構體的位關係以及各種宏定義替
換
,
可以實現地址的編譯時自動確
定
。
暫存器的封裝好在對於實際記憶體位置不同的芯
片
,
移植只需更改暫存器宏定義對映即
可
。
而且已經很容易讀
了
。
4。
常見的暫存器配置初始
化
(
使
用
STM32F1x
x
系列舉例
子
)
我們寫驅
動
,
最常用的就
是
GPI
O
的配置
了
。
我們就舉一些例
子
。
RC
C
暫存器
RC
C
暫存器一般用來配
置
RC
C
時鐘相關的代
碼
。
比如我要使
用
PA
0
的
話
,
我必須開
啟
P
A
的時鐘通道才
行
。
時鐘相當於處理器的心
跳
,
沒有心跳當然是死
的
。
但是沒理由的開啟時鐘會帶來額外
的
EM
I
干擾和整機功
耗
。
原理暫且不
談
,
實際危害就是干擾大
了
,
耗電多
了
。
尤其針對於一些對功耗要求嚴格的場
合
,
例如手環手
表
,
應該儘可能在待機時關閉能關閉的所有時
鍾
。
用來省
電
(
比如有的智慧手錶升級系統後待機變久
了
,
可能就是這個原
因
。
我們說的
固
件
(
firmware)
升級實際上指的就是把新寫的程式碼下載到了老裝置
上
)。
舉個例
子
, RC
C
時鐘的配置如下
RCC->APB2ENR|=1<
/
使
能
PORT
C
時鐘
RCC->APB2ENR|=1<
/
使
能
PORT
B
時鐘
RCC->APB2ENR|=1<
/
使
能
PORT
A
時鐘
具體使能哪一個位是控制
誰
,
需要查
表
(
除非你背過
了
)。
如果是需要快速出項
目
,
不管其他
的
,
可以省
事
,
直接使能所有時
鍾
。
但是不建議這麼
幹
。