ARM学习笔记基础STM32

资料来源:正点原子STM32

目录

GPIO工作原理

常用寄存器

实验:跑马灯(库函数版本)

实验步骤

时钟

IO引脚的复用和映射

串口

串行通信方式

异步通信特点

串口配置方法

串口配置步骤(将PA9,PA10设置为串口1)

实验:串口通信

独立看门狗

实验:看门狗

窗口看门狗

窗口看门狗实验

通用定时器

实验:定时器中断

外部中断

外部中断配置步骤

ADC

量程(模拟量输入范围)

精度(分辨率resolution)

转换速率(MSPS与conventor clock的不同)

通道数

转换模式

ADC采样时间

参数相关配置

DMA

请求映射

流配置过程

DMA中断

库函数

多重ADC

三重ADC转换原理

DMA模式请求

三重ADC采样单通道信号ADC与DMA配置


GPIO工作原理

一个stm35包括7组IO,一组IO有16个IO口,一组IO口下面有10个寄存器用于配置这16个IO口的状
态。 
如果一个IO口需要两位寄存器控制,则一组IO口由32位的IO口控制。 
如果一个IO口需要一位寄存器控制,则一组IO口由32位的IO口控制,其中只用到了低16位,高16
位保留。
GPIO工作方式: 
4种输入模式: 
输入浮空 
输入上拉 
输入下拉 
模拟输入 
4种输出模式: 
开漏输出(带上拉或者下拉) 
开漏复用功能(带上拉或者下拉) 
推挽式输出(带上拉或者下拉) 
推挽式复用功能(带上拉或者下拉)
4种最大输出速度: 
­2MHZ 
­25MHz 
­50MHz 
­100MHz

常用寄存器

每组IO口都拥有以下常用寄存器: 
一个端口模式寄存器(GPIOx_MODER) 
一个端口输出类型寄存器(GPIOx_OTYPER) 
一个端口输出速度寄存器(GPIOx_OSPEEDR) 
一个端口上拉下拉寄存器(GPIOx_PUPDR) 
一个端口输入数据寄存器(GPIOx_IDR) 
一个端口输出数据寄存器(GPIOx_ODR) 
一个端口置位/复位寄存器(GPIOx_BSRR) 
一个端口配置锁存寄存器(GPIOx_LCKR) 
两个复位功能寄存器(低位GPIOx_AFRL & GPIOx_AFRH)

实验:跑马灯(库函数版本)

以下是几个寄存器的库函数参数: 
GPIO_InitTypeDef GPIO_InitStructure;
MODER寄存器 
端口模式控制寄存器,用于控制GPIOx的工作模式,每2位控制一个IO口,代码中属性为:
GPIO_InitStructure.GPIO_Mode

typedef enum
{
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode 输入模式*/
GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode 输出模式*/
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode 复用功能模式*/
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode 模拟模式*/
}GPIOMode_TypeDef;

OTYPER寄存器 
用于控制GPIOx的输出类型,每1位控制一个IO口,该寄存器仅用于输出模式,输入模式下不
起作用,代码属性:GPIO_InitStructure.GPIO_OType

typedef enum
{
GPIO_OType_PP = 0x00,/*输出推挽(复位状态)*/
GPIO_OType_OD = 0x01/*输出开漏*/
}GPIOOType_TypeDef;

OSPEEDR寄存器 
用于控制GPIOx的输出速度,每2位控制一个IO口,仅用于输出模式,代码中属性为:
GPIO_InitStructure.GPIO_Speed

#define GPIO_Speed_2MHz GPIO_Low_Speed
#define GPIO_Speed_25MHz GPIO_Medium_Speed
#define GPIO_Speed_50MHz GPIO_Fast_Speed
#define GPIO_Speed_100MHz GPIO_High_Speed

PUPDR寄存器 
控制GPIOx的上拉/下拉,每2位控制一个IO口,代码属性位:GPIO_InitStructure.GPIO_PuPd

typedef enum
{
GPIO_PuPd_NOPULL = 0x00, /*无上拉或下拉*/
GPIO_PuPd_UP = 0x01, /*上拉*/
GPIO_PuPd_DOWN = 0x02 /*下拉*/
}GPIOPuPd_TypeDef;

实验步骤

1.模板文件夹新建HARDWARE文件,并且打开程序建立led.h,led.c保存。引入.h文件,添加头文件
路径。 
2.在头文件中定义初始化函数.

#ifndef __LED_H
#define __LED_H
void LED_Init(void);
#endif

3.再.c重实现初始化函数

#include "led.h"
#include "stm32f4xx.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;//设置一个结构体对GPIO各个状态的设置
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);//设置GPIOF为始能状态
//F9
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//设置GPIOF9
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//50MHZ
GPIO_Init(GPIOF,&GPIO_InitStructure);//库函数中的初始化函数
GPIO_SetBits(GPIOF,GPIO_Pin_9);//设置为1
//F10
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOF,&GPIO_InitStructure);
GPIO_SetBits(GPIOF,GPIO_Pin_10);
}

4.main函数

#include "stm32f4xx.h"
#include "led.h"
#include "delay.h"
int main(void)
{
delay_init(168);
LED_Init();
while(1){
GPIO_SetBits(GPIOF,GPIO_Pin_9);
GPIO_SetBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
GPIO_ResetBits(GPIOF,GPIO_Pin_9);
GPIO_ResetBits(GPIOF,GPIO_Pin_10);
delay_ms(500);
}
}

时钟

同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,可以理解为时钟就是执行命令的
时间,始终越快,执行一条命令时间越短。时钟图:

MCO1和MCO2是两个引脚用于输出芯片中的时钟,最大频率不超过100M
CSS:时钟安全单元,如果系统时钟的输入HSE外部晶振出了问题,则CSS会将系统时钟切换
到HSI时钟源 
STM32 中有5个时钟源,HSI,HSE,LSI,LSE,PLL。 
①、LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使
用。 
②、LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。 
③、HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为
4MHz~26MHz。 
我们的开发板接的是 8M 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。 
④、HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作 PLL 
输入。 
⑤、PLL 为锁相环倍频输出。STM32F4 有两个 PLL: 
1) 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。 
第一个输出 PLLP 用于生成高速的系统时钟(最高 168MHz) 
第二个输出 PLLQ 用于生成 USB OTG FS 的时钟(48MHz),随机数发生器的时钟和 SDIO 
时钟。 
2)专用 PLL(PLLI2S)用于生成精确时钟,从而在 I2S 接口实现高品质音频性能。
主 PLL 时钟的时钟源要先经过一个分频系数为 M 的分频器,然后经过 
倍频系数为 N 的倍频器出来之后的时候还需要经过一个分频系数为 P(第一个输出 PLLP)或 
者 Q(第二个输出 PLLQ)的分频器分频之后,最后才生成最终的主 PLL 时钟。 
例如我们的外部晶振选择 8MHz。同时我们设置相应的分频器 M=8,倍频器倍频系数 N=336, 
分频器分频系数 P=2,那么主 PLL 生成的第一个输出高速时钟 PLLP 为: 
PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz
系统初始化或者复位时,调用 SystemInit 函数对相关时钟进行默认配置,如果我们需要修改某些时
钟源配置,我们则再RCC相关寄存器中配置。 
STM32F4 的外设在使用之前,必须对时钟进行使能,如果没有使能时钟,那么外设是无法正常工
作的。下面是外设始能相关函数:

