一、前言

  由于各位对于的单片机较为生疏,在此我再仔细介绍一下单片机。
  俗话说,以项目驱动学习,才是学习快速的秘诀
通过一次次的项目才能更加对某一个东西熟悉。
  废话不多说,直接开始做
如果对单片机不熟悉的话,可以看我的另一篇blog(虽然还没更完)

二、所需模块

一个stm32模块,任何型号都可以,因为这个项目所需的资源不是太多,当然了,太老太旧的型号也是不行的。
下图是stm32f103c8t6
在这里插入图片描述
有单片机也要有电源是吧,这里使用了一个12V的电源,因为它比较方便
在这里插入图片描述
有电源也要有处理电源的东西,一般我们使用AMS1117芯片去降压,也用LDO电路和DC-DC电路去升降压,不过有现成的模块会方便一点。以上仅限于直流电,交流电需要变压器,很大的那种或者其他奇奇怪怪的电路。
在这里插入图片描述

这里使用了一个7针的OLED,它可以使用I2C和SPI通信协议进行通信。比较方便、便宜。主要是用来显示灯的挡位的,没有的话也可以,通过观察LED的亮度就可以。
在这里插入图片描述
一个直插式小LED,电压大概是3V左右,电流18mA跟单片机的电流差很多,不过不并联电阻也是可以的,就算炸了也没事,一块钱一大堆,炸了也是给自己的教训。一般的LED不会爆炸,只会融化损毁,除非你的LED与众不同。
在这里插入图片描述
准备好上面这些东西就可以做了。
在这里插入图片描述
是不是还不错,虽然有点丑,不过没关系,能用就行,主要这是我拆了我的小风扇做的。

三、代码

代码一,主函数代码

static u8 title2[5][32] = {
{0x08,0x00,0x29,0x00,0xCA,0x00,0x4C,0xFF,0x78,0x92,0x4C,0x92,0x4A,0x92,0x08,0x92,0x00,0x92,0x7E,0x92,0x42,0x92,0x42,0xFF,0x42,0x00,0x7E,0x00,0x00,0x00,0x00,0x00},/*"智",4*/
{0x10,0x00,0x33,0xFF,0x52,0x48,0x92,0x48,0x12,0x4A,0x52,0x49,0x33,0xFE,0x18,0x00,0x00,0x00,0xFE,0x7E,0x11,0x11,0x11,0x11,0x21,0x21,0x41,0x41,0x07,0x07,0x00,0x00},/*"能",5*/
{0x00,0x10,0x00,0x20,0x00,0xC0,0x07,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0xFF,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x02,0x00,0x01,0x80,0x00,0x70,0x00,0x00},/*"小",6*/
{0x00,0x00,0x02,0x00,0x07,0x00,0x0A,0x7F,0x12,0x42,0x22,0x42,0xC2,0x42,0x02,0x42,0x02,0x42,0x02,0x42,0x12,0x42,0x0A,0x7F,0x06,0x00,0x03,0x00,0x00,0x00,0x00,0x00},/*"台",7*/
{0x01,0x01,0x0E,0x06,0x00,0x18,0xFF,0xE0,0x04,0x10,0x08,0x0C,0x20,0x00,0x20,0x00,0x20,0x02,0x20,0x01,0x3F,0xFE,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x00,0x00},/*"灯",8*/
};

int main(void)
{  
  
  InitSoftware();   //初始化软件相关函数
  InitHardware();   //初始化硬件相关函数
  
  printf("Init System has been finished.\r\n" );  //打印系统状态
    OLEDShowChinese(4,16,Hzk,2,16,0);
    OLEDShowChinese(28,0,title2,5,16,1);
  while(1)
  {  
    CalcPWM(); 
  }
}

是不是非常简单?除了一个OLED的取模。
CalcPWM是用来控制PWM的,以此来控制LED的亮度。这里我使用了一个函数来进行实现。
OLED取模是需要配合自己的OLED代码的,我这里使用了顺向逐列式,当然了,也可以使用其他取模方法。自定义格式一定要使用c51除非有特殊说明。最好是使用十六进制,因为十六进制更加符合OLED屏幕的排列方式。
更多的OLED知识需要自行寻找。
一般我们使用PCtoLCD去取模,不过也可以使用其他软件,在百度上搜一个也可以。(我会上传)
在这里插入图片描述

在这里插入图片描述

代码二、按键
  因为我使用的是矩阵按键,且较为便宜,因此,不太好用,不过使用开发板的话是可以避免的,可以加一个上拉或下拉电阻,再加一个电容滤波,最后得到的按键信号是较为稳定的。
  这里使用44的矩阵按键,就是4行4列等于16按键,是不是十分巧妙。因此只需要8根线就可以控制16个按键了。
代码如下:

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "SysTick.h"
#include "LED.h"
#include "KEY.h"

	int key_val = -1;

void InitKeyBoard(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; 
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}	

