目录

  • STM32f1库函数开发学习
    • 实战一 · I/O口
      • 1. 文件夹结构
      • 2. 配置细节 · 从寄存器到库函数
      • 3. 跑马灯
      • 4. 按键输入

STM32f1库函数开发学习

“追毛求疵” 的学习STM32,实际操作过程中知识盲区比想象中要多很多!只做了两个GPIO口项目。

实战一 · I/O口

1. 文件夹结构

  • USER

    • system_stm32f10x.c 系统时钟初始化函数SystemInit定义
    • stm32f10x_it.c 部分中断服务函数
    • main.c 主函数
  • HARDWARE

    • 外设驱动代码,调用FWLib固件库函数
  • SYSTEM 共用代码

  • CORE 核心启动文件

  • FWLib 外设驱动固件库文件,定义库函数

  • README 说明文件

文件层次关系

2. 配置细节 · 从寄存器到库函数

寄存器原理

每个GPI/O口有7个寄存器:

  1. 32位端口配置 CRL/CRH 常用
  2. 32位数据寄存器 IDR/ODR 常用
  3. 32位置位/复位寄存器 BSRR 常用
  4. 16位复位寄存器 BRR
  5. 32位锁存器 LCKR

通过寄存器可以将I/O口配置成多种输入输出模式

I/O口模式介绍参照下文:


STM32八种I/O口模式区别


I/O口配置结构图

I/O口的“输入和输出”是以片上外设为参考系的,左边是片内,右边是外部引脚

 


  1. 输入输出模式配置

STM32将I/O口分成多个组:GPIOA,GPIOB…

每组I/O有16个I/O口,每个I/O端口位可以自由编程,必须按32位被访问。

CRL和CRH控制IO口模式以及传输速度,CRL控制低八位,CRH控制高八位。

以CRL为例,有8个2位CNF和8个二位MODE一共32位,CNF选择输出模式,MODE选择输出速度,配置表如下:

寄存器配置成CNF=01,MODE=00时,进入浮空输入模式,达到复位的效果

单片机的GPIO口在复位后默认是输入模式,而且是浮空输入模式

综上: CRL控制每组IO端口中的低八位IO口模式,每个IO口占用CRL4位,高两位CNF,低两位MODE。CRH与之相同,控制高八位GPIOx_(8-15)

常用模式的配置:

  • 0X0 模拟输入 (ADC用)
  • 0X3 推挽输出 (50M输出口)
  • 0X8 上/下拉输入 (输入口)
  • 0XB 复用输出 (50M IO口复用)

回到库函数封装开发

//GPIO初始化函数
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
  • 第一个参数GPIOx指定GPIO,取值GPIOA,GPIOB…
  • 第二个参数为初始化参数结构体指针,结构体GPIO_InitStruct,类型为GPIO_InitTypeDef,定义:
typedef struct
{ uint16_t GPIO_Pin; 
  GPIOSpeed_TypeDef GPIO_Speed; 
  GPIOMode_TypeDef GPIO_Mode; 
} GPIO_InitTypeDef;

初始化实例:

GPIO_InitTypeDef GPIO_InitStructure;
//结构体变量定义
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; 
//端口配置Pin_5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//速度 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);
//根据设定参数配置 GPIOB

2. IDR 端口输入数据寄存器,低16位只读寄存器,可以用于读取某个IO口电平状态。

寄存器描述:

库函数:

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

示例

GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);
//读取GPIOA_5电平状态,返回0/1

3. ODR 端口输出数据寄存器,只用第十六位,可读写,写入数据控制输出,读取数据获取当前电平状态。

寄存器描述:

库函数:

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);


BSRR端口位设置/清除寄存器,可以设置GPIO端口输出位高低电平

寄存器描述:

库函数:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//置1
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
//置0

了解了GPIO配置细节,下面来进行两个基础实验:

  • 跑马灯
  • 按键输入

3. 跑马灯

需要的固件库文件:

  • stm32f10x_gpio.c /stm32f10x_gpio.h
  • stm32f10x_rcc.c/stm32f10x_rcc.h
  • misc.c/ misc.h
  • stm32f10x_usart /stm32f10x_usart.h

