STM32标准库学习笔记:深入理解与实践

stm32标准库学习笔记(随便写的),来源于B站江科大教学视频,相关的代码可以去B站下载,笔记里很多细节方面没有体现出来.

【STM32入门教程-2023版 细致讲解 中文字幕-哔哩哔哩】

一、工程建立

添加工程必要文件:打开Libraries->CMSIS -> CM3-> DeviceSupport-> ST-> STM32F10x-> startup-> arm->文件为启动文件,全选复制到工程模板文件夹(新建Start文件夹),回到STM32F10x(复制那三个文件stmxxx.h、systemxxx.c、systemxxx.h)复制到Start文件夹,接着打开CM3->CoreSupport(两个文件复制到Start文件夹),然后回到Keil软件将文件添加到工程里->将Source Group1单击改名Start->右键Start选择添加已存在文件,打开Start文件夹,打开文件过滤器选择All files,添加启动文件(视频里所用型号为添加后缀md.s的启动文件)和剩下所有.c和.h文件->最后在工程选项里添加该文件夹头文件路径 魔术棒按钮->C/C++->Include Paths点右边三个点按钮然后新建路径再点右三个点按钮添加Start路径,最后点ok ->打开新建工程文件夹添加新文件夹User(放main函数),然后点keil里Target右键添加组  改名User右击添加文件选.c命名为main(注意路径选择User文件夹),在main里右键插入头文件stm32f10x.h,写上主函数,最后一行必须为空行,至此就完成了工程建立(基于寄存器开发)keil软件扳手按钮里Encoding->UTF-8可以防止中文乱码                                   e7ec19c52284495a91db0f3448f8bfa0.png

efa0b417b1fd49c29ee34fe462cf6279.png

系统结构

3396f3f799f74dee9188859152d9710c.png

AHB系统总线:用于挂载主要的外设,AHB指先进高性能总线,挂载最基本的或者性能比较高的外设 

APB外设总线:用于来连接一般的外设

DMA:大量的数据搬运,防止覆盖

1d6aa06827d5470fa94d84f23ffd7199.pngb1983a0522254f9da47dcdcf96765ff8.png

红色是电源相关的引脚,蓝色是最小系统相关的引脚,绿色是I/O功能相关的引脚

main.c 

#include "stm32f10x.h"                  // Device header

int main(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	//开启GPIOC的时钟
									         //使用各个外设前必须开启时钟,否则对外设的操作无效
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				//GPIO引脚,赋值为第13号引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
	
	GPIO_Init(GPIOC, &GPIO_InitStructure);		//将赋值后的构体变量传递给GPIO_Init函数
											    //函数内部会自动根据结构体的参数配置相应寄存器
												//实现GPIOC的初始化
	
	/*设置GPIO引脚的高低电平*/
	/*若不设置GPIO引脚的电平,则在GPIO初始化为推挽输出后,指定引脚默认输出低电平*/
//	GPIO_SetBits(GPIOC, GPIO_Pin_13);						//将PC13引脚设置为高电平
	GPIO_ResetBits(GPIOC, GPIO_Pin_13);						//将PC13引脚设置为低电平
	
	while (1)
	{
		
	}
}

