stm32week1+2
stm32学习
一.初步
1.简介
以上是stm32的所有外设,部分stm32没有部分外设
学习的是stm32f103c8t6
类型中:s代表电源
I/O口电平中:FT表示该引脚能容忍5v的电压,没有的就3.3v,没有的要接5v的话要加一个电平芯片
主功能中:如果与名称不同的话,引脚的实际功能而不是引脚名称的功能
默认复用功能是IO口上同时连接的外设功能引脚
2.新建工程
stm32的开发方式有基于寄存器、基于标准库和基于HAL库的方式
由于stm32的寄存器复杂,HAL库高度封装,所以学习用的是标准库
标准库的所有.c文件和头文件都需要自己引入
二.外设
1.GPIO输出
GPIO通用输入输出口
可配置为8中输入输出模式
引脚电平:0~3.3v,部分引脚可容忍5v
输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序
输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接受数据
肖特基实际上是施密特触发器
施密特触发器有一个上限和下限,电压上升过上限时,后续一直输出1,下降过下限时,后续一直输出0
推挽输出:输入正弦输出正弦
推挽输出电路示意图:
开漏输出:输入正弦输出倒着的正弦
代码:
电路:
2.GPIO输入
按键:按键存在抖动
一般考虑用sleep睡过抖动的部分
LM393芯片是一个比较器芯片
最右边的P1是光敏传感芯片,DO是数字信号输出。AO是模拟信号输出
对于上拉下拉的理解:
串联电路的两个电阻就像吊在天花板与地板之间的两个弹簧,上面电阻小就相当于上面的弹簧拉力大,电压被拉高,这就是上拉,下拉同理
用的较多是上面两个电路
第一个电路需要stm32使用上拉模式,保证在K1断路时会有电压输入,同理第三个需要下拉模式
用按钮控制LED1、用光敏电阻控制LED2的代码:
Init函数均用于初始化:确定输入输出模式,设定管脚,设定频率
GPIO_Mode_AIN:模拟输入模式
GPIO_Mode_IN_FLOATING:浮空输入模式
GPIO_Mode_IPD:带下拉电阻的输入模式
GPIO_Mode_IPU:带上拉电阻的输入模式
GPIO_Mode_Out_OD:开漏输出模式
GPIO_Mode_Out_PP:推挽输出模式
GPIO_Mode_AF_OD:复用开漏输出模式
GPIO_Mode_AF_PP:复用推挽输出模式
电路图(同时将光敏电路板的DO接到B13脚):
3.OLED调试工具
调试方法:
- 串口调试:通过串口,将信息传给电脑,在电脑上显示
- 显示屏调试:直接将显示屏连接到单片机,将信息打印在显示屏上
- Keil调试:用Keil的调试模式
OLED:有机发光二极管,供电3~5.5v。通信协议:I2C/SPI
有4引脚和7引脚两种版本,4引脚用的I2C,7引脚用的SPI
硬件电路:
SCI和SDA是通信接口,可以用GPIO模拟的I2C通信的驱动函数模块
代码:
电路图:
4.Keil调试工具
左边的Simulator是在电脑上模拟stm32,右边是需要插上ST-link和stm32的
点击上方的放大镜,镜中带d的按钮进入debug模式
- RST复位,回到程序的开始
- 全速运行,直到断点
- 停止程序
- 下一步
- 跳过函数
- 跳出函数
- 运行到光标处
- 跳转到暂停行
- 命令行窗口
- 反汇编窗口
- 符号窗口
- 寄存器窗口
- 调用栈窗口
- 观察窗口
- 内存窗口
- 串行窗口
- 系统分析窗口
- 追踪窗口
- 系统观察窗口
- 工具箱
5.中断系统
中断:在主程序运行过程中,出现了特定的中断触发条件,CPU中断执行程序,转而去执行中断程序
中断优先级:中断源有优先级之分
中断嵌套:当一个中断程序正在执行时,有优先级更高的中断源申请中断,就会中断中断程序的执行,转而执行新的中断程序
stm32有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
stm32使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先级,可对优先级进行分组,进一步设置优先级和响应优先级
灰色的是内核的中断,用不到
优先级是值越小,优先级越高
6.EXTI简介
EXTI(Extern Interrupt)外部中断
EXTI可以监测指定GPIOl口的电平信号,当电平信号发生变化时,EXTI将向NVIC发出中断申请,经过NVIC判断后即可中断CPU主程序
触发方式:上升沿/下降沿/双边沿/软件触发
支持的GPIO口:所有GPIO口
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
引脚复用功能的重定义就是指管脚定义中的默认的其它功能
旋转编码器
作用:测量位置、速度、旋转方向
类型:机械触点式/霍尔传感式/光栈式
当正转时,A输出方波,B输出的波形比A的滞后90°,反转时反之
初始化函数:
void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
}
中断函数设置
/**
* 函 数:EXTI0外部中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
{
Encoder_Count --; //此方向定义为反转,计数变量自减
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
电路图:
7.TIM定时中断
TIM定时器:对输入的时钟进行计时,并在计数值达到设定值时触发中断
16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
所以STM32F103C8只有高级定时器和通用定时器
下方的三个部分组成时基电路,是最基本的计数计时电路
内部时钟CK_INT来自RCC的TIMxCLK,一般是系统主频72MHz,所以通向时基单元的计数基准频率是72MHz
PSC预分频器会对基准频率进行预分频,如果PSC写0,那就是不分频或者1分频,也就是72MHz,如果写1,那就是2分频,就是36MHz,以此类推
PSC是16位的,最大值65535,也就是65536分频
计数器每遇到一个上升沿,就会++,当计数器的值与自动重装寄存器相等时,就会触发更新中断UI,会通往NVIC,同时计数器清零
U是更新事件,U不会触发中断,但会触发内部其他电路的工作
通用定时器可以设置下降计数和中央对齐的计数模式,就是每一个下降沿和每一个上升沿+下降沿计数器++
通用定时器可以选择外部时钟,通过TIMx_ETR外设接入外部振荡电路
输出TRGO可以输出到其他定时器,ITR0~3就是来自其他定时器的4个TRGO输出
这部分电路可以实现级联的功能,级联就是将输出信号返回,扩大计时器的范围
高级定时器相对于通用定时器多出来的部分是服务于三相无刷电机的
CK_PSC是基准频率,CNT_EN是使能:高电平使能,CK_CNT是预分频后的时钟,寄存器到FC后清零触发更新事件,预分频缓冲器:若预分频的值改变,会缓冲到一次事件后触发,预分频计数器:到0时会触发CK_CNT的一次上升
UIF置1后,会去申请中断
有预装时序的计数器,F5被改成36会等在下一个事件后触发事件
RCC时钟树
时钟书有一个内部的8MHz的RC振荡和一个外部的一般是8MHz的晶振,在PLLMUL处倍频9倍,变为72MHz
CSS是时钟安全系统,当一个时钟坏掉时,会换成其他时钟
时基单元初始化的代码:
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
配置中断函数:
/**
* 函 数:TIM2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
{
Num ++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
关于电路:除了最小系统板和显示屏外就没了