STM32(标准库)学习笔记

2024-03-01 1567阅读

温馨提示:这篇文章已超过424天没有更新,请注意相关的内容是否还可用!

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可以防止中文乱码                                   STM32(标准库)学习笔记

STM32(标准库)学习笔记

系统结构

STM32(标准库)学习笔记

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

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

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

STM32(标准库)学习笔记STM32(标准库)学习笔记

红色是电源相关的引脚,蓝色是最小系统相关的引脚,绿色是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电   压采集、模拟通信协议接收数据等   

                                                                            STM32(标准库)学习笔记STM32(标准库)学习笔记  STM32(标准库)学习笔记           STM32(标准库)学习笔记模拟输入一般 使用ADC的时候配置STM32(标准库)学习笔记P-MOS如果无效就是开漏输出,如果P-MOS和N-MOS都有效就是推挽输出

    STM32(标准库)学习笔记​​​STM32(标准库)学习笔记

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

    STM32(标准库)学习笔记STM32(标准库)学习笔记

    示例代码:  

    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&光敏传感器控制蜂鸣器

    STM32(标准库)学习笔记

    STM32(标准库)学习笔记

    STM32(标准库)学习笔记

    STM32(标准库)学习笔记C语言宏定义

    • 关键字:#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 
          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的另一个专栏里)

            STM32(标准库)学习笔记

            STM32(标准库)学习笔记

            STM32(标准库)学习笔记

            示例代码:

            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 > 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 = 0)
            	{
            		OLED_ShowChar(Line, Column, '+');
            		Number1 = Number;
            	}
            	else
            	{
            		OLED_ShowChar(Line, Column, '-');
            		Number1 = -Number;
            	}
            	for (i = 0; i  
            

            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再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
            • 在中断函数里最好不要执行耗时过长的代码,最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件

              STM32(标准库)学习笔记

              STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

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

              STM32(标准库)学习笔记STM32(标准库)学习笔记

              示例代码:

              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的定时
              • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
              • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

                STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

                示例代码:

                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个通道额外拥有死区生成和互补输出的功能

                  STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

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

                  STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

                  示例代码:

                  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)				//速度变量超过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模式,同时测量频率和占空比
                  • 可配合主从触发模式,实现硬件全自动测量

                    STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

                    代码示例

                    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

                      STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

                      示例代码

                      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个外部输入通道

                        STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

                        示例代码

                        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个通道)

                          存储器映像:

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

                          STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记STM32(标准库)学习笔记

                          代码示例

                          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就可以一直连续不断地工作
                          }
                          
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]