STM32单片机开发-01 STM32介绍

通过野火开发板学习单片机

1.STM32介绍

1.1 STM32分类

从内核上分有Cortex-M0、M3、M4 和M7

F1 代表了基础型,基于Cortex-M3 内核,主频为72MHZ

F4 代表了高性能,基于Cortex-M4 内核,主频180M。

1.2 STM32命名


1.3 参考手册和数据手册

  • 数据手册:用于芯片选型和设计原理图
  • 参考手册:用于编程时查阅
  • 1.4 STM32架构

  • Icode总线
    – 该总线讲M3内核的指令总线与闪存指令接口向量,完成指令预取
  • 4个驱动单元
  • Dcode总线
  • M3内核的Dcode总线与闪存的数据接口相连(完成常量加载和调用)
  • 系统总线(S-bus)
  • M3内核的系统总线到总线矩阵,总线矩阵协调内核和DMA的访问
  • 通用DMA1总线
  • DMA的AHB主控接口与总线矩阵相联
  • 通用DMA2
  • 以太网DMA(部分设备)
  • 4个被动单元
  • 内部SRAM
  • 内部FLASH
  • FSMC(灵活的静态存储器控制器,可以扩展内存如SRAM NORFLASH)
  • AHB到APB的桥,连接APB设备
  • 总线矩阵
  • 协调内核系统总线和DMA主控总线之间的访问和仲裁。
  • AHB外设通过总线矩阵与系统总线相连,允许DMA访问。
  • AHB/APB桥
  • 两个桥在AHB和APB之间提供同步连接。
  • APB1操作速度限于36MHz
  • APB2操作于全速,最高72MHz
  • 在使用外设前,必须设置寄存器RCC_AHBENR

    系统架构

    1.5 存储器映射

    1. 所有的被控单元FLASH、RAM、FSMC、AHB到APB的桥(片上外设)共同排列在一个4GB的地址空间,通过他们的地址来操作他们。

    2. 存储器本身不具有地址,是由芯片厂家或用户分配的,这个过程叫做存储器映射。

    3. 给存储器再分配一个地址叫做存储器重映射。

    4. 数据以小端格式存放在存储器中(最低地址在最低字节,最高地址在最高字节)

    5. 可访问的存储器空间背分成8个主要块,每个512MB。
      8个主要块

    6. Block0:用来设计成内部的FLASH

    7. ZET6 和VET6的FLASH都是512k,属于大容量,RCT6是256k
    8. 存储了ST出厂时烧写的bootloader(自举程序,用户无法修改,可以通过串口下载)
    9. 用户程序放在FLASH,即0x0800 0000 开始的512k空间。
    10. Block1:用来设计成内部的RAM

    11. ZET6和VET6都是64KB
    12. 默认地址为0x20000000
    13. Block2:用来设计成片上的外设

    14. 根据总线速度不同,分为AHB、APB1和APB2
    15. 寄存器映射

    16. 以block2为例,设计的是片上外设,他们以4个字节为一个单位,共32bit,每个单元对应不同的功能,控制这些单元时就可以驱动外设工作。
    17. 找到每个单元的起始地址,通过指针操作,来访问这些单元。给这个内存单元取个别名就叫寄存器映射。
    18. 如GPIOB端口输出寄存器为ODR(地址0x40010C0C),32bit的低16bit有效,对应着16个外部IO,写0/1 对应的IO则输出低、高电平。
    19. 通过寄存器的方式来访问,更容易记忆,不容易出错
    		1 // GPIOB 端口全部输出高电平
    		2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
    
    		1 // GPIOB 端口全部输出高电平
    		2 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
    		3 * GPIOB_ODR = 0xFF;
    

    为了方便操作,可以直接把指针操作* 也定义到寄存器别名里

    		1 // GPIOB 端口全部输出高电平
    		2 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
    		3 GPIOB_ODR = 0xFF;
    
    1. 片上外设地址映射
    2. 不同总线挂载不同外设
    3. APB1 挂载低速外设
    4. APB2和AHB挂载高速外设
    5. 对应总线最低地址称为基地址,也是挂载在该总线上的首个外设地址。
    6. GPIO属于告诉外设,挂载到APB2总线上。

    7. GPIOB为例介绍寄存器地址列表,每个GPIO都有这些寄存器,每个占4个字节 32bit
    8. 每个GPIO都有两个配置寄存器(GPIOx_CRL、GPIOx_CRH)
    9. 两个32位数据寄存器(GPIOx_IDR、GPIOx_ODR)
    10. 一个32位置位/复位寄存器(GPIOx_BSRR)
    11. 一个16位复位寄存器(GPIOx_BRR)
    12. 一个32位锁定寄存器(GPIOx_LCKR)
    13. 每个GPIO可由寄存器配置位多种模式
    14. 输入浮空
    15. 输入上拉
    16. 输入下拉
    17. 模拟输入
    18. 开漏输出
    19. 推挽式输出
    20. 推挽式复用功能
    21. 开漏复用功能
    22. 设置清楚寄存器
    1. GPIOx中的x都是从A-E
    2. 地址偏移:是相对于这个外设的基地址的偏移即GPIOA的外设基地址偏移就是0x40010800 + 0x10
    3. 寄存器位表,描述了每个bit的名称和权限以及对应的操作。y的取值是0-15,即对应GPIOA.1脚
    4. 注意对BRy写1:是清除,即设置低电平,写0无效
    5. 对BSy写1:是设置,即设置为高电平,写0无效
    1. 封装总线和外设基地址
    2. 为了方便理解和记忆,把总线基地址和外设基地址都以相应的宏定义
    1 /* 外设基地址*/
    2 #define PERIPH_BASE ((unsigned int)0x40000000)
    3
    4 /* 总线基地址*/
    5 #define APB1PERIPH_BASE PERIPH_BASE
    6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
    7 #define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
    8
    9
    10 /* GPIO 外设基地址*/
    11 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
    12 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
    13 #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
    14 #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
    15 #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
    16 #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
    17 #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
    18
    19
    20 /* 寄存器基地址,以GPIOB 为例*/
    21 #define GPIOB_CRL (GPIOB_BASE+0x00)
    22 #define GPIOB_CRH (GPIOB_BASE+0x04)
    23 #define GPIOB_IDR (GPIOB_BASE+0x08)
    24 #define GPIOB_ODR (GPIOB_BASE+0x0C)
    25 #define GPIOB_BSRR (GPIOB_BASE+0x10)
    26 #define GPIOB_BRR (GPIOB_BASE+0x14)
    27 #define GPIOB_LCKR (GPIOB_BASE+0x18)
    
    1. 首先定义片上外设基地址PERIPH_BASE
    2. 接着在此基础上,加入个总线低地址偏移,得到APB1、APB2总线低基地址
    3. 在其上再加上外设地址的偏移,得到GPIOA-G的外设地址
    4. 最后在外设地址上假如各寄存器的地址偏移,得到特定寄存器的地址。
      有了具体的地址,就可以使用指针读写该寄存器了。
    1 /* 控制GPIOB 引脚0 输出低电平(BSRR 寄存器的BR0 置1) */
    2 *(unsigned int *)GPIOB_BSRR = (0x01<<(16+0));
    3
    4 /* 控制GPIOB 引脚0 输出高电平(BSRR 寄存器的BS0 置1) */
    5 *(unsigned int *)GPIOB_BSRR = 0x01<<0;
    6
    7 unsigned int temp;
    8 /* 读取GPIOB 端口所有引脚的电平(读IDR 寄存器) */
    9 temp = *(unsigned int *)GPIOB_IDR;
    
    1. 该代码使用(Unsigned int *)把GPIOB_BSRR宏的数值强制转换成了地址
    2. 再取* 并赋值,是对改地址的写操作。
    3. 读寄存器也是用取指针操作,把寄存器中的数据取到变量里,从而获取外设的状态。
    1. 封装寄存器列表
    2. 如果对每个地址都进行定义,会显得分繁琐,可以使用结果提对一组GPIO进行封装
    1 typedef unsigned int uint32_t; /* 无符号32 位变量*/
    2 typedef unsigned short int uint16_t; /* 无符号16 位变量*/
    3
    4 /* GPIO 寄存器列表*/
    5 typedef struct {
    	6 uint32_t CRL; /*GPIO 端口配置低寄存器地址偏移: 0x00 */
    	7 uint32_t CRH; /*GPIO 端口配置高寄存器地址偏移: 0x04 */
    	8 uint32_t IDR; /*GPIO 数据输入寄存器地址偏移: 0x08 */
    	9 uint32_t ODR; /*GPIO 数据输出寄存器地址偏移: 0x0C */
    	10 uint32_t BSRR; /*GPIO 位设置/清除寄存器地址偏移: 0x10 */
    	11 uint32_t BRR; /*GPIO 端口位清除寄存器地址偏移: 0x14 */
    	12 uint16_t LCKR; /*GPIO 端口配置锁定寄存器地址偏移: 0x18 */
    13 } GPIO_TypeDef;
    
    1. 使用GPIO_TypeDef结构体,其中的7个成员变量名就是对应的寄存器名称。
    2. 利用了C语言结构体内变量的存储空间是连续的,32位变量占4个字节,16位占2个字节
    3. 假如这个结构体首地址位0x40010C00,那么第二个成员变量就是+0x04
    4. 这样GPIO外设定义的寄存器地址就可以一一对应了,可以通过结构体的形式访问寄存器
    1 GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef 型结构体指针GPIOx
    2 GPIOx = GPIOB_BASE; //把指针地址设置为宏GPIOB_BASE 地址
    3 GPIOx->IDR = 0xFFFF;
    4 GPIOx->ODR = 0xFFFF;
    5
    6
    7 uint32_t temp;
    8 temp = GPIOx->IDR; //读取GPIOB_IDR 寄存器的值到变量temp 中
    
    1. 先用 GPIO_TypeDef 类型定义一个结构体指针,并将其指向GPIOB_BASE
    2. 可以使用->ODR来读写寄存器。
    1 /* 使用GPIO_TypeDef 把地址强制转换成指针*/
    2 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
    3 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
    4 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
    5 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
    6 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
    7 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
    8 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
    9 #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
    10
    11
    12
    13 /* 使用定义好的宏直接访问*/
    14 /* 访问GPIOB 端口的寄存器*/
    15 GPIOB->BSRR = 0xFFFF; //通过指针访问并修改GPIOB_BSRR 寄存器
    16 GPIOB->CRL = 0xFFFF; //修改GPIOB_CRL 寄存器
    17 GPIOB->ODR =0xFFFF; //修改GPIOB_ODR 寄存器
    18
    19 uint32_t temp;
    20 temp = GPIOB->IDR; //读取GPIOB_IDR 寄存器的值到变量temp 中
    21
    22 /* 访问GPIOA 端口的寄存器*/
    23 GPIOA->BSRR = 0xFFFF;
    24 GPIOA->CRL = 0xFFFF;
    25 GPIOA->ODR =0xFFFF;
    26
    27 uint32_t temp;
    28 temp = GPIOA->IDR; //读取GPIOA_IDR 寄存器的值到变量temp 中
    
    1. 我们可以直接使用宏定义好各端口的首地址
    2. 使用时直接用该宏访问寄存器即可。
    3. 这部分的工作都已经由固件库帮我们完成了
    1. 寄存器操作
    2. 寄存器操作主要是修改寄存器的某几位,且要保持其他值不变
    3. 利用C语言的位操作

    例:假设a变量代表寄存器,假设已有数值,需要将其清零,其他位保持不变

    1 //定义一个变量a = 1001 1111 b (二进制数)
    2 unsigned char a = 0x9f;
    3
    4 //对bit2 清零
    5
    6 a &= ~(1<<2);
    7
    8 //括号中的1 左移两位,(1<<2) 得二进制数:0000 0100 b
    9 //按位取反,~(1<<2) 得1111 1011 b
    10 //假如a 中原来的值为二进制数: a = 1001 1111 b
    11 //所得的数与a 作”位与&”运算,a = (1001 1111 b)&(1111 1011 b),
    12 //经过运算后,a 的值a=1001 1011 b
    13 // a 的bit2 位被被零,而其它位不变。
    
    1 //若把a 中的二进制位分成2 个一组
    2 //即bit0、bit1 为第0 组,bit2、bit3 为第1 组,
    3 // bit4、bit5 为第2 组,bit6、bit7 为第3 组
    4 //要对第1 组的bit2、bit3 清零
    5
    6 a &= ~(3<<2*1);
    7
    8 //括号中的3 左移两位,(3<<2*1) 得二进制数:0000 1100 b
    9 //按位取反,~(3<<2*1) 得1111 0011 b
    10 //假如a 中原来的值为二进制数: a = 1001 1111 b
    11 //所得的数与a 作”位与&”运算,a = (1001 1111 b)&(1111 0011 b),
    12 //经过运算后,a 的值a=1001 0011 b
    13 // a 的第1 组的bit2、bit3 被清零,而其它位不变。
    14
    15 //上述(~(3<<2*1)) 中的(1) 即为组编号; 如清零第3 组bit6、bit7 此处应为3
    16 //括号中的(2) 为每组的位数,每组有2 个二进制位; 若分成4 个一组,此处即为4
    17 //括号中的(3) 是组内所有位都为1 时的值; 若分成4 个一组,此处即为二进制数“1111 b”
    18
    19 //例如对第2 组bit4、bit5 清零
    20 a &= ~(3<<2*2);
    
  • 对变量的某几位进行赋值
  • 在上边清零操作之后,可以方便的对某几位写入所需要的数值
  • 1 //a = 1000 0011 b
    2 //此时对清零后的第2 组bit4、bit5 设置成二进制数“01 b ”
    3
    4 a |= (1<<2*2);
    5 //a = 1001 0011 b,成功设置了第2 组的值,其它组不变
    
  • 对某个位进行取反操作,可以直接使用取反命令
  • 1 //a = 1001 0011 b
    2 //把bit6 取反,其它位不变
    3
    4 a ^=(1<<6);
    5 //a = 1101 0011 b
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32单片机开发-01 STM32介绍

    发表评论