纸上得来终觉浅,绝知此事要躬行
一、写在前面
待整理补充
二、GPIO
待整理补充
三、EXIT外部终端
3.1 简介
32中总共有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。
而CPU不可能在工作的同时来处理这么多中断的响应优先顺序,所以这个工作就交给秘书–NVIC来做
3.1.1.NVIC
NVIC是Cotex-M内核中的资源,比较高深,它主要是用来给中断通道配置优先级的,总共有4位,可配置16种优先级,且不同的优先级对应着不同的抢占优先级和响应优先级(。。。。。。。。。。。。。。。。。。。。。。。。现在暂时还不知道这样划分的意义,留一个坑)
3.1.2.抢占优先级和响应优先级
这里举一个排队看病的例子
当房间里面有病人A在看病了,这时候来了一个情况危急的病人B,当A看完后,B可以不用排队直接进去看病,这就是响应优先级。重点在A看完之后,B才能忽视外面排队的病人提前进去看病。(有点插队的意思)
同样是A在里面看病,这时来了个B,B直接冲进病房,让A站到一边,先看B,看完B后再看A,再考虑后面排队的病人,这就是抢占优先级(有点不讲理的意思)
3.1.3.EXTI
EXTI(Extern Interrupt)外部中断,其可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
支持的触发方式:上升沿/下降沿/双边沿/软件触发(实际上就是通过代码直接进入中断,无需满足进入中断的条件)
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断。这句话的意思就是监视的PIN口必须是每个GPIOA、GPIOB、GPIOC等外设中的不同的pin口,比如GPIOA_Pin0和GPIOB_Pin0不能同时使用,而必须是GPIOA_Pin0和GPIOB_Pin1这种。
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(主要以16个GPIOI_Pin口为主)
触发响应方式:中断响应/事件响应。
中断响应就是正常的流程:满足中断的条件后,NVIC告诉CPU,CPU进入中断函数执行相应操作
事件相应就是满足中断的条件后,绕过CPU这个总指挥,直接去完成对应的操作。
EXIT的0-4号输出是单独的中断函数,5-9和10-15用同一个中断函数,所以在编程时需要根据标志位来区分是具体哪一个通道进来的中断.
3.1.4.AFIO
AFIO实际上是一个16选1的数据选择器,GPIO外设每一个都有16个PIN,AFIO最终会从七个GPIO外设的7*16个PIN口中选择16个接入到EXIT的16个PIN通道。所以需要16个AFIO来进行选择。
AFIO主要完成中断引脚的选择(上面讲的),复用功能引脚重映射(可以重写有复用功能的引脚的功能。。。。可能是,先留一个坑)
3.1. 5.EXTI框图(运行流程)
3.2实操:外部中断的开启
打通外部中断的这一条信号电路的五个步骤(跟着EXTI的基本结构图走就行)
1.配置RCC(不打开时钟,外设是没法工作的,AFIO的时钟是自动开启的,NVIC是在Cotex-M内核中的,也无需手动开启)
2.配置GPIO为输入模式
3.配置AFIO,选择上一步配置好的GPIO的PIN口连接上EXTI上
4.配置EXTI,选择边沿触发方式(上升沿、下降沿、双边沿),选择触发响应方式(一般选择的是中断响应)
5.配置NVIC,给中断一个合适的优先级
最后CPU就能收到中断信号,然后跳转到中断函数里面去了
这里以检测GPIOB_Pin14口的下降沿为例子,进行EXTI外部中断的初始化
这里尤其注意中断函数的名字是已经写好了封装在库函数里面的,需要我们在启动文件Startup_stm32中去找对应的中断函数
由于我们用的是EXTI的14号通道对应的中断函数,而10-15号通道共用一个中断函数,所以我们需要在中断函数中先判断中断的来源是否是14号通道
#include "stm32f10x.h" // Device header
void CountSensor_Init(void)
{
//第一步:开启时钟,AFIO的时钟是自动开启的,NVIC是在Cotex内核中的,无需手动开启
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启GPIOB和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);
//第三步:配置AFIO,选择对应的PIN口连接到EXTI上
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//AFIO的第十四个数据选择器就配置好了
//第四步:配置EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line14;//我们需要EXTI输入端的第14条线路
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//开启EXTI中断
EXTI_InitStructure.EXTI_Mode=0x00;//选择触发响应模式
EXTI_InitStructure.EXTI_Trigger=0x0C;//选择触发中断模式:下降沿
EXTI_Init(&EXTI_InitStructure);
//第五步:配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC的中断优先级为两位抢占(前两位),两位响应(后两位)
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;//搜索变量时,由于IRQChannel的定义是在stm32的头文件里面,所以需要将搜索范围切换至current project
//搜索到之后,由于我们芯片是中等密度MD,所以就只需要在后缀为MD的文段里面找即可
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//开启IRQChannel
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//这两位的值是在表里面给出来的,由于上面我们配置了抢占和响应优先各占两位,所以就按照表格上面的值填写即可
NVIC_Init(&NVIC_InitStructure);
}
void EXTI15_10_IRQHandler(void)//中断函数的名字都是指定好了的,在启动文件Startup_stm32中可以找到对应的
{
if(EXTI_GetITStatus(EXTI_Line14)== SET)//由于这个中断函数是10-15通道通用,所以我们需要首先判断一下是不是我们所期望的那条14通道传来的终端
//同时,这个函数是特定的在中断中读取挂起寄存器的值的函数,返回值为SET或RESET
{
EXTI_ClearITPendingBit(EXTI_Line14);//一定要记得手动清除标志位!!!!!!!!!!!!!!!!!
}
}
四、定时器
4.1 基本介绍
定时器(Timer)可以对输入的时钟进行计数,这里的输入可以是系统始终SYSclk(72Mhz),也可以是外界输入,并在计数值达到设定值时触发中断,这是最基本的功能。
其包含16位 计数器**(CNT**)、16位 预分频器(PSC)、16位 自动重装寄存器(ARR)的时基单元。在72MHz计数时钟下可以实现最大59.65s的定时。同时32的寄存器还支持级联模式
- 计数器就是执行计数定时的一个寄存器,每来一个时钟,计数器就加一
- 预分频器可以对计数器的时钟进行分频(回想51部分,对12MHZ的系统时钟进行12分频或者是6分频)特别特别注意!!!:预分频器的值和实际的分频相差1,比如给预分频为1,则实际分频为2!!!!!!
- 自动重装寄存器就是计数的目标值,就是我们想要计多少个时钟后申请中断,ARR的实际值与给定值之间也相差1,实际计算时用ARR+1
4.2 三种类型的定时器
STM32F103C8T6这块板子上只有TIM1-TIM4,即一个高级定时器和三个通用定时器,无基本定时器
4.2.1 基本定时器
红色框里面的就是时基单元,包含了上面所提到的PSC、CNT、ARR三部分。
UI为“更新中断”,是计数器的值达到了目标值后产生的中断,产生后会通往NVIC,然后通完CPU。
U为“更新事件”,它不会触发中断,但会触发内部其他电路的工作。
基本流程如下
- 对系统时钟进行分频
- 计数器对进入的时钟数进行计数,计数一共有三种模式
- 计数器与自动重装寄存器的值不断比较,当达到自动重装寄存器的值(计数目标值)后产生中断
三种计数模式
基本定时器只支持向上计数这一种模式,而通用定时器和高级定时器支持三种模式。
- 1.向上计数:CNT的值从0开始自增到ARR的值,在清0并重复前面的过程
- 2.向下计数:CNT的值从ARR的值开始自减,减到0后回到ARR,并重复前面过程
- 3.向上/向下(中央对齐计数):CNT的值从0开始自增到ARR,在从ARR的值自减到0,重复上面过程
时基单元的时序图和几个重要公式
预分频器时序图和计数器计数频率
- 第一行CK_PSC为预分配器的输入时钟。
- 第二行CNT_EN为计数器使能,高电平时计数器正常运行
- 第三行CK_CNT为计数器时钟,它即是预分频器的时钟输出,也是计数器的时钟输入
- 第四行计数器寄存器,从图中可以看出计数到FC后寄存器的值就清零,同时产生一个更新事件,可以推断出ARR的值为FC,且要注意:当计数器的值与重装值相等且下一个时钟来临时,计数器才清零。
- 重点:预分频器带有缓冲机制,图中可以看到有预分频器控制寄存器和预分配器缓冲器(缓冲寄存器或者是影子寄存器),前者只供我们读写预分频器的值,真正改变预分频值的是后者。途中可以看到,当在计数过程中改变预分频器控制寄存器的值,按理来说计数频率会发生改变,但实际上没有,因为预分配值并没有发生变化,这一切都是预分配器缓冲器的功劳。
- 最后得出计数器计数频率CK_CNT=CK_PSC/(PSC+1)
计数器时序图和计数器溢出频率
- 第一行CK_INT为内部时钟(72MHZ)
- 第二行为计数使能,高电平是计数器正常工作
- 第三行为计数器时钟,由于分频系数为2,所以对比CK_INT可以看出是内部时钟计数频率的一半
- 第四行为计数器寄存器,当达到ARR重装值36后溢出,产生一个更新事件脉冲(第五行),同时置一个更新中断标志位UIF(第七行),这个标志位会去申请中断,中断响应后要手动清零。
- 计数器溢出频率CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)
同时,计数器也是带有缓冲机制的寄存器
4.2.2通用定时器
红框部分为时基单元,黄线为不同的时钟来源,蓝框部分为输入捕获/输出比较寄存器CCR
先来讲讲时钟来源:
- 1.外部ETR引脚,其通过极性选择、边沿检测和预分频器后通过滤波电路,分出得到的ETRF作为外部时钟来源,这一模式称作外部时钟模式2(常用)
- 2.触发输入TRGI作为外部时钟输入,这一模式称作外部时钟模式1。然后触发输入这一通道的信号来源有好几路,第一路就是外部ETR引脚信号通过极性选择、边沿检测和预分频器后通过滤波电路分出的向下进入数据选择器的那一路信号;第二路就是ITR信号,ITR0-ITR3就是其他定时器的主模式输出TRGO信号;第三路就是TI1F_ED,就是CH1引脚的边沿;第四路就是TI1FP1和TI2FP2,也就是CH1和CH2引脚
4.2.3高级定时器
相比较通用定时器,高级定时器中增加了两大部分:
- 重复次数计数器,其可以实现CNT达到ARR的值后,统计其次数,当次数达到要求值后在进行操作
- 输出比较比分增加了死区生成和互补输出部分,主要是针对驱动三相无刷电机的。
4.3定时器的多种功能:
- 1.定时中断功能
- 2.输入捕获
- 3.输出比较
- 4.编码器接口
- 5.主从触发模式
4.3.1功能1:定时中断
看框图很好理解工作流程:
- 1.选择时钟来源:内部时钟、外部时钟
- 2.配置时基单元
- 3.中断输出控制部分,因为定时器内部有很多部分都需要申请中断,中断输出控制实际上就是某个中断的允许位,如果需要这个中断,就允许。
4.3.1.1实操:选择内部时钟作为时钟来源,实现内部时钟计数:
#include "stm32f10x.h" // Device header
uint8_t Num;
void Timer_Init(void)//初始化通用计时器2
{
//Step1: 开启时钟,Timer2是挂在在APB1上的
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//Step2:选择内部时钟作为Timer2时基单元中的时钟源(主频72MHZ),不过32上电默认选择内部时钟,这一行代码可以忽略
TIM_InternalClockConfig(TIM2);
//Step3: 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;
TIM_TimeBaseInitStructure.TIM_Period=10000-1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//Step4: 配置中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//Step5:配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
//Step6:打开定时器
TIM_Cmd(TIM2,ENABLE);
}
uint8_t NUM(void)
{
return Num;
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
4.3.1.2实操:选择外部时钟作为时钟来源,实现按键计数
#include "stm32f10x.h" // Device header
uint8_t Keynum;
void CountSensor_Init(void)
{
//第一步:开启时钟,AFIO的时钟是自动开启的,NVIC是在Cotex内核中的,无需手动开启
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启GPIOB和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);
//第三步:配置AFIO,选择对应的PIN口连接到EXTI上
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//AFIO的第十四个数据选择器就配置好了
//第四步:配置EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line14;//我们需要EXTI输入端的第14条线路
EXTI_InitStructure.EXTI_LineCmd=ENABLE;//开启EXTI中断
EXTI_InitStructure.EXTI_Mode=0x00;//选择触发响应模式
EXTI_InitStructure.EXTI_Trigger=0x0C;//选择触发终端模式:下降沿
EXTI_Init(&EXTI_InitStructure);
//第五步:配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC的中断优先级为两位抢占(前两位),两位响应(后两位)
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn;//搜索变量时,由于IRQChannel的定义是在stm32的头文件里面,所以需要将搜索范围切换至current project
//搜索到之后,由于我们芯片是中等密度MD,所以就只需要在后缀为MD的文段里面找即可
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//开启IRQChannel
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//这两位的值是在表里面给出来的,由于上面我们配置了抢占和响应优先各占两位,所以就按照表格上面的值填写即可
NVIC_Init(&NVIC_InitStructure);
}
void EXTI15_10_IRQHandler(void)//中断函数的名字都是指定好了的,在启动文件Startup_stm32中可以找到对应的
{
if(EXTI_GetITStatus(EXTI_Line14)== SET)//由于这个中断函数是10-15通道通用,所以我们需要首先判断一下是不是我们所期望的那条14通道传来的终端
//同时,这个函数是特定的在中断中读取挂起寄存器的值的函数,返回值为SET或RESET
{
Keynum++;
EXTI_ClearITPendingBit(EXTI_Line14);//一定要记得手动清除标志位!!!!!!!!!!!!!!!!!
}
}
uint8_t Key(void)
{
return Keynum;
}
4.3.2功能2:输出比较功能
OC(Output Compare)输出比较可以通过比较CNT与CCR的大小关系,来对输出电平进行置1、置0、翻转的操作、用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都有4个输出比较/输入捕获通道,两个功能共用一个通道和寄存器
CCR,每个通道有各自的CCR寄存器,但是四个通道共用一个CNT寄存器。
同时高级定时器的前三个通道额外拥有死区生成和互补输出的功能。
4.3.2.1通用定时器输出比较通道
CNT和CCR的比较结果是输出模式控制器的输入,而输出模式控制器通过选择不同的模式,输出OC1ref电压,然后在通过极性选择,经过输出使能,最后输出。
如下为不同的输出比较模式,我们常用PWM模式,PWM模式有两种,两种模式只是在ref电平上取相反的极性,同时每一种模式也有两种不同的计数方式,可以通过选择极性来选择计数方式
4.3.2.2PWM介绍
PWM(Pulse Width Modulation)脉冲宽度调制,在具有惯性的系统(LED、Steering engine)中,可以通过对一系列脉冲的宽度进行调制,来等效的获得所需要的模拟参量,常用于电机控诉等领域
4.3.2.3PWM基本结构
图中是选择PWM1模式并采取向上计数
重要参数:
- 频率Freq=1/Ts=计数器频率=CK_PSC/(PSC+1)/(ARR+1)
- 占空比Duty=Th/Ts=CCR/(ARR+1)
- 分辨率Reso=1/(ARR+1)(解释:例如LED呼吸灯的分辨率为1%,即说明LED的亮度是1%亮,2%亮这样一直到100%亮)。
4.3.2.4舵机介绍
舵机是根据输入PWM信号占空比来控制输出,输入PWM信号要求:周期20ms,高电平宽度0.5ms-2.5ms,具体转动角度见下图:
在这里PWM实际上是当作一种通信协议来用的
注意舵机需要5V电压来驱动
4.3.2.5PWM输出LED呼吸灯
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//step1:开启外设时钟
//step2:配置时基单元,包括时钟源选择
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1; //PSC
TIM_TimeBaseInitStructure.TIM_Period=100-1; //ARR
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//step3:配置输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//由于TIM_OCInitStructure是局部变量,且其内部成员我们没有全部赋初始值,这可能会导致一些问题
//所以我们调用这个函数来将其所有成员赋初始值,然后再修改我们需要用到的成员
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse=50; //就是CCR的值
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
//step4:配置GPIO为推挽输出
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//step5:启动计数器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
五、AD数模转换
5.1.简介
GPIO口只能读取引脚的高低电平,要么是高电平,要么是低电平,而实用了ADC之后,我们就能够对高低电平之间的任意电平进行量化!! (之前对AD困惑的点解决了!!!)
STM32的ADC输入电压范围为0-3.3V!
- 12位逐次逼近型,12位指的就是ADC最多能将模拟量量化为2^12这么多,也就是将模拟量A量化为0-4096这么多。
- 16个外部输入通道就是16个GPIO口,且不需要额外的电路,只需要将GPIO配置为AD模式即可,两个内部信号源是内部温度传感器和内部参考电压,其主要用于校准。
- 普通的AD转化流程是启动一次转换,读一次值,以此往复
- 模拟看门狗可以检测某些指定的通道,到AD值高于或者低于它所设定的阈值时,就会申请中断
5.2 逐次逼近型ADC
1.简单的逐次逼近行ADC
- 1部分是一个可以选择模拟输入通路的数据选择器,IN0-IN7是八路模拟输入通道,下面的地址锁存和译码部分可以接受三位地址ADDA-ADDC,然后通过ALE使能开启对应的模拟输入通道
- 2部分是重点,DAC是数模转换器,比较器上面的输入是未知编码的模拟输入量,比较器下面的输入是已知数字编码的模拟输入量,要想知道前面模拟输入通道输入的模拟电压的数字编码,这里我们采用二分法,首先对Vref进行数字编码,对应最大的数字量,拿8位的ADC来说,就是255,然后经过DAC来和未知量比较,总共需要比较8次,**这里是几位就需要比较几次,如果高了,对应的Di通道就输出0,低了就输出1(**这里就是简单二分法的思想,自己能够理解),然后最后就能得到未知模拟量的数字编码。
- 3EOC是转换结束信号
- 4.START是开始转换信号,给一个输入脉冲,开始转换
- 5CLOCK是ADC时钟!!!
- 规则组最多能够同时转换16个通道,但是其数据寄存器只能存一个结果,这样就会导致之前的数据被覆盖掉,如果要避免这样,就需要在转换之后尽快将数据拿走,这就需要用到DMA
- 注入组一次可以转换4个通道,并且它的寄存器可以存4个结果
- STM32的触发信号(就是前面提到的START信号)分两种,一种是软件触发,只需要在程序中调用一条代码即可
- ADCCLK是来源于ADC预分频器,而ADC预分频器是来源于RCC时钟,ADCCLK=APB2(72Mhz)/ADC预分频值,并且ADCCLK最大频率为14Mhz!!,所以只能选择6分频或者是8分频
5.3 ADC基本结构图
5.4 ADC的四种转换模式
总的来说,是选择单次转换/连续转换,扫描/非扫描。
1.单次转换,非扫描:转换一次后就会停下来,若想继续转换,则需要再次触发开始
2.连续转换,非扫描:他和上面的区别就是,一次转换结束后不会停止,会接着转换下去,就只需要最开始触发一次,就可以不断的转换下去了
3.单次转换,扫描模式:这里就需要用到这个"菜单"了,这里就可以同时指定多个通道进行转换,并且在规则组情况下,需要用到DMA来转运数据。
4.连续转换,扫描模式
六、DMA直接存储器存取
6.1简介
DMA可以支持存储器到存储器之间的数据转运,以及外设和存储器之间的数据转运,前者可以通过软件触发,这是DMA将会以最快速度将数据进行转运,而后者需要硬件触发,比如ADC中需要等这一通道中的ADC转换完成后再将其转运。
本块板子上只有1个DMA资源,7个DMA通道。
- 总体来看,DMA就分为两块:cpu(Cortex-M3)和存储器(主存储器Flash、运行内存SRAM,外设存储器SRAM)
七、通信
7.1简介
- 全双工是指通信双方可以同时进行数据的发送和接收;半双工是指通信双方共用一条数据线,不能同时发送接收数据,但是数据可以在双方之间转递;单工是指数据只能从一方到另一方。
- 同步是指通信双方拥有一条共同的时钟线,异步是指双方没有时钟线,需要双方约定采样频率,并且还需要加一些帧头帧尾,来进行采样位置的对齐
- 单端指的是电平信号是相对GND而言的,所以需要接GND,而差分是靠两个差分引脚的电压差来传输信号的
7.2串口(USART)
框图如下:
- 发送数据寄存器(TDR)和接收数据寄存器(RDR)物理上是两个寄存器,但是实际占用一个地址,前者只读,后者只写
- 当向TDR写入一个数据后,硬件就会检测到,同时硬件也会去检测硬件移位寄存器是否为空,若为空,则立刻将TDR中的数据转移到移位寄存器中准备发射,数据从TDR转移后,会置一个标志位TXM(发送寄存器空),我们可以来检测这个标志位是否为1来决定是否想TDR继续写入数据。然后发送寄存器会在发送器控制的驱动下,将数据一位一位的移出去,注意这里是向右移位,对应低位先行
- 接收数据也是同样的过程,接收的数据最开始是从低位向右一位一位地往接受寄存器中转移,当转移完成后,就会立刻将数据转移到接受数据寄存器RDR中,同时会置一个标志位RXNE(接受寄存器非空),如果检测到这个标志位为1,我们就可以将数据从RDR中读走了。
- 硬件数据流控是为了避免数据发送过快时造成数据丢失,引脚nRTS(Request To Send)是请求发送,是输出脚,引脚nCTS(Clear To Send)是清楚发送,是输入脚,就是用于接受别人的nRTS的信号,n是代表低电平有效。CTS和RTS也是需要交叉连接的,如同TX和RX一样,想想为什么