STM32(M3)

6/30/2022 stm32,嵌入式

# 初级

# 软件环境搭建

# MDK安装

  1. 请点击:mdk_xxx.exe,安装MDK软件【忽略版本号,x以实际文件内容为准】
  2. 请点击:Keil.STM32FXxx_DFP.x.x.x.pack,安装芯片支持包【忽略版本号,x以实际文件内容为准】

**说明:**MDK5 以后对于每种芯片进行数据包支持的方式,需要单独安装www.keil.com/dd2/pack/

**说明:**MDK软件试用版本可以公开下载,且无需付费,所以我们下载了试用版本存放在光盘供大家试用,MDK软件版权属于ARM公司,其他试用版本均可在Keil官网下载:http://www.keil.com/download/product

上面提供的MDK软件是试用版本,只能编译32K程序,超过32K程序需要注册。未注册软件编译程序会提示错误信息。由于MDK版权属于ARM公司,非学习研究用途,请咨询ARM官方获取相关许可证

# 破解

https://www.jb51.net/softs/781873.html

# USB串口驱动安装

安装后如果出现预处理安装成功,可以链接板子,让系统自己识别安装

# JLINK安装

# 硬件链接

# 环境准备 CH340驱动

# FlyMcu下载软件(免安装板)

# FlyMcu 下载 - 配置信息

# STLINK程序下载

# 环境配置

  1. ST驱动安装
  2. MDK软件安装

# MDK配置

  1. 点击

  2. 配置信息

    点击settings

# 新建STM32工程模板 - 固件

# 新建STM32工程模板 - 寄存器

# GPIO工作原理

# GPIO的输入工作模式1—输入浮空模式

# GPIO的输入工作模式2—输入上拉模式

# GPIO的输入工作模式3—输入下拉模式

# GPIO的输入工作模式4—模拟模式

# GPIO的输出工作模式1—开漏输出模式

# GPIO的输出工作模式2—开漏复用输出模式

# GPIO的输出工作模式3—推挽输出模式

# GPIO的输出工作模式4—推挽复用输出模式

# 跑马灯

# GPIO函数库介绍

  1. 初始化函数

    void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
    
    1
  2. 读取输入电平函数

    uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
    
    1
    2
  3. 读取输出电平函数

    uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
    
    1
    2
  4. 设置输出电平函数

    void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
    void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
    void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
    
    1
    2
    3
    4

在初始化GPIO口之前需要使能IO口时钟

RCC_APB2PeriphColckCmd()

  1. 编写LED初始化程序led.c

    #include "led.h"
    #include "stm32f10x.h"
    
    void LED_Init(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	
    	
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); // GPIOA
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE); // GPIOD
    	
    	// GPIOA初始化 
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	// 设置为高电平 - 不点亮
    	GPIO_SetBits(GPIOA,GPIO_Pin_8);
    	
    	// GPIOD初始化 
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOD,&GPIO_InitStructure);
    	// 设置为高电平 - 不点亮
    	GPIO_SetBits(GPIOD,GPIO_Pin_2);
     }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
  2. 主函数调动

    #include "stm32f10x.h"
    #include "delay.h"
    #include "led.h"
    
    
    
    int main(void)
    {
    	delay_init();
    	LED_Init();
    	
    	while(1) {
    		GPIO_SetBits(GPIOA,GPIO_Pin_8);
    		GPIO_ResetBits(GPIOD,GPIO_Pin_2);
    		delay_ms(500);
    		
    		GPIO_ResetBits(GPIOA,GPIO_Pin_8);
    		GPIO_SetBits(GPIOD,GPIO_Pin_2);
    		delay_ms(500);
    		
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

# PGIO作输入

按键扫描(支持连续按和非连续按操作)

 u8 KEY_Scan(u8 mode)
    {
     static u8 key_up=1;
     if(mode==1) key_up=1;//支持连续按
      if(key_up &&  KEY按下)
      {
        delay_ms(10);//延时,防抖
        key_up=0;//标记这次key已经按下
        if(KEY确实按下)
          {
           return KEY_VALUE;
          }
        }else if(KEY没有按下)  key_up=1;
       return 没有按下
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

key.c

#include "stm32f10x.h"
#include "key.h"
#include "delay.h"

// KEY初始化
void KEY_Init(void) {
	
	GPIO_InitTypeDef GPIO_InitTypeDefStruct;
	
	// GPIOA使能时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	// GPIOC使能时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	// 初始化 GPIOC_5  KEY0
	GPIO_InitTypeDefStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitTypeDefStruct.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitTypeDefStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitTypeDefStruct);
	
	// 初始化 GPIOA_15  KEY1
	GPIO_InitTypeDefStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitTypeDefStruct.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitTypeDefStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitTypeDefStruct);
	
	// 初始化 GPIOA_0  WK_UP
	GPIO_InitTypeDefStruct.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_InitTypeDefStruct.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitTypeDefStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitTypeDefStruct);
}

// 获取按下的按键
// 0 代表KEY0按下, 1 代表KEY1 按下, 3 代表WK_UP按下 - 1代表未按下
// mode 0 不支持连续按 1 支持连续按
char KEY_Scan(char mode){
	
	static char key_up = 1; // 按键松开标识位
	
	if(mode) { // 支持长按
		key_up = 1;
	}
	
	// 有按键按下
	if(	key_up && (!GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)  || 
			!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15) || 
			GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0))){
				
				delay_ms(10); // 消抖
				key_up = 0; // 按下
				if(!GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)) return 0;
				if(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)) return 1;
				if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)) return 2;
	}else if(	key_up && 
						GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5) && 
						GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15) && 
						!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)){
						key_up = 1;
	}			
		
		return -1; // 无按键按下
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

主函数调用

#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"

char KEY_Press = -1; // 0 代表KEY0按下, 1 代表KEY1 按下, 3 代表WK_UP按下 - 1代表未按下