void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewS
tate);
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewS
tate);
void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewS
tate);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewS
tate);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewS
tate);

这里主要有5个外设始能函数,5个函数分别代表5个总线下面挂载的外设时钟,分别是AHB1 总
线,AHB2 总线,AHB3 总线,APB1 总线以及 APB2 总线。要使能某个外设,调用对应的总线外
设时钟使能函数即可。
修改系统时钟: 
在system_stm32f4xx.c中,代码721行设置HSE为系统时钟输入源,在316行设置各个分频系
数,默认输出是168M的系统时钟
初始化后的时钟: 
SYSCLK(系统时钟) =168MHz 
AHB总线时钟(HCLK=SYSCLK) =168MHz 
APB1总线时钟(PCLK1=SYSCLK/4) =42MHz 
APB2总线时钟(PCLK2=SYSCLK/2) =84MHz 
PLL主时钟 =168MHz

IO引脚的复用和映射

STM32F4系列微控制器IO引脚通过一个复用器连接到内置外设或模块。该复用器一次只允许一个
外设的复用功能(AF)连接到对应的IO口。这样可以确保共用同一个IO引脚的外设之间不会发生冲
突。 
每个IO引脚都有一个复用器,该复用器采用16路复用功能输入(AF0到AF15),可通过
GPIOx_AFRL(针对引脚0­7)和GPIOx_AFRH(针对引脚8­15)寄存器对这些输入进行配置,每四
位控制一路复用。 
相当于每个IO口的引脚都有一个复用器,而这个复用器对每个IO口的功能进行配置。比如说有一个
IO口PA2,他的一个复用器如图: 

因为是引脚2,所以用寄存器AFPL进行配置,AFPL配置该引脚的复用器选择哪一个AF,则选定了
该IO口的功能。
接下来看AFRL的示意图:

AFRL是一个32位寄存器,对于每一组IO口,一共16位IO,AFRL控制一个IO口的低8位,AFRH控
制一组IO口的高8位,比如我们要控制GPIOA2引脚的复用功能则调用GPIOA 这组IO的寄存器
AFRL,如上图,需要修改AFRL2的值来控制PA2 IO口,如果我们需要他的串口1功能,由图可以看
出USART1接连到复用器的AF7,所以我们需要根据图选择AF7,就是把AFRL2设置为0111。
接下来以PA9,PA10配置为串口1为例。 
①GPIO端口时钟使能。 
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
②复用外设时钟使能。 
比如你要将端口PA9,PA10复用为串口,所以要使能串口时钟。 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
③端口模式配置为复用功能。 GPIO_Init()函数。 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能 
④配置GPIOx_AFRL或者GPIOx_AFRH寄存器,将IO连接 
到所需的AFx。
/PA9连接AF7,复用为USART1_TX /
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
/* PA10连接AF7,复用为USART1_RX*/ 
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟 ①
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟 ②
//USART1端口配置③
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//串口1对应引脚复用映射 ④
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USAR
T1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为US
ART1

串口

串行通信方式

按照数据传送方向,分为: 
单工: 
数据传输只支持数据在一个方向上传输 
半双工: 
允许数据在两个方向上传输,但是,在某一时刻,只允许数 
据在一个方向上传输,它实际上是一种切换方向的单工通信; 
全双工: 
允许数据同时在两个方向上传输,因此,全双工通信是两个 
单工通信方式的结合,它要求发送设备和接收设备都有独立 
的接收和发送能力。

按照时钟分类 
同步通信:带时钟同步信号传输。(由时钟控制) 
­SPI,IIC通信接口 
异步通信:不带时钟同步信号。(有一个约定) 
­UART(通用异步收发器),单总线
常见串行通信接口

stm32中的UART串口引脚:

异步通信特点

异步传输需要定义的参数: 
起始位 
数据位(8位或者9位) 
奇偶校验位(第9位) 
停止位(1,15,2位) 
波特率设置

奇偶校验的意思:比如数据位8位有偶数个1,进行偶校验,则第九位填0,若奇校验,则补1.

串口配置方法

相关寄存器: 
USART_SR状态寄存器 
USART_DR数据寄存器 
USART_BRR波特率寄存器
串口初始化方法: 
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct); 
第一个参数是串口几,第二个是一个结构体,结构体参数: 
结构体就是一种约定

typedef struct
{
uint32_t USART_BaudRate;//波特率
uint16_t USART_WordLength; //字长,8还是9位
uint16_t USART_StopBits; //停止位
uint16_t USART_Parity; //奇偶校验
uint16_t USART_Mode; //始能发送,接收选择
uint16_t USART_HardwareFlowControl; //硬件流控制
} USART_InitTypeDef;

串口配置步骤(将PA9,PA10设置为串口1)

1.同样打开模板,清空main函数,始能串口时钟与GPIO时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口时钟,始能AHB2,r
cc.h
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//GPIO时钟,始能GPI
OA,IO口用AHB1

2.引脚复用映射:

GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);//PA9映射到串口1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//PA10映射到串
口1

3.对IO口进行初始化

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//设置为复用模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

4.对串口1进行初始化,并且始能:

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=115200;//约定波特率位115200
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_N
one;//不需要硬件流控制
USART_InitStructure.USART_Mode=USART_Mode_Rx |USART_Mode_Tx;//始能发送也接收
USART_InitStructure.USART_Parity=USART_Parity_No;//不使用奇偶校验
USART_InitStructure.USART_StopBits=USART_StopBits_1;//停止位设置为1
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长设置为8位字
长。因为没有奇偶校验。
USART_Init(USART1,&USART_InitStructure);//usart.h
USART_Cmd(USART1,ENABLE);

5.中断始能设置

USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//始能中断

第一个参数是中断发生的串口号,第二个是中断类型,RXNE为接受非空,所以这里只要接收到数
据就产生中断
6.中断分组设置: 
在main函数中对中断进行分组:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2位抢占优先级,2位相应优先级

