单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础

上篇文章 单片机入门学习四 STM32单片机学习一 跑马灯程序和创建工程 仅介绍了入门程序及其编译运行过程,下面开始对stm32的一些基础知识做一个记录。

1、stm32f103zet6(上篇问题3 stm32f103是什么)

上篇的跑马灯程序采用的开发板使用的mcu是stm32f103zet6,根据stm32系列产品命名规则,我们知道这款mcu为基于ARM®的32位微控制器、是增强型的、拥有144个引脚、512K字节的闪存存储器、采用LQFP封装、工作温度范围在-40℃~85℃。这款芯片包含的资源有 64KB SRAM(Static Random Access Memory静态随机存取存储器)、512KB FLASH、2个基本定时器、4个通用定时器、2个高级定时器、2个DMA(Direct Memory Access直接内存存取)控制器(公12个通道)、3个SPI(Serial Peripheral Interface串行外设接口)、2个IIC(Inter-Integrated Circuit集成电路总线)、5个串口、1个USB、1个CAN(控制器局域网络Controller Area Network)、3个12位ADC(Analog-to-Digital Converter模/数转换)、1个12位DAC(Digital-to-Analog Converter数/模转换)、1个SDIO(Secure Digital Input and Output安全数字输入输出卡)接口、1个FSMC(Flexible Static Memory Controller可变静态存储控制器)接口以及112个GPIO(General Purpose Input Output 通用输入/输出)口。
下图是其系统结构图
单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第1张图片
虽然上面已经将stm32f103zet6芯片系统构成已经介绍,但是下面的介绍是针对上图给的一个更直观的概况。
stm32主要由四个驱动单元和四个被动单元构成。

  • 四个驱动单元
    • Cortex™-M3内核Dcode总线(D-bus)
    • 系统总线(S-bus)
    • 通用DMA1
    • 通用DMA2
  • 四个被动单元
    • 内部SRAM
    • 内部闪存存储器
    • FSMC
    • AHB到APB的桥(AHB2APBx) ,它连接所有的APB设备

ICode总线:该总线将Cortex™-M3内核的指令总线与闪存接口相连接。指令预取在此总线上完成。
DCode总线:该总线将Cortex™-M3内核的DCode总线与闪存存储器的数据接口相连接(常量加载和调试访问)
系统总线:此总线连接Cortex™-M3内核的系统总线(外设总线)到总线矩阵,总线矩阵协调着内核和DMA间的访问
DMA总线:此总线将DMA的AHB主控接口与总线矩阵相联,总线矩阵协调着CPU的DCode和DMA到 SRAM、闪存和外设的访问。
总线矩阵:协调内核系统总线和DMA主控总线之间的访问仲裁,仲裁利用轮换算法。在互联型产品中,总线矩阵包含5个驱动部件(CPU的DCode、系统总线、以太网DMA、DMA1总线和DMA2总线)和3个从部件(闪存存储器接口(FLITF)、SRAM和AHB2APB桥)。在其它产品中总线矩阵包含4个驱动部件(CPU的DCode、系统总线、DMA1总线和DMA2总线)和4个被动部件(闪存存储器接口(FLITF)、SRAM、FSMC和AHB2APB桥)。AHB外设通过总线矩阵与系统总线相连,允许DMA访问。
AHB/APB桥(APB) :两个AHB/APB桥在AHB和2个APB总线间提供同步连接。APB1操作速度限于36MHz,APB2操作于全速(最高72MHz)。 在每一次复位以后,所有除SRAM和FLITF以外的外设都被关闭,在使用一个外设之前,必须设置寄存器RCC_AHBENR来打开该外设的时钟。 注意: 当对APB寄存器进行8位或者16位访问时,该访问会被自动转换成32位的访问:桥会自动将8位或者32位的数据扩展以配合32位的向量。

注: ROM、RAM、DRAM、SRAM和FLASH的区别
1、ROM和RAM指的都是半导体存储器,ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是在掉电之后就丢失数据,典型的RAM就是计算机的内存。
2、RAM有两大类:
1)静态RAM(SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。
2)动态RAM(DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。
3、FLASH,又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据(NVRAM的优势),U盘和MP3里用的就是这种存储器。在过去的20年里,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,然而近年来Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,它用作存储Bootloader以及操作系统或者程序代码,或者直接当硬盘使用(U盘)。

AHB (Advanced High-performance Bus高级高性能总线) 主要是针对高效率、高频宽及快速系统模块所设计的总线,它可以连接如微处理器、芯片上或芯片外的内存模块和DMA等高效率模块。
APB (Advanced Peripheral Bus高级外围总线) 主要用在低速且低功率的外围,可针对外围设备作功率消耗及复杂接口的最佳化。APB在AHB和低带宽的外围设备之间提供了通信的桥梁,所以APB是AHB的二级拓展总线。

2、GPIO

1)概念

