STM32 基础与系统架构

STM32 是 ST 公司基于 ARM Cortex-M 内核开发的 32 位微控制器。

ARM 内核

STM32F103C8T6 采用 Cortex-M3 内核,该内核发布于 2004 年 10 月。

片上资源/外设

英文缩写 名称 英文缩写 名称
NVIC 嵌套向量中断控制器 CAN CAN 通信
SysTick 系统滴答定时器 USB USB 通信
RCC 复位和时钟控制 RTC 实时时钟
GPIO 通用 IO 口 CRC CRC 校验
AFIO 复用 IO 口 PWR 电源控制
EXTI 外部中断 BKP 备份寄存器
TIM 定时器 IWDG 独立看门狗
ADC 模数转换器 WWDG 窗口看门狗
DMA 直接内存访问 DAC 数模转换器
USART 同步/异步串口通信 SDIO SD 卡接口
I2C I2C 通信 FSMC 可变静态存储控制器
SPI SPI 通信 USB OTG USB 主机接口

命名规则

系统结构

引脚定义

红色:电源相关引脚 蓝色:最小系统相关引脚 绿色:IO 口、功能口

带 FT 标识的引脚可接 5V 电压,未带 FT 的引脚只能容忍 3.3V 电压。

  • 主功能:上电默认功能。
  • 分区供电:STM32 采用分区供电,拥有多个供电引脚。

存储器映像

启动配置

  • 从主闪存存储器启动:主闪存存储器被映射到启动空间 (0x0000 0000),但仍然能够在它原有的地址 (0x0800 0000) 访问它。即闪存存储器的内容可以在两个地址区域访问。
  • 从系统存储器启动:系统存储器被映射到启动空间 (0x0000 0000),但仍然能够在它原有的地址访问它。
  • 从内置 SRAM 启动:只能在 0x2000 0000 开始的地址区访问 SRAM。

常用模式说明:

  • 主闪存存储器模式:正常执行闪存里面的用户程序。
  • 系统存储器模式:用于串口下载。内嵌的自举程序由 ST 在生产线上写入,用于通过串行接口对闪存进行编程。
  • 内置 SRAM 模式:主要用于程序调试。

  • 复位逻辑:在系统复位后,SYSCLK 的第 4 个上升沿将锁存 BOOT 引脚的值。此时引脚 20 的 BOOT1 功能转变为 PB2 功能。

最小系统电路

STM32核心板

C语言数据类型

关键字 位数 表示范围 stdint关键字 ST关键字
char 8 -128 ~ 127 int8_t s8
unsigned char 8 0 ~ 255 uint8_t u8
short 16 -32768 ~ 32767 int16_t s16
unsigned short 16 0 ~ 65535 uint16_t u16
int 32 -2147483648 ~ 2147483647 int32_t s32
unsigned int 32 0 ~ 4294967295 uint32_t u32
long 32 -2147483648 ~ 2147483647
unsigned long 32 0 ~ 4294967295
long long 64 -(2^64)/2 ~ (2^64)/2-1 int64_t
unsigned long long 64 0 ~ (2^64)-1 uint64_t
float 32 -3.4e38 ~ 3.4e38
double 64 -1.7e308 ~ 1.7e308

typedef和define的区别

(1)原理不同

define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。

typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。用typedef定义数组、指针、结构等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。

(2)功能不同

typedef用来定义类型的别名,起到类型易于记忆的功能。另一个功能是定义机器无关的类型。如定义一个REAL的浮点类型,在目标机器上它可以获得最高的精度:typedef long double REAL, 在不支持long double的机器上,看起来是这样的,typedef double REAL,在不支持double的机器上,是这样的,typedef float REAL

define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

(3)作用域不同

define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。

typedef和define有什么区别_define和typedef的区别-CSDN博客

结构体struct:数据打包,不同类型变量的集合,因为结构体变量类型较长,所以通常用typedef更改变量类型名。

枚举enum:定义一个取值受限制的整型变量,用于限制变量取值范围;宏定义的集合

STM32新建工程

工程架构

步骤

  • 建立工程文件夹,Keil中新建工程,选择型号
  • 工程文件夹里建立Start、Library、User等文件夹,复制固件库里面的文件到工程文件夹,保持工程独立性
  • 工程里对应建立Start、Library、User等同名称的分组,然后将文件夹内的文件添加到工程分组里
  • 工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹
  • 工程选项,C/C++,Define内定义USE_STDPERIPH_DRIVER
  • 工程选项,Debug,下拉列表选择对应调试器,Settings,Flash Download里勾选Reset and Run

这里比较复杂直接看视频就行,下面是一个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stm32f10x.h"                  // Device header

int main(void)
{
// RCC->APB2ENR = 0x00000010; 使用寄存器
// GPIOC->CRH = 0x00300000;
// GPIOC->ODR = 0x00002000;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使用标准库
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
// GPIO_SetBits(GPIOC,GPIO_Pin_13); //设置高电平,灭灯
GPIO_ResetBits(GPIOC,GPIO_Pin_13); //设置低电平,电灯
while(1)
{

}

}