7.中断初始化设置

NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;;//设置通道为串口1
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//始能通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//设置相应优先级
NVIC_Init(&NVIC_InitStructure);//misc.h

8.编写中断服务函数:

void USART1_IRQHandler(void)
{
u8 res;
if(USART_GetITStatus(USART1,USART_IT_RXNE))//判断前面始能的中断是否发生,
如果发生则调用串口接收函数
{
res=USART_ReceiveData(USART1);//接收串口1的函数
USART_SendData(USART1,res);//发数
}
}

9.在main函数中完善

int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2位抢占优先级,2位相应优
先级
My_USART1_Init();
while(1);//等待中断的发生
}

实验:串口通信

本次实验前面步骤与上一个串口讲解相同,前面省略,列出了步骤和代码:
1.串口时钟使能:RCC_APBxPeriphClockCmd(); 
GPIO时钟使能:RCC_AHB1PeriphClockCmd(); 
② 引脚复用映射: 
GPIO_PinAFConfig();
③GPIO端口模式设置:GPIO_Init(); 模式设置为GPIO_Mode_AF 
④串口参数初始化:USART_Init(); 
⑤使能串口:USART_Cmd(); 
⑥开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤) 
NVIC_Init();
USART_ITConfig(); 
⑦编写中断处理函数:USARTx_IRQHandler();

//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为
USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用
为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA1
0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位
数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowCon
trol_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发
模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
#if EN_USART1_RX //该处是宏定义,1.
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
#endif
}
//下面中断处理函数完成的任务是来一个数据,触发一次中断,然后将来的数据存在USART_RX_BU
F[USART_RX_STA&0X3FFF]数组中,存满USART_REC_LEN个后又重新存储。
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到
的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成?SART_RX_STA16位,接收状态标
记,相当于检验最高位是否为0,最高位为0,if成立,没有接收完成。
{
//USART_RX_STA是一个16位的接收状态标记,本次接收的数据存在Res里,
if(USART_RX_STA&0x4000)//检验高第二位是否为1,如果为1,则表示接收到
了0X0d,进入if语句,否则进入else,//最后才会进入这个if
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了 ,将最高位标记为1
}
else //还没收到0X0D,
{
if(Res==0x0d)USART_RX_STA|=0x4000;///再进入第二个if
else//程序首先进入本个第一个else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//0011 1111 11
11 1111
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))
USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif

上述代码的头文件中添加了宏定义:

#define USART_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末
字节为换行符
extern u16 USART_RX_STA; //接收状态标记
//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);

主函数实现了检测最高位,如果检测到最高位为1,则发送完成,然后将接收到的数据发送回去:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
//ALIENTEK 探索者STM32F407开发板 实验4
//串口通信实验 -库函数版本
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com
//广州市星翼电子科技有限公司
//作者:正点原子 @ALIENTEK
int main(void)
{
u8 t;
u8 len;
u16 times=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //延时初始化
uart_init(115200); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)//不停的判断最高位接收完成标志是否是1
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]); //向串口1
发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等
待发送结束,发送结束后,USART_FLAG_TC为1,不等于1时,发送未结束,一直在循环里等到发送
结束标志位置1
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nALIENTEK 探索者STM32F407开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
}
}

独立看门狗

 

出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模
块或者芯片,俗称“看门狗”(watchdog) 。 
在启动正常运行的时候,系统不能复位。 
在系统跑飞(程序异常执行)的情况,系统复位,程序重新执行。 
STM32有两个看门狗:
独立看门狗(IWDG)由专用的低速时钟(LSI=32K)驱动,即使主时钟发生 故障它仍有效。独
立看门狗适合应用于需要看门狗作为一个在主程序之外 能够完全独立工作,并且对时间精度要
求低的场合。
窗口看门狗由从APB1时钟分频后得到时钟驱动。通过可配置的时间窗口来检测应用程序非正
常的过迟或过早操作。 窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序。
功能: 
在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗。此时计数器开始从其复位值
0xFFF递减,当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)。 
无论何时,只要在键值寄存器IWDG_KR中写入0xAAAA(通常说的喂狗), 自动重装载寄存器
IWDG_RLR的值就会重新加载到计数器,从而避免看门狗复位。 
如果程序异常,就无法正常喂狗,从而系统复位。

首先在IWDG_KR中写入0xCCCC,开启看门狗,LSI提供一个时钟,开启看门狗后,12位递减计数
器中的数据开始递减,直到减到0,系统复位,如果在递减的过程中(比如递减到50),在IWDG_KR
中输入0xAAAA,则12位的重装寄存器会把重载寄存器IWDG_RLR中的数据装配到递减计数器中,
此时就从新装载进来的数值递减。如果这中途又喂狗了,又从IWDG_RL重装的计数值再一次递
减,不断循环。

 

实验:看门狗

库函数: 
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//取消写保护:0x5555使能 
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PR 
void IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLR 
void IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KR 
void IWDG_Enable(void);//使能看门狗:写0xCCCC到KR 
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);//状态:重装载/预分频 更新
溢出时间计算

 

操作步骤: 
1.取消寄存器写保护: 
IWDG_WriteAccessCmd(); 
② 设置独立看门狗的预分频系数,确定时钟: 
IWDG_SetPrescaler(); 
③ 设置看门狗重装载值,确定溢出时间: 
IWDG_SetReload(); 
④ 使能看门狗 
IWDG_Enable(); 
⑤ 应用程序喂狗: 
IWDG_ReloadCounter();
溢出时间计算: 
Tout=((4×2^prer) ×rlr) /32 (M4)
具体步骤: 
1.本节利用案件实验3为基础,添加iwdg.c与iwdg.h文件,再.h文件中声明初始化函数:

#ifndef __WDG_H
#define __WDG_H
#include "sys.h"
void IWDG_Init(u8 prer,u16 rlr);//prer为分频系数,rlr为重装载值
#endif

 

2.添加看门狗固件库,在工程文件FWLIB上右键,添加,选取工程目录FWLIB中的src中的stm32法 
xx_iwdg.c,添加进工程,编译添加的文件 
3.在iwdg.c中编写Init函数

#include "iwdg.h"
void IWDG_Init(u8 prer,u16 rlr)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//取消写保护
IWDG_SetPrescaler(prer);//编写预分频系数
IWDG_SetReload(rlr);//设置重装载值
void IWDG_ReloadCounter(void);//喂狗,将0XAAAA加载到KR寄存器,那么系统就会
喂狗
IWDG_Enable();//看门狗始能
}