*/
u16 keyboard_scan(void)
{
	u8 temp = 0;

	
	GPIO_ResetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);			//拉低行线
	DelayNms(10);

	temp=(GPIO_ReadInputData(GPIOB) >> 8)&0xff;	
	
	//没有按键按下时扫描
	if (temp == 0xf0) 
	{
			DelayNms(50); 
			GPIO_ResetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11);			//拉低行线
			DelayNus(10);
			temp=(GPIO_ReadInputData(GPIOB) >> 8)&0xff;	
		
			if (temp != 0xf0) //按键按下时,对键值进行赋值
			{
				//第一行
				GPIO_Write(GPIOB,0);
				DelayNms(5);
				GPIO_Write(GPIOB,(uint16_t)(0xFE << 8)); 
				
				if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) != 0XF0)
				{
						DelayNms(20);//消抖

						if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) != 0XF0)
						{
								temp=((GPIO_ReadInputData(GPIOB) >> 8) & 0XFE);		//对列进行扫描
								switch(temp)
								{
										case 0xEE:  key_val = 1;   break;

										case 0xDE:  key_val = 2;   break;

										case 0xBE:  key_val = 3;   break;

										case 0x7E:  key_val = 4;   break;

										default:    key_val = -1;   break;
								}
						}
				}
				
				//第二行
				GPIO_Write(GPIOB,0);
				DelayNms(5);
				GPIO_Write(GPIOB,(uint16_t)(0xFD << 8));
				
				if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0)!= 0XF0)
				{
						DelayNms(20);

						if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) != 0XF0)
						{
								temp=((GPIO_ReadInputData(GPIOB) >> 8) & 0XFD);
								switch(temp)
								{
										case 0xED:  key_val = 5;   break;

										case 0xDD:  key_val = 6;   break;

										case 0xBD:  key_val = 7;   break;

										case 0x7D:  key_val = 8;   break;

										default:    key_val = -1;   break;
								}
						}
				}
				
				//第三行
				GPIO_Write(GPIOB,0);
				DelayNms(5);
				GPIO_Write(GPIOB,(uint16_t)(0xFB << 8));
				
				if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) != 0XF0)
				{
						DelayNms(20);

						if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) != 0XF0)
						{
								temp=((GPIO_ReadInputData(GPIOB) >> 8) & 0XFB);
								switch(temp)
								{
										case 0xEB:  key_val = 9;   break;

										case 0xDB:  key_val = 10;   break;

										case 0xBB:  key_val = 11;   break;

										case 0x7B:  key_val = 12;   break;

										default:    key_val = -1;   break;
								}
						}
				}
				
				//第四行
				GPIO_Write(GPIOB,0);
				DelayNms(5);
				GPIO_Write(GPIOB,(uint16_t)(0xF7 << 8));
				
				if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) !=0XF0)
				{
						DelayNms(20);

						if(((GPIO_ReadInputData(GPIOB) >> 8) & 0XF0) != 0XF0)
						{
								temp=((GPIO_ReadInputData(GPIOB) >> 8) & 0XF7);
								switch(temp)
								{
										case 0xE7:  key_val = 13;   break;

										case 0xD7:  key_val = 14;   break;

										case 0xB7:  key_val = 15;   break;

										case 0x77:  key_val = 16;   break;

										default:    key_val = -1;   break;
								}
						}
					}
				}
			}
	
	return key_val;

}

这个代码可以自己琢磨一下,还是非常巧妙的。
代码三

/*********************************************************************************************************
* 模块名称:OLED.c   
* 摘    要:OLED显示屏模块,4线串行接口,CS、DC、SCK、DIN、RES
* 当前版本:1.0.0
* 作    者:SZLY(COPYRIGHT 2018 - 2020 SZLY. All rights reserved.)
* 完成日期:2020年01月01日
* 内    容:
* 注    意:OLED的显存
            存放格式如下.
            [0]0 1 2 3 ... 127
            [1]0 1 2 3 ... 127
            [2]0 1 2 3 ... 127
            [3]0 1 2 3 ... 127
            [4]0 1 2 3 ... 127
            [5]0 1 2 3 ... 127
            [6]0 1 2 3 ... 127
            [7]0 1 2 3 ... 127                                                                   
**********************************************************************************************************
* 取代版本:
* 作    者:
* 完成日期: 
* 修改内容:
* 修改文件: 
*********************************************************************************************************/

/*********************************************************************************************************
*                                              包含头文件
*********************************************************************************************************/
#include "OLED.h"
#include "stm32f10x_conf.h"
#include "OLEDFont.h"
#include "SysTick.h"
#include "DelayNms.h"
/*********************************************************************************************************
*                                              宏定义
*********************************************************************************************************/  		   
#define OLED_CMD    0 //命令
#define OLED_DATA   1 //数据

//OLED端口定义  
#define CLR_OLED_CS()   GPIO_ResetBits(GPIOA, GPIO_Pin_4)  //CS,片选
#define SET_OLED_CS()   GPIO_SetBits  (GPIOA, GPIO_Pin_4)

#define CLR_OLED_RES()  GPIO_ResetBits(GPIOA, GPIO_Pin_6)  //RES,复位
#define SET_OLED_RES()  GPIO_SetBits  (GPIOA, GPIO_Pin_6)

#define CLR_OLED_DC()   GPIO_ResetBits(GPIOB, GPIO_Pin_11)   //DC,命令数据标志(0-命令/1-数据)
#define SET_OLED_DC()   GPIO_SetBits  (GPIOB, GPIO_Pin_11)
 
#define CLR_OLED_SCK() GPIO_ResetBits(GPIOA, GPIO_Pin_5)   //SCK,时钟
#define SET_OLED_SCK() GPIO_SetBits  (GPIOA, GPIO_Pin_5)

#define CLR_OLED_DIN() GPIO_ResetBits(GPIOA, GPIO_Pin_7)   //DIN,数据
#define SET_OLED_DIN() GPIO_SetBits  (GPIOA, GPIO_Pin_7)

/*********************************************************************************************************
*                                              枚举结构体定义
*********************************************************************************************************/	 

/*********************************************************************************************************
*                                              内部变量
*********************************************************************************************************/
static  u8  s_arrOLEDGRAM[132][8];    //OLED显存缓冲区
 
/*********************************************************************************************************
*                                              内部函数声明
*********************************************************************************************************/ 
static  void  ConfigOLEDGPIO(void);             //配置OLED的GPIO
static  void  ConfigOLEDReg(void);              //配置OLED的SSD1306寄存器
                                                                                                
static  u32   CalcPow(u8 m, u8 n);              //计算m的n次方

