STM32单片机开发-01 STM32介绍

2023-11-18

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

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个主要块

    • Block0:用来设计成内部的FLASH

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

      • ZET6和VET6都是64KB
      • 默认地址为0x20000000
        在这里插入图片描述
    • Block2:用来设计成片上的外设

      • 根据总线速度不同,分为AHB、APB1和APB2
        在这里插入图片描述
  6. 寄存器映射

    • 以block2为例,设计的是片上外设,他们以4个字节为一个单位,共32bit,每个单元对应不同的功能,控制这些单元时就可以驱动外设工作。
    • 找到每个单元的起始地址,通过指针操作,来访问这些单元。给这个内存单元取个别名就叫寄存器映射。
    • 如GPIOB端口输出寄存器为ODR(地址0x40010C0C),32bit的低16bit有效,对应着16个外部IO,写0/1 对应的IO则输出低、高电平。
    • 通过寄存器的方式来访问,更容易记忆,不容易出错
		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. 片上外设地址映射
    • 不同总线挂载不同外设
    • APB1 挂载低速外设
    • APB2和AHB挂载高速外设
    • 对应总线最低地址称为基地址,也是挂载在该总线上的首个外设地址。
    • GPIO属于告诉外设,挂载到APB2总线上。
      在这里插入图片描述
      在这里插入图片描述
    • GPIOB为例介绍寄存器地址列表,每个GPIO都有这些寄存器,每个占4个字节 32bit
      在这里插入图片描述
      • 每个GPIO都有两个配置寄存器(GPIOx_CRL、GPIOx_CRH)
      • 两个32位数据寄存器(GPIOx_IDR、GPIOx_ODR)
      • 一个32位置位/复位寄存器(GPIOx_BSRR)
      • 一个16位复位寄存器(GPIOx_BRR)
      • 一个32位锁定寄存器(GPIOx_LCKR)
    • 每个GPIO可由寄存器配置位多种模式
      • 输入浮空
      • 输入上拉
      • 输入下拉
      • 模拟输入
      • 开漏输出
      • 推挽式输出
      • 推挽式复用功能
      • 开漏复用功能
        在这里插入图片描述
      • 设置清楚寄存器
        在这里插入图片描述
  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. 封装总线和外设基地址
    • 为了方便理解和记忆,把总线基地址和外设基地址都以相应的宏定义
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. 封装寄存器列表
    • 如果对每个地址都进行定义,会显得分繁琐,可以使用结果提对一组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. 寄存器操作
    • 寄存器操作主要是修改寄存器的某几位,且要保持其他值不变
    • 利用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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32单片机开发-01 STM32介绍 的相关文章

  • selenium+java实现web自动化例子

    简单记录 有不正确的地方请指出 selenium java可以实现对web页面的自动化控制 在公司内部比较稳定 页面迭代较少的后台web系统使用时非常有效 web自动化收益最大化的情况 1 多更新于后端 前端页面迭代较少 2 在日常迭代中页
  • C++vector容器

    vector容器被称为动态数组 也被称为向量 它与array容器的区别是 array是静态数组 动态扩展 并不是在原空间之后续接新空间 而是找更大的内存空间 然后将原数据拷贝新空间 释放原空间 at 函数 返回对矢量中指定位置的元素的引用

随机推荐