一、STM32F103系列芯片的地址映射和寄存器映射原理
1、寄存器
寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
其实,寄存器就是存放东西的东西。存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址。
指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。
2、寄存器映射
在存储器的区域单元中,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
3、地址映射
为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这一过程称为地址映射。
二、GPIO端口的初始化设置
1、什么是GPIO
GPIO(general porpose intput output):通用输入输出端口的简称。可以通过软件控制其输出和输入。stm32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通信,控制以及数据采集的功能。用户可以通过GPIO口和硬件进行数据交互(如UART),控制硬件工作(如LED、蜂鸣器等),读取硬件的工作状态信号(如中断信号)等。
STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚,如型号为 STM32F103VET6 型号芯片有 GPIOA、GPIOB、GPIOC至 GPIOE共 5组 GPIO,芯片一共 100个引脚,其中 GPIO就占了一大部分,所有的 GPIO 引脚都有基本的输入输出功能。
最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现开关控制,如把 GPIO引脚接入到 LED灯,那就可以控制 LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。
最基本的输入功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。
2、GPIO端口初始化步骤
使能GPIOx口的时钟。
指明GPIOx口的哪一位,这一位的速度大小以及模式。
调用GPIOx初始化函数进行初始化。
调用GPIO-SetBits函数,进行相应位的置位。
3、GPIO的工作模式
GPIO的八种工作模式:输入输出是相对于CPU,四种输入、四种输出模式以及四种输出最大速度。
输入:外部数据输入到开发板
输出:开发板的数据输出到外部设备
1、GPIO_Mode_AIN模拟输入(将IO口作为模拟输入接口。输入的可能是变化的值,接受外部的模拟信息输入)
2、GPIO_Mode_IN_FLOATING 浮空输入(复位上电的时候,引脚不确定电平的高低)
3、GPIO_Mode_IPD 下拉输入(将IO口作为通用输入接口,只能输入0或1,强制下拉,一般是为了输入强低电平)
4、GPIO_Mode_IPU 上拉输入(将IO口作为通用输入接口,只能输入0或1,强制上位,一般是为了输入强高电平)
5、GPIO_Mode_Out_OD 开漏输出(带上拉或者下拉)(要得到高电平状态需要上拉电阻才行,可以作为电流型驱动)
6、GPIO_Mode_AF_OD 开漏复用输出(带上拉或者下拉) 复用功能,不只是单纯的作为输入输出,可以作为其他功能的引脚:串口、12C、SPI、要得到高电平状态需要上拉电阻才行
7、GPIO_Mode_Out_PP 推挽输出(带上拉或者下拉)IO输出0接GND,IO输出1接VCC,读输入值是未知的,输出0就一定是0.输出1就一定是1
8、GPIO_Mode_AF_PP 推挽复用输出(带上拉或者下拉)复用功能,不只是单纯的作为输入输出,可以作为其他功能的引脚:串口、12C、SPI。输出0就一定是0,输出1就一定是1
GPIO四种最大输出速度:2MHZ、25MHZ、50MHZ、100MHZ
三、点亮LED流水灯
1、打开GPIO口的时钟
时钟控制名字叫做RCC,属于AHB总线。GPIOB属于APB2。
时钟的地址
GPIO的地址:GPIO是属于APB2的
外设时钟使能寄存器,设偏移量为0x18,起始地址0x4002 1000,该寄存器地址为0x4002 1018
打开三个IO口的时钟需要将三个位都置1:
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
// 打开时钟
RCC_APB2ENR |= (1<<3); // 打开 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 打开 GPIOC 时钟
RCC_APB2ENR |= (1<<2); // 打开 GPIOA 时钟
2、初始化GPIO口
GPIO的CRL、CRH、CNF和MODE的关系:
STM32的CRL控制着每个IO端口的位占用CRL的4个位,高两位为CNF、低两位为MODE,CRH的作用和CRL完全一样,只是CRL控制的书是低8位输出口,而CRH控制的是高8位输出口。
① 端口配置低寄存器(GPIOx_CRL) (x=A…E)
结合下图来说明,低位寄存器是用来配置GPIO0到GPIO7的,A~E共5组GPIO,相对应的可以知道高位寄存器则用来配置A ~ E组寄存器的GPIO8到GPIO15;可以发现,每一个GPIO引脚由4个比特位控制,其中第2位是MODE,用来配置该GPIO是输入还是输出模式,如果是输出,对应引脚输出的最大速度;其中的高2位是CNF,用来配置引脚输入/输出时的具体功能模式,不同应用需求对应不同的模式设置。
② 端口配置高寄存器(GPIOx_CRH) (x=A…E)
同上面的低位配置寄存器一样,只不过高位配置寄存器是用来配置GPIO8~GPIO15的;要知道,一般配置一个GPIO的输入输出模式时,先配置MODE,确定引脚是用来输入还是输出,其次再配置CNF来设置具体的工作模式
这里使用推挽输出,并设置最大速度为10MHz,则将控制A0的四个位设置为0001:
#define GPIOA_CRL (*(unsigned int *)0x40010800)
// 最后四位变为0001
GPIOA_CRL |= (1<<0); // 最后一位变1
GPIOA_CRL &= ~(0xE<<0); // 倒数2、3、4位变0
补充
1.总线基地址
2、外设基地址
3、外设寄存器地址
对于GPIOB的B9、GPIOC的C15、GPIOA的A4,设置如下:
#define GPIOB_CRH (*(unsigned int *)0x40010C04)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
GPIOB_CRH&= 0xffffff0f;
GPIOB_CRH|=0x00000020;
GPIOC_CRH &= 0x0fffffff;
GPIOC_CRH|=0x30000000;
GPIOA_CRL &= 0xfff0ffff;
GPIOA_CRL|=0x00010000;
配置代码的解析:
STM32的一组GPIO有16个IO口,PIOA这一组,有GPIOA0~GPIOA15一共16个IO口。那么一组GPIO就需要16x4=64位的寄存器来存放这一组GPIO的工作模式的配置,但STM32的寄存器都是32位的,所以只能使用2个32位的寄存器来存放了。CRL用来存放低八位的IO口(GPIOx0—GPIOx7)的配置,CRH用来存放高八位的IO口(GPIOx8—GPIOx15)的配置。
GPIOB->CRL&=0XFFFFFFF0; //GPIOB0
GPIOB->CRL&=0XFFFFFF0F; //GPIOB1
GPIOB->CRL&=0XFFFFF0FF; //GPIOB2
GPIOB->CRL&=0XFFFF0FFF; //GPIOB3
GPIOB->CRL&=0XFFF0FFFF; //GPIOB4
GPIOB->CRL&=0XFF0FFFFF; //GPIOB5
GPIOB->CRL&=0XF0FFFFFF; //GPIOB6
GPIOB->CRL&=0X0FFFFFFF; //GPIOB7
GPIOB->CRH&=0XFFFFFF0F;GPIOB->CRH|=8<<4;
(1):GPIOB->CRH
的意思是GPIOB配置寄存器的高八位CRH,这个寄存器有32位,划分成了八部分,每部分有4位
第一部分是配置GPIOB8的,第二部分是配置GPIOB9,以此类推。
(2):GPIOB->CRH&=0XFFFFFF0F
; 我们知道&是按位与操作,那么这个语句的意思就很清楚了,用0XFFFFFF0F(化成二进制是32位)和CRH进行与操作,这一句代码的结果是CRH的4-7位变成0,其他位的数据不变,这个是&语句的特性。
(3):GPIOB->CRH|=8<<4
; 表示将0x08左移4位。如果我们将8换成0x00000008就很明白了:
GPIOB->CRH|=0x00000008<<4
那么我们将0x00000008左移4位也就是:0x00000008<<4变成0x00000080
3、设置高低电平
输出高电平则为1,低电平则为0
以GPIOB和0号引脚(B0)为例,将其设置为低电平:
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
GPIOB_ODR &= ~(1<<0); // 最后一位变0
对于GPIOB的B9、GPIOC的C15、GPIOA的A4,设置如下:
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
GPIOB_ODR &= ~(1<<9);
GPIOC_ODR &= ~(1<<15);
GPIOA_ODR &= ~(1<<4);
四、C语言
1、新建项目
芯片选择STM32F103C8
出现如下此图删掉,不做选择
添加main.c文件
点击左侧project里的Target1->右击Source Group 1->Add New Item to Group “Source Group 1”
创建hex文件
添加驱动文件
将下图中的文件复制到新建的项目下
右击文件夹,选择Add Existing Files to Group Source Group 1(或双击文件夹),选择All FIles,选择刚刚添加的启动文件,Add,Add之后Close
如果找不到记得修改下面的文件类型为all。
在main.c中写入以下内容使用的引脚是PA7,PB9,PC15
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define GPIOA_BASE 0x40010800
#define RCC_APB2ENR (*(unsigned int *)0x40021018)
#define GPIOB_CRH (*(unsigned int *)0x40010C04)
#define GPIOC_CRH (*(unsigned int *)0x40011004)
#define GPIOA_CRL (*(unsigned int *)0x40010800)
#define GPIOB_ODR (*(unsigned int *)0x40010C0C)
#define GPIOC_ODR (*(unsigned int *)0x4001100C)
#define GPIOA_ODR (*(unsigned int *)0x4001080C)
void SystemInit(void);
void Delay_ms(volatile unsigned int);
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
void A_LED_LIGHT(){
GPIOA_ODR=0x0<<4; //PA4低电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x1<<15; //PC15高电平
}
void B_LED_LIGHT(){
GPIOA_ODR=0x1<<4; //PA4高电平
GPIOB_ODR=0x0<<9; //PB9低电平
GPIOC_ODR=0x1<<15; //PC15高电平
}
void C_LED_LIGHT(){
GPIOA_ODR=0x1<<4; //PA4高电平
GPIOB_ODR=0x1<<9; //PB9高电平
GPIOC_ODR=0x0<<15; //PC15低电平
}
int main(){
int j=100;
// 开启时钟
RCC_APB2ENR |= (1<<3); // 开启 GPIOB 时钟
RCC_APB2ENR |= (1<<4); // 开启 GPIOC 时钟
RCC_APB2ENR |= (1<<2); // 开启 GPIOA 时钟
// 设置 GPIO 为推挽输出
GPIOB_CRH&= 0xffffff0f; //设置位 清零
GPIOB_CRH|=0x00000020; //PB9推挽输出
GPIOC_CRH &= 0x0fffffff; //设置位 清零
GPIOC_CRH|=0x30000000; //PC15推挽输出
GPIOA_CRL &= 0xfff0ffff; //设置位 清零
GPIOA_CRL|=0x00010000; //PA4推挽输出
// 3个LED初始化为不亮(即高点位)
GPIOB_ODR |= (1<<9);
GPIOC_ODR |= (1<<15);
GPIOA_ODR |= (1<<4);
while(j){
B_LED_LIGHT();
Delay_ms(1000000);
C_LED_LIGHT();
Delay_ms(1000000);
A_LED_LIGHT();
Delay_ms(1000000);
}
}
void SystemInit(){
}
点击build生成hex文件
2、电路连接
将串口USB转TTL线与stm32核心板连接如图所示
USB转TTL模块与芯片连接:
GND——GND 3V3——3V3 TXD——A10 RXD——A9
红灯——B9 绿灯——C15 黄灯——A4
3、程序编译
点击build
将板连接到电脑上,打开mcuisp,上传HEX文件
(安装mcuisp参考https://blog.csdn.net/xzzszka/article/details/123768965)
点击搜索串口->选择联机时下载的程序文件(HEX文件)->下方选择DTR的低电平复位,RTS高电平进BootLoader
(未擦除成功可先清除芯片)
注意:在烧录的时候一定要在USB转TTL设备插入电脑前,将boot0设置为1.
在烧录完成之后拔出USB转TLL设备,将boot0又换成0,再插入,之后会正常运行程序。
4、效果实现
五、汇编语言
1、新建项目
新建一个项目LED1,同上述过程相同,把main.c换成led.s文件其余与上述步骤相同,同样要生成HEX文件。
2、led.s
RCC_APB2ENR EQU 0x40021018;配置RCC寄存器,时钟,0x40021018为时钟地址
GPIOB_BASE EQU 0x40010C00
GPIOC_BASE EQU 0x40011000
GPIOA_BASE EQU 0x40010800
GPIOB_CRH EQU 0x40010C04
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_B
BL Delay
BL LED_OFF_B
BL Delay
BL LED_ON_C
BL Delay
BL LED_OFF_C
BL Delay
BL LED_ON_A
BL Delay
BL LED_OFF_A
BL Delay
B MainLoop;B:无条件跳转。
LED_Init;LED初始化
PUSH {R0,R1, LR};R0,R1,LR中的值放入堆栈
;控制时钟
LDR R0,=0x40021018;LDR是把地址装载到寄存器中(比如R0)。
ORR R0,R0,#0x1c
LDR R1,=0x40021018
STR R0,[R1]
;初始化GPIOA_CRL
LDR R0,=0x40010800
BIC R0,R0,#0xfff0ffff;BIC 先把立即数取反,再按位与
LDR R1,=0x40010800
STR R0,[R1]
LDR R0,=0x40010800
ORR R0,#0x00010000
LDR R1,=0x40010800
STR R0,[R1]
;将PA4置1
MOV R0,#0x10
LDR R1,=0x4001080C
STR R0,[R1]
;初始化GPIOB_CRL
LDR R0,=0x40010C04
BIC R0,R0,#0xffffff0f;BIC 先把立即数取反,再按位与
LDR R1,=0x40010C04
STR R0,[R1]
LDR R0,=0x40010C04
ORR R0,#0x00000020
LDR R1,=0x40010C04
STR R0,[R1]
;将PB9置1
MOV R0,#0x200
LDR R1,=0x40010C0C
STR R0,[R1]
;初始化GPIOC
LDR R0,=0x40011004
BIC R0,R0,#0x0fffffff
LDR R1,=0x40011004
STR R0,[R1]
LDR R0,=0x40011004
ORR R0,#0x30000000
LDR R1,=0x40011004
STR R0,[R1]
;将PC15置1
MOV R0,#0x8000
LDR R1,=0x4001100C
STR R0,[R1]
POP {R0,R1,PC};将栈中之前存的R0,R1,LR的值返还给R0,R1,PC
LED_ON_B;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x000
LDR R1,=0x40010C0C
STR R0,[R1]
POP {R0,R1,PC}
LED_OFF_B;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x200
LDR R1,=0x40010C0C
STR R0,[R1]
POP {R0,R1,PC}
LED_ON_C;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x0000
LDR R1,=0x4001100C
STR R0,[R1]
POP {R0,R1,PC}
LED_OFF_C;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x8000
LDR R1,=0x4001100C
STR R0,[R1]
POP {R0,R1,PC}
LED_ON_A
PUSH {R0,R1, LR}
MOV R0,#0x00
LDR R1,=0x4001080C
STR R0,[R1]
POP {R0,R1,PC}
LED_OFF_A
PUSH {R0,R1, LR}
MOV R0,#0x10
LDR R1,=0x4001080C
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
3、编译
可能会出现下述错误
解决方法:
右击startup_stm32f10x_md.s->点击第一个魔术棒,取消Include in Target Build和Always build前面的√
再重新编译点击rebuild。
电路连接不变,连接到电脑,打开mcuisp,上传HEX文件到stm32f103c8t6上。
4、效果实现
六、 参考资料
STM32F103寄存器配置相关学习
https://blog.csdn.net/Fancy_white/article/details/120935744