在学习STM32之前,首先应该学习一下51单片机。51单片机作为一款入门级的经典单片机,是目前应用最广泛的8位单片机之一。但随着市场产品竞争的加剧,51单片机现有的资源就显得力不从心了。
ARM公司首先推出了基于ARMv7架构对的32位Cortex-M3微型控内核,紧随其后ST公司推出了基于Cortex-M3内核的STM32单片机。因此,STM32的芯片由两部分组成:Cortex-M3微型控内核+ST公司开发的片上外设。
Cortex-M3内核相当于CPU,CPU和片上外设是通过4条总线来完成的:
1、ICode总线中的I表示Instruction,即指令。我们写好的程序编译后就变成一条条指令,都存储在Flash中,ICode总线就是内核读取指令的专用通道。
2、Dcode总线中的D表示Data,即数据。数据分为常量和变量,常量存储在内部Flash中,变量存储在内部SRAM中。因为DMA也可以访问数据,为了便面访问冲突,在取数的时候需要经过一个总线矩阵仲裁。
3、System总线主要用来访问外设的寄存器。
4、DMA总线即可以传输内部Flash中的数据、也可以传输SRAM和外设的寄存器中的数据。因为DCode也可以访问数据,为了便面访问冲突,在取数的时候需要经过一个总线矩阵仲裁。
5、片上外设(如GPIO、USART、IIC、SPI等)是挂在由AHB总线延伸出来的APB1和APB2总线上
存储器本身不具有地址信息,它的地址是由厂家或者用户分配的,给存储器分配地址的过程叫做存储器映射,如果给存储器再分配一个地址就叫存储器重映射。
STM32的内部Flash、SRAM、AHB桥和APB桥,片上外设等共同排列在一个4GB的地址空间内。这个4GB空间被分为个块,分别为:Block0、Block1、Block2、Block3、Block4、Block5、Block6、Block7,其中Block0被设计成内部Flash、Block1位SRAM、Block2被设计成片上外设。
序号 | 用途 | 地址范围 |
Block0 | Code | 0x0000 0000~0x1FFF FFFF |
Block1 | SRAM | 0x2000 0000~0x3FFF FFFF |
Block2 | 片上外设 | 0x4000 0000~0x5FFF FFFF |
Block3 | FSMC的bank1~bank2 | 0x6000 0000~0x7FFF FFFF |
Block4 | FSMC的bank3~bank4 | 0x8000 0000~0x9FFF FFFF |
Block5 | FSMC寄存器 | 0xA000 0000~0xBFFF FFFF |
Block6 | 没有使用 | 0xC000 0000~0xDFFF FFFF |
Block7 | Cortex-M3内部外设 | 0xE000 0000~0xFFFF FFFF |
Block2中设计的是片上外设,其中每4个字节(32位)为一个单元,每一个单元对应不同的功能,控制这些单元就可以驱动片上外设工作。我们可以利用指针来访问这些单元,但是STM32中有很多单元,利用这种方式来访问,不好记忆且容易出错。根据每一个单元功能的不同,以功能为名给这个内存单元去一个别名,这个别名就是寄存器。
给已经分配好地址的、有特定功能的内存单元取别名的过程就叫寄存器映射。
寄存器的封装示例
#define FLASH_BASE ((uint32_t)0x08000000) //FLASH
#define SRAM_BASE ((uint32_t)0x20000000) //SRAM
#define PERIPH_BASE ((uint32_t)0x40000000) //片上外设基地址
#define APB1PERIPH_BASE PERIPH_BASE //APB1总线基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)//APB2总线基地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)//AHB总线基地址
#define TIM2_BASE (APB1PERIPH_BASE + 0x0000) //定时器TIM2基地址
#define TIM3_BASE (APB1PERIPH_BASE + 0x0400)
#define TIM4_BASE (APB1PERIPH_BASE + 0x0800)
#define TIM5_BASE (APB1PERIPH_BASE + 0x0C00)
#define TIM6_BASE (APB1PERIPH_BASE + 0x1000)
#define TIM7_BASE (APB1PERIPH_BASE + 0x1400)
#define TIM12_BASE (APB1PERIPH_BASE + 0x1800)
#define TIM13_BASE (APB1PERIPH_BASE + 0x1C00)
#define TIM14_BASE (APB1PERIPH_BASE + 0x2000)
#define RTC_BASE (APB1PERIPH_BASE + 0x2800)
#define WWDG_BASE (APB1PERIPH_BASE + 0x2C00)
#define IWDG_BASE (APB1PERIPH_BASE + 0x3000)
#define SPI2_BASE (APB1PERIPH_BASE + 0x3800)
#define SPI3_BASE (APB1PERIPH_BASE + 0x3C00)
#define USART2_BASE (APB1PERIPH_BASE + 0x4400)
#define USART3_BASE (APB1PERIPH_BASE + 0x4800)
#define UART4_BASE (APB1PERIPH_BASE + 0x4C00)
#define UART5_BASE (APB1PERIPH_BASE + 0x5000)
#define I2C1_BASE (APB1PERIPH_BASE + 0x5400)
#define I2C2_BASE (APB1PERIPH_BASE + 0x5800)
#define CAN1_BASE (APB1PERIPH_BASE + 0x6400)
#define CAN2_BASE (APB1PERIPH_BASE + 0x6800)
#define BKP_BASE (APB1PERIPH_BASE + 0x6C00)
#define PWR_BASE (APB1PERIPH_BASE + 0x7000)
#define DAC_BASE (APB1PERIPH_BASE + 0x7400)
#define CEC_BASE (APB1PERIPH_BASE + 0x7800)
#define AFIO_BASE (APB2PERIPH_BASE + 0x0000)
#define EXTI_BASE (APB2PERIPH_BASE + 0x0400)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) //GPIOA基地址
#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 ADC1_BASE (APB2PERIPH_BASE + 0x2400)
#define ADC2_BASE (APB2PERIPH_BASE + 0x2800)
#define TIM1_BASE (APB2PERIPH_BASE + 0x2C00)
#define SPI1_BASE (APB2PERIPH_BASE + 0x3000)
#define TIM8_BASE (APB2PERIPH_BASE + 0x3400)
#define USART1_BASE (APB2PERIPH_BASE + 0x3800)
#define ADC3_BASE (APB2PERIPH_BASE + 0x3C00)
#define TIM15_BASE (APB2PERIPH_BASE + 0x4000)
#define TIM16_BASE (APB2PERIPH_BASE + 0x4400)
#define TIM17_BASE (APB2PERIPH_BASE + 0x4800)
#define TIM9_BASE (APB2PERIPH_BASE + 0x4C00)
#define TIM10_BASE (APB2PERIPH_BASE + 0x5000)
#define TIM11_BASE (APB2PERIPH_BASE + 0x5400)
#define SDIO_BASE (PERIPH_BASE + 0x18000)
#define DMA1_BASE (AHBPERIPH_BASE + 0x0000)
#define DMA1_Channel1_BASE (AHBPERIPH_BASE + 0x0008)
#define DMA1_Channel2_BASE (AHBPERIPH_BASE + 0x001C)
#define DMA1_Channel3_BASE (AHBPERIPH_BASE + 0x0030)
#define DMA1_Channel4_BASE (AHBPERIPH_BASE + 0x0044)
#define DMA1_Channel5_BASE (AHBPERIPH_BASE + 0x0058)
#define DMA1_Channel6_BASE (AHBPERIPH_BASE + 0x006C)
#define DMA1_Channel7_BASE (AHBPERIPH_BASE + 0x0080)
#define DMA2_BASE (AHBPERIPH_BASE + 0x0400)
#define DMA2_Channel1_BASE (AHBPERIPH_BASE + 0x0408)
#define DMA2_Channel2_BASE (AHBPERIPH_BASE + 0x041C)
#define DMA2_Channel3_BASE (AHBPERIPH_BASE + 0x0430)
#define DMA2_Channel4_BASE (AHBPERIPH_BASE + 0x0444)
#define DMA2_Channel5_BASE (AHBPERIPH_BASE + 0x0458)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define CRC_BASE (AHBPERIPH_BASE + 0x3000)
#define FLASH_R_BASE (AHBPERIPH_BASE + 0x2000) /*!< Flash registers base address */
#define OB_BASE ((uint32_t)0x1FFFF800) /*!< Flash Option Bytes base address */
#define ETH_BASE (AHBPERIPH_BASE + 0x8000)
#define ETH_MAC_BASE (ETH_BASE)
#define ETH_MMC_BASE (ETH_BASE + 0x0100)
#define ETH_PTP_BASE (ETH_BASE + 0x0700)
#define ETH_DMA_BASE (ETH_BASE + 0x1000)
#define FSMC_Bank1_R_BASE (FSMC_R_BASE + 0x0000) /*!< FSMC Bank1 registers base address */
#define FSMC_Bank1E_R_BASE (FSMC_R_BASE + 0x0104) /*!< FSMC Bank1E registers base address */
#define FSMC_Bank2_R_BASE (FSMC_R_BASE + 0x0060) /*!< FSMC Bank2 registers base address */
#define FSMC_Bank3_R_BASE (FSMC_R_BASE + 0x0080) /*!< FSMC Bank3 registers base address */
#define FSMC_Bank4_R_BASE (FSMC_R_BASE + 0x00A0) /*!< FSMC Bank4 registers base address */
#define DBGMCU_BASE ((uint32_t)0xE0042000) /*!< Debug MCU registers base address */
了解了存储器和寄存器映射的相关知识,我们就可以进一步了解其编程方法。
51单片机编程是经典的位操作,例如通过关键字sbit实现位定义,通过对一个比特位进行读和写,来控制I/O点的电平或者完成其他操作(定时、串口通信等)。其编程实质就是利用位操作,控制51单片机上的片上寄存器。
例如,点亮一个LED。
/*--------------------------------------------------------------------------
点亮一个LED
--------------------------------------------------------------------------*/
#include //51单片机头文件
#define uchar unsigned char
#define uint unsigned int
sbit LED1=P2^0; //P2^0即为51单片机的寄存器,其封装定义可在reg52.h中查看
void main(void)
{
LED1=0;
while(1);
}
/*--------------------------------------------------------------------------
头文件 REG52.H ,内部有对51单片机的寄存器的封装
--------------------------------------------------------------------------*/
#ifndef __REG52_H__
#define __REG52_H__
/* BYTE Registers */
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;
sfr PSW = 0xD0;
sfr ACC = 0xE0;
sfr B = 0xF0;
sfr SP = 0x81;
sfr DPL = 0x82;
sfr DPH = 0x83;
sfr PCON = 0x87;
sfr TCON = 0x88;
sfr TMOD = 0x89;
sfr TL0 = 0x8A;
sfr TL1 = 0x8B;
sfr TH0 = 0x8C;
sfr TH1 = 0x8D;
sfr IE = 0xA8;
sfr IP = 0xB8;
sfr SCON = 0x98;
sfr SBUF = 0x99;
/* 8052 Extensions */
sfr T2CON = 0xC8;
sfr RCAP2L = 0xCA;
sfr RCAP2H = 0xCB;
sfr TL2 = 0xCC;
sfr TH2 = 0xCD;
/* BIT Registers */
/* PSW */
sbit CY = PSW^7;
sbit AC = PSW^6;
sbit F0 = PSW^5;
sbit RS1 = PSW^4;
sbit RS0 = PSW^3;
sbit OV = PSW^2;
sbit P = PSW^0; //8052 only
/* TCON */
sbit TF1 = TCON^7;
sbit TR1 = TCON^6;
sbit TF0 = TCON^5;
sbit TR0 = TCON^4;
sbit IE1 = TCON^3;
sbit IT1 = TCON^2;
sbit IE0 = TCON^1;
sbit IT0 = TCON^0;
/* IE */
sbit EA = IE^7;
sbit ET2 = IE^5; //8052 only
sbit ES = IE^4;
sbit ET1 = IE^3;
sbit EX1 = IE^2;
sbit ET0 = IE^1;
sbit EX0 = IE^0;
/* IP */
sbit PT2 = IP^5;
sbit PS = IP^4;
sbit PT1 = IP^3;
sbit PX1 = IP^2;
sbit PT0 = IP^1;
sbit PX0 = IP^0;
/* P3 */
sbit RD = P3^7;
sbit WR = P3^6;
sbit T1 = P3^5;
sbit T0 = P3^4;
sbit INT1 = P3^3;
sbit INT0 = P3^2;
sbit TXD = P3^1;
sbit RXD = P3^0;
/* SCON */
sbit SM0 = SCON^7;
sbit SM1 = SCON^6;
sbit SM2 = SCON^5;
sbit REN = SCON^4;
sbit TB8 = SCON^3;
sbit RB8 = SCON^2;
sbit TI = SCON^1;
sbit RI = SCON^0;
/* P1 */
sbit T2EX = P1^1; // 8052 only
sbit T2 = P1^0; // 8052 only
/* T2CON */
sbit TF2 = T2CON^7;
sbit EXF2 = T2CON^6;
sbit RCLK = T2CON^5;
sbit TCLK = T2CON^4;
sbit EXEN2 = T2CON^3;
sbit TR2 = T2CON^2;
sbit C_T2 = T2CON^1;
sbit CP_RL2 = T2CON^0;
#endif
和51单片机一样,32也可以通过类似的方法(位带操作),控制片上外设的寄存器。STM32中没有sbit这样的关键字,其对片上外设寄存器的控制是通过访问位带别名区来实现的。
例如,点亮LED
//32对寄存器的封装头文件stm32f10x.h
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE+0x08)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE+0x10)
#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE+0x14)
#define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE+0x18)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
//主函数
#include "stm32f10x.h"
int main(void)
{
RCC_APB2ENR |= (1<<3);
GPIOB_CRL &= ~( 0x0F<< (4*0));
GPIOB_CRL |= (1<<4*0);
GPIOB_ODR &= ~(1<<0);
while(1);
}
void SystemInit(void)
{
}
寄存器编程:51单片机的编程,就是经典的寄存器编程。51单片机在配置I/O口,定时器和中断等的时候,都要查阅寄存器表,看看需要配置哪些位,置零还是置一。STM32同一可以直接对寄存器进行编程,不过因为32的寄存器过于繁多复杂,一般不推荐这种方式。
库函数编程:了解库函数之前,需要先了解存储器映射和寄存器映射。