/*********************************************************************************************************
*                                              内部函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称:ConfigOLEDGPIO 
* 函数功能:配置OLED的GPIO 
* 输入参数:void 
* 输出参数:void
* 返 回 值:void 
* 创建日期:2018年01月01日
* 注    意:PB12(OLED_CS)、PB14(OLED_RES)、PB11(OLED_DC)、PB13(OLED_SCK)、PB15(OLED_DIN)
*********************************************************************************************************/
static  void  ConfigOLEDGPIO(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
    
  //使能RCC相关时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //使能GPIOC的时钟
  
  
  //配置PB13(OLED_SCK)
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_5;          //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOA, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_SetBits(GPIOA, GPIO_Pin_5);                     //设置初始状态为高电平

  //配置PB15(OLED_DIN)
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_7;          //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOA, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_SetBits(GPIOA, GPIO_Pin_7);                     //设置初始状态为高电平

  //配置PB14(OLED_RES)
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;          //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOA, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_SetBits(GPIOA, GPIO_Pin_6);                     //设置初始状态为高电平

  //配置PB12(OLED_CS)
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4;          //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOA, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_SetBits(GPIOA, GPIO_Pin_4);                     //设置初始状态为高电平

  //配置PC3(OLED_DC)
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;           //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOB, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_SetBits(GPIOB, GPIO_Pin_11);                      //设置初始状态为高电平
  
  //配置Vcc
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0;           //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOB, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_SetBits(GPIOB, GPIO_Pin_0);                      //设置初始状态为高电平
  
   //配置Gnd
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;           //设置引脚
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;     //设置模式  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //设置I/O输出速度
  GPIO_Init(GPIOB, &GPIO_InitStructure);                //根据参数初始化GPIO
  GPIO_ResetBits(GPIOB, GPIO_Pin_1);                      //设置初始状态为高电平
}

/*********************************************************************************************************
* 函数名称:ConfigOLEDReg 
* 函数功能:配置OLED的寄存器 
* 输入参数:void 
* 输出参数:void
* 返 回 值:void 
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
static  void  ConfigOLEDReg( void )
{
  OLEDWriteByte(0xAE, OLED_CMD); //关闭显示
  
  OLEDWriteByte(0xD5, OLED_CMD); //设置时钟分频因子,振荡频率
  OLEDWriteByte(0x50, OLED_CMD); //[3:0]为分频因子,[7:4]为振荡频率
  
  OLEDWriteByte(0xA8, OLED_CMD); //设置驱动路数
  OLEDWriteByte(0x3F, OLED_CMD); //默认0x3F(1/64) 
  
  OLEDWriteByte(0xD3, OLED_CMD); //设置显示偏移
  OLEDWriteByte(0x00, OLED_CMD); //默认为0

  OLEDWriteByte(0x40, OLED_CMD); //设置显示开始行,[5:0]为行数
  
  OLEDWriteByte(0x8D, OLED_CMD); //设置电荷泵
  OLEDWriteByte(0x14, OLED_CMD); //bit2用于设置开启(1)/关闭(0)
  
  OLEDWriteByte(0x20, OLED_CMD); //设置内存地址模式
  OLEDWriteByte(0x02, OLED_CMD); //[1:0],00-列地址模式,01-行地址模式,10-页地址模式(默认值)
  
  OLEDWriteByte(0xA1, OLED_CMD); //设置段重定义,bit0为0,列地址0->SEG0,bit0为1,列地址0->SEG127
  
  OLEDWriteByte(0xC0, OLED_CMD); //设置COM扫描方向,bit3为0,普通模式,bit3为1,重定义模式
  
  OLEDWriteByte(0xDA, OLED_CMD); //设置COM硬件引脚配置
  OLEDWriteByte(0x12, OLED_CMD); //[5:4]为硬件引脚配置信息
  
  OLEDWriteByte(0x81, OLED_CMD); //设置对比度
  OLEDWriteByte(0xEF, OLED_CMD); //1~255,默认为0x7F(亮度设置,越大越亮)
  
  OLEDWriteByte(0xD9, OLED_CMD); //设置预充电周期
  OLEDWriteByte(0xf1, OLED_CMD); //[3:0]为PHASE1,[7:4]为PHASE2
  
  OLEDWriteByte(0xDB, OLED_CMD); //设置VCOMH电压倍率
  OLEDWriteByte(0x00, OLED_CMD); //[6:4],000-0.65*vcc,001-0.77*vcc,011-0.83*vcc

  OLEDWriteByte(0xA4, OLED_CMD); //全局显示开启,bit0为1,开启,bit0为0,关闭
  
  OLEDWriteByte(0xA6, OLED_CMD); //设置显示方式,bit0为1,反相显示,bit0为0,正常显示 
  
  OLEDWriteByte(0xAF, OLED_CMD); //开启显示
  

}

/*********************************************************************************************************
* 函数名称:CalcPow
* 函数功能:计算m的n次方
* 输入参数:m,n 
* 输出参数:void
* 返 回 值:m的n次方结果 
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
static  u32 CalcPow(u8 m, u8 n)
{
  u32 result = 1;     //定义用来存放结果的变量      
                      
  while(n--)          //随着每次循环,n递减,直至为0
  {                   
    result *= m;      //循环n次,相当于n个m相乘
  }                   
                      
  return result;      //返回m的n次幂的值
}
 
/*********************************************************************************************************
*                                              API函数实现
*********************************************************************************************************/	
/*********************************************************************************************************
* 函数名称:InitOLED
* 函数功能:初始化OLED模块
* 输入参数:void 
* 输出参数:void
* 返 回 值:void 
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/   
void  InitOLED(void)
{ 
  ConfigOLEDGPIO();     //配置OLED的GPIO
  
  CLR_OLED_RES();
  DelayNms(10);  
  SET_OLED_RES();       //RES引脚务必拉高
  DelayNms(10);
  
  ConfigOLEDReg();      //配置OLED的寄存器
  
  OLEDClear();          //清除OLED屏内容
  OLEDRefreshGRAM();
}  

/*********************************************************************************************************
* 函数名称:OLEDDisplayOn
* 函数功能:开启OLED显示
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/   
void  OLEDDisplayOn( void )
{
  //打开关闭电荷泵,第一个字节为命令字,0x8D,第二个字节设置值,0x10-关闭电荷泵,0x14-打开电荷泵
  OLEDWriteByte(0x8D, OLED_CMD);  //第一个字节0x8D为命令
  OLEDWriteByte(0x14, OLED_CMD);  //0x14-打开电荷泵

  //设置显示开关,0xAE-关闭显示,0xAF-开启显示
  OLEDWriteByte(0xAF, OLED_CMD);  //开启显示
}

/*********************************************************************************************************
* 函数名称:OLEDDisplayOff
* 函数功能:关闭OLED显示
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018-01-01
* 注    意:
*********************************************************************************************************/ 
void  OLEDDisplayOff( void )
{
  //打开关闭电荷泵,第一个字节为命令字,0x8D,第二个字节设置值,0x10-关闭电荷泵,0x14-打开电荷泵
  OLEDWriteByte(0x8D, OLED_CMD);  //第一个字节为命令字,0x8D
  OLEDWriteByte(0x10, OLED_CMD);  //0x10-关闭电荷泵

  //设置显示开关,0xAE-关闭显示,0xAF-开启显示
  OLEDWriteByte(0xAE, OLED_CMD);  //关闭显示
}

