启动文件的功能
启动文件由汇编编写,是系统上电复位后第一个执行的程序。
- 初始化堆栈指针
SP=_initial_sp
- 初始化程序计数器指针
PC=Reset_Handler
- 设置堆和栈的大小
- 设置中断向量表的入口地址,初始化中断向量表
- 配置外部
SRAM
作为数据存储 - 配置系统时钟
- 调用
C
库函数_main
初始化用户栈,从而最终调用main
函数转到C
世界
来自p45、p108
GPIO工作模式
- 输入模式(
上拉
/下拉
/浮空
) - 输出模式(
推挽
/开漏
、上拉
/下拉
) - 复用功能(
推挽
/开漏
、上拉
/下拉
) - 模拟输入输出
来自p42-43
STM32的GPIO的配置模式有哪几种?工作场景?
模式名称 | 性质 | 场景 |
---|---|---|
浮空输入 | 数字输入 | 外部按键输入/USART RX 引脚 |
上拉输入 | 数字输入 | 需要IO 内部上拉电阻输入时,器件的外部中断(IRQ )引脚触发中断条件为下降沿触发/低电平触发,这样在无信号输入时始终保持高电平,如果有事件触发中断IRQ 可以输出一个低电平,进而可产生(下降沿/低电平)中断。例如单片无线收发器芯片NRF24L01 的IRQ 引脚的工作模式即为上拉输入模式 |
下拉输入 | 数字输入 | 需要IO 内部下拉电阻输入时,器件的外部中断(IRQ )引脚触发中断条件为上升沿触发/高电平触发时,该端口可以选择下拉输入模式 |
模拟输入 | 模拟输入 | ADC 模拟输入/低功耗下省电 |
开漏输出 | 数字输出 | IIC /SMBus |
推挽输出 | 数字输出 | 普通的GPIO 用于驱动LED 、数码管等电子元器件或输出控制某个信号 |
复用开漏输出 | 数字输出 | 常见片内外设(I2C /SMBus 等等) |
复用推挽输出 | 数字输出 | 常见片内外设(USART TX 引脚/SPI /PWM 输出等等) |
来自# STM32 GPIO的8种工作模式与应用场合 和 中文使用手册 和 p40
栈和堆(启动文件)
- 栈的作用是用于局部变量、函数调用、函数形参等的开销,栈的大小不能超过内部
SRAM
的大小。标号_initial_sp
紧挨着SPACE
语句(用于分配一定大小的内存空间,单位为字节)放置,表示栈的结束地址,即栈顶地址。栈,由高向低生长。 - 堆主要用于动态内存的分配,像
malloc()
函数申请的内存就在堆中,由低向高生长。
来自p109-110
简述SPI和IIC的区别
IIC | SPI |
---|---|
半双工,2 根线SCL SDA | 全双工,4 根线SCK CS MOSI MISO |
多主机总线,通过SDA 上的地址信息来锁定从设备 | 只有一个主设备,主设备通过CS 片选来确定从设备 |
总线传输速度100Kbps-4Mbps | 达30Mbps 以上 |
高电平时SDA 下降沿标志传输开始,上升沿标志传输结束 | CS 拉低标志传输开始,CS 拉高标志传输结束 |
读写时序比较固定统一,设备驱动编写方便 | 不同从设备datasheet 来实现读写,相对复杂一些 |
CPOL/CPHA及通信模式
SPI
一般有4
种通信模式,它们的主要区别是总线空闲时SCK
的时钟状态及数据采样时刻。
时钟极性CPOL
是指SPI
通信设备处于空闲状态时,SCK
信号线的电平信号(即SPI
通信开始前、NSS
线为高电平时SCK
的状态)。CPOL=0
时,SCK
在空闲状态时为低电平,CPOL=1
时,SCK
在空闲状态时为高电平。
时钟相位CPHA
是指数据的采样的时刻,当CPHA=0
时,MOSI
或MISO
数据线上的信号将会在SCK
时钟线的“奇数边沿”被采样。
CPOL
及CPHA
的不同状态,SPI
分为4
种模式。主机与从机需要工作在相同的模式下才可以正常通信,实际中采用较多的是“模式0
”和“模式3
”
SPI模式 | CPOL | CPHA | 空闲时SCK时钟 | 采样时刻 |
---|---|---|---|---|
0 | 0 | 0 | 低电平 | 奇数边沿 |
1 | 0 | 1 | 低电平 | 偶数边沿 |
2 | 1 | 0 | 高电平 | 奇数边沿 |
3 | 1 | 1 | 高电平 | 偶数边沿 |
来自p237-238
IIC挂机设备的个数
由IIC
地址决定,8
位地址,减去1
位广播地址,是7
位地址,2^7=128
,但是地址0x00
不用,那就是127
个地址, 所以理论上可以挂127
个从器件。
但是IIC
协议没有规定总线上device
最大数目,但是规定了总线电容不能超过400pF
。
管脚都是有输入电容的,PCB
上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8
个器件。
总线之所以规定电容大小是因为,IIC
的OD
要求外部有电阻上拉,电阻和总线电容产生了一个RC
延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。
传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC
乘积必须更小。
来自# IIC总线最多可以挂多少个设备 和 p205 -206
时钟源与时钟和总线对应外设
在STM32
中,可以用内部时钟,也可以用外部时钟,在要求进度高的应用场合最好用外部晶体震荡器,内部时钟存在一定的精度误差。
准确的来说有4
个时钟源可以选分别是HSI
、LSI
、HSE
、LSE
(即内部高速,内部低速,外部高速,外部低速),高速时钟主要用于系统内核和总线上的外设时钟。低速时钟主要用于独立看门狗IWDG
、实时时钟RTC
。
HSI
是高速内部时钟,RC
振荡器,频率为8MHz
,上电后默认的系统时时钟SYSCLK = 8MHz
,Flash
编程时钟HSE
是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz
LSI
是低速内部时钟,RC
振荡器,频率为40kHz
,可用于独立看门狗IWDG
、实时时钟RTC
LSE
是低速外部时钟,接频率为32.768kHz
的石英晶体
PLL
为锁相环倍频输出,其时钟输入源可选择为HSI/2
、HSE
或者HSE/2
。倍频可选择为2~16
倍,但是其输出频率最大不得超过72MHz
。通过倍频之后作为系统时钟的时钟源( 有很多人说是5
个时钟源,这种说法有点问题,学习之后就会发现PLL
并不是自己产生的时钟源,而是通过其他三个时钟源倍频得到的时钟)。
从左到右可以简单理解为 各个时钟源 -> 系统时钟来源的设置 -> 各个外设时钟的设置
系统时钟SYSCLK
3种时钟源
HSI
振荡器时钟HSE
振荡器时钟PLLCLK
时钟
PLL锁相环倍频(输入和输出)
PLL
的输入(3
种)
PLLi = HSI /2
PLLi = HSE /2
PLLi = HSE
PLL
的输出(15
种)
PLLout = PLLi Xn (n = 2…16)
F4系列总线对应外设
APB2
总线:高级定时器timer1
、timer8
,通用定时器timer9
、timer10
、timer11
,UTART1
、USART6
APB1
总线:通用定时器timer2
、timer5
,通用定时器timer12
、timer14
,基本定时器timer6
、timer7
、UTART2
~UTART5
F4
系列的系统时钟频率最高能到168M
来自# 图文并茂详解STM32时钟配置 和 p117-123
串口定义
STM32F4
系统控制器有4
个USART
和4
个UART
,其中USART1
和USART6
的时钟来源于APB2
总线时钟,最大频率为90MHz
,其它6
个时钟来源于APB1
总线时钟,其最大频率为45MHz
。
来自p161
串口调试为什么收到一直乱码?
- 首先检查上下两机位是否一致:波特率、数据位、停止位、奇偶校验等等
- 其次检查线缆:线缆长度是否过长,引入串扰,阻抗是否匹配,信号畸变是否在可控范围内
- 地电位:因为串口调试一般只需要三线
RX
、TX
、GND
,地电位也非常重要,务必使你调试用的电脑地和你要调试的电路地电位基本一致(最好共地)。 - 电平:如果你使用的
TTL
串口,那么还需要考虑是不是5V
和3.3V
的差异导致上述情况。
抢占优先级与子优先级
假设STM32
配置了3
个中断向量
-
当
STM32
响应中断时,中断A
能打断中断B
的中断服务函数吗? -
中断
C
能打断中断A
吗? -
如果中断
A
和中断C
中断同时到达,响应哪个中断?
中断向量 | 抢占优先级 | 子优先级 |
---|---|---|
A | 2 | 0 |
B | 3 | 0 |
C | 2 | 1 |
-
A
可以打断B
-
C
不能打断A
-
响应中断
A
原因略
三种定时器
TIM1和TIM8定时器的功能包括【高级】
16
位向上、向下、向上/下自动装载计数器16
位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535
之间的任意数值- 多达
4
个独立通道: 输入捕获、输出比较、PWM
生成(边缘或中间对齐模式)、单脉冲模式输出 - 死区时间可编程的互补输出
- 使用外部信号控制定时器和定时器互联的同步电路
- 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
- 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
- 如下事件发生时产生中断/
DMA
: 更新(计数器向上溢出/向下溢出)、计数器初始化(通过软件或者内部/外部触发) 、触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) 、输入捕获、输出比较、刹车信号输入 - 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
TIMx主要功能通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括【通用】
16
位向上、向下、向上/向下自动装载计数器16
位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536
之间的任意数值4
个独立通道: 输入捕获、输出比较、PWM
生成(边缘或中间对齐模式)、单脉冲模式输出- 使用外部信号控制定时器和定时器互连的同步电路
- 如下事件发生时产生中断/
DMA
:更新(计数器向上溢出/向下溢出)、计数器初始化(通过软件或者内部/外部触发)、触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)、输入捕获 、输出比较 - 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
TIM6和TIM7定时器的主要功能包括【简单
16
位自动重装载累加计数器16
位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536
之间的任意数值分频- 触发
DAC
的同步电路 注:此项是TIM6/7
独有功能 - 在更新事件(计数器溢出)时产生中断/
DMA
请求
EXTI 外部中断/事件控制器
EXTI
外部中断/事件控制器管理了控制器的23
个中断/事件线,EXTI
可分为两部分功能:一是产生中断,另一个是产生事件。
来自p136
SysTick
SysTick
系统定时器是CM4
内核中的一个外设,内嵌在NVIC
中。系统定时器是一个24
位的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK
,一般我们设置系统时钟SYSCLK等于180MHz。
来自参考手册
RCC
通过AHB
时钟(HCLK
)8
分频后作为Cortex
系统定时器(SysTick
)的外部时钟。
通过对SysTick
控制与状态寄存器的设置,可选择上述时钟或Cortex
(HCLK
)时钟作为SysTick
时钟。
也就是说SysTick
时钟源可以来自两个地方:
AHB
时钟8
分频HCLK
(内核)时钟
通过SysTick
控制与状态寄存器的设置进行选择时钟源。
来自p145 和 STM32的SysTick时钟源来自哪里?
波特率计算
B a u d = f P L C K 8 × ( 2 − O V E R 8 ) × U S A R T D I V Baud=\frac{f_{PLCK}}{8{\times}(2-OVER8){\times}USARTDIV} Baud=8×(2−OVER8)×USARTDIVfPLCK
时钟控制逻辑计算
SCL
线的时钟信号,由IIC
接口根据时钟控制寄存器(CCR
)控制,控制参数主要为时钟频率。配置IIC
的CCR
寄存器可修改通信速率相关的参数
- 可选择
IIC
通信的“标准/快速”模式,这两个模式分别对应100kbps/400kbps
的通信速率。 - 在快速模式下可选择
SCL
时钟的占空比 T l o w T h i g h \frac{T_{low}}{T_{high}} ThighTlow,可选2
或16/9
模式。 CCR
寄存器中还有一个12
位的配置因子CCR
,它与IIC
外设的输入时钟源共同作用,产生SCL
时钟。STM32
的IIC
外设都挂载在APB1
总线上,使用APB1
的时钟源PCLK1
。
SCL
信号线的输出时钟公式如下
标准模式
T h i g h = C C R × T P C L K 1 T_{high}=CCR{\times}T_{PCLK1} Thigh=CCR×TPCLK1
T l o w = C C R × T P C L K 1 T_{low}=CCR{\times}T_{PCLK1} Tlow=CCR×TPCLK1
快速模式 T l o w T h i g h = 2 \frac{T_{low}}{T_{high}}=2 ThighTlow=2
T h i g h = C C R × T P C L K 1 T_{high}=CCR{\times}T_{PCLK1} Thigh=CCR×TPCLK1
T l o w = 2 × C C R × T P C L K 1 T_{low}=2{\times}CCR{\times}T_{PCLK1} Tlow=2×CCR×TPCLK1
快速模式 T l o w T h i g h = 16 / 9 \frac{T_{low}}{T_{high}}=16/9 ThighTlow=16/9
T h i g h = 9 × C C R × T P C L K 1 T_{high}=9{\times}CCR{\times}T_{PCLK1} Thigh=9×CCR×TPCLK1
T l o w = 16 × C C R × T P C L K 1 T_{low}=16{\times}CCR{\times}T_{PCLK1} Tlow=16×CCR×TPCLK1
例如,PCLK1=45MHz
,想要配置400kbps
的速率
PCLK
时钟周期:
T
P
C
L
K
1
=
1
45000000
T_{PCLK1}=\frac{1}{45000000}
TPCLK1=450000001
目标SCL
时钟周期:
T
S
C
L
=
1
400000
T_{SCL}=\frac{1}{400000}
TSCL=4000001
SCL
时钟周期内的高电平时间:
T
H
I
G
H
=
1
3
T
S
C
L
T_{HIGH}=\frac{1}{3}T_{SCL}
THIGH=31TSCL
SCL
时钟周期内的低电平时间:
T
L
O
W
=
2
3
T
S
C
L
T_{LOW}=\frac{2}{3}T_{SCL}
TLOW=32TSCL
CCR
的值:
C
C
R
=
T
H
I
G
H
T
P
C
L
K
1
=
37.5
CCR=\frac{T_{HIGH}}{T_{PCLK1}}=37.5
CCR=TPCLK1THIGH=37.5
由于CCR
寄存器是无法配置小数参数,所以我们智能把CCR
取值为38
,所以SCL
实际频率无法达到400kHz
。
来自p211
程序题1
初始化LED_GPIO灯
#include "bsp_led.h"
// 0- 首先要开GPIO端口的时钟
// 1- 要先确定引脚号
// 2- 要确定是输入还是输出 MODER
// 3- 如果是输出,那么是推挽还是开漏输出 OTYPER
// 4- 是是上拉还是下拉
// 5- 那么输出的速度是多少呢
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHB1PeriphClockCmd(LED_R_GPIO_CLK,ENABLE);
GPIO_InitStruct.GPIO_Pin = LED_R_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
GPIO_Init(LED_R_GPIO_PORT,&GPIO_InitStruct);
}
初始化BASIC_TIMx(NVIC和TIM)
by the way
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE);
这句代码实现的功能是什么?
实现开启外设
#include "bsp_basic_tim.h"
static void TIMx_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIMx_IRQn;
// 设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置子优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void BASIC_TIMx_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(BASIC_TIMx_CLK, ENABLE);
/* 累计 TIM_Period个后产生一个更新或者中断*/
//当定时器从0计数到4999,即为5000次,为一个定时周期
TIM_TimeBaseStructure.TIM_Period = 5000-1;
//定时器时钟源TIMxCLK = 2 * PCLK1
// PCLK1 = HCLK / 4
// => TIMxCLK=HCLK/2=SystemCoreClock/2=90MHz
// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10000Hz
TIM_TimeBaseStructure.TIM_Prescaler = 9000-1; //预分频系数
// 初始化定时器TIMx, x[2,3,4,5]
TIM_TimeBaseInit(BASIC_TIMx, &TIM_TimeBaseStructure);
// 清除定时器更新中断标志位
TIM_ClearFlag(BASIC_TIMx, TIM_FLAG_Update);
// 开启定时器更新中断
TIM_ITConfig(BASIC_TIMx,TIM_IT_Update,ENABLE);
// 使能定时器
TIM_Cmd(BASIC_TIMx, ENABLE);
}
void BASIC_TIMx_Config(void)
{
TIMx_NVIC_Configuration();
BASIC_TIMx_Mode_Config();
}
main函数
#include "stm32f4xx.h"
#include "bsp_led.h"
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
BASIC_TIMx_Config();
while (1)
{
}//进入循环等待中断
}
程序题2
初始化按键口
//宏定义WK_UP口
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
void KEY_Init(void)//定义 按键初始化函数
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量,IO初始化函数中数据传入使用
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//PA时钟,使能
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//WK_UP
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);//WK_UP初始化
}
初始化LED接口
//LED0,LED1口宏定义
#define LED0 PFout(9)
#define LED1 PFout(10)
void LED_Init(void) //LED初始化函数定义
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量,IO初始化函数中数据传入使用
//注意:定义结构体 应在 时钟使能 之前,否则编译出现警告!
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//时钟,使能
//F9
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//第九位
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//100MHz
GPIO_Init(GPIOF,&GPIO_InitStructure);//初始化
//F10
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//第十位
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//100MHz
GPIO_Init(GPIOF,&GPIO_InitStructure);//初始化
//设置LED初始不亮
LED0 = 1;
LED1 = 1;
}
初始化蜂鸣器
#define BEEP PFout(8)//位操作蜂鸣器
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量,IO初始化函数中数据传入使用
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//时钟,使能
//F8
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;//第八位
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//100MHZ
GPIO_Init(GPIOF,&GPIO_InitStructure);//初始化
GPIO_ResetBits(GPIOF,GPIO_Pin_8);//置低电平,蜂鸣器不响
}
LED和蜂鸣器同时响应
按键按下一次,LED
灯亮,同时蜂鸣器发出响声,再次按下按键,LED
灯灭,同时蜂鸣器停止发声。
int main(void)
{
u8 key;//保存按键扫描返回值
//初始化部分
LED_Init();
BEEP_Init();
KEY_Init();
delay_init(168);
//循环部分
while(1)
{
key=KEY_Scan();//将扫描结果传给key
if(key)//key不为零,即有按键按下
{
LED0=!LED0;
LED1=!LED1;
BEEP=!BEEP;
}
delay_init(10);
}
}