一、实验要求
1、学习和理解STM32F103系列芯片的地址映射和寄存器映射原理;了解GPIO端口的初始化设置三步骤(时钟配置、输入输出模式设置、最大速率设置)。
2、以 STM32最小系统核心板(STM32F103C8T6)+面板板+3只红绿蓝LED 搭建电路,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯,轮流闪烁,间隔时长1秒。 1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数; 2)分别用汇编语言,C语言编程实现。
二、实验过程及结果
(一)任务1
1、地址映射和寄存器映射原理
1)地址映射 地址映射,也被称为存储器映射。存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程称为存储器映射,如果再分配一个地址就叫重映射。 ARM 将 4GB 的存储器空间,平均分成 8 块区域,每块区域的大小是 512MB,STM32 芯片只用其中一部分。ARM 对 4GB 容量分块是按照其功能划分,每块都有特殊的用途。 在 8 个 Block 里面,Block0、Block1 和 Block2包含了 STM32 芯片的内部 Flash、RAM 和片上外设。
存储器 Block0 内部区域功能划分
Block0 主要用于设计片内的 FLASH,STM32F103 系列芯片内部 FLASH 最大是 512KB。在芯片内部集成更大的 FLASH 或者 SRAM ,意味着芯片成本的增加,所以片内集成的FLASH 都不会太大。 存储器Block1内部区域功能划分
Block1用于设计片内的SRAM。 存储器Block2内部区域功能划分
Block2 用于设计片内外设,根据外设总线速度的不同,Block2 被划分为 AHB 和 APB 两部分,APB 又被分成 APB1 和 APB2 总线。 Block3/4/5中包含了FSMC扩展区域,可用于扩展外部存储器,比如 SRAM,NORFLASH 和 NANDFLASH 等。
2)寄存器映射 寄存器映射是在存储器映射的基础上进行的。 在存储器片上外设区域,以四个字节为一个单元,每个单元对应不同的功能。当我们控制这些单元时就可驱动外设工作,找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元。但若每次都是通过这种方式访问地址,不好记忆且易出错。因此,我们根据每个单元功能的不同,以功能为名给内存单元取一个别名,其实质就是寄存器名字。给已分配好地址(通过存储器映射实现)的有特定功能的内存单元取别名的过程就是寄存器映射。
2、GPIO端口的初始化设置
1)时钟 STM32的时钟是由内部或外部振荡器产生的“频率”,被称为“系统时钟”。最大为72MHz,换成周期T约为13.9ns。
2)GPIO工作模式
GPIO_Mode_AIN 模拟输入(应用ADC模拟输入,或者低功耗下省电)
GPIO_Mode_IN_FLOATING 浮空输入(浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
GPIO_Mode_IPD 下拉输入(IO内部下拉电阻输入)
GPIO_Mode_IPU 上拉输入(IO内部上拉电阻输入)
GPIO_Mode_Out_OD 开漏输出(开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
GPIO_Mode_Out_PP 推挽输出(推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
GPIO_Mode_AF_OD 复用开漏输出(片内外设功能(I2C的SCL,SDA))
GPIO_Mode_AF_PP 复用推挽输出(片内外设功能TX1,MOSI,MISO.SCK.SS)
3)GPIO初始化步骤 第一步:使能GPIOx口的时钟。 第二步:指明GPIOx口的哪一位,这一位的速度大小以及模式。 第三步:调用GPIOx口初始化函数,进行初始化。 第四步:调用GPIO-SetBits函数,进行相应为的置位。
对于单个GPIO口的初始化如下 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); //输出高
对于多个GPIO口的初始化如下 GPIO_InitTypeDef GPIO_InitStructure; 第一步:使能GPIOA,GPIOE的时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); 第二步:设置GPIOA,GPIOE参数:输出OR输入,工作模式,端口翻转速率 第三步:调用GPIOA口初始化函数,进行初始化。 第四步:调用GPIO-SetBits函数,进行相应为的置位。 ?把第二、三、四步合并分别设置GPIOA和GPIOE 先设置GPIOA GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 第四个口,PA4 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz GPIO_Init(GPIOA,&GPIO-InitST); //根据设定参数初始化GPIOA GPIO_SetBits(GPIOA,GPIO_Pin_4); //输出高 再设置GPIOE GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 第三个口,PE3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置为推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz GPIO_Init(GPIOE,&GPIO-InitST); //根据设定参数初始化GPIOE GPIO_SetBits(GPIOE,GPIO_Pin_3); //输出高
(二)任务2
1、STM32F103C8T6芯片
核心板原理图:
2、寄存器详情
查看寄存器组起始地址表,发现:RCC地址范围、寄存器地址范围。
寄存器地址:端口起始地址+偏移地址
1)时钟
2)GPIOx端口 端口配置寄存器有两个,分别为端口配置低寄存器(CRL)和端口配置高寄存器(CRH),每四位配置一个端口。
端口输出寄存器(ORD)在相应的位赋值可以控制输出电压,0为低电压,1为高电压。
3、流水灯原理
将 GPIO 的引脚设置成推挽输出模式并且默认下拉,输出低电平, LED灯点亮。
4、C语言编程
1)源码
LED.c程序:
#include "stm32f10x.h"
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
void Delay(volatile unsigned int);
void Delay(volatile unsigned int t)
{
unsigned int i;
while(t--)
for(i=0;i<800;i++);
}
int main(void)
{
RCC_APB2ENR|=1<<2|1<<3|1<<4;
GPIOA_CRL&=0x0FFFFFFF;
GPIOA_CRL|=0x20000000;
GPIOA_ODR|=(1<<7);
GPIOB_CRH&=0xFFFFFF0F;
GPIOB_CRH|=0x00000020;
GPIOB_ODR|=(1<<9);
GPIOC_CRH&=0x0FFFFFFF;
GPIOC_CRH|=0x20000000;
GPIOC_ODR|=(1<<15);
while(1)
{
GPIOA_ODR&=~(1<<7);
Delay(1000000);
GPIOA_ODR|=1<<7;
GPIOB_ODR&=~(1<<9);
Delay(1000000);
GPIOB_ODR|=1<<9;
GPIOC_ODR&=~(1<<15);
Delay(1000000);
GPIOC_ODR|=1<<15;
}
}
2)步骤
① 项目创建 点击 Project ——> Open Project,打开之前实验“STM32汇编语言编程与仿真调试”创建的工程。
配置环境如下:
① 工程的目标环境设置为STM32F103C8;
② CMSIS下选择CORE,Device下选择Startup;
③ Output 中勾选 Create HEX File 生成 hex 文件;
④ Debug中选择“Use Simulator”,设置Dialog DLL项为“DARMSTM.DLL”,parameter项为“-pSTM32F103C8”。
右击 Source Group 1 ,点击 Add New Item to Group;点击 C Flie(.c)创建C语言文件LED.c。
② 编译调试 编译程序:
设置示波器:
观察波形图:
③ 硬件操作 使用USB-TTL直接进行串口下载,将USB-TTL的GND和3.3V接入STM32最新系统板的GND和3.3V,然后TXD和RXD分别接入A10和A9引脚。 接入后按照程序中GPIOx的引脚接上LED灯,将LED灯+极接入3.3V的电压,且最小核心板要利用跳线帽实现boot0置1,boot1置0。
安装 USB 转串口驱动—CH340 版本。
打开 mcuisp 软件,开始烧录。
配置如下:
搜索串口,设置波特率 115200(尽量不要设置太高)
选择要下载的HEX文件
校验、编程后执行DTR低电平复位,RTS高电平进入bootloader
开始编程,如果出现一直连接的情况,按一下开发板的复位键RESET
5、汇编语言编程
LED.s程序:
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
硬件操作过程同C语言编程时相同。
6、实际效果
三、实验总结
通过完成本次STM32F103寄存器方式点亮LED流水灯实验,我了解了STM32F103系列芯片的地址映射和寄存器映射原理,更加深入地学习了GPIOx端口寄存器和GPIO端口初始化设置的知识,进一步知晓了stm32系列开发板和面包板等硬件的连接方式。在本次实验操作的过程中,我深刻认识到仿真操作和硬件操作存在着很大的区别,使用硬件操作不仅能学到更多与硬件相关的基础知识,同时还能锻炼我的动手操作能力。总而言之,硬件操作与仿真操作相结合的实验方式让我受益匪浅。
四、参考资料
1、零死角玩转STM32—F103指南者.pdf 2、STM32F10xxx参考手册.pdf 3、3 . 存储器映射 和 寄存器映射 4、STM32入门-GPIO初始化步骤 5、STM32寄存器点亮流水灯的三种方法 6、STM32F103寄存器方式点亮LED流水灯
|