自学STM32成长记录: 第一篇 跑马灯实验

目地:掌握STM32基本的IO口使用。

内容:通过寄存器方式实现跑马灯效果。

STM32的IO口可以由软件配置为如下8种模式,但必须是以32位字被访问。如:0XFFFF FFFF;

8种模式分别为: 1:输入浮空。浮空悬空,特点电压的不特定性,可能是VCC,也可能是0V,易受干扰,呈高阻态,做

                                2:输入上拉。通过弱上拉将电平拉至VCC,将不确定的信号通过一个电阻嵌位在高电平.

                                3:输入下拉。通过弱下拉将电平拉至GND,将不确定的信号通过一个电阻嵌位在低电平.

                                4:模拟输入。做ADC时使用。

                                5:开漏输出。不输出电压,低电平时接地,如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻

                                      电源电压,这种方式适合在连接的外设电压比单片机电压低的时候。

                                6:推挽输出。单片机引脚可以直接输出高电平电压。低电平时接地。

                                7:开漏复用功能。可以理解为GPIO口被用作第二功能时的配置情况

                                8:推挽复用功能。可以理解为GPIO口被用作第二功能时的配置情况

每个IO口 由STM32 7个寄存器控制: 

1:GPIOx_CRL

2 : GPIOx_CRH

CRL CRH为配置模式的寄存器,低8位与高8位区别以32位字被访问,举例GPIOx_CRL

自学STM32成长记录: 第一篇 跑马灯实验

自学STM32成长记录: 第一篇 跑马灯实验

从上图可以看出,CRL控制每组IO口(A-G)低8位模式,每个IO端口占用CRL的4个位,高两位为CNF,配置输出或者输入的模式,低两位MODE,选择输入或者输出。

                   举例,如果我们要配置PORTA.11为上拉输入

                    GPIOA->CRH&=0XFFFF 0FFF   //清除之前的设置,同时不影响其它位

                    GPIOA->CRH|=0X0000 8000   //第11位 1000  CNF:10:上拉或者下拉输入。MODE:00:输入模式

                    GPIOA->ODR|=1<<11;   //第11位配置为上拉输入

3:GPIOx_ODR  能控制管脚为高电平,也能控制管脚为低电平。管脚对于位写1 gpio 管脚为高电平,写 0 为低电平

4:GPIOx_IDR    只读寄存器,读状态

5:GPIOx_BSRR  只写寄存器:既能控制管脚为高电平,也能控制管脚为低电平。对寄存器高 16bit 写1清除对应的ODRy位为0
                               对寄存器低16bit 写1设置对应的ODRy位为1。写 0 ,无动作

6:GPIOx_BRR    只能改变管脚状态为低电平,对寄存器 管脚对于位写 1 相应管脚会为低电平。写 0 无动作

7:GPIOx_LCKR  端口配置锁定寄存器,具体不了解。

到此,我们可以写出LED灯的初始化

自学STM32成长记录: 第一篇 跑马灯实验

通过上图了解到 LED0,LED1是分别连到PB5 PE5,只能配置这两个IO为推挽输出高即可,注 在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟!

#include"led.h"

void LED_Init(void)

{

RCC->APB2ENR|=1<<3;   //使能PORTB时钟

RCC->APB2ENR|=1<<6;  //使能PORTE时钟

GPIOB->CRL&=0XFF0F FFFF ;

GPIOB->CRL|=0X0030 0000;

GPIOB->ODR|=1<<5;

GPIOE->CRL&=0XFF0F FFFF;

GPIOE->CRL|=0X0030 0000;

GPIOE->ODR|=1<<5;

}

APB2ENR是APB2总线上的外设时钟使能寄存器,使能哪组IO口需将其对应BIT位写1:

自学STM32成长记录: 第一篇 跑马灯实验

下面写LED.H

#ifndef __LED_H

#define __LED_H

#include"sys.h"

#define LED0 PBout(5)

#define LED1 PEout(5)

void LED_Init();

#endif

这个头文件,有两个需要注意的地方,一是位带操作,二是预编译

#define LED0 PBout(5) 关于这一句,要复习一下位带操作

位带操作:顾名思义,就是开发人员可以单独对CPU寄存器的某一位进行读写操作

我们在以前学习51单片机时,对某一位IO位进行操作时 sbit LED=PX^n;     LED=1,然而STM32不允许这样操作,那么为了方便能像51单片机那样对某一位IO口进行操作,就引入了位带这个概念。

在CM3中有两个区域可以进行位带操作,其一是SRAM中最低1MB范围,其二是片内外设区最低1MB范围,这两个区中的地址除了可以像普通RAM一样使用外,它们都有自己的位带别外区,

位带别名区是把每个比特膨胀位32位的字,当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

自学STM32成长记录: 第一篇 跑马灯实验

自学STM32成长记录: 第一篇 跑马灯实验

自学STM32成长记录: 第一篇 跑马灯实验

自学STM32成长记录: 第一篇 跑马灯实验

自学STM32成长记录: 第一篇 跑马灯实验

公式:AliasAddr=0x2200 0000+(A-0x2000 0000)*32+n*4

例1:位带区0x2000 0004.1,求出他的位带别名区地址

AliasAddr=0x2200 0000+(0x2000 0004-0x2000 0000)*32+1*4

                =0x2200 0000+80+4

                =0x2200 0084

和上面图表中一致

再来看看LED.H头文件中对LED0的定义 #define LED0 PAout(8)    // 用LED0代替PA8

看一下对PAout(n)的定义:#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 

                                              {    #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 

                                                   #define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)

                                                   #define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)

                                                   #define PERIPH_BASE           ((uint32_t)0x40000000)

                                             }  从上面的定义可以看出,GPIOA_ODR_Addr的地址 

                          =0x4000 0000+0x10000+0x0800+12=0x4001080C 

这个GPIOA_ODR_Addr位带区的地址为0x4001080C

                                           #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

把这句话层层分解:1: 定义 BIT_ADDR(addr,bitnum)  代替 MEM_ADDR(BITBAND(addr,bitnum))

                                            MEM_ADDR(BITBAND(addr,bitnum))

可以分解为两条语句 1:   MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 

  *((volatile unsigned long  *)(addr)) 这句话的意思是:是把这个地址常量addr强制转化为volatile unsigned long  类型指针,

再取地址addr里的数据。 {这和我们之前学的指针一样,int a =10,int *p, p=&a,如果要对指针变量P所指的地址赋值,或者要读取指针变量P所指地址中的数据时  int b=*p, *p=20,或者是 int b=*(&a), *(&a)=20}.

                                     2: BITBAND(addr,bitnum)  ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 

前面已经讲过  支持位带操作的两个区域:1:0X2000 0000--0X200F FFFF(SRAM最低1MB)

                                                                           2:  0X4000 0000--0X400F FFFF(片上外设区最低1MB)

1:  AliasAddr = 0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32+n*4  

2: AliasAddr = 0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32+n*4   

 ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 

1)addr & 0xF0000000 意思是取出最高的4位,其实就是用于区别SRAM(0x20000000)还是片上外设(0x40000000)

2)++0x2000000 对于SRAM位带区则得到 0x22000000,对于片上外设位带区则得到0x42000000 

3)addr &0xFFFFF 就是addr&0x000F FFF ,屏蔽高12位,对于SRAM就是0X200F FFFF 对片上外设是0X400F FFFF

4)(addr &0xFFFFF)<<5) 相当于(A-0x40000000)*32  1<<5=1*2^5; 

5)   (bitnum<<2)  相当于 n*4   1<<2=1*2^2;