【STM32】江科大STM32小白养成记
本文章主要记录本人在学习stm32过程中的笔记,我是看的江科大的视频,其中也插入了不少的例程代码。绝大多数内容为本人手写,代码部分大多来自江科大的例程,注释是我自己添加,如果有错误,欢迎提出!
(PS:目前我还是个小菜鸡,有没有一起学习的呀?哈哈哈哈哈哈)
Part 1 空白项目创建
1.右上角”Project”->”New uVision Project”
2.新建文件,创建项目名称
3.进入新建文件夹,下面“文件名”叫做”Project”,保存
4.选择芯片型号,”STMicroelectronics”->”STM32F1 Series”->”STM32F103”->”STM32F103C8”,点击“OK”,关闭自动弹出的新建工程小助手
5.打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Libraries”->“CMSIS”->“CM3”->“DeviceSupport”->“ST”->“STM32F10x”->“startup”->“arm”,内含文件为启动文件,复制所有文件
6.找到刚才的新建工程项目文件夹,在里面新建文件夹“Start”,把刚才复制的所有文件全部粘贴到“Start”文件夹
7.然会返回固件库的文件夹,到“STM32F10x”,复制“stm32f10x.h”、“system_stm32f10x.c”、“system_stm32f10x.h”,粘贴到新建的“Start”文件夹下
“stm32f10x.h”:外设寄存器描述文件
“system_stm32f10x.c”、“system_stm32f10x.h”:配置时钟
8.添加内核寄存器的描述文件,打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Libraries”->“CMSIS”->“CM3”->“CoreSupport”,全选复制“core_cm3.c”、“core_cm3.h”,粘贴到“Start”文件夹下
9.打开Keil软件,点击选中“Source Group 1”,单击,重命名为“Start”,然后右键,选择“Add Existing Files to Group ‘ Start’... ”,打开“Start”文件夹,将下面的“文件类型(T)”选择“All Files(*.*)”,然后添加启动文件,F103C8T6型号选择“md.s”的启动文件,然后将文件里所有以“.c”和“.h”的文件全部添加,然后点击“Add”,然后“Close”
10.点击Keil软件的“”,打开工程选项,选择“C/C++”,选择下面“InClude Paths”的右边“...”,新建路径,然后再点“...”,选择“Start”文件夹,点击“OK”
11.打开新建的项目文件夹,新建项目文件夹“User”。在Keil左边选择“Target”->“Add Group”,把“New Group”重命名为“User”,右键“User”,点击“Add New Item to Group ‘User’”,选择“C file(.c)”,“Name”那一栏叫“main”,“Location”选择“User”文件夹,点击“Add”
12.在“main.c”的文件中右键,选择“Insert ‘#include file ’”->“stm32f10x.h”。将main函数初始化。文件最后一行必须是空行,否则会报错
13.配置调试器,点击“”,选择“Debug”,右上角选择驱动“ST-Link Debugger”,点击右边的“Settings”,选择“Flash DownLoad”,勾选“Reset and Run”,点击“确认”->“OK”
14.打开工程文件夹,新建文件夹“Library”用来存放库函数,然后打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Libraries”->“STM32F10x_StdPeriph_Driver”->“src”,全选复制到“Library”文件夹粘贴,然后打开固件库的“inc”文件夹,全选复制到“Library”文件夹粘贴
15.回到Keil软件,在Keil左边选择“Target”->“Add Group”,把“New Group”重命名为“Library”,右键“Library”,点击“Add Existing Files to Group ‘Library’”,打开“Library”文件夹,全选,然后“Add”,“Close”
16.打开“资料”->“固件库”->“STM32F10x_StdPeriph_Lib_V3.5.0”->“Project”->“STM32F10x_Stdperiph_Template”,复制“stm32f10x_conf.h”、“stm32f10x_it.c”、“stm32f10x_it.h”到新建工程的“User”目录之下,回到Keil软件,右键“User”选择“Add Existing Files to Group ‘User’”,选择“stm32f10x_conf.h”、“stm32f10x_it.c”、“stm32f10x_it.h”,点击“Add”,“Close”
17.点击Keil软件的“”,选择“C/C++”,选择“Define”栏,粘贴“USE_STDPERIPH_DRIVER”,选择下面“InClude Paths”的右边“...”,新建路径,然后再点“...”,选择“User”、“Library”文件夹,点击“OK”
如此,项目就建好啦!!!但是后面更多的是复制之前的项目,毕竟新建空项目可太麻烦了!
Part 2 GPIO输出
RCC寄存器用来使能GPIO的时钟,GPIO都是APB2的外设。
第一步:配置使能时钟:RCC_APB2PeriphClockCmd(选择外设,ENABLE/DISENABLE)
第二步:配置端口模式:GPIO_Init(选择GPIO,&参数的结构体)
初始化结构体:GPIO_InitTypeDef GIOP_InitStructure
GIOP_InitStructure.GPIO_Mode = 输出模式
GIOP_InitStructure.GPIO_Pin = 输出引脚
GIOP_InitStructure.GPIO_Speed = 输出速度
第三步:设置GPIO的高低电平
端口的8种模式:
GPIO_DeInit:复位外设
GPIO_AFIOInit:复位AFIO外设
GPIO_Init:用结构体的参数来初始化GPIOk口
GPIO_StructInit:将结构体变量赋一个默认值
GPIO输出函数:
GPIO_SetBits:将指定端口设置为高电平
GPIO_ResetBits:将指定端口设置为低电平
GPIO_WriteBit:根据第三个参数的值来设置指定端口
GPIO_Write:同时对16个端口进行写入操作
ADC:从引脚直接接入片上外设
推挽输出和开漏输出的选择
使用推挽
1.驱动能力需求较高的场合
2.高速信号传输
3.无需共用信号线的场合
使用开漏
1.多个设备共用信号线
2.不同电压系统之间的接口
3.需要外部上拉电阻来确定逻辑高电平的场合
接下来‘就是我们第一个点灯程序(本人当时超级兴奋的,好吧?)
#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_0;//A0口GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度//-------------------------------------------------------------GPIO_Init(GPIOA,&GPIO_InitStructure);//GPIO_ResetBits(GPIOA, GPIO_Pin_0);//A0口while(1){GPIO_WriteBit(GPIOA, GPIO_Pin_0,Bit_RESET);//点亮Delay_ms(500);//延迟500msGPIO_WriteBit(GPIOA, GPIO_Pin_0,Bit_SET);//熄灭Delay_ms(500);//延迟500ms}}
接下来是流水灯,超级好看!!!!我用的红黄蓝绿交替的,一共是八个灯珠,当然,大家要是有时间可以发挥想象,成为 新一代点灯大师,哈哈哈哈~~~~~~~
但是流水灯需要用到Delay函数,有心的江老师(江科大)已经为我们准备好了!这是源文件
#include \"stm32f10x.h\"/** * @brief 微秒级延时 * @param xus 延时时长,范围:0~233015 * @retval 无 */void Delay_us(uint32_t xus){SysTick->LOAD = 72 * xus;//设置定时器重装值SysTick->VAL = 0x00;//清空当前计数值SysTick->CTRL = 0x00000005;//设置时钟源为HCLK,启动定时器while(!(SysTick->CTRL & 0x00010000));//等待计数到0SysTick->CTRL = 0x00000004;//关闭定时器}/** * @brief 毫秒级延时 * @param xms 延时时长,范围:0~4294967295 * @retval 无 */void Delay_ms(uint32_t xms){while(xms--){Delay_us(1000);}} /** * @brief 秒级延时 * @param xs 延时时长,范围:0~4294967295 * @retval 无 */void Delay_s(uint32_t xs){while(xs--){Delay_ms(1000);}}
还有头文件
#ifndef __DELAY_H#define __DELAY_Hvoid Delay_us(uint32_t us);void Delay_ms(uint32_t ms);void Delay_s(uint32_t s);#endif
以及最主要的流水灯程序
#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(100);//延迟500msGPIO_Write(GPIOA, ~0x0002);//0000 0000 0000 0010点亮第二个 低电平点亮Delay_ms(100);//延迟500msGPIO_Write(GPIOA, ~0x0004);//0000 0000 0000 0100点亮第三个 低电平点亮Delay_ms(100);//延迟500msGPIO_Write(GPIOA, ~0x0008);//0000 0000 0000 1000点亮第四个 低电平点亮Delay_ms(100);//延迟500msGPIO_Write(GPIOA, ~0x0010);//0000 0000 0001 0000点亮第五个 低电平点亮Delay_ms(100);//延迟500msGPIO_Write(GPIOA, ~0x0020);//0000 0000 0010 0000点亮第六个 低电平点亮Delay_ms(100);//延迟500msGPIO_Write(GPIOA, ~0x0040);//0000 0000 0100 0000点亮第七个 低电平点亮Delay_ms(100);//延迟500msGPIO_Write(GPIOA, ~0x0080);//0000 0000 1000 0000点亮第八个 低电平点亮Delay_ms(100);//延迟500ms}}
这里运用到了一点点数电的知识,二进制码,逢1进0哈!比如10就是2,11是3,100是4,以此类推。
Part 3 GPIO输入
大家可以看看,在单片机里,咱们不用C语言那一套,我们定义变量看的是stdint关键字,大家可以在后面的代码中看到。
浅浅解释一下函数哈!
GPIO_ReadInputDataBit:读取数据寄存器某一端口的输入值
GPIO_ReadInputData:读取整个输入寄存器
GPIO_ReadOutputDataBit:读取数据寄存器某一端口的输出值(用于输出模式)
GPIO_ReadOutputData:读取整个输出寄存器
调用这些函数前,我们要分清楚一个项目输入是哪些,输出是哪些。比如,在我们按键控制LED的项目中,输入是按键,而输出是LED,对于按键的状态,我们就能够调用GPIO_ReadInputDataBit函数,对于灯的状态,我们就调用GPIO_ReadOutputDataBit函数。再比如我们光线传感器控制蜂鸣器项目中,光纤传感器的传感数据就是输入,而输出是我们的蜂鸣器。
下面是蜂鸣器的代码:
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;//B12输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出速度//-------------------------------------------------------------GPIO_Init(GPIOB,&GPIO_InitStructure);//GPIO_ResetBits(GPIOA, GPIO_Pin_0);//A0口while(1){GPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_RESET);Delay_ms(100);//延迟100msGPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_SET);Delay_ms(100);//延迟100msGPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_RESET);Delay_ms(100);//延迟100msGPIO_WriteBit(GPIOB, GPIO_Pin_12,Bit_SET);Delay_ms(700);//延迟700ms}}
学过数电的同学都知道,Set是置高电平,Reset是置低电平。当GPIO引脚为低电平时,电流会通过蜂鸣器,使其发出声音;当GPIO引脚为高电平时,蜂鸣器停止发声。
下面是按键控制LED,大家可以看到,江科大的代码都是使用多个部分,这样的模块化编程在后面十分方便,遇到相似的问题就可以直接调用模块,省时省力哈~
#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 = Key_GetNum();if(KeyNum == 1) {LED1_Turn();}if(KeyNum == 2){LED2_Turn();}}}
#include \"stm32f10x.h\" // Device headervoid 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口GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2);//初始化高电平,LED是熄灭状态}void LED1_ON(void){GPIO_ResetBits(GPIOA, GPIO_Pin_1);}void LED1_Off(void){GPIO_SetBits(GPIOA, GPIO_Pin_1);}void LED1_Turn(void){if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0)//GPIOA为低电平{GPIO_SetBits(GPIOA,GPIO_Pin_1);//设置为高电平}else{GPIO_ResetBits(GPIOA,GPIO_Pin_1);//设置为低电平}}void LED2_ON(void){GPIO_ResetBits(GPIOA, GPIO_Pin_2);}void LED2_Off(void){GPIO_SetBits(GPIOA, GPIO_Pin_2);}void LED2_Turn(void){if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0)//GPIOA为低电平{GPIO_SetBits(GPIOA,GPIO_Pin_2);//设置为高电平}else{GPIO_ResetBits(GPIOA,GPIO_Pin_2);//设置为低电平}}
#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){uint8_t KeyNum = 0;if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)//代表按下按键{Delay_ms(10);//暂停10ms,避免按键抖动来带的误触while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0) ;//按键一直按下,一直在循环Delay_ms(10);//暂停10ms,避免按键抖动来带的误触KeyNum = 1;}if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0)//代表按下按键{Delay_ms(10);//暂停10ms,避免按键抖动来带的误触while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 0) ;//按键一直按下,一直在循环Delay_ms(10);//暂停10ms,避免按键抖动来带的误触KeyNum = 2;}return KeyNum;}
接下来是光线传感器控制蜂鸣器的程序
#include \"stm32f10x.h\" // Device headervoid LightSensor_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_13; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);}uint8_t LightSensor_Get(void){return GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13);}
#include \"stm32f10x.h\" // Device headervoid Buzzer_Init(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);//初始化GPIO口GPIO_SetBits(GPIOB,GPIO_Pin_12);}void Buzzer_ON(void){GPIO_ResetBits(GPIOB, GPIO_Pin_12);}void Buzzer_OFF(void){GPIO_SetBits(GPIOB, GPIO_Pin_12);}void Buzzer_Turn(void){if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_12) == 0)//GPIOB为低电平{GPIO_SetBits(GPIOB,GPIO_Pin_12);//设置为高电平}else{GPIO_ResetBits(GPIOB,GPIO_Pin_12);//设置为低电平}}
#include \"stm32f10x.h\" // Device header#include \"Delay.h\"#include \"BUZZER.h\"#include \"LightSensor.h\"int main(void){Buzzer_Init();LightSensor_Init();while(1){if(LightSensor_Get() == 1){Buzzer_ON();}else{Buzzer_OFF();}}}
Part 4 OLED显示屏
OLED(Organic Light Emitting Diode):有机发光二极管
OLED显示屏:性能优异的新型显示屏,具有功耗低、相应速度快、宽视角、轻薄柔韧等特点
0.96寸OLED模块:小巧玲珑、占用接口少、简单易用,是电子设计中非常常见的显示屏模块
供电:3~5.5V,通信协议:I2C/SPI,分辨率:128*64
江科大用的是4引脚的OLED屏幕。
调试方式:
串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息
显示屏调试:直接将显示屏连接到单片机,将调试信息打印在显示屏上
Keil调试模式:借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能
下面是调用的代码
#include \"stm32f10x.h\" // Device header#include \"Delay.h\"#include \"OLED.h\"int main(void){OLED_Init();OLED_ShowChar(1,1,\'A\');//字符OLED_ShowString(1,3,\"Hello World!\");//字符串OLED_ShowNum(2,1,12345,5);//十进制OLED_ShowSignedNum(2,7,-12345,5);//有符号的十进制OLED_ShowHexNum(3,1,0xAA55,4);//16进制OLED_ShowBinNum(4,1,0xAA55,16);//二进制数while(1){}}
上面ShowHexNum是显示十六进制,范围是0~0xFFFFFFFF,演示代码只显示了四位,我们需要注意的就是OLED显示屏只显示最后四位哈。ShowBinNum是显示二进制,范围:0~1111 1111 1111 1111,具体的大家可以看看下面的真值表。
Part 5 EXTI外部中断
EXTI(外部中断/事件控制器)
EXTI专注于外部信号的中断触发。
功能:用于检测外部引脚的信号变化(如上升沿、下降沿或双边沿),并将这些变化作为中断请求传递给NVIC。
特点:EXTI可以将多个外部信号映射到有限的中断线上,增加了系统的灵活性。
AFIO(高级功能I/O)
AFIO负责将GPIO引脚与EXTI中断线连接。
功能:用于管理GPIO引脚的重映射和中断线的连接。它将GPIO引脚与EXTI的中断线连接起来。
特点:AFIO负责将多个GPIO引脚选择到特定的EXTI线上,但同一时间只能选择一个引脚。
NVIC(嵌套向量中断控制器)
NVIC作为中断管理的核心,负责调度和处理所有中断请求。
功能:管理整个系统的中断请求,包括EXTI、定时器中断、串口中断等。NVIC根据中断优先级决定何时响应中断请求。
特点:NVIC支持中断嵌套、中断优先级分组和中断屏蔽。
TIM(定时器)
TIM用于定时功能,其中断请求通过NVIC管理。
功能:用于定时功能,如定时中断、PWM输出、输入捕获等。TIM本身是一个计数器,需要配置时钟源。
特点:TIM的中断功能需要单独使能,例如通过TIM_ITConfig()函数。
协同工作
EXTI与NVIC:EXTI检测到外部信号变化后,向NVIC发送中断请求。NVIC根据优先级决定是否响应并执行中断服务例程(ISR)。
AFIO与EXTI:AFIO负责将GPIO引脚连接到EXTI的中断线,EXTI再将中断请求传递给NVIC。
TIM与NVIC:TIM的中断请求同样通过NVIC进行管理。TIM的中断功能需要使能后,NVIC才会接收并处理其中断。
中断处理流程
GPIO → AFIO → EXTI → NVIC:GPIO引脚的信号变化通过AFIO映射到EXTI的中断线,EXTI检测到信号变化后向NVIC发送请求,NVIC根据优先级处理中断。
TIM → NVIC:TIM的中断请求直接发送给NVIC,NVIC根据优先级处理。
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
抢占式优先级:
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,高抢占式优先级的中断可以嵌套在低抢占式优先级的中断中。
响应式优先级:
当两个抢占式优先级同时来时,先处理响应式优先级高的(谁优先级高先响应谁)。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
总结:抢占式优先级>响应优先级>中断表中的排位顺序
NVIC(中断矢量控制器)优先级分组
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级
抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
中断事件是一种可以导致中断发生的事件,中断则是因为中断事件的发生而导致的后续行为过程。事件与中断事件是包含关系,即事件可分为中断事件或非中断事件。而中断事件与中断之间属于前后关联的因果关系,虽有关联,但二者在时序上、行为上并不一样。
EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
支持的触发方式:上升沿/下降沿/双边沿/软件触发
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(例如PA0和PB0,但是如果有多个中断引脚,要选择不同Pin的引脚,类似于PB0和 PB1,PA9和PA7,PA6和PB15之类)
通道数:16个GPIO_Pin(GPIO_Pin_0—GPIO_Pin_15),外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
中断响应:申请中断,CPU执行中断函数。
事件响应:当外部中断检测到引脚电平变化,选择触发事件,外部中断信号不通向CPU,通向其他外设。
外部中断的功能之一:从低功耗模式的停止模式下唤醒STM32。
PVD电源电压监测:当电源从电压过低恢复时,需要PVD借助外部中断退出停止模式。
RTC闹钟:为了省电,RTC定闹钟之后STM32会进入停止模式,等到闹钟响的时候借助外部中断唤醒。
EXTI基本结构:
每个GPIO外设有16个引脚,
AFIO就是数据选择器,可以在前面3个GPIO外设中选择其中一个GPIO外设的16个引脚连接到后面的EXTI的通道。
Patience:外部中断的9~5会触发同一个中断函数和15~10会触发同一个中断函数,所以要设立标志位来区分是哪一个中断函数进入的NVIC。
AFIO复用IO口
AFIO主要用于引脚复用功能的选择和重定义。
在STM32中,AFIO主要完成两个任务:复用功能引脚重映射和中断引脚选择。
一共16个数据选择器,每个数据选择器选择一个接到后面的EXTI上。
使用AFIO将“默认复用功能”换到“重定义功能”的位置
EXTI框图
配置GPIO AFIO
void GPIO_AFIODeInit(void);复位AFIO外设
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);锁定GPIO配置,防止更改
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);配置GPIO事件输出功能
void GPIO_EventOutputCmd(FunctionalState NewState);配置GPIO事件输出功能
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);配置引脚重映射
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);配置AFIO的数据选择器,选择中断引脚
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);配置以太网相关
配置EXTI
void EXTI_DeInit(void);恢复默认状态
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);根据结构体初始化EXTI外设
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);将结构体变量赋值
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);软件触发外部中断
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);获取指定标志位是否置1
void EXTI_ClearFlag(uint32_t EXTI_Line);对置1 的标志位进行清除
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);获取中断标志位是否置1
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);清除中断挂起标志位
主程序查看和清除标志位:FlagStatus EXTI_GetFlagStatus、void EXTI_ClearFlag
中断函数查看和清除标志位:ITStatus EXTI_GetITStatus、void EXTI_ClearITPendingBit
E.g.
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;//指定配置中断线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//指定选择中断线的新状态
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//指定外部中断线的模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//指定触发信号的有效边沿
EXTI_Init(&EXTI_InitStructure);
配置NVIC
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);中断分组
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);利用结构体初始化NVIC
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);系统低功耗配置
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
E.g.
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//只能使用一种分组方式
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//指定中断通道来开启或关闭
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定中断通道是使能还是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定所选通道的抢占优先级和响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
void EXTI15_10_IRQHandler(void)//中断函数
//中断函数都是无参无返回值的
旋转编码器
旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向
类型:机械触点式/霍尔传感器式/光栅式
Part 6 TIM定时中断
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
所有定时器的时钟都是72MHz
STM32有16位计数器、预分频器、自动重装寄存器的时基单元(核心部分),在72MHz计数时钟下可以实现最大59.65s的定时
计数器:执行技术定时的寄存器,每来一个时钟,计数器+1
预分频器:对计数器的时钟进行分频,使计数更加灵活
自动重装寄存器:计数的目标值,即及多少个时钟申请中断
当预分频器设置最大,自动重装寄存器设置最大,定时器的最大定时时间就是59.65s,接近1min
72M/65536/65536 = 中断频率,再取倒数就是59.65s (2^16 = 65536)
STM32不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
STM32的定时器根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
1MHz = 1 us
1KHz = 1ms
定时器类型
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
基本定时器(仅支持 向上计数模式)
时基单元:计数器、预分频器、自动重装寄存器
预分频器:基本定时器只能选择内部时钟CK_INT,内部时钟的来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHZ,因此通向时基单元的技术基准频率就是72MHz。预分频器对72MHz的时钟进行预分频,
当预分频器写0,那就是不分频,或者是1分频,输出频率 = 输入频率 = 72MHz
当预分频器写1,那就是2分频,输出频率 = 输入频率 / 2 = 36MHz;
当预分频器写2,那就是3分频,输出频率 = 输入频率 / 3;
.......
总之,实际分频系数 = 预分频器的值 + 1
16位预分频器最大值是65535,也就是65536分频
计数器:对分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就 + 1.
16位计数器的值可以从0到65535。所以计数器的值在计数过程中会不断地自增运行,当自增运行到目标值时,产生中断,完成定时任务。
自动重装载寄存器:存写入计数的固定目标
当计数值 = 自动重装值,产生中断信号,并清零计数器,计数器自动开始下一次的计数计时。
更新中断:计数值等于自动重装值产生的中断
产生更新中断,更新中断之后就通往NVIC,我们配置好NVIC的定时器通道,那定时器的更新中断就能够得到CPU响应
产生事件,这里产生的事件叫更新事件,更新事件不会触发中断,但可以触发内部其他电路工作
主模式触发DAC的功能:让内部硬件不受主程序的控制下实现自动运行
通用计时器(向上计数、向下计数,中央对齐三种模式)
外部时钟模式二(ETR外部引脚提供时钟,或者对ETR时钟进行计数,把定时器当作计数器):TIMx_ETR引脚(PA0), 配置内部的极性选择、边沿检测额预分频器电路,再配置输入滤波电路,对外部时钟进行整形。滤波后的信号兵分两路,上一路ETRF进入触发控制器,然后选择作为时基单元的时钟。
外部时钟模式一(将TRGI当作外部时钟来使用):
ITR0到ITR3分别来自其他4个定时器的TRGO输出。外部时钟模式一的输入可以是ETR引脚、其他定时器、TIMx_CH1引脚的边沿(上升沿、下降沿均有效)、TIMx_CH1引脚和TIMx_CH2引脚。
TRGI提供时钟()
输出比较电路
四个通道,分别对应CH1到CH4的引脚,可用于输出PWM波形、驱动电机
输入比较电路
四个通道,分别对应CH1到CH4的引脚,可用于测量输入方波的频率
捕获/比较寄存器
输入捕获和输出比较共用的,输入捕获和输出比较不能同时使用
高级定时器
可实现每隔几个计数周期才发生一次更新事件和更新中断
定时中断基本结构
预分频器时序
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
溢出时间:溢出频率取倒数
计数器无预装时序
计数器有预装时序
影子寄存器的目的:让值的变化和更新事件同时发生,防止在运行图中更改造成错误
RCC时钟树
8 MHz HSI RC和4-16 MHz HSE OSC是告诉晶振,提供系统时钟,AHB、APB1、APB2都来自于这两个高速晶振。外部石英振荡器比内部RC振荡器更稳定。
CSS(Clock Security System)时钟安全系统,负责切换时钟。监测外部时钟运行状态,一旦外部时钟失效,自动把外部时钟切换回内部时钟,保证系统时钟的运行,防止程序卡死造成事故。
AHB总线有预分频器,在System_Init函数里配置的分配系数为1,即分配的时钟为72MHz
APB1总线,分配系数为2,APB1总线的时钟为72MHz / 2 = 36MHz
所有定时器内部基准时钟都是72MHz
void TIM_DeInit(TIM_TypeDef* TIMx);恢复缺省配置
Void TIM_TimeBaseInit(TIM_TypeDef* TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);时基单元初始化(时基单元:计数器、预分频器、自动重装寄存器)
TIMx:选择某个定时器
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);给结构体变量赋默认值
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);使能计数器,对应图上的“运行控制”
TIMx:选择计数器
FunctionalState NewState:使能/失能
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);使能中断输出信号,对应图中“中断输出控制”
TIMx:选择计时器
TIM_IT:选择配置哪个中断输出
FunctionalState NewState:使能/失能
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);选择内部时
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);选择ITRx其他定时器的时钟
TIMx:选择要配置的定时器
TIM_InputTriggerSource:选择要接入哪个的定时
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);选择ITx捕获通道的时钟
TIM_TIxExternalCLKSource:选择TIx具体的引脚
TIM_ICPolarity:输入的极性
ICFilter:滤波器
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);选择ETR通过外部时钟模式1输入的时钟
TIM_ExtTRGPrescaler:外部触发预分频器,对ETR的外部时钟再提前做一个分频
TIM_ICPolarity:输入的极性
ICFilter:滤波器
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);选择ETR通过外部时钟模式2输入的时钟
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);单独配置ETR引脚的预分频器、极性、滤波器这些参数
OC(Output Compare)输出比较
1.主要用于输出PWM波形,PWM波形是驱动电机的必要条件。
2.IC(Input Capture) 输入捕获,CC(Capture/Compare) 输入捕获和输出比较的单元。
3.输出比较可以通过比较CNT(计数器)与CCR(捕获/比较寄存器)寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。
4.每个高级定时器和通用定时器都拥有4个输出比较通道。
5.高级定时器的前3个通道额外拥有死区生成和互补输出的功能。
PWM(Pulse Width Modulation)脉冲宽度调制 周期固定,占空比可调的信号
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等地方。
PWM参数:
频率 = 1 / TS 占空比 = TON / TS 分辨率 = 占空比变化步距
PWM的频率越快,等效模拟信号越平稳,性能开销越大
占空比决定了PWM等效出来的模拟电压的大小。占空比越大,模拟电压越趋近于高电平。
输出比较通道(高级)
死区发生器:延迟时间,防止未完全关闭就打开另外一条通道,
输出比较通道(通用)
输出比较模式
输入捕获:
输入捕获的基本原理:通道1捕获上升电压,通道2捕获下降电压,然后ccr2 - ccr1就是脉宽。
输入捕获的内部结构:
输入滤波:滤掉毛刺,得到特定的频率。
边沿检测:检测上升沿还是下降沿。
信号选择:对应引脚是直接,非对应引脚是间接。
分频:几分频就是几个上升沿(或下降沿)产生ccx事件。
PWM基本结构
极性选择:
参数及计算
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
舵机
舵机是一种根据输入PWM信号占空比来控制输出角度的装置
输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms
直流电机及驱动简介
直流电机是一种将电能转换为机械能的装置,有两个电极。当电极正接时,电机正转;当电极反接时,电机反转。
直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作。
TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向
IC(Input Capture)输入捕获
1.输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。
2.每个高级定时器和通用定时器都拥有4个输入捕获通道
3.可配置为PWMI模式(PWM输入模式,为测量PWM频率和占空比设计),同时测量频率和占空比
4.可配合主从触发模式,实现硬件全自动测量
1.测频法:在闸门时间T内,对上升沿计次,得到N,则频率f_x=N / T(适用于信号频率高,结果更新慢,测量结果是一段时间的平均值)
2.测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率f_x=f_c / N,f = 1/T(适用于信号频率低,只测量一个周期就能出结果,结果更新快,但波动大)
3.中界频率:测频法与测周法误差相等的频率点f_m=√f_c / T
待测频率 < 中界频率,测周法误差更小;
待测频率 > 中界频率,测频法误差更小
主从触发模式(主模式、从模式、触发源选择)
主模式:将定时器内部信号映射到TRGO引脚,用于触发别的外设。
从模式:通过TRGI接收其他外设或者自身外设的信号,用于控制自身定时器的运行。
触发源选择:选择从模式的触发信号源
8种从模式:
8种主模式:
输入捕获基本结构
PWMI基本结构(两个通道同时捕获一个引脚,可以同时测量周期和占空比)
编码器接口Encoder Interface
1.编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度。
2.每个高级定时器和通用定时器都拥有1个编码器接口
3.两个输入引脚借用了输入捕获的通道1和通道2
正交编码器一般可以测量位置或者带有方向的速度值,有两个信号输出引脚,一个是A相,一个是B相。
编码器接口基本结构
工作模式
TI1FP1和TI2FP2分别接A、B相,在A相和B相的上升沿或者下降沿触发计数。
Part 7 ADC(Analog-Digital Converter)模拟-数字转换器
1.ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
2.12位逐次逼近型ADC,1us转换时间
3.输入电压范围:0~3.3V,转换结果范围:0~4095
4.18个输入通道,可测量16个外部和2个内部信号源
5.规则组和注入组两个转换单元
规则组:常规使用
注入组:用于突发事件
6.模拟看门狗自动监测输入电压范围
STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
DAC:数模转换器
ADC 的时钟频率(ADCCLK)不能超过 14MHz。如果超过这个频率,ADC 的转换精度会显著下降。
12位表示采样深度。
ADC模块常用编程接口
逐次逼近型ADC
比较器类似于天平底座,用于衡量。结果寄存器用来写1/0,1表示放砝码,0表示不放砝码。电压发生器针对结果寄存器的值产生对应电压输入比较器。
STM32ADC框图
左边的输入通道:16个GPIO口和两个内部通道:温湿度传感器、VREFINT(V Reference Internal,内部参考电压)。模数转换器(模拟至数字转换器)执行逐次比较过程,转换结果直接存放在数据寄存器。规则通道数据寄存器只有一个,只能存放一个结果,所以最好配合DMA进行数据转运。EOC是规则或注入通道完成的信号,JEOC是注入通道完成的信号,JEOC和EOC会在状态寄存器置标志位。
对于STM32,触发ADC转换的信号有两种,一种是软件触发,一种是硬件触发,图中左下角的触发源。VREF+和VREF-是ADC的参考电压,决定了ADC输入电压的范围。VDDA和VSSA是ADC的供电引脚,一般情况VREF+接VDDA,VREF-接VSSA。
框图
前面的开关表示采样开关。
ADC基本结构
输入通道
常规序列
DR寄存器是用来保存常规序列转换结果。
常规序列:ADC模块的一份计划
例子:
配置TIM3
注入序列
转换模式
1.单次转换,非扫描模式
非扫描模式下,只有序列1的位置有效。写入转换通道,然后ADC对通道进行模数转换,转换结果放在数据寄存器里,同时EOC标志位置1。
2.连续转换,非扫描模式
一次转换后立刻开始下一轮转换。
3.单次转换,扫描模式
每个通道的位置任意可重复,需要给定通道数目。
4.连续转换,扫描模式
触发控制
“外部引脚/来自片上定时器的内部信号”需要用AFIO引脚重映射来确定
数据对齐
数据右对齐(常用):
数据左对齐:
转换时间
1.AD转换的步骤:采样,保持,量化,编码
2.STM32 ADC的总转换时间为:
TCONV = 采样时间 + 12.5个ADC周期
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
校准
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。
1.建议在每次上电后执行一次校准
2.启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
ADC采样时间和转换时间
ADC的时钟频率
ADC的输入时钟不得超过14MHZ,它是由PCLK2经分频产生
转换时间的计算方法
1周期表示输入ADC模块的时钟信号的周期。12个寄存器,尝试12次,每次1MHz,ADC模块时钟信号的频率是12MHz。
采样时间和信号原源内阻的关系
信号源内阻越大,采样时间越长。
信号源内阻的计算方法
常规单通道转换
1.初始化IO引脚
2.配置ADC的时钟
3.ADC的编程接口
4.初始化ADC的基本参数
5.配置常规序列
6.闭合ADC总开关
7.启动并读取转换结果
定时器触发初始化串口
配置定时器1的TRGO
每一毫秒产生一次脉冲。
注入序列的编程接口
初始化ADC(注入序列)
1.初始化IO引脚
2.配置时钟
3.配置ADC的基本参数
4.配置注入序列
5.闭合总开关
Part 8 DMA(Direct Memory Access) 直接存储器存取
1.DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
2.12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
3.每个通道都支持软件触发和特定的硬件触发
STM32F103C8T6 DMA资源:DMA1(7个通道)
存储器映像
ROM(只读存储器)是一种非易失性,掉电不丢失的存储器,RAM(随机存储器)是一种易失性,掉电丢失的存储器。内核外设就是NVIC和SysTick。
寄存器是特殊的存储器,一方面,CPU可以对寄存器进行读写,另一方面,寄存器的每一位背后都连接了一根导线,这些导线可以用于控制外设电路的状态。DCode总线专门访问Flash,系统总线访问其他。
仲裁器:调度各个通道,防止产生冲突。
AHB从设备:用于配置DMA参数。
DMA请求:用于硬件触发DMA的数据转运。
DMA基本结构
传输计数器必须先关闭DMA(开关控制DISABLE)然后再继续操作。
M2M是数据选择器的控制位,用于选择软件触发或硬件触发。
EN位是开关控制,EN = 0停止;EN= 1 工作。
1字节:uint8_t
半字符:uint16_t
1字符:uint32_t
DMA转运条件:
1.传输计数器 > 0
2.触发源有触发信号
3.DMA使能
SPI和IIC在进行通讯时,都是高位先行,而串口是低位先行。
Part 9 USART串口协议
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统。
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发。
1.一般全双工的通信都有两根通信线,发送线路和接收线路互不影响。
2.单工的数据只能从一个设备到另一个设备,但不能反向。
3.单端通信的双方必须要共GND。
串口通信
1.串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
2.单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。
硬件电路
1.简单双向串口通信有两根通信线(发送端TX和接收端RX)。
2.TX与RX要交叉连接。
3.当只需单向的数据传输时,可以只接一根通信线。
4.当电平标准不一致时,需要加电平转换芯片。
电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
1.TTL电平:+3.3V或+5V表示1,0V表示0
2.RS232电平:-3~-15V表示1,+3~+15V表示0
3.RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
奇偶校验
串口参数及时序
1.波特率:串口通信的速率
2.起始位:标志一个数据帧的开始,固定为低电平
3.数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
4.校验位:用于数据验证,根据数据位计算得来
5.停止位:用于数据帧间隔,固定为高电平
LSB First:先传最低有效位
对于数据帧传输而言,先传LSB,再传MSB
书写习惯总是先写MSB,再写LSB
CTS(Clear To Send):可以发送。低电平有效,发送方有
RTS(Request To Send):请求发送。低电平有效,接收方有效
Part 10 USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器P
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。
1.自带波特率发生器,最高达4.5Mbits/s
2.可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
3.可选校验位(无校验/奇校验/偶校验)
4.支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
STM32F103C8T6 USART资源: USART1(APB2)、 USART2(APB1)、 USART3(APB1)
大致流程:
USART基本结构
- 开启时钟
- GPIO初始化
- 使用结构体配置USART
- 如果使用接收需要开启中断
- 开启USART
当 UART 接收到一个字节并触发中断
数据帧
空闲
起始位
数据位
停止位
例子:
单个数据发送
波特率发生器(分频器)
1.发送器和接收器的波特率由波特率寄存器BRR里的DIV确定。
2.计算公式:波特率 = fPCLK2/1 / (16 * DIV)
数据包的作用:把单独的数据打包,便于多字节数据通信。
HEX数据包
1.固定包长,含包头(FF)包尾(FE)
2.可变包长,含包头(FF)包尾(FE)
文本数据包
3.固定包长,含包头包尾
4.可变包长,含包头包尾
状态转移图(HEX数据包接收)
画状态机步骤:
1.根据项目要求定义状态。
2.转移条件。
状态转移图(文本数据包接收)
USART的完整框图
USART的寄存器组
TDR - 发送数据寄存器
RDR - 接收数据寄存器
CR - 配置寄存器
SR - 状态寄存器
BRR - 波特率寄存器
IER - 中断使能寄存器
UART模块使用方法
移位寄存器和串并转换(低位先行)
USART参数设置
串口数据帧格式
波特率的设置方法
波特率:
双缓冲
TXE(Transmit Data Register Empty)发送数据寄存器空
TC(Transmit Complete)发送完成
TDR空 && 移位寄存器空
RXNE(Receive data register Not Empty)接收数据寄存器非空
错误标志位
错误标志位的使用方法
单个数据的发送
连续数据发送
USART标准库函数编程
使用USART与计算机通信
USART的初始化
编程接口
USART_Init初始化
USARTInitStruct.USART HardwareFlowControl=xxx硬件流控
参数:
USART_HardwareFlowControl_RTS
USART_HardwareFlowContrgl_CTS
USART_HardwareFlowControL RTS_CTS
USART HardwareFlowControl_None
CTS(Clear To Send):可以发送。低电平有效,发送方有
RTS(Request To Send):请求发送。低电平有效,接收方有效
例子:
编写代码(默认PA9,PA10)
编写代码(重映射PB6,PB7)
USART_Cmd总开关
USART_SendData写TDR寄存器
一次发送多个字节
USART ReceiveData读RDR寄存器
接收数据代码:
例子:
USART_GetFlagstatus读标志位
格式化打印字符串
何时需要引脚复用?
(1) 外设功能需求
当需要使用片内外设(如USART、SPI、ADC等)时,必须将引脚配置为对应的复用功能模式(Alternate Function, AF)。例如:
(2) 资源冲突优化
当默认外设引脚被其他功能占用时,可通过复用功能重映射(Remap)切换到其他引脚(部分型号支持)。
例如:STM32F103的USART1默认在PA9/PA10,可通过重映射到PB6/PB7。
(3) 多外设共享引脚
同一引脚在不同时段可切换为不同外设功能(需动态配置,避免冲突)。
USART初始化流程
引脚参数设置
1、初始化IO引脚
2、使能USART时钟
3、设置USART参数
4、闭合总开关
中断方式的数据接收
中断共用
中断屏蔽
编程接口
USART_ITConfig使能/禁止中断
USART_GetlTStatus获取标志位的值(标志位&&中断使能位)
Part 11 I2C(Inter IC Bus)通信
由Philips公司开发的一种通用数据总线。
1.标志性引脚,两根通信线:SCL(Serial Clock)串行时钟线、SDA(Serial Data)串行数据线
2.同步时序,半双工
3.带数据应答
4.支持总线挂载多设备(一主多从、多主多从)
流程:起始位-寻址-传输字节-停止位
SCL为高电平时,SDA的电平不能再变了。
波特率:
硬件电路
1.所有I2C设备的SCL连在一起,SDA连在一起。
2.禁止所有设备输出强上拉的高电平,采用外置若上来电阻+开漏输出的电路结构来避免电源短路。设备的SCL和SDA均要配置成开漏输出模式。
3.SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。
寄存器组与内部框图
I²C协议严格定义MSB-first(高位先行)
TDR-Transmit Data Register-发送数据寄存器
RDR-Receive Data Register-接收数据寄存器
CR -Control Register-控制寄存器
SR1-Status Register1-状态寄存器
SR2-Status Register 2-状态寄存器
CCR-Clock Control Register - 时钟控制寄存器
寻址
IIC速度模式
单片机只支持 标准模式 和 快速模式。
只有在快速模式下才能设置时钟信号占空比。
I2C时序基本单元(线与,一个设备是低电平,那该线都是低电平)
每发送一个字节就回复一个ACK。R/W:0表示写入操作,1表示读出操作
1.起始条件:SCL高电平期间,SDA从高电平切换到低电平
2.终止条件:SCL高电平期间,SDA从低电平切换到高电平
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
起始位和停止位
寻址与应答
ACK:接收方才会回答
主机发完地址和RW之后松开SDA,等待从机下拉SDA。
R/W:0表示写入操作,1表示读出操作
主机发送:
R/W:0表示写入操作,1表示读出操作
主机接收:
IIC模块初始化
IIC初始化流程
整体代码:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); 使能AFIO(Alternate Function I/O)用于引脚重映射
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); 启用I2C1的引脚重映射功能
写数据
模式:标准(SM)、快速(FM)
占空比:2:1 / 16:9
BUSY — 总线忙标志位
0 - 总线空闲 1 - 总线忙
START-发送起始位
向该比特位写1发送起始位
SB-起始位发送完成
0-起始位未发送 1-起始位发送完成
AF-Acknowledge Failure 应答失败 1-未收到ACK
ADDR-寻址成功 1-寻址成功
RW为0表示写。
若要执行后续操作则需要对ADDR进行清零。
发送数据
发送数据之前要查询TXE是否为空 和 ACK(AF标志位)是否被拒收。
通过查询BTF标志位了解发送数据寄存器(将要发送的数据位)和正在发送数据的寄存器是否为空(标志位由0变为1表示发送完成)。同时要查询AF标志位,防止上一个数据被拒收。
读数据
1.发送起始位
START-发送起始位
向该比特位写1发送起始位
SB-起始位发送完成
1-起始位未发送 1-起始位发送完成
AF-Acknowledge Failure 应答失败 1-未收到ACK
ADDR-寻址成功 1-寻址成功
接收数据
接收一个字节:
接收2个字节
接收n个字节
IIC初始化
1、GPIO初始化
2、IICC1初始化
RCC APBlPeriphclockCmd(RCC APBlPeriph 12C1,ENABLE); -使能I2C
RCC APBlPeriphResetCmd(RCC APBlPeriph I2C1,ENABLE);
RCC APBlPeriphResetCmd(RCC APBlPeriph I2C1,DISABLE): -对I2C复位
IIC时序
1.指定地址写
对于指定设备(从机地址Slave Address),在指定地址(寄存器地址Reg Address)下,写入指定数据(Data)。第八位是读写位,0表示主机进行写入操作;1表示主机进行读出操作。
2.当前地址读
对于指定设备(从机地址Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
3.指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)。
先指定从机的寄存器指针。Sr(Start Repeat)重复起始条件。指定读写标志位只能是起始条件的第一个字节。
MPU60506轴姿态传感器
测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角(欧拉角),常应用于平衡车、飞行器等需要检测自身姿态的场景。
1.3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度。
2.3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度。
加速度计具有静态稳定性,不具有动态稳定性。
陀螺仪具有动态稳定性,不具有静态稳定性。
MPU6050参数
1.16位ADC采集传感器的模拟信号,量化范围:-32768~32767
2.加速度计满量程选择:±2、±4、±8、±16(重力加速度g)
3.陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
满量程越大,测量范围越广;满量程越小,测量分辨率越高。
4.可配置的数字低通滤波器
5.可配置的时钟源
6.可配置的采样分频
I2C从机地址:1101000(AD0=0)
1101001(AD0=1)
I2C外设简介
1.STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。
2.支持多主机模型(可变多主机)。
3.支持7位/10位地址模式。
4.支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)。
5.支持DMA。(连续读/写很多字节)
6.兼容SMBus(系统管理总线)协议,主要用于电源管理系统。
STM32F103C8T6 硬件I2C资源:I2C1、I2C2。
I2C基本结构
一个SCL时钟,左移一位,最终移8位,一个字节就接收完成。
使用硬件IIC,GPIO需要配置复用开漏输出模式。
BTF字节发送结束标志位
软件I2C代码编写
1、GPIO初始化(PA0和PA1)
2、IO读写和延迟函数
3、发送起始位和停止位
4、发送一个字节
5、接收一个字节
6、综合
写:
读:
Part 12 SPI(Serial Peripheral Interface)
由Motorola公司开发的一种通用数据总线。
1.四根通信线:SCK(Serial Clock)串行时钟线、MOSI(Master Output Slave Input)主机输出从机输入、 MISO(Master Input Slave Output)主机输入从机输出、 SS(Slave Select)从机选择(低电压表示被选择)
2.同步,全双工
3.支持总线挂载多设备(仅支持一主多从)
硬件电路
1.所有SPI设备的SCK、MOSI、MISO分别连在一起。
2.主机另外引出多条SS控制线,分别接到各从机的SS引脚,SS低电平有效。
3.对主机而言,输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
移位示意图
SPI时序基本单元
CPOL 时钟极性 CPHA 时钟相位
模式0和模式3是SCK上升沿采样,模式1和模式2是SKC下降沿采样。
1.起始条件:SS从高电平切换到低电平。
终止条件:SS从低电平切换到高电平。
2.交换一个字节(模式0) SCK变化之前移出数据
CPOL=0:空闲状态时,SCK为低电平。
CPHA=0:SCK第一个边沿移入(采样)数据,第二个边沿移出数据。
3.交换一个字节(模式1)
CPOL=0:空闲状态时,SCK为低电平。
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据。
4.交换一个字节(模式2)
CPOL=1:空闲状态时,SCK为高电平。
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据。
5.交换一个字节(模式3)
CPOL=1:空闲状态时,SCK为高电平。
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据。
IO引脚初始化
- 初始化IO引脚
PB3、PB4、PB5是重映射,所以首先使能AFIO时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 使能AFIO时钟
GPIO_PinRemapConfig(GPIO_Remap_SPI1, ENABLE);//使能重映射
SPI模块的初始化
1.SPI模块的基本工作原理
2.SPI_Init
3.选择数据通信方向
4.数据宽度、极性、相位、比特位传输顺序
极性:时钟信号的初始状态。
相位:接收方在第一变边沿或第二信边沿对信息进行采集。
5.设置波特率
6.NSS的配置方式
通过下面的函数对内部NSS写1输出高电压。
整体代码:
SPI数据收发
SPI数据收发的特点
声明数据收发的编程接口
TDR:发送数据寄存器
RDR:接收数据寄存器
SPI时序
1.发送指令
向SS指定的设备,发送指令(0x06)
SCK低电平变化,高电平读取。
2.指定地址写
向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)
3.指定地址读
向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)
W25Q64简介
1.W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景。
2.存储介质:Nor Flash(闪存)
3.时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)。
4.存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
硬件电路
W25Q64框图
Flash操作注意事项
写入操作时:
1.写入操作前,必须先进行写使能。
2.每个数据位只能由1改写为0,不能由0改写为1。
3.写入数据前必须先擦除,擦除后,所有数据位变为1。
4.擦除必须按最小擦除单元进行。
5.连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。
6.写入操作结束后,芯片进入忙状态,不响应新的读写操作。
读取操作时:
直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取。
W25Q64存储字节
App_SPI_MasterTransmitReceive:主机发送同时接收字节
SPI外设简介
1.STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担。
2.可配置8位/16位数据帧、高位先行/低位先行
3.时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
4.支持多主机模型、主或从操作
5.可精简为半双工/单工通信
6.支持DMA
7.兼容I2S协议
SCK的时钟频率 = PCLK的频率 / 分频系数
分频系数越大,SCK时钟频率越小,传输越慢
STM32F103C8T6 硬件SPI资源:SPI1(APB2)、SPI2(APB1)
SPI基本结构
TDR置TXE,RDR置RXNE。
主模式全双工连续传输
非连续传输(损失性能,但简单)
步骤:
- 等待TXE为1
- 写入发送的数据至TDR
- 等待RXNE为1
- 读取RDR接收的数据
Part 13 RTC实时时钟
1.Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒。
2.时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量。
3.世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。