4.编写main函数

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "iwdg.h"
int main(void)
{
u8 key; //保存键值
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口 ,此时将LED灯电平拉高,灯灭
BEEP_Init(); //初始化蜂鸣器端口
KEY_Init(); //初始化与按键连接的硬件接口
delay_ms(500); //为了看到复位的现象
LED0=0; //先点亮红灯
IWDG_Init(4,1000);//这里的4是16进制的,2s
while(1)
{
if(KEY_Scan(0)==WKUP_PRES)
{
IWDG_ReloadCounter();
}
}
}

这里对main函数进行解释,首先看门狗的功能是在开启看门狗后(看门狗初始化函数里开启),系
统会开始一个倒计时,倒计时结束后,系统复位,如果倒计时没有结束发生喂狗,则又开始新的倒
计时,本节实验中,复位状态的LED是高电平,灯灭,延时500ms后,点亮led,在看门狗初始化
后,每经过2s,灯回到复位状态,灭,然后又点亮。反复亮灭。在循环中,按下按钮,就会引发喂
狗,这时又回到倒计时两秒,不会产生复位,那么灯就不会灭。 
2s的算法

这里IWDG_Init函数参数4是16进制的如图PR,相当于预分频选择64,看门狗LSI时钟是32K低俗时
钟,分频后32/64=0.5KHZ,1/0.5=2ms,经过参数1000个周期,变为2000ms就是2s。

窗口看门狗

其喂狗时间是一个有上下限的范围内(窗口),你可以通过设定相关寄存器,设定其上限时间(下限
固定)。喂狗的时间不能过早也不能过晚。 
而独立看门狗限制喂狗时间在0­x内,x由相关寄存器决定。喂狗的时间不能过晚。 
工作示意图:

设置的倒计时时间是W[6:0],在W[6:0]上面的值,不允许刷新,就是不允许喂狗,如果喂狗则会复
位,而在3Fh­W[6:0]之间可以喂狗,如果小于3Fh喂狗则又会复位。

配置寄存器WWDG_CFR中存放看门狗值域上限,而下限是3Fh,3Fh换成二进制0011 1111,也就
是说WWDG_CR中是一个7位的递减计数器,最高为为WDGA看门狗状态,如图,比如设置了上限
CFR,当CR的值>CFR时,比较器比较结果为1,经过与门与或门,产生复位,这是第一种复位情
况,第二种是当CR的递减减到3Fh时,T6变为0,T6出来的信号经过或门,产生复位。

窗口看门狗超时时间

窗口看门狗实验

1. 使能看门狗时钟: 
RCC_APB1PeriphClockCmd(); 
② 设置分频系数: 
WWDG_SetPrescaler(); 
③ 设置上窗口值: 
WWDG_SetWindowValue(); 
④ 开启提前唤醒中断并分组(可选): 
WWDG_EnableIT(); 
NVIC_Init(); 
⑤ 使能看门狗: 
WWDG_Enable(); 
⑥ 喂狗: 
WWDG_SetCounter(); 
⑦编写中断服务函数 
WWDG_IRQHandler();
看门狗初始化函数以及中断:

void WWDG_Init(u8 tr,u8 wr,u32 fprer)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE); //使能窗口看门狗时钟
WWDG_CNT=tr&WWDG_CNT; //初始化WWDG_CNT计数器值. CR寄存器最高位是看门狗状
态,只用到了低7位寄存器
WWDG_SetPrescaler(fprer); //设置分频值
WWDG_SetWindowValue(wr); //设置窗口值
// WWDG_SetCounter(WWDG_CNT);//设置计数值
WWDG_Enable(WWDG_CNT); //开启看门狗
NVIC_InitStructure.NVIC_IRQChannel=WWDG_IRQn; //窗口看门狗中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02; //抢占优先
级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;
//子优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //使能窗口看门狗
NVIC_Init(&NVIC_InitStructure);
WWDG_ClearFlag();//清除提前唤醒中断标志位
WWDG_EnableIT();//开启提前唤醒中断,开启后,到达3Fh后会进入中断
}
//窗口看门狗中断服务程序
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(WWDG_CNT); //重设窗口看门狗值
WWDG_ClearFlag();//清除提前唤醒中断标志位
LED1=!LED1;
}

先来看main函数,如果不加看门狗

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "wwdg.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
KEY_Init(); //初始化按键
LED0=0; //点亮LED0
delay_ms(1000);
//WWDG_Init(0x7F,0X5F,WWDG_Prescaler_8); //计数器值为7f,窗口寄存器为5
f,分频数为8
while(1)
{
LED0=1; //熄灭LED灯
}
}

没有加看门狗时,LED灯初始化时是灭的,然后亮一秒后熄灭。 
再来看加入看门狗后,有提前提醒中断,在中断函数中设置到达最低阈值后,重新喂狗,并且点亮
或熄灭led1作为标志,这样就不会复位,led0一直保持灭。

通用定时器

定时器分类:

通用定时器可以向上计数、向下计数、向上向下双向计数模式。 
①向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个
计数器溢出事件。 
②向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新
开始,并产生一个计数器向下溢出事件。 
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值­1,产生一个计数器溢出
事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。

定时器原理图

标号1部分产生一个定时器时钟CK_PSC,并且控制定时器的递增或者递减,标号2为定时器基本单
元。标号3为输入比较,标号4为输出比较.
在标号1部分,计数器时钟可以由下列时钟提供: 
内部时钟(CK_INT) 
外部时钟模式1:外部输入脚(TIx) 
外部时钟模式2:外部触发输入(ETR)(仅适用TIM2,3,4) 
内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器
Timer1而作为另一个定时器Timer2的预分频器
相关寄存器:
标号2中的当前值CNT

标号2中的预分频PSC设置寄存器

标号2中的自动重装寄存器ARR

 

控制寄存器CR1

中断始能寄存器DIER

定时器参数初始化函数:

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_Ti
meBaseInitStruct);

结构体构造