二、GPIO输入输出

  • GPIO(General Purpose Input Output)通用输入输出口 
  • 可配置为8种输入输出模式     
  • 引脚电平:0V~3.3V,部分引脚可容忍5V   
  • 输出模式下可控制端口输出高低电平,用以驱动LED 控制蜂鸣器 模拟通信协议输出时序等                                  
  •  输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电   压采集、模拟通信协议接收数据等   
  •                                                                         693b0993b22f415e8223af314c10a6f8.pngc23930c38775401bb7b1de277be114af.png  e3dd0771ec9341ad912f6ece128c09a6.png           97668d5a29714ea1aaad8fe6b0c30daf.png模拟输入一般 使用ADC的时候配置d0fe1f47d2ce4570a42398844fe1a148.pngP-MOS如果无效就是开漏输出,如果P-MOS和N-MOS都有效就是推挽输出

    b9d47a1fb1e445daa222b6a1a890d2cb.png​​​8c2c2bf826524c028f34a8d12d0ed36e.png

    三 、LED闪烁&LED流水灯&蜂鸣器

    b631bc365e0f4922a6aedfcaf79386b9.pngbaa338fa064a4d9c8b20cf74df8b84bc.png

    示例代码:  

    main.c      (点亮LED)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    
    int main(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    										//使用各个外设前必须开启时钟,否则对外设的操作无效
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
    	
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				//GPIO引脚,赋值为第0号引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
    	
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将赋值后的构体变量传递给GPIO_Init函数
    											//函数内部会自动根据结构体的参数配置相应寄存器
    															//实现GPIOA的初始化
    	
    	/*主循环,循环体内的代码会一直循环执行*/
    	while (1)
    	{
    		/*设置PA0引脚的高低电平,实现LED闪烁,下面展示3种方法*/
    		
    		/*方法1:GPIO_ResetBits设置低电平,GPIO_SetBits设置高电平*/
    		GPIO_ResetBits(GPIOA, GPIO_Pin_0);					//将PA0引脚设置为低电平
    		Delay_ms(500);										//延时500ms
    		GPIO_SetBits(GPIOA, GPIO_Pin_0);					//将PA0引脚设置为高电平
    		Delay_ms(500);										//延时500ms
    		
    		/*方法2:GPIO_WriteBit设置低/高电平,由Bit_RESET/Bit_SET指定*/
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);		//将PA0引脚设置为低电平
    		Delay_ms(500);										//延时500ms
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);			//将PA0引脚设置为高电平
    		Delay_ms(500);										//延时500ms
    		
    	/*方法3:GPIO_WriteBit设置低/高电平,由数据0/1指定,数据需要强转为BitAction(枚举)类型*/
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);		//将PA0引脚设置为低电平
    		Delay_ms(500);										//延时500ms
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);		//将PA0引脚设置为高电平
    		Delay_ms(500);										//延时500ms
    	}
    }
    

      main.c     (LED流水灯)                                                                           

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    
    int main(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    															//使用各个外设前必须开启时钟,否则对外设的操作无效
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
    	
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;				//GPIO引脚,赋值为所有引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
    	
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将赋值后的构体变量传递给GPIO_Init函数
    															//函数内部会自动根据结构体的参数配置相应寄存器
    															//实现GPIOA的初始化
    	
    	/*主循环,循环体内的代码会一直循环执行*/
    	while (1)
    	{
    		/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
    		GPIO_Write(GPIOA, ~0x0001);	//0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0002);	//0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0004);	//0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0008);	//0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0010);	//0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0020);	//0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0040);	//0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    		GPIO_Write(GPIOA, ~0x0080);	//0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
    		Delay_ms(100);				//延时100ms
    	}
    }
    
    

    main.c     (蜂鸣器)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    
    int main(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
    															//使用各个外设前必须开启时钟,否则对外设的操作无效
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
    	
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;				//GPIO引脚,赋值为第12号引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
    	
    	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将赋值后的构体变量传递给GPIO_Init函数
    															//函数内部会自动根据结构体的参数配置相应寄存器
    															//实现GPIOB的初始化
    	
    	/*主循环,循环体内的代码会一直循环执行*/
    	while (1)
    	{
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);		//将PB12引脚设置为低电平,蜂鸣器鸣叫
    		Delay_ms(100);							//延时100ms
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);		//将PB12引脚设置为高电平,蜂鸣器停止
    		Delay_ms(100);							//延时100ms
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);		//将PB12引脚设置为低电平,蜂鸣器鸣叫
    		Delay_ms(100);							//延时100ms
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);		//将PB12引脚设置为高电平,蜂鸣器停止
    		Delay_ms(700);							//延时700ms
    	}
    }
    

    延时函数

    Delay.c  (接下来所有的代码延时函数都用这个)

    #include "stm32f10x.h"
    
    /**
      * @brief  微秒级延时
      * @param  xus 延时时长,范围:0~233015
      * @retval 无
      */
    void Delay_us(uint32_t xus)
    {
    	SysTick->LOAD = 72 * xus;				//设置定时器重装值
    	SysTick->VAL = 0x00;					//清空当前计数值
    	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
    	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
    	SysTick->CTRL = 0x00000004;				//关闭定时器
    }
    
    /**
      * @brief  毫秒级延时
      * @param  xms 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_ms(uint32_t xms)
    {
    	while(xms--)
    	{
    		Delay_us(1000);
    	}
    }
     
    /**
      * @brief  秒级延时
      * @param  xs 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_s(uint32_t xs)
    {
    	while(xs--)
    	{
    		Delay_ms(1000);
    	}
    } 
    

    Delay.h 

    #ifndef __DELAY_H
    #define __DELAY_H
    
    void Delay_us(uint32_t us);
    void Delay_ms(uint32_t ms);
    void Delay_s(uint32_t s);
    
    #endif
    

    (因为.h文件里的内容就是函数的声明且比较多,所以这里就只写一个.h文件作为参考,下面的代码里不会再列出.h文件,为了笔记看起来不那么繁琐相同的代码块也不会重复出现)

    四、按键控制LED&光敏传感器控制蜂鸣器

    50ddf9ef4f2b40d49f06f17d0c322946.png

    1614476470c7431489e0bfe79f4d9b04.png

    35b5133f383b46ae88ad8d0326ee3bc7.png

    908362161ff44c0aa07e7f5aa376d4db.pngC语言宏定义

  • 关键字:#define
  • 用途:用一个字符串代替一个数字,便于理解,防止出错;提取程序中经常出现的参数,便于快速修改
  • 定义宏定义:     #define ABC 12345
  • 引用宏定义:     int a = ABC;    //等效于int a = 12345;
  • C语言typedef

  • 关键字:typedef
  • 用途:将一个比较长的变量类型名换个名字,便于使用
  • 定义typedef:  typedef unsigned char uint8_t;
  • 引用typedef:  uint8_t a;    //等效于unsigned char a;
  • C语言结构体

  • 关键字:struct
  • 用途:数据打包,不同类型变量的集合
  • 定义结构体变量:     struct{char x; int y; float z;} StructName;     因为结构体变量类型较长,所以通常用typedef更改变量类型名
  • 引用结构体成员:     StructName.x = 'A';     StructName.y = 66;     StructName.z = 1.23; 或    pStructName->x = 'A';    //pStructName为结构体的地址    pStructName->y = 66;     pStructName->z = 1.23;
  • #include <stdio.h>
    
    typedef struct{
        char x;
        int y;
        float z;
    } StructName_t;  //用typedef给结构体重命名
    
    int main(void)
    {
        StructName_t c;  //声明一个结构体变量c
        StructName_t d;  //声明一个结构体变量d
    }

    C语言枚举

  • 关键字:enum
  • 用途:定义一个取值受限制的整型变量,用于限制变量取值范围;宏定义的集合
  • 定义枚举变量:     enum{FALSE = 0, TRUE = 1} EnumName;     因为枚举变量类型较长,所以通常用typedef更改变量类型名
  • 引用枚举成员:     EnumName = FALSE;     EnumName = TRUE;
  • 示例代码:

    main.c     (按键控制LED)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "LED.h"
    #include "Key.h"
    
    uint8_t KeyNum;		//定义用于接收按键键码的变量
    
    int main(void)
    {
    	/*模块初始化*/
    	LED_Init();		//LED初始化
    	Key_Init();		//按键初始化
    	
    	while (1)
    	{
    		KeyNum = Key_GetNum();		//获取按键键码
    		
    		if (KeyNum == 1)			//按键1按下
    		{
    			LED1_Turn();			//LED1翻转
    		}
    		
    		if (KeyNum == 2)			//按键2按下
    		{
    			LED2_Turn();			//LED2翻转
    		}
    	}
    }
    

    Key.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    
    /**
      * 函    数:按键初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Key_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB1和PB11引脚初始化为上拉输入
    }
    
    /**
      * 函    数:按键获取键码
      * 参    数:无
      * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
      * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
      */
    uint8_t Key_GetNum(void)
    {
    	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
    	
    	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下
    	{
    		Delay_ms(20);											//延时消抖
    		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	//等待按键松手
    		Delay_ms(20);											//延时消抖
    		KeyNum = 1;												//置键码为1
    	}
    	
    	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下
    	{
    		Delay_ms(20);											//延时消抖
    		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	//等待按键松手
    		Delay_ms(20);											//延时消抖
    		KeyNum = 2;												//置键码为2
    	}
    	
    	return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
    }
    

    LED.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:LED初始化
      * 参    数:无
      * 返 回 值:无
      */
    void LED_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA1和PA2引脚初始化为推挽输出
    	
    	/*设置GPIO初始化后的默认电平*/
    	GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);				//设置PA1和PA2引脚为高电平
    }
    
    /**
      * 函    数:LED1开启
      * 参    数:无
      * 返 回 值:无
      */
    void LED1_ON(void)
    {
    	GPIO_ResetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为低电平
    }
    
    /**
      * 函    数:LED1关闭
      * 参    数:无
      * 返 回 值:无
      */
    void LED1_OFF(void)
    {
    	GPIO_SetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为高电平
    }
    
    /**
      * 函    数:LED1状态翻转
      * 参    数:无
      * 返 回 值:无
      */
    void LED1_Turn(void)
    {
    	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
    	{
    		GPIO_SetBits(GPIOA, GPIO_Pin_1);					//则设置PA1引脚为高电平
    	}
    	else													//否则,即当前引脚输出高电平
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_1);					//则设置PA1引脚为低电平
    	}
    }
    
    /**
      * 函    数:LED2开启
      * 参    数:无
      * 返 回 值:无
      */
    void LED2_ON(void)
    {
    	GPIO_ResetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为低电平
    }
    
    /**
      * 函    数:LED2关闭
      * 参    数:无
      * 返 回 值:无
      */
    void LED2_OFF(void)
    {
    	GPIO_SetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为高电平
    }
    
    /**
      * 函    数:LED2状态翻转
      * 参    数:无
      * 返 回 值:无
      */
    void LED2_Turn(void)
    {
    	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
    	{                                                  
    		GPIO_SetBits(GPIOA, GPIO_Pin_2);               		//则设置PA2引脚为高电平
    	}                                                  
    	else                                               		//否则,即当前引脚输出高电平
    	{                                                  
    		GPIO_ResetBits(GPIOA, GPIO_Pin_2);             		//则设置PA2引脚为低电平
    	}
    }
    

    main.c     (光敏传感器控制蜂鸣器)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "Buzzer.h"
    #include "LightSensor.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	Buzzer_Init();			//蜂鸣器初始化
    	LightSensor_Init();		//光敏传感器初始化
    	
    	while (1)
    	{
    		if (LightSensor_Get() == 1)		//如果当前光敏输出1
    		{
    			Buzzer_ON();				//蜂鸣器开启
    		}
    		else							//否则
    		{
    			Buzzer_OFF();				//蜂鸣器关闭
    		}
    	}
    }
    

    Buzzer.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:蜂鸣器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB12引脚初始化为推挽输出
    	
    	/*设置GPIO初始化后的默认电平*/
    	GPIO_SetBits(GPIOB, GPIO_Pin_12);							//设置PB12引脚为高电平
    }
    
    /**
      * 函    数:蜂鸣器开启
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_ON(void)
    {
    	GPIO_ResetBits(GPIOB, GPIO_Pin_12);		//设置PB12引脚为低电平
    }
    
    /**
      * 函    数:蜂鸣器关闭
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_OFF(void)
    {
    	GPIO_SetBits(GPIOB, GPIO_Pin_12);		//设置PB12引脚为高电平
    }
    
    /**
      * 函    数:蜂鸣器状态翻转
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_Turn(void)
    {
    	if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
    	{
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);						//则设置PB12引脚为高电平
    	}
    	else														//否则,即当前引脚输出高电平
    	{
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);						//则设置PB12引脚为低电平
    	}
    }
    

    LightSensor.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:光敏传感器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void LightSensor_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB13引脚初始化为上拉输入
    }
    
    /**
      * 函    数:获取当前光敏传感器输出的高低电平
      * 参    数:无
      * 返 回 值:光敏传感器输出的高低电平,范围:0/1
      */
    uint8_t LightSensor_Get(void)
    {
    	return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);			//返回PB13输入寄存器的状态
    }
    

    五、OLED调试工具

    (OLED显示屏的教程在UP的另一个专栏里)

    5ccb2db2f4604e9083ca7dbee24a765f.png

    a4e71f1363d0421f9d7a2a33726f3cd7.png

    31f3228d57bc49088d88b3a96e4e010b.png

    示例代码:

    main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	
    	/*OLED显示*/
    	OLED_ShowChar(1, 1, 'A');				//1行1列显示字符A
    	
    	OLED_ShowString(1, 3, "HelloWorld!");	//1行3列显示字符串HelloWorld!
    	
    	OLED_ShowNum(2, 1, 12345, 5);			//2行1列显示十进制数字12345,长度为5
    	
    	OLED_ShowSignedNum(2, 7, -66, 2);		//2行7列显示有符号十进制数字-66,长度为2
    	
    	OLED_ShowHexNum(3, 1, 0xAA55, 4);		//3行1列显示十六进制数字0xA5A5,长度为4
    	
    	OLED_ShowBinNum(4, 1, 0xAA55, 16);		//4行1列显示二进制数字0xA5A5,长度为16
    											//C语言无法直接写出二进制数字,故需要用十六进制表示
    	
    	while (1)
    	{
    		
    	}
    }
    

    OLED.C

    #include "stm32f10x.h"
    #include "OLED_Font.h"
    
    /*引脚配置*/
    #define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
    #define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
    
    /*引脚初始化*/
    void OLED_I2C_Init(void)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	OLED_W_SCL(1);
    	OLED_W_SDA(1);
    }
    
    /**
      * @brief  I2C开始
      * @param  无
      * @retval 无
      */
    void OLED_I2C_Start(void)
    {
    	OLED_W_SDA(1);
    	OLED_W_SCL(1);
    	OLED_W_SDA(0);
    	OLED_W_SCL(0);
    }
    
    /**
      * @brief  I2C停止
      * @param  无
      * @retval 无
      */
    void OLED_I2C_Stop(void)
    {
    	OLED_W_SDA(0);
    	OLED_W_SCL(1);
    	OLED_W_SDA(1);
    }
    
    /**
      * @brief  I2C发送一个字节
      * @param  Byte 要发送的一个字节
      * @retval 无
      */
    void OLED_I2C_SendByte(uint8_t Byte)
    {
    	uint8_t i;
    	for (i = 0; i < 8; i++)
    	{
    		OLED_W_SDA(Byte & (0x80 >> i));
    		OLED_W_SCL(1);
    		OLED_W_SCL(0);
    	}
    	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
    	OLED_W_SCL(0);
    }
    
    /**
      * @brief  OLED写命令
      * @param  Command 要写入的命令
      * @retval 无
      */
    void OLED_WriteCommand(uint8_t Command)
    {
    	OLED_I2C_Start();
    	OLED_I2C_SendByte(0x78);		//从机地址
    	OLED_I2C_SendByte(0x00);		//写命令
    	OLED_I2C_SendByte(Command); 
    	OLED_I2C_Stop();
    }
    
    /**
      * @brief  OLED写数据
      * @param  Data 要写入的数据
      * @retval 无
      */
    void OLED_WriteData(uint8_t Data)
    {
    	OLED_I2C_Start();
    	OLED_I2C_SendByte(0x78);		//从机地址
    	OLED_I2C_SendByte(0x40);		//写数据
    	OLED_I2C_SendByte(Data);
    	OLED_I2C_Stop();
    }
    
    /**
      * @brief  OLED设置光标位置
      * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
      * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
      * @retval 无
      */
    void OLED_SetCursor(uint8_t Y, uint8_t X)
    {
    	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
    	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
    	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
    }
    
    /**
      * @brief  OLED清屏
      * @param  无
      * @retval 无
      */
    void OLED_Clear(void)
    {  
    	uint8_t i, j;
    	for (j = 0; j < 8; j++)
    	{
    		OLED_SetCursor(j, 0);
    		for(i = 0; i < 128; i++)
    		{
    			OLED_WriteData(0x00);
    		}
    	}
    }
    
    /**
      * @brief  OLED显示一个字符
      * @param  Line 行位置,范围:1~4
      * @param  Column 列位置,范围:1~16
      * @param  Char 要显示的一个字符,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
    {      	
    	uint8_t i;
    	OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);		//设置光标位置在上半部分
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WriteData(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容
    	}
    	OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分
    	for (i = 0; i < 8; i++)
    	{
    		OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);		//显示下半部分内容
    	}
    }
    
    /**
      * @brief  OLED显示字符串
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  String 要显示的字符串,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
    {
    	uint8_t i;
    	for (i = 0; String[i] != '\0'; i++)
    	{
    		OLED_ShowChar(Line, Column + i, String[i]);
    	}
    }
    
    /**
      * @brief  OLED次方函数
      * @retval 返回值等于X的Y次方
      */
    uint32_t OLED_Pow(uint32_t X, uint32_t Y)
    {
    	uint32_t Result = 1;
    	while (Y--)
    	{
    		Result *= X;
    	}
    	return Result;
    }
    
    /**
      * @brief  OLED显示数字(十进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~4294967295
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
    /**
      * @brief  OLED显示数字(十进制,带符号数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:-2147483648~2147483647
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	uint32_t Number1;
    	if (Number >= 0)
    	{
    		OLED_ShowChar(Line, Column, '+');
    		Number1 = Number;
    	}
    	else
    	{
    		OLED_ShowChar(Line, Column, '-');
    		Number1 = -Number;
    	}
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
    /**
      * @brief  OLED显示数字(十六进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
      * @param  Length 要显示数字的长度,范围:1~8
      * @retval 无
      */
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i, SingleNumber;
    	for (i = 0; i < Length; i++)							
    	{
    		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
    		if (SingleNumber < 10)
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
    		}
    		else
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
    		}
    	}
    }
    
    /**
      * @brief  OLED显示数字(二进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
      * @param  Length 要显示数字的长度,范围:1~16
      * @retval 无
      */
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
    	}
    }
    
    /**
      * @brief  OLED初始化
      * @param  无
      * @retval 无
      */
    void OLED_Init(void)
    {
    	uint32_t i, j;
    	
    	for (i = 0; i < 1000; i++)			//上电延时
    	{
    		for (j = 0; j < 1000; j++);
    	}
    	
    	OLED_I2C_Init();			//端口初始化
    	
    	OLED_WriteCommand(0xAE);	//关闭显示
    	
    	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
    	OLED_WriteCommand(0x80);
    	
    	OLED_WriteCommand(0xA8);	//设置多路复用率
    	OLED_WriteCommand(0x3F);
    	
    	OLED_WriteCommand(0xD3);	//设置显示偏移
    	OLED_WriteCommand(0x00);
    	
    	OLED_WriteCommand(0x40);	//设置显示开始行
    	
    	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
    	
    	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置
    
    	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
    	OLED_WriteCommand(0x12);
    	
    	OLED_WriteCommand(0x81);	//设置对比度控制
    	OLED_WriteCommand(0xCF);
    
    	OLED_WriteCommand(0xD9);	//设置预充电周期
    	OLED_WriteCommand(0xF1);
    
    	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
    	OLED_WriteCommand(0x30);
    
    	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭
    
    	OLED_WriteCommand(0xA6);	//设置正常/倒转显示
    
    	OLED_WriteCommand(0x8D);	//设置充电泵
    	OLED_WriteCommand(0x14);
    
    	OLED_WriteCommand(0xAF);	//开启显示
    		
    	OLED_Clear();				//OLED清屏
    }
    

    OLED_Font.h (定义OLED的字库数据)      (代码实在太长了感觉全部写出来没什么意义,往下翻浪费时间,列出几行作为参考)

    #ifndef __OLED_FONT_H
    #define __OLED_FONT_H
    
    /*OLED字模库,宽8像素,高16像素*/
    const uint8_t OLED_F8x16[][16]=
    {
    	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//  0
    	
    	0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
    	0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
    	
    	0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
    	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
    };
    
    #endif
    

    六、外部中断

  • 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
  • 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
  • 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
  • 在中断函数里最好不要执行耗时过长的代码,最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件
  • 8a65e09dee594540b9c5ebf98dee2a70.png

    6aae0f07975f4f3ca9fe9afc9daa1f4e.png67e513f71e2c4b4cadc9cc3ab328676d.png234edc866c444d34ba810c4595296afc.png

    七、对射式红外传感器计次&旋转编码器计次

    c9cbcd26c70e441fb7035f4187595e85.png48945336423b4290a6352a5528e94f20.png

    示例代码:

    main.c     (对射式红外传感器计次)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "CountSensor.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();			//OLED初始化
    	CountSensor_Init();		//计数传感器初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Count:");	//1行1列显示字符串Count:
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 7, CountSensor_Get(), 5);		//OLED不断刷新显示CountSensor_Get的返回值
    	}
    }
    

    CountSensor.c

    #include "stm32f10x.h"                  // Device header
    
    uint16_t CountSensor_Count;				//全局变量,用于计数
    
    /**
      * 函    数:计数传感器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void CountSensor_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB14引脚初始化为上拉输入
    	
    	/*AFIO选择中断引脚*/
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
    	
    	/*EXTI初始化*/
    	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量
    	EXTI_InitStructure.EXTI_Line = EXTI_Line14;					//选择配置外部中断的14号线
    	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
    	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
    	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发
    	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设
    	
    	/*NVIC中断分组*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
    																//即抢占优先级范围:0~3,响应优先级范围:0~3
    																//此分组配置在整个工程中仅需调用一次
    																//若有多个中断,可以把此代码放在main函数内,while循环之前
    																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
    	
    	/*NVIC配置*/
    	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
    	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
    }
    
    /**
      * 函    数:获取计数传感器的计数值
      * 参    数:无
      * 返 回 值:计数值,范围:0~65535
      */
    uint16_t CountSensor_Get(void)
    {
    	return CountSensor_Count;
    }
    
    /**
      * 函    数:EXTI15_10外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI15_10_IRQHandler(void)
    {
    	if (EXTI_GetITStatus(EXTI_Line14) == SET)		//判断是否是外部中断14号线触发的中断
    	{
    		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
    		{
    			CountSensor_Count ++;					//计数值自增一次
    		}
    		EXTI_ClearITPendingBit(EXTI_Line14);		//清除外部中断14号线的中断标志位
    													//中断标志位必须清除
    													//否则中断将连续不断地触发,导致主程序卡死
    	}
    }
    

    main.c     (旋转编码器计次)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Encoder.h"
    
    int16_t Num;			//定义待被旋转编码器调节的变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	Encoder_Init();		//旋转编码器初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
    	
    	while (1)
    	{
    		Num += Encoder_Get();				//获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
    		OLED_ShowSignedNum(1, 5, Num, 5);	//显示Num
    	}
    }
    

    Encoder.c

    #include "stm32f10x.h"                  // Device header
    
    int16_t Encoder_Count;					//全局变量,用于计数旋转编码器的增量值
    
    /**
      * 函    数:旋转编码器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Encoder_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB0和PB1引脚初始化为上拉输入
    	
    	/*AFIO选择中断引脚*/
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
    	
    	/*EXTI初始化*/
    	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量
    	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;		//选择配置外部中断的0号线和1号线
    	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
    	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
    	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发
    	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设
    	
    	/*NVIC中断分组*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
    																//即抢占优先级范围:0~3,响应优先级范围:0~3
    																//此分组配置在整个工程中仅需调用一次
    																//若有多个中断,可以把此代码放在main函数内,while循环之前
    																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
    	
    	/*NVIC配置*/
    	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//选择配置NVIC的EXTI0线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
    	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
    
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			//选择配置NVIC的EXTI1线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2
    	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
    }
    
    /**
      * 函    数:旋转编码器获取增量值
      * 参    数:无
      * 返 回 值:自上此调用此函数后,旋转编码器的增量值
      */
    int16_t Encoder_Get(void)
    {
    	/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
    	/*在这里,也可以直接返回Encoder_Count
    	  但这样就不是获取增量值的操作方法了
    	  也可以实现功能,只是思路不一样*/
    	int16_t Temp;
    	Temp = Encoder_Count;
    	Encoder_Count = 0;
    	return Temp;
    }
    
    /**
      * 函    数:EXTI0外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI0_IRQHandler(void)
    {
    	if (EXTI_GetITStatus(EXTI_Line0) == SET)		//判断是否是外部中断0号线触发的中断
    	{
    		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
    		{
    			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)		//PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
    			{
    				Encoder_Count --;					//此方向定义为反转,计数变量自减
    			}
    		}
    		EXTI_ClearITPendingBit(EXTI_Line0);			//清除外部中断0号线的中断标志位
    													//中断标志位必须清除
    													//否则中断将连续不断地触发,导致主程序卡死
    	}
    }
    
    /**
      * 函    数:EXTI1外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI1_IRQHandler(void)
    {
    	if (EXTI_GetITStatus(EXTI_Line1) == SET)		//判断是否是外部中断1号线触发的中断
    	{
    		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
    		{
    			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)		//PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
    			{
    				Encoder_Count ++;					//此方向定义为正转,计数变量自增
    			}
    		}
    		EXTI_ClearITPendingBit(EXTI_Line1);			//清除外部中断1号线的中断标志位
    													//中断标志位必须清除
    													//否则中断将连续不断地触发,导致主程序卡死
    	}
    }
    

    八、TIM定时中断 (通用定时器)

  • TIM(Timer)定时器
  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
  • 7c09cd87a63044e9b2c87a4a0076f3f7.png3d04af5e35f6467198a9f735f953c528.png094247dfe59746e987cda980e2d5eb43.pnge4812ad2dea34731b7f92a990e37ec42.png04db887a60c3430a999fabe3cf84ef07.pnga778d3ded8bd471bb2cae222353668ba.png12eed14e85a04bb888471d2c37ad383e.png2c90a63e5eac4d749c007a1e01c601be.pngef5fdf413c54444ab9953f5852145506.pngb8c57d1dd8734867bbc814167f05363e.png

    示例代码:

    main.c     (定时器定时中断)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"
    
    uint16_t Num;			//定义在定时器中断里自增的变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	Timer_Init();		//定时中断初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
    	}
    }
    
    /**
      * 函    数:TIM2中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
    	{
    		Num ++;												//Num变量自增,用于测试定时中断
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
    															//中断标志位必须清除
    															//否则中断将连续不断地触发,导致主程序卡死
    	}
    }
    

    Timer.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:定时中断初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Timer_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
    	
    	/*中断输出配置*/
    	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
    																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
    																//若不清除此标志位,则开启中断后,会立刻进入一次中断
    																//如果不介意此问题,则不清除此标志位也可
    	
    	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
    	
    	/*NVIC中断分组*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
    																//即抢占优先级范围:0~3,响应优先级范围:0~3
    																//此分组配置在整个工程中仅需调用一次
    																//若有多个中断,可以把此代码放在main函数内,while循环之前
    																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
    	
    	/*NVIC配置*/
    	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
    	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
    	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /* 定时器中断函数,可以复制到使用它的地方
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    	{
    		
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    	}
    }
    */
    

    main.c     (定时器外部时钟)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"
    
    uint16_t Num;			//定义在定时器中断里自增的变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	Timer_Init();		//定时中断初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
    	OLED_ShowString(2, 1, "CNT:");			//2行1列显示字符串CNT:
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
    		OLED_ShowNum(2, 5, Timer_GetCounter(), 5);		//不断刷新显示CNT的值
    	}
    }
    
    /**
      * 函    数:TIM2中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
    	{
    		Num ++;												//Num变量自增,用于测试定时中断
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
    															//中断标志位必须清除
    															//否则中断将连续不断地触发,导致主程序卡死
    	}
    }
    

    Timer.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:定时中断初始化
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数配置为外部时钟,定时器相当于计数器
      */
    void Timer_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA0引脚初始化为上拉输入
    	
    	/*外部时钟配置*/
    	TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
    																//选择外部时钟模式2,时钟从TIM_ETR引脚输入
    																//注意TIM2的ETR引脚固定为PA0,无法随意更改
    																//最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;					//计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;				//预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
    	
    	/*中断输出配置*/
    	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
    																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
    																//若不清除此标志位,则开启中断后,会立刻进入一次中断
    																//如果不介意此问题,则不清除此标志位也可
    																
    	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
    	
    	/*NVIC中断分组*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
    																//即抢占优先级范围:0~3,响应优先级范围:0~3
    																//此分组配置在整个工程中仅需调用一次
    																//若有多个中断,可以把此代码放在main函数内,while循环之前
    																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
    	
    	/*NVIC配置*/
    	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
    	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
    	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:返回定时器CNT的值
      * 参    数:无
      * 返 回 值:定时器CNT的值,范围:0~65535
      */
    uint16_t Timer_GetCounter(void)
    {
    	return TIM_GetCounter(TIM2);	//返回定时器TIM2的CNT
    }
    
    /* 定时器中断函数,可以复制到使用它的地方
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    	{
    		
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    	}
    }
    */
    

    九、TIM输出比较

  • OC(Output Compare)输出比较
  • 输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
  • 每个高级定时器和通用定时器都拥有4个输出比较通道
  • 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
  • 879595da707c411e8399de5454ba627a.pngba56697660ef4d10909dad77b0166841.png78854ab5c20b4cd2bc6f0b543a855169.png5579fca0e97f45cabf2915c716cca70d.pngae161fde2d0c4271b5eb1e2d63266c49.png8b09f15ec92f486b8140169851c37d18.png

    十、PWM驱动LED呼吸灯&PWM驱动直流电机

    1d472351b57f437f982288fe43ed0ede.png20c0ce8928a64d7da096814445fffe97.png0db4035a983445539196062755a62eea.pngd39a0c6c951f4cb08ae046112a433440.png

    示例代码:

    main.c      (PWM驱动呼吸灯)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "PWM.h"
    
    uint8_t i;			//定义for循环的变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	PWM_Init();			//PWM初始化
    	
    	while (1)
    	{
    		for (i = 0; i <= 100; i++)
    		{
    			PWM_SetCompare1(i);			//依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
    			Delay_ms(10);				//延时10ms
    		}
    		for (i = 0; i <= 100; i++)
    		{
    			PWM_SetCompare1(100 - i);	//依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
    			Delay_ms(10);				//延时10ms
    		}
    	}
    }
    

    PWM.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:PWM初始化
      * 参    数:无
      * 返 回 值:无
      */
    void PWM_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO重映射*/
    //	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
    //	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
    //	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
    																	//受外设控制的引脚,均需要配置为复用模式		
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    	
    	/*输出比较初始化*/
    	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
    	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值
    																	//则最好执行此函数,给结构体所有成员都赋一个默认值
    																	//避免结构体初值不确定的问题
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
    	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:PWM设置CCR
      * 参    数:Compare 要写入的CCR的值,范围:0~100
      * 返 回 值:无
      * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
      *           占空比Duty = CCR / (ARR + 1)
      */
    void PWM_SetCompare1(uint16_t Compare)
    {
    	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
    }
    

     main.c      (PWM驱动舵机)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Servo.h"
    #include "Key.h"
    
    uint8_t KeyNum;			//定义用于接收键码的变量
    float Angle;			//定义角度变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	Servo_Init();		//舵机初始化
    	Key_Init();			//按键初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Angle:");	//1行1列显示字符串Angle:
    	
    	while (1)
    	{
    		KeyNum = Key_GetNum();			//获取按键键码
    		if (KeyNum == 1)				//按键1按下
    		{
    			Angle += 30;				//角度变量自增30
    			if (Angle > 180)			//角度变量超过180后
    			{
    				Angle = 0;				//角度变量归零
    			}
    		}
    		Servo_SetAngle(Angle);			//设置舵机的角度为角度变量
    		OLED_ShowNum(1, 7, Angle, 3);	//OLED显示角度变量
    	}
    }
    

    PWM.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:PWM初始化
      * 参    数:无
      * 返 回 值:无
      */
    void PWM_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	
    																	//受外设控制的引脚,均需要配置为复用模式
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;				//计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    	
    	/*输出比较初始化*/ 
    	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
    	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
    	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
    	                                                                //避免结构体初值不确定的问题
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
    	TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:PWM设置CCR
      * 参    数:Compare 要写入的CCR的值,范围:0~100
      * 返 回 值:无
      * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
      *           占空比Duty = CCR / (ARR + 1)
      */
    void PWM_SetCompare2(uint16_t Compare)
    {
    	TIM_SetCompare2(TIM2, Compare);		//设置CCR2的值
    }
    

    Servo.c

    #include "stm32f10x.h"                  // Device header
    #include "PWM.h"
    
    /**
      * 函    数:舵机初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Servo_Init(void)
    {
    	PWM_Init();									//初始化舵机的底层PWM
    }
    
    /**
      * 函    数:舵机设置角度
      * 参    数:Angle 要设置的舵机角度,范围:0~180
      * 返 回 值:无
      */
    void Servo_SetAngle(float Angle)
    {
    	PWM_SetCompare2(Angle / 180 * 2000 + 500);	//设置占空比
    												//将角度线性变换,对应到舵机要求的占空比范围上
    }
    

    main.c      (PWM驱动直流电机)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Motor.h"
    #include "Key.h"
    
    uint8_t KeyNum;		//定义用于接收按键键码的变量
    int8_t Speed;		//定义速度变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	Motor_Init();		//直流电机初始化
    	Key_Init();			//按键初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
    	
    	while (1)
    	{
    		KeyNum = Key_GetNum();				//获取按键键码
    		if (KeyNum == 1)					//按键1按下
    		{
    			Speed += 20;					//速度变量自增20
    			if (Speed > 100)				//速度变量超过100后
    			{
    				Speed = -100;				//速度变量变为-100
    											//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位
    											//若出现了此现象,则应避免使用这样的操作
    			}
    		}
    		Motor_SetSpeed(Speed);				//设置直流电机的速度为速度变量
    		OLED_ShowSignedNum(1, 7, Speed, 3);	//OLED显示速度变量
    	}
    }
    

    PWM.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:PWM初始化
      * 参    数:无
      * 返 回 值:无
      */
    void PWM_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA2引脚初始化为复用推挽输出	
    																	//受外设控制的引脚,均需要配置为复用模式
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;                 //计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;               //预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    	
    	/*输出比较初始化*/ 
    	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
    	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
    	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
    	                                                                //避免结构体初值不确定的问题
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
    	TIM_OC3Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:PWM设置CCR
      * 参    数:Compare 要写入的CCR的值,范围:0~100
      * 返 回 值:无
      * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
      *           占空比Duty = CCR / (ARR + 1)
      */
    void PWM_SetCompare3(uint16_t Compare)
    {
    	TIM_SetCompare3(TIM2, Compare);		//设置CCR3的值
    }
    

    Motor.c

    #include "stm32f10x.h"                  // Device header
    #include "PWM.h"
    
    /**
      * 函    数:直流电机初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Motor_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA4和PA5引脚初始化为推挽输出	
    	
    	PWM_Init();													//初始化直流电机的底层PWM
    }
    
    /**
      * 函    数:直流电机设置速度
      * 参    数:Speed 要设置的速度,范围:-100~100
      * 返 回 值:无
      */
    void Motor_SetSpeed(int8_t Speed)
    {
    	if (Speed >= 0)							//如果设置正转的速度值
    	{
    		GPIO_SetBits(GPIOA, GPIO_Pin_4);	//PA4置高电平
    		GPIO_ResetBits(GPIOA, GPIO_Pin_5);	//PA5置低电平,设置方向为正转
    		PWM_SetCompare3(Speed);				//PWM设置为速度值
    	}
    	else									//否则,即设置反转的速度值
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//PA4置低电平
    		GPIO_SetBits(GPIOA, GPIO_Pin_5);	//PA5置高电平,设置方向为反转
    		PWM_SetCompare3(-Speed);			//PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
    	}
    }
    

    十一、输入捕获

  • IC(Input Capture)输入捕获
  • 输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
  • 每个高级定时器和通用定时器都拥有4个输入捕获通道
  • 可配置为PWMI模式,同时测量频率和占空比
  • 可配合主从触发模式,实现硬件全自动测量
  • 1f4196f4ac864c2894f6a6d49702e0fe.png8ca6b9dc97874abdbfb778635470d9d5.png0dd65f85dad049f7a493e34d4b885a77.png5204f2052daa47408f0f8b02dbf8017d.pngf8b4148f575d4a7db5ad2a30377fc7c1.png

    代码示例

    main.c      (输入捕获模式测频率)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "PWM.h"
    #include "IC.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	PWM_Init();			//PWM初始化
    	IC_Init();			//输入捕获初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000Hz
    	
    	/*使用PWM模块提供输入捕获的测试信号*/
    	PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100
    	PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率
    	}
    }
    

    PWM.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:PWM初始化
      * 参    数:无
      * 返 回 值:无
      */
    void PWM_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO重映射*/
    //	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);			//开启AFIO的时钟,重映射必须先开启AFIO的时钟
    //	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);			//将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
    //	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);		//将JTAG引脚失能,作为普通GPIO引脚使用
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		//GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
    																	//受外设控制的引脚,均需要配置为复用模式		
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;					//计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;				//预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    	
    	/*输出比较初始化*/
    	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
    	TIM_OCStructInit(&TIM_OCInitStructure);							//结构体初始化,若结构体没有完整赋值
    																	//则最好执行此函数,给结构体所有成员都赋一个默认值
    																	//避免结构体初值不确定的问题
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//输出极性,选择为高,若选择极性为低,则输出高低电平取反
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//输出使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
    	TIM_OC1Init(TIM2, &TIM_OCInitStructure);						//将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
    }
    
    /**
      * 函    数:PWM设置CCR
      * 参    数:Compare 要写入的CCR的值,范围:0~100
      * 返 回 值:无
      * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
      *           占空比Duty = CCR / (ARR + 1)
      */
    void PWM_SetCompare1(uint16_t Compare)
    {
    	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
    }
    
    /**
      * 函    数:PWM设置PSC
      * 参    数:Prescaler 要写入的PSC的值,范围:0~65535
      * 返 回 值:无
      * 注意事项:PSC和ARR共同决定频率,此函数仅设置PSC的值,并不直接是频率
      *           频率Freq = CK_PSC / (PSC + 1) / (ARR + 1)
      */
    void PWM_SetPrescaler(uint16_t Prescaler)
    {
    	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);		//设置PSC的值
    }
    

    IC.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:输入捕获初始化
      * 参    数:无
      * 返 回 值:无
      */
    void IC_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    	
    	/*输入捕获初始化*/
    	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
    	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
    	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性,选择为上升沿触发捕获
    	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频,选择不分频,每次信号都触发捕获
    	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉,选择直通,不交叉
    	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    	
    	/*选择触发源及从模式*/
    	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
    	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
    																	//即TI1产生上升沿时,会触发CNT归零
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
    }
    
    /**
      * 函    数:获取输入捕获的频率
      * 参    数:无
      * 返 回 值:捕获得到的频率
      */
    uint32_t IC_GetFreq(void)
    {
    	return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
    }
    

    main.c      (PWMI模式测频率占空比)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "PWM.h"
    #include "IC.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	PWM_Init();			//PWM初始化
    	IC_Init();			//输入捕获初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000Hz
    	OLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%
    	
    	/*使用PWM模块提供输入捕获的测试信号*/
    	PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100
    	PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率
    		OLED_ShowNum(2, 6, IC_GetDuty(), 2);	//不断刷新显示输入捕获测得的占空比
    	}
    }
    

    IC.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:输入捕获初始化
      * 参    数:无
      * 返 回 值:无
      */
    void IC_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入
    	
    	/*配置时钟源*/
    	TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    	
    	/*PWMI模式初始化*/
    	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
    	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
    	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性,选择为上升沿触发捕获
    	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频,选择不分频,每次信号都触发捕获
    	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉,选择直通,不交叉
    	TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);						//将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道
    																	//此函数同时会把另一个通道配置为相反的配置,实现PWMI模式
    
    	/*选择触发源及从模式*/
    	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1
    	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位
    																	//即TI1产生上升沿时,会触发CNT归零
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
    }
    
    /**
      * 函    数:获取输入捕获的频率
      * 参    数:无
      * 返 回 值:捕获得到的频率
      */
    uint32_t IC_GetFreq(void)
    {
    	return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
    }
    
    /**
      * 函    数:获取输入捕获的占空比
      * 参    数:无
      * 返 回 值:捕获得到的占空比
      */
    uint32_t IC_GetDuty(void)
    {
    	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);	//占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
    }
    

    十二、编码器接口 

  • Encoder Interface 编码器接口
  • 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
  • 每个高级定时器和通用定时器都拥有1个编码器接口
  • 两个输入引脚借用了输入捕获的通道1和通道2
  • be5fdd23f91a4b27a72a2a4e4f362571.png0ff28b1dc9fa433bb7a549a71db7a657.png9ee5cf1ee23c4788adf9de5167860c43.pngbdf0762d0ec5493ba698666076c4669a.png3e0d83fabd1f40a1b2fc54df427382f6.png

    示例代码

    main.c      (编码器接口测速)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"    //(Timer.c用的定时器中断中的Timer.c)
    #include "Encoder.h"
    
    int16_t Speed;			//定义速度变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();		//OLED初始化
    	Timer_Init();		//定时器初始化
    	Encoder_Init();		//编码器初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
    	
    	while (1)
    	{
    		OLED_ShowSignedNum(1, 7, Speed, 5);	//不断刷新显示编码器测得的最新速度
    	}
    }
    
    /**
      * 函    数:TIM2中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
    	{
    		Speed = Encoder_Get();								//每隔固定时间段读取一次编码器计数增量值,即为速度值
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
    															//中断标志位必须清除
    															//否则中断将连续不断地触发,导致主程序卡死
    	}
    }
    

    Encoder.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:编码器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Encoder_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6和PA7引脚初始化为上拉输入
    	
    	/*时基单元初始化*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;                //预分频器,即PSC的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
    	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    	
    	/*输入捕获初始化*/
    	TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量
    	TIM_ICStructInit(&TIM_ICInitStructure);							//结构体初始化,若结构体没有完整赋值
    																	//则最好执行此函数,给结构体所有成员都赋一个默认值
    																	//避免结构体初值不确定的问题
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1
    	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
    	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;				//选择配置定时器通道2
    	TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动
    	TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    	
    	/*编码器接口配置*/
    	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    																	//配置编码器模式以及两个输入通道是否反相
    																	//注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
    																	//此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
    	
    	/*TIM使能*/
    	TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
    }
    
    /**
      * 函    数:获取编码器的增量值
      * 参    数:无
      * 返 回 值:自上此调用此函数后,编码器的增量值
      */
    int16_t Encoder_Get(void)
    {
    	/*使用Temp变量作为中继,目的是返回CNT后将其清零*/
    	int16_t Temp;
    	Temp = TIM_GetCounter(TIM3);
    	TIM_SetCounter(TIM3, 0);
    	return Temp;
    }
    

    十三、ADC数模转换器

  • ADC(Analog-Digital Converter)模拟-数字转换器
  • ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
  • 12位逐次逼近型ADC,1us转换时间
  • 输入电压范围:0~3.3V,转换结果范围:0~4095
  • 18个输入通道,可测量16个外部和2个内部信号源
  • 规则组和注入组两个转换单元
  • 模拟看门狗自动监测输入电压范围
  • STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道
  • 720b9100143e47109c2cdcc157616304.png4404ec73f9ff488fbf2480c6a4e3308a.pngf928e080bb744a9e80c37442928b3f81.pngf32f30a986324a808240a5a1d5d1220e.png3f0bfa2f480d45b290e8bb0abafc3564.png29951aa63b524809b591085e4282d6a0.png3b34779360c3469490a347d49b4f4fe0.png9eba0899a10c45b79dccafe75675f848.png4142b88ff76742f0b73eea0ff73fbd1d.png7b972f16c3324fbbbaa00d9f9188c045.pngfd30a963ceca461ea3c7e2913efa2a17.pngb8ade0da6d9c4800a45603994d7114bc.pnge0a2231bf4f84951ae9ddb59d70ad698.png

    示例代码

    main.c      (AD单通道)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "AD.h"
    
    uint16_t ADValue;			//定义AD值变量
    float Voltage;				//定义电压变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();			//OLED初始化
    	AD_Init();				//AD初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "ADValue:");
    	OLED_ShowString(2, 1, "Voltage:0.00V");
    	
    	while (1)
    	{
    		ADValue = AD_GetValue();					//获取AD转换的值
    		Voltage = (float)ADValue / 4095 * 3.3;		//将AD值线性变换到0~3.3的范围,表示电压
    		
    		OLED_ShowNum(1, 9, ADValue, 4);				//显示AD值
    		OLED_ShowNum(2, 9, Voltage, 1);				//显示电压值的整数部分
    		OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);	//显示电压值的小数部分
    		
    		Delay_ms(100);			//延时100ms,手动增加一些转换的间隔时间
    	}
    }
    

    AD.c 

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:AD初始化
      * 参    数:无
      * 返 回 值:无
      */
    void AD_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	
    	/*设置ADC时钟*/
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0引脚初始化为模拟输入
    	
    	/*规则组通道配置*/
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);		//规则组序列1的位置,配置为通道0
    	
    	/*ADC初始化*/
    	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
    	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
    	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
    	
    	/*ADC使能*/
    	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
    	
    	/*ADC校准*/
    	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    	ADC_StartCalibration(ADC1);
    	while (ADC_GetCalibrationStatus(ADC1) == SET);
    }
    
    /**
      * 函    数:获取AD转换的值
      * 参    数:无
      * 返 回 值:AD转换的值,范围:0~4095
      */
    uint16_t AD_GetValue(void)
    {
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
    	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
    	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
    }
    

    main.c      (AD多通道)

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "AD.h"
    
    uint16_t AD0, AD1, AD2, AD3;	//定义AD值变量
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();				//OLED初始化
    	AD_Init();					//AD初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "AD0:");
    	OLED_ShowString(2, 1, "AD1:");
    	OLED_ShowString(3, 1, "AD2:");
    	OLED_ShowString(4, 1, "AD3:");
    	
    	while (1)
    	{
    		AD0 = AD_GetValue(ADC_Channel_0);		//单次启动ADC,转换通道0
    		AD1 = AD_GetValue(ADC_Channel_1);		//单次启动ADC,转换通道1
    		AD2 = AD_GetValue(ADC_Channel_2);		//单次启动ADC,转换通道2
    		AD3 = AD_GetValue(ADC_Channel_3);		//单次启动ADC,转换通道3
    		
    		OLED_ShowNum(1, 5, AD0, 4);				//显示通道0的转换结果AD0
    		OLED_ShowNum(2, 5, AD1, 4);				//显示通道1的转换结果AD1
    		OLED_ShowNum(3, 5, AD2, 4);				//显示通道2的转换结果AD2
    		OLED_ShowNum(4, 5, AD3, 4);				//显示通道3的转换结果AD3
    		
    		Delay_ms(100);			//延时100ms,手动增加一些转换的间隔时间
    	}
    }
    

    AD.c

    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:AD初始化
      * 参    数:无
      * 返 回 值:无
      */
    void AD_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	
    	/*设置ADC时钟*/
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
    	
    	/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
    	
    	/*ADC初始化*/
    	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
    	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
    	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
    	
    	/*ADC使能*/
    	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
    	
    	/*ADC校准*/
    	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    	ADC_StartCalibration(ADC1);
    	while (ADC_GetCalibrationStatus(ADC1) == SET);
    }
    
    /**
      * 函    数:获取AD转换的值
      * 参    数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
      * 返 回 值:AD转换的值,范围:0~4095
      */
    uint16_t AD_GetValue(uint8_t ADC_Channel)
    {
    	ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);	//在每次转换前,根据函数形参灵活更改规则组的通道1
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
    	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
    	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
    }
    

    十四、DMA直接存储器存取

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

    类型 起始地址 存储器                         用途
    ROM 0x0800 0000 程序存储器Flash 存储C语言编译后的程序代码
    0x1FFF F000 系统存储器 存储BootLoader,用于串口下载
    0x1FFF F800 选项字节 存储一些独立于程序代码的配置参数
    RAM 0x2000 0000 运行内存SRAM 存储运行过程中的临时变量
    0x4000 0000 外设寄存器 存储各个外设的配置参数
    0xE000 0000 内核外设寄存器 存储内核各个外设的配置参数

    代码示例

    main.c      (DMA数据转运)
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "MyDMA.h"
    
    uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};				//定义测试数组DataA,为数据源
    uint8_t DataB[] = {0, 0, 0, 0};							//定义测试数组DataB,为数据目的地
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();				//OLED初始化
    	
    	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);	//DMA初始化,把源数组和目的数组的地址传入
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "DataA");
    	OLED_ShowString(3, 1, "DataB");
    	
    	/*显示数组的首地址*/
    	OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
    	OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
    		
    	while (1)
    	{
    		DataA[0] ++;		//变换测试数据
    		DataA[1] ++;
    		DataA[2] ++;
    		DataA[3] ++;
    		
    		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
    		OLED_ShowHexNum(2, 4, DataA[1], 2);
    		OLED_ShowHexNum(2, 7, DataA[2], 2);
    		OLED_ShowHexNum(2, 10, DataA[3], 2);
    		OLED_ShowHexNum(4, 1, DataB[0], 2);		//显示数组DataB
    		OLED_ShowHexNum(4, 4, DataB[1], 2);
    		OLED_ShowHexNum(4, 7, DataB[2], 2);
    		OLED_ShowHexNum(4, 10, DataB[3], 2);
    		
    		Delay_ms(1000);		//延时1s,观察转运前的现象
    		
    		MyDMA_Transfer();	//使用DMA转运数组,从DataA转运到DataB
    		
    		OLED_ShowHexNum(2, 1, DataA[0], 2);		//显示数组DataA
    		OLED_ShowHexNum(2, 4, DataA[1], 2);
    		OLED_ShowHexNum(2, 7, DataA[2], 2);
    		OLED_ShowHexNum(2, 10, DataA[3], 2);
    		OLED_ShowHexNum(4, 1, DataB[0], 2);		//显示数组DataB
    		OLED_ShowHexNum(4, 4, DataB[1], 2);
    		OLED_ShowHexNum(4, 7, DataB[2], 2);
    		OLED_ShowHexNum(4, 10, DataB[3], 2);
    
    		Delay_ms(1000);		//延时1s,观察转运后的现象
    	}
    }
    

    MyDMA.c

    #include "stm32f10x.h"                  // Device header
    
    uint16_t MyDMA_Size;					//定义全局变量,用于记住Init函数的Size,供Transfer函数使用
    
    /**
      * 函    数:DMA初始化
      * 参    数:AddrA 原数组的首地址
      * 参    数:AddrB 目的数组的首地址
      * 参    数:Size 转运的数据大小(转运次数)
      * 返 回 值:无
      */
    void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
    {
    	MyDMA_Size = Size;					//将Size写入到全局变量,记住参数Size
    	
    	/*开启时钟*/
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);						//开启DMA的时钟
    	
    	/*DMA初始化*/
    	DMA_InitTypeDef DMA_InitStructure;										//定义结构体变量
    	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;						//外设基地址,给定形参AddrA
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据宽度,选择字节
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			//外设地址自增,选择使能
    	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;							//存储器基地址,给定形参AddrB
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//存储器数据宽度,选择字节
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器地址自增,选择使能
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;						//数据传输方向,选择由外设到存储器
    	DMA_InitStructure.DMA_BufferSize = Size;								//转运的数据大小(转运次数)
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//模式,选择正常模式
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;								//存储器到存储器,选择使能
    	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//优先级,选择中等
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);							//将结构体变量交给DMA_Init,配置DMA1的通道1
    	
    	/*DMA使能*/
    	DMA_Cmd(DMA1_Channel1, DISABLE);	//这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
    }
    
    /**
      * 函    数:启动DMA数据转运
      * 参    数:无
      * 返 回 值:无
      */
    void MyDMA_Transfer(void)
    {
    	DMA_Cmd(DMA1_Channel1, DISABLE);					//DMA失能,在写入传输计数器之前,需要DMA暂停工作
    	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);	//写入传输计数器,指定将要转运的次数
    	DMA_Cmd(DMA1_Channel1, ENABLE);						//DMA使能,开始工作
    	
    	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);	//等待DMA工作完成
    	DMA_ClearFlag(DMA1_FLAG_TC1);						//清除工作完成标志位
    }
    
    main.c      (DMA+AD多通道)
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "AD.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	OLED_Init();				//OLED初始化
    	AD_Init();					//AD初始化
    	
    	/*显示静态字符串*/
    	OLED_ShowString(1, 1, "AD0:");
    	OLED_ShowString(2, 1, "AD1:");
    	OLED_ShowString(3, 1, "AD2:");
    	OLED_ShowString(4, 1, "AD3:");
    	
    	while (1)
    	{
    		OLED_ShowNum(1, 5, AD_Value[0], 4);		//显示转换结果第0个数据
    		OLED_ShowNum(2, 5, AD_Value[1], 4);		//显示转换结果第1个数据
    		OLED_ShowNum(3, 5, AD_Value[2], 4);		//显示转换结果第2个数据
    		OLED_ShowNum(4, 5, AD_Value[3], 4);		//显示转换结果第3个数据
    		
    		Delay_ms(100);							//延时100ms,手动增加一些转换的间隔时间
    	}
    }
    

    AD.c

    #include "stm32f10x.h"                  // Device header
    
    uint16_t AD_Value[4];					//定义用于存放AD转换结果的全局数组
    
    /**
      * 函    数:AD初始化
      * 参    数:无
      * 返 回 值:无
      */
    void AD_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
    	
    	/*设置ADC时钟*/
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
    	
    	/*规则组通道配置*/
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	//规则组序列1的位置,配置为通道0
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);	//规则组序列2的位置,配置为通道1
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);	//规则组序列3的位置,配置为通道2
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);	//规则组序列4的位置,配置为通道3
    	
    	/*ADC初始化*/
    	ADC_InitTypeDef ADC_InitStructure;											//定义结构体变量
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;							//模式,选择独立模式,即单独使用ADC1
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;						//数据对齐,选择右对齐
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;			//外部触发,使用软件触发,不需要外部触发
    	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;							//连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
    	ADC_InitStructure.ADC_ScanConvMode = ENABLE;								//扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
    	ADC_InitStructure.ADC_NbrOfChannel = 4;										//通道数,为4,扫描规则组的前4个通道
    	ADC_Init(ADC1, &ADC_InitStructure);											//将结构体变量交给ADC_Init,配置ADC1
    	
    	/*DMA初始化*/
    	DMA_InitTypeDef DMA_InitStructure;											//定义结构体变量
    	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;				//外设基地址,给定形参AddrA
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据宽度,选择半字,对应16为的ADC数据寄存器
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址自增,选择失能,始终以ADC数据寄存器为源
    	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;					//存储器基地址,给定存放AD转换结果的全局数组AD_Value
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			//存储器数据宽度,选择半字,与源数据宽度对应
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//存储器地址自增,选择使能,每次转运后,数组移到下一个位置
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
    	DMA_InitStructure.DMA_BufferSize = 4;										//转运的数据大小(转运次数),与ADC通道数一致
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;								//模式,选择循环模式,与ADC的连续转换一致
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;								//存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
    	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;						//优先级,选择中等
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);								//将结构体变量交给DMA_Init,配置DMA1的通道1
    	
    	/*DMA和ADC使能*/
    	DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能
    	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
    	ADC_Cmd(ADC1, ENABLE);									//ADC1使能
    	
    	/*ADC校准*/
    	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    	ADC_StartCalibration(ADC1);
    	while (ADC_GetCalibrationStatus(ADC1) == SET);
    	
    	/*ADC触发*/
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
    }
    

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32标准库学习笔记:深入理解与实践

    发表评论