GPIO

基本结构

GPIO位结构

  • 推挽模式(强推输出模式):P-MOS,N-MOS均有效,STM32对IO口有绝对的控制权。
  • 开漏模式:只有N-MOS工作,只有低电平有驱动能力,可以作为通信协议的驱动方式,输出5V电平信号
  • 关闭模式:输出关闭,端口的电平由外部信号控制

GPIO模式

模式名称 性质 特征
浮空输入 数字输入 可读取引脚电平,若引脚悬空,则电平不确定
上拉输入 数字输入 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
下拉输入 数字输入 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
模拟输入 模拟输入 GPIO无效,引脚直接接入内部ADC
开漏输出 数字输出 可输出引脚电平,高电平为高阻态,低电平接VSS
推挽输出 数字输出 可输出引脚电平,高电平接VDD,低电平接VSS
复用开漏输出 数字输出 由片上外设控制,高电平为高阻态,低电平接VSS
复用推挽输出 数字输出 由片上外设控制,高电平接VDD,低电平接VSS
  • LED:发光二极管,正向通电点亮,反向通电不亮

  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定

  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

LED

硬件电路

LED一般采用方式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //设置结构体对应的引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //频率
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化


// GPIO_ResetBits(GPIOA, GPIO_Pin_0); 低电平
// GPIO_SetBits(GPIOA, GPIO_Pin_0); 高电平
// GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); 高电平

// GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); 低电平
while(1)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);
Delay_ms(25);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
Delay_ms(25);
}

}

LED流水灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
int i = 0;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
while(1)
{
for(i=0;i<8;i++)
{
GPIO_Write(GPIOA,~(0x0001<<i));
Delay_ms(500);
}
}
}

恭喜您达成成就~——点灯大师!

蜂鸣器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int main(void)
{
int i = 0;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure1;
GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure1);
while(1)
{
for(i=0;i<8;i++)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
GPIO_Write(GPIOA,~(0x0001<<i));
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
}
}
}

按键控制LED

硬件电路

  • 传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压出,再通过电压比较器进行二值化即可得到数字电压输出

按键一般用上两种方式

左边两种接法必须要求引脚是上拉或下拉输出

右边两种接法允许引脚是浮空输入的模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*						key.c					*/
#include "stm32f10x.h"
#include "Delay.h"
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //对输入无影响
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //上拉输入
GPIO_Init(GPIOB, &GPIO_InitStructure);

}

uint8_t Key_GetNum(void)
{
uint8_t KeyNum=0;
if(!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1))//读输入数据寄存器值
{
Delay_ms(50);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1));//消抖
Delay_ms(50);
KeyNum=1;
}
if(!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11))
{
Delay_ms(50);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11));
Delay_ms(50);
KeyNum=11;
}

return KeyNum;

}
/* key.c */


/* led.c */
#include "stm32f10x.h"

void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);

}


void LED_ON(uint16_t Pin) //开启
{
GPIO_ResetBits(GPIOA, Pin );
}


void LED_OFF(uint16_t Pin) //关闭
{
GPIO_SetBits(GPIOA, Pin );
}
void LED2_TURN(void) //翻转
{
if(!GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2)) //读输出数据寄存器值
GPIO_SetBits(GPIOA, GPIO_Pin_2);
else
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
/* led.c */

/* main.c */
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
uint8_t Key;
int main(void)
{
LED_Init();
Key_Init();
while(1)
{
Key = Key_GetNum();
if(Key == 11)
LED2_TURN();

if(Key == 1)
LED_ON(GPIO_Pin_1);
else
LED_OFF(GPIO_Pin_1);
Delay_ms(100);

}
}
/* main.c */

蜂鸣器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*						main.c					*/
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"

int main(void)
{
Buzzer_Init();
LightSensor_Init();
while(1) //被黑暗遮挡发出尖锐爆鸣声音
{
if(LightSensor_Get() == 1)
Buzzer_ON();
else
Buzzer_OFF();
}
}
/* main.c */


/* LightSensor.c */
#include "stm32f10x.h"

void LightSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;
GPIO_Init(GPIOB, &GPIO_InitStructure);

}



uint8_t LightSensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);

}



/* LightSensor.c */

/* Buzzer.c */
#include "stm32f10x.h"

void Buzzer_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_Init(GPIOB, &GPIO_InitStructure);

}


void Buzzer_ON(void) //开启
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}


void Buzzer_OFF(void) //关闭
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}



void Buzzer_TURN(void) //翻转
{
if(!GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_12)) //读输出数据寄存器值
GPIO_SetBits(GPIOB, GPIO_Pin_12);
else
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}

/* Buzzer.c */


OLED 调试模块