/*********************************************************************************************************
* 函数名称:OLEDRefreshGRAM
* 函数功能:将STM32的GRAM写入到SSD1306的GRAM 
* 输入参数:void 
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void  OLEDRefreshGRAM(void)
{
  u8 i;
  u8 n;
                                          
  for(i = 0; i < 8; i++)                  //遍历每一页
  {                                       
    OLEDWriteByte(0xb0 + i, OLED_CMD);    //设置页地址(0~7)
    OLEDWriteByte(0x00, OLED_CMD);        //设置显示位置—列低地址
    OLEDWriteByte(0x10, OLED_CMD);        //设置显示位置—列高地址 
    for(n = 0; n < 132; n++)              //遍历每一列
    {
      //通过循环将STM32的GRAM写入到SSD1306的GRAM
      OLEDWriteByte(s_arrOLEDGRAM[n][i], OLED_DATA); 
    }
  }   
}

/*********************************************************************************************************
* 函数名称:OLEDClear
* 函数功能:清屏函数,清完屏整个屏幕是黑色的,和没点亮一样
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/  
void  OLEDClear(void)  
{  
  u8 i;
  u8 n;
                                  
  for(i = 0; i < 8; i++)          //遍历每一页
  {                               
    for(n = 0; n < 132; n++)      //遍历每一列
    {                             
      s_arrOLEDGRAM[n][i] = 0x00;   //将指定点清零
    }                             
  }                               
}


/*********************************************************************************************************
* 函数名称:OLEDWriteByte
* 函数功能:向SSD1306写入一个字节
* 输入参数:dat为要写入的数据或命令,cmd为数据/命令标志,0-命令,1-数据
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void  OLEDWriteByte(u8 dat, u8 cmd)
{
  i16 i;
                                
  //判断要写入数据还是写入命令    
  if(OLED_CMD == cmd)         //如果标志cmd为传入命令时
  {                             
    CLR_OLED_DC();            //DC输出低电平用来读写命令  
  }                             
  else if(OLED_DATA == cmd)   //如果标志cmd为传入数据时
  {                             
    SET_OLED_DC();            //DC输出高电平用来读写数据  
  }                             
                                
  CLR_OLED_CS();              //CS输出低电平为写入数据或命令作准备
                                
  for(i = 0; i < 8; i++)      //循环8次,从高到低取出要写入的数据或命令的8个bit
  {                             
    CLR_OLED_SCK();           //SCK输出低电平为写入数据作准备
                                
    if(dat & 0x80)            //判断要写入的数据或命令的最高位是1还是0
    {                           
      SET_OLED_DIN();         //要写入的数据或命令的最高位是1,DIN输出高电平表示1
    }                           
    else                        
    {                           
      CLR_OLED_DIN();         //要写入的数据或命令的最高位是0,DIN输出低电平表示0
    }                           
    SET_OLED_SCK();           //SCK输出高电平,DIN的状态不再变化,此时写入数据线的数据
                                
    dat <<= 1;                //左移一位,次高位移到最高位
  }                             
                                
  SET_OLED_CS();              //OLED的CS输出高电平,不再写入数据或命令  
  SET_OLED_DC();              //OLED的DC输出高电平
} 

/*********************************************************************************************************
* 函数名称:OLEDDrawPoint
* 函数功能:在指定位置画点
* 输入参数:x取值范围为0~127,y取值范围为0~63,t为1表示填充,t为0表示清空
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:(1)x-y轴体系的原点坐标在屏幕左上角;
*           (2)物理显示的y与SSD1306显存的COMn的n相加为63,当然,屏幕的行号(按字节)与SSD1306的行号(按
*                字节)相加为7。
*********************************************************************************************************/			   
void  OLEDDrawPoint(u8 x, u8 y, u8 t)
{
  u8 pos;                           //存放点所在的页数
  u8 bx;                            //存放点所在的屏幕的行号
  u8 temp = 0;                      //用来存放画点位置相对于字节的位
                                    
  if(x > 127 || y > 63)             //如果指定位置超过额定范围
  {                                 
    return;                         //返回空,函数结束
  }                                 
                                    
  pos = 7 - y / 8;                  //求指定位置所在页数
  bx = y % 8;                       //求指定位置在上面求出页数中的行号
  temp = 1 << (7 - bx);             //(7-bx)求出相应SSD1306的行号,并在字节中相应的位置为1
                                    
  if(t)                             //判断填充标志为1还是0
  {                                 
    s_arrOLEDGRAM[x][pos] |= temp;  //如果填充标志为1,指定点填充
  }                                 
  else                              
  {                                 
    s_arrOLEDGRAM[x][pos] &= ~temp; //如果填充标志为0,指定点清空  
  }
}