int main(void)
{
	delay_init();
	LED_Init();
	KEY_Init();
	while(1) {
		KEY_Press = KEY_Scan(1); // 只有支持长按才能点亮LED
		switch(KEY_Press) {
			case 0:
					GPIO_ResetBits(GPIOA,GPIO_Pin_8);
					GPIO_SetBits(GPIOD,GPIO_Pin_2);
				break;
			case 1:
					GPIO_SetBits(GPIOA,GPIO_Pin_8);
					GPIO_ResetBits(GPIOD,GPIO_Pin_2);
				break;
			case 2:
					GPIO_ResetBits(GPIOA,GPIO_Pin_8);
					GPIO_ResetBits(GPIOD,GPIO_Pin_2);
				break;
			default:
					GPIO_SetBits(GPIOA,GPIO_Pin_8);
					GPIO_SetBits(GPIOD,GPIO_Pin_2);
			break;
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 时钟系统

  1. STM32 有5个时钟源:HSI、HSE、LSI、LSE、PLL

    1. HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高

    2. HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz

    3. LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。WDG

    4. LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC

    5. PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

  2. 系统时钟SYSCLK可来源于三个时钟源

    1. HSI振荡器时钟
    2. HSE振荡器时钟
    3. PLL时钟
  3. STM32可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL 输出的2分频、HSI、HSE、或者系统时钟

  4. 任何一个外设在使用之前,必须首先使能其相应的时钟

几个重要的时钟

  1. SYSCLK(系统时钟)
  2. AHB总线时钟
  3. APB1总线时钟(低速): 速度最高36MHz
  4. APB2总线时钟(高速): 速度最高72MHz
  5. PLL时钟

http://www.openedv.com/posts/list/302.htm

# 相关配置寄存器

typedef struct
{
  __IO uint32_t CR;                //HSI,HSE,CSS,PLL等的使能和就绪标志位 
  __IO uint32_t CFGR;           //PLL等的时钟源选择,分频系数设定
  __IO uint32_t CIR;               // 清除/使能 时钟就绪中断
  __IO uint32_t APB2RSTR;  //APB2线上外设复位寄存器
  __IO uint32_t APB1RSTR;   //APB1线上外设复位寄存器
  __IO uint32_t AHBENR;    //DMA,SDIO等时钟使能
  __IO uint32_t APB2ENR;   //APB2线上外设时钟使能
  __IO uint32_t APB1ENR;   //APB1线上外设时钟使能
  __IO uint32_t BDCR;        //备份域控制寄存器
  __IO uint32_t CSR;           //控制状态寄存器
} RCC_TypeDef;
1
2
3
4
5
6
7
8
9
10
11
12
13

# RCC相关头文件和固件库源文件

# 头文件

stm32f10x_rcc.h

# 源文件

stm32f10x_rcc.c

# 在线软件调试

# 软件仿真

  1. 检查芯片型号和晶振频率

  2. 再点击 Debug 选项卡

    1. 选择:Use Simulator,即使用软件仿真
    2. 选择:Run to main(),即跳过汇编代码,直接跳转到 main 函数开始仿真
    3. 设置下方的:Dialog DLL 分别为:DARMSTM.DLL和 和 TARMSTM.DLL ,Parameter 均为:-pSTM32F103RC ,用于设置支持 STM32F103RC 的软 过 硬件仿真(即可以通过 Peripherals ) 选择对应外设的对话框观察仿真结果)
    4. 接下来点击 (开始/停止仿真按钮)
  3. 调试工具条

  1. 在 Debug 栏选择仿真工具为 ST-Link Debugger

    上图中我们还勾选了 Run to main(),该选项选中后,只要点击仿真就会直接运行到 main 函数,如果没选择这个选项,则会先执行 startup_stm32f10x_hd.s 文件的 Reset_Handler,再跳到main 函数。

  2. 然后我们点击 Settings,设置 ST-LINK 的一些参数

    我们使用 ST-LINK 的 SW 模式调试,因为我们 JTAG 需要占用比 SW 模式多很多的 IO 口,而在开发板上这些 IO 口可能被其他外设用到,可能造成部分外设无法使用。所以,我们建议大家择 在调试的时候,一定要选择 SW 模式。可能造成部分外设无法使用。所以,我们建议大家择 在调试的时候,一定要选择 SW 模式。Max Clock 我们设置为最大:4Mhz(需要更新固件,否则最大只能到 1.8Mhz),这里,如果你的 USB 数据线比较差,那么可能会出问题,此时,你可以通过降低这里的速率来试试。

  3. 在 Utilities 选项卡里面设置下载时的目标编程器

  4. 选择 ST-LINK 来给目标器件的 FLASH 编程,然后点击 Settings,进入 FLASH 算法设置

# 端口复用

STM32有很多的内置外设,这些外设的外部引脚都是与GPIO复用的。也就是说,一个GPIO如果可以复用为内置外设的功能引脚,那么当这个GPIO作为内置外设使用的时候,就叫做复用。

例如串口1 的发送接收引脚是PA9,PA10,当我们把PA9,PA10不用作GPIO,而用做复用功能串口1的发送接收引脚的时候,叫端口复用。

端口复用配置过程

以PA9,PA10配置为串口1为例

  1. GPIO端口时钟使能

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)

  2. 复用外设时钟使能

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)

  3. 端口模式配置。 GPIO_Init() 函数

PA9,PA10复用为串口1配置过程

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//①IO时钟使能

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//②外设时钟使能

//③初始化IO为对应的模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9//复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
GPIO_Init(GPIOA, &GPIO_InitStructure);
  
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 PA.10 浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);  
1
2
3
4
5
6
7
8
9
10
11
12
13

# 端口重映射

每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在STM32中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。

为了使不同器件封装的外设IO功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。STM32中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。

AFIO重映射的操作步骤

  1. 使能被重映射到的I/O端口时钟
  2. 使能被重新映射的外设时钟
  3. 使能AFIO功能的时钟 (勿忘!)
  4. 进行重映射

8

引脚重映射配置过程(串口1为例)

  1. 使能GPIO时钟(重映射后的IO)

  2. 使能功能外设时钟(例如串口1)

  3. 使能AFIO时钟。重映射必须使能AFIO时钟

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE)

  4. 开启重映射

    GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE)

哪些情况需要开启AFIO辅助功能时钟

对寄存器AFIO_MAPR,AFIO_EXTICRX和AFIO_EVCR进行读写操作前,应当首先打开AFIO时钟。

  1. AFIO_MAPR:配置复用功能重映射
  2. AFIO_EXTICRX:配置外部中断线映射
  3. AFIO_EVCR: 配置EVENTOUT事件输出

# NVIC中断优先级管理

# 中断优先级分组

  1. CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。
  2. STM32并没有使用CM3内核的全部东西,而是只用了它的一部分。
  3. STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
  4. STM32F103系列上面,又只有60个可屏蔽中断(在107系列才有68个)。

# 中断管理方法

首先,对STM32中断进行分组,组0~4。同时,对每个中断设置一个抢占优先级和一个响应优先级值。

分组配置是在寄存器SCB->AIRCR中配置

# 抢占优先级 & 响应优先级区别

  1. 高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
  2. 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
  3. 抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
  4. 如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行。

举例

假定设置中断优先级组为2,然后设置中断3(RTC中断)的抢占优先级为2,响应优先级为1。 中断6(外部中断0)的抢占优先级为3,响应优先级为0。中断7(外部中断1)的抢占优先级为2,响应优先级为0。

那么这3个中断的优先级顺序为:中断7>中断3>中断6

# 中断优先级分组函数

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);


void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
1
2
3
4
5
6
7
8
9
10
11

# 中断设置相关寄存器

__IO uint8_t  IP[240]; //中断优先级控制的寄存器组

