stm32

本文详细介绍了STM32的启动文件功能,包括初始化堆栈指针、程序计数器、中断向量表和系统时钟。还探讨了GPIO的不同工作模式,如浮空输入、上拉/下拉输入、模拟输入和各种输出模式。此外,解析了STM32的堆栈和堆的管理以及SPI和IIC的区别,如传输速度、总线电容限制和挂载设备数量。最后,讨论了串口调试问题、中断优先级、定时器配置以及串口波特率计算。
摘要由CSDN通过智能技术生成
启动文件的功能

启动文件由汇编编写,是系统上电复位后第一个执行的程序。

  • 初始化堆栈指针SP=_initial_sp
  • 初始化程序计数器指针PC=Reset_Handler
  • 设置堆和栈的大小
  • 设置中断向量表的入口地址,初始化中断向量表
  • 配置外部SRAM作为数据存储
  • 配置系统时钟
  • 调用C库函数_main初始化用户栈,从而最终调用main函数转到C世界

来自p45、p108

GPIO工作模式
  • 输入模式(上拉/下拉/浮空
  • 输出模式(推挽/开漏上拉/下拉
  • 复用功能(推挽/开漏上拉/下拉
  • 模拟输入输出

来自p42-43

STM32的GPIO的配置模式有哪几种?工作场景?
模式名称性质场景
浮空输入数字输入外部按键输入/USART RX引脚
上拉输入数字输入需要IO内部上拉电阻输入时,器件的外部中断(IRQ)引脚触发中断条件为下降沿触发/低电平触发,这样在无信号输入时始终保持高电平,如果有事件触发中断IRQ可以输出一个低电平,进而可产生(下降沿/低电平)中断。例如单片无线收发器芯片NRF24L01IRQ引脚的工作模式即为上拉输入模式
下拉输入数字输入需要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的区别
IICSPI
半双工,2根线SCL SDA全双工,4根线SCK CS MOSI MISO
多主机总线,通过SDA上的地址信息来锁定从设备只有一个主设备,主设备通过CS片选来确定从设备
总线传输速度100Kbps-4Mbps30Mbps以上
高电平时SDA下降沿标志传输开始,上升沿标志传输结束CS拉低标志传输开始,CS拉高标志传输结束
读写时序比较固定统一,设备驱动编写方便不同从设备datasheet来实现读写,相对复杂一些

CPOL/CPHA及通信模式

SPI一般有4种通信模式,它们的主要区别是总线空闲时SCK的时钟状态及数据采样时刻。

时钟极性CPOL是指SPI通信设备处于空闲状态时,SCK信号线的电平信号(即SPI通信开始前、NSS线为高电平时SCK的状态)。CPOL=0时,SCK在空闲状态时为低电平,CPOL=1时,SCK在空闲状态时为高电平。

时钟相位CPHA是指数据的采样的时刻,当CPHA=0时,MOSIMISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。

CPOLCPHA的不同状态,SPI分为4种模式。主机与从机需要工作在相同的模式下才可以正常通信,实际中采用较多的是“模式0”和“模式3

SPI模式CPOLCPHA空闲时SCK时钟采样时刻
000低电平奇数边沿
101低电平偶数边沿
210高电平奇数边沿
311高电平偶数边沿

来自p237-238

IIC挂机设备的个数

IIC地址决定,8位地址,减去1位广播地址,是7位地址,2^7=128,但是地址0x00不用,那就是127个地址, 所以理论上可以挂127个从器件。

但是IIC协议没有规定总线上device最大数目,但是规定了总线电容不能超过400pF

管脚都是有输入电容的,PCB上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8个器件。

总线之所以规定电容大小是因为,IICOD要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。

传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。

来自# IIC总线最多可以挂多少个设备p205 -206

时钟源与时钟和总线对应外设

STM32中,可以用内部时钟,也可以用外部时钟,在要求进度高的应用场合最好用外部晶体震荡器,内部时钟存在一定的精度误差。

准确的来说有4个时钟源可以选分别是HSILSIHSELSE(即内部高速,内部低速,外部高速,外部低速),高速时钟主要用于系统内核和总线上的外设时钟。低速时钟主要用于独立看门狗IWDG、实时时钟RTC

  • HSI是高速内部时钟,RC振荡器,频率为8MHz,上电后默认的系统时时钟 SYSCLK = 8MHzFlash编程时钟
  • HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz
  • LSI是低速内部时钟,RC振荡器,频率为40kHz,可用于独立看门狗IWDG、实时时钟RTC
  • LSE是低速外部时钟,接频率为32.768kHz的石英晶体

PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。通过倍频之后作为系统时钟的时钟源( 有很多人说是5个时钟源,这种说法有点问题,学习之后就会发现PLL并不是自己产生的时钟源,而是通过其他三个时钟源倍频得到的时钟)。

image.png

从左到右可以简单理解为 各个时钟源 -> 系统时钟来源的设置 -> 各个外设时钟的设置

系统时钟SYSCLK3种时钟源

  • HSI振荡器时钟
  • HSE振荡器时钟
  • PLLCLK时钟

image.png

PLL锁相环倍频(输入和输出)

PLL的输入(3种)

  • PLLi = HSI /2
  • PLLi = HSE /2
  • PLLi = HSE

PLL的输出(15种)

PLLout = PLLi Xn (n = 2…16)

image.png

F4系列总线对应外设

APB2总线:高级定时器timer1timer8,通用定时器timer9timer10timer11UTART1USART6

APB1总线:通用定时器timer2timer5,通用定时器timer12timer14,基本定时器timer6timer7UTART2~UTART5

F4系列的系统时钟频率最高能到168M

来自# 图文并茂详解STM32时钟配置p117-123

串口定义

STM32F4系统控制器有4USART4UART,其中USART1USART6的时钟来源于APB2总线时钟,最大频率为90MHz,其它6个时钟来源于APB1总线时钟,其最大频率为45MHz

来自p161

串口调试为什么收到一直乱码?

  • 首先检查上下两机位是否一致:波特率、数据位、停止位、奇偶校验等等
  • 其次检查线缆:线缆长度是否过长,引入串扰,阻抗是否匹配,信号畸变是否在可控范围内
  • 地电位:因为串口调试一般只需要三线RXTXGND,地电位也非常重要,务必使你调试用的电脑地和你要调试的电路地电位基本一致(最好共地)。
  • 电平:如果你使用的TTL串口,那么还需要考虑是不是5V3.3V的差异导致上述情况。

抢占优先级与子优先级

假设STM32配置了3个中断向量

  1. STM32响应中断时,中断A能打断中断B的中断服务函数吗?

  2. 中断C能打断中断A吗?

  3. 如果中断A和中断C中断同时到达,响应哪个中断?

中断向量抢占优先级子优先级
A20
B30
C21
  1. A可以打断B

  2. C不能打断A

  3. 响应中断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×(2OVER8)×USARTDIVfPLCK

时钟控制逻辑计算

SCL线的时钟信号,由IIC接口根据时钟控制寄存器(CCR)控制,控制参数主要为时钟频率。配置IICCCR寄存器可修改通信速率相关的参数

  • 可选择IIC通信的“标准/快速”模式,这两个模式分别对应100kbps/400kbps的通信速率。
  • 在快速模式下可选择SCL时钟的占空比 T l o w T h i g h \frac{T_{low}}{T_{high}} ThighTlow,可选216/9模式。
  • CCR寄存器中还有一个12位的配置因子CCR,它与IIC外设的输入时钟源共同作用,产生SCL时钟。STM32IIC外设都挂载在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

初始化按键口

image.png

image.png

//宏定义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接口

image.png

image.png

//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;

}

初始化蜂鸣器

image.png

image.png

#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);
	}
	
}	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值