/*********************************************************************************************************
* 函数名称:OLEDShowChar
* 函数功能:在指定位置显示一个字符
* 输入参数:x取值范围为0~127,y取值范围为0~63,chr为待显示的字符,size用于选择字体(16/12),mode为取模方式
*           mode为0反白显示,mode为1正常显示
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:有6种取模方式
*********************************************************************************************************/
void  OLEDShowChar(u8 x, u8 y, u8 chr, u8 size, u8 mode)
{ 
  u8  temp;                         //用来存放字符顺向逐列式的相对位置
  u8  t1;                           //循环计数器1
  u8  t2;                           //循环计数器2
  u8  y0 = y;                       //当前操作的行数
                                      
  chr = chr - ' ';                  //得到相对于空格(ASCII为0x20)的偏移值,求出要chr在数组中的索引
                                      
  for(t1 = 0; t1 < size; t1++)      //循环逐列显示
  {                                   
    if(size == 12)                  //判断字号大小,选择相对的顺向逐列式
    {                                 
      temp = g_iASCII1206[chr][t1]; //取出字符在g_iASCII1206数组中的第t1列
    }                                 
    else                              
    {                                 
      temp = g_iASCII1608[chr][t1]; //取出字符在g_iASCII1608数组中的第t1列                   
    }                                 
                                      
    for(t2 = 0; t2 < 8; t2++)       //在一个字符的第t2列的横向范围(8个像素)内显示点
    {                                 
      if(temp & 0x80)               //取出temp的最高位,并判断为0还是1
      {                             
        OLEDDrawPoint(x, y, mode);  //如果temp的最高位为1填充指定位置的点
      }                               
      else                            
      {                               
        OLEDDrawPoint(x, y, !mode); //如果temp的最高位为0清除指定位置的点
      }                               
                                      
      temp <<= 1;                   //左移一位,次高位移到最高位
      y++;                          //进入下一行
                                    
      if((y - y0) == size)          //如果显示完一列
      {                               
        y = y0;                     //行号回到原来的位置
        x++;                        //进入下一列
        break;                      //跳出上面带#的循环
      }
    } 
  } 
}

/*********************************************************************************************************
* 函数名称:OLEDShowNum
* 函数功能:显示数字
* 输入参数:x-y为起点坐标,len为数字的位数,size为字体大小,mode为模式(0-填充模式,1-叠加模式)
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:num:数值(0~4294967295)
*********************************************************************************************************/	 		  
void  OLEDShowNum(u8 x, u8 y, u32 num, u8 len, u8 size)
{
  u8 t;                                                       //循环计数器
  u8 temp;                                                    //用来存放要显示数字的各个位
  u8 enshow = 0;                                              //区分0是否为高位0标志位
                                                                
  for(t = 0; t < len; t++)                                      
  {                                                             
    temp = (num / CalcPow(10, len - t - 1) ) % 10;            //按从高到低取出要显示数字的各个位,存到temp中
    if(enshow == 0 && t < (len - 1))                          //如果标记enshow为0并且还未取到最后一位
    {                                                           
      if(temp == 0 )                                          //如果temp等于0
      {                                                         
        OLEDShowChar(x + (size / 2) * t, y, '0', size, 1);    //此时的0在高位,用空格替代
        continue;                                             //提前结束本次循环,进入下一次循环
      }                                                         
      else                                                      
      {                                                         
        enshow = 1;                                           //否则将标记enshow置为1
      }                                                                                                                        
    }                                                           
    OLEDShowChar(x + (size / 2) * t, y, temp + '0', size, 1); //在指定位置显示得到的数字
  }
}

/*********************************************************************************************************
* 函数名称:OLEDShowString
* 函数功能:显示字符串
* 输入参数:x-y为起点坐标,p为字符串起始地址
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:用16号字体显示
*********************************************************************************************************/
void  OLEDShowString(u8 x, u8 y, const u8* p)
{
#define MAX_CHAR_POSX 122             //OLED屏幕横向的最大范围
#define MAX_CHAR_POSY 58              //OLED屏幕纵向的最大范围
                                      
  while(*p != '\0')                   //指针不等于结束符时,循环进入
  {                                   
    if(x > MAX_CHAR_POSX)             //如果x超出指定最大范围,x赋值为0
    {                                 
      x  = 0;                         
      y += 16;                        //显示到下一行左端
    }                                 
                                      
    if(y > MAX_CHAR_POSY)             //如果y超出指定最大范围,x和y均赋值为0
    {                                 
      y = x = 0;                      //清除OLED屏幕内容
      OLEDClear();                    //显示到OLED屏幕左上角
      OLEDRefreshGRAM();
    }                                 
                                      
    OLEDShowChar(x, y, *p, 16, 1);    //指定位置显示一个字符
                                      
    x += 8;                           //一个字符横向占8个像素点
    p++;                              //指针指向下一个字符
  }  
}

/*********************************************************************************************************
* 函数名称:OLEDShowChinese
* 函数功能:显示汉字
* 输入参数:x-y为起点坐标,p为字符串起始地址
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:用16号字体显示
*********************************************************************************************************/
void OLEDShowChinese(u8 x,u8 y,u8 chinese[][32],u8 chr,u8 size,u8 mode)
{
  u8 temp;      //用来存放字符顺向逐列式的相对位置
  u8 t1;        //循环计数器1
  u8 t2;        //循环计数器2
  u8 t3;
  u8 y0=y;      //当前操作的行数
  for(t3=0;t3<chr;t3++)
  {
    for(t1=0;t1<size*2;t1++)            //循行逐列显示
    {
      temp=chinese[t3][t1];
    
      for(t2=0;t2<8;t2++)             //在一个字符的第t2列的横向范围(8像素)内显示点
      {
        if(temp&0x80)                 //取出temp的最高位,并判断是0还是1
        {
          OLEDDrawPoint(x,y,mode);    //如果temp最高位是1,填充指定位置的点
        }
        else
        { 
          OLEDDrawPoint(x,y,!mode);   //如果temp最高位是0,清除指定位置的点
        }
        temp<<=1;                     //左移一位,次高位移到最高位
        y++;                          //进入下一行
      
        if((y-y0)==size)              //如果显示完一列
        {
          y=y0;                       //行号回到原来的位置
          x++;                        //进入下一列
          break;                      //跳出循环
        }
      }
    }
  }
  //OLEDRefreshGRAM();
}

