一、STM32简介
- 单片微型计算机简称单片机(MCU(MicrbControl Unit)),我们自己的个人计算机中,CPU、RAM、ROM、I/O这些都是单独的芯片,然后这些芯片被安装在一个主板上,这样就构成了我们的PC主板,进而组装成电脑,而单片机只是将这所有的集中在了一个芯片上而已。单片机又有8位的如51单片机、16位的如MSP430、32位的如STM32,通常我们说的多少位通常指的是内核(CPU)一次处理的数据宽度。也就是说内核一次处理的位数越多单片机的计算速度就越快,性能也就越强悍。
STM32是意法半导体(ST)推出一款32位的单片机。STM32具有超低的价格、超多的外设、丰富的型号、优异的实时性、极低的开发成本等优势。STM32凭借其产品线的多样化、极高的性价比、简单易用的库开发方式,迅速在众多32位单片机中脱颖而出。 STM32芯片内部可以粗略划分两部分:内核+片上外设。如果与电脑类比,内核与片上外设就如同电脑的CPU与主板、内存、显卡、硬盘的关系。 ARM公司只设计内核不生产芯片,他会将有关内核的技术授权给各半导体厂商例如ST、TI、Atme1、NXP等厂商。这些厂商都是基于这个内核自己设计片上外设如SRAM、ROM、FLASH、USART、GPIO等,然后集成到一个硅片上,这就是我们现在用的芯片。 芯片内部架构见图: - 在开始之前需要下载keil,并创建一个新的项目,具体方法可以参考这篇博客。
二、地址映射和寄存器映射
什么是寄存器?
现代的计算机主要包括三级存储,寄存器、内存储器和外存储器,存储数据的速率也依次递减。
我们不妨将寄存器和内存储器都抽象成一个大的数组,其中的每个元素都有一个字节(8位)大小,CPU寻址的时候就是以该元素为最小单位完成的。如前一个元素的地址是0x1FFFFFF0的话,那么下一个元素的地址就是0x1FFFFFF1。我们可以理解为硬件构成上寄存器和内存储器也都是由一个8位大小的元器件线性排列组成的,地址对应着上面讲到的数组中元素的地址。
寄存器是 CPU 内部的构造,它主要用于信息的存储
为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址的过程。
给有特定功能的内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元 取别名的过程就叫寄存器映射。
三、GPIO端口的初始化设置
GPIO简介
GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚。 如型号为 STM32F103VET6 型号的芯片有 GPIOA、GPIOB、GPIOC至 GPIOE共 5组 GPIO,芯片一共 100个引脚,其中 GPIO就占了一大部分,所有的 GPIO 引脚都有基本的输入输出功能。 最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现开关控制,如把 GPIO引脚接入到 LED灯,那就可以控制 LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。 最基本的输入功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低 区分按键是否被按下。
- GPIO端口的初始化设置三步骤
(1)时钟配置 (2)输入输出模式设置 (3)最大速率设置) - 实例
GPIO_InitTypeDef GPIO_InitStructure;
第一步:使能GPIOA的时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
第二步:设置GPIOA参数:输出OR输入,工作模式,端口翻转速率
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_6| GPIO_Pin_7| GPIO_Pin_8; //设定要操作的管脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
第三步:调用GPIOA口初始化函数,进行初始化。
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA
第四步:调用GPIO-SetBits函数,进行相应为的置位。
GPIO_SetBits(GPIOA,GPIO_Pin_0); //输出高
四、代码实现
实现原理
# include "stm32f10x.h"
void delay(unsigned int i);
int main(void)
{
*(unsigned int*) 0x40021018 |= (1<<3);
*(unsigned int*) 0x40010C00 |= (1<<0);
*(unsigned int*) 0x40010C00 |= (1<<4);
*(unsigned int*) 0x40010C00 |= (1<<20);
while(1)
{
*(unsigned int*) 0x40010C0C &= ~(1<<0);
*(unsigned int*) 0x40010C0C |= (1<<1);
*(unsigned int*) 0x40010C0C |= (1<<5);
delay(2000);
*(unsigned int*) 0x40010C0C &= ~(1<<1);
*(unsigned int*) 0x40010C0C |= (1<<0);
*(unsigned int*) 0x40010C0C |= (1<<5);
delay(2000);
*(unsigned int*) 0x40010C0C &= ~(1<<5);
*(unsigned int*) 0x40010C0C |= (1<<0);
*(unsigned int*) 0x40010C0C |= (1<<1);
delay(2000);
*(unsigned int*) 0x40010C0C &= ~(1<<0);
*(unsigned int*) 0x40010C0C &= ~(1<<1);
*(unsigned int*) 0x40010C0C &= ~(1<<5);
delay(2000);
}
}
void SystemInit(void)
{
}
void delay(unsigned int i)
{
unsigned char j;
for(i;i>0;i--)
for(j = 255; j>0; j--);
}
-
烧录 -
汇编代码实现
RCC_APB2ENR EQU 0x40021018;配置RCC寄存器,时钟,0x40021018为时钟地址
GPIOC_CRH EQU 0x40011004;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04
GPIOC_ORD EQU 0x4001100c;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOA_CRL EQU 0x40010800;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04
GPIOA_ORD EQU 0x4001080C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOB_CRH EQU 0x40010C04;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04
GPIOB_ORD EQU 0x40010C0C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
Stack_Size EQU 0x00000400;栈的大小
;分配一个stack段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。
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;ORR 按位或操作,11100将R0的第二位置1,其他位不变
LDR R1,=RCC_APB2ENR
STR R0,[R1];STR是把值存储到寄存器所指的地址中,将r0里存储的值给rcc寄存器
;上面一部分汇编代码是控制时钟的
;初始化GPIOA部分
LDR R0,=GPIOA_CRL
BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与
LDR R1,=GPIOA_CRL
STR R0,[R1]
;上面的代码是初始化CPIOC_CRH
LDR R0,=GPIOA_CRL
ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOA_CRL
STR R0,[R1]
;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
;将PC15置1
MOV R0,#0x80; 二进制为0b1000 0000 ,第7位就是a7引脚的输出电压
LDR R1,=GPIOA_ORD ;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
;初始化GPIOB部分
LDR R0,=GPIOB_CRH
BIC R0,R0,#0xffffff0f;BIC 先把立即数取反,再按位与,用的是b9,所以把第二位置零
LDR R1,=GPIOB_CRH
STR R0,[R1]
;上面的代码是初始化CPIOC_CRH
LDR R0,=GPIOB_CRH
ORR R0,#0x00000020;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOB_CRH
STR R0,[R1]
;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
;将PC15置1
MOV R0,#0x200; 二进制为0b10 0000 0000,第16位就是b9引脚的输出电压
LDR R1,=GPIOB_ORD ;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
;初始化GPIOC部分
LDR R0,=GPIOC_CRH
BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与,就是将三十二位全部置零
LDR R1,=GPIOC_CRH
STR R0,[R1]
;上面的代码是初始化CPIOC_CRH
LDR R0,=GPIOC_CRH
ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOC_CRH
STR R0,[R1]
;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
;将PC15置1
MOV R0,#0x8000; 二进制为0b1000 0000 0000 0000,第16位就是c15引脚的输出电压
LDR R1,=GPIOC_ORD ;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
POP {R0,R1,PC};将栈中之前存的R0,R1,LR的值返还给R0,R1,PC
LED_ON_A;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x00 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
STR R0,[R1];将r0的值赋予在GPIOC_ORD中
POP {R0,R1,PC}
LED_OFF_A;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x80 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
POP {R0,R1,PC}
LED_ON_B;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
STR R0,[R1];将r0的值赋予在GPIOC_ORD中
POP {R0,R1,PC}
LED_OFF_B;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x200 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
POP {R0,R1,PC}
LED_ON_C;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x0000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
STR R0,[R1];将r0的值赋予在GPIOC_ORD中
POP {R0,R1,PC}
LED_OFF_C;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x8000 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
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
总结
- 本次实验熟悉了寄存器映射,GPIO端口设置的原理,但在面包板连线上让存在问题,led流水灯存在一些问题,会继续改进和完善。
参考文章
【嵌入式07】寄存器映射原理详解,GPIO端口的初始化设置步骤 【嵌入式08】STM32F103C8T6寄存器方式借助面包板点亮LED流水灯详解
|