【嵌入式04.1】:STM32F103系列的地址映射和寄存器映射+GPIO端口初始化

概述:本文主要讲解STM32F103系列的地址映射、寄存器原理和GPIO端口的初始化三步骤(时钟配置、输入输出配置、最大数率配置)。

一、原理学习

1.STM32F103C8T6各字段含义

STM32(芯片系列):STM32代表ARM Cortex-M 内核的32位微控制器
103(芯片子系列):101基本型,102USB基本型(USB2.0),103代表增强型系列,105或107互联型
F(产品类型):F代表通用系列
C(引脚数量):T=36, C=48, R=64, V=100 , Z =144
8(闪存容量):4=16K,6=32K,8=64K,B=128K,C=256K,D=384K,E=512K
T(表封装):
H代表BGA封装
T代表LQFP封装
U代表VFQFPN封装
Y代表WLCSP64
6(工作温度范围):6代表-40 — 85℃,7代表-40 —105℃

2.基础概念(寄存器、内存)

单片机有寄存器和内存
寄存器:单片机中的寄存器是一种特殊的存储器,主要用于存储和检查微机的状态。CPU寄存器用于存储和检查CPU的状态,具体包括计算中途数据、程序因中断或子程序分支时的返回地址、计算结果为零时的负值、计算结果为零时的信息、进位值等。单片机的外围功能控制寄存器用于设置外围功能,例如称为通用I/O GPIO的I/O端口、定时器、串行通信、AD转换器和DA转换器。有显示外围功能状态的状态寄存器,存储AD转换器转换结果的结果寄存器,以及存储通信功能中发送/接收数据的发送/接收数据寄存器。
内存:就是RAM、ROM/FLASH,这里的内存的概念和电脑的不太一样。
ROM/FLASH:程序存储区(只读存储器),不变的东西都存这俩里头,并且存里头的东西只读。ROM是存储程序的,即电脑编译完成的bin或者hex文件会通过下载线存入ROM。
RAM:随机存储器,存储C中的变量或者汇编中的直接地址的,掉电就没了,可以简单理解为变量都存这里。(单片机都有自带ROM/FLASH、RAM,但是很多厂商也会给他们外扩一些,即片外RAM、片外FLASH、ROM。)
内存和寄存器的关系:寄存器和内存都可以存东西,但是我们的代码的所有数据一般都存在内存中,除非你用一些命令将命令其将东西存入寄存器,例如register之类的命令。cpu要用内存的东西,cpu会先向寄存器要,然后寄存器帮cpu去内存中找到要的东西,然后内存将东西取到(原因是寄存器存取速度快)。

二、地址映射和寄存器映射原理

1.地址映射

为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址的过程。

2.寄存器映射原理

CPU要进行访存就涉及到内存地址的概念,因此存储器映射就是为物理内存按一定编码规则分配地址的行为。值得注意,存储器映射一般是由产家规定,用户不能随意更改。
寄存器映射是在存储器映射的基础上进行的。
在存储器Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

三、GPIO端口的初始化设置三步骤

GPIO是什么?
General Purpose Input Output (通用输入/输出)简称为GPIO,就是IO口而已。
每个 GPI/O 端口有两个 32 位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个 32位数据寄存器(GPIOx_IDR,GPIOx_ODR),一个 32 位置位/复位寄存器(GPIOx_BSRR),一个 16 位复位寄存器(GPIOx_BRR)和一个 32 位锁定寄存器(GPIOx_LCKR)。根据数据手册中列出的每个 I/O 端口的特定硬件特征, GPIO 端口的每个位可以由软件分别配置成多种模式。
每个 I/O 端口位可以自由编程,然而 I/0 端口寄存器必须按 32 位字被访问(不允许半字或字节访问)。GPIOx_BSRR 和 GPIOx_BRR 寄存器允许对任何 GPIO 寄存器的读/更改的独立访问;这样,在读和更改访问之间产生 IRQ 时不会发生危险。

I/O端口位的基本结构图如下:
I/O端口位的基本结构

5伏兼容I/O端口位的基本结构图如下:5伏兼容I/O端口位的基本结构

1.时钟配置

时钟是单片机的脉搏,是单片机的驱动源,使用任何一个外设都必须打开相应的时钟。这样的好处是,如果不使用一个外设的时候,就把它的时钟关掉,从而可以降低系统的功耗,达到节能,实现低功耗的效果。

系统时钟原理图:系统时钟原理图

三种不同的时钟源可被用来驱动系统时钟(SYSCLK):
● HSI 振荡器时钟
● HSE 振荡器时钟
● PLL 时钟
这些设备有以下 2 种二级时钟源:
● 40kHz 低速内部 RC,可以用于驱动独立看门狗和通过程序选择驱动 RTC。
RTC 用于从停机/待机模式下自动唤醒系统。
复位和时钟控制 STM32F10xxx 参考手册
37/463
● 32.768kHz 低速外部晶体也可用来通过程序选择驱动 RTC(RTCCLK)。
当不被使用时,任一个时钟源都可被独立地启动或关闭,由此优化系统功耗。当 HSI 被用于作为 PLL 时钟的输入时,系统时钟的最大频率不得超过 64MHz。

系统时钟配置方法如下:
(1)通过汇编进入系统初始化函数(startup_stm32f10x_hd.s)

//通过汇编进入系统初始化函数
IMPORT  SystemInit   

(2)在系统初始化函数SystemInit中调用系统时钟设置函数SetSysClock(system_stm32f10x.c)

