STM32文章(GPIO口八种模式、串口、定时器、中断、DMA...)

STM32文章
1.
再说怎么样学的好,怎么样就算好,本身就说不好,做项目的时候,考虑的是一个综合的电路板。单从STM32来说的话,就是要熟练的使用它的各个模块,比如IO,ADC,DAC,PWM等。
初学者都有一个心理误区,看到别人的STM32工程,既庞大又复杂,就把你给吓住了。其实,你可以问问他,这么大个工程,有多少是他自己一个字母一个字母的敲上去的?所以…
这里有必要说一下,一个工程中,有很多文件都是官方提供的,我们只需要学会怎么使用它的函数即可。再有,例如,当你做第一个项目时,用到了ADC模块,当你做第二个项目时,又需要用ADC模块,那就可以把之前的ADC部分程序复制粘贴过来,然后再稍作修改就可以。所以…
AHB系统总线分为APB1(36MHz)和APB2(72MHz),其中2>1,意思是APB2接高速设备
HSE Osc(High Speed External Oscillator)高速外部晶振,一般为8MHz,HSI RC(High Speed InternalRC)高速内部RC,8MHz
LSE Osc(Low Speed External Oscillator)低速外部晶振,一般为32.768KHz,LSI RC(Low Speed InternalRC)低速内部晶振,大概为40KHz左右,提供看门狗时钟和自动唤醒单元时钟源 FLASH在写的时候,一定不能读,如果有读操作,那么将会锁住总线
STM32有两种看门狗(IWDG独立看门狗《独立时钟》,WWDG窗口看门狗《由APB1分频而来》)
SPI的的最高频率为36MHz(fpclk/2)
TIM1和TIM8高级定时器在输出PWM时,需要配置一下主输出功能(CtrlPWMOutputs)才能输出PWM。其他的通用定时器不需要这样配置。但是TIM6和TIM7没有PWM输出功能。
TIMx通用定时器有4个独立通道,分别可以用来作为:输入捕获、比较输出、PWM生成、单脉冲模式输出。
ADC的时钟不要超过14MHz,否则转换精度会下降。最大转换速率为1MHz,即转换周期为1us(14MHz,采样周期为1.5个ADC时钟) 
CAN总线(ControllerArea Network)。CAN控制器根据两根线上的电位差来判断总线电平,总线电平又分为显性电平和隐性电平,二者必居其一。
CAN总线具有6个特点:1:多主控制(挂接在总线上的所有设备均可以成为主设备,并且设备ID是用来决定设备的优先级,没有设备地址概念),2:系统若软性(没有设备地址概念),3、通讯速度较快,通讯距离较远(1Mbps下40M,5kbps下10KM),4、具有错误检测、错误通知(通知其他设备)和错误恢复功能(强制结束发送,重复发送接收错误的信息。),5、故障封闭,当总线上的设备发生连续故障错误时,CAN控制器会把改控制器踢出总线。6、连接节点多。理论上可以无限制加载,但是受到时间延迟和电气负载的限制,实际数目是有限制的。降低传输速度可以适当增加可挂接负载个数。
①、PWM主要就是控制频率和占空比的:这两个因素分别通过两个寄存器控制: TIMX_ARR和TIMX_CCRX。ARR寄存器就是自动重装寄存器,也就是计数器记到这个数以后清零再开始计,这样PWM的频率就是tim_frequency/(TIMX_ARR-1)。在计数时会不停的和CCRX寄存器中的数据进行比较,如果小于的话是高电平或者低电平,计数值大于CCRX值的话电平极性反相。所以这也就控制了占空比。

1.模拟输入

从上图我们可以看到,我觉得模拟输入最重要的一点就是,他不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。
2.浮空输入

该输入状态,我的理解是,它的输入完全由外部决定,我觉得在数据通信中应该可以使用该模式。应为在数据通信中,我们直观的理解就是线路两端连接着发送端和接收断,他们都需要准确获取对方的信号电平,不需要外界的干预。所以我觉得这种情况适合浮空输入。比如我们熟悉的I2C通信的输入状态。

1)外部通过IO口输入电平,外部电平通过上下拉部分(浮空模式下都关闭,既无上拉也无下拉电阻)
2)传输到施密特触发器(此时施密特触发器为打开状态)
3)继续传输到输入数据寄存器IDR
4)CPU通过读输入数据寄存器IDR实现读取外部输入电平值。在输入浮空模式下可以读取外部输入电平
3.上拉输入
上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。