__IO uint32_t ISER[8]; //中断使能寄存器组
__IO uint32_t ICER[8]; //中断失能寄存器组
__IO uint32_t ISPR[8]; //中断挂起寄存器组
__IO uint32_t ICPR[8]; //中断解挂寄存器组
__IO uint32_t IABR[8]; //中断激活标志位寄存器组
1
2
3
4
5
6
7

# 对于每个中断怎么设置优先级?

中断优先级控制的寄存器组:IP[240] 全称是:Interrupt Priority Registers

240个8位寄存器,每个中断使用一个寄存器来确定优先级。

STM32F10x系列一共60个可屏蔽中断,使用IP[59]~IP[0]。

每个IP寄存器的高4位用来设置抢占和响应优先级(根据分组),低4位没有用到。

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

# 中断使能寄存器组:ISER[8]

作用:用来使能中断

32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ISER[0]和ISER[1]。

ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~27对应中断32~59;

# 中断失能寄存器组:ICER[8]

作用:用来失能中断

32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ICER[0]和ICER[1]。

ICER[0]的bit0~bit31分别对应中断0~31。ICER[1]的bit0~27对应中断32~59;

配置方法跟ISER一样。

# 中断挂起ISPR[8]、解挂控制寄存器组ICPR[8]

static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn)static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
1
2
3

# 中断激活标志位寄存器组:IABR [8]

作用:只读,通过它可以知道当前在执行的中断是哪一个,如果对应位为1,说明该中断正在执行。

static __INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)

# 中断参数初始化函数

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

typedef struct
{
  uint8_t NVIC_IRQChannel; //设置中断通道
  uint8_t NVIC_IRQChannelPreemptionPriority;//设置响应优先级
  uint8_t NVIC_IRQChannelSubPriority; //设置抢占优先级
  FunctionalState NVIC_IRQChannelCmd; //使能/使能
} NVIC_InitTypeDef;
1
2
3
4
5
6
7
NVIC_InitTypeDef   NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure);	//根据上面指定的参数初始化NVIC寄存器
1
2
3
4
5
6

# 中断优先级设置步骤

  1. 系统运行后先设置中断优先级分组。调用函数:void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);整个系统执行过程中,只设置一次中断分组。
  2. 针对每个中断,设置对应的抢占优先级和响应优先级:void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
  3. 如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可。

# 串口通信原理UART

# 处理器与外部设备通信的两种方式

  1. 并行通信
    1. 传输原理:数据各个位同时传输
    2. 优点:速度快
    3. 缺点:占用引脚资源多
  2. 串行通信
    1. 传输原理:数据按位顺序传输。
    2. 优点:占用引脚资源少
    3. 缺点:速度相对较慢

# 串行通信三种传送方式

# 串行通信的通信方式

  1. 同步通信:带时钟同步信号传输。SPI,IIC通信接口
  2. 异步通信:不带时钟同步信号。UART(通用异步收发器),单总线

# 常见的串行通信接口

# STM32的串口通信接口

  • UART:通用异步收发器
  • USART:通用同步异步收发器

大容量STM32F10x系列芯片,包含3个USART和2个UART

# UART异步通信方式特点

  • 全双工异步通信
  • 分数波特率发生器系统,提供精确的波特率
    • 发送和接受共用的可编程波特率,最高可达4.5Mbits/s
  • 可编程的数据字长度(8位或者9位)
  • 可配置的停止位(支持1或者2位停止位)
  • 可配置的使用DMA多缓冲器通信
  • 单独的发送器和接收器使能位
  • 检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志
  • 多个带标志的中断源。触发中断
  • 其他:校验控制,四个错误检测标志

# 串口通信过程

# STM32串口异步通信需要定义的参数

  1. 起始位
  2. 数据位(8位或者9位)
  3. 奇偶校验位(第9位)
  4. 停止位(1,15,2位)
  5. 波特率设置

# 常用的串口相关寄存器

  • USART_SR状态寄存器
  • USART_DR数据寄存器
  • USART_BRR波特率寄存器

# 串口操作相关库函数(省略入口参数)

void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能
void USART_Cmd();//使能串口
void USART_ITConfig();//使能相关中断

void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据

FlagStatus USART_GetFlagStatus();//获取状态标志位
void USART_ClearFlag();//清除状态标志位
ITStatus USART_GetITStatus();//获取中断状态标志位
void USART_ClearITPendingBit();//清除中断状态标志位
1
2
3
4
5
6
7
8
9
10
11

# USART_SR状态寄存器

# USART_DR数据寄存器

# USART_BRR波特率寄存器

# 波特率计算方法

# 串口配置的一般步骤

  1. 串口时钟使能,GPIO时钟使能:RCC_APB2PeriphClockCmd()
  2. 串口复位:USART_DeInit(); 这一步不是必须的
  3. GPIO端口模式设置:GPIO_Init(); 模式设置为GPIO_Mode_AF_PP
  4. 串口参数初始化:USART_Init();
  5. 开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)
    1. NVIC_Init()
    2. USART_ITConfig()
  6. 使能串口:USART_Cmd()
  7. 编写中断处理函数:USARTx_IRQHandler()
  8. 串口数据收发
    1. void USART_SendData();//发送数据到串口,DR
    2. uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
  9. 串口传输状态获取
    1. FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
    2. void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)

# 实验代码

#include "stm32f10x.h"
#include "delay.h"

// 串口初始化函数
void My_Uart1_Init(void) {
	GPIO_InitTypeDef GPIO_InitTypeDefStrue;
	NVIC_InitTypeDef NVIC_InitTypeDefStrue;
	USART_InitTypeDef USART_InitTypeDefStrue;
	
	// 使能GPIO_A时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	// 使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	// 设置GPIO输入输出模式
	GPIO_InitTypeDefStrue.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用
	GPIO_InitTypeDefStrue.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitTypeDefStrue.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA, &GPIO_InitTypeDefStrue);
	
	GPIO_InitTypeDefStrue.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
	GPIO_InitTypeDefStrue.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitTypeDefStrue.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA, &GPIO_InitTypeDefStrue);
	
	// 串口参数初始化
	USART_InitTypeDefStrue.USART_BaudRate = 115200; // 波特率
	USART_InitTypeDefStrue.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制
	USART_InitTypeDefStrue.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 接收发送模式
	USART_InitTypeDefStrue.USART_Parity = USART_Parity_No; // 有无奇偶校验
	USART_InitTypeDefStrue.USART_StopBits = USART_StopBits_1; // 停止位
	USART_InitTypeDefStrue.USART_WordLength = USART_WordLength_8b; // 数据长度
	USART_Init(USART1,&USART_InitTypeDefStrue);
	
	// 初始化NVIC
	NVIC_InitTypeDefStrue.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitTypeDefStrue.NVIC_IRQChannelCmd = ENABLE;	// IRQ通道使能
	NVIC_InitTypeDefStrue.NVIC_IRQChannelPreemptionPriority = 3; // 抢占优先级
	NVIC_InitTypeDefStrue.NVIC_IRQChannelSubPriority = 3; // 子优先级
	NVIC_Init(&NVIC_InitTypeDefStrue);
	
	// 启动中断
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	// 使能串口
	USART_Cmd(USART1, ENABLE);
}

 // 串口1中断