typedef struct
{
uint16_t TIM_Prescaler; //预分频系数
uint16_t TIM_CounterMode; //模式(向上,向下计数),操控CR1寄存器
uint16_t TIM_Period; //自动装载值
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;

实验:定时器中断

目的:通过定时器中断配置,每500ms中断一次,然后中断服务函数中控制LED实现LED1状态取反(闪烁)

溢出时间的算法: 
首先我们选择的是原理图中的内部时钟,内部时钟的选取方式:

CK_ININT的时钟为84M,这的的84M就是公式的Tclk,84经过预分频(PSC+1)分频系数后频率
=Tclk/(Psc+1)M,所以时钟周期等于频率的倒数(Psc+1)/Tclk,再乘以重装寄存器的数据,就
是溢出时间。所以如果想要溢出时间是500ms,设置PSC是8399,则频率
=84M/8400=10000Hz=10KHz,周期就为0.1ms,要为500ms,就应该乘以5000,所以ARR=4999.
实验步骤: 
1.实验从跑马灯实验为基础,在HARDWARE中新建timer.c与.h,将.c到工程的HARDWARE中,在.c
中写#include “timer.h”,重新编译,.h自动被编辑进.c,再再锤子标志那里添加头文件路径。再在
FWLIB中添加FWLIB/src中的tim库函数 
2.选用定时器3,在.h中写

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
void TIM3_Int_Init(u16 arr,u16 psc);
#endif

3.在.c中编写函数: 
始能定时器,并且初始化arr,psc与mode

TIM_TimeBaseInitTypeDef TIM_TimBaseInitStrue;
RCC_AHB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//始能定时器3时钟
TIM_TimBaseInitStrue.TIM_Period=arr;//设置重装载值
TIM_TimBaseInitStrue.TIM_Prescaler=psc;//预分频系数
TIM_TimBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up;//向上计数
TIM_TimBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1;//(随便选的)
TIM_TimeBaseInit(TIM1,&TIM_TimBaseInitStrue);

开启定时器3中断并设置

NVIC_InitTypeDef NVIC_InitStructure;
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//始能定时器的更新中断
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);

始能定时器

TIM_Cmd(TIM3,ENABLE);//始能定时器3

编写中断服务函数(函数名在启动文件复制):

void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)//检验如果TIM3状态发生更新中
断
{
LED1=!LED1;//反转LED
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除标志位
}

主函数: 
设置中断优先级分组,(misc中复制函数)

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
TIM3_Int_Init(5000-1,8400-1); //定时器时钟84M,分频系数8400,所以84M/840
0=10Khz的计数频率,计数5000次为500ms
while(1)
{
LED0=!LED0;//DS0翻转
delay_ms(200);//延时200ms
};
}

外部中断

IO口为正常IO口,Line1为外部中断线1,外部中断根据IO口电平状态触发,比如上升沿触发,当IO
口变为上升沿时,中断触发,中断触发前后,对IO口的状态没有影响。 
STM32F4xx系列的IO口中断分配:

每组IO口的引脚数字ID相同的分配到一个中断线,比如PA,PB,PC….PI的0口都分配到中断线EXTI0
上,对于每个中断线,我们可以设置相应的触发方式(上升沿触发,下降沿触发,边沿触发)以及
使能。 
中断线: 
EXTI线0~15:对应外部IO口的输入中断。 
EXTI线16:连接到PVD输出。 
EXTI线17:连接到RTC闹钟事件。 
EXTI线18:连接到USB OTG FS唤醒事件。 
EXTI线19:连接到以太网唤醒事件。 
EXTI线20:连接到USB OTG HS(在FS中配置)唤醒事件。 
EXTI线21:连接到RTC入侵和时间戳事件。 
EXTI线22:连接到RTC唤醒事件。 
意思就是很多外部中断通过IO口(EXIT线0-15)连接到STM32,比如我们要使用PA0的中断,就直接将中断
线0和GPIOA映射起来就OK了,我们再通过PA0配置连接在PA0连接的外设产生的中断处理.  
我们来看中断线0­15,IO口的中断,一共16个中断线对应IO口,那么设置这16个中断线的函数共
有7个中断服务函数: 
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
他们的分配如下:

从表中可以看出,外部中断线5~9分配一个中断向量,共用一个服务函数外部中断线10~15分配一
个中断向量,共用一个中断服务函数。

相关函数:
①void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex); 
//设置IO口与中断线的映射关系
②void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); 
//初始化中断线:触发方式等 
其中结构体成员

typedef struct
{
uint32_t EXTI_Line; //外部中断线选择
EXTIMode_TypeDef EXTI_Mode; //模式,可选值为中断EXTI_Mode_Interrupt
和事件 EXTI_Mode_Event
EXTITrigger_TypeDef EXTI_Trigger; //触发方式
FunctionalState EXTI_LineCmd; //始能
}EXTI_InitTypeDef;

③ITStatus EXTI_GetITStatus(uint32_t EXTI_Line); 
//判断中断线中断状态,是否发生
④void EXTI_ClearITPendingBit(uint32_t EXTI_Line); 
//清除中断线上的中断标志位
⑤RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟 
//这个函数非常重要,在使用外部中断的时候一定要先使能SYSCFG时钟

 

外部中断配置步骤

1.使能SYSCFG时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);

2.按照Key连接配置按键初始化(初始化GPIO)

void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE, ENABL
E);//使能GPIOA,GPIOE时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //KEY0
KEY1 KEY2对应引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//WK_UP对应引脚PA0
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;//下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA0
}

3.设置IO口与中断线的映射关系。(按照原理图KEY0对应的PE4加入外部中断)

SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4);

4.初始化外部中断线:(按照原理图,Key0默认时高电平,按下后变为低点评,设置为下降沿触

EXTI_InitTypeDef EXIT_InitStruct;
EXIT_InitStruct.EXTI_Line =EXTI_Line4;//初始化中断线4
EXIT_InitStruct.EXTI_LineCmd =ENABLE;
EXIT_InitStruct.EXTI_Mode =EXTI_Mode_Interrupt;//模式为中断
EXIT_InitStruct.EXTI_Trigger =EXTI_Trigger_Falling;//下降沿触发
EXTI_Init(&EXIT_InitStruct);

5.配置中断分组(NVIC),并使能中断。注意使用NVIC管理中断时需要在main函数中首先对中断
进行分组

NVIC_InitTypeDef NVIC_InitSture;
NVIC_InitSture.NVIC_IRQChannel=EXTI4_IRQn;//设置中断通道为外部中断线4
NVIC_InitSture.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitSture.NVIC_IRQChannelPreemptionPriority=1;//设置抢占优先级
NVIC_InitSture.NVIC_IRQChannelSubPriority=1;//子优先级
NVIC_Init(&NVIC_InitSture);

6.编写中断服务函数。线4中断服务函数

void EXTI4_IRQHandler(void)
{
delay_ms(10);//消除抖动
if(KEY0==0)//确实按下
{
LED0=!LED0;
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line4);
}

7.编写主函数

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //串口初始化
LED_Init(); //初始化LED端口
BEEP_Init(); //初始化蜂鸣器端口
EXTIX_Init(); //初始化外部中断输入
LED0=0; //先点亮红灯
while(1)
{
printf("OK\r\n"); //打印OK提示程序运行
delay_ms(1000); //每隔1s打印一次
}
}

ADC

量程(模拟量输入范围)