和输入浮空模式相比较,不同之处在于内部有一个上拉电阻连接到VDD(输入上拉模式下,上拉电阻开关接通,阻值约30-50K)
外部输入通过上拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取。
4、下拉输入
和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。

和输入浮空模式相比较,不同之处在于内部有一个下拉电阻连接到VSS(输入下拉模式下,下拉电阻开关接通,阻值约30-50K)
外部输入通过下拉电阻,施密特触发器存入输入数据寄存器IDR,被CPU读取

5、开漏输出
开漏输出,输出端相当于三极管的集电极,所以适合与做电流驱动的应用。要得到高电平,需要上拉电阻才可以。(例如模拟软件实现I2C的输出)

6、 推挽输出
推挽输出使用了推挽电路,结合推挽电路的特性,它是由两个MOSFET组成,一个导通的同时,另外一个截至,两个MOSFET分别连接高低电平,所以哪一个导通就会输出相应的电平。推挽电路速度快,输出能力强,直接输出高电平或者低电平。

7、复用功能的开漏输出
8、 复用推挽输出
注:推挽输出和开漏输出的区别
   推挽输出:
    可以输出强高/强低电平,可以连接数字器件
   开漏输出:
    只能输出强低电平(高电平需要依靠外部上拉电子拉高),适合做电流型驱动,吸收电流能力较强(20ma之内)
总结一下,在STM32中选用IO模式:
(1)模拟输入_AIN ——应用ADC模拟输入
(2)浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1
(3)带上拉输入_IPU——IO内部上拉电阻输入
(4)带下拉输入_IPD—— IO内部下拉电阻输入
(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能
(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的
(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)
(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)
 2,端口的重映射:
  串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7
  端口重映射的作用:方便布线
STM32所有的IO口都可作为中断输入

UART

  1. UART是什么
    UART是通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是设备间进行异步通信的关键模块。UART负责处理数据总线和串行口之间的串/并、并/串转换,并规定了帧格式;通信双方只要采用相同的帧格式和波特率,就能在未共享时钟信号的情况下,仅用两根信号线(Rx 和Tx)就可以完成通信过程,因此也称为异步串行通信。
    UART使用的是 异步,串行通信。
    串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
    数据传送速率用波特率来表示,即每秒钟传送的二进制位数。例如数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200字符/秒=1200波特。

ADC分辨率与精度的区别
回到电子技术上,我们考察一个常用的数字温度传感器:AD7416。供应商只是大肆宣扬它有10位的AD,分辨率是1/1024。那么,很多人就会这么欣喜:哇塞,如果测量温度0-100摄氏度,100/1024……约 等于0.098摄氏度!这么高的精度,足够用了。但是我们去浏览一下AD7416的数据手册,居然发现里面赫然写着:测量精度0.25摄氏度!所以说分辨率跟精度完全是两回事,在这个温度传感器里,只要你愿意,你甚至可以用一个14位的AD,获得1/16384的分辨率,但是测量值的精度还是0.25摄氏 度_

三、常用库函数
void ADC_Init(ADC_TypeDefADCx, ADC_InitTypeDef ADC_InitStruct);

void ADC_DeInit(ADC_TypeDef*ADCx)

void ADC_Cmd(ADC_TypeDef*ADCx, FunctionalState NewState);

voidADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);

voidADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);

voidADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,

uint8_t Rank, uint8_tADC_SampleTime);

uint16_tADC_GetConversionValue(ADC_TypeDef* ADCx);

voidADC_ResetCalibration(ADC_TypeDef* ADCx);

FlagStatusADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);

void ADC_StartCalibration(ADC_TypeDef*ADCx);

FlagStatusADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

• ADC初始化函数ADC_Init
void ADC_Init(ADC_TypeDef* ADCx,ADC_InitTypeDef* ADC_InitStruct);

typed struct
{
uint32_tADC_Mode;
FunctionalState ADC_ScanConvMode;
FunctionalState ADC_ContinuousConvMode;
uint32_t ADC_ExternalTrigConv;
uint32_t ADC_DataAlign;
uint8_t ADC_NbrOfChannel;
}ADC_InitTypeDef;

结构体内成员函数说明:
ADC_Mode:ADC模式,配置ADC_CR1寄存器的位[19:16] :DUALMODE[3:0]位
ADC_ScanConvMode:是否使用扫描模式;ADC_CR1位8:SCAN位
ADC_ContinuousConvMode:单次转换OR连续转换;ADC_CR2的位1:CONT
ADC_ExternalTrigConv:触发方式:ADC_CR2的位[19:17] :EXTSEL[2:0]
DC_DataAlign:对齐方式,左对齐还是右对齐;ADC_CR2的位11:ALIGN
ADC_NbrOfChannel:规则通道序列长度:ADC_SQR1的位[23:20]: L[3:0]