void USART1_IRQHandler(){
	u8 res;
	if(USART_GetITStatus(USART1,USART_IT_RXNE)){
		res = USART_ReceiveData(USART1);
		USART_SendData(USART1,res);
	}

}

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	My_Uart1_Init();
	while(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

# 外部中断

# 外部中断概述

STM32的每个IO都可以作为外部中断输入。

STM32的中断控制器支持19个外部中断/事件请求:

  • 线0~15:对应外部IO口的输入中断。
  • 线16:连接到PVD输出。
  • 线17:连接到RTC闹钟事件。
  • 线18:连接到USB唤醒事件。

每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。

从上面可以看出,STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112),STM32F103RCT6(51),那么中断线怎么跟io口对应呢?

对于每个中断线,我们可以设置相应的触发方式(上升沿触发,下降沿触发,边沿触发)以及使能。

# 是不是16个中断线就可以分配16个中断服务函数呢?

IO口外部中断在中断向量表中只分配了7个中断向量,也就是 只能使用7个中断服务函数

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

# 中断服务函数列表

  • EXTI0_IRQHandler
  • EXTI1_IRQHandler
  • EXTI2_IRQHandler
  • EXTI3_IRQHandler
  • EXTI4_IRQHandler
  • EXTI9_5_IRQHandler
  • EXTI15_10_IRQHandler

# 外部中断常用库函数

  1. 设置IO口与中断线的映射关系

    void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)

  2. 初始化中断线:触发方式等

    void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

  3. 判断中断线中断状态,是否发生

    ITStatus EXTI_GetITStatus(uint32_t EXTI_Line)

  4. 清除中断线上的中断标志位

    void EXTI_ClearITPendingBit(uint32_t EXTI_Line)

# EXTI_Init函数

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

typedef struct
{
  uint32_t EXTI_Line;   //指定要配置的中断线           
  EXTIMode_TypeDef EXTI_Mode;   //模式:事件 OR中断
  EXTITrigger_TypeDef EXTI_Trigger;//触发方式:上升沿/下降沿/双沿触发
  FunctionalState EXTI_LineCmd;  //使能 OR失能
}EXTI_InitTypeDef;
1
2
3
4
5
6
7
EXTI_InitStructure.EXTI_Line=EXTI_Line2;	 
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
1
2
3
4
5

# 外部中断的一般配置步骤

  1. 初始化IO口为输入GPIO_Init()
  2. 开启IO口复用时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE)
  3. 设置IO口与中断线的映射关系void GPIO_EXTILineConfig()
  4. 初始化线上中断,设置触发条件等EXTI_Init()
  5. 配置中断分组(NVIC),并使能中断NVIC_Init()
  6. 编写中断服务函数EXTIx_IRQHandler()
  7. 编写中断服务函数EXTI_ClearITPendingBit()

exti.c

#include "stm32f10x.h"
#include "exti.h"
#include "key.h"
#include "delay.h"

// 初始化
void EXTIX_Init(void){
	
	GPIO_InitTypeDef GPIO_InitTypeDefStruct;
	EXTI_InitTypeDef EXTI_InitTypeDefStruct;
	NVIC_InitTypeDef NVIC_InitTypeDefStruct;
	
	// GPIOC使能时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	
	// 开启IO口复用时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	// 初始化 GPIOC_5  KEY0
	GPIO_InitTypeDefStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitTypeDefStruct.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitTypeDefStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitTypeDefStruct);
	
	//设置IO口与中断线的映射关系
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);
	
	// 初始化线上中断,设置触发条件等
	EXTI_InitTypeDefStruct.EXTI_Line = EXTI_Line5;
	EXTI_InitTypeDefStruct.EXTI_LineCmd = ENABLE;
	EXTI_InitTypeDefStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitTypeDefStruct.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitTypeDefStruct);
	
	// 配置中断分组(NVIC)
	NVIC_InitTypeDefStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitTypeDefStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitTypeDefStruct.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitTypeDefStruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitTypeDefStruct);
	
}

// 中断服务函数
void EXTI9_5_IRQHandler(){

	u8 key;
	delay_ms(10);

	if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_8)){
			GPIO_ResetBits(GPIOA,GPIO_Pin_8);
			GPIO_ResetBits(GPIOD,GPIO_Pin_2);
		}else {
			GPIO_SetBits(GPIOA,GPIO_Pin_8);
			GPIO_SetBits(GPIOD,GPIO_Pin_2);
		}
	EXTI_ClearITPendingBit(EXTI_Line5); //清除 LINE5 上的中断标志位
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

main.c

#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"

char KEY_Press = -1; // 0 代表KEY0按下, 1 代表KEY1 按下, 3 代表WK_UP按下 - 1代表未按下

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	KEY_Init();
	LED_Init();
	delay_init();
	EXTIX_Init();
	while(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 独立看门狗

# 看门狗概念

在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。

STM32内置两个看门狗,提供了更高的安全性,时间的精确性和使用的灵活性。两个看门狗设备(独立看门狗/窗口看门狗)可以用来检测和解决由软件错误引起的故障。当计数器达到给定的超时值时,触发一个中断(仅适用窗口看门狗)或者产生系统复位。

独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它仍有效。

独立看门狗适合应用于需要看门狗作为一个在主程序之外 能够完全独立工作,并且对时间精度要求低的场合

窗口看门狗由从APB1时钟分频后得到时钟驱动。通过可配置的时间窗口来检测应用程序非正常的过迟或过早操作。

窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序

# 看门狗解决的问题是什么

  1. 在启动正常运行的时候,系统不能复位

  2. 在系统跑飞(程序异常执行)的情况,系统复位,程序重新执行

# 独立看门狗功能描述

  1. l在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗。此时计数器开始从其复位值0xFFF递减,当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)。
  2. 无论何时,只要在键值寄存器IWDG_KR中写入0xAAAA(通常说的喂狗), 自动重装载寄存器IWDG_RLR的值就会重新加载到计数器,从而避免看门狗复位。
  3. 如果程序异常,就无法正常喂狗,从而系统复位。

  • 键值寄存器IWDG_KR: 0~15位有效
  • 预分频寄存器IWDG_PR:0~2位有效。具有写保护功能,要操作先取消写保护
  • 重装载寄存器IWDG_RLR:0~11位有效。具有写保护功能,要操作先取消写保护
  • 状态寄存器IWDG_SR:0~1位有效

置 设置 MOE 输出,使能 PWM 输出。 。 普通定时器在完成以上设置了之后,就可以输出 PWM 了,但是高级定时器,我们还需要 使能刹车和死区寄存器(TIM1_BDTR)的 MOE 位,以使能整个 OCx(即 PWM)输出。库函 数的设置函数为

