type
status
date
slug
summary
tags
category
icon
password
STM32
新建工程
- 建立工程文件夹,在Keil中新建工程,选择单片机的型号
- 在工程文件夹中建立Library、Start、User等文件夹,复制固件库里面的文件到工程文件夹
- 在Keil工程里建立Library、Start、User等同名称的分组,然后将文件夹内的文件添加到工程分组内
- 在工程选项中,在C/C++里,在Include Paths内声明所有包含头文件的文件夹
- 在工程选项中,在C/C++里,Define内定义USESTDPERIPHDRIVER(使用标准外设驱动)
- 在工程选项中,在Debug中的下拉列表内选择对应调试器(目前为STlink),然后在Setting中,在Flash Download里勾选Reset and Run
常用库函数
- RCC
//AHB外设时钟控制 //第一个参数填入需要控制的端口,第二个参数选择端口状态,使能或失能 void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
//APB2外设时钟控制 //第一个参数填入需要控制的端口,第二个参数选择端口状态,使能或失能 void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
//APB1外设时钟控制 //第一个参数填入需要控制的端口,第二个参数选择端口状态,使能或失能 void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
- GPIO
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
标准外设库就是库函数
GPIOC代表pc13口
GPIOModeOut_PP通用推挽输出
GPIOSetBits(GPIOC,GPIOPin_13);将pc13口置为高电平
GPIOResetBits(GPIOC,GPIOPin_13);将pc13口置为低电平
启动器选择
建立工程的步骤
工程架构
这样就可以使用所有库函数了
gpio简介
gpio模式
led长脚是正极
蜂鸣器低电平触发(灯也是低电平点亮,好像大部分设备都是低电平触发,可以理解为设备的一端已经接上了高电平,然后stm提供一个低电平就能形成电压差了,这个是低电平驱动的原理,高电平驱动就是把设备的一端先接地,其余相反即可。单片机中一般选取低电平驱动,因为很多单片机都采取了高电平弱驱动,低电平强驱动)
led和蜂鸣器的硬件原理图
面包板的连接方式
下面两根条线实际上没什么用,只是为了让下面也能供电
操作gpio一共有三个步骤
1.使用rcc开启gpio的时钟
2.使用gpio_init初始化gpio
3.使用输入或输出函数控制gpio口
实际上就是之前的点灯操作,那个时候的输出函数就是
GPIOSetBits(GPIOC,GPIOPin13);
GPIOResetBits(GPIOC,GPIOPin13);
gpio常用函数
GPIO的基本结构
系统结构,之后不知道是apb1还是apb2就看这个图就好了(apb12都是外设总线)
函数参数里面的enable表示开启,disable表示关闭
这里展示了GPIO_Init函数中的几个参数的值代表的含义
其中mode参数表示GPIO的八种工作模式
推挽模式高低电平都有驱动能力,说明不论是高电平灯亮还是低电平灯亮都是可以的
开漏输出模式高电平没有驱动能力(高电平时相当于高阻态),说明高电平灯亮无法通过开漏输出模式做到
一般输出都用推挽输出
上拉下拉输入模式实际上就是悬空状态下默认电平分别为高电平和低电平
通常都是低电平有操作,有操作输入低电平
所以按钮一般都是接地的,按下时可以将信号下拉至GND,就是低电平,而输出设备比如电灯泡一般就是接高电平,这样输出低电平的时候就有电压差了
pin实际上就是引脚的编号
GPIOSetBits(GPIOC,GPIOPin_13);
可以将指定的端口设置为高电平
GPIOResetBits(GPIOC,GPIOPin_13);
可以将指定的端口设置为低电平
函数GPIO_WriteBit
根据前两个参数的值来制定端口,最后一个参数设置端口(设置端口输出高电平还是低电平)
函数GPIO_Write
支持多端口写入,后一个参数写入端口编号,应该是输出默认电平,默认电平是高电平,通过按位取反(~号)可以调整高低电平(取反后电平变为低电平)
主循环实际上就是写要干的事情的
这是用来添加头文件的,就是让程序运行的时候可以从这个path中找到头文件
这是用来导入文件的
流水灯闪烁实际上就是反复闪烁
可以同时初始化所有端口,是通过按位或的操作实现的,每一个端口对应二进制的一个位
pin可以通过按位或初始化多个,rcc时钟也可以通过按位或的操作控制多个外设
这三个引脚先别选,因为这三个引脚跟普通的io口不同
入门如何使用库函数
1.看头文件右键转到定义
2.查看库函数用户使用手册
3.百度
就是基尔霍夫分压定律
基本输入的硬件电路
do是数字输出口,只有高电平和低电平,也就是1和0
ao就是模拟电路输出,有具体的电压值
stm32中的数据类型
枚举的使用
头文件一般只是进行函数声明
c文件里面写主体代码
头文件的开头要写这些东西
uint8t GPIOReadInputDataBit(GPIOTypeDef* GPIOx, uint16t GPIOPin);
uint16t GPIOReadInputData(GPIOTypeDef* GPIOx);
与输出函数的用法几乎相同,返回值代表高低电平。低电平就返回0,高电平就返回1
uint16t GPIOReadInputData(GPIO_TypeDef* GPIOx);
用来读取整个寄存器的输入
所有文件的最后一行一定要是空行
GPIO的mode应该填out的pp,而不是af的pp
注意按键按下和按键松开后都是有抖动的,一定要消抖,否则会导致LED灯频闪
光敏电阻遮光灯灭输出高电平,有光灯亮输出低电平
蜂鸣器低电平响
HardWare文件夹里面写每个硬件的驱动函数
点灯调试
注释调试
显示器硬件电路
OLED常用函数
只需要改变头两行代码和下面的引脚初始化
分别表示scl和sda分别接在哪个口上
勾选这个选项就是用电脑模拟stm32
调试按钮
这个调试stm32也是跟着调试步骤一步步来走的
不能在调试模式下直接修改
keil调试在OLED显示这节课中
就是让cpu停下来先执行中断程序
中断子函数一般不需要我们自己调用,而是由硬件直接调用
中断和if语句的区别就是,if一定要到if语句的地方才能开始执行,而中断随时都能执行
stm32中断有很多
抢占优先级可以打断当前执行步骤,而响应优先级是先等待当前步骤执行完毕
所以只有抢占才能出现中断嵌套
中断的执行
有外部电平变化就进行中断申请(外部中断)
这里要注意相同的pin不能同时触发中断(如pa0和pb0不能同时触发中断)
触发方式
中断响应就是默认将信号传给cpu,让cpu进行处理;事件响应就是可以不将信号传给cpu,而是选择执行一个事件,此时就没有触发中断
由于存在引脚选择,所以pin不能重复
9到5触发同一个函数,15到10触发同一个函数
afio作用,也可以将引脚功能转至重定义功能模块
外部中断内部硬件逻辑
如果想要获取的信号是外部信号并且变化很快的情况就需要使用外部中断
但是不建议使用外部中断来控制按键,因为外部中断没办法处理抖动的问题,并且按键导致的信号变化可能并不快
旋转编码器是通过相位差来进行旋转方向判断的
相差90度的波形就是正交波形,是可以用来测方向的
旋转编码器的内部电路图
因为a口右边断路,所以被上拉
外部中断需要配置的外设(其实就是把外部中断的路打通)
配置外部中断
- 配置RCC外设时钟,不打开RCC时钟,外设就没办法工作
- 配置GPIO将端口设置为输入模式
- 配置AFIO,将选择的GPIO连接到后面的外部中断外设
- 配置EXTI选择中断模式(上升沿中断,下降沿中断等),还有响应方式(中断响应或者是事件响应)
- 配置NVIC选择一个合适的中断优先级
GPIOA、B、C都是接在APB2总线上的,AFIO也是接在APB2总线上的
EXTI和NVIC不需要开启时钟,因为已经开启了
NVIC是内核外设,内核外设都不要开启时钟
RCC管理内核外的外设
外设借用GPIO口的时候不知道需要什么输入输出模式,就去查手册
倒数两个函数用在中断函数内部
倒数第三第四个函数用在主函数内部
都是用来查看当前标志位是否被置为1了
EXTI结构体中的中断线与借用的GPIO引脚是一一对应关系(可以理解为这个是从EXIT中引出的先)
整个芯片NVIC优先级分组只能有一个(分几位抢占几位响应)
NVIC选择这个中断通道(通道跟中断线一一对应关系,可以理解为这个是从EXTI中接收的线,要确保通道一致)
中断函数格式
通常在调用中断函数前(特别是通道集成的中断函数)要判断一下当前中断标志为是否被置为1,中断函数调用结束之后也要将当前中断标志位置为0
EXTI和NVIC都是16位的
中断函数不需要放到头文件中,因为中断函数是自动声明、自动执行的
电位由高变低就是下降沿触发,由低变高就是上升沿触发
对射式红外传感器未遮挡时输出低电平,遮挡时输出高电平
NVIC的1234号中断通道每种型号的stm32f1芯片都有,所以写在最上面
当前文件一定要编译之后才能在调试中生效
正转和反转的判断
中断编程的建议
- 中断代码中不放耗时太长的代码
- 不要在中断函数和主函数中使用同一个函数或者调用同一个硬件
定时器简介
定时器类型
三种计数模式
外部时钟引脚
定时中断基本结构
预分频器时序
预分频缓冲器才是真正的寄存器
输出信号也是通过计数控制的
外部晶振比内部时钟更快
其中的与门就是RCC时钟函数使能的地方
定时中断在stm32内部,这里是调用内部的定时器,之后也可以用外部时钟中断,就是外部时钟模式2了
初始化定时器实际上就是打通这条路
- 开启RCC外设时钟
- 选择时基单元的时钟源(定时中断选择内部时钟源)
- 配置时基单元
- 配置中断输出控制
- 配置NVIC
- 运行控制配置(启动定时器)
void TIMDeInit(TIMTypeDef* TIMx);恢复默认配置
void TIMTimeBaseInit(TIMTypeDef* TIMx, TIMTimeBaseInitTypeDef* TIMTimeBaseInitStruct);时基单元初始化
void TIMTimeBaseStructInit(TIMTimeBaseInitTypeDef* TIM_TimeBaseInitStruct);给结构体变量赋一个默认值
void TIMCmd(TIMTypeDef* TIMx, FunctionalState NewState);用来使能计数器,配置的是运行控制部分
void TIMITConfig(TIMTypeDef* TIMx, uint16t TIMIT, FunctionalState NewState);用来使能中断输出信号
ITConfig函数就是用来使能外设中断输出的
下面这些函数时用来选择时钟源的(就是时钟选择部分)TIx是选择要配置的定时器的(这里面没有编码模式,前五个函数对应五个时钟源选择的线路)
void TIMInternalClockConfig(TIMTypeDef* TIMx);选择内部时钟
void TIMITRxExternalClockConfig(TIMTypeDef* TIMx, uint16t TIMInputTriggerSource);选择ITRx其他定时器的时钟
void TIMTIxExternalClockConfig(TIMTypeDef* TIMx, uint16t TIMTIxExternalCLKSource,
uint16t TIMICPolarity, uint16t ICFilter);选择TIx捕获通道的时钟
void TIMETRClockMode1Config(TIMTypeDef* TIMx, uint16t TIMExtTRGPrescaler, uint16t TIMExtTRGPolarity,
uint16t ExtTRGFilter);选择ETR外部时钟模式1
void TIMETRClockMode2Config(TIMTypeDef* TIMx, uint16t TIMExtTRGPrescaler,
uint16t TIMExtTRGPolarity, uint16t ExtTRGFilter);选择ETR外部时钟模式2
void TIMETRConfig(TIMTypeDef* TIMx, uint16t TIMExtTRGPrescaler, uint16t TIMExtTRGPolarity,
uint16t ExtTRGFilter);用来配置ETR引脚的预分频器、极性、滤波器等参数
void TIMPrescalerConfig(TIMTypeDef* TIMx, uint16t Prescaler, uint16t TIM_PSCReloadMode);用来单独写预分频值,第二个值输入的模式就与缓冲器有关系
void TIMCounterModeConfig(TIMTypeDef* TIMx, uint16t TIMCounterMode);用来改变计数器的计数模式
void TIMARRPreloadConfig(TIMTypeDef* TIMx, FunctionalState NewState);自动重装器预装功能配置(预装就跟影子寄存器很像)
void TIMSetCounter(TIMTypeDef* TIMx, uint16_t Counter);给计数器写入一个值
void TIMSetAutoreload(TIMTypeDef* TIMx, uint16_t Autoreload);给自动重装器写入一个值(自动重装器是用来设定阈值的)
uint16t TIMGetCounter(TIM_TypeDef* TIMx);获取当前计数器的值
uint16t TIMGetPrescaler(TIM_TypeDef* TIMx);获取当前预分频器的值
下面四个函数是用来获取和清除中断标志位的(前两个用在主函数中,后两个用在中断函数中)
FlagStatus TIMGetFlagStatus(TIMTypeDef* TIMx, uint16t TIMFLAG);
void TIMClearFlag(TIMTypeDef* TIMx, uint16t TIMFLAG);
ITStatus TIMGetITStatus(TIMTypeDef* TIMx, uint16t TIMIT);
void TIMClearITPendingBit(TIMTypeDef* TIMx, uint16t TIMIT);
默认使用内部时钟
滤波器是间隔相同时间进行一次采样,如果采样稳定,就说明输入信号稳定
重复计数器只有高级定时器
一个数的偏差是因为从零开始计数
计数器重置就是一次更新,所以算更新中断
预分频器的更新也会导致更新中断(也是电平的变化)
中断函数在启动文件内,因为只有一个,所以在文件启动的时候直接声明
TIMx_ETR表示外部时钟引脚
TIMx_CH1表示x时钟的通道1
ITRx就是用来接收其他时钟源的数据的,可以用来做定时器级联
外部滤波器的取值意义
不要将自动装载器的阈值设定为1,这样会导致计数器的值一直都是0,这样就没办法触发更新中断了
内存的地址映射
pwm波形可以驱动电机
最后一句话说模拟值可以用pwm进行编码,所以可以通过pwm做到将数字信号转化为模拟信号,就可以做出呼吸灯的效果
输出比较简介
输出比较就是用来输出pwm波形的
比较的是计数器和CCR(输入输出寄存器)的值,根据不同情况输出1或者0
pwm简介
pwm也是一个数字输出信号,只不过通过占空比来模拟模拟信号
pwm就是频繁输出电平信号,只要足够快就可以实现类似模拟信号的效果
惯性系统,就是具有一定的惯性,比如LED灯会有余晖,电机停转之后还会继续转动
pwm频率越大,模拟效果就越好,但是性能开销也越大(通过时钟配置频率),通常使用1kHz
等效电电压与占空比一般是线性的
通用输出比较
ref是参考信号的意思
输出模式控制器中的几种输出模式
其中冻结就等价为cnt和ccr无效,ref一直保持原状态
匹配时电平翻转可以配置一个占空比50频率可调的波形
pwm基本结构,这部分就是设置频率占空比和分辨率的
ccr来设置占空比,cnt来设置频率
pwm的频率对应一个更新频率
分辨率就是ccr的最小变化(就是计数器的1)占比,ccr的范围取决于cnt的值
舵机的简介
这里舵机是把pwm波形当做一个通信协议来使用,这也是pwm一种常见的使用
舵机三线的对应关系
两个gnd要连通保证是一个回路
能用stlink供电是因为stm32也是用stlink供电,这样其实stm32和舵机也是共地的
直流电机和驱动的简介
因为属于大功率器件,所以不能直接通过GPIO口驱动,要外接电路来进行驱动‘
驱动电路的硬件电路
右下角的图是上面三个引脚的输入输出逻辑
看寄存器描述
CH就是pwm的输出比较通道
复位引脚上电之后就是高电平,之前pa0接上就亮是因为前一个程序将pa0口设置为了上拉输入模式
pwm的初始化就是打通这幅图
- RCC打开外设时钟
- 配置时基单元,时基单元是依托前面的时钟源的,所以前面的时钟源初始化也要做
- 配置输出比较单元,包括ccr,输出比较模式,还有极性选择,输出使能等
- 配置GPIO,配置为复用推挽输出的模式
- 运行控制,就是启动计数器
下面四个函数用来配置输出比较模块,1234对应通道1234
void TIMOC1Init(TIMTypeDef* TIMx, TIMOCInitTypeDef* TIMOCInitStruct);
void TIMOC2Init(TIMTypeDef* TIMx, TIMOCInitTypeDef* TIMOCInitStruct);
void TIMOC3Init(TIMTypeDef* TIMx, TIMOCInitTypeDef* TIMOCInitStruct);
void TIMOC4Init(TIMTypeDef* TIMx, TIMOCInitTypeDef* TIMOCInitStruct);
用来给输出比较结构体赋一个默认值
void TIMOCStructInit(TIMOCInitTypeDef* TIM_OCInitStruct);
配置强制输出模式(强制输出高电平或者低电平,对应输出比较的两种强制模式)
void TIMForcedOC1Config(TIMTypeDef* TIMx, uint16t TIMForcedAction);
void TIMForcedOC2Config(TIMTypeDef* TIMx, uint16t TIMForcedAction);
void TIMForcedOC3Config(TIMTypeDef* TIMx, uint16t TIMForcedAction);
void TIMForcedOC4Config(TIMTypeDef* TIMx, uint16t TIMForcedAction);
配置ccr预装功能,实际上就是影子寄存器(写入的值不会立即生效,而是在发生更新事件之后生效)
void TIMOC1PreloadConfig(TIMTypeDef* TIMx, uint16t TIMOCPreload);
void TIMOC2PreloadConfig(TIMTypeDef* TIMx, uint16t TIMOCPreload);
void TIMOC3PreloadConfig(TIMTypeDef* TIMx, uint16t TIMOCPreload);
void TIMOC4PreloadConfig(TIMTypeDef* TIMx, uint16t TIMOCPreload);
配置快速使能
void TIMOC1FastConfig(TIMTypeDef* TIMx, uint16t TIMOCFast);
void TIMOC2FastConfig(TIMTypeDef* TIMx, uint16t TIMOCFast);
void TIMOC3FastConfig(TIMTypeDef* TIMx, uint16t TIMOCFast);
void TIMOC4FastConfig(TIMTypeDef* TIMx, uint16t TIMOCFast);
外部事件时清除ref信号
void TIMClearOC1Ref(TIMTypeDef* TIMx, uint16t TIMOCClear);
void TIMClearOC2Ref(TIMTypeDef* TIMx, uint16t TIMOCClear);
void TIMClearOC3Ref(TIMTypeDef* TIMx, uint16t TIMOCClear);
void TIMClearOC4Ref(TIMTypeDef* TIMx, uint16t TIMOCClear);
配置极性或者说修改极性,因为结构体初始化的时候有极性这一个参数(带n的就是高级定时器中互补通道的配置,所以没有oc4n)
void TIMOC1PolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCPolarity);
void TIMOC1NPolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCNPolarity);
void TIMOC2PolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCPolarity);
void TIMOC2NPolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCNPolarity);
void TIMOC3PolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCPolarity);
void TIMOC3NPolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCNPolarity);
void TIMOC4PolarityConfig(TIMTypeDef* TIMx, uint16t TIMOCPolarity);
用来单独修改输出使能函数
void TIMCCxCmd(TIMTypeDef* TIMx, uint16t TIMChannel, uint16t TIMCCx);
void TIMCCxNCmd(TIMTypeDef* TIMx, uint16t TIMChannel, uint16t TIMCCxN);
用来单独修改输出比较模式
void TIMSelectOCxM(TIMTypeDef* TIMx, uint16t TIMChannel, uint16t TIMOCMode);
用来单独修改CCR寄存器的值(用来运行时更改占空比)
void TIMSetCompare1(TIMTypeDef* TIMx, uint16t Compare1);
void TIMSetCompare2(TIMTypeDef* TIMx, uint16t Compare2);
void TIMSetCompare3(TIMTypeDef* TIMx, uint16t Compare3);
void TIMSetCompare4(TIMTypeDef* TIMx, uint16t Compare4);
这个函数仅在调用高级定时器,调用高级定时器输出pwm时要调用这个函数,否则pwm不能正常输出
void TIMCtrlPWMOutputs(TIMTypeDef* TIMx, FunctionalState NewState);
上面的函数大部分都是用来单独修改初始化结构体中的值的
重映射功能就是通过AFIO完成的(afio,引脚选择器)
默认的开漏输出和推挽输出是由数据输出寄存器控制的,而这里输出pwm波形需要用时钟来控制引脚,所以要选择用复用输出模式,这里选择复用推挽输出,这个时候数据的输出源就是tim2的ch1
对于同一个时钟的不同通道频率要一样,计数器跳变的时候PWM同时更新,使得每个通道PWM输出的相位必须一样
- 作者:Noah
- 链接:https://imnoah.top/article/STM32F103
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。