配置说明
ADC_InitTypeDef ADC_InitStructure; //定义一个ADC结构体
ADC_InitStructure.ADC_Mode =ADC_Mode_Independent; //独立模式
ADC_InitStructure.ADC_ScanConvMode =DISABLE; //不开启扫描ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //触发软件
ADC_InitStructure.ADC_DataAlign =ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //初始化结构体成员

• ADC使能函数 ADC_Cmd();
void ADC_Cmd(ADC_TypeDef* ADCx,FunctionalState NewState);
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1

• ADC使能软件转换函数 ADC_SoftwareStartConvCmd
void ADC_SoftwareStartConvCmd(ADC_TypeDef*ADCx, FunctionalState NewState)
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能

• ADC规则通道配置函数ADC_RegularChannelConfig
void ADC_RegularChannelConfig(ADC_TypeDef*ADCx,
uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
ADC_RegularChannelConfig(ADC1, ch, 1,ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5周期

• ADC获取转换结果函数ADC_GetConversionValue
uint16_t ADC_GetConversionValue(ADC_TypeDef*ADCx);
ADC_GetConversionValue(ADC1);//获取ADC1转换结果

四、程序
(一)程序实例是ADC1的通道10(PC0)进行单次转化,实现ADC功能需要以下几个步骤。

1开启PC口时钟和ADC1时钟,设置PC0为模拟输入。
GPIO_Init();
RCC_APB2PeriphClockCmd();

2复位ADC1,同时设置ADC1分频因子。
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
ADC_DeInit(ADC1);

3初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息。
void ADC_Init(ADC_TypeDef* ADCx,ADC_InitTypeDef* ADC_InitStruct);

4使能ADC并校准。
ADC_Cmd(ADC1,ENABLE);

5配置规则通道参数:
ADC_RegularChannelConfig();

6开启软件转换:ADC_SoftwareStartConvCmd(ADC1);

7等待转换完成,读取ADC值。
ADC_GetConversionValue(ADC1);

具体代码分成了两个部分,driver_adc.c中的void Adc_Init(void)初始化函数和main.c中的u16 Get_Adc(u8 ch)函数

//对ADC获取值函数调用,获取采集times的平均值
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32temp_val=0;
u8t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch); //每次获取的值累加到temp_val
delay_ms(5);
}
returntemp_val/times; //返回平均值
}

主函数main.c对ADC初始化函数和ADC的平均值函数调用,并通过串口打印出来
int main(void)
{
u16adcx;
float temp;
uart_init(115200); //串口初始化为115200
Adc_Init(); //初始化ADC
while (1)
{

adcx=Get_Adc_Average(ADC_Channel_10,10);//获取ADC1通道10的10次平均值
 temp=(float)adcx*(3.3/4096);            //将采到的ADC值转换为电压值
 printf("ad: %.3f V\r\n",temp);         //串口打印出来
 delay_ms(100);         

}
}
可以通过调用STM32的固件库中的函数 NVIC_PriorityGroupConfig( ) 选择使用哪种优先级分组方式,这个函数的参数有下列5种:
NVIC_PriorityGroup_0 => 选择第0组
NVIC_PriorityGroup_1 => 选择第1组
NVIC_PriorityGroup_2 => 选择第2组
NVIC_PriorityGroup_3 => 选择第3组
NVIC_PriorityGroup_4 => 选择第4组