# 独立看门狗超时时间

# IWDG独立看门狗操作库函数

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
2
3
4
5
6

# 独立看门狗操作步骤

  1. 取消寄存器写保护IWDG_WriteAccessCmd()
  2. 设置独立看门狗的预分频系数,确定时钟:IWDG_SetPrescaler()
  3. 设置看门狗重装载值,确定溢出时间:IWDG_SetReload()
  4. 使能看门狗:IWDG_Enable()
  5. 应用程序喂狗:IWDG_ReloadCounter()

溢出时间计算:Tout=((4×2^prer) × rlr) /40 (M3)

# 窗口看门狗

# 概述

之所以称为窗口就是因为其喂狗时间是一个有上下限的范围内(窗口),你可以通过设定相关寄存器,设定其上限时间(下限固定)。喂狗的时间不能过早也不能过晚。

而独立看门狗限制喂狗时间在0-x内,x由相关寄存器决定。喂狗的时间不能过晚。

# 窗口看门狗工作示意图

# 窗口看门狗框图

# 窗口看门狗工作过程总结

STM32F的窗口看门狗中有一个7位的递减计数器T[6:0],它会在出现下述2种情况之一时产生看门狗复位:

  1. 当喂狗的时候如果计数器的值大于某一设定数值W[6:0]时,此设定数值在WWDG_CFR寄存器定义。
  2. 当计数器的数值从0x40减到0x3F时【T6位跳变到0】

如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),它可以用于喂狗以避免WWDG复位。

# 窗口看门狗超时时间

# 为什么要窗口看门狗

对于一般的看门狗,程序可以在它产生复位前的任意时刻刷新看门狗,但这有一个隐患,有可能程序跑乱了又跑回到正常的地方,或跑乱的程序正好执行了刷新看门狗操作,这样的情况下一般的看门狗就检测不出来了;

如果使用窗口看门狗,程序员可以根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗也不会滞后刷新看门狗,这样可以检测出程序没有按照正常的路径运行非正常地跳过了某些程序段的情况

# 窗口看门狗其他注意事项

  1. 上窗口值W[6:0]必须大于下窗口值0x40。否则就无窗口了
  2. 窗口看门狗时钟来源PCLK1(APB1总线时钟)分频后

# 控制寄存器WWDG_CR

# 配置寄存器WWDG_CFR

# 状态寄存器WWDG_SR

# 窗口看门狗配置过程

  1. 使能看门狗时钟RCC_APB1PeriphClockCmd()
  2. 设置分频系数WWDG_SetPrescaler()
  3. 设置上窗口值WWDG_SetWindowValue()
  4. 使能看门狗WWDG_Enable()
  5. 开启提前唤醒中断并分组(可选)
    1. WWDG_EnableIT()
    2. NVIC_Init()
  6. 喂狗WWDG_SetCounter()
  7. 编写中断服务函数WWDG_IRQHandler()

# 通用定时器

# STM32定时器

STM32F10x系列总共最多有8个定时器

# 三种STM32定时器区别

# 通用定时器功能特点描述

STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能特点包括:

  1. 位于低速的APB1总线上(APB1)
  2. 16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)
  3. 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值。
  4. 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
    1. 输入捕获
    2. 输出比较
    3. PWM 生成(边缘或中间对齐模式)
    4. 单脉冲模式输出
  5. 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
  6. 如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器)
    1. 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
    2. 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    3. 输入捕获
    4. 输出比较
    5. 支持针对定位的增量(正交)编码器和霍尔传感器电路
    6. 触发输入作为外部时钟或者按周期的电流管理
  7. STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等
  8. 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。

# 计数器模式

通用定时器可以向上计数、向下计数、向上向下双向计数模式

  1. 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件
  2. 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件
  3. 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数

# 通用定时器工作过程

# 定时器中断实验相关寄存器

# 计数器当前值寄存器CNT

# 预分频寄存器TIMx_PSC

# 自动重装载寄存器(TIMx_ARR)

# 控制寄存器1(TIMx_CR1)

# DMA中断使能寄存器(TIMx_DIER)

# 常用库函数

  1. 定时器参数初始化:void TIM_TimeBaseInit**(**TIM_TypeDef TIMx, TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct);

    typedef struct
    {
      uint16_t TIM_Prescaler;        
      uint16_t TIM_CounterMode;     
      uint16_t TIM_Period;        
      uint16_t TIM_ClockDivision;  
      uint8_t TIM_RepetitionCounter;
    } TIM_TimeBaseInitTypeDef; 
    
    TIM_TimeBaseStructure.TIM_Period = 4999; TIM_TimeBaseStructure.TIM_Prescaler = 7199; 
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  2. 定时器使能函数:void TIM_Cmd(TIM_TypeDefTIMx,FunctionalState NewState)

  3. 定时器中断使能函数:void TIM_ITConfig**(**TIM_TypeDef TIMx , uint16_t TIM_IT, FunctionalState NewState);

  4. 状态标志位获取和清除

    FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
    void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
    ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
    void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
    
    1
    2
    3
    4

# 定时器中断实现步骤

  1. 能定时器时钟RCC_APB1PeriphClockCmd()
  2. 初始化定时器,配置ARR,PSCTIM_TimeBaseInit()
  3. 开启定时器中断,配置NVIC
    1. void TIM_ITConfig()
    2. NVIC_Init()
  4. 使能定时器TIM_Cmd()
  5. 编写中断服务函数TIMx_IRQHandler()

# 溢出时间

Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
1

# PWM

# STM32 PWM工作过程(通道1为例)

  1. CCR1:捕获比较(值)寄存器(x=1,2,3,4):设置比较值。
  2. CCMR1: OC1M[2:0]位: 对于PWM方式下,用于设置PWM模式1【110】或者PWM模式2【111】
  3. CCER:CC1P位:输入/捕获1输出极性。0:高电平有效,1:低电平有效。
  4. CCER:CC1E位:输入/捕获1输出使能。0:关闭,1:打开。

# PWM模式1 & PWM模式2

寄存器TIMx_CCMR1的OC1M[2:0]位来分析:

# 自动重载的预装载寄存器

# STM32 定时器3输出通道引脚

# 函数

  • PWM输出库函数

    void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
    
    typedef struct
    {
      uint16_t TIM_OCMode;  //PWM模式1或者模式2
      uint16_t TIM_OutputState; //输出使能 OR失能
      uint16_t TIM_OutputNState;
      uint16_t TIM_Pulse; //比较值,写CCRx
      uint16_t TIM_OCPolarity; //比较输出极性
      uint16_t TIM_OCNPolarity; 
      uint16_t TIM_OCIdleState;  
      uint16_t TIM_OCNIdleState; 
    } TIM_OCInitTypeDef;
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //PWM模式2
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    TIM_OCInitStructure. TIM_Pulse=100;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  • 设置比较值函数

    void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
    
    1
  • 使能输出比较预装载

    void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
    
    1
  • 使能自动重装载的预装载寄存器允许位

    void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
    
    1