/*********************************************************************************************************
* 函数名称:OLEDShowPicture
* 函数功能:显示32*32图片
* 输入参数:x-y为起点坐标,
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:用16号字体显示
*********************************************************************************************************/
void  OLEDShowPicture(u8 x,u8 y,u8 picture[],u8 mode)
{
  u8 i=0;
  u8 t1;
  u8 t2;
  u8 t3;
  u8 temp;
  for(t1=x ;t1<x+32; t1++)
  {
    for(t2=0 ;t2<4 ;t2++)
    {
      temp = picture[i];
      for(t3=0 ;t3<8 ;t3++)
      {
        if(temp&0x80)                 //取出temp的最高位,并判断是0还是1
        {
          OLEDDrawPoint(t1,y+t2*8+t3,mode);    //如果temp最高位是1,填充指定位置的点
        }
        else
        { 
          OLEDDrawPoint(t1,y+t2*8+t3,!mode);   //如果temp最高位是0,清除指定位置的点
        }
        temp<<=1;                     //左移一位,次高位移到最高位
      }
      i++;
    }
  }
}

OLED这里没啥好说的,都是固定的东西,改一改就适配了,只适配SPI通信的OLED
代码四、

/*********************************************************************************************************
* 模块名称:CalcPWM.c
* 摘    要:CalcPWM模块
* 当前版本:1.0.0
* 作    者:SZLY(COPYRIGHT 2018 - 2020 SZLY. All rights reserved.)
* 完成日期:2020年01月01日 
* 内    容:
* 注    意:                                                                  
**********************************************************************************************************
* 取代版本:
* 作    者:
* 完成日期:
* 修改内容:
* 修改文件:
*********************************************************************************************************/

/*********************************************************************************************************
*                                              包含头文件
*********************************************************************************************************/
#include "LED.h"
#include "stm32f10x_conf.h"
#include "PWM.h"
#include "KEY.h"
#include "ADC.h"
#include "OLED.h"
#include "rang.h"
#include "SysTick.h"
#include "bsp_i2c.h"
/*********************************************************************************************************
*                                              宏定义
*********************************************************************************************************/

/*********************************************************************************************************
*                                              枚举结构体定义
*********************************************************************************************************/

/*********************************************************************************************************
*                                              内部变量
*********************************************************************************************************/
extern  uint16_t key_val;
static u8 num;
static u8 num2;
static u8 key = 0;

static u8 number[7][32] = {
  
{0x08,0x20,0x30,0x20,0xA0,0x40,0xAA,0x50,0xAA,0x90,0xAA,0x94,0xA1,0x54,0xFE,0x32,0xA1,0x12,0xAA,0x95,0xAA,0x98,0xAA,0x40,0xA0,0x40,0x28,0x20,0x30,0x20,0x00,0x00},/*"零",0*/
{0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00},/*"一",0*/
{0x00,0x08,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x00,0x08,0x00,0x08,0x00,0x00},/*"二",1*/
{0x00,0x04,0x20,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x21,0x04,0x20,0x04,0x00,0x04,0x00,0x00},/*"三",2*/
{0x00,0x00,0x3F,0xFE,0x20,0x14,0x20,0x24,0x20,0xC4,0x3F,0x04,0x20,0x04,0x20,0x04,0x20,0x04,0x3F,0x84,0x20,0x44,0x20,0x44,0x20,0x44,0x3F,0xFE,0x00,0x00,0x00,0x00},/*"四",3*/
{0x00,0x02,0x40,0x02,0x42,0x02,0x42,0x02,0x42,0x1E,0x43,0xE2,0x7E,0x02,0x42,0x02,0x42,0x02,0x42,0x02,0x42,0x02,0x43,0xFE,0x40,0x02,0x40,0x02,0x00,0x02,0x00,0x00},/*"五",4*/

};


static u8 mode[6][32] = {
  
{0x00,0x40,0x00,0x40,0x24,0x40,0x24,0x40,0x24,0x40,0x24,0x42,0x24,0x41,0x3F,0xFE,0x44,0x40,0x44,0x40,0x44,0x40,0xC4,0x40,0x44,0x40,0x00,0x40,0x00,0x40,0x00,0x00},/*"手",0*/
{0x02,0x08,0x22,0x3C,0x23,0xC8,0x22,0x08,0x22,0x28,0x22,0x1D,0x02,0x02,0x08,0x0C,0x08,0x70,0xFF,0x80,0x08,0x02,0x08,0x01,0x08,0x02,0x0F,0xFC,0x00,0x00,0x00,0x00},/*"动",1*/
{0x02,0x00,0x42,0x00,0x33,0xFE,0x00,0x04,0x00,0x08,0x41,0x00,0x49,0x3F,0x49,0x22,0x4F,0x22,0x79,0x22,0x49,0x22,0x49,0x22,0x4F,0x3F,0x41,0x00,0x01,0x00,0x00,0x00},/*"语",0*/
{0x02,0x00,0x02,0x00,0x22,0x00,0x22,0xFF,0x2A,0x92,0x26,0x92,0xA2,0x92,0x62,0x92,0x22,0x92,0x26,0x92,0x2A,0x92,0x22,0xFF,0x22,0x00,0x02,0x00,0x02,0x00,0x00,0x00},/*"音",1*/
{0x08,0x20,0x06,0x20,0x40,0x7E,0x31,0x80,0x00,0x02,0x00,0x7E,0x7F,0x42,0x49,0x42,0x49,0x7E,0x49,0x42,0x49,0x7E,0x49,0x42,0x7F,0x42,0x00,0x7E,0x00,0x02,0x00,0x00},/*"温",2*/
{0x08,0x40,0x08,0x42,0x08,0x81,0xFF,0xFE,0x09,0x00,0x04,0x02,0x19,0x02,0x12,0x42,0x14,0x42,0x90,0x42,0x70,0x7E,0x14,0x42,0x12,0x42,0x15,0x42,0x18,0x02,0x00,0x00},/*"控",3*/

};

