一、關於STM32
STM32F103C8T6是一款由意法半導體公司(ST)推出的基於Cortex-M3內核的32位微控制器,硬件采用LQFP48封裝,屬於ST公司微控制器中的STM32系列。
主要有三種類型的MCU:主流級別MCU、高性能MCU、低功耗MCU。再詳細一點,我們可以具體到STM32的命名規則,比如STM32F103C8T6中的“F”,代表的就是通用型,另外還有,比如S代表的是簡單型、L代表的是低功耗、H代表高性能、AL是汽車應用低功耗型、AF是汽車應用通用型。
二、STM32F103系列芯片的地址映射和寄存器映射原理
2.1 GPIO(general porpose intput output)
GPIO(general porpose intput output):通用輸入輸出端口的簡稱。可以通過軟件控制其輸出和輸入。stm32芯片的GPIO引腳與外部設備連接起來,從而實現與外部通信,控制以及數據采集的功能。
2.2 寄存器映射與寄存器空間
- Cortex‐M3 支持4GB 存儲空間。整塊4G存儲器開始地址標為0x0000_0000,結束地址為0xFFFF_FFFF,地址的位數是32位,那么2^32=4,294,967,296。
- 由於一個基本的存儲單元是8bits即1Byte(每個地址對應一個存儲單元,這樣如果只是訪問某一bit就要使用位操作,或者使用位帶操作),因此4,294,967,296/1024=4,194,304KB,4,194,304/1024=4096MB,4094/1024=4GB。
這4GB的存儲空間被划分成8個塊,每一塊用來與特定功能完成映射。映射關系如圖所示。每個寄存器都是32bit,占用4個Byte即4個存儲單元。可以把寄存器看作一個特殊的單元,一個這樣的單元占32bit,只要找到這個單元的起始地址就可以對其進行操作。
其映射地址 = 外設總基地址(塊基地址)+ 總線相對於外設總基地址的偏移 + 具體外設基地址相對於總線基地址的偏移 + 寄存器相對於具體外設基地址的偏移。
2.3 寄存器訪問
以GPIOE_ODR寄存器為例:查芯片手冊知:ODR寄存器地址相對於GPIOE起始地址的偏移為:0Ch
/*GPIOE_ODR = GPIOE_BASE+0x0C GPIOE_BASE = APB2PERIPH_BASE + 0x1800 APB2PERIPH_BASE = PERIPH_BASE + 0x10000 PERIPH_BASE = 0x40000000 所以: GPIOE_ODR = 0x4001180C(寄存器的起始地址)*/ /*****直接地址操作,改變寄存器的值****/ *(unsigned int *)(0x4001180C)&= 0x00; //初始化port5賦值為1,要拉低電平使用&操作 delay_ms(300); *(unsigned int *)(0x4001180C)|= 0x20; delay_ms(300);
三、初始化設置
3.1 打開GPIO口時鍾
將GPIOA GPIOB GPIOC三個時鍾打開 #define RCC_APB2ENR (*(unsigned int *)0x40021018) // 打開時鍾 RCC_APB2ENR |= (1<<2); // 打開 GPIOA 時鍾 RCC_APB2ENR |= (1<<3); // 打開 GPIOB 時鍾 RCC_APB2ENR |= (1<<4); // 打開 GPIOC 時鍾
3.2 采用推挽輸出模式
端口1-7為低,端口8-15為高。每個引腳由四個位控制。
以GPIOB和0號引腳(B0)為例,將其設置為推挽輸出,並設置最大速度為10MHz,則將控制B0的四個位設置為0001
#define GPIOB_CRL (*(unsigned int *)0x40010c00) // 最后四位變為0001 GPIOB_CRL |= (1<<0); // 最后一位變1 GPIOB_CRL &= ~(0xE<<0); // 倒數2、3、4位變0
#define GPIOB_CRL (*(unsigned int *)0x40010C00) #define GPIOC_CRH (*(unsigned int *)0x40011004) #define GPIOA_CRL (*(unsigned int *)0x40010800) // 配置 GPIO 口為推免輸出 // GPIOB----最后四位為0001 GPIOB_CRL |= (1<<0); // 最后一位變1 GPIOB_CRL &= ~(0xE<<0); // 倒數2、3、4位變0 // GPIOC----前四位為0001 GPIOC_CRH |= (1<<28); // 第四位變1 GPIOC_CRH &= ~(0xE0000000); // 前三位變0 // GPIOA----最后四位為0001 GPIOA_CRL |= (1<<0); // 最后一位變1 GPIOA_CRL &= ~(0xE<<0); // 倒數2、3、4位變0
3.3 設置低電平
1為高電平 0為低電平
以GPIOB和0號引腳(B0)為例,將其設置為低電平
#define GPIOB_ODR (*(unsigned int *)0x40010C0C) GPIOB_ODR &= ~(1<<0); // 最后一位變0
#define GPIOB_ODR (*(unsigned int *)0x40010C0C) #define GPIOC_ODR (*(unsigned int *)0x4001100C) #define GPIOA_ODR (*(unsigned int *)0x4001080C) GPIOB_ODR &= ~(1<<0); //最后一位變為0 GPIOC_ODR &= ~(1<<15); //倒數16位變為0 GPIOA_ODR &= ~(1<<0); //最后一位變為
四、一些細節以及結果展示(First)
以下是一些我搭建電路過程中遇到的一些問題,希望能對大家有幫助
4.1需要准備的器件
- USB轉TTL轉接口一個
- 面包板一個
- 杜邦線若干
- STM32單片機一個
4.2如何燒錄
這是32的單片機芯片引腳,我們可以看到A9口和A10口分別是TX和RX,我們需要將USB轉TTL上的TX接到單片機上的A10,RX接到單片機上的A9。方便我們使用MCUISP軟件燒錄HEX文件。
這是我們剛剛拿到芯片時的樣子,我們可以從圖中看到黃色部分,上面是BOOT0,下面是BOOT1,我們需要將BOOT0置1,BOOT1置0,才能燒錄進程序。
將將BOOT0置1,BOOT1置0后,我們按上述接上A9和A10,然后接3.3v和GND,安裝完CH340驅動,我們就可以燒錄程序。
否則,燒錄不成功,MCUISP程序會自增到401然后結束顯示無法燒錄。
關於Project的建立以及燒錄過程
在source group里創建led.c,並寫入代碼,注意項目結構,使用的引腳是PA7,PB9,PC15,同時如果燈不閃爍,程序沒有正常運行,可以先試試仿真調試,仿真調試正常了一般在板子上運行就正常了
燒錄的過程選擇你要的HEX文件,然后保證上述過程正確,單片機已經可以被識別,然后點擊開始編程
就會有如上圖的提示,如果開始編程后跳到401自行中斷,請檢查是否沒有置正確的BOOT0/1,A9/A10是否正確連接,如果都正確,可以按下單片機黃色下方的RESET鍵即可成功燒錄!
下面展示一下流水燈的結果
五、代碼結果
流水燈代碼C語言實現
//--------------APB2???????------------------------ #define RCC_AP2ENR *((unsigned volatile int*)0x40021018) //----------------GPIOA????? ------------------------ #define GPIOA_CRL *((unsigned volatile int*)0x40010800) #define GPIOA_ORD *((unsigned volatile int*)0x4001080C) //----------------GPIOB????? ------------------------ #define GPIOB_CRH *((unsigned volatile int*)0x40010C04) #define GPIOB_ORD *((unsigned volatile int*)0x40010C0C) //----------------GPIOC????? ------------------------ #define GPIOC_CRH *((unsigned volatile int*)0x40011004) #define GPIOC_ORD *((unsigned volatile int*)0x4001100C) //-------------------???????----------------------- void Delay_ms( volatile unsigned int t) { unsigned int i; while(t--) for (i=0;i<800;i++); } void A_LED_LIGHT(){ GPIOA_ORD=0x0<<7; GPIOB_ORD=0x1<<9; GPIOC_ORD=0x1<<15; } void B_LED_LIGHT(){ GPIOA_ORD=0x1<<7; GPIOB_ORD=0x0<<9; GPIOC_ORD=0x1<<15; } void C_LED_LIGHT(){ GPIOA_ORD=0x1<<7; GPIOB_ORD=0x1<<9; GPIOC_ORD=0x0<<15; } int main() { int j=100; RCC_AP2ENR|=1<<2; RCC_AP2ENR|=1<<3; RCC_AP2ENR|=1<<4; //????????? RCC_APB2ENR|=1<<3|1<<4; GPIOA_CRL&=0x0FFFFFFF; GPIOA_CRL|=0x20000000; GPIOA_ORD|=1<<7; GPIOB_CRH&=0xFFFFFF0F; GPIOB_CRH|=0x00000020; GPIOB_ORD|=1<<9; GPIOC_CRH&=0x0FFFFFFF; GPIOC_CRH|=0x30000000; GPIOC_ORD|=0x1<<15; while(j) { A_LED_LIGHT(); Delay_ms(10000000); B_LED_LIGHT(); Delay_ms(10000000); C_LED_LIGHT(); Delay_ms(10000000); } }
流水燈代碼匯編實現
RCC_APB2ENR EQU 0x40021018;配置RCC寄存器,時鍾,0x40021018為時鍾地址 GPIOB_BASE EQU 0x40010C00 GPIOC_BASE EQU 0x40011000 GPIOA_BASE EQU 0x40010800 GPIOB_CRL EQU 0x40010C00 GPIOC_CRH EQU 0x40011004 GPIOA_CRL EQU 0x40010800 GPIOB_ODR EQU 0x40010C0C GPIOC_ODR EQU 0x4001100C GPIOA_ODR EQU 0x4001080C Stack_Size EQU 0x00000400;棧的大小 AREA STACK, NOINIT, READWRITE, ALIGN=3 ;NOINIT: = NO Init,不初始化。READWRITE : 可讀,可寫。ALIGN =3 : 2^3 對齊,即8字節對齊。 Stack_Mem SPACE Stack_Size __initial_sp AREA RESET, DATA, READONLY __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY THUMB REQUIRE8 PRESERVE8 ENTRY Reset_Handler bl LED_Init;bl:帶鏈接的跳轉指令。當使用該指令跳轉時,當前地址(PC)會自動送入LR寄存器 MainLoop BL LED_ON_C BL Delay BL LED_OFF_C BL Delay BL LED_ON_A BL Delay BL LED_OFF_A BL Delay BL LED_ON_B BL Delay BL LED_OFF_B BL Delay B MainLoop;B:無條件跳轉。 LED_Init;LED初始化 PUSH {R0,R1, LR};R0,R1,LR中的值放入堆棧 ;控制時鍾 LDR R0,=RCC_APB2ENR;LDR是把地址裝載到寄存器中(比如R0)。 ORR R0,R0,#0x1c LDR R1,=RCC_APB2ENR STR R0,[R1] ;初始化GPIOA_CRL LDR R0,=GPIOA_CRL BIC R0,R0,#0x0fffffff;BIC 先把立即數取反,再按位與 LDR R1,=GPIOA_CRL STR R0,[R1] LDR R0,=GPIOA_CRL ORR R0,#0x00000001 LDR R1,=GPIOA_CRL STR R0,[R1] ;將PA0置1 MOV R0,#0x01 LDR R1,=GPIOA_ORD STR R0,[R1] ;初始化GPIOB_CRL LDR R0,=GPIOB_CRL BIC R0,R0,#0x0fffffff;BIC 先把立即數取反,再按位與 LDR R1,=GPIOB_CRL STR R0,[R1] LDR R0,=GPIOB_CRL ORR R0,#0x00000001 LDR R1,=GPIOB_CRL STR R0,[R1] ;將PB0置1 MOV R0,#0x01 LDR R1,=GPIOA_ORD STR R0,[R1] ;初始化GPIOC LDR R0,=GPIOC_CRH BIC R0,R0,#0x0fffffff LDR R1,=GPIOC_CRH STR R0,[R1] LDR R0,=GPIOC_CRH ORR R0,#0x01000000 LDR R1,=GPIOC_CRH STR R0,[R1] ;將PC15置1 MOV R0,#0x8000 LDR R1,=GPIOC_ORD STR R0,[R1] POP {R0,R1,PC};將棧中之前存的R0,R1,LR的值返還給R0,R1,PC LED_ON_A PUSH {R0,R1, LR} MOV R0,#0x00 LDR R1,=GPIOA_ORD STR R0,[R1] POP {R0,R1,PC} LED_OFF_A PUSH {R0,R1, LR} MOV R0,#0x01 LDR R1,=GPIOA_ORD STR R0,[R1] POP {R0,R1,PC} LED_ON_B;亮燈 PUSH {R0,R1, LR} MOV R0,#0x00 LDR R1,=GPIOB_ORD STR R0,[R1] POP {R0,R1,PC} LED_OFF_B;熄燈 PUSH {R0,R1, LR} MOV R0,#0x01 LDR R1,=GPIOB_ORD STR R0,[R1] POP {R0,R1,PC} LED_ON_C;亮燈 PUSH {R0,R1, LR} MOV R0,#0x00 LDR R1,=GPIOC_ORD STR R0,[R1] POP {R0,R1,PC} LED_OFF_C;熄燈 PUSH {R0,R1, LR} MOV R0,#0x0100 LDR R1,=GPIOC_ORD STR R0,[R1] POP {R0,R1,PC} Delay PUSH {R0,R1, LR} MOVS R0,#0 MOVS R1,#0 MOVS R2,#0 DelayLoop0 ADDS R0,R0,#1 CMP R0,#330 BCC DelayLoop0 MOVS R0,#0 ADDS R1,R1,#1 CMP R1,#330 BCC DelayLoop0 MOVS R0,#0 MOVS R1,#0 ADDS R2,R2,#1 CMP R2,#15 BCC DelayLoop0 POP {R0,R1,PC} NOP END
六、參考鏈接
https://blog.csdn.net/NiceBabyaaa/article/details/120834837?spm=1001.2014.3001.5501
https://blog.csdn.net/junseven164/article/details/120804940
https://blog.csdn.net/geek_monkey/article/details/86291377
https://blog.csdn.net/geek_monkey/article/details/86293880
七、心得體會
之前從未接觸過STM32的我經過這次的學習過程,粗略了解了STM32的使用過程和理解了STM32的部分工作原理,希望今后的學習過程,能夠理解掌握STM32的使用知識,身為新手的我一開始對單片機的這些使用細節並不了解,在老師和同學的幫助下成功完成並且實現了流水燈的效果。希望今后自己的水平能夠提高到不僅僅只是模仿代碼以及電路的地步,而是提高到能夠獨立完成任務,甚至設計實現一些自己想要的東西。這次的學習是重新撿起單片機使用的一個過程,讓我了解到了利用硬件實現軟件端的困難和艱辛,同時克服困難的快樂也是無與倫比的,希望今后能夠更認真的學習單片機和嵌入式開發。