# PWM输出配置步骤

  1. 使能定时器3和相关IO口时钟

    1. 使能定时器3时钟:RCC_APB1PeriphClockCmd();
    2. 使能GPIOB时钟:RCC_APB2PeriphClockCmd();
  2. 初始化IO口为复用功能输出。函数:GPIO_Init();

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

  3. 这里我们是要把PB5用作定时器的PWM输出引脚,所以要重映射配置**,**所以需要开启AFIO时钟。同时设置重映射。

    1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    2. GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
  4. 初始化定时器:ARR,PSC等:

    TIM_TimeBaseInit();

  5. 初始化输出比较参数:

    TIM_OC2Init()

  6. 使能预装载寄存器:

    TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);

  7. 使能定时器。

    TIM_Cmd()

  8. 不断改变比较值CCRx,达到不同的占空比效果:

    TIM_SetCompare2();

# 输入捕获

# 输入捕获工作过程(通道1为例)

**一句话总结工作过程:**通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。

# 设置输入捕获滤波器

# 设置输入捕获极性(通道1为例)

# 设置输入捕获映射通道(通道1为例)

# 设置输入捕获分频器(通道1为例)

# 捕获到有效信号可以开启中断

# 看看定时器通道对应引脚TIM5为例

# 相关函数

# 输入捕获通道初始化函数

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

typedef struct
{
  uint16_t TIM_Channel; //捕获通道1-4   
  uint16_t TIM_ICPolarity; //捕获极性
  uint16_t TIM_ICSelection; //映射关系
  uint16_t TIM_ICPrescaler; //分频系数
  uint16_t TIM_ICFilter;  //滤波器
} TIM_ICInitTypeDef;

TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; 
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 通道极性设置独立函数

void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity)
1

# 获取通道捕获值

uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx)
1

# 输入捕获的一般配置步骤

  1. 初始化定时器和通道对应IO的时钟

  2. 初始化IO口,模式为输入:GPIO_Init()

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入

  3. 初始化定时器ARR,PSC

    TIM_TimeBaseInit()

  4. 初始化输入捕获通道

    TIM_ICInit()

  5. 如果要开启捕获中断

    TIM_ITConfig() NVIC_Init()

  6. 使能定时器:TIM_Cmd()

  7. 编写中断服务函数:TIMx_IRQHandler()

# 实验:测量信号的脉冲宽度

# LCD

//TODO

# USMART

//TODO

# RTC

# RTC 实时时钟特征和原理

  • RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期。
  • RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。

# 特征

# 工作原理框图

RTC由两部分组成

  1. APB1接口:用来和APB1总线相连。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)
  2. RTC核心:由一组可编程计数器组成。分两个主要模块
    1. 第一个是RTC预分频模块,它可以编程产生最长1秒的RTC时间基TR_CLK。如果设置了秒中断允许位,可以产生秒中断
    2. 第二个是32位的可编程计数器,可被初始化为当前时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比,当匹配时候如果设置了闹钟中断允许位,可以产生闹钟中断
  3. RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读

# RTC原理

# RTC时钟源

# BKP备份寄存器

  1. 备份寄存器是42个16位的寄存器。可用来存储84个字节数据。
  2. 它们处在备份区域,当VDD电源切断,仍然由VBAT维持供电。
  3. 当系统在待机模式下被唤醒,或者系统复位或者电源复位,它们也不会复位。
  4. 执行以下操作将使能对后备寄存器和RTC访问:
    1. 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备时钟
    2. 设置寄存器PWR_CR的DBP位,使能对RTC和后备寄存器的访问

**提醒:**一共有42个16位备份寄存器。常用来保存一些系统配置信息和相关标志位。

# RTC相关寄存器

  1. RTC控制寄存器 (RTC_CRH, RTC_CRL)
  2. RTC预分频装载寄存器 (RTC_PRLH, RTC_PRLL)
  3. RTC预分频余数寄存器 (RTC_DIVH, RTC_DIVL)
  4. RTC计数器寄存器 (RTC_CNTH, RTC_CNTL)
  5. RTC闹钟寄存器 (RTC_ALRH ,RTC_ALRL)

# RTC控制寄存器高位(RTC_CRH)

# RTC控制寄存器低位(RTC_CRL)

  1. 修改CRH/CRL寄存器,必须先判断RSF位,确定已经同步。
  2. 修改CNT,ALR,PRL的时候,必须先配置CNF位进入配置模式,修改完之后,设置CNF位为0退出配置模式。
  3. 同时在对RTC相关寄存器写操作之前,必须判断上一次写操作已经结束,也就是判断RTOFF位是否置位。

# RTC预分频装载寄存器高位(RTC_PRLH)

# RTC预分频余数寄存器 (RTC_DIVH, RTC_DIVL)

# RTC计数器寄存器 (RTC_CNTH, RTC_CNTL)

# RTC闹钟寄存器 (RTC_ALRH ,RTC_ALRL)

# RTC寄存器配置流程

# RTC相关库函数

stm32f10x_rtc.c / stm32f10x_rtc.h

void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
void RTC_EnterConfigMode(void);
void RTC_ExitConfigMode(void);
uint32_t  RTC_GetCounter(void);
void RTC_SetCounter(uint32_t CounterValue);
void RTC_SetPrescaler(uint32_t PrescalerValue);
void RTC_SetAlarm(uint32_t AlarmValue);
uint32_t  RTC_GetDivider(void);
void RTC_WaitForLastTask(void);
void RTC_WaitForSynchro(void);
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. RTC时钟源和时钟操作函数:
    1. void RCC_RTCCLKConfig(uint32_t CLKSource);//时钟源选择
    2. void RCC_RTCCLKCmd(FunctionalState NewState)//时钟使能
  2. RTC配置函数(预分频,计数值):
    1. void RTC_SetPrescaler(uint32_t PrescalerValue);//预分频配置:PRLH/PRLL
    2. void RTC_SetCounter(uint32_t CounterValue);//设置计数器值:CNTH/CNTL
    3. void RTC_SetAlarm(uint32_t AlarmValue);//闹钟设置:ALRH/ALRL
  3. RTC中断设置函数:
    1. void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
  4. RTC允许配置和退出配置函数:
    1. void RTC_EnterConfigMode(void);//允许RTC配置 :CRL位 CNF
    2. void RTC_ExitConfigMode(void);//退出配置模式:CRL位 CNF
  5. 同步函数:
    1. void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFF
    2. void RTC_WaitForSynchro(void);//等待时钟同步:CRL位RSF
  6. 相关状态位获取清除函数:
    1. FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
    2. void RTC_ClearFlag(uint16_t RTC_FLAG);
    3. ITStatus RTC_GetITStatus(uint16_t RTC_IT);
    4. void RTC_ClearITPendingBit(uint16_t RTC_IT);
  7. 其他相关函数(BKP等)
    1. PWR_BackupAccessCmd();//BKP后备区域访问使能
    2. RCC_APB1PeriphClockCmd();//使能PWR和BKP时钟
    3. RCC_LSEConfig();//开启LSE,RTC选择LSE作为时钟源
    4. uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读BKP寄存器
    5. void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//写BKP

