一、 题目要求
1、学习和理解STM32F103系列芯片的地址映射和寄存器映射原理;了解GPIO端口的初始化设置三步骤(时钟配置、输入输出模式设置、最大速率设置)。 2、以 STM32最小系统核心板(STM32F103C8T6)+面包板+3只红绿蓝LED 搭建电路,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯,轮流闪烁,间隔时长1秒。 1)写出程序设计思路,包括GPIOx端口的各寄存器地址和详细参数; 2)分别用汇编语言,C语言编程实现。
二、学习和理解STM32F103系列芯片的地址映射和寄存器映射原理;了解GPIO端口的初始化设置三步骤
关于此处一些拓展说明具体可见文末参考文献第一个
1、什么是寄存器
根据百度百科介绍,寄存器是中央处理器内的组成部分。 寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
这是比较专业化的解释,理解起来比较难,简单来说,寄存器就是存放东西的一个空间器物。寄存器可能存放的是指令、数据或地址。 存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址,就像老张行李寄存处在101号店铺,老王行李寄存处在258号店铺。 指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。
2、地址映射和寄存器映射原理
(1)地址映射:由百度词条可知为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这一过程称为地址映射。 (2)寄存器映射:在存储器的区域单元中,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射
3、GPIO端口的初始化设置三步骤(时钟配置、输入输出模式设置、最大速率设置)
(1)GPIO初始化步骤: 第一步:使能GPIOx口的时钟 第二步:指明GPIOx口的哪一位,这一位的速度大小以及模式 第三步:调用GPIOx初始化函数进行初始化 第四步:调用GPIO-SetBits函数,进行相应位的置位 (2)实例 对单个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); //输出高
三、以 STM32最小系统核心板STM32F103C8T6+面板板+3只红绿蓝LED 搭建电路,使用GPIOB、GPIOC、GPIOD这3个端口控制LED灯,轮流闪烁,间隔时长1秒。
1、工程文件的建立
新建工程LLight文件,工程名为Light,选择STM32F103C8 之后弹出的添加库文件窗口Manage Run-Time Environment,在这个界面,选择Cancel即可。 接下来我们需要启动文件,可以到这个网址去下载:http://www.openedv.com/posts/list/313.htm 将启动文件拷贝到Light工程文件夹下。 我们找到 Target1→Source Group1→双击→设置打开文件类型为 Asm Source file→选择 startup_stm32f10x_hd.s→点击 Add,如下图所示 添加完之后,得到如下界面 先关闭 KEIL 软件,为了不让之后生成的文件显得混乱,在Light2文件夹下新建一个OBJ文件夹和USER文件夹, OBJ 用来存放这些编译过程中产生的中间文件(包括.hex 文件也将存放在这个文件夹里面)。USER 文件夹专门用来存放启动文件(startup_stm32f10x_md.s)、工程文件等不可缺少的文件,而然后把 DebugConfig和Listings 和Objects 文件夹全部移到 OBJ 文件夹下,剩余的全部移到USER文件夹里。 由于上面我们还没有任何代码在工程里面,这里我们把系统代码 copy 过来(即 SYSTEM文件夹,该文件夹由 ALIENTEK 提供,这些代码在任何 STM32F10x 的芯片上都是通用的,可以用于快速构建自己的工程。
在 Target 目录树上点击右键→Manage Project Items,弹出对话框。 在上面对话框的中间栏,点新建,新建 USER 和 SYSTEM 两个组。然后点击 Add Files 按钮,把 SYSTEM 文件夹三个子文件夹里面的:sys.c、usart.c、delay.c 加入到 SYSTEM 组中。注意:此时 USER 组下还是没有任何文件,我们只添加SYSTEM的三个。 点击 OK,退出该界面返回 IDE。 此时界面如图所示 接着,我们新建一个 test.c 文件,并保存在 USER 文件夹下。然后双击 USER 组,会弹出加载文件的对话框,此时我们在 USER 目录下选择 test.c 文件,加入到 USER 组下。 如果我们此时编译的话,生成的中间文件,还是会存放在 Listings 和 Objects 文件夹下,所以,我们先设置输出路径,再编译。 点击魔法棒,弹出 Options for Target’Target 1’对话框,选择 Output 选项卡→选中 Create Hex File(用于生成 Hex 文件,后面会用到)→点击 Select Folder for Objects→找到 OBJ 文件夹→点击 OK!
接着,再设置 Listings 文件路径,在上图的基础上,打开 Listing 选项卡→点击 Select Folder for Listings→找到 OBJ 文件夹→点击 OK 加入sys、delay、usart的include路径: 注意!我们必须根据所用 STM32F1 型号的容量,来输入相关宏定义,对于STM32F103 系列芯片,设置原则如下:
16KB≤FLASH≤32KB 选择:STM32F10X_LD 64KB≤FLASH≤128KB 选择:STM32F10X_MD
256KB≤FLASH≤512KB 选择:STM32F10X_HD
至此,一个完整的 STM32F1 开发工程模板下建立好了。接下来我们就可以进行代码下载和仿真调试了。
2、使用寄存器点亮LED灯——代码部分
(1)硬件连接设计
根据题目要求,使用GPIOB,GPIOC,GPIOD端口来控制LED灯,在查询C8T6数据手册后,我选用了PA1,PB0,PB5管脚分别连接红绿蓝三种颜色的灯(由于我只有红绿黄三个小灯,在之后实际硬件中,会用黄灯代替蓝灯) 图中从 3 个 LED 灯的阳极各经过 1 个限流电阻连接到 3.3V 电源,阴极连接STM32 的 3 个 GPIO 引脚中,所以我们只要控制这三个引脚输出高低电平,即可控制其所连接 LED 灯的亮灭。 目标是把 GPIO 的引脚设置成推挽输出模式并且默认下拉,输出低电平,这样就能让 LED 灯亮起来了。
(2)打开之前建立的工程文件
此部分在三.1工程文件的建立部分已经写好。这里不再赘述。
(3)代码编写
在Light2文件夹下新建一个LOULU文件夹,用来存储以后与硬件相关的代码。 然后我们打开 USER 文件夹下的 工程,新建两个文件,然后保存在LOULU→LED 文件夹下面,保存为 led.c,led.h 我们将文件添加到工程中,步骤如下图 在魔法棒这里将LOULU路径加进去,否则之后会报错。
下面来编写led.c文件,要用到GPIOB、GPIOC、GPIOD则,对应时钟设置: 在设置完时钟之后就是配置完时钟之后,LED_Init 配置了 目标三个端口 PB0 PB5 PA1 的模式为推挽输出, 并且默认输出 1。这样就完成了对这三个 IO 口的初始化。 代码如下: led.c
#include "led.h"
//初始化 PB1 PC4 PD8为输出口.并使能这三个口的时钟
//LED IO 初始化
void LED_Init(void)
{
RCC->APB2ENR|=1<<2; //使能 PORTA 时钟
RCC->APB2ENR|=1<<3; //使能 PORTB 时钟
// RCC->APB2ENR|=1<<4; //使能 PORTC 时钟
// RCC->APB2ENR|=1<<5; //使能 PORTD 时钟
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00300000;//PB.5 推挽输出
GPIOB->ODR|=1<<5; //PB.5 输出高
GPIOB->CRL&=0XFFFFFFF0;
GPIOB->CRL|=0X00000003;//PB.0 推挽输出
GPIOB->ODR|=1<<0; //PB.0 输出高
GPIOA->CRL&=0XFFFFFF0F;
GPIOA->CRL|=0X00000030;//PA.1 推挽输出
GPIOA->ODR|=1<<1; //PA.1 输出高
}
这段代码里面最关键就是 3 个推挽输出代码的撰写 led.h
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED 端口定义
#define LED0 PBout(5) // DS0
#define LED1 PBout(0) // DS1
#define LED2 PAout(1) // DS2
void LED_Init(void); //初始化
#endif
这段代码里面最关键就是 3 个宏定义:
#define LED0 PBout(5) // DS0
#define LED1 PBout(0) // DS1
#define LED2 PAout(1) // DS2
在USER文件夹的test.c中撰写main函数 test.c
#include "sys.h"
#include "delay.h"
#include "led.h"
int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
LED_Init(); //初始化与 LED 连接的硬件接口
while(1)
{
LED0=0;
LED1=1;
LED2=1;
delay_ms(1000);
LED0=1;
LED1=0;
LED2=1;
delay_ms(1000);
LED0=1;
LED1=1;
LED2=0;
delay_ms(1000);
}
}
代码包含了#include "led.h"这句,使得 LED0、LED1、LED2、LED_Init 等能在 main 函数里被调用。 接下来,main 函数先调用 Stm32_Clock_Init 函数,配置系统时钟为 9 倍频,也就是 8*9=72M(外部晶振是 8Mhz),然后调用 delay_init 函数,初始化延时函数。接着就是调用 LED_Init 来初始化 三个管脚 为输出。最后在死循环里面实现 LED0 LED1 LED2 交替闪烁,间隔为 1s。 接下来编译看看是否出错 这里编译时会出现错误,按照以下改正: (1)在STM32中的sys.c文件编译报出这个错误时:
__ASM void MSR_MSP(u32 addr) { MSR MSP, r0 //set Main Stack value BX r14 } 如果你上上面那种写法的话,那就修改成下面这种应该就没事了 void MSR_MSP(u32 addr) { __ASM volatile(“MSR MSP, r0”); __ASM volatile(“BX r14”); } (2)复制正点原子的以下代码,不使用微库,采用ARM Compiler 6 报错: 报错::’#pragma import’ is an ARM Compiler 5 extension, and is not supported by ARM Compiler 6 删了2个下划线struct __FILE 点击魔法棒 (3)在多字节的目标代码页中,没有此 Unicode 字符可以映射到的字符。通过百度搜索,更改中文路径后错误已经解决。 改过之后已经可以编译,无错误。
(4)硬件连接
打开C8T6数据手册,查找TXD和RXD管脚位置 PA9——TX PA10——RX GND------GND 3V3------3V3 务必将boot0设为1,boot1设为0,利用跳线帽实现
(5) 烧录:STM32F103C8T6与PC端连接
借助mcuisp下载软件,即可将.hex载入
3、使用寄存器点亮LED灯——电路部分
芯片面包板连好电路后,通电,LED满足实验要求 GIF动态图如下
四、总结
通过此次实验,可以深刻了解STM32F103寄存器方式点亮LED流水灯的方式,同时对寄存器和keil操作有了更加深刻的认识,通过烧录程序,更加增强了动手操作能力,总之,这次实验收获满满,虽然实验过程遇到了很多困难但是通过参考大佬博客,以及自己百度更改启动文件,把所有文件夹中文路径改为英文路径,最终使程序可以正常编译。
五、参考文献
https://blog.csdn.net/qq_46467126/article/details/120737655 https://blog.csdn.net/qq_46467126/article/details/120791793 https://blog.csdn.net/geek_monkey/article/details/86293880 https://blog.csdn.net/geek_monkey/article/details/86291377
|