stm32基础知识1-参考野火、正点原子

目录

1、stm芯片架构:

1.1ARM内核:Cortex-M3内核

1.2在小容量、中容量和 大容量产品中,主系统由以下部分构成:

● 四个驱动单元:

● 四个被动单元

2、外设地址与外设存储器

2.1外设基地址

2.2外设寄存器

2.3C语言封装寄存器

2.4 从代码角度解释寄存器地址:

2.5利用MDK固件库组织代码--跑马灯实验为例--GPIO初始化


1、stm芯片架构:

1.1ARM内核:Cortex-M3内核

1.2在小容量、中容量和 大容量产品中,主系统由以下部分构成:

● 四个驱动单元:

─ Cortex™-M3内核DCode总线(D-bus),和系统总线(S-bus)

ICode 总线:该总线将 M3 内核指令总线和闪存指令接口相连,指令的预取在该总线上面完成。

DCode总线:  D 表示 Data,即数据,那说明这条总线是用来取数的。我们在写程序的时候,数据有 常量和变量两种,常量就是固定不变的,用 C 语言中的 const 关键字修饰,是放到内部的 FLASH 当中的,变量是可变的,不管是全局变量还是局部变量都放在内部的 SRAM。因为数据可以被 Dcode 总线和 DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来 仲裁,决定哪个总线在取数。

系统总线:主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统 总线来完成的。

─ 通用DMA1和通用DMA2

DMA 总线也主要是用来传输数据,这个数据可以是在某个外设的数据寄存器,可以在 SRAM, 可以在内部的 FLASH。因为数据可以被 Dcode 总线和 DMA 总线访问,所以为了避免访问冲突, 在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。

● 四个被动单元

内部SRAM:即通常说的 RAM,程序的变量、堆栈等的开销都是基于内部的 SRAM。内核通过 DCode 总线来访问它。

内部闪存存储器:即FLASH,编写好的程序放在这里。内核通过ICode总线来取里面的指令。

FSMC:英文全称是 Flexible static memory controller,叫灵活的静态的存储器控制器,是 STM32F10xx 中一个很有特色的外设,通过 FSMC,我们可以扩展内存,如外部的 SRAM,NAND-FLASH 和 NORFLASH。但有一点我们要注意的是,FSMC 只能扩展静态的内存,即名称里面的 S:static,不能是动态的内存,比如 SDRAM 就不能扩展。

AHB到APB的桥(AHB2APBx):从 AHB 总线延伸出来的两条 APB2 和 APB1 总线,上面挂载着 STM32 各种各样的特色外设。我 们经常说的 GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习 STM32 的 重点,就是要学会编程这些外设去驱动外部的各种设备。它连接所有的APB设备 25/754 这些都是通过一个多级的AHB总线构架相互连接的,如下图图1所示:

2、外设地址与外设存储器

2.1外设基地址

片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1 挂载低速外设,APB2 和 AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中 APB1 总线的地址最低,片上外设从这里开始, 也叫外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX 外设基地址”,也叫 XX 外设的边界地址。以 GPIO 这个外设来讲解外设的基地址,GPIO 属于高速的外设,挂载到 APB2 总线 上,具体见表格外设 GPIO 基地址 。

2.2外设寄存器

GPIO简介:在 XX 外设的地址范围内,分布着的就是该外设的寄存器。以 GPIO 外设为例,GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把 GPIO 的引脚连接到 LED 灯的阴极,LED 灯的阳极接电源,然后通 过 STM32 控制该引脚的电平,从而实现控制 LED 灯的亮灭。----跑马灯实验

GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占四个字节,在该外设的 基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。

2.3C语言封装寄存器

引入 C 语言中的结构体语法对寄存器进行封装:

typedef unsigned int uint32_t; /* 无符号 32 位变量 */
typedef unsigned short int uint16_t; /* 无符号 16 位变量 */

/* GPIO 寄存器列表 */
typedef struct {
uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 *
uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
} GPIO_TypeDef;

更进一步,直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可。

/* 使用 GPIO_TypeDef 把地址强制转换成指针 */
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)

/* 使用定义好的宏直接访问 */
/* 访问 GPIOB 端口的寄存器 */
GPIOB->BSRR = 0xFFFF; //通过指针访问并修改 GPIOB_BSRR 寄存器
GPIOB->CRL = 0xFFFF; //修改 GPIOB_CRL 寄存器
GPIOB->ODR =0xFFFF; //修改 GPIOB_ODR 寄存器

uint32_t temp;
temp = GPIOB->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 

其封装固件库已完成。

2.4 从代码角度解释寄存器地址:

GPIOA 的 7 个寄存器都是 32 位的,所以每个寄存器占有 4 个地址,一共占用 28 个地址,地址偏移范围为(000h~01Bh)。这个地址偏移是相对 GPIOA 的基地址而言的。

又因为 GPIO 都是挂载在 APB2 总线 之上,所以GPIOA 的基地址是由 APB2 总线的基地+GPIOA 在 APB2 总线上的偏移地址。

打开 stm32f10x.h 定位到 GPIO_TypeDef 定义处:

typedef struct
{
 __IO uint32_t CRL;
 __IO uint32_t CRH;
 __IO uint32_t IDR;
 __IO uint32_t ODR;
 __IO uint32_t BSRR;
 __IO uint32_t BRR;
 __IO uint32_t LCKR;
} GPIO_TypeDef;

定位到: #define GPIOA      ((GPIO_TypeDef *) GPIOA_BASE)

GPIOA 是将 GPIOA_BASE 强制转换为 GPIO_TypeDef 指针,GPIOA 指向地址 GPIOA_BASE,GPIOA_BASE 存放的数据类型为 GPIO_TypeDef。

双击“GPIOA_BASE”选中之后右键选中“Go to definition of ”,查看 GPIOA_BASE 的宏定义: #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)

依次类推,可以找到最顶层:

#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)

#define PERIPH_BASE ((uint32_t)0x40000000)

算出 GPIOA 的基地址位:GPIOA_BASE= 0x40000000+0x10000+0x0800=0x40010800

GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相对 GPIOA 基地址的偏移值,故寄存器地址:

GPIOA->BRR=0x40010800+0x14

把 GPIO_TypeDef 的定义中的成员变量的顺序和 GPIOx 寄存器地址映像对比可以发现,他们的顺序是一致的,如果不一致,就会导致地址混乱了。 这就是为什么固件库里面:

GPIOA->BRR=value;就是设置地址为 0x40010800 +0x014(BRR 偏移量)=0x40010814 的寄存器 BRR 的值了。

2.5利用MDK固件库组织代码--跑马灯实验为例--GPIO初始化

在头文件 stm32f10x_gpio.h 头文件中,定义 GPIO 初始化函数为:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

双击 GPIO_InitTypeDef 后右键选择“Go to definition…”,定位到 stm32f10x_gpio.h 中 GPIO_InitTypeDef 的定义处:

typedef struct
{
  uint16_t GPIO_Pin;           
  GPIOSpeed_TypeDef GPIO_Speed;  
  GPIOMode_TypeDef GPIO_Mode;           
}GPIO_InitTypeDef;

这个结构体有 3 个成员变量,包含了初始化 GPIO 所需要的信息--引脚号、工作模式、输出速率,我们首先要定义一个结构体变量,根据需要配置 GPIO 的模式, 对这个结构体的各个成员进行赋值,然后把这个变量作为“GPIO 初始化函数”的输入参数,该函数能根据这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。下面我们定义:

GPIO_InitTypeDef GPIO_InitStructure;

找到stm32f10x_gpio.c 文件中的 GPIO_Init 函数体:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
……
 /* Check the parameters */
 assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
 assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
 assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); 
……
 assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
……
}

assert_param 函数式对入口参数的有效性进行判断,我们可以从这个函数入手, 确定我们的入口参数的范围。双击 “IS_GPIO_ALL_PERIPH”右键点击“go to defition of…” 定位到了下面的定义:

#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
 ((PERIPH) == GPIOB) || \
((PERIPH) == GPIOC) || \
((PERIPH) == GPIOD) || \
 ((PERIPH) == GPIOE) || \
((PERIPH) == GPIOF) || \
((PERIPH) == GPIOG))

GPIOx 的取值规定只允许是 GPIOA~GPIOG。

同样的办法,我们双击“IS_GPIO_MODE” 右键点击“go to defition of…”,定位到下面的定义:

typedef enum
{ GPIO_Mode_AIN = 0x0,
 GPIO_Mode_IN_FLOATING = 0x04,
 GPIO_Mode_IPD = 0x28,
 GPIO_Mode_IPU = 0x48,
 GPIO_Mode_Out_OD = 0x14,
 GPIO_Mode_Out_PP = 0x10,
 GPIO_Mode_AF_OD = 0x1C,
 GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || \
                     ((MODE) == GPIO_Mode_IN_FLOATING) || \
                     ((MODE) == GPIO_Mode_IPD) || \
                     ((MODE) == GPIO_Mode_IPU) || \
                     ((MODE) == GPIO_Mode_Out_OD) || \
                     ((MODE) == GPIO_Mode_Out_PP) || \
                     ((MODE) == GPIO_Mode_AF_OD) || \
                     ((MODE) == GPIO_Mode_AF_PP))
GPIO_InitStruct->GPIO_Mode 成员的取值范围只能是上面定义的 8 种。这 8 中模式是通过
一个枚举类型组织在一起的。
同样的方法可以找出 GPIO_Speed 的参数限制:
typedef enum
{ 
     GPIO_Speed_10MHz = 1,
     GPIO_Speed_2MHz, 
     GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_Speed_10MHz) || \
                             ((SPEED) == GPIO_Speed_2MHz) || \
                             ((SPEED) == GPIO_Speed_50MHz))
同样的方法我们双击“ IS_GPIO_PIN ” 右键点击“ go to defition of… , 定位到下面的定义:
#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))
GPIO_Pin 成员变量的取值范围为 0x0000 0xffff,MDK 会将这些数字的意思通过宏定义定义出来,这样可读性大大增强。我们可以看到在 IS_GPIO_PIN(PIN)宏定义的上面还有数行宏定义:

#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
……
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))
于是我们可以组织起来下面的代码:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
初始化多个 IO 口的方式可以是如下:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6| GPIO_Pin_7; //指定端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //端口模式:推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值