// ADC1转换的电压值通过MDA方式传到SRAM
extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL];

// 局部变量,用于保存转换计算后的电压值 	 
float ADC_ConvertedValueLocal[NOFCHANEL];  

float length; //测量的距离

extern uint32_t  H1;  //Humility
extern uint32_t  T1;  //Temperature
/*********************************************************************************************************
*                                              内部函数声明
*********************************************************************************************************/
//static void voiceCheck(void);                //语音控制
static void keyCheck(void);                  //按键控制
static void rangeCheck(void);                //测距控制
static void TemparetureCheck(void);          //温度控制
static void PWMControl(u8 number);           //pwm的控制
/*********************************************************************************************************
*                                              内部函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称:voiceCheck
* 函数功能:语音控制 
* 输入参数:void 
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
//static void voiceCheck(void)
//{
//  ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*3.3;
//	ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*3.3;
//  ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*3.3;

//  if(ADC_ConvertedValueLocal[0] > 1.6 && ADC_ConvertedValueLocal[1] < 1.6 && ADC_ConvertedValueLocal[2] < 1.6)         /*001  一档*/
//  {
//    key = 1;
//    num  = 1;
//  }
//  else if(ADC_ConvertedValueLocal[0] < 1.6 && ADC_ConvertedValueLocal[1] > 1.6 && ADC_ConvertedValueLocal[2] < 1.6)     /*010  二挡*/
//  {
//    key = 1;
//    num = 2;
//  }
//    else if(ADC_ConvertedValueLocal[0] > 1.6 && ADC_ConvertedValueLocal[1] > 1.6 && ADC_ConvertedValueLocal[2] < 1.6)   /*011  三档*/
//  {
//    key = 1;
//    num = 3;
//  }
//    else if(ADC_ConvertedValueLocal[0] < 1.6 && ADC_ConvertedValueLocal[1] < 1.6 && ADC_ConvertedValueLocal[2] > 1.6)   /*100  四挡*/
//  {
//    key = 1;
//    num = 4;
//  }
//    else if(ADC_ConvertedValueLocal[0] > 1.6 && ADC_ConvertedValueLocal[1] < 1.6 && ADC_ConvertedValueLocal[2] > 1.6)   /*101 五档*/
//  {
//    key = 1;
//    num = 5;
//  }
//    else if(ADC_ConvertedValueLocal[0] > 1.6 && ADC_ConvertedValueLocal[1] > 1.6 && ADC_ConvertedValueLocal[2] > 1.6)   /*111 开风扇*/
//  {
//    key = 1;
//    num = num;
//  }
//    else if(ADC_ConvertedValueLocal[0] < 1.6 && ADC_ConvertedValueLocal[1] < 1.6 && ADC_ConvertedValueLocal[2] < 1.6)    /*000  关风扇*/
//  {
//    key = 0;
//    num = 0;
//    SetPWM(0);
//  }
//}

static void keyCheck(void)                  //按键控制
{
     if(keyboard_scan() == 1)              //按键1,增加档位
    {
      key_val = 0;                         //按下后不再受到按键的影响
      num++;

      if(num > 5)
      {
        num = 5;
      }
    }
     else if(keyboard_scan() == 2)          //按键2,减小档位
    {
      key_val = 0;
      num--;
      if(num < 1)
      {
        num = 0;
      }
    }
    else if(keyboard_scan() == 3)          //按键3,风扇的开关
   {
     static u8 num1;
     key_val = 0;
     num1++;
     if(num1 > 2)
     {
       num1 = 1;
     }
     if(num1 == 1)
     {
       key = 1;
       num = num;
     }
     else if(num1 == 2)
     {
       key = 0;
       num = 0;
       SetPWM(0);
     }
 
   }
   else if(keyboard_scan() == 16)
   {
     key = 0;
     num = 0;
   } 

}


static void rangeCheck(void)                //测距控制
{
  length = HCSR04GetLength(); 
  DelayNms(500);
  if(length > 60.00)
  {
    num = 0;
  }
  else
  {
    num = num;
  }


}
static void TemparetureCheck(void)          //温度控制
{
	DelayNms(500);
  if(T1 < 27.0)
  {
    num = 0;
  }
  else if(T1 > 27.0 && T1 < 27.5)
  {
    num = 1;
  }
  else if(T1 >= 27.5 && T1 < 28.0)
  {
    num = 2;
  }
  else if(T1 >= 28.0 && T1 < 28.5)
  {
    num = 3;
  }
  else if(T1 >= 28.5 && T1 < 29.0)
  {
    num = 4;
  }
  else if(T1 >= 29.0 && T1 < 30.0)
  {
    num = 5;
  }
  else if(T1 > 30.0)
  {
    num = 5;
  }
}


static void PWMControl(u8 numb)           //pwm的控制
{
       if(key == 1)
     {
       OLEDShowString(4,0,"   ");
       OLEDShowString(4,0,"ON");
       SetPWM(numb * 100);
     }
     else
     {
       OLEDShowString(4,0,"   ");
       OLEDShowString(4,0,"OFF");
     }
     switch(numb)
   {
     case 1:OLEDShowChinese(52,17,&number[1],1,16,1);break;
     case 2:OLEDShowChinese(52,17,&number[2],1,16,1);break;
     case 3:OLEDShowChinese(52,17,&number[3],1,16,1);break;
     case 4:OLEDShowChinese(52,17,&number[4],1,16,1);break;
     case 5:OLEDShowChinese(52,17,&number[5],1,16,1);break;
     default:OLEDShowChinese(52,17,&number[0],1,16,1);break;
   }
     OLEDRefreshGRAM();
}

