版权归如下公司,禁止非授权转载:

  • 北京西普阳光教育科技股份有限公司(https://www.simpleware.com.cn)
  • 维周机器人科技有限公司(http://www.vejoe.com)


【实验目的】

1、通过练习初步掌握STM32基本IO口的使用,迈向STM32的第一步。
2、通过对GPIO端口配置和对FLASH的读写编程,设计程序按要求点亮小灯,初步了解和熟悉GPIO端口的配置方法以及FLASH的写入和读取。

【实验原理】

一、GPIO简介

STM32的IO口可以由软件通过编程配置成8种模式:输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽输出、推挽式复用功能和开漏复用功能。每个IO口可以自由编程,单IO口寄存器必须要按32位字被访问。STM32的每个IO端口都有7个寄存器来控制,如表1所示。
在这里插入图片描述
表1 GPIO相关寄存器

在STM32芯片上,I/O引脚可以被软件编程设置成各种不同的功能,如输入或输出,所以被称为GPIO(General-purposeI/O)。而GPIO引脚又被分为GPIOA、GPIOB…GPIOG不同的组,每组端口分为0~15,共16个不同的引脚,对于不同型号的芯片,端口的组和引脚的数量不同,具体请参考相应芯片型号datasheet。

二、STM32时钟系统

从时钟频率来说,分为高速时钟和低速时钟,高速时钟是提供给芯片主体的主时钟,而低速时钟只是提供给芯片中的RTC(实时时钟)及独立看门狗使用。

从芯片角度来说,时钟源分为内部时钟与外部时钟源,内部时钟是在芯片内部RC振荡器产生的,起振较快,所以时钟在芯片刚上电的时候,默认使用内部高速时钟。而外部时钟信号是由外部的晶振输入的,在精度和稳定性上都有很大优势,所以上电之后我们再通过软件配置,转而采用外部时钟信号。

我们实验中调用了RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState),其中RCC_APB2Periph用于指示要配置的时钟,在应用时可以使用“|”或操作同时配置几个外设的时钟;NewState用于设置状态,可输入“Disable”关闭或者“Enable”使能时钟。

三、STM32的内部FLASH读写

在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行。

我们实验中实现对FLASH的读写是通过调用库函数:
STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
其中WriteAddr为起始地址(此地址必须为2的倍数),pBuffer为数据指针,NumToWrite和NumToRead为半字数(就是要写入或读取16位数据的个数)。

四、硬件连接和GPIO相关库函数介绍

控制LED灯,通过控制I/O引脚电平的高低来实现。通过图1,LED的硬件连接图可知当I/O引脚为高电平时,LED点亮,为低电平是,LED熄灭。

在这里插入图片描述
图1 LED硬件连接示意图

对应GPIO_InitTypeDef类型的结构体配置说明如下,其中GPIO 输入/输出模式配置如表2所示,GPIO的输出速率配置如表示所示。
typedef struct
{
u16 GPIO_Pin; //指定将要配置的GPIO引脚序号
GPIOSpeed_TypeDef GPIO_Speed; //指定GPIO引脚可以输出的最高频率
GPIOMode_TypeDef GPIO_Mode; //指定GPIO引脚配置的工作状态
}GPIO_InitTypeDef;
在这里插入图片描述
表2 GPIO 输入/输出模式配置
在这里插入图片描述
表3 GPIO 输出速率配置

对结构体中的各个成员进行赋值,然后把这个变量作为“GPIO初始化函数”GPIO_Init的输入参数,该函数能根据这个变量值中的内容去配置寄存器,从而实现GPIO的初始化。对初始化结构体赋值后,把它输入到GPIO初始化函数GPIO_Init,由它来实现寄存器配置,完成GPIO引脚初始化。
库函数GPIO_Init根据GPIO_InitStruct中指定的参数初始化外设GPIOx寄存器,调用形式为:
GPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct)
其中GPIOx为GPIO_TypeDef类型的指针,指向GPIO端口的地址;GPIO_InitStruct为GPIO_TypeDef结构体指针,指向初始化变量。
我们实验中选择的GPIOC的1~3引脚连接到电路上的3个LED小灯。控制引脚的电平变化时,直接使用GPIO_SetBits、GPIO_ResetBits函数控制引脚的输出,就可以控制小灯的亮灭。
库函数GPIO_SetBits/GPIO_RessetBits的主要功能为置/复位指定的数据端口位,其调用形式为:
GPIO_SetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
GPIO_ResetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
其中GPIOx为GPIO_TypeDef类型的指针,指向GPIO端口的地址;GPIO_Pin为指定要写入的端口位,可输入宏GPIO_Pin_0-15,表示GPIOx端口的0-15号引脚。其他库函数说明见附录说明。

