江科大32study
江科大自化協(stm32)
STM32F103RCT6產品參數
| 產品型號 | 內核 | 主頻(MHz) | Flash (Kbytes) | |
|---|---|---|---|---|
| STM32F103RCT6 | Cortex-M3 | 72 | 256 | |
| RAM(Kbytes) | E2PROM(Bytes) | 封裝 | IO | |
| 48 | 0 | LQFP64 | 51 | |
| 工作電壓 | 16位定時器 | 32位定時器 | 電機控制定時器 (16-bit) | |
| 2-3.6 | 8 | 0 | 2 | |
| 低功耗定時器 | 高分辨率定時器 | 12位ADC轉換單元 | 12位ADC通道 | |
| 0 | 0 | 3 | 16 | |
| 14位ADC轉換單元 | 14位ADC通道 | 16位ADC轉換單元 | 16位ADC通道 | |
| 0 | 0 | 0 | 0 | |
| 12位DAC通道 | 比較器 | 放大器 | SPI | |
| 2 | 0 | 0 | 3 | |
| I2S | M-SPI | I2C | U(S)ART | |
| 2 | 0 | 2 | 5 | |
| 低功耗UART | CAN | SDIO | F(S)MC | |
| 0 | 1 | 1 | 0 | |
| USB Device | USB FS HOST/OTG | USB HS OTG | Ethernet | |
| 1 | 0 | 0 | 0 | |
| MDIO | Segment LCD | JPEG Codec | GPU | |
| 0 | 0 | N/A | N/A | |
| 3D GPU | TFT LCD | MIPI_DSI | SAI | |
| N/A | 0 | 0 | 0 | |
| SPDIFRX | DFSDM | DCMI | SWPMI | |
| 0 | 0 | 0 | 0 | |
| Math Accelerator | RF | Trust'Zone | TRNG | |
| N/A | N/A | N/A | N/A | |
| OTFDEC | PKA | AES/DES | SHA/HMAC | |
| N/A | N/A | N/A | N/A | |
| T° Max(℃) | URL | content | ||
| 85 | https://www.st.com/en/product/STM32F103RC |
STM32F103RCT6引腳定義 


輸入輸出形式:GPIO的八種工作模式
| 模式名稱 | 性質 | 特征 |
|---|---|---|
| IN_FLOATING浮空輸入 | 數字輸入 | 可讀取引腳電平,若引腳懸空,則電平不確定 |
| IPU上拉輸入 | 數字輸入 | 可讀取引腳電平,內部連接上拉電阻,懸空時默認高電平 |
| IPD下拉輸入 | 數字輸入 | 可讀取引腳電平,內部連接下拉電阻,懸空時默認低電平 |
| AIN模擬輸入 | 模擬輸入 | GPIO無效,引腳直接接入內部ADC |
| Out_OD開漏輸出 | 數字輸出 | 可輸出引腳電平,高電平為高阻態,低電平接VSS |
| Out_PP推挽輸出 | 數字輸出 | 可輸出引腳電平,高電平接VDD,低電平接VSS |
| AF_OD復用開漏輸出 | 數字輸出 | 由片上外設控制,高電平為高阻態,低電平接VSS |
| AF_PP復用推挽輸出 | 數字輸出 | 由片上外設控制,高電平接VDD,低電平接VSS |
推挽輸出Out_PP與開漏輸出Out_OD:
推挽輸出高低電平均有驅動能力.
開漏輸出高電平相當于高阻態,沒有驅動能力,低電平有驅動能力.
(一般輸出用推挽就行,特殊采用開漏)
VCC,VDD,VSS:
電壓的作用對象不同:VCC的供電電壓作用于電路。VDD的工作電壓作用于芯片。VSS的電壓作用于器件內部。
來源不同:VDD來源于漏極電源電壓,用于 MOS 晶體管電路, 一般指正電源。 Vss來源于極電源電壓,在 CMOS 電路中指負電源, 在單電源時指零伏或接地。
GPIO以及AFIO各個函數:
void GPIO_DeInit(GPIO_TypeDef* GPIOx);//對GPIOX進行復位
void GPIO_AFIODeInit(void);//AFIO外設復位,清除其配置
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);//IO引腳的初始化函數
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
//GPIO的四個讀取函數
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//讀取輸入數據寄存器某一個端口的輸入值
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);//讀取整個輸入數據寄存器的,返回值16位,每一位代表一個端口值
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//讀取輸出數據寄存器的某一個位(一般用于輸出模式,看一下自己輸出了什么)
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);//讀取整個輸出寄存器
//GPIO的四個讀取函數
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//把指定的端口設置為高電平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//把指定的端口設置為低電平
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);//前兩個參數指定端口,根據第三個參數的值設定指定端口,Bit_RESET清除端口引腳,置低電平,Bit_SET設置端口引腳,置高電平
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//“portval”可以同時對16個端口進行寫入操作
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//鎖定GPIO某個引腳的(用的不多)
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);//配置AFIO的事件輸出功能(用的不多)
void GPIO_EventOutputCmd(FunctionalState NewState);//配置AFIO的事件輸出功能(用的不多)
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);//可以用來引腳重映射(重映射的方式,新的狀態)
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);//配置AFIO的數據選擇器,選擇我們想要的中斷引腳
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);//與以太網有關,暫時用不到
stm新建工程啟動文件后綴意義
(stm32F103RCT6的容量是256kb)
| 縮寫 | 釋義 | Flash****容量 | 型號 |
|---|---|---|---|
| LD_VL | 小容量產品超值系列 | 16~32K | STM32F100 |
| MD_VL | 中容量產品超值系列 | 64~128K | STM32F100 |
| HD_VL | 大容量產品超值系列 | 256~512K | STM32F100 |
| LD | 小容量產品 | 16~32K | STM32F101/102/103 |
| MD | 中容量產品 | 64~128K | STM32F101/102/103 |
| HD | 大容量產品 | 256~512K | STM32F101/102/103 |
| XL | 加大容量產品 | 大于512K | STM32F101/102/103 |
| CL | 互聯型產品 | - | STM32F105/107 |
LED,蜂鳴器,按鍵電路圖示

STM32低電平點亮LED燈。低電平強驅動

STM32高電平點亮LED燈。高電平弱驅動

PNP三極管驅動電路(左邊是基極,帶箭頭的是發射極,剩下的是集電極)(左邊基極給低電平,三極管就會導通,通過3.3v和GND,就可以給蜂鳴器提供驅動電流,反之基極給高電平,截止,沒有電流)(PNP三極管最好接上邊,這是因為三極管的通斷是需要在發射極和基極直接產生一定的開啟電壓 )

NPN三極管驅動電路(左邊是基極,帶箭頭的是發射極,剩下的是集電極)(基極給高電平導通,低電平斷開)(NPN三極管最好接下邊, 這是因為三極管的通斷是需要在發射極和基極直接產生一定的開啟電壓 )