stm32f10x_rcc文件包含了系统时钟配置函数、外设时钟使能函数,在每个实验都需要引入

在外设硬件文件夹HARDWARE中新建led.c,依次使能时钟,查原理图配置端口,端口初始化,输出高电平,推挽输出模式,速度50MHz

GPIO是APB2总线上的外设,APB2总线上的外设时钟使能函数是:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
RCC_APB2Periph_GPIOD, ENABLE); 
//使能 PA,PD 端口时钟

.c程序代码:

#include "led.h"
//初始化 PA8 和 PD2 为输出口.并使能这两个口的时钟
//LED IO 初始化
void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
	RCC_APB2Periph_GPIOD, ENABLE);
	//使能 PA,PD 端口时钟

	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
	//IO 口速度为 50MHz
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	//推挽输出

	//PA.8
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; 
	//LED0-->PA.8 端口配置
	GPIO_Init(GPIOA, &GPIO_InitStructure); 
	//初始化 GPIOA.8
	GPIO_SetBits(GPIOA,GPIO_Pin_8); 
	//PA.8 输出高

	//PD.2
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; 
	//LED1-->PD.2 
	GPIO_Init(GPIOD, &GPIO_InitStructure); 
	//初始化 GPIOD.2
	GPIO_SetBits(GPIOD,GPIO_Pin_2); 
	//PD.2 输出高
}

.h程序代码:

#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED 端口定义
//位带操作控制某个IO口的1个位
#define LED0 PAout(8) // LED0 = PA8
#define LED1 PDout(2) // LED1 = PD2
void LED_Init(void);//初始化 
#endif

位带操作: PAout (6) = 1 如是

main函数代码

#include "led.h"
#include "delay.h"
#include "sys.h"

//跑马灯实验 
int main(void)
{
	delay_init(); 
	//延时函数初始化 
	LED_Init();
	//初始化与 LED 连接的硬件接口
	while(1)
	{ 
		LED0=0; 
		LED1=1;
		delay_ms(300); 
		//延时 300ms
		LED0=1;
		LED1=0;
		delay_ms(300); //延时 300ms
	}	 
}

4. 按键输入

IO口知识在前面已经讲解完善,直接开始硬件查接口+软件设计

硬件接口查询

按键项目需要两个组成部分,初始化和扫描

初始化函数

查接口电路发现按键的PA15与JTAG的数据输入端JTDI冲突了,于是使用函数:

GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

禁用JTAG,开启SWD

void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//结构体定义


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);
//使能 PORTA,PORTC 时钟
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//关闭 jtag,使能 SWD,可以用 SWD 模式调试


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
//PA15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
//设置成上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化 GPIOA15


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
//PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
//设置成上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure);
//初始化 GPIOC5


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
//PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; 
//PA0 设置成输入,默认下拉 
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化 GPIOA.0
}

按键处理函数

//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//返回值:
//0,没有任何按键按下
//KEY0_PRES,KEY0 按下
//KEY1_PRES,KEY1 按下
//WKUP_PRES,WK_UP 按下
//注意此函数有响应优先级,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按 
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
	{
		delay_ms(10);//去抖动
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(WK_UP==1)return WKUP_PRES; 
	}
	else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; 
	return 0;// 无按键按下
}

扫描优先级:

  • KEY0
    • KEY1
      • WK_UP

按键松开标志 key_up 是 static 静态变量:

  • 变量放在程序的全局存储区中,下次调用仍然保持原来的值
  • 但变量只在自己的变量作用范围内可见,这是与全局变量的区别
  • 补充学习:static变量的使用

 

所以该函数是一个不可重入函数:

“在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。”


补充学习:浅谈可重入函数与不可重入函数

OS: Operation System,操作系统

RTOS: Real Time Operation System实时操作系统,指一类系统,包括:

  • UCOS
  • FreeRTOS
  • RTX
  • RT-Thread

STM32的操作系统开发后期再进行学习补充