开关总中断
在STM32/Cortex-M3中是通过改变CPU的当前优先级来允许或禁止中断。
PRIMASK位:只允许NMI和hard fault异常,其他中断/ 异常都被屏蔽(当前CPU优先级=0)。
FAULTMASK位:只允许NMI,其他所有中断/异常都被屏蔽(当前CPU优先级=-1)。
在STM32固件库中(stm32f10x_nvic.c和stm32f10x_nvic.h) 定义了四个函数操作PRIMASK位和FAULTMASK位,改变CPU的当前优先级,从而达到控制所有中断的目的。
下面两个函数等效于关闭总中断:
void NVIC_SETPRIMASK( void );void NVIC_SETFAULTMASK( void );
下面两个函数等效于开放总中断:
void NVIC_RESETPRIMASK( void );void NVIC_RESETFAULTMASK( void );
上面两组函数要成对使用,不能交叉使用。
第一种方法:
NVIC_SETPRIMASK( ); //关闭总中断NVIC_RESETPRIMASK( );//开放总中断
第二种方法:
NVIC_SETFAULTMASK( ); //关闭总中断NVIC_RESETFAULTMASK( );//开放总中断
通常使用
NVIC_SETPRIMASK( ); //Disable InterruptsNVIC_RESETPRIMASK( );//Enable Interrupts
在3.0的库中已经没有第一种方法
NVIC_SETPRIMASK(); //关闭总中断NVIC_RESETPRIMASK();//开放总中断
第二种方法:
NVIC_SETFAULTMASK( ); //关闭总中断NVIC_RESETFAULTMASK( );//开放总中断
也可以用宏定义
#define CLI( ) __set_PRIMASK( 1 )#define SEI( ) __set_PRIMASK( 0 )
2.程序代码
按键的电路在之前已经给出,可参照上一节。
程序目的:使用中断式按键控制LED灯的开关。
exti.h源代码:
#ifndef __EXTI_H#define __EXTI_H#include "stm32f10x.h"void EXTI_PE4_Init(void);#endif
exti.c源代码:
#include “stm32f10x.h”#include “exti.h”/************************************************************************
PAx ~ PGx 端口的中断事件都连接到了EXTIx,即同一时刻EXTIx只能响应一个端口
多个 GPIO 口的时间无法同一时间响应,但是可以分时复用
EXTI最普通的应用就是接上一个按键,设置为下降沿触发,用中断来检测按键
************************************************************************/static void NVIC_Configuration( void ){

NVIC_InitTypeDef NVIC_InitStructure;    
//将优先级组配置成第1组
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_1 );    
/*****************************************************************************
NVIC_IRQChannel指需要配置的中断向量。因使用PE4口的按键,所以配置在4通道
如果使用GPIO_PIN_5~GPIO_PIN9的任意一个,    则配置通道为EXTI9_5_IRQn
如果使用GPIO_PIN_10~GPIO_PIN15的任意一个,则配置通道为EXTI15_10_IRQn
******************************************************************************/
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init( &NVIC_InitStructure );

}void EXTI_PE4_Init(void){

GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;    //打开GPIOE时钟和AFIO时钟
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );

NVIC_Configuration();    
// PE4外部中断配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init( GPIOE, &GPIO_InitStructure );

GPIO_EXTILineConfig( GPIO_PortSourceGPIOE, GPIO_PinSource4 );
EXTI_InitStructure.EXTI_Line = EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;

EXTI_Init( &EXTI_InitStructure );

}
中断服务函数:
/********************************************************
中断服务函数写在stm32f10x_it_c中,新定义一个函数即可。
它的名字必须要与启动文件startup中的中断向量表定义一致。
中断函数入口的函数名只有下面两种写法:
(1)
EXTI0_IRQHandler ;EXTI Line 0
EXTI1_IRQHandler ;EXTI Line 1
EXTI2_IRQHandler ;EXTI Line 2
EXTI3_IRQHandler ;EXTI Line 3
EXTI0_IRQHandler ;EXTI Line 4
(2)
EXTI9_5_IRQHandler ;EXTI Line 5~9
EXTI15_10_IRQHandler ;EXTI Line 10~15
********************************************************/void EXTI4_IRQHandler( void ){ //确保产生了 EXTI Line 中断
if( EXTI_GetITStatus( EXTI_Line4 ) != RESET )
{
LED2_REV;
//清除中断标志位
EXTI_ClearITPendingBit( EXTI_Line4 );
}
}
主函数main.c:
#include “stm32f10x.h”#include “led.h”#include "exti.h"int main(void){
LED_Init();
LED2(ON);

EXTI_PE4_Init();    
while(1)
{        //主函数不执行任何动作,只需要等待中断
}

}

什么是DMA???
DMA:全称Direct Memory Access,即直接存储器
DMA 传输将数据从一个地址空间复制到另外一个地址空间。CPU只需初始化DMA即可,传输动作本身是由 DMA 控制器来实现和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。这样的操作并没有让处理器参与处理,CPU可以干其他事情,当DMA传输完成的时候产生一个中断,告诉CPU我已经完成了,然后CPU知道了就可以去处理数据了,这样子提高了CPU的利用率,因为CPU是大脑,主要做数据运算的工作,而不是去搬运数据。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。

STM32F1系列的MCU有两个DMA控制器(DMA2只存在于大容量产品中),DMA1有7个通道,DMA2有5个通道,每个通道专门用来管理来自于一个或者多个外设对存储器的访问请求。还有一个仲裁器来协调各个DMA请求的优先权。

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值