# RTC配置一般步骤

  1. 使能PWR和BKP时钟:RCC_APB1PeriphClockCmd();
  2. 使能后备寄存器访问: PWR_BackupAccessCmd();
  3. 配置RTC时钟源,使能RTC时钟:
    1. RCC_RTCCLKConfig();
    2. RCC_RTCCLKCmd();
    3. 如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
  4. 设置RTC预分频系数:RTC_SetPrescaler();
  5. 设置时间:RTC_SetCounter();
  6. 开启相关中断(如果需要):RTC_ITConfig();
  7. 编写中断服务函数:RTC_IRQHandler();
  8. 部分操作要等待写操作完成和同步。
    1. RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
    2. RTC_WaitForSynchro(); //等待RTC寄存器同步

# ADC

Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。

典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。

# STM32F10x ADC特点

  1. 12位逐次逼近型的模拟数字转换器。
  2. 最多带3个ADC控制器。
  3. 最多支持18个通道,可最多测量16个外部和2个内部信号源。
  4. 支持单次和连续转换模式。
  5. 转换结束,注入转换结束,和发生模拟看门狗事件时产生中断。
  6. 通道0到通道n的自动扫描模式
  7. 自动校准
  8. 采样间隔可以按通道编程
  9. 规则通道和注入通道均有外部触发选项
  10. 转换结果支持左对齐或右对齐方式存储在16位数据寄存器
  11. ADC转换时间:最大转换速率 1us。(最大转换速度为1MHz,在ADCCLK=14M,采样周期为1.5个ADC时钟下得到。)
  12. ADC供电要求:2.4V-3.6V
  13. ADC输入范围:VREF- ≤ VIN ≤ VREF+

# STM32F10x大容量芯片带3个ADC控制器

其中144脚芯片因为带PF脚,所以多5个通道,为21个外部通道。小于144脚芯片只有16个外部通道

# STM32F10x系列芯片ADC通道和引脚对应关系

# ADC引脚

# ADC框图

# STM32通道组

  1. 规则通道组:相当正常运行的程序。最多16个通道。规则通道和它的转换顺序在ADC_SQRx寄存器中选择,规则组转换的总数应写入ADC_SQR1寄存器的L[3:0]中
  2. 注入通道组:相当于中断。最多4个通道。 注入组和它的转换顺序在ADC_JSQR寄存器中选择。注入组里转化的总数应写入ADC_JSQR寄存器的L[1:0]中

# ADC的各通道可以单次,连续,扫描或者间断模式执行

# 单次转化 VS 连续转换

# 扫描模式

# ADC中断

# ADC时钟配置

# ADC_CR1寄存器

# ADC_CR2寄存器

# 数据对齐方式

# ADC_CR2寄存器

# ADC_SMPR1寄存器

# ADC_SMPR2寄存器

# ADC的采样时间

最小采样时间1us(ADC时钟=14MHz,采样周期为1.5周期下得到)
1

# ADC_SQR1/SQR2/SQR3规则序列寄存器

# ADC_JSQR注入系列寄存器

# ADC_DR规则通道数据寄存器

# ADC_JDR注入通道数据寄存器

# ADC_SR状态寄存器

# 常用库函数

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_DeInit(ADC_TypeDef* ADCx)
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
1
2
3
4
5
6
7
8
9
10
11
12

# ADC初始化函数ADC_Init

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

typedef struct
{
  uint32_t ADC_Mode;//ADC模式:配置ADC_CR1寄存器的位[19:16]  :DUALMODE[3:0]位
  FunctionalState ADC_ScanConvMode; //是否使用扫描模式。ADC_CR1位8:SCAN位 
  FunctionalState ADC_ContinuousConvMode; //单次转换OR连续转换:ADC_CR2的位1:CONT
  uint32_t ADC_ExternalTrigConv;  //触发方式:ADC_CR2的位[19:17] :EXTSEL[2:0]                
  uint32_t ADC_DataAlign;   //对齐方式:左对齐还是右对齐:ADC_CR2的位11:ALIGN         
  uint8_t ADC_NbrOfChannel;//规则通道序列长度:ADC_SQR1的位[23:20]: L[3:0]       
}ADC_InitTypeDef;


void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

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数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure);	
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# ADC使能函数 ADC_Cmd()

void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState)
    
ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1    
1
2
3

# ADC使能软件转换函数 ADC_SoftwareStartConvCmd

void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState)
    
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能ADC1的软件转换启动
1
2
3

# ADC 规则通道配置函数ADC_RegularChannelConfig

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);


ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1,  ADC_SampleTime_239Cycles5 );
1
2
3
4

# ADC 获取转换结果函数ADC_GetConversionValue

uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);

ADC_GetConversionValue(ADC1);//获取ADC1转换结果
1
2
3

# ADC1的通道1(PA1)进行单次转化

  1. 开启PA口时钟和ADC1时钟,设置PA1为模拟输入
    1. GPIO_Init();
    2. APB2PeriphClockCmd();
  2. 复位ADC1,同时设置ADC1分频因子
    1. RCC_ADCCLKConfig(RCC_PCLK2_Div6)
    2. ADC_DeInit(ADC1)
  3. 初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息
    1. void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct)
  4. 使能ADC并校准
    1. ​ ADC_Cmd(ADC1, ENABLE)
  5. 配置规则通道参数
    1. ADC_RegularChannelConfig()
  6. 开启软件转换
    1. ADC_SoftwareStartConvCmd(ADC1)
  7. 等待转换完成,读取ADC值
    1. ADC_GetConversionValue(ADC1)

# 内部温度传感器

# 内部温度传感器框图

STM32有一个内部的温度传感器,可以用来测量CPU及周围的温度(TA)。

该温度传感器在内部和ADCx_IN16输入通道相连接,此通道把传感器输出的电压转换成数字值。

温度传感器模拟输入推荐采样时间是17.1μs

STM32的内部温度传感器支持的温度范围为:-40~125度。精度比较差,为±1.5℃左右。

内部温度传感器更适合于检测温度的变化,而不是测量绝对温度。如果需要测量绝度温度,应该使用一个外部温度传感器。

# 内部温度传感器使用注意事项

第一个地方,我们要使用STM32的内部温度传感器,必须先激活ADC的内部通道,这里通过ADC_CR2的TSVREFE位(bit23)设置。设置该位为1则启用内部温度传感器。

