STM32学习笔记
一、STM32简介
1.、STM32片上资源/外设
英文缩写 | 名称 | 英文缩写 | 名称 |
---|---|---|---|
NVIC | 嵌套向量中断控制器 | CAN | CAN通讯 |
SysTICK | 系统滴答定时器 | USB | USB通讯 |
RCC | 复位和时钟控制 | RTC | 实时时钟 |
GPIO | 通用IO口 | CRC | CRC校验 |
AFIO | 复用IO口 | PER | 电源控制 |
EXTI | 外部中断 | BKP | 备份寄存器 |
TIM | 定时器 | IWDG | 独立看门狗 |
ADC | 模数转换器 | WWDG | 窗口看门狗 |
DMA | 直接访问内存 | DAC | 数模转换器 |
USART | 同步/异步串口通讯 | SDIO | SD卡接口 |
I2C | I2C通讯 | FSMC | 可变静态存储控制器 |
SPI | SPI通讯 | USB OTG | USB主机接口 |
二、MDK新建工程
-
建立工程文件夹,在keil中新建工程,选择型号
-
工程文件中建立Start,Library,User等文件夹,复制固件库中的文件到工程文件夹
-
工程里建立对应Start,Library,User等同名称的分组,然后将文件夹内的文件添加到工程分组
-
依次点击魔法棒,C/C++,Include Paths内声明所有包含头文件的文件夹
-
依次点击魔法棒,C/C++,Define内定义USE_STDPERIPH_DRIVER
-
依次点击魔法棒,Debug,下拉列表选择对应调试器,Settings,Flash Download里勾选Reset and Run
-
复位中断:该部分为程序的主入口,当STM32复位后,程序进入复位中断函数中执行,复位中断主要调用SystemInit以及main
三,GPIO
第一章:GPIO通用输出输入
第一节:GPIO模式
- 通过配置GPIO的端口配置寄存器,端口可以配置以下8种模式
模式名称 | 性质 | 特征 | Code |
---|---|---|---|
浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空,则电平不稳定 | GPIO_Mode_IN_FLOATING |
上拉输入 | 数字输入 | 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 | GPIO_Mode_IPU |
下拉输入 | 数字输入 | 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 | GPIO_Mode_IPD |
模拟输入 | 模拟输入 | GPIO无效引脚直接进入内部ADC | GPIO_Mode_AIN |
开漏输出 | 数字输出 | 可引出引脚电平,高电平为高阻态,低电平接VSS | GPIO_Mode_Out_OD |
推挽输出 | 数字输出 | 可引出引脚电平,高电平接VDD,低电平接VSS | GPIO_Mode_Out_PP |
复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态。低电平接VSS | GPIO_Mode_AF_OD |
复用推挽输出 | 数字输出 | 由片上外设控制,高电平接VDD,低电平接VSS | GPIO_Mode_AF_PP |
知识点:
- 推挽输出高低电平都有驱动能力
- 开漏输出低电平有驱动能力,高电平则没有
项目一:点亮LED
#include "stm32f10x.h" // 引用STM32F10X头文件
int main(void)
{
//打开APB2时钟,因为LED连接的是PC13要使用GPIO C,所以要打开APB2时钟
//因为要使用CPIO C所以传入RCC_APB2Periph_GPIOC
//ENABLE意为:打开,因为要打开时钟所以输入ENABLE,关闭则输入DISABLEE
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
//定义GPIO结构体 名称为 GPIO_InitStructure
GPIO_InitTypeDef GPIO_InitStructure;
//对结构体进行引出
//mode:配置GPIO的工作模式 GPIO_Mode_Out_PP:推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
//Pin:配置GPIO的引脚 因为使用PC13所以填入
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_13;
//Speed:配置GPIO的速度 选择50MHz
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
//配置GPIO因为使用GPIOC所以第一部分填入GPIOC
//将结构体传入函数,参数是指针形式,所以添加取地址符“&”
GPIO_Init(GPIOC,&GPIO_InitStructure);
//让IO口输出高电平
//GPIO_SetBits(GPIOC,GPIO_Pin_13);
//让IO口输出低电平
//为告知所以将GPIOC,GPIO_Pin_13传入
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
while (1)
{
}
}
总结:
1.在使用外设之前,要先打开对应的总线时钟
2.初始化配置GPIO时,如果有不会使用的函数,可以跳转查看定义,也可以直接打开对应文件,去查看可以使用的函数
项目二:LED闪烁
#include "stm32f10x.h"
#include "Delay.h" //引用延时函数头文件
int main(void)
{
//开启APB2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//定义结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
//配置GPIO为推挽输出模式,选择0引脚,频率设为50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//对GPIO进行初始化配置
GPIO_Init(GPIOA, &GPIO_InitStructure);
while (1){
//点亮LED延时500MS后熄灭然后在点亮/熄灭继续循环
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(500);
}
}
项目三:LED流水灯
#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_All;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
while (1){
GPIO_Write(GPIOA,~0X0001); // 0000 0000 0000 0001
Delay_ms(500);
GPIO_Write(GPIOA,~0X0002); // 0000 0000 0000 0010
Delay_ms(500);
GPIO_Write(GPIOA,~0X0004); // 0000 0000 0000 0100
Delay_ms(500);
GPIO_Write(GPIOA,~0X0008); // 0000 0000 0000 1000
Delay_ms(500);
GPIO_Write(GPIOA,~0X0010); // 0000 0000 0001 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0X0020); // 0000 0000 0010 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0X0040); // 0000 0000 0100 0000
Delay_ms(500);
GPIO_Write(GPIOA,~0X0080); // 0000 0000 1000 0000
Delay_ms(500);
}
}
项目四:蜂鸣器
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
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);
while (1){
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
Delay_ms(500);
}
}
- 标注:因为使用有源蜂鸣器,所以只需通电便会发出叫声
项目五:按键控制LED
- 在工程文件下新建“Hardware”用于存放硬件驱动程序
- LED初始化
#include "stm32f10x.h" // Device header
//该函数为初始化LED
void LED_Init(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_1 |GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2);
}
//该函数为打开LED1
void LED1_NO(void){
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
//该函数为关闭LED1
void LED1_OFF(void){
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
//在调用该函数时,LED1的状态取反
void LED1_Tum(void){
//读取当前端口状态,如果当前状态为为1,则置0、如果为0,则值1;实现端口电平反转
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
}
void LED2_NO(void){
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
void LED2_OFF(void){
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
void LED2_Tum(void){
//读取当前端口状态,如果当前状态为为1,则置0、如果为0,则值1;实现端口电平反转
if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
}
}
- 配置头文件
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED1_NO(void);
void LED1_OFF(void);
void LED1_Tum(void);
void LED2_NO(void);
void LED2_OFF(void);
void LED2_Tum(void);
#endif
- 按键初始化
#include "stm32f10x.h" // Device header
#include "Delay.h"
//以下为按键初始化函数
void Key_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
//因为此处需要读取按键所以选择上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
//以下为读取按键函数
uint8_t Key_GetNum(void){
//按键初始化为0,如果没有按下按键返回0,
uint8_t KeyNum = 0;
//读取PB1端口值
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
//消抖
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum = 1;
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0)
{
//消抖
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
- 按键初始化头文件
#ifndef __KEY_H
#define __KEY_H
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
- main主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
//存储按键返回值
uint8_t KeyNum;
int main(void)
{
LED_Init();
Key_Init();
while(1)
{
//不断读取按键值赋值给KeyNum
KeyNum = Key_GetNum();
//当按键一按下
if(KeyNum == 1)
{
LED1_Tum();
}
if(KeyNum == 2)
{
LED2_Tum();
}
}
}
四、EXTI外部中断
第一章:EXTI简介
第一节:中断系统
- EXTI外部中断
- 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的的中断源
- 中断嵌套:当一个中断程序正在运行时,又有新的更高优先的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后在依次返回
第二节:STM32中断
1.68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设
2.使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级
第三节:NVIC优先级分组
1.NVIC的中断优先级由优先寄存器的4位(0-15)决定,这四位可以进行切分,分为高n位的抢占优先级和低4-n位的抢占优先级
2.抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
分组方式 | 抢占优先级 | 响应优先级 |
---|---|---|
分组0 | 0位,取值为0 | 4位,取值为0-15 |
分组1 | 1位,取值0-1 | 3位,取值为0-7 |
分组2 | 2位,取值为0-3 | 2位,取值为0-1 |
分组3 | 3位,取值为0-7 | 1位,取值为0-1 |
分组4 | 4位,取值为0-15 | 0位,取值为0 |
第四节:EXTI简介
- EXTI:外部中断
- EXTI可以检测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI立即向NVIC发送中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
- 支持的触发方式:上升沿/下降沿/双边沿/软件触发
- 支持的GPIO口:所有的GPIO口,但相同的pin不能同时触发中断
- 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
- 触发响应方式:中断响应/事件响应
第五节:配置EXTI中断步骤
1.配置RCC,打开所相关的外设时钟
2.配置GPIO,选择端口输入模式
3.配置AFIO,选择使用的GPIO连接下级EXTI
4.配置EXTI选择边沿触发模式。例如:上升沿,下降沿或双边沿
5.配置NVIC选择合适的中断优先级
第六节:EXTI中断配置
项目一:对射式红外传感器计数
void CountSensor_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_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
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 = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
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外设
}
项目二:旋转编码器计数
#include "stm32f10x.h" // Device header
int16_t Encoder_Count; //全局变量,用于计数旋转编码器的增量值
/**
* 函 数:旋转编码器初始化
* 参 数:无
* 返 回 值:无
*/
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外设
}
/**
* 函 数:旋转编码器获取增量值
* 参 数:无
* 返 回 值:自上此调用此函数后,旋转编码器的增量值
*/
int16_t Encoder_Get(void)
{
/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
/*在这里,也可以直接返回Encoder_Count
但这样就不是获取增量值的操作方法了
也可以实现功能,只是思路不一样*/
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}
/**
* 函 数: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号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
/**
* 函 数:EXTI1外部中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
{
Encoder_Count ++; //此方向定义为正转,计数变量自增
}
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
main主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t NUM;
int main(void)
{
Encoder_Init();
/*模块初始化*/
OLED_Init(); //OLED初始化
OLED_ShowString(1, 1, "NUM:"); //1行3列显示字符串HelloWorld!
while (1)
{
NUM += Encoder_Get();
OLED_ShowSignedNum(1,5,NUM,5);
}
}
五、TIM定时器中断
第一章:TIM简介
- TIM定时器
- 定时器可以对输入的时钟计数,并在计数值达到设定值后触发中断
- 16位计数器,预分频器,自动重装寄存器的时基单元,在72Mz计数时钟下可以实现最大59.65s的定时
- 不仅具备基本定时中断功能,而且还包含内外时钟选择,输入捕获,输出比较,编码器接口,主从触发模式等多种功能
- 根据应用场景分为:高级定时器,通用定时器,基础定时器三种类型
第二章:定时器类型
类型 | 编号 | 总线 | 功能 |
---|---|---|---|
高级定时器 | TIM1,TIM8 | APB2 | 拥有通用定时器的所有功能,并额外具有重复计数器,死区生成、互补输出、刹车输入等功能 |
通用定时器 | TIM2,TIM3,TIM4,TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外部时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6,TIM7 | APB1 | 拥有定时器中断,主模式触发DAC等功能 |
- 在STM32F103C8T6定时器资源有:TIM1,TIM2,TIM3,TIM4(芯片不同拥有的定时器资源也不同)
第三章:定时中断的基本结构
第四章:TIM输出比较
1.TIM输出比较简介
- OC输出比较
- 输出比较可以通过比较CNT与CCR寄存器值的关系,来对比输出电平进行置1,置0,或翻转的操作,用于输出一定的频率和占空比的PWM波形
- 每个高级定时器和通用定时器都拥有四个输出比较通道
- 高级定时器的前三个通道额外拥有四区生成和互补输出的功能
2.PWM简介
- PWM脉冲宽度调制
- 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效的获得所需要的模拟量,常用于电机控速等领域
- PWM参数
频率 = 1 /Ts 占空比 = T on/Ts 分辨率 = 占空比变化步距
3.输出比较模式
模式 | 描述 |
---|---|
冻结 | CNT=CCR时,REF保持为原始状态 |
匹配时置有效电平 | CNT=CCR时,REF置有效电平 |
匹配时置无效电平 | CNT=CCR时,REF置无效电平 |
匹配时电平反转 | CNT=CCR时,REF电平反转 |
强制为无效模式 | CNT与CCR无效时,REF强制转为无效电平 |
强制为有效模式 | CNT与CCR无效时,REF强制转为有效电平 |
PWM模式1 | 向上计数:CNT<CCR时,REF置有效电平,CNT>=CCR时,REF置无效电平 向下计数:CNT>CCR时,REF置无效电平,CNT<=CCR时,REF置有效电平 |
PWM模式2 | 向上计数:CNT<CCR时,REF置无效电平,CNT>=CCR时,REF置有效电平 向下计数:CNT>CCR时,REF置有效电平,CNT<=CCR时,REF置无效电平 |
4.参数计算
- PWM频率:Freq=CK_PSC/(PSC+1)/(ARR+1)
- PWM占空比:Duty=CCR/(ARR+1)
- PWM分辨率:Reso=1/(ARR+1)
5.舵机简介
- 舵机是一种根据输入PWM信号占空比来控制输出角度的装置
- 输入PWM信号要求:周期为20s,高电平宽带如为0.5-2.5s
输入信号脉冲宽度 | 舵机输出轴转角 |
---|---|
0.5ms | -90 |
1ms | -45 |
1.5ms | -0 |
2ms | 45 |
2.5 | 90 |
6.直流电机及驱动简介
- 直流电机是一种将电能转化为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
- 直流电机属于大功率器件,GPIO无法直接驱动,需要配合电机驱动电机来进行操作
- TB6612是一款双路H桥形直流电机驱动芯片,可以驱动两个直流电机并控制其转速及方向
项目一:定时器定时中断
六、ADC采集
一、ADC简介
- ADC 模拟-数字转换器
- ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
- 12位逐次逼近型ADC,1us转换时间
- 输入电压范围:0-3.3V,转换结果范围:0-4095
- 18个输入通道,可测量16个外部和2个内部信号源
- 规则组和注入组两个转换的单元
- 模拟看门狗自动检测输入电压范围
- STM32F103C8T6 ADC资源:ADC1,ADC2,10个外部通道
二、转换时间
- AD转换的步骤:采样,保持,量化,编码
- STM32 ADC的总转换时间为 Tconv=采样时间+12.5个ADC周期
- 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
Tconv=1.5+12.5=14个ADC周期=1us
七、DMA
一、DMA简介
- DMA 直接存储器存取
- DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无需CPU干预,节省了CPU资源
- 12个独立可配置通道:DMA1(7个通道),DMA2(5个通道)
- 每个通道支持独立的软件触发和特定的硬件触发
- STM32F10C8T6 DMA资源:DMA1(7个通道)
二、存储器映像
类型 | 起始地址 | 存储器 | 用途 |
---|---|---|---|
0X0800 0000 | 程序存储器Flash | 存储C语言编译后的程序代码 | |
ROM | 0X1FFF F000 | 系统存储器 | 存储BootLoader,用于串口下载 |
0X1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 | |
0X2000 0000 | 运行内存SRAM | 存储运行中的临时变量 | |
RAM | 0X4000 0000 | 外设寄存器 | 存储各个外设的配置参数 |
0XE000 0000 | 内核外设寄存器 | 存储内核各个外设的配置参数 |
八、USART
一、通信接口
- 通讯的目的:将一个设备的数据传输到另外一个设备,扩展硬件系统
- 通讯协议:制定通讯的规则,通讯双方按照协议规则进行数据收发
名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
---|---|---|---|---|---|
USART | TX,RX | 全双工 | 异步 | 单端 | 点对点 |
I2C | SCL,SDA | 半双工 | 同步 | 单端 | 多设备 |
SPI | SCLK,MOSI,MISO.CS | 全双工 | 同步 | 单端 | 多设备 |
CAN | CAN_H,CAN_L | 半双工 | 异步 | 差分 | 多设备 |
USB | DP,DM | 半双工 | 异步 | 差分 | 点对点 |
二、硬件电路
- 简单双向串口通讯有两根通讯线(发送端和接收端)
- TX与RX要交叉连接
- 当只需单向的数据传输时,可以只接一根通讯线
- 当电平标准不一致时,需要加电平转换芯片
三、电平标准
-
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的关系,串口常用的电平标准有以下三种
-
TTL电平:+3.3V或5V表示1,0V表示0
-
RS232:-3~-15V表示1,+3-+15v表示0
-
RS485;两线压差+2-+6表示1,-2~-6表示0
四、串口参数及时序
- 波特率:串口通讯的速率
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
四、USART简介
- USART 同步/异步收发器
- USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX发出去,也可自动从RX接受引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器中
- 自带波特率发生器,最高可达4.5Mbit/s
- 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
- 支持同步模式,硬件流控制,DMA、智能卡、IrDA、LIN
- STM32C8T6 USART资源:USART1,USART2,USART3
五、波特率检测器
- 发送器和接受器的波特率由波特率寄存器BRR里的DIV确定
- 计算公式:波特率 = Fpclk2/1(16*DIV)
| 多设备 |
| USB | DP,DM | 半双工 | 异步 | 差分 | 点对点 |
二、硬件电路
- 简单双向串口通讯有两根通讯线(发送端和接收端)
- TX与RX要交叉连接
- 当只需单向的数据传输时,可以只接一根通讯线
- 当电平标准不一致时,需要加电平转换芯片
三、电平标准
-
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的关系,串口常用的电平标准有以下三种
-
TTL电平:+3.3V或5V表示1,0V表示0
-
RS232:-3~-15V表示1,+3-+15v表示0
-
RS485;两线压差+2-+6表示1,-2~-6表示0
四、串口参数及时序
- 波特率:串口通讯的速率
- 起始位:标志一个数据帧的开始,固定为低电平
- 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
- 校验位:用于数据验证,根据数据位计算得来
- 停止位:用于数据帧间隔,固定为高电平
四、USART简介
- USART 同步/异步收发器
- USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX发出去,也可自动从RX接受引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器中
- 自带波特率发生器,最高可达4.5Mbit/s
- 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
- 支持同步模式,硬件流控制,DMA、智能卡、IrDA、LIN
- STM32C8T6 USART资源:USART1,USART2,USART3
五、波特率检测器
- 发送器和接受器的波特率由波特率寄存器BRR里的DIV确定
- 计算公式:波特率 = Fpclk2/1(16*DIV)