按鍵圖示(PA0內部必須是上拉輸入模式,則此時按鍵松開引腳懸空輸入的是高電平,按下按鍵連接GND以致PA是低電平)

按鍵圖示(已經存在上拉電阻,PA0可以是上拉輸入(內外上拉電阻共同作用)也可以是浮空輸入)按下為低電平,松開為高電平

(拓展,不常用)按鍵圖示(下拉輸入,不可浮空輸入)按下是高電平,松開是低電平

(拓展,不常用)按鍵圖示(下拉輸入或者浮空輸入)按下是高電平,松開是低電平

傳感器模塊電路圖示:DO數字輸出,AO模擬輸出
OLED文件提供:
引腳配置與引腳初始化需要按照實際板子樣式配置,其余不需要更改。
OLED顯示屏總共4行16列(1行一列開頭,無0行0列)
| 函數 | 作用 |
|---|---|
| OLED_Init(); | 初始化 |
| OLED_Clear(); | 清屏 |
| OLED_ShowChar(1, 1, 'A'); | 顯示一個字符 |
| OLED_ShowString(1, 3, "HelloWorld!"); | 顯示字符串 |
| OLED_ShowNum(2, 1, 12345, 5); | 顯示十進制數字 |
| OLED_ShowSignedNum(2, 7, -66, 2); | 顯示有符號十進制數字 |
| OLED_ShowHexNum(3, 1, 0xAA55, 4); | 顯示十六進制數字 |
| OLED_ShowBinNum(4, 1, 0xAA55, 16); | 顯示二進制數字 |
(上表第四個數字為長度)(C語言不能直接寫二進制的數)
C語言知識拓展
C語言數字類型
(“int”數據在51單片機中占16位,在STM32中占32位)(32想表示16位的數據得用“short”)(stdint.h頭文件與ST庫函數對這些變量的重命名)
| 關鍵字 | 位數 | 表示范圍 | stdint****關鍵字 | ST****關鍵字 |
|---|---|---|---|---|
| char | 8 | -128 ~ 127 | int8_t | s8 |
| unsigned char | 8 | 0 ~ 255 | uint8_t | u8 |
| short | 16 | -32768 ~ 32767 | int16_t | s16 |
| unsigned short | 16 | 0 ~ 65535 | uint16_t | u16 |
| int | 32 | -2147483648 ~ 2147483647 | int32_t | s32 |
| unsigned int | 32 | 0 ~ 4294967295 | uint32_t | u32 |
| long | 32 | -2147483648 ~ 2147483647 | ||
| unsigned long | 32 | 0 ~ 4294967295 | ||
| long long | 64 | -(2^64)/2 ~ (2^64)/2-1 | int64_t | |
| unsigned long long | 64 | 0 ~ (2^64)-1 | uint64_t | |
| float | 32 | -3.4e38 ~ 3.4e38 | ||
| double | 64 | -1.7e308 ~ 1.7e308 |
extern (外部變量,跨越工程下的不同文件)
typedef,define,結構體,枚舉
結構體eg:
StructName.z = 1.23;//結構體變量名.結構體成員名
pStructName->z = 1.23;//結構體指針名->結構體成員名
枚舉:關鍵字:enum
用途:定義一個取值受限制的整型變量,用于限制變量取值范圍;宏定義的集合
定義枚舉變量:enum{FALSE = 0, TRUE = 1} EnumName;
因為枚舉變量類型較長,所以通常用typedef更改變量類型名
引用枚舉成員:
EnumName = FALSE;
EnumName = TRUE;
enum{MONDAY=0,TUSDAY,WEDNESDAY}week;//后面的值可不賦,系統自動按順序賦值0,1,2,3,4~~~
STM32中斷
68個可屏蔽中斷通道,包含EXTI(外部中斷)、TIM(定時中斷)、ADC(模數轉換器)、USART(串口)、SPI(通信)、I2C(通信)、RTC(實時時鐘)等多個外設
使用NVIC統一管理中斷,每個中斷通道都擁有16個可編程的優先等級,可對優先級進行分組,進一步設置搶占優先級和響應優先級(NVIC是用來管理中斷,分配16個優先級的)
中斷函數模塊示例
#include "stm32f10x.h" // Device header
uint16_t CountSensor_Count;//全局變量默認是0(用于記錄中斷的次數)
void CountSensor_Init(void)//配置外部中斷
{
//第一步:把涉及的外設時鐘都打開->配置RCC(不打開時鐘,外設是沒法工作的)
//第二步:配置GPIO,選擇我們的端口為輸入模式
//第三步:配置AFIO,選擇我們用的這一路的GPIO,連接到后面的EXTI
//第四步:配置EXTI,選擇邊緣觸發方式(上升沿,下降沿,雙邊沿),選擇觸發響應方式(中斷響應,事件響應)
//第五步:配置NVIC,給這個中斷選擇一個合適的優先級->通過NVIC,外部中斷信號就能進入CPU了,CPU才能跳轉到中斷函數執行中斷程序
//第一步:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//開啟外部時鐘GPIOB(GPIOB是APB2的外設)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//開啟AFIO時鐘(AFIO也是APB2的外設)
//EXTI與NVIC的外設是一直打開著的,不需要我們再開啟(NVIC是內核外設,不需要開啟時鐘,和CPU一起住皇宮里的)
//第二步:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉輸入,默認為高電平的輸入方式(對于外部中斷來說,要選擇浮空輸入,上拉輸入或者下拉輸入。)
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;//PB14號口
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//速度
GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化GPIOB外設
//第三步
//AFIO外設沒有專門的庫函數文件,他的庫函數是與GPIO一起的
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//連接PB14號口的第14個中斷線路
//AFIO外部的中斷引腳選擇配置完成,執行完上面這個函數后,AFIO的第14個數據選擇器就撥好了,其中輸入端被撥到了GPIOB,對應的就是PB14號引腳,輸出端固定連接的是EXTI的第14個中斷線路
//如此,PB14號引腳的電平信號就可以順利通過AFIO,進入到后級EXTI電路了
//第四步:將EXTI的第14號線路配置為中斷模式,下降沿觸發,開啟中斷
EXTI_InitTypeDef EXTI_InitStructure;//定義結構體
EXTI_InitStructure.EXTI_Line=EXTI_Line14 ;//指定我們要配置的中斷線->PB14所在的第十四號線路
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//指定選擇的中斷線的新狀態(開啟中斷或關閉中斷)
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//指定外部中斷線的模式(中斷模式,事件模式)
EXTI_InitStructure.EXTI_Trigger= EXTI_Trigger_Falling ;//指定觸發信號的有效邊沿(上升沿,下降沿,雙邊沿)
EXTI_Init(&EXTI_InitStructure);//外部中斷初始化
//如此,PB14的電平信號就能通過EXTI進入下一級NVIC了
//第五步
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2);//中斷分組方式->搶占2,響應2
NVIC_InitTypeDef NVIC_InitStructure;//定義結構體
NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;//指定中斷通道來開啟或者關閉
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//中斷通道是使能還是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//指定所選通道的搶占優先級
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//指定所選通道的響應優先級
NVIC_Init(&NVIC_InitStructure);
//外部中斷的信號從GPIO到AFIO再到EXTI再到NVIC最后通向CPU,讓CPU從主程序跳轉到中斷程序執行
//STM32中,中斷函數的名字都是固定的,每個中斷通道對應一個中斷函數(去啟動文件找中斷函數的名字)
}
void EXTI15_10_IRQHandler(void)//中斷函數的格式(啟動文件中找中斷函數的固定名字)(中斷函數都是無參無返回值的)(中斷函數無需在.h文件中聲明)
{
if(EXTI_GetITStatus(EXTI_Line14)==SET)//先進行一個中斷標志位的判斷(因為這個函數EXTI10到EXTI15都能進來,需要判斷是不是EXTI14進來)
{
CountSensor_Count++;//用于記錄中斷的次數
EXTI_ClearITPendingBit(EXTI_Line14);//中斷函數結束后,調用一下清除中斷標志位的函數(只要中斷標志位置1,就會跳轉到中斷函數)
}
}
uint16_t CountSensor_Count_Get(void)//返回記錄中斷的變量值CountSensor_Count
{
return CountSensor_Count;
}
NVIC基本結構
(NVIC是一個內核外設,是CPU的小助手)

