一、关于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的使用知识,身为新手的我一开始对单片机的这些使用细节并不了解,在老师和同学的帮助下成功完成并且实现了流水灯的效果。希望今后自己的水平能够提高到不仅仅只是模仿代码以及电路的地步,而是提高到能够独立完成任务,甚至设计实现一些自己想要的东西。这次的学习是重新捡起单片机使用的一个过程,让我了解到了利用硬件实现软件端的困难和艰辛,同时克服困难的快乐也是无与伦比的,希望今后能够更认真的学习单片机和嵌入式开发。