STM32点灯
开始学习一款单片机都是从操作IO口开始,所以首先准备点亮个LED灯,刚开始以为STM32和51类似,直接操作端口,但是LED却没有反应,查询资料后发现,对于ARM,要操作IO口都要先配置IO口。
对于普通的IO口,配置较为简单,大概是这几个步骤:
1.打开相应IO端口的时钟;
2.配置IO口的模式;
3.初始化IO端口。
一、直接操作寄存器绝对地址进行PA1-LED的点亮:
1.打开时钟
//配置时钟-打开GPIOA的时钟
*(unsigned int * )0x40021018 |= (1<<2);
2.配置IO口工作模式-端口配置低寄存器(GPIOx_CRL) (x=A…E)
//配置IO口输出(00 01 通用推挽输出 最大速度10MHz)
*(unsigned int * )0x40010800 |= ((1)<<(4*0)); // 置位: |=
3.初始化IO端口-端口输出数据寄存器(GPIOx_ODR) (x=A…E)
//初始化IO口-PA1
*(unsigned int * )0x4001080C &= ~(1<<1); //端口输出数据寄存器
(GPIOA_ODR)0x4001 0800 + 0xC0(地址偏移) 清零: &= ~
二、寄存器映射进行PA1-LED的点亮:
// "stm32f10x.h"
// 用来存放STM32寄存器映射的代码
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define RCC_APB2ENR *(unsigned int *)(RCC_BASE + 0x18)
#define GPIOA_CRL *(unsigned int *)(GPIOA_BASE + 0x00)
#define GPIOA_CRH *(unsigned int *)(GPIOA_BASE + 0x04)
#define GPIOA_IDR *(unsigned int *)(GPIOA_BASE + 0x08)
#define GPIOA_ODR *(unsigned int *)(GPIOA_BASE + 0x0C)
#define GPIOA_BSRR *(unsigned int *)(GPIOA_BASE + 0x10)
#define GPIOA_BRR *(unsigned int *)(GPIOA_BASE + 0x14)
#define GPIOA_LCKR *(unsigned int *)(GPIOA_BASE + 0x18)
//配置时钟-打开GPIOA的时钟
RCC_APB2ENR |= (1<<2);
//配置IO口输出(00 01 通用推挽输出 最大速度10MHz)端口配置低寄存器(GPIOx_CRL) (x=A..E)
GPIOA_CRL &= ~((0x0F)<<(4*0)); //清零操作
GPIOA_CRL |= ((1)<<(4*0)); // 置位: |=
//初始化IO口-PA1
GPIOA_ODR &= ~(1<<1); //端口输出数据寄存器(GPIOA_ODR)0x4001 0800 + 0xC0(地址偏移) 清零: &= ~
三、结构体封装寄存器映像
//联系上方<二>中的代码
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t LCKR;
}GPIO_typeDef;
typedef struct
{
uint32_t CR;
uint32_t CFGR;
uint32_t CIR;
uint32_t APB2RSTR;
uint32_t APB1RSTR;
uint32_t AHBENR;
uint32_t APB2ENR;
uint32_t APB1ENR;
uint32_t BDCR;
uint32_t CSR;
}RCC_typeDef;
#define GPIOA ((GPIO_typeDef *)GPIOA_BASE)
#define RCC ((RCC_typeDef *)RCC_BASE)
//配置时钟-打开GPIOA的时钟
RCC->APB2ENR |= (1<<2);
//配置IO口输出(00 01 通用推挽输出 最大速度10MHz)-端口配置低寄存器(GPIOx_CRL) (x=A..E)
GPIOA->CRL &= ~((0x0F)<<(4*0)); //清零操作
GPIOA->CRL |= ((1)<<(4*0)); // 置位: |=
//初始化IO口-PA1
GPIOA->ODR &= ~(1<<1); //端口输出数据寄存器(GPIOA_ODR)0x4001 0800 + 0xC0(地址偏移) 清零: &= ~
四、自定义封装库函数—学习库函数编程的思想
4.1:寄存器结构体定义
操作寄存器的时候,操作的是对寄存器的绝对地址进行预处理,当外设过多会非常麻烦。
#define GPIOA_CRL *(unsigned int *)(GPIOA_BASE + 0x00)
#define GPIOA_CRH *(unsigned int *)(GPIOA_BASE + 0x04)
#define GPIOA_IDR *(unsigned int *)(GPIOA_BASE + 0x08)
#define GPIOA_ODR *(unsigned int *)(GPIOA_BASE + 0x0C)
#define GPIOA_BSRR *(unsigned int *)(GPIOA_BASE + 0x10)
#define GPIOA_BRR *(unsigned int *)(GPIOA_BASE + 0x14)
#define GPIOA_LCKR *(unsigned int *)(GPIOA_BASE + 0x18)
考虑到外设寄存器的地址都是在基于外设基地址偏移下逐个连续递增的,每个寄存器占 32 个字节,这种方式跟结构体成员类似。所以我们可以定义一种外设结构体:
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
//结构体来代表GPIO寄存器
//GPIO寄存器每个之间有规律的相差四个字节(32位)
//那我就定义一个32位的数据类型:typedef unsigned int uint32_t;
//这样就可以形成GPIO寄存器的结构体
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t LCKR;
}GPIO_typeDef;
//结构体来代表RCC寄存器 由上同理
typedef struct
{
uint32_t CR;
uint32_t CFGR;
uint32_t CIR;
uint32_t APB2RSTR;
uint32_t APB1RSTR;
uint32_t AHBENR;
uint32_t APB2ENR;
uint32_t APB1ENR;
uint32_t BDCR;
uint32_t CSR;
}RCC_typeDef;
#define GPIOA ((GPIO_typeDef *)GPIOA_BASE)
#define RCC ((RCC_typeDef *)RCC_BASE)
4.2:端口置位复位函数
```c
#define GPIO_Pin_0 ((uint16_t)0x0001) /*< 选择Pin0 >*/ //(00000000 00000001)b
#define GPIO_Pin_1 ((uint16_t)0x0002) /*< 选择Pin1 >*/ //(00000000 00000010)b
#define GPIO_Pin_2 ((uint16_t)0x0004) /*< 选择Pin2 >*/ //(00000000 00000100)b
#define GPIO_Pin_3 ((uint16_t)0x0008) /*< 选择Pin3 >*/ //(00000000 00001000)b
#define GPIO_Pin_4 ((uint16_t)0x0010) /*< 选择Pin4 >*/ //(00000000 00010000)b
#define GPIO_Pin_5 ((uint16_t)0x0020) /*< 选择Pin5 >*/ //(00000000 00100000)b
#define GPIO_Pin_6 ((uint16_t)0x0040) /*< 选择Pin6 >*/ //(00000000 01000000)b
#define GPIO_Pin_7 ((uint16_t)0x0080) /*< 选择Pin7 >*/ //(00000000 10000000)b
#define GPIO_Pin_8 ((uint16_t)0x0100) /*< 选择Pin8 >*/ //(00000001 00000000)b
#define GPIO_Pin_9 ((uint16_t)0x0200) /*< 选择Pin9 >*/ //(00000010 00000000)b
#define GPIO_Pin_10 ((uint16_t)0x0400) /*< 选择Pin10 >*/ //(00000100 00000000)b
#define GPIO_Pin_11 ((uint16_t)0x0800) /*< 选择Pin11 >*/ //(00001000 00000000)b
#define GPIO_Pin_12 ((uint16_t)0x1000) /*< 选择Pin12 >*/ //(00010000 00000000)b
#define GPIO_Pin_13 ((uint16_t)0x2000) /*< 选择Pin13 >*/ //(00100000 00000000)b
#define GPIO_Pin_14 ((uint16_t)0x4000) /*< 选择Pin14 >*/ //(01000000 00000000)b
#define GPIO_Pin_15 ((uint16_t)0x8000) /*< 选择Pin15 >*/ //(10000000 00000000)b
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*< 选择全部引脚 >*/ //(11111111 11111111)b
void GPIO_SetBits(GPIO_typeDef *GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRR |= GPIO_Pin;
}
//通过BSRR进行置位
GPIO_SetBits(GPIOA,GPIO_Pin_1);
//通过BRR进行清除
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
4.2:GPIO初始化结构体及初始化函数
用GPIO_InitTypeDef的结构体类型封装GPIO,初始化 GPIO 前,定义结构体变量
gpio.h
/**
* GPIO 输出速率枚举定义
*/
typedef enum
{
GPIO_Speed_10MHz = 1, // 10MHZ (01)b
GPIO_Speed_2MHz, // 2MHZ (10)b
GPIO_Speed_50MHz // 50MHZ (11)b
}GPIOSpeed_TypeDef;
/**
* GPIO 工作模式枚举定义
*/
typedef enum
{
GPIO_Mode_AIN = 0x0, // 模拟输入 (0000 0000)b
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 (0000 0100)b
GPIO_Mode_IPD = 0x28, // 下拉输入 (0010 1000)b
GPIO_Mode_IPU = 0x48, // 上拉输入 (0100 1000)b
GPIO_Mode_Out_OD = 0x14, // 开漏输出 (0001 0100)b
GPIO_Mode_Out_PP = 0x10, // 推挽输出 (0001 0000)b
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 (0001 1100)b
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 (0001 1000)b
}GPIOMode_TypeDef;
/**
* GPIO 初始化结构体类型定义
*/
typedef struct
{
uint16_t GPIO_Pin; /*!< 选择要配置的 GPIO 引脚
可输入 GPIO_Pin_ 定义的宏 */
GPIOSpeed_TypeDef GPIO_Speed; /*!< 选择 GPIO 引脚的速率
可输入 GPIOSpeed_TypeDef 定义的枚举值*/
GPIOMode_TypeDef GPIO_Mode; /*!< 选择 GPIO 引脚的工作模式
可输入 GPIOMode_TypeDef 定义的枚举值 */
}GPIO_InitTypeDef;
GPIO 初始化函数,实现寄存器配置。编程是一种技巧
gpio.c
/**
*函数功能:初始化引脚模式
*参数说明:GPIOx,该参数为 GPIO_TypeDef 类型的指针,指向 GPIO 端口的地址
* GPIO_InitTypeDef:GPIO_InitTypeDef 结构体指针,指向初始化变量
*/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode =0x00,currentpin = 0x00,pinpos = 0x00,pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*---------------- GPIO 模式配置 -------------------*/
// 把输入参数 GPIO_Mode 的低四位暂存在 currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) &
((uint32_t)0x0F);
// bit4 是 1 表示输出,bit4 是 0 则是输入
// 判断 bit4 是 1 还是 0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) &
((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-----GPIO CRL 寄存器配置 CRL 寄存器控制着低 8 位 IO- ----*/
// 配置端口低 8 位,即 Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin &
((uint32_t)0x00FF)) != 0x00)
{
// 先备份 CRL 寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从 Pin0 开始配对,找出具体的 Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos 的值为 1 左移 pinpos 位
pos = ((uint32_t)0x01) << pinpos;
// 令 pos 与输入参数 GPIO_PIN 作位与运算
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若 currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos 的值左移两位(乘以 4),因为寄存器中 4 个位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的 4 个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置 0,对 BRR 寄存器写 1 对引脚置 0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为 1,对 BSRR 寄存器写 1 对引脚置 1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到 CRL 寄存器之中
GPIOx->CRL = tmpreg;
}
/*--------GPIO CRH 寄存器配置 CRH 寄存器控制着高 8 位 IO- -----*/
// 配置端口高 8 位,即 Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份 CRH 寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从 Pin8 开始配对,找出具体的 Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos 与输入参数 GPIO_PIN 作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若 currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos 的值左移两位(乘以 4),因为寄存器中 4 个位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的 4 个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置 0,对 BRR 寄存器写 1 可对引脚置 0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为 1,对 BSRR 寄存器写 1 可对引脚置 1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到 CRH 寄存器之中
GPIOx->CRH = tmpreg;
}
}
main.c
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIO 端口初始化 */
/*选择要控制的 GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
/*设置引脚模式为输出模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚的输出类型为推挽输出*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
(本文参考的是野火的stm32教程:https://www.bilibili.com/video/BV1yW411Y7Gw)