NVIC優先級分組
NVIC的中斷優先級由優先級寄存器的4位(0~15)決定,這4位可以進行切分,分為高n位的搶占優先級和低4-n位的響應優先級
搶占優先級高的可以中斷嵌套,響應優先級高的可以優先排隊,搶占優先級和響應優先級均相同的按中斷號排隊
| 分組方式 | 搶占優先級 | 響應優先級 |
|---|---|---|
| 分組0 | 0位,取值為0 | 4位,取值為0~15 |
| 分組1 | 1位,取值為0~1 | 3位,取值為0~7 |
| 分組2 | 2位,取值為0~3 | 2位,取值為0~3 |
| 分組3 | 3位,取值為0~7 | 1位,取值為0~1 |
| 分組4 | 4位,取值為0~15 | 0位,取值為0 |
NVIC函數
(在中斷配置之前,先指定一下中斷的分組,再初始化NVIC)(分組的方式整個芯片只能用一種,分組的代碼整個工程只需要執行一次)(如果把它放在模塊里進行分組,要保證每個模塊分組都選的是同一個,或者把這個代碼放在主函數的最開始,這樣模塊里就不用再進行分組了)
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);//用于中斷分組(參數是中斷分組的方式)
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);//根據結構體里面指定的參數初始化NVIC
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);//設置中斷向量表(用的不多)
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);//系統低功耗配置(用的不多)
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
中斷詳情
灰色部分:內核的中斷(比較高深,一般用不到)
白色部分:STM32外設的中斷

WWDG(窗口定時器中斷:窗口看門狗)
EXTI外部中斷
EXTI(Extern Interrupt)外部中斷
EXTI可以監測指定GPIO口的電平信號,當其指定的GPIO口產生電平變化時,EXTI將立即向NVIC發出中斷申請,經過NVIC裁決后即可中斷CPU主程序,使CPU執行EXTI對應的中斷程序
支持的觸發方式:上升沿/下降沿/雙邊沿/軟件觸發(引腳啥事沒有,程序里一句代碼就能執行中斷)
支持的GPIO口:所有GPIO口,但相同的Pin不能同時觸發中斷(eg.PA0與PB0不能同時用)(后面四個其實是來外部中斷蹭網的,因為外部中斷能從低功耗模式的停止模式下喚醒STM32)
通道數:16個GPIO_Pin,外加PVD輸出、RTC鬧鐘、USB喚醒、以太網喚醒(總共20個中斷線路)
觸發響應方式:中斷響應(申請中斷,讓CPU執行中斷)/事件響應(當外部中斷檢測到引腳電平變化時,選擇觸發事件,外部中斷的信號就不會通向CPU了而是通向·觸發其他外設(屬于外設之間的聯合操作))

EXTI框圖

EXTI函數
void EXTI_DeInit(void);//把EXTI的配置都清除,恢復成上電默認的狀態
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);//初始化EXTI,根據這個結構體里的參數配置EXTI外設
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);//可以把參數傳遞的結構體變量賦一個默認值
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);//用來軟件觸發外部中斷(參數給一個指定的中斷線,就能軟件觸發一次這個外部中斷)(如果只需要外部引腳觸發中斷,就不需要這個函數了)
//在主程序里查看和清除標志位(對狀態寄存器的讀寫)(一般的讀寫標志位,能不能觸發中斷的標志位都能讀取)
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);//可以獲取指定的標志位是否被置1了
void EXTI_ClearFlag(uint32_t EXTI_Line);//可以對置1的標志位進行清除
//在中斷函數里查看和清除標志位(對狀態寄存器的讀寫)(只能讀寫與中斷有關的標志位,并且對中斷是否允許做出了判斷)
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);//可以獲取指定的中斷標志位是否被置1了
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);//可以對置1的中斷標志位進行清除
AFIO復用IO口
AFIO主要用于引腳復用功能的選擇和重定義
在STM32中,AFIO主要完成兩個任務:復用功能引腳重映射、中斷引腳選擇

旋轉編碼器
用來測量位置、速度或旋轉方向的裝置,當其旋轉軸旋轉時,其輸出端可以輸出與旋轉速度和方向對應的方波信號,讀取方波信號的頻率和相位信息即可得知旋轉軸的速度和方向
類型:機械觸點式/霍爾傳感器式/光柵式
旋轉編碼器視圖及框架

void GPIO_AFIODeInit(void);//復位AFIO外設,清除其配置
STM32->TIM定時器
定時器通道引腳

| 類型 | 編號 | 總線 | 功能 |
|---|---|---|---|
| 高級定時器 | TIM1、TIM8 | APB2 | 擁有通用定時器全部功能,并額外具有重復計數器、死區生成、互補輸出、剎車輸入等功能 |
| 通用定時器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 擁有基本定時器全部功能,并額外具有內外時鐘源選擇、輸入捕獲、輸出比較、編碼器接口、主從觸發模式等功能 |
| 基本定時器 | TIM6、TIM7 | APB1 | 擁有定時中斷、主模式觸發DAC的功能 |
TIM(Timer)定時器