第二个地方,STM32的内部温度传感器固定的连接在ADC的通道16上,所以,我们在设置好ADC之后只要读取通道16的值,就是温度传感器返回来的电压值了。 根据这个值,我们就可以计算出当前温度。计算公式如下:

T(℃)={(V25-Vsense)/Avg_Slope}+25

上式中: V25=Vsense在25度时的数值(典型值为:1.43)。 Avg_Slope=温度与Vsense曲线的平均斜率(单位为mv/℃或uv/℃)(典型值为4.3Mv/℃)。 利用以上公式,我们就可以方便的计算出当前温度传感器的温度了。

内部温度传感器温度和电压关系图

# 开启内部温度传感器步骤

  1. 选择ADC_IN16输入通道
  2. 设置采样时间大于17.1us
  3. 设置ADC_CR2的TSVREFE位,打开内部温度传感器
  4. 设置ADON位启动转换
  5. 读取ADC结果
  6. 计算

# DMA

# DMA简介

  1. DMA(Direct Memory Access)直接存储器存取
  2. DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  3. 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
  4. 每个通道都支持软件触发和特定的硬件触发
  5. STM32F103C8T6 DMA资源:DMA1(7个通道)

# 存储器映像

# DMA框图

# DMA基本结构

# DMA请求

# 数据宽度与对齐

# 数据转运+DMA

# ADC扫描模式+DMA

# 扫描 + 非连续转换

#include "dma_adc.h"
#include "delay.h"

uint16_t DMA_ADC_VALUES[8];

uint16_t DMA_ADC_SIZE;

// 初始化
void DMA_ADC_Init(uint16_t Size){
	
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStrture;
	
	DMA_ADC_SIZE = Size;
	
	//开启PA口时钟和ADC1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA,ENABLE);
	// 开启DMA1时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	//复位ADC1,同时设置ADC1分频因子
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	//设置PA1为模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;				 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; 		 //模拟输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);					 


	ADC_DeInit(ADC1);
	// 配置规则通道参数
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_4,5,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_5,6,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_6,7,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_7,8,ADC_SampleTime_55Cycles5);
	
	//初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	ADC_InitStructure.ADC_NbrOfChannel = DMA_ADC_SIZE;
	ADC_Init(ADC1,&ADC_InitStructure);
	
	// 初始化DMA   外设站点3个参数、存储器站点3个参数
	DMA_InitStrture.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 外设基本地址
	DMA_InitStrture.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度
	DMA_InitStrture.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 地址是否自增
	DMA_InitStrture.DMA_MemoryBaseAddr = (uint32_t)DMA_ADC_VALUES; //存储器基本地址
	DMA_InitStrture.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 存储器数据宽度
	DMA_InitStrture.DMA_MemoryInc = DMA_MemoryInc_Enable; // 地址是否自增
	DMA_InitStrture.DMA_DIR = DMA_DIR_PeripheralSRC; // 指定外设站点是源端还是目的地
	DMA_InitStrture.DMA_BufferSize = DMA_ADC_SIZE; // 传输次数
	DMA_InitStrture.DMA_Mode = DMA_Mode_Normal; // 传输模式
	DMA_InitStrture.DMA_M2M = DMA_M2M_Disable; // 是否为存储器到存储器转运(软件触发)(硬件触发)
	DMA_InitStrture.DMA_Priority = DMA_Priority_Medium; // 优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStrture);
	
	// 使能
	DMA_Cmd(DMA1_Channel1,ENABLE);
	 
	// 开启ADC到DMA输出 
	ADC_DMACmd(ADC1,ENABLE);
	
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1); // 复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);// 等待复位校准完成
  ADC_StartCalibration(ADC1); // 开始校准
	while(ADC_GetCalibrationStatus(ADC1));// 等待开始校准完成

}

void DMA_ADC_GetValue() {
	
	//失能
	 DMA_Cmd(DMA1_Channel1,DISABLE);
	//设置转运次数
	 DMA_SetCurrDataCounter(DMA1_Channel1,DMA_ADC_SIZE);
	//使能
	 DMA_Cmd(DMA1_Channel1,ENABLE);
	
	// 开始转换
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
	
	// 等待转运完成
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	
	// 清除标志位
	DMA_ClearFlag(DMA1_FLAG_TC1);

}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

# 扫描 + 连续转换

#include "dma_adc.h"
#include "delay.h"

uint16_t DMA_ADC_VALUES[8];

uint16_t DMA_ADC_SIZE;

// 初始化
void DMA_ADC_Init(uint16_t Size){
	
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	DMA_InitTypeDef DMA_InitStrture;
	
	DMA_ADC_SIZE = Size;
	
	//开启PA口时钟和ADC1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA,ENABLE);
	// 开启DMA1时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	//复位ADC1,同时设置ADC1分频因子
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	//设置PA1为模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;				 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; 		 //模拟输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);					 


	ADC_DeInit(ADC1);
	// 配置规则通道参数
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_4,5,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_5,6,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_6,7,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_7,8,ADC_SampleTime_55Cycles5);
	
	//初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	ADC_InitStructure.ADC_NbrOfChannel = DMA_ADC_SIZE;
	ADC_Init(ADC1,&ADC_InitStructure);
	
	// 初始化DMA   外设站点3个参数、存储器站点3个参数
	DMA_InitStrture.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 外设基本地址
	DMA_InitStrture.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度
	DMA_InitStrture.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 地址是否自增
	DMA_InitStrture.DMA_MemoryBaseAddr = (uint32_t)DMA_ADC_VALUES; //存储器基本地址
	DMA_InitStrture.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 存储器数据宽度
	DMA_InitStrture.DMA_MemoryInc = DMA_MemoryInc_Enable; // 地址是否自增
	DMA_InitStrture.DMA_DIR = DMA_DIR_PeripheralSRC; // 指定外设站点是源端还是目的地
	DMA_InitStrture.DMA_BufferSize = DMA_ADC_SIZE; // 传输次数
	DMA_InitStrture.DMA_Mode = DMA_Mode_Circular; // 传输模式
	DMA_InitStrture.DMA_M2M = DMA_M2M_Disable; // 是否为存储器到存储器转运(软件触发)(硬件触发)
	DMA_InitStrture.DMA_Priority = DMA_Priority_Medium; // 优先级
	DMA_Init(DMA1_Channel1, &DMA_InitStrture);
	
	// 使能
	DMA_Cmd(DMA1_Channel1,ENABLE);
	 
	// 开启ADC到DMA输出 
	ADC_DMACmd(ADC1,ENABLE);
	
	ADC_Cmd(ADC1, ENABLE);
	
	ADC_ResetCalibration(ADC1); // 复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);// 等待复位校准完成
  ADC_StartCalibration(ADC1); // 开始校准
	while(ADC_GetCalibrationStatus(ADC1));// 等待开始校准完成
	
	// 开始转换
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Last Updated: 7/6/2022, 2:57:26 PM