(1)AD转换器是一个电子器件,所以他只能输入电压信号。其他种类的模拟信号要先经过传感器
(Sensor)的转换变成模拟的电压信号然后才能给AD。 
(2)AD输入端的模拟电压要求有一个范围,一般是0~3.3V或0~5V或者是0~12V等等。模拟电压的
范围是AD芯片本身的一个参数。实际工作时给AD的电压信号不能超过这个电压范围。

精度(分辨率resolution)

(1)AD转换输出的数字值是有一定的位数的(譬如说10位,意思就是输出的数字值是用10个二进制
位来表示的,这种就叫10位AD)。这个位数就表示了转换精度。 
(2)10位AD就相当于把整个范围分成了1024个格子,每个格子之间的间隔就是电压的表示精度。加
入AD芯片的量程是0~3.3V,则每个格子代表的电压值是3.3V/1024=0.0032265V。如果此时AD转
换后得到的数字量是447,则这个数字量代表的模拟值是:447×0.0032265V=1.44V。 
(3)AD的位数越多,则每个格子表示的电压值越小,将来算出来的模拟电压值就越精确。 
(4)AD的模拟量程一样的情况下,AD精度位数越多精度越高,测出来的值越准。但是如果AD的量程
不一样。譬如2个AD,A的量程是0~50V,B的量程是0~0.5V,A是12位的,B是10位的,可能B的
精度比A的还要高。(A的精度:50/1024=0.04883,B的精度:0.5/4096=0.000122)

转换速率(MSPS与conventor clock的不同)

(1)首先要明白:AD芯片进行AD转换是要耗费时间的。这个时间需要多久,不同的芯片是不一样
的,同一颗芯片在配置不一样(譬如说精度配置为10位时时间比精度配置为12位时要小,譬如说有
些AD可以配转换时钟,时钟频率高则转换时间短)时转换时间也不一样。 
(2)详细的需要时间可以参考数据手册。一般数据手册中描述转换速率用的单位是MSPS(第一个M
是兆,S是sample,就是采样;PS就是per second,总的意思就是兆样本每秒,每秒种转出来多少
M个数字值) 
(3)AD工作都需要一个时钟,这个时钟有一个范围,我们实际给他配置时不要超出这个范围就可以
了。AD转换是在这个时钟下进行的,时钟的频率控制着AD转换的速率。注意:时钟频率和MSPS
不是一回事,只是成正比不是完全相等。譬如S5PV210中的AD转换器,MSPS = 时钟频率/5

通道数

AD芯片有多少路analog input通道,代表了将来可以同时进行多少路模拟信号的输入。 
STM32F4xx 系列一般都有 3 个 ADC,这些 ADC 可以独立使用,也可以使用双重/三重模式(提高
采样率)。STM32F4 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有 19 个通道,可测量 16
个外部源、2 个内部源和 Vbat 通道的信号。这些通道的 A/D 转换可以单次、连续、扫描或间断模
式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。 
1.规则通道组:相当正常运行的程序。最多16个通道。 
规则通道和它的转换顺序在ADC_SQRx寄存器中选择,规则 
组转换的总数应写入ADC_SQR1寄存器的L[3:0]中
2. 注入通道组:相当于中断。最多4个通道。 
注入组和它的转换顺序在ADC_JSQR寄存器中选择。注入组 
里转化的总数应写入ADC_JSQR寄存器的L[1:0]中

转换模式

ADC单次转换:单次转换下,ADC执行一次转换,如果在规则通道中进行了单次转换,则转换的数
据存储在16位ADC_DR寄存器中,其中EOC转换结束标志置1,若EOCIE位置为1则产生中断。如
果在注入通道中进行单次转换,则转换的数据存储在ADC_JDR1中,JEOC转换结束标志置1,
JEOCIE置1时产生中断。然后ADC停止。 
ADC连续转换:

扫描模式:在一个通道扫描完成后自动开始下一个通道的转换.在扫描模式时,规则组序列就有n个通道

ADC采样时间

这里0.5微秒的算法:ADCCLK的时钟为30MHz,分成15个周期,则每个周期2MHz的时钟,相当于
0.5微秒采一次。 
最小采样时间0.42us(ADC时钟=36MHz,采样周期为3周期下得到),ADC时钟不能超过36MHz 
影响ADC采样时间的参数主要有两个: 
1.ADC_CommonInit中的ADC_Prescaler时钟设置,分频越大,ADC时钟频率越小,ADC采样周期
越长,精度越小。 
2.ADC_RegularChannelConfig中的采样周期,采样周期越小,越精确

参数相关配置

void ADC_CommonInit(ADC_CommonInitTypeDef* ADC_CommonInitStruct)
typedef struct
{
uint32_t ADC_Mode;//若有多重ADC,则可以选择多重ADC的工作模式
uint32_t ADC_Prescaler;//ADC分频系数,ADC总线时84M,要求ADC不超过36M
uint32_t ADC_DMAAccessMode; //多重ADC下的DMA工作模式
uint32_t ADC_TwoSamplingDelay; //转换间隔
}

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
typedef struct
{
uint32_t ADC_Resolution;//ADC分辨率
FunctionalState ADC_ScanConvMode; //是否使用扫描模式。多通道时启用
FunctionalState ADC_ContinuousConvMode; //单次转换OR连续转换,连续转换就是在
完成一次AD转换后马上开启下一次AD转换,软件开启一次转换后就会一直转换,不用再循环里一直
触发ADC转换
uint32_t ADC_ExternalTrigConvEdge; //外部触发使能方式
uint32_t ADC_ExternalTrigConv; //触发方式
uint32_t ADC_DataAlign; //对齐方式:左对齐还是右对齐
uint8_t ADC_NbrOfChannel;//规则通道序列长度,采样通道,如果只采一个脉冲就只用一
个通道,那么采样通道只有1个
}ADC_InitTypeDef;