定時器可以對輸入的時鐘進行計數,并在計數值達到設定值時觸發中斷
16位計數器、預分頻器、自動重裝寄存器的時基單元,在72MHz計數時鐘下可以實現最大59.65s的定時
不僅具備基本的定時中斷功能,而且還包含內外時鐘源選擇、輸入捕獲、輸出比較、編碼器接口、主從觸發模式等多種功能
根據復雜度和應用場景分為了高級定時器、通用定時器、基本定時器三種類型
定時器圖示
高級定時器圖示
(增加了重復計數寄存器,原先結構每個計數周期結束都會發生更新,這里可以實現每隔幾個技術周期發生更新(相當于對輸出的更新信號又做了一次分頻))
DTG(Dead Time Generate死區生成電路)(右邊的輸出引腳由一個變成了兩個互補的輸出,輸出一對互補的PWM波(為了驅動三項無刷電機(三項無刷電機的驅動電路需要三個橋臂,每個橋臂需要兩個大功率開關管控制,總共需要六個大功率開關)為了防止互補輸出的PWM驅動橋臂時,在開關切換的瞬間,由于器件的不理想,造成短暫的直通現象,所以前面加上死區生成電路,在開關切換的瞬間,產生一定時長的死區,讓橋臂的上下管全部關斷,防止直通現象))
(增加了剎車輸入功能(為了給電機驅動提供安全保障)如果外部引腳BKIN(break in)產生了剎車信號或者內部時鐘失效產生故障,那么控制電路就會自動切斷電機的輸出,防止意外發生)

通用定時器圖示(計數器向上計數,向下計數,中央對齊計數模式)
外部時鐘模式2:想在ETR外部引腳提供時鐘或者對ETR時鐘進行計數,把定時器當作定時器用(外部時鐘首選)
外部時鐘模式1:將TRGI觸發輸入當作外部時鐘輸入(通過這一路的外部時鐘有1.ETR引腳信號(這一路輸入會占用觸發輸入的通道)2.ITR信號(這一部分時鐘信號來自其他定時器)3.CH1引腳的邊沿(上升沿下降沿都可)4.CH1引腳和CH2引腳)(通過外部時鐘模式1的ITR,TRGO連接方式可以實現定時器的級聯)
(外部ETR引腳和TRGI可以提供時鐘)(TRGI用作觸發輸入,可以觸發定時器的從模式))(在此處將TRGI觸發輸入當作外部時鐘輸入)(主模式的輸出TRGO可以通向其他定時器的ITR引腳)(ITR0~ITR3分別來自其他四個定時器的輸出,如下表TIM2的ITR0是接在TIM1的TRGO上的,TIM2的ITR1是接在TIM8的TRGO上,以此類推)
| 從定時器 | ITR0 (TS = 000) | ITR1 (TS = 001) | ITR2 (TS = 010) | ITR3 (TS = 011) |
|---|---|---|---|---|
| TIM2 | TIM1 | TIM8 | TIM3 | TIM4 |
| TIM3 | TIM1 | TIM2 | TIM5 | TIM4 |
| TIM4 | TIM1 | TIM2 | TIM3 | TIM8 |
| TIM5 | TIM2 | TIM3 | TIM4 | TIM8 |

基本定時器圖示(計數器向上計數)
(產生更新中斷或者更新事件)(更新事件不會觸發中斷,但可以觸發內部其他電路的工作)

定時器定時中斷流程:
(內部時鐘)基準時鐘-->預分頻器-->計數器-->計數器計數自增,同時不斷與自動重裝寄存器進行比較-->它倆值相等時,計時時間到,產生一個更新中斷或者更新事-->CPU響應更新中斷,完成定時中斷的任務
主從觸發模式
(STM32定時器一大特色)它能讓內部的硬件在不受程序的控制下實現自動運行
eg:主模式觸發DAC:當用DAC輸出一段波形時,需要每隔一段時間來觸發DAC,讓它輸出下一個電壓點-->定時器設計了一個主模式,使用這個主模式可以把這個定時器的更新事件映射到這個觸發輸出TRGO(Trigger Out)的位置,然后TRGO直接接到DAC的觸發轉換引腳上。
定時中斷基本結構

(中斷輸出控制就是中斷輸出的允許位)
時序圖
預分頻器時序圖
CK_PSC:預分頻器的輸入時鐘
CNT_EN:計數器使能(高電平計數器正常運行,低電平計數器停止)
CK_CNT:計數器時鐘(預V分頻器的時鐘輸出,計數器的時鐘輸入)(看圖,前半段,計數器未使能,計數器/定時器時鐘不運行,使能后,前半段,預分頻器系數為1,計數器時鐘等于預分頻器時鐘,后半段,預分頻器系數為2,計數器時鐘等于預分頻器時鐘的一半)(預分頻器系數=預分頻值+1)(在計數器時鐘的驅使下,計數器寄存器也跟著時鐘的上升沿而不斷自增)
計數器計數頻率:CK_CNT = CK_PSC / (PSC + 1)

計數器時序圖
CK_INT:內部時鐘72MHz
CNT_EN:時鐘使能,高電平啟動
CK_CNT:計數器時鐘/定時器時鐘:(eg:頻率為CK_CNT/分頻系數=36MHz)
更新中斷標志位UIF:只要置1了,就回去申請中斷,中斷響應后,需要手動清零
計數器溢出頻率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
溢出時間=1/溢出頻率
(ARR:自動重裝載值)

計數器無預裝時序圖
(無緩沖寄存器/影子寄存器)

計數器有預裝時序圖
(有緩沖寄存器/影子寄存器)
(讓值的變化與更新事件同步發生,防止在運行途中更改造成錯誤)

RCC時鐘樹
STM32中用來產生和配置時鐘,并且把配置好的時鐘發送到各個外設的系統(時鐘是所有外設運行的基礎,是最先需要配置的東西(主函數運行前執行的SystemInit函數就是用來配置RCC時鐘樹的))
圖示左側都是時鐘的產生電路,右側都是時鐘的分配電路(SYSCLK->系統時鐘72MHz)(時鐘產生電路:四個震蕩源:內部的8MHz高速RC振蕩器,外部的4-16MHz高速石英晶體振蕩器—>晶振(一般8MHzx),外部的32.768KHz低速晶振->給RTC提供時鐘,內部40KHz低速RC振蕩器->給看門狗提供時鐘)(AHB,APB1,APB2的時鐘來自這兩個高速晶振)

TIM輸出比較
輸出比較:OC

輸出比較八種模式


