笔者初学单片机,直接学习STM32,用的是正点原子的F1精英系列开发板,本来照着B站上正点原子的官方教程学习,但是学习一段时间后,发现该教程某些地方讲的不尽如人意,一番比较之后,笔者选择了欧启标老师的课程(【1.1STM32入门_实操_点亮一颗LED灯】1.1STM32入门_实操_点亮一颗LED灯_哔哩哔哩_bilibili ,正点原子官方也发布了同一套视频 【【新手入门】STM32从0到1,从浅至深知识讲解-原子哥强烈推荐!】【新手入门】STM32从0到1,从浅至深知识讲解-原子哥强烈推荐!_哔哩哔哩_bilibili)。但是,课程中使用的单片机为stm32 F4系列,F4与F1系列有较多不同之处。所以,本文在记录欧启标老师课上所讲知识之外,也附上笔者根据欧启标老师课上的F4代码修改得到的F1的对应代码。
本笔记是按照欧启标老师课程整理而来,若与课程有不同之处,则以课程为准。笔者初学单片机,难免有一些错误,希望各位网友批评指正。
1.1 点亮LED灯
keil的安装、破解、美化、新建工程等教程网上已有很多,不再赘述。本文直接从 1.1 点亮LED灯开始。
1.1.1 电路原理
此电路原理非常简单,LED0、LED1阳极均与3.3V电压相连,阴极分别与PB5、PE5相连。若想点亮二极管,使对应管脚置低电平即可,但在设置引脚输出为低电平时,需要配置对应IO口的时钟,并设置输出模式。
由以上分析,点亮LED0实现步骤为:
1 使能PB时钟
2 设置引脚为输出模式
3 设置低电平
下面,结合数据手册分析如何实现以上三个步骤。
1.1.2 使能PB时钟
使能IO端口时钟对应的寄存器为:RCC_APB2ENR
该寄存器各位对应的功能为(部分):
可以看到,若要使能IOPB,使RCC_APB2ENR寄存器bit3置1即可。也即 RCC_APB2ENR|= 1<<3;
(初学者应适应这种操作bit的方式,1<<3即为 0000 0000 0000 0000 0000 0000 0000 1000,RCC_APB2ENR|(1<<3)的效果是将bit3置1,同时不改变其他bit的值。)
1.1.3 设置引脚为输出模式:
stm32 F1有7组GPIO,每组GPIO有16个gpio口(0-15),配置每个gpio口需要4位配置寄存器,所以需要64位,即两个32位,0-7为端口配置低寄存器GPIOx_CRL ,8-15为端口配置高寄存器GPIOx_CRH。由于LED0对应的是PB5,所以应配置GPIOx_CRL 。
由下图,PB5对应的bit为23:20,将位23:33置成00,位21:20置成01、10、11均可。也即GPIOB_CRL|=1<<20;(置为01)
注意:在配置寄存器时,有可能寄存器中原本有值,会影响赋值结果,因此,赋值前应将对应位清零,也即GPIOB_CRL&= ~(15<<20) (该写法与上面类似,~(15<<20)=1111 0000 1111 1111 1111 1111 1111,与运算后,只将第23-20位置0,并不改变其他位的值)
1.1.4 设置输出为低电平:
端口输出对应的寄存器为:GPIOx_ODR,该寄存器的16位与IO口的16位一一对应,将寄存器的某一位设置成1,则对应端口输出高电平,反之输出低电平。
设置PB5输出低电平,应使GPIOB_ODR的bit5置成0,也即 GPIOB_ODR &= ~(1<<5)
(该写法与上面类似,~(1<<5)=1111 1111 1111 1111 1111 1110 1111,与运算后,只将第五位置成0,并不改变其他位的值)
经过以上分析,可以得到以下代码:
RCC_APB2ENR|= 1<<3;
GPIOB_CRL&= ~(15<<20);
GPIOB_CRL|=1<<20;
GPIOB_ODR &= ~(1<<5);
但是,RCC_APB2ENR、GPIOB_CRL、GPIOB_ODR均代表的是单片机内寄存器的地址,如果我们不声明的话,编译器是识别不到这些地址的,所以,我们应当加以声明。可以从参考手册获取寄存器地址。
寄存器的地址是由基地址和偏移地址组成,查找手册,可以看到RCC寄存器的基地址为:0x4002 1000,而APB2ENR相对于RCC的偏移地址为0x18,所以,RCC_APB2ENR的地址为:
0x4002 1000+0x18=0x4002 1018
与上面计算过程一致,GPIOB_CRL的地址为:
0x4001 0c00+0x00=0x4001 0c00
GPIOB_ODR的地址为:
0x4001 0c00+0x0C=0x4001 0c0c
所以,STM32F1上完整代码为:
#define RCC_APB2ENR (*(volatile unsigned int*)(0x40021018))
#define GPIOB_CRL (*(volatile unsigned int*)(0x40010c00))
#define GPIOB_ODR (*(volatile unsigned int*)(0x40010c0c))
//0x40021018是一个数字---->(*)0x40021018 加星号变成地址---->(*(*)0x40021018) 地址指向的存储单元
//---->(*(unsigned int*) (0x40021000+0x18)) 地址指向的存储单元中存储的数据类型是unsigned int---->
//(*(volatile unsigned int*) (0x40021000+0x18)) 直接访问地址,不要去缓存访问
//
int main()
{
//使能GPIOB时钟
RCC_APB2ENR |= (1<<3);
//设置PB5的功能
GPIOB_CRL&= ~(15<<20);
GPIOB_CRL|=1<<20;
//设置PB5输出高电平,LED灭
GPIOB_ODR |= (1<<5);;
while(1)
{
GPIOB_ODR &=~(1<<5);//LED亮
}
// return 0;
}
欧启标老师STM32F4上完整代码为:
/* GPIOF 口相关寄存器的定义 */
#define GPIOF_MODER (*(volatile unsigned *)0x40021400) //端口 x 输入输出模式配置寄存器
#define GPIOF_ODR (*(volatile unsigned *)0x40021414) //端口输出数据寄存器
/* 时钟系统相关寄存器的定义 */
#define RCC_AHB1ENR (*(volatile unsigned *)0x40023830) //外设时钟使能寄存器
/* 主函数定义 */
int main(void)
{
RCC_AHB1ENR |= (1<<5); //使能 PORTF 时钟
GPIOF_MODER &= ~(3<<(9*2)); //将配置 PF9 引脚相关位 bit18,bit19 清 0
GPIOF_MODER |= (1<<(9*2)); //将模式寄存器的 bit19、bit18 设置为 01
GPIOF_ODR |= (1<<9); //LED0 灭
while(1)
{
GPIOF_ODR &= ~(1<<9); //LED0 亮
}
}
附:GPIO配置对应的寄存器:
两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH)
两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),
一个32位置位/ 复位寄存器(GPIOx_BSRR),
一个16位复位寄存器(GPIOx_BRR),
一个32位锁定寄存器(GPIOx_LCKR)。
(1)配置寄存器:stm32有7组GPIO,每组GPIO有16个gpio口(0-15),配置每个gpio口需要4位配置寄存器,所以需要64位,即两个32位,0-7为端口配置低寄存器GPIOx_CRL ,8-15为端口配置高寄存器GPIOx_CRH。
(2)数据寄存器
端口输入数据寄存器GPIOx_IDR: 每个IO口一位,一组16位。
端口输出数据寄存器GPIOx_ODR:每个IO口一位,一组16位。
(3)端口位设置/清除寄存器GPIOx_BSRR:
高位BR:每个io对应一位,若设置为1,则设置对应的DDR寄存器位为0,若设置为0,则对对应的DDR位不产生影响。
低位BS:每个io对应一位,若设置为1,则设置对应的DDR寄存器位为1,若设置为0,则对对应的DDR位不产生影响。
BS优先级大于BR。