ADC_DeInit; //函数的功能是将外设ADCx的全部寄存器重设为默认值。
3.ADC_Cmd //函数的功能是使能或失能指定的ADC,其中ADC_Cmd只能在其他ADC设置函数之后被
调用
ADC_Cmd(ADC1,ENABLE);
4.ADC_DMACmd //函数的功能是使能或者失能指定的ADC的DMA请求。
ADC_DMACmd(ADC1,ENABLE);
5.ADC_ITConfig //函数的功能是使能或者失能指定的ADC的中断,其中可以是EOC/AWD/JEOC
ADC_ITConfig(ADC2,ADC_IT_EOC|ADC_IT_AWD);
6.ADC_ResetCalibration //函数的功能是重置指定的ADC的校准寄存器。
ADC_ReserCalibration(ADC1);
7.ADC_GetResetCalibrationSttaus //函数的功能是获取ADC重置校准寄存器的状态。
FlagStatus Status
Status = ADC_GetResetCalibrationSttaus(ADC2);
8.ADC_StartCalibration //函数的功能是开始指定ADC的校准。
ADC_StartCalibration(ADC2);
9.ADC_GetCalibrationStatus //函数的功能是获取ADC的校准状态。具有返回值
FlagStatus Status;
Status = ADC_GetCalibrationStatus(ADC2);
10.ADC_SoftwareStartConvCmd //函数的功能是使能或者失能指定的ADC的软件启动功能。
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
11.ADC_DiscModeChannelCountConfig //函数的功能是对ADC规则通道配置间断模式。其中
参数可以是1~8,间断模式计数器的值。
ADC_DiscModeChannelCountConfig(ADC1,2);
12.ADC_DiscModeCmd //函数的功能是使能或者失能指定的ADC规则组通道的间断模式。
ADC_DiscModeCmd(ADC1,ENABLE);
13.ADC_RegularChannelConfig //函数的功能是设置ADC的规则组通道,设置他们的转化顺序
和采样时间,其中ADC_Channel指定了通过本函数来设置的ADC通道,可以是0~17,ADC_Sample
Time设置了选中通道的ADC采样时间。
ADC_RegularChannelConfig(ADC2,ADC_Channel_2,1,ADC_SampleTime_1Cycles5);
14.ADC_ExternalTrigConvConfig //函数的功能是使能或者失能ADCx的经外部触发启动转换
功能。
ADC_ExternalTrigConvConfig(ADC2,ENABLE);
15.ADC_GetConversionValue //函数的功能是返回最近一次ADCx规则组的转换结果。
u16 DataValue;
DataValue = ADC_GetConversionValue(ADC2);
16.ADC_GetDuelModeConversionValue //函数的功能是返回最近一次双ADC模式下的转换结
果。
u32 DataValue;
DataValue = ADC_GetDuelModeConversionValue();
17.ADC_AutoInjectedConvCmd //函数的功能是使能或者失能指定ADC在规则组转化后自动开始
注入组转换。
ADC_AutoInjectedConvCmd(ADC2,ENABLE);
18.ADC_InjectedDiscModeCmd //函数的功能是使能或者失能指定的ADC注入组间断模式。
ADC_InjectedDiscModeCmd(ADC2,ENABLE);
19.ADC_ExternalITrigInjectedConvConfig //函数的功能是配置ADCx外部触发启动注入组
转换功能。其中ADC_ExternalITrigConv_IT可以取多种启动触发模式
例:用定时器1的捕获比较4触发ADC1注入组转换功能。
ADC_ExternalITrigInjectedConvConfig(ADC1,ADC_ExternalITrigConv_IT_CC4);
20.ADC_ExternalITrigInjectedConvCmd //函数的功能是使能或者失能ADCx的经外部触发启
动注入组转换功能。
ADC_ExternalITrigInjectedConvCmd(ADC2,ENABLE);
21.ADC_SoftwareStartInjectedConvCmd //函数的功能是使能或者失能ADCx软件启动注入组
转换功能。
ADC_SoftwareStartInjectedConvCmd(ADC2,ENABLE);
22.ADC_GetSoftwareStartInjectedConvStatus //函数的功能是获取指定ADC的软件启动注
入组转换状态,会返回一个ADC软件触发启动注入转换的新状态。
FlagStatus Status;
Status = ADC_GetSoftwareStartInjectedConvStatus(ADC2);
23.ADC_InjectedChannelConfig //函数的功能是设置指定ADC的注入组通道,设置他们的转
化顺序和采样时间。不过先决条件是之前必须调用函数ADC_InjectedSequencerLengthConfig
来确定注入转换通道的数目,特别是在通道数目小于4的情况下,先正确配置每一个通道的转化顺
序。
例:配置ADC1第12通道采样周期28.5,第二个开始转换。
ADC_InjectedChannelConfig(ADC1,ADC_Channel_12,2,ADC_SampleTime_28Cycles
5);
24.ADC_InjectedSequenceLengthConfig //函数的功能是设置注入组通道的转换序列长度。
且序列长度的取值范围是~4。
ADC_InjectedSequenceLengthConfig(ADC1,1);
25.ADC_SetInjectefOffset //函数的功能是设置注入组通道的转换偏移值。选择注入通道可以
是1~4.偏移量是16位值。
ADC_SetInjectefOffset(ADC_InjectedChannel_1,0x100);
26.ADC_GetInjectedConversionValue //函数的功能是返ADC指定注入通道的转换结果。
u16 InjectedConversionValue;
InjectedConversionValue = ADC_GetInjectedConversionValue(ADC1,ADC_Injecte
dChannel_1);
27.ADC_TampSensorVrefintCmd //函数的功能是使能或者失能温度传感器和内部参考电压通
道。
ADC_TempSensorVrefintCmd(ENABLE);
28.ADC_GetFlagStatus //函数的功能是检查指定的ADC标志位是否置1.会返回一个新的ADC_
FLAG值。其中指定标志位可以取5种值。
FlagStatus Status;
FlagStatus = ADC_GetFlagStatus(ADC1,ADC_FLAG_AWD);
29.ADC_ClearFlag //函数的功能是清除ADCx待处理的标志位。在使用本函数之前是调用过了AD
C_GetFlagStatus函数的。
ADC_ClearFlag(ADC2,ADC_FLAG_AWD).
30.ADC_DMARequestAfterLastTransferCmd //源变化时开启DMA传输
ADC_DMARequestAfterLastTransferCmd(ADC1,ENABLE);
31.ADC_DMACmd //始能ADC的DMA传输
ADC_DMACmd(ADC1,ENABLE);
}

DMA

STM32F4最多有2个DMA控制器,2个DMA控制器总共有16个数据流(每个控制器8个)。每个
DMA控制器都用于管理一个或者多个外设的存储器访问请求。每个数据流总共可以有多达8个通道
(或请求),每个通道都有一个仲裁器,用于处理DMA请求间的优先级。

请求映射

流配置过程

DMA中断

其中在写DMA的中断函数时,中断函数可以写在主函数里,中断标志位例如
DMA_GetITStatus(DMA2_Stream2,DMA_IT_TCIF2)  
表示的是DMA2数据流2某个通道传输完成,DMA_IT_TCIF2后面的数字是数据流的号,不是通道的号

库函数