TIM函數
void TIM_DeInit(TIM_TypeDef* TIMx);//恢復缺省配置
//時基單元
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//時基單元初始化(TIMx,結構體(包含配置時基單元的一些參數))
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//可以把結構體變量賦一個默認值
//運行控制
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//用來使能計數器
//中斷輸出控制
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//使能中斷輸出信號(使能外設的中斷輸出)
//時基單元的時鐘源選擇部分(RCC內部時鐘,ETR外部時鐘,ITRx其他定時器,TIx捕獲通道)
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);//選擇內部時鐘(TIMx)
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//選擇ITRx其他定時器時鐘(TIMx,選擇要接入的哪個其他定時器)
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);//選擇TIX捕獲通道的時鐘(TIMx,選擇TIx具體的某個引腳,輸入的極性,輸入的濾波器)
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//選擇ETR通過外部時鐘模式1輸入的時鐘(TIMx,外部觸發預分頻器,輸入極性,輸入濾波器)
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//選擇ETR通過外部時鐘模式2輸入的時鐘(TIMx,外部觸發預分頻器,輸入極性,輸入濾波器)
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//不是用來選擇時鐘,而是單獨用來配置ETR引腳的預分頻器,極性,濾波器
//
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);//單獨寫預分頻值(要寫入的預分頻值,寫入的模式(聽從安排,在更新事件后生效或者在寫入后手動產生一個更新事件讓值立刻生效))
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);//用來改變計數器的計數模式(TIMx,選擇新的計數器模式)
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);//自動重裝器預裝功能配置(TIMx,使能還是失能->有預裝或者無預裝)
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);//給計數器手動寫入一個值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);//給自動重裝器手動寫入一個值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);//獲取當前計數器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);//獲取當前預分頻器的值
//獲取標志位和清除標志位
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
//以下是輸出比較函數
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);//(選擇定時器,結構體(輸出比較的參數))
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
//(0c:輸出比較)(輸出比較單元有四個與之一一對應)(選擇定時器,結構體(輸出比較的參數))
//(結構體來初始化輸出比較單元)
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);//用來給輸出比較賦一個默認值的
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
//配置強制輸出模式(用于在運行過程中暫停輸出波形并強制輸出高或低電平)(等于設置100%或0%占空比效果類似,所以用的不多)(不掌握)
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
//配置CCR寄存器的預裝功能(即影子寄存器,寫入的值在更新事件后才生效)(不掌握)
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
//配置快速使能(不掌握)
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
//外部事件清除REF信號(不掌握)
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
//單獨設置輸出比較的極性(不掌握)(帶個N的是高級定時器里互補通道的配置)
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
//單獨修改輸出使能參數的
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
//單獨更改輸出比較模式的函數
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
//單獨更改CCR寄存器值的函數(用于在運行時更改占空比)(比較重要)
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
//僅高級定時器使用,在使用高級定時器輸出PWM時,需要調用這個函數,使能主輸出,否則PWM將不能正常輸出
//以上是輸出比較函數
PWM基本結構

把模塊都打通,就可以輸出PWM了
1.RCC開啟時鐘,把要用的TIM外設和GPIO外設的時鐘打開
2.配置時基單元,包括前面的時鐘源選擇
3.配置輸出比較單元,包括CCR的值,輸出比較模式,極性選擇,輸出使能等參數(在庫函數里用結構體來配置)
4。配置GPIO,把PWM對應的GPIO口初始化為復用推挽輸出
5。運行控制,啟動計數器,輸出PWM
串口通信


串口參數
波特率:串口通信的速率
起始位:標志一個數據幀的開始,固定為低電平
數據位:數據幀的有效載荷,1為高電平,0為低電平,低位先行
校驗位:用于數據驗證,根據數據位計算得來
停止位:用于數據幀間隔,固定為高電平
串口中斷
串口內部有兩個寄存器:發送數據寄存器(TDR),發送移位寄存器

STM調試
keil 5調試

RTC實現
RTC代碼實現所需函數
//復位BKP的值為默認值
void BKP_DeInit(void);
//使能外部晶振,查看時鐘樹
void RCC_LSEConfig(uint8_t RCC_LSE);
//向指定的用戶寄存器嗎寫入用戶程序數據
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
//從指定的后備寄存器讀出數據
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
//在備份域控制寄存器中(RCC_BDCR)里的LSERDY指示LSE晶體震蕩是否穩定
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
//RTC時鐘源選擇和時鐘使能
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
void RCC_RTCCLKCmd(FunctionalState NewState);
//等待RTC的寄存器域RTC的APB時鐘同步:等待RTC寄存器(RTC_CNT,RTC_ALR,RTC_PRL)于RTC的APB時鐘同步
void RTC_WaitForSynchro(void);
//等待RTC寄存器完成:等待最近一次對RTC的寄存器寫操作完成
void RTC_WaitForLastTask(void);
//使能RTC秒中斷,產生秒中斷
//RTC_RT值包括:RTC_IT_ON(溢出中斷使能),RTC_IT_ALR(鬧鐘中斷使能),RTC_IT_SEC(秒中斷使能)
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
//進入RTC配置模式
void RTC_EnterConfigMode(void);//允許配置
//推出配置模式
void RTC_ExitConfigMode(void);
//設置RTC預分頻的值:預分頻配置:PRLH/PRLL
void RTC_SetPrescaler(uint32_t PrescalerValue);
void RTC_ClearFlag(uint16_t RTC_FLAG);//清除復位標志
//獲取RTC計數器的值
uint32_t RTC_GetCounter(void);
//設置RTC計數器的值:CNTH/CNTL
void RTC_SetCounter(uint32_t CounterValue);//設置RTC計數器的值
//設置RTC鬧鐘的值:ALRH/ALRL
void RTC_SetAlarm(uint32_t AlarmValue);
//檢查RTC的中斷發生與否
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
//清除RTC的中斷待處理位
void RTC_ClearITPendingBit(uint16_t RTC_IT);
RTC配置流程
使能PWR和BKP時鐘
使能后備寄存器訪問
配置RTC時鐘源,使能RTC時鐘
如果使用LSE,要打開LSE
配置RTC預分頻器系數
設置時間
開啟相關中斷(如果需要)
編寫中斷服務函數
部分操作要等待寫操作和完成和同步
I2C
SCL和SDA應該外掛上拉電阻
軟件I2C
不用看I2C的庫函數,只需要用GPIO的讀取函數
軟件I2C初始化
- 把SCL和SDA都初始化為開漏輸出模式
- 把SCL和SDA都置高電平
//SCL PB10,SDA PB11
//I2C初始化
void MyI2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//開漏輸出 (開漏輸出模式仍然可以輸入,輸入時,先輸出1,再直接讀取輸入數據就行)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_10|GPIO_Pin_11);
}
//調用MyI2C_Init函數PB10,PB11兩個端口就被初始化為開漏輸出模式,然后釋放總線,SCL和SDA處于高電平,此時I2C總線處于空閑狀態
完成I2C的六個時序基本單元
(所有的單元都會保證以SCL低電平結束)
0,封裝
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
1.起始條件

//首先SCL和SDA都確保釋放,然后先拉低SDA,再拉低SCL,就能產生起始條件了
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
2.終止條件

void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
3.發送一個字節