【实验环境】

操作系统

Windows7/8/10,32bit/64bit

硬件设备

双轮自平衡机器人。小车所搭载的电路板已经将STM32芯片上的PC1、PC2、PC3管脚引出,连接到电路上的3个LED灯上。
ST-Link下载器(包括USB线与下载线)。如图1.2所示。
在这里插入图片描述
图1.2 ST-Link下载器与下载线

软件环境:

Keil 5
实验场地:

实验步骤

1 配置工程环境

1.1 打开已经建立好的工程模板,在新建立的工程模板中已经添加五个文件夹,分别命名为USER、HARDWARE、SYSTEM、CORE、FWLib文件夹,如图3所示。其中USER文件夹存放的是主函数,HARDWARE文件夹存放的是本实验对应的硬件设备函数,SYSTEM存放的是本课程所有实验通用的函数,CORE文件夹存放的是启动文件,FWLib文件夹存放的是底层驱动函数。
在这里插入图片描述
图3 工程模板对应的文件夹
1.2 在SYSTEM文件夹下新建两个文件,分别为led.c和led.h。分别存放LED控制函数与LED头文件,如图4所示。以及在HARDWARE文件夹下添加FLASH相关的stmflash.c和stmflash.h文件,如图5所示。
在这里插入图片描述
图4 在SYSTEM文件夹下建立led.c与led.h文件
在这里插入图片描述
图1.5 在HARDWARE文件夹下添加stmflash.c和stmflash.h文件

2 开启时钟,完成端口初始化

2.1 打开程序中的led.c文件,首先将led.h文件包含进来。对LED_Init函数进行编写和修改。选择要控制的GPIO引脚并设置引脚模式和速率。

#include "led.h"//包含头文件
/*初始化PC1、PC2、PC3为输出口。并使能时钟*/		
/*对LED的IO口进行初始化*/
void LED_Init(void)
{
	/*定义一个GPIO_InitTypeDef类型的结构体*/
	GPIO_InitTypeDef GPIO_InitStructure;
	/*开启GPIOC的外设时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); 
	/*选择要控制的GPIOC引脚*/											   
  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;	
	/*设置引脚模式为通用推挽输出*/
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
	/*设置引脚速率为50MHz */   
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	/*调用库函数,初始化GPIOC*/
  	GPIO_Init(GPIOC,&GPIO_InitStructure);		  
	/*关闭所有led灯	*/
	GPIO_ResetBits(GPIOC,GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3 );	
}

2.2 打开led.h文件,添加LED_Init函数声明。

#ifndef __LED_H
#define __LED_H	 
void LED_Init(void);  //初始化	 				    
#endif

2.3 打开stmflash.c文件,查看在编写程序时需调用的STMFLASH_Write,STMFLASH_Read函数的变量类型和用法。

//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数),pBuffer:数据指针

//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)  

编写main函数实现实验要求
函数流程图如图6所示,首先对定义函数所使用的的变量,并对各模块进行初始,循环写入和读取FLASH,并根据数据对LED小灯的闪烁次数进行控制。
在这里插入图片描述
图6 程序流程图