/*********************************************************************************************************
*                                              API函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称:CalcPWM
* 函数功能:通过按键,语音模块、温度变化等方法改变输出的PWM模块
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void CalcPWM(void)
{
  if(keyboard_scan() == 4)
  {
    num2++;
    if(num2 > 1)
    {
      num2 = 0;
    }

  }
  
    if(num2 == 0)
    {
      key_val = 0;
      keyCheck();
      OLEDShowChinese(70,17,&mode[0],1,16,1);
      OLEDShowChinese(86,17,&mode[1],1,16,1);
    }
    else if(num2 == 1)
    {
      key_val = 0;
//      voiceCheck();
      OLEDShowChinese(70,17,&mode[2],1,16,1);
      OLEDShowChinese(86,17,&mode[3],1,16,1);
    }
//    else if(num2 == 2)
//    {
//      key_val = 0;
//      TemparetureCheck(); 
//      OLEDShowChinese(70,17,&mode[4],1,16,1);
//      OLEDShowChinese(86,17,&mode[5],1,16,1);
//    }
  
  
//  read_AHT20_once();
//   rangeCheck();


   
   PWMControl(num);

//  OLEDShowString(4,48,"Length:");
//  OLEDShowNum(60,52,(int)length,2,12);
//  OLEDShowChar(74,52,'.',12,1);
//  OLEDShowNum(82,52,(int)(length * 100) % 100,2,12);
//  OLEDShowString(100,48,"cm");


}

这里是改变PWM波的控制中心,附加了其他的模块代码,不删了,接其他模块的时候可以使用。

最后一个代码就是PWM的配置了。

/*********************************************************************************************************
* 模块名称:PWM.c
* 摘    要:PWM模块
* 当前版本:1.0.0
* 作    者:SZLY(COPYRIGHT 2018 - 2020 SZLY. All rights reserved.)
* 完成日期:2020年01月01日 
* 内    容:
* 注    意:                                                                  
**********************************************************************************************************
* 取代版本:
* 作    者:
* 完成日期:
* 修改内容:
* 修改文件:
*********************************************************************************************************/

/*********************************************************************************************************
*                                              包含头文件
*********************************************************************************************************/
#include "PWM.h"
#include "stm32f10x.h"
/*********************************************************************************************************
*                                              宏定义
*********************************************************************************************************/

/*********************************************************************************************************
*                                              枚举结构体定义
*********************************************************************************************************/

/*********************************************************************************************************
*                                              内部变量
*********************************************************************************************************/
i16 s_iDutyCycle = 0;                      //用于存放占空比值
/*********************************************************************************************************
*                                              内部函数声明
*********************************************************************************************************/
static void ConfigTimer3ForPWMPB5(u16 arr,u16 psc);                 //配置PWM
/*********************************************************************************************************
*                                              内部函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称:ConfigTimer3ForPWMPB5
* 函数功能:配置PWM
* 输入参数:u16 arr,u16 psc
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
static void ConfigTimer3ForPWMPB5(u16 arr,u16 psc)
{
  GPIO_InitTypeDef GPIO_InitStructure;                       
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  TIM_OCInitTypeDef TIM_OCInitStructure;
  
  //使能RCC相关时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);    
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  
  //注意,GPIO
  GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);                        //TIM3部分重映射TIM3.CH2->PB5
  
  //配置PB5,对应TIM3的CH2
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB,&GPIO_InitStructure);
  
  //配置TIM3
  TIM_TimeBaseStructure.TIM_Period = arr;
  TIM_TimeBaseStructure.TIM_Prescaler = psc;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;                 //设置递增计数模式
  TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);                              //根据参数初始化TIM3
  
  //配置TIM3的CH2为PWM模式,在TIM_CounterMode_Up模式下,TIMx_CNT < TIMx_CCRx时为无效电平(高电平)
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;                           //设置为PWM2模式
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;               //使能比较输出
  TIM_OCInitStructure.TIM_OCPolarity  = TIM_OCPolarity_Low;                   //设置极性,OC2低电平有效
  TIM_OC2Init(TIM3,&TIM_OCInitStructure);                                     //根据参数初始化TIM3的CH2
  
  TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);                                 //使能TIM3的CH2预装载
  
  TIM_Cmd(TIM3,ENABLE);                                                        //使能TIM3
}  
/*********************************************************************************************************
*                                              API函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称:InitPWM
* 函数功能:初始化PWM
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void InitPWM(void)
{
  ConfigTimer3ForPWMPB5(599,999);                                              //配置TIM3,72000000/(999+1)/(599+1) = 120Hz
  TIM_SetCompare2(TIM3,0);                                                     //设置初始值为0
}
/*********************************************************************************************************
* 函数名称:SetPWM
* 函数功能:设置PWM
* 输入参数:i16 val
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void SetPWM(i16 val)     //设置PWM
{
  s_iDutyCycle = val;                                                         //获取占空比的值
  
  TIM_SetCompare2(TIM3,s_iDutyCycle);                                         //设置占空比
}
/*********************************************************************************************************
* 函数名称:IncPWMDutyCycle
* 函数功能:增加PWM
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void IncPWMDutyCycle(void)   //增加PWM
{
  if(s_iDutyCycle >= 600)
  {
    s_iDutyCycle = 600;
  }
  else
  {
    s_iDutyCycle += 100;
  }
  
  TIM_SetCompare2(TIM3,s_iDutyCycle);
}
/*********************************************************************************************************
* 函数名称:DecPWMDutyCycle
* 函数功能:减少PWM
* 输入参数:void
* 输出参数:void
* 返 回 值:void
* 创建日期:2018年01月01日
* 注    意:
*********************************************************************************************************/
void DecPWMDutyCycle(void)   //减少PWM
{
  if(s_iDutyCycle <= 0)
  {
    s_iDutyCycle = 0;
  }
  else
  {
    s_iDutyCycle -= 100;
  }
  
  TIM_SetCompare2(TIM3,s_iDutyCycle);                                               //设置占空比
}

当然了,这里也是标准配置,自己研究研究就会了。前提是知道啥是PWM

四、成品

https://www.bilibili.com/video/BV1cu411t7Kt/?vd_source=de78c9e88a60b2bce782148bbf8657b2

转自:https://blog.csdn.net/qq_48824303/article/details/130631887