//SCL低電平變換數據,高電平保持數據穩定(高位先行,先放最高位,再放次高位,等等,最后最低位,依次把一個字節的每一位放在SDA線上,每放完一位后,執行釋放SCL,拉低SCL的操作)
//首先趁SCL低電平,先把Byte的最高位放在SDA線上(寫SDA,寫1還是寫0取決于Byte的最高位)
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(Byte&(0x80>>i));//0x80:1000 0000 ,把Byte的第i位放在SDA線上
MyI2C_W_SCL(1);//釋放SCL
MyI2C_W_SCL(0); //拉低SCL
//驅動時鐘走一個脈沖
}
}
4.接收一個字節

//接收一個字節時序開始時,SCL低電平,此時從機需要把數據放在SDA上,為了防止主機干擾從機寫入數據,主機需要先釋放SDA,釋放SDA也相當于切換輸入模式,在SCL低電平時,從機會把數據放到SDA,如果從機想發1,就釋放SDA,如果從機想發0,就拉低SDA,然后主機釋放SCL,在SCL高電平期間讀取SDA,再拉低SCL,低電平期間,從機會把下一位數據放到SDA上,重復八次,主機就能讀到一個字節了(SCL低電平變換數據,高電平讀取數據,是一種讀寫分離的設計,低電平時間定義為寫的時間,高電平期間定義為讀的時間,如果非要在SCL高電平時變換數據,就變成了起始條件或者終止條件)
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte =0x00;
MyI2C_W_SDA(1);
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1)
{Byte |=( MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1)
{Byte |=0x80;}>>i);}
MyI2C_W_SCL(0);
}
return Byte;
}
5.發送應答,接收應答
(發送一個字節就是發8位,發送應答就是發1位)接收應答(接收一個字節就是收八位,接收應答就是收一位)

//發送應答
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);//主機把AckBit放在SDA上
MyI2C_W_SCL(1);//釋放SCL,SCL高電平,從機讀取應答,SCL低電平,進入下一個時序單元
MyI2C_W_SCL(0); //拉低SCL
//驅動時鐘走一個脈沖
}
//接收應答 讀到0,代表從機給了應答,讀到1,代表從機沒給應答
uint8_t MyI2C_ReceiveAck(void)
{//函數進來時,SCL低電平,
uint8_t AckBit;
MyI2C_W_SDA(1);//主機釋放SDA,防止干擾從機,同時,從機把應答位放在SDA上
MyI2C_W_SCL(1);//SCL高電平
AckBit=MyI2C_R_SDA();//主機讀取應答位
MyI2C_W_SCL(0); //SCL低電平,進入下一個時序單元
//I2C的引腳都是開漏輸出+弱上拉的配置,主機輸出1,并不是強制SDA為高電平,而是釋放SDA
//I2C是在進行通信,主機釋放SDA,從機會對SDA操作,即使之前主機把SDA置1,之后再讀取SDA,讀到的值也可能是0
}
軟件I2C讀取MPU6050
MPU讀寫字節封裝
#define MPU6050_ADDRESS 0xD0
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//實現指定地址寫單個字節(多個字節用for循環)
{
MyI2C_Start();//起始時序
MyI2C_SendByte(MPU6050_ADDRESS);//發送一個字節 0xD0:從機地址+讀寫位
MyI2C_ReceiveAck();//接收應答 返回一個值:應答位
//這里有應答位可以判斷從機有沒有接收到數據
//尋址找到從機之后,就可以發送下一個字節了
MyI2C_SendByte(RegAddress);//第二個字節的內容,就是指定寄存器地址,這個字節會存在MPU6050的當前地址指針里面,用于指定具體讀寫哪個寄存器
//發送一個字節后,也得接收一下應答
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);//第三個字節,就是我要寫入指定寄存器地址下的數據了
MyI2C_ReceiveAck(); //接收應答
MyI2C_Stop();//終止這個時序
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)//實現指定地址讀單個字節,參數是指定讀的地址
{
uint8_T Data;
MyI2C_Start();//起始時序
MyI2C_SendByte(MPU6050_ADDRESS);//發送一個字節 0xD0:從機地址+讀寫位
MyI2C_ReceiveAck();//接收應答 返回一個值:應答位
//這里有應答位可以判斷從機有沒有接收到數據
//尋址找到從機之后,就可以發送下一個字節了
MyI2C_SendByte(RegAddress);//第二個字節的內容,就是指定寄存器地址,這個字節會存在MPU6050的當前地址指針里面,用于指定具體讀讀哪個寄存器
//發送一個字節后,也得接收一下應答
MyI2C_ReceiveAck();
//指定地址就是設定了MPU6050的當前地址指針,設定完地址之后,我們要轉入讀的時序,必須重新指定讀寫位,必須重新起始
MyI2C_Start();//起始時序
MyI2C_SendByte(MPU6050_ADDRESS|0x01);//指定從機地址和讀寫位,從機地址仍然是MPU6050的地址,但是是寫地址,讀寫位是1
MyI2C_ReceiveAck(); //接收應答
//總線控制權正式交給從機
//從機開始發送一個字節,主機就是調用->
Data=MyI2C_ReceiveByte()//接收一個字節,這個函數的返回值就是接收到的數據
//主機接收一個字節后,要給從機發送一個應答
MyI2C_SendAck(1);//0給從機應答 1不給從機應答(不想繼續讀了就不給應答,主機收回總線的控制權),這里只讀一個數據
MyI2C_Stop();//終止時序
return Data;//把讀到的數據返回回去,想讀多個字節就用for循環(讀取最后一個字節給非應答)
}
MPU初始化,獲取數據封裝
//讀寄存器
{
OLED_Init();
MPU6050_Init();
//調用讀寫寄存器的地址
uint8_t ID = MPU6050_ReadReg(0x75);//參數是要讀的寄存器地址,返回值是ID號的內容
OLED_ShowHexNum(1,1,ID,2);
}
//寫寄存器,首先需要解除芯片的睡眠模式,否則寫入無效
//睡眠模式是電源管理寄存器1的這一位SLEEP,直接將這個寄存器寫入0x00,這樣就能解除睡眠模式了
{
OLED_Init();
MPU6050_Init();
//調用讀寫寄存器的地址
MPU6050_WriteReg(0x6B,0x00);//0x6B:電源管理寄存器地址,往內寫入0x00,解除睡眠模式
//采樣率分頻寄存器,地址是0x19,值的內容是采樣分頻
MPU6050_WriteReg(0x19,0xAA);//往其中寫入0xAA
uint8_t ID = MPU6050_ReadReg(0x19);//參數是要讀的寄存器地址,返回值是ID號的內容
OLED_ShowHexNum(1,1,ID,2);
}
void MPU6050_Init(void)
{
MyI2C_Init(); //開啟I2C通信
MPU6050_WriteReg(0x6B,0x01);//配置電源管理寄存器1
//設備復位,給0->不復位 睡眠模式,給0->解除睡眠
//循環模式,給0->不需要循環 無關位,給0即可
//溫度傳感器失能,給0->不失能,最后3位選擇時鐘,給000->給內部時鐘,給001->選x軸的陀螺儀時鐘
//總:0000 0001:0x01
MPU6050_WriteReg(0x6C,0x00);//配置電源管理寄存器2
//前兩位:循環模式喚醒頻率,給00->不需要
//后六位:每個軸的待機位,全都給0->不需要待機
//總:0000 0000:0x00
MPU6050_WriteReg(0x19,0x09);//配置采樣率分頻寄存器
//這八位決定了數據輸出的快慢,值越小越快
//總:0x09:10分頻
MPU6050_WriteReg(0x1A,0x06);//"配置寄存器"
//外部同步,Bit5,Bit4,Bit3全部給0->不需要
//數字低通濾波器 Bit2,Bit1,Bit0給個110->最平滑的濾波
//總 :0000 0110 :0x06
MPU6050_WriteReg(0x1B,0x18);//陀螺儀配置寄存器
//Bit7,Bit6,Bit5自測使能 都給0->不自測
//Bit4,Bit3滿量程選擇 給11->選擇最大量程
//Bit2,Bit1,Bit0 無關位給000
//總:0001 1000:0x18
MPU6050_WriteReg(0x1C,0x18); //加速度計配置寄存器
//Bit7,Bit6,Bit5自測使能 都給0->不自測
//Bit4,Bit3滿量程選擇 給11->選擇最大量程
//高通濾波器 Bit2,Bit1,Bit0給個000->用不到
//總:0001 1000:0x18
//總:解除睡眠,選擇陀螺儀時鐘,六個軸均不待機采樣分頻為10,濾波參數給最大陀螺儀和加速度計都選擇最大量程
//配置完之后,陀螺儀內部就在不斷地進行數據轉換了,輸出的數據就存放在就存放在數據寄存器里,想獲取數據的話,只需要再寫一個獲取數據寄存器的函數即可
}
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ)//這個函數需要返回6個int16_t的數據,分別表示XYZ軸的加速度值和陀螺儀值
{
uint8_t Data_H,Data_L;
Data_H=MPU6050_ReadReg(0x3B);//首先讀取加速度寄存器X軸的高8位
Data_L=MPU6050_ReadReg(0x3C);//再讀取加速度寄存器的低8位
*AccX=(Data_H<<8)|Data_L;//高八位左移八位再或上低八位就是加速度計X軸的16位數據
Data_H=MPU6050_ReadReg(0x3D);//首先讀取加速度寄存器Y軸的高8位
Data_L=MPU6050_ReadReg(0x3E);//再讀取加速度寄存器的低8位
*AccY=(Data_H<<8)|Data_L;//高八位左移八位再或上低八位就是加速度計Y軸的16位數據
Data_H=MPU6050_ReadReg(0x3F);//首先讀取加速度寄存器Z軸的高8位
Data_L=MPU6050_ReadReg(0x40);//再讀取加速度寄存器的低8位
*AccZ=(Data_H<<8)|Data_L;//高八位左移八位再或上低八位就是加速度計Z軸的16位數據
Data_H=MPU6050_ReadReg(0x43);//首先讀取陀螺儀寄存器X軸的高8位
Data_L=MPU6050_ReadReg(0x44);//再讀取陀螺儀寄存器的低8位
*AccX=(Data_H<<8)|Data_L;//高八位左移八位再或上低八位就是陀螺儀計X軸的16位數據
Data_H=MPU6050_ReadReg(0x45);//首先讀取陀螺儀寄存器Y軸的高8位
Data_L=MPU6050_ReadReg(0x46);//再讀取陀螺儀寄存器的低8位
*AccY=(Data_H<<8)|Data_L;//高八位左移八位再或上低八位就是陀螺儀Y軸的16位數據
Data_H=MPU6050_ReadReg(0x47);//首先讀取陀螺儀寄存器Z軸的高8位
Data_L=MPU6050_ReadReg(0x48);//再讀取陀螺儀寄存器的低8位
*AccZ=(Data_H<<8)|Data_L;//高八位左移八位再或上低八位就是陀螺儀Z軸的16位數據
}
I2C通信外設
(同步時序)(硬件I2C)