typedef struct
{
uint32_t DMA_Channel; //DMA通道
uint32_t DMA_PeripheralBaseAddr; //外设的基地址
uint32_t DMA_Memory0BaseAddr; //存储器的基地址
uint32_t DMA_DIR; // 数据传输方向
uint32_t DMA_BufferSize; //传输的数据数量,不管传输的是32位还是16位。比如从
外设传到数组,这里的buffersize就是数组的长度。
uint32_t DMA_PeripheralInc; //传输数据时外设地址是否递增,外设一般都不增
uint32_t DMA_MemoryInc; //传输数据时存储器地址是否递增,如果是数组,那
么地址要递增
uint32_t DMA_PeripheralDataSize; //外设传输的数据宽度
uint32_t DMA_MemoryDataSize; //存储器传输的数据宽度
uint32_t DMA_Mode; //是否开启循环模式,循环模式就是是否开启DMA 自动
转换,比如ADC一直采集数据,然后开启循环模式,则ADC采到了数据就会通过DMA发送
uint32_t DMA_Priority; //设置优先级
uint32_t DMA_FIFOMode; //FIFO的配置
uint32_t DMA_FIFOThreshold;
uint32_t DMA_MemoryBurst; //存储器突发设置
uint32_t DMA_PeripheralBurst; //外设的突发设置
}DMA_InitTypeDef;

DMA初始化(例子):

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 ma
r,u16 ndtr)//(数据流,通道,外设基地址,存储器基地址,传输量)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存储器地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非
增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数
据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单
次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外
设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输
}

多重ADC

三重ADC转换原理

AD 转换包括采样阶段和转换阶段,在采样阶段才对通道数据进行采集;而在转换阶段只是将采集
到的数据进行转换为数字量输出,此刻通道数据变化不会改变转换结果。独立模式的 ADC采集需要
在一个通道采集并且转换完成后才会进行下一个通道的采集。双重或者三重 ADC的机制使用两个或
以上 ADC同时采样两个或以上不同通道的数据或者使用两个或以上 ADC交叉采集同一通道的数
据。双重或者三重 ADC模式较独立模式一个最大的优势就是转换速度快。 
介绍三重 ADC交替模式,三重 ADC交替模式是针对同一通道的使用三个 ADC交叉采集,就是在
ADC1 采样完等几个时钟周期后 ADC2 开始采样,此时 ADC1处在转换阶段,当 ADC2 采样完成再
等几个时钟周期后 ADC3就进行采样此时 ADC1 和 ADC2 处在转换阶段,如果 ADC3 采样完成并且
ADC1已经转换完成那么就可以准备下一轮的循环,这样充分利用转换阶段时间达到增快采样速度
的效果。

DMA模式请求

DMA 模式 2:每发送一个 DMA 请求(两个数据项可用),就会以字的形式传输表 
示两个 ADC 转换数据项的两个半字。在双重 ADC 模式下,发出第一个请求时会传输 ADC2 和
ADC1 的数据(ADC2 数据占用高位半字,ADC1 数据占用低位半字),依此类推。 
在三重 ADC 模式下,将生成三个 DMA 请求:发出第一个请求时,会传输 ADC2 和ADC1 的数据
(ADC2 数据占用高位半字,ADC1 数据占用低位半字)。发出第二个请求时,会传输 ADC1 和
ADC3 的数据(ADC1 数据占用高位半字,ADC3 数据占用地位半字)。发出第三个请求时,会传
输 ADC3 和 ADC2 的数据(ADC3占用高位半字,ADC2 数据占用地位半字),依此类推。 
DMA 模式 2 用于交替模式和常规同步模式(仅适用于双重 ADC 模式)。

三重ADC采样单通道信号ADC与DMA配置

ADC

#include "adc.h"
#include "sys.h"
void MYADC_configure()//从adc中
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3|RCC_APB2Periph_ADC1|RCC_AP
B2Periph_ADC2, ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOC,&GPIO_InitStructure);
//复位ADC
ADC_DeInit();
//配置通用寄存器ADC_CCR
ADC_CommonInitStructure.ADC_Mode=ADC_TripleMode_Interl;//三重交替模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cy
cles;
ADC_CommonInitStructure.ADC_DMAAccessMode=ADC_DMAAccessMode_2;//DMA模
式2
ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4;//84/4=21M
ADC_CommonInit(&ADC_CommonInitStructure);
//配置ADC
ADC_InitStructure.ADC_Resolution=ADC_Resolution_12b;//分辨率12位
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//关闭扫描模式
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//开启连续转换 CR2
ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_N
one;//不采用边沿触发
//ADC_InitStructure.ADC_ExternalTrigConv=ADC_SOFTWARE_START;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右齐
ADC_InitStructure.ADC_NbrOfConversion=1;//采样通道1个
ADC_Init(ADC1,&ADC_InitStructure);
ADC_InitStructure.ADC_Resolution=ADC_Resolution_12b;//分辨率12位
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//关闭扫描模式
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//开启连续转换 CR2
ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_N
one;//不采用边沿触发
//ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T8_TRG
O;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右齐
ADC_InitStructure.ADC_NbrOfConversion=1;//采样通道1个
ADC_Init(ADC2,&ADC_InitStructure);
ADC_InitStructure.ADC_Resolution=ADC_Resolution_12b;//分辨率12位
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//关闭扫描模式
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//开启连续转换 CR2
ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_N
one;//不采用边沿触发
//ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T8_TRG
O;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//右齐
ADC_InitStructure.ADC_NbrOfConversion=1;//采样通道1个
ADC_Init(ADC3,&ADC_InitStructure);
//始能ADC
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_3Cyc
les ); //配置通道优先级 SMPR2,SQR3
ADC_RegularChannelConfig(ADC2, ADC_Channel_10, 1, ADC_SampleTime_3Cyc
les ); //配置通道优先级 SMPR2,SQR3
ADC_RegularChannelConfig(ADC3, ADC_Channel_10, 1, ADC_SampleTime_3Cyc
les ); //配置通道优先级 SMPR2,SQR3
ADC_DMACmd(ADC1,ENABLE);
ADC_MultiModeDMARequestAfterLastTransferCmd(ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_Cmd(ADC2, ENABLE);
ADC_Cmd(ADC3, ENABLE);
ADC_SoftwareStartConv(ADC1);
}

DMA

#include "dma.h"
#include "sys.h"
void MYDMA_DMA_DIR_PeripheralToMemoryConfig(DMA_Stream_TypeDef *DMA_Strea
mx,u32 chanlx,u32 par,u32 memory,u16 ndtr)//(数据流,通道,外设基地址,存储器
基地址,传输量,不管是多少位的,传输量只是数据的个数,这里设置为数组的长度)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chanlx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址,S0PAR
DMA_InitStructure.DMA_Memory0BaseAddr = memory;//DMA 存储器地址 S0M0AR
DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralToMemory ;//外设到存储器
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量 ,NDTR
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非
增量模式,S0CR
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式,S0
CR
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 循环模式,S0CR
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级,S0CR
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_HalfFull;//
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单
次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外
设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream
DMA_Cmd(DMA_Streamx, ENABLE);
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值