新建工程
寄存器方式
要命啊,一看名字我就不想试。寄存器新建不得麻烦死。
哎算了为了学习原理,干了。
我们尝试自己写一个寄存器的库函数来引用。
首先我们需要引用 st 官方启动文件 stmf4xx.s,具体用途后面章节再展开讲解。然后我们自己新建一个 stm32f4xx.h 文件来映射寄存器。不过只是把这个文件包含进项目,编译会报错:
.\Objects\led_reg.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f40xx.o).
进入启动文件后,可以看到这么一个函数:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
import 的作用相当于 extern,所以没有找到这个函数的定义,需要我们自己去定义。这就是为什么简单引入了启动文件会报错。
而 __main 是当我们定义了 main() 函数后,编译器会自动链接一些c语言库定义好的函数,用于初始化堆栈并且调用我们的 main().
注意,如果想要生成 __main 函数,必须勾选下面这一项。
野火你讲的是真好啊。我之前草草学了学 stm32 单片机用法,比赛的时候自己想移植代码,改了启动文件也不好使,就是报错。原来是这个原因。
那么我们只需要定义这么一个函数,哪怕内容是空都无所谓。
最终我们定义的初步项目框架如下:
stm32f4xx.h:内容为空,有这么个东西就行。
main.c:
#include "stm32f4xx.h"
int main(){
while(1){
}
}
void SystemInit(){
}
好了,这个程序可以烧录到板子上的。烧录成功之后没有任何反应(因为本来程序也没做什么哈哈),但是这就是一个大进步了。
点灯——51单片机版
51单片机版就是引用 reg51.h 头文件,在其中声明了各个引脚的地址。我们只需要直接给引脚赋值即可。
调用代码:
#include "reg51.h"
#ifdef 0
void main(){
PA0=0xFE;
while(1){}
}
#endif
接下来我们需要定义 LED 灯的寄存器位置。阅读原理图如下:
大致可以看出,板子上的这个 RGB LED 通过三个引脚来控制 RGB 亮度。输出低电平则导通点亮。
具体输出方式是通过 ODR 进行输出。查找 stm32f4xx 中文参考手册可见:
那么我们就要给 0x4002 1400 +14 的地址赋值,让 1<<6 1<<7 1<<8 的位分别赋值为低电平.
int main(){
*(unsigned int *)(0x40021400+14)&=~(1<<6);
while(1){
}
}
然而这样也不亮。亮就怪了,stm32 寄存器是需要先做初始化配置的。
点灯——stm32 版
首先我们要设置 GPIO 模式。
想点灯 输出高低电平,是 01 通用输出模式。
*(unsigned int *)(0x40021400+0)&=~(3<<(6*2));
*(unsigned int *)(0x40021400+0)|=(1<<(6*2));
意思是先把 PF6 模式位置为00,然后赋值为01通用输出。
配置完模式之后,还需要配置时钟,stm32 每个外设都需要配置时钟。
前面提到过 GPIO 是在 AHB1.
全部代码如下:
#include "stm32f4xx.h"
int main(){
//RCC
*(unsigned int *)(0x40023800+0x30)|=(1<<5);
//Mode
*(unsigned int *)(0x40021400+0)&=~(3<<(6*2));
*(unsigned int *)(0x40021400+0)|=(1<<(6*2));
*(unsigned int *)(0x40021400+0x14)&=~(1<<6);
while(1){
}
}
void SystemInit(){
}
接下来,我们把这几个地址值提取出来,宏定义映射寄存器。
//stm32f4xx.h
/* 用来存放寄存器映射相关的代码 */
#define RCC_AHB1_ENR *(unsigned int *)(0x40023800+0x30)
#define GPIOF_MODER *(unsigned int *)(0x40021400+0)
#define GPIOF_ODR *(unsigned int *)(0x40021400+0x14)
//main.c
#include "stm32f4xx.h"
int main(){
//RCC
RCC_AHB1_ENR|=(1<<5);
//Mode
GPIOF_MODER&=~(3<<(6*2));
GPIOF_MODER|=(1<<(6*2));
GPIOF_ODR&=~(1<<6);
while(1){
}
}
void SystemInit(){
}
点灯——流水灯闪烁
利用软件延时实现 RGB 流水灯闪烁。很简单,前面已经看了3个 LED 通道 PF678 了。
#include "stm32f4xx.h"
void delay_ms(int time);
int main(){
//RCC
RCC_AHB1_ENR|=(1<<5);
//Mode
GPIOF_MODER&=~(3<<(6*2));
GPIOF_MODER|=(1<<(6*2));
GPIOF_MODER&=~(3<<(7*2));
GPIOF_MODER|=(1<<(7*2));
GPIOF_MODER&=~(3<<(8*2));
GPIOF_MODER|=(1<<(8*2));
while(1){
GPIOF_ODR|=(7<<6);
GPIOF_ODR&=~(1<<6);
delay_ms(1000);
GPIOF_ODR|=(7<<6);
GPIOF_ODR&=~(1<<7);
delay_ms(1000);
GPIOF_ODR|=(7<<6);
GPIOF_ODR&=~(1<<8);
delay_ms(1000);
}
}
void SystemInit(){
}
//毫秒级的延时
void delay_ms(int time)
{
int i=0;
while(time--)
{
i=4000;
while(i--) ;
}
}
点灯——GPIO 具体功能框图对应
GPIO:通用输入输出引脚。我们可以通过编程来输出或者读取数据。大部分 GPIO 是已经连接、定义好了一些功能(比如上面尝试过的 PF6 LED),有的引脚有多个功能支持重新映射。
STM32 GPIO 除了 adc 是 3.3v,其他 GPIO 都是 5v 容忍。
GPIO 框图(重点)如下:
先从输出开始看。最右侧的 IO 引脚是连接在芯片周围一圈的144个引脚之一。除了 IO 引脚,此图中其他所有部分都是封装在芯片内部我们看不到的。
往左有两个保护二极管。当电压大于 5V,电流会往上 VDD_FT 走。当电压为负电压,电流会由 VSS 往 IO 引脚走。
上下拉电阻:比武外接一个低电平工作的设备,但是我们不希望一上电外设就工作,可以设置上拉电阻,稳定一段时间。
GPIO 输出的数据来源:复位寄存器 BSRR,或者 ODR 设置(图中的3下路部分)。复位寄存器高16位复位(写1置0)低16位置位(写1置1),置位优先级更高。
配置 GPIO 模式(输入/输出,选择哪一路)通过前面用过的 MODER 配置。
输出模式(图中输出控制部分)配置端口输出类型寄存器 OTYPER,比如推挽输出,开漏输出。
推挽输出:有直接驱动能力,输出0就是低电平,输出1就输出可以工作的高电平。原理是采用了一个放大的电路?
输入(INT)为高电平时,反向后 PMOS 导通,输出高电平。输入为低电平时,反向后 NMOS 导通,输出低电平。我们可以用一个小电流去驱动出来一个大电流。
开漏输出:自己本身没有输出高电平的手段。低电平可以接地,高电平没有 PMOS 管,是浮空状态。需要外接一个电阻。
stm32 输出 5V 电压的方法就是开漏输出外接电阻。通过接两个三极管的方式反向。
框图中的模拟部分输入输出则不用配置这些模式信息,直接由外设接到保护二极管再接到输出引脚。
框图中的输入部分经过保护电压后,还需要施密特触发器调整一下。比如原来电压的数值并非精确的0或 3.3V,施密特触发器将高于 1.8V 的全部视作1,低于的全部视作0后输入芯片。模拟部分则不需要经过施密特触发器。
因此配置 GPIO 输出的步骤如下:
- GPIO 功能,通用输出、复用功能、模拟输入等 MODER;
- 输出推挽 or 开漏 OTYPER;
- 输出速度 OSPEEDR;
- 上下拉电阻是否需要开启 PUPDR;
- 具体输出内容 BSRR or ODR.
输入部分后面输入实验介绍~
按整个流程重新串一遍代码,如下:(其实和前面差不多,就是重新按照流程串了一遍)
/* 用来存放寄存器映射相关的代码 */
#define RCC_BASE (unsigned int *) 0x40023800
#define GPIOF_BASE (unsigned int *) 0x40021400
#define RCC_AHB1ENR *(RCC_BASE+0x30)
#define GPIOF_MODER *(GPIOF_BASE+0x00)
#define GPIOF_OSPEEDR *(GPIOF_BASE+0x08)
#define GPIOF_PUPDR *(GPIOF_BASE+0x0C)
#define GPIOF_ODR *(GPIOF_BASE+0x14)
#define GPIOF_BSRR *(GPIOF_BASE+0x18)
//main.c
#include "stm32f4xx.h"
int main()
{
RCC_AHB1ENR |= (1<<5);
GPIOF_MODER &= ~(3<<(6*2));
GPIOF_MODER |= (1<<(6*2));
while (1)
{
}
}
void SystemInit()
{
}
烧录前记得勾选:use MicroLib.