GPIO英文为General Purpose Input Output,称为通用输入/输出
stm32f103zet6包含112个GPIO(stm32f103zet6有144个引脚,其中有11个VSS、11个VDD、还有10个其他用途的引脚分别是VBAT、OSC_IN、OSC_OUT、NRST、Vref+、Vref-、VDDA、VSSA、BOOT0、NC),112个GPIO分别是PA[0..15]~PG[0..15],stm32f103zet6引脚图如下:
单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第2张图片

2)如何使用

stm32单片机是操作寄存器来控制IO口的,首先我们应该知道GPIO的寄存器地址为多少,下表中记录了STM32F10ZET6中内置外设的起始地址。
单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第3张图片
从图中我们可以看到GPIO起始地址是0x40010800,在系统库函数stm32f10x.h中为我们定义了宏定义,方便我们操作寄存器控制外设,stm32f10x.h中的GPIOx定义如下

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)

#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE            (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE            (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE            (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE            (APB2PERIPH_BASE + 0x2000)

#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)

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;

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;

typedef enum
{ 
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

typedef struct
{
  uint16_t GPIO_Pin;             /*!< Specifies the GPIO pins to be configured.
                                      This parameter can be any value of @ref GPIO_pins_define */

  GPIOSpeed_TypeDef GPIO_Speed;  /*!< Specifies the speed for the selected pins.
                                      This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOMode_TypeDef GPIO_Mode;    /*!< Specifies the operating mode for the selected pins.
                                      This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;

#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_5                 ((uint16_t)0x0020)  /*!< Pin 5 selected */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< Pin 6 selected */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< Pin 7 selected */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< Pin 10 selected */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< Pin 11 selected */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< Pin 12 selected */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< Pin 13 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 */

上述代码定义了8个GPIO的访问地址GPIOA~GPIOG,同时定义了每个GPIO 16个引脚的选择码,同时还可以看到定义了几个GPIO相关的结构体,分别是GPIO_TypeDef、GPIO_InitTypeDef、GPIOMode_TypeDef、GPIOSpeed_TypeDef,下面会对每个结构体做一下更细的介绍。

① GPIO寄存器介绍(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;

从库给出的结构体结合芯片手册,我们知道GPIO端口有:

  • 2个32位 配置寄存器 CRL、CRH
    CRL控制每个IO口低8位引脚的模式(例如控制GPIOA的0~7引脚),CRH控制每个IO口高8位引脚的模式(例如控制GPIOA8~15引脚),下图是对CRL/CRH的截图说明
    单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第4张图片
    CRL:结构体偏移地址0x00、(默认)复位值0x44444444,即默认为浮空输入模式
    CRH:结构体偏移地址0x04、(默认)复位值0x44444444,即默认为浮空输入模式
    注:IO口的模式与硬件电路有关,在此不细说了(本人目前对这些模式具体应用场景也没有很清晰的感念,即在此不详述了)

  • 2个32位 数据寄存器 IDR、ODR
    IDR:端口输入寄存器,只用了低16位。该寄存器为只读寄存器,并且只能以16位的形式读出。该寄存器描述如下图:
    单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第5张图片
    ODR:端口输出数据寄存器,只用了低16位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前IO口的输出状态。而向该寄存器写数据,则可以控制某个IO口的输出电平。
    单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第6张图片

  • 1个32位 置位/复位寄存器 BSRR
    可以用来设置GPIO端口的输出位是1还是0.
    单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第7张图片
    该寄存器往相应的位写0是无影响的,所以我们要设置某些位,不用管其他位的值。例如设置GPIOA的GPIO_Pin_1引脚值为1,即往低16位写1即可。

    GIPOA->BSRR=1<<1;

    设置GPIOA的GPIO_Pin_1引脚值为0,即往寄存器高16位写1即可,而不是往第十六为写0。

    GIPOA->BSRR=1<<(16+1);
  • 1个16位 复位寄存器 BRR
    该寄存器是端口位清除寄存器,其作用跟BSRR的高16位雷同。
    单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第8张图片
  • 1个32位 锁定寄存器 LCKR
    当执行正确的写序列设置了位16(LCKK)时,该寄存器用来锁定端口位的配置。位[15:0]用于锁定GPIO端口的配置。在规定的写入操作期间,不能改变LCKP[15:0]。当对相应的端口位执行了LOCK序列后,在下次系统复位之前将不能再更改端口位的配置。 每个锁定位锁定控制寄存器(CRL, CRH)中相应的4个位。
    单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第9张图片
②库函数编程(GPIO_InitTypeDef、GPIOMode_TypeDef、GPIOSpeed_TypeDef)

上面①中给我们详细介绍了GPIO寄存器的相应的功能,在了解之后我们即可直接操作寄存器写自己的IO控制程序了,如跑马灯的程序我们就可按如下代码编写:

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

GPIOB->CRL&=0XFF0FFFFF; 
GPIOB->CRL|=0X00300000;//PB.5 推挽输出       
GPIOB->ODR|=1<<5;      //PB.5 输出高

GPIOE->CRL&=0XFF0FFFFF;
GPIOE->CRL|=0X00300000;//PE.5推挽输出
GPIOE->ODR|=1<<5;      //PE.5输出高 
while(1)
{
     GPIOB->BRR=GPIO_Pin_5;//LED0亮
     GPIOE->BSRR=GPIO_Pin_5;//LED1灭
     delay_ms(300);
     GPIOB->BSRR=GPIO_Pin_5;//LED0灭
     GPIOE->BRR=GPIO_Pin_5;//LED1亮
     delay_ms(300);
 }

stm32有上百个寄存器,每次编程都需要翻手册在编程可想象这种场景应该很恐怖,故官方为我们提供了相应的库函数,这些函数将寄存器的操作封装,即可靠又方便使用。
GPIO官方为我们提供了如下几个库函数

/** @defgroup GPIO_Exported_Functions**/
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

GPIO相应的库函数很多,可以查看手册或示例了解使用方法,跑马灯程序使用到的函数有:

//用来设置GPIO端口的模式
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
//用来给GPIO端口设置1
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
//用来给GPIO端口设置0
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

跑马灯库函数程序

GPIO_InitTypeDef  GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);   //使能PB,PE端口时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;                //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;         //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);                   //根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5);                      //PB.5 输出高

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;                //LED1-->PE.5 端口配置, 推挽输出
GPIO_Init(GPIOE, &GPIO_InitStructure);                   //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOE,GPIO_Pin_5);                          //PE.5 输出高

while(1)
{
    GPIO_ResetBits(GPIOB,GPIO_Pin_5);  //LED0对应引脚GPIOB.5拉低,亮  等同LED0=0;
    GPIO_SetBits(GPIOE,GPIO_Pin_5);   //LED1对应引脚GPIOE.5拉高,灭 等同LED1=1;
    delay_ms(300);             //延时300ms
    GPIO_SetBits(GPIOB,GPIO_Pin_5);    //LED0对应引脚GPIOB.5拉高,灭  等同LED0=1;
    GPIO_ResetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉低,亮 等同LED1=0;
    delay_ms(300);                     //延时300ms
}

3、板子原理图确定GPIO端口

看了跑马灯的代码估计很多人会发出疑问:怎么知道操作的是GPIOB的GPIO_Pin_5去控制一个灯,操作GPIOE的GPIO_Pin_5去控制另外一个灯。
针对这个疑问,解决方法是 我们必须看所使用的板子的原理图了,我使用的板子关于LED灯的原理图如下:
单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第10张图片
控制LED灯的stm32芯片部分原理图
单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第11张图片
从上面两个图可以看出,我所使用的板子已将LED与stm32f103芯片连接好了。LED0接PB5,所以我们使用的是GPIOB、GPIO_Pin_5这两个宏定义,代表PB5。
LED1接PE5,所以我们使用的是GPIOE、GPIO_Pin_5这两个宏定义,代表PE5。

4、时钟

从跑马灯程序中我们看到以下代码:

/** 
 **使用寄存器初始化时钟** 
RCC->APB2ENR|=1<<3;    //使能PORTB时钟       
RCC->APB2ENR|=1<<6;    //使能PORTE时钟
**/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);   //使能PB,PE端口时钟

之所以在程序开头要开启时钟,是因为任何MCU的任何外设都需要有时钟 ,STM32为了让用户更好地掌握功耗,对每个外设的时钟都设置了开关,所以我们在使用外设(GPIO)前需要开启时钟(51单片机不用配置IO时钟,只是因为默认使用同一个时钟)
下图是RCC_APB2ENR外设时钟使能寄存器图
单片机入门学习五 STM32单片机学习二 跑马灯程序衍生出的stm32编程基础_第12张图片

你可能感兴趣的:(楼宇自控,单片机,单片机入门学习)