由硬件電路自動反轉引腳電平
軟件只需要寫入控制寄存器CR(踩油門,打方向盤),數據寄存器DR(看儀表盤),就可以實現協議了。
為了實時監控時序的狀態,軟件還得讀取狀態寄存器SR(觀看儀表盤,了解汽車的運行狀態)
掌握一主多從,7位地址的I2C(起始條件之后,緊跟的一個字節必須是7位地址+讀寫位)
主機:擁有主動控制總線的權利。從機:只能在主機允許的情況下,才能控制總線


I2C是高位先行,在發送的時候,最高位先移出去 ,然后是次高位
移位8次,就能發送一個字節
在接收的時候,數據通過GPIO口從右邊依次移進來,移8次,一個字節就接收完成了
配置硬件I2C時,兩個GPIO口都要配置成復用開漏輸出的模式(復用:GPIO口的狀態是交由片上外設來控制的。開漏輸出:是I2C協議要求的端口配置(即使是開漏輸出模式,GPIO口也是可以進行輸入的))
主機發送

主機接收

起始,從機地址+讀,接收應答 接收數據,發送應答 接收數據,發送應答 ,最后一個數據給非應答,之后終止(這個時序是當前地址讀的時序,指定地址讀的復合格式這里沒有給,需要我們自己組合一下)
硬件I2C讀取MPU6050
-
開啟I2C外設和對應GPIO口的時鐘
-
把I2C外設對應的GPIO口初始化為復用開漏模式
-
使用結構體,對整個I2C進行配置
-
I2C_Cmd 使能I2C
硬件I2C函數
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);//生成起始條件,調用下這個函數,就可以生辰起始條件了
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);//調用一下,生成終止條件
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);//配置CR1的Ack這一位,這里Ack就是應答使能(STM32作為主機,在接收一個字節后,是給從機應答,還是非應答,取決于此,Ack是1,就是給從機應答,0就是給從機非應答)
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);//發送數據,就是把Data這個數據直接寫入到DR寄存器(數據寄存器,用于存放接收到的數據或放置用于發送到總線的數據)
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);//讀取DR的數據作為返回值(在接收器模式下,接收到的字節被拷貝到DR寄存器)(讀取DR,接收數據)
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);//發送七位地址的專用函數
硬件I2C初始化
void MPU6050_Init(void)
{
//1開啟I2C外設和對應GPIO口的時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//2
GPIO_InitTypeDef GPIO_InitStruct;//定義GPIO_InitStruct結構體
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_OD;//結構體初始化—>復用開漏輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;//結構體初始化—>
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//結構體初始化->速度
GPIO_Init(GPIOB,&GPIO_InitStruct);//GPIOA初始化
//3初始化I2C外設
I2C_InitTypeDef I2C_InitStructure;
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;//應答位配置,配置Ack位,用于確定在接收一個字節后是否給從機應答,之后要更改再用單獨的函數更改
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//指定STM32作為從機,可以響應幾位的地址(可以響應10位或者7位的地址)
I2C_InitStructure.I2C_ClockSpeed=50000;//50KHz時鐘速度,配置SCL的時鐘頻率,數值越大,SCL頻率越高,數據傳輸就越快。0~100KHz標準速度,100KHz~400KHz:快速狀態
I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//時鐘占空比,只用在時鐘頻率大于100KHz,也就是進入到快速狀態時才有用,在0~100KHz的標準速度下,占空比是固定的1:1(低電平時間:高電平時間=1:1)
//時鐘占空比是低:高,是為了快速模式而設定的(讀取速度大于寫入速度)(所以給低電平的寫入時間多分配點資源)
I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;//模式
I2C_InitStructure.I2C_OwnAddress1=0x00;//自身地址1,STM32作為從機使用,用于指定STM32的自身地址,方便別的主機呼叫它(如果上面的參數選擇了響應7位地址,這里就得給STM32指定一個自身的7位地址)
I2C_Init(I2C2,&I2C_InitStructure);
//4:I2C2使能
I2C_Cmd(I2C2,ENABLE);
}
硬件I2C封裝函數
寫寄存器,指定地址寫一個字節的時序
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
//1.生成起始條件
I2C_GenerateSTART(I2C2,ENABLE);
//硬件I2C是非阻塞式的程序,在函數結束之后,都要等待相應的標志位,來確保這個函數的操作執行到位了
//此時要等待EV5事件的到來,檢查EV5事件
//用到狀態監控函數
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCESS);//監測EV5事件是否發生:主機模式選擇(因為STM32默認位主機,發送起始條件之后變為主機),EV5事件也可以叫做主機模式已選擇事件,,,返回值:SUCCESS表示最后一次事件等于我們指定的事件 ERROR表示指定事件未發生
//為了等待EV5事件發生,所以套while循環
//2.發送從機地址,接收應答
//發送從機地址,就是發送一個字節,直接向DR寄存器寫入一個字節就行
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//第二個參數是從機地址,采用宏定義地址,第三個參數是方向,也就是從機地址的最低為->讀寫位。第三個參數:Transmitter,發送,給地址的最低位清0,Receiver,接收,它就給你的地址最低位置1
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCESS);//“接收模式已選擇”等待EV6事件發生
I2C_SendData(I2C2,RegAddress);//第二個參數是一個字節的數據,宏定義
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCESS);//等待EV8事件"字節正在發送"
I2C_SendData(I2C2,Data);
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCESS);//等待EV8_2事件"事件已經發送完畢",移位完成了并且沒有新的數據可以發的時候置BTF
I2C_GenerateSTOP(I2C2,ENABLE);//生成終止條件
}
讀寄存器的函數,指定地址讀寄存器
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
//1.生成起始條件
I2C_GenerateSTART(I2C2,ENABLE);
//硬件I2C是非阻塞式的程序,在函數結束之后,都要等待相應的標志位,來確保這個函數的操作執行到位了
//此時要等待EV5事件的到來,檢查EV5事件
//用到狀態監控函數
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCESS);//監測EV5事件是否發生:主機模式選擇(因為STM32默認位主機,發送起始條件之后變為主機),EV5事件也可以叫做主機模式已選擇事件,,,返回值:SUCCESS表示最后一次事件等于我們指定的事件 ERROR表示指定事件未發生
//為了等待EV5事件發生,所以套while循環
//2.發送從機地址,接收應答
//發送從機地址,就是發送一個字節,直接向DR寄存器寫入一個字節就行
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//第二個參數是從機地址,采用宏定義地址,第三個參數是方向,也就是從機地址的最低為->讀寫位。第三個參數:Transmitter,發送,給地址的最低位清0,Receiver,接收,它就給你的地址最低位置1
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCESS);//“接收模式已選擇”等待EV6事件發生
I2C_SendData(I2C2,RegAddress);//第二個參數是一個字節的數據,宏定義
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCESS);//等待EV8事件"字節正在發送"
}
狀態機