3 编写程序

3.1 将工程编译需要用到的头文件包含进来

#include "delay.h"     //包含延时函数头文件
#include "stmflash.h"  //包含FLASH头文件
#include "led.h"      //包含LED头文件

3.2 编写要写入FLASH中的字符串数组

#define FLASH_SAVE_ADDR  0X08070000  //设置FLASH保存地址(必须为偶数)
#define ON   0   //定义全局变量ON=0
#define OFF  1   //定义全局变量OFF=1

3.3 在主函数中调用延时函数与LED初始化函数。

delay_init();	    	 //延时函数初始化	  
LED_Init();           //调用LED初始化函数

3.4 将已编辑好的初始化函数写入main函数。

int main(void)
{	
u16 j=0;
u16 datatemp; 
u16 ledtime=5;
delay_init();	    	 //延时函数初始化	  
LED_Init();  
while(1)
{
......
......
}
}

3.5 因为要实现在FLASH中写入一个数字,再将这个数字读出作为小灯的闪烁次数,因此接下来需要编写一个循环来实现小灯的闪烁,这里我们使用while语句来进行循环。

3.6 在while(1)中编写实现读写FLASH的程序。首先调用STMFLASH_Write函数,写入FLASH存储起始地址、数据指针地址和写入数据的个数。之后调用STMFLASH_Read读取存储的数据。

STMFLASH_Write(FLASH_SAVE_ADDR,&ledtime,1);	
STMFLASH_Read(FLASH_SAVE_ADDR,&datatemp,1);

3.7 在while(1)中编写小灯闪烁程序。定义变量j,当j小于FLASH中写入的数字时,满足if语句的判断条件,将GPIO_Pin_2管脚位置低,小灯点亮,变量j加1,延时100ms后关闭,再重新if判断,直到完成小灯的j次闪烁。

if(j<datatemp)
{			
	GPIO_ResetBits(GPIOC,GPIO_Pin_2);
	delay_ms(100);
   GPIO_SetBits(GPIOC,GPIO_Pin_2);	
delay_ms(100);	
	j++;
}	

4 编译并下载,观察实验现象

4.1本实验采用仿真器为STLink V2,将仿真器与小车相连,注意正负极不要接反,如图7所示。
在这里插入图片描述
图7 仿真器与下载线连接图

4.2编译程序,当编译完成后,如果没有问题,Build Output栏会出现无错误,无警号的提示,如图8所示。
在这里插入图片描述
图8 编译通过后Build Output栏提示信息

4.3下载程序到小车,并观察小灯闪烁。如图9所示。
在这里插入图片描述
图9 Keil编译环境下的下载按键

【实验思考】

一、选择题

题目1:下面哪条语句是设置引脚输出模式(D)
A:GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
B:GPIO_Init(GPIOC, &GPIO_InitStructure);
C:GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
D:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

题目2:GPIO所用的时钟PCLK2的默认值是多少?(A)
A:72MHz
B:36MHz

二、简答题

题目1:GPIO分别有哪些工作模式?简单介绍各个模式的工作场合。

GPIO引脚配置为上拉输入模式,在默认状态下(GPIO引脚无输入),读取得的GPIO引脚数据为1,高电平。而下拉模式则相反,在默认状态下其引脚数据为0,低电平。浮空输入模式一般用于标准的通讯协议如I2C、USART的接收端。模拟输入模式可以用于ADC模块。

普通推挽输出模式一般应用在输出电平为0和3.3伏的场合。而普通开漏输出一般应用在电平不匹配的场合。对于相应的复用模式,则是根据GPIO的复用功能来选择的,如GPIO的引脚用作串口的输出,则使用复用推挽输出模式。如果用在IC、SMBUS这些需要线与功能的复用场合,就使用复用开漏模式。

附录:GPIO相关库函数

在这里插入图片描述



版权声明:本文为suoxd123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/suoxd123/article/details/101226536