//在系统初始化函数SystemInit中调用系统时钟设置函数SetSysClock()
void SystemInit (void)
{
  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();
}

(3)对各时钟进行宏定义(system_stm32f10x.c)

//对各时钟进行宏定义
#define SYSCLK_FREQ_HSE    HSE_VALUE //8000000
//#define SYSCLK_FREQ_16MHz  16000000 
//#define SYSCLK_FREQ_24MHz  24000000 
//#define SYSCLK_FREQ_36MHz  36000000
//#define SYSCLK_FREQ_48MHz  48000000
//#define SYSCLK_FREQ_56MHz  56000000
//#define SYSCLK_FREQ_72MHz  72000000

(4)声明各系统时钟配置函数(system_stm32f10x.c)

//声明各系统时钟配置函数
#ifdef SYSCLK_FREQ_HSE
  static void SetSysClockToHSE(void);
#elif defined SYSCLK_FREQ_16MHz
  static void SetSysClockTo16(void);
#elif defined SYSCLK_FREQ_24MHz
  static void SetSysClockTo24(void);
#elif defined SYSCLK_FREQ_36MHz
  static void SetSysClockTo36(void);
#elif defined SYSCLK_FREQ_48MHz
  static void SetSysClockTo48(void);
#elif defined SYSCLK_FREQ_56MHz
  static void SetSysClockTo56(void);  
#elif defined SYSCLK_FREQ_72MHz
  static void SetSysClockTo72(void);
#endif

(5)调用各系统时钟配置函数(system_stm32f10x.c)

//调用各系统时钟配置函数
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();                 //8MHz
#elif defined SYSCLK_FREQ_16MHz
  SetSysClockTo16();                  //16MHz
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();                  //24MHz
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();                  //36MHz
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();                  //48MHz
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();                  //56MHz
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();                  //72MHz
#endif
 /* If none of the define above is enabled, the HSI is used as System clock
    source (default after reset) */ 
}

(6)以设置系统时钟为16MHz为例(system_stm32f10x.c)

//设置系统时钟为16MHz
static void SetSysClockTo16(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
#if !defined STM32F10X_LD_VL && !defined STM32F10X_MD_VL && !defined STM32F10X_HD_VL 
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 0 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;    
#endif
 
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;
		
    /*  PLL configuration:  = (HSE / 2) * 4 = 16 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL4);

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }

    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
  } 
}

2.输入、输出

下图为STM32F103中文教程及参考手册中截取图片:
STM32F103中文教程中端口位配置表及输出模式位
GPIO的工作模式主要有八种,四种输入模式,四种输出模式。

typedef enum
{
GPIO_Mode_AIN = 0x0, // 模拟输入(应用ADC模拟输入,或者低功耗下省电)
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入(浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
GPIO_Mode_IPD = 0x28, // 下拉输入(IO内部下拉电阻输入)
GPIO_Mode_IPU = 0x48, // 上拉输入(IO内部上拉电阻输入)
GPIO_Mode_Out_OD = 0x14, // 开漏输出(输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
GPIO_Mode_Out_PP = 0x10, // 推挽输出(推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出(片内外设功能(I2C的SCL,SDA))
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出(片内外设功能(TX1,MOSI,MISO.SCK.SS))
} GPIOMode_TypeDef;

(1)输入时I/O端口配置如下:

● 输出缓冲器被禁止
● 施密特触发输入被激活
● 根据输入配置(上拉,下拉或浮动)的不同,弱上拉和下拉电阻被连接
● 出现在 I/O 脚上的数据在每个 APB2 时钟被采样到输入数据寄存器
● 对输入数据寄存器的读访问可得到 I/O 状态

下图为输入浮空/上拉/下拉配置图(VDD_FT 对 5 伏兼容 I/O 脚是特殊的,它与 VDD 不同):

输入浮空/上拉/下拉配置

(2)输出时I/O端口配置如下:

● 输出缓冲器被激活
− 开漏模式:输出寄存器上的 0 激活 N-MOS,而输出寄存器上的 1 将端口
置于高阻状态(P-MOS 从不被激活)。
− 推挽模式:输出寄存器上的 0 激活 N-MOS,而输出寄存器上的 1 将激活
P-MOS。
● 施密特触发输入被激活
● 弱上拉和下拉电阻被禁止
● 出现在 I/O 脚上的数据在每个 APB2 时钟被采样到输入数据寄存器
● 在开漏模式时,对输入数据寄存器的读访问可得到 I/O 状态
● 在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值。

下图为输出配置时的I/O端口位:

输出配置

3.GPIO初始化步骤

(1)使能GPIOx口的时钟
(2)指明GPIOx口的哪一位,这一位的速度大小以及模式。
(3)调用GPIOx口初始化函数,进行初始化。
(4)调用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); //输出高

四、总结

本文根据所给《STM32F103中文教程及参考手册》以及网络文献简要地阐述了STM32F103系列的地址映射、寄存器原理和GPIO端口的初始化三步骤(时钟配置、输入输出配置、最大数率配置)的部分相关内容,想要更加具体的了解还需要进一步地学习。

五、参考文献

http://www.enroo.com/support/category1/dpjrmzs/16147054.html
https://blog.csdn.net/baidu_39491365/article/details/103197178
https://blog.csdn.net/weixin_42700740/article/details/103532363
https://blog.csdn.net/EDman_linux/article/details/104359020
http://t.csdn.cn/qHVLW
https://blog.csdn.net/weixin_44188050/article/details/103999663
https://zhuanlan.zhihu.com/p/96133532

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值