舉例:

freertos

實時操作系統(允許多任務同時運行):freertos,ucos,rt-thread

Freerots編程風格

(int從不使用)(short是16位,long是32位)

任務創建
獨立的無法返回的函數是函數

任務棧
棧是單片機在ram中一段連續的空間
為每個任務分獨立的棧的內存空間

任務函數

任務控制塊

列表和列表項(C語言當中的鏈表):用于跟蹤任務

1和5用于檢查完整性
2用于記錄列表項數量
3用于記錄列表當前索引項
4記錄最后一個列表項

3->下一個成員
4->前一個成員
6->指向就緒列表
迷你列表項(有些情況不需要列表項這么全的功能)

1.值
2.指向下一個
3.指向上一個
列表初始化

列表項初始化

列表項插入函數

函數的具體實現過程

任務創建函數

任務就緒列表


調度器:實現任務切換

任務切換

總:任務創建->啟動->就緒列表->任務調度->任務切換->
任務創建實踐過程

1.初始化硬件
2.創建開始任務(創建任務所需堆棧大小,任務控制塊,任務函數)
3.啟動任務調度器
main此時就完成了(函數要繼續執行下去就從開始任務(開始任務有指定的任務函數)(在開始任務函數這里創建其他的任務子函數)這里)(創建完之后再刪除開始任務)
上位機和下位機
上位機:
上位機指可以直接發送操作指令的計算機或單片機,一般提供用戶操作交互界面并向用戶展示反饋數據。
典型設備類型:電腦,手機,平板,面板,觸摸屏
下位機:
下位機指直接與機器相連接的計算機或單片機,一般用于接收和反饋上位機的指令,并且根據指令控制機器執行動作以及從機器傳感器讀取數據。
典型設備類型:PLC,STM32,51,FPGA,ARM等各類可編程芯片
上位機軟件:
用于完成上位機操作交互的軟件被定義為“上位機軟件”;
- 上位機給下位機發送控制命令,下位機收到此命令并執行相應的動作。
- 上位機給下位機發送狀態獲取命令,下位機收到此命令后調用傳感器測量,然后轉化為數字信息反饋給上位機。
- 下位機主動發送狀態信息或報警信息給上位機。
為了實現以上過程,上位機和下位機都需要單獨編程,都需要專門的開發人員在各自兩個平臺編寫代碼。
上位機與下位機關系示意圖:


浙公網安備 33010602011771號