我们下面通过TB6612 电机驱动模块、OLED 显示屏、直流电机等学习直流电机控制。

元器件

采用0.96oled 显示各种参数

  • OLED,即有机发光二极管(Organic Light Emitting Diode)。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
  • LCD都需要背光,而OLED不需要,因为它是自发光的。这样同样的显示OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。
  • 1、0.96 寸OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上1/4部分为黄光,下3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;蓝色则为纯蓝,也就是黑底蓝字。
  • 2、分辨率为128*64。
  • 3、多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3线或4线串行SPI 接口方式、IIC 接口方式(只需要2根线就可以控制OLED ),这五种接口是通过屏上的BS0~BS2 来配置的。

注意: 0.96 寸OLED显示屏所用的驱动IC为SSD1306;其具有内部升压功能;所以在设计的时候不需要再专一设计升压电路;当然了本屏也可以选用外部升压。SSD1306的每页包含了128个字节,总共8页,这样刚好是 128*64 的点阵大小。这点与1.3寸OLED驱动IC(SSD1106)稍有不同,SSD1106 每页是132个字节,也是8页。所以在用0.96寸OLED 移植1.3 寸OLED程序的时候需要将0.96寸的显示地址向右偏移2,这样显示就正常了;否则在用1.3 寸的时候1.3寸屏右边会有4个像素点宽度显示不正常或是全白。

STM32F103控制器

电机是 GB37520减速电

编码器

编码器是一种将角位移或者角速度转换为一连串电数字脉冲的旋转式传感器,所以可以用来测量转动位置和测量速度。
电机编码器有AB两相,AB相输出两个脉冲,编码器的所有信息都融合在这两个脉冲。一般编码器都有四根线,VCC、GND、A相、B相。AB相接到单片机IO口捕获脉冲。

  • (1)根据A相超前还是滞后B相来判断正反转。
  • (2)电机每转一圈的脉冲数目是相同的,所以测量的脉冲数目除上一圈的总数目就是电机当前角度了。如果采用四倍频计数,还要再除上4。
  • (3)如果每隔一段时间读脉冲数目然后把计数器清0,那么所读的数目除上时间就相当于电机转速。如果使用STM32,可以用定时器输入捕获的编码器模式来计数。直接读取计数器数值就可以了,而且自带滤波。

驱动是TB6612

TB6612FNG相对于传统的L298N 效率上提高很多,体积上也大幅度减少,在额定范围内,芯片基本不发热。TB6612FNG每通道输出最高1.2 A的连续驱动电流,启动峰值电流达2A/3.2 A(连续脉冲/单脉冲);片内集成低压检测电路与热停机保护电路;PWM支持频率高达100 kHz,支持待机状态。

  • TB6612是双驱动,可同时驱动两个电机
  • STBY:接单片机的IO口清零电机全部停止,置1通过AIN1 AIN2,BIN1,BIN2 来控制正反转
  • VM:建议接10V以内电源( 瞬间上电12V可能会有尖峰电压击穿器件 )
  • VCC:接5V电源
  • GND:接电源负极
  • PWMA:接单片机的PWM口 ,控制转速
  • PWMB:接单片机的PWM口 ,控制转速
  • AO1、AO2:接电机1的两个脚
  • BO1、BO2:接电机2的两个脚

TB6612 电机驱动模块,不同模块的驱动方式不尽相同,但是也都大同小异,基本思路都是通过 PWM 来控制电机。在使用时,VM端需要接相对较大的电压,如12V,而且VCC端还要接5V,两个都得接。控制电机时,AO1,AO2,BO1,BO2连接电机的两个引脚,PWMA,PWMB输入PWM信号,AIN1,AIN2,BIN1,BIN2用来控制电机的运动方向,还有就是它的STBY端,是一个使能信号,当STBY=1时,正常工作,输入PWM信号,电机即可运行;当STBY=0时,电机驱动处于待机状态,输入信号,电机不会运行。

TB6612 引脚说明

AIN1,AIN2 A 电机方向控制引脚
PWMA A 电机转速控制引脚
AO1,AO2 输出控制电机 A
BIN1, BIN2 B 电机方向控制引脚
PWMB B 电机转速控制引脚
BO1, BO2 输出控制电机 B
VM1/2/3 12V 电源输入
PGND1/2/3 接地
VCC 3.3/5V 输入
GND 接地
STBY 驱动模块使能引

通过 TB6612 引脚说明,以控制 B电机为例,控制器的电机接口也是接的驱动模块上的 B 电机输出引脚(BO1、BO2)。(电机上的 2、3、4、5 引脚是编码器引脚,将在下一节速度闭环控制使用)

给 TB6612 接上电源,12V 或者 5V 都可以,区别在于接 5V 时电机转速会比较慢。STBY 接高电平使能驱动模块,我们控制器硬件上已经接了高电平,

所以无须再进行软件设置。

BIN1、BIN2、PWMB 接单片机,其中 PWMB 需要接单片机的 PWM 输出引脚。BO1、BO2 接直流电机的 1、2 号引脚。

BIN1=0、BIN2=1 时 BO1 输出 0V、BO2 输出>0V(大小取决于 PWMB 输入大小及供电电源的大小),电机顺时针旋转,转速大小取决于 BO2 的电压大小BIN1=1、BIN2=0 时 BO1 输出>0V(大小取决于 PWMB 输入大小及供电电源的大小)、BO2 输出 0V,电机逆时针旋转,转速大小取决于 BO1 的电压大小。

当然A 电机的控制原理也是通用的。

当然还有L298N了解一下

  • L298N使用时需要用12V供电(9V也行),然后其内部带有稳压模块,如果5V使能跳帽没有取下,则内部的逻辑控制的5V电源由稳压模块提供,外部不需要再供电5V,甚至还可以为外界提供5V电源。如果跳帽被取下,则需要另外接一个5V电源进来,芯片才能正常工作。
    控制使用。
  • 两侧的OUT1~OUT4接两个直流电机,用来输出较大电流的,然后那一排公针为控制信号输入端。需要注意的是,L298N有两种控制方案。
  • 使能跳帽不取下,则需要连接四个PWM输出引脚,左侧两个控制左边的输出,右侧两个控制右边的输出。在控制时,电机的运行速度和方向由两个PWM信号的占空比之差来控制,差值越大,电机运行速度越快。差值为正时,电机正转,差值为负,电机反转(和接线方式有关)。
  • 使能跳帽取下,则两个使能端为PWM信号输入端,中间四个引脚为方向控制端,只需要输入高低电平即可。此时电机的速度与PWM占空比直接关联。而转动方向与两个控制引脚的高低电平有关,左高右低为正转,左低右高为反转(和接线方式有关)。

TB6612函数

定义相关结构体

GPIO_InitTypeDef GPIO_InitStructure; //定义一个引脚初始化的结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrue; //定义一个定时中断的结构体
TIM_OCInitTypeDef TIM_OCInitTypeStrue; //定义一个 PWM 输出的结构体

使能相关时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能 GPIOB 时钟,GPIOB 挂载在 APB2 时钟下,在 STM32 中使用 IO 口前都要使能对应时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能通用定时器 3时钟

初始化方向控制 GPIO

//TB6612 控制方向引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12|GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //引脚输入输出模式为推挽输出模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //引脚输出速度为 50MHZ
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据上面设置好的GPIO_InitStructure 参数,初始化引脚
GPIO_ResetBits(GPIOB, GPIO_Pin_12|GPIO_Pin_13); //初始化设置引脚低电平

初始化 PWM 输出 GPIO

//TB6612PWM 输出引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//引脚 0
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出模式,定时器功能为 B1 引脚复用功能
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //定义该引脚输出速度为50MHZ
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化引脚 GPIOB1

设置并初始化定时器 TIM3(PB1 对应 TIM3CH4)

TIM_TimeBaseInitStrue.TIM_Period=arr; //计数模式为向上计数时,定时器从0开始计数,计数超过到 arr 时触发定时中断服务函数
TIM_TimeBaseInitStrue.TIM_Prescaler=psc; //预分频系数,决定每一个计数的时长
TIM_TimeBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up; //计数模式:向上计数
TIM_TimeBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1; //一般不使用,默认TIM_CKD_DIV1
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStrue); //根据TIM_TimeBaseInitStrue 的参数初始化定时器 TIM3

设置并初始化 PWM 通道(TIM3CH4)

TIM_OCInitTypeStrue.TIM_OCMode=TIM_OCMode_PWM1; //PWM 模式 1,当定时器计数小于 TIM_Pulse 时,定时器对应 IO 输出有效电平
TIM_OCInitTypeStrue.TIM_OCPolarity=TIM_OCNPolarity_High; //输出有效电平为高电平
TIM_OCInitTypeStrue.TIM_OutputState=TIM_OutputState_Enable; //使能 PWM输出
TIM_OCInitTypeStrue.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM_OC4Init(TIM3, &TIM_OCInitTypeStrue); //根 TIM_OCInitTypeStrue 参数初始化定时器 3 通道 4
//TIM_CtrlPWMOutputs(TIM3,ENABLE); //MOE 主输出使能

相关使能

TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Disable); //CH4 预装载使能,使能后改变 TIM_Pulse(即 PWM)的值立刻生效,不使能则下个周期生效
TIM_ARRPreloadConfig(TIM3, ENABLE); //TIM3 预装载使能
TIM_Cmd(TIM3, ENABLE); //使能定时器 TIM3

电机控制函数,通过控制方向引脚和 PWM

void SetPWM(int pwm)
{
    if(pwm>=0)//pwm>=0 (BIN1, BIN2)=(0, 1) 正转 顺时针
    {
        PBout(13)=0; //BIN1=0
        PBout(12)=1; //BIN2=1
        TIM3->CCR4=pwm;
        TIM_SetCompare4(TIM3, pwm);
    }
    else if(pwm<0)//pwm<0 (BIN1, BIN2)=(1, 0) 反转 逆时针
    {
        PBout(13)=1; //BIN1=1
        PBout(12)=0; //BIN2=0
        TIM3->CCR4=-pwm;
        TIM_SetCompare4(TIM3, -pwm);
    }
}

实验操作

  • 编写 TB6612 初始化函数使能 GPIO、TIM 时钟→初始化 GPIO 引脚→初始化定时器→初始化 PWM→使能相关功能、定时器。
  • 编写设置 PWM 值函数通过控制方向引脚和 PWM 引脚实现电机控制,顺时针旋转为正,逆时针旋转为负。
  • 编写主函数调用相关初始化函数,在 while 循环中控制电机旋转或停止。
  • 实验效果为:接通电源打开电源开关后,连接电机和控制器,按下用户按键,电机将循环加减速旋转,再次按下按键电机停止。同时 OLED 显示屏会显示当

前 PWM 值。

TB6612.c

#include "TB6612.h"  

/**************************************************************************
函数功能:TB6612初始化函数
入口参数:定时器3计数上限 定时器3预分频系数
返回  值:无
**************************************************************************/
void TB6612_Init(int arr, int psc)
{
    GPIO_InitTypeDef GPIO_InitStructure; //定义一个引脚初始化的结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrue; //定义一个定时中断的结构体    
    TIM_OCInitTypeDef TIM_OCInitTypeStrue; //定义一个PWM输出的结构体

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟,GPIOB挂载在APB2时钟下,在STM32中使用IO口前都要使能对应时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能通用定时器3时钟

    //TB6612控制方向引脚
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12|GPIO_Pin_13; 
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //引脚输入输出模式为推挽输出模式
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //引脚输出速度为50MHZ
    GPIO_Init(GPIOB, &GPIO_InitStructure); //根据上面设置好的GPIO_InitStructure参数,初始化引脚

    GPIO_ResetBits(GPIOB, GPIO_Pin_12|GPIO_Pin_13); //初始化设置引脚低电平

    //TB6612PWM输出引脚
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;//引脚0
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出模式,定时器功能为B1引脚复用功能
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //定义该引脚输出速度为50MHZ
  GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化引脚GPIOB1

    TIM_TimeBaseInitStrue.TIM_Period=arr; //计数模式为向上计数时,定时器从0开始计数,计数超过到arr时触发定时中断服务函数
    TIM_TimeBaseInitStrue.TIM_Prescaler=psc; //预分频系数,决定每一个计数的时长
    TIM_TimeBaseInitStrue.TIM_CounterMode=TIM_CounterMode_Up; //计数模式:向上计数
    TIM_TimeBaseInitStrue.TIM_ClockDivision=TIM_CKD_DIV1; //一般不使用,默认TIM_CKD_DIV1
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStrue); //根据TIM_TimeBaseInitStrue的参数初始化定时器TIM3

    TIM_OCInitTypeStrue.TIM_OCMode=TIM_OCMode_PWM1; //PWM模式1,当定时器计数小于TIM_Pulse时,定时器对应IO输出有效电平
    TIM_OCInitTypeStrue.TIM_OCPolarity=TIM_OCNPolarity_High; //输出有效电平为高电平
    TIM_OCInitTypeStrue.TIM_OutputState=TIM_OutputState_Enable; //使能PWM输出
    TIM_OCInitTypeStrue.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
    TIM_OC4Init(TIM3, &TIM_OCInitTypeStrue); //根TIM_OCInitTypeStrue参数初始化定时器3通道4

  //TIM_CtrlPWMOutputs(TIM3,ENABLE);    //MOE 主输出使能

    TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Disable); //CH4预装载使能 使能后改变TIM_Pulse(即PWM)的值立刻生效,不使能则下个周期生效

    TIM_ARRPreloadConfig(TIM3, ENABLE); //TIM3预装载使能

    TIM_Cmd(TIM3, ENABLE); //使能定时器TIM3
}

/**************************************************************************
函数功能:设置TIM3通道4PWM值
入口参数:PWM值
返回  值:无
**************************************************************************/
void SetPWM(int pwm)
{
  if(pwm>=0)//pwm>=0 (BIN1, BIN2)=(0, 1) 正转 顺时针
  {
        PBout(13)=0; //BIN1=0
        PBout(12)=1; //BIN2=1
        TIM3->CCR4=pwm;
        TIM_SetCompare4(TIM3, pwm);
  }
  else if(pwm<0)//pwm<0 (BIN1, BIN2)=(1, 0) 反转 逆时针
  {
        PBout(13)=1; //BIN1=1
        PBout(12)=0; //BIN2=0
        TIM3->CCR4=-pwm;
        TIM_SetCompare4(TIM3, -pwm);
  }
}

tb6612.h

#ifndef __TB6612_H 
#define __TB6612_H 
#include "sys.h"

void TB6612_Init(int arr, int psc);
void SetPWM(int pwm);

#endif

main.c

#include "delay.h"
#include "usart.h"            
#include "TB6612.h"
#include "motorencoder.h"
#include "usart.h"
#include "led.h"
#include "oled.h"
#include "key.h"

int PWM=7000;              //PWM控制变量
int Step=500;           //速度渐变速率 相当于加速度
int MortorRun;          //允许电机控制标志位

/**************************************************************************
函数功能:OLED显示屏显示内容
入口参数:无
返回  值:无
**************************************************************************/
void Oled_Show(void)
{  
        OLED_Refresh_Gram(); //刷新显示屏
        OLED_ShowString(00,00,"VelocityDirect"); //速度开环控制


        //显示速度控制值,即PWM值,分正负
        OLED_ShowString(00,20,"PWM      :");         
        if(PWM>=0)
        {
            OLED_ShowString(80,20,"+");
            OLED_ShowNumber(100,20,PWM,4,12);
        }
        else
        {
            OLED_ShowString(80,20,"-");
            OLED_ShowNumber(100,20,-PWM,4,12);
        }
}

/**************************************************************************
函数功能:主函数
入口参数:无
返回  值:无
**************************************************************************/
int main(void)
{
     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组
     delay_init();                //延迟函数初始化
     JTAG_Set(JTAG_SWD_DISABLE);  //关闭JTAG才能开启OLED显示屏 
     LED_Init();                  //LED灯初始化
     KEY_Init();
   OLED_Init();                 //OLED显示屏初始化
   //uart_init(9600);           //串口初始化     
   TB6612_Init(7199, 0);        //电机驱动外设初始化 使用定时器3 
     delay_ms(2000);              //延迟等待初始化完成
     while(1)
      {        
            delay_ms(200);    //延迟1秒
            LED=!LED;    //LED灯闪烁        
      if(KEY_Scan())MortorRun=!MortorRun; //按下按键MortorRun取反

            if(MortorRun)
            {
                //速度循环变化 
                if(PWM<=-7000)Step=500;      //减速到反转最大速度后加速
                else if(PWM>=7000)Step=-500; //加速到正转最大速度后减速

                PWM=PWM+Step; //速度渐变
                SetPWM(PWM);  //设置PWM
            }
            else PWM=0,SetPWM(PWM); //电机停止

            Oled_Show();  //OLED显示屏显示内容
      }
}

oled.c

#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"       
#include "delay.h"

u8 OLED_GRAM[128][8];       //oled显示内容存放的数组,将数据写入这个数组来实现显示
void OLED_Refresh_Gram(void) //刷新显示
{
    u8 i,n;            
    for(i=0;i<8;i++)  
    {  
        OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
        OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
        OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
        for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); 
    }   
}
/**************************************************************************
函数功能:向OLED写入一个字节
入口参数:dat:要写入的数据/命令  ,cmd:数据/命令标志 0,表示命令;1,表示数据;
返 回 值:无
**************************************************************************/
void OLED_WR_Byte(u8 dat,u8 cmd)
{    
    u8 i;              
    if(cmd)
      OLED_RS_Set();
    else 
      OLED_RS_Clr();          
    for(i=0;i<8;i++)
    {              
        OLED_SCLK_Clr();
        if(dat&0x80)
           OLED_SDIN_Set();
        else 
           OLED_SDIN_Clr();
        OLED_SCLK_Set();
        dat<<=1;   
    }                           
    OLED_RS_Set();         
} 
//////////////////////////////////////////////////////////////////////////////      
void OLED_Display_On(void)//开启OLED显示
{
    OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
    OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//////////////////////////////////////////////////////////////////////////////     
void OLED_Display_Off(void)//关闭OLED显示
{
    OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
    OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
    OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}    
////////////////////////////////////////////////////////////////////////////////
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!      
void OLED_Clear(void)  
{  
    u8 i,n;  
    for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n][i]=0X00;  
    OLED_Refresh_Gram();//更新显示
}
/**************************************************************************
函数功能:在OLED中画点
入口参数:坐标:x:0~127,y:0~63  ,t:1 填充 0,清空        
返 回 值:无
**************************************************************************/
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
    u8 pos,bx,temp=0;
    if(x>127||y>63)return;//超出范围了.
    pos=7-y/8;
    bx=y%8;
    temp=1<<(7-bx);
    if(t)OLED_GRAM[x][pos]|=temp;
    else OLED_GRAM[x][pos]&=~temp;        
}
/**************************************************************************
函数功能:在指定的位置显示一个字符
入口参数:坐标:x:0~127,y:0~63 ,mode:0,反白显示;1,正常显示    ,size:选择字体 12-16        
返 回 值:无
**************************************************************************/
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{                      
    u8 temp,t,t1;
    u8 y0=y;
    chr=chr-' ';//得到偏移后的值                   
    for(t=0;t<size;t++)
    {   
        if(size==12)temp=oled_asc2_1206[chr][t];  //调用1206字体
        else temp=oled_asc2_1608[chr][t];         //调用1608字体                               
        for(t1=0;t1<8;t1++)
        {
            if(temp&0x80)OLED_DrawPoint(x,y,mode);
            else OLED_DrawPoint(x,y,!mode);
            temp<<=1;
            y++;
            if((y-y0)==size)
            {
                y=y0;
                x++;
                break;
            }
        }       
    }          
}

//m^n函数
u32 oled_pow(u8 m,u8 n)
{
    u32 result=1;     
    while(n--)result*=m;    
    return result;
}    
/**************************************************************************
函数功能:显示2个数字
入口参数:坐标:x:0~127,y:0~63 ;num:数值(0~4294967295);mode:0,填充模式 1,叠加模式; size:选择字体 12-16        
返 回 值:无
**************************************************************************/  
void OLED_ShowNumber(u8 x,u8 y,u32 num,u8 len,u8 size)
{             
    u8 t,temp;
    u8 enshow=0;                           
    for(t=0;t<len;t++)
    {
        temp=(num/oled_pow(10,len-t-1))%10;
        if(enshow==0&&t<(len-1))
        {
            if(temp==0)
            {
                OLED_ShowChar(x+(size/2)*t,y,' ',size,1);
                continue;
            }else enshow=1; 

        }
         OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); 
    }
} 
/**************************************************************************
函数功能:显示字符串
入口参数:坐标:x:0~127,y:0~63 ;*p:字符串起始地址;
返 回 值:无
**************************************************************************/  
void OLED_ShowString(u8 x,u8 y,const u8 *p)
{
#define MAX_CHAR_POSX 122
#define MAX_CHAR_POSY 58          
    while(*p!='\0')
    {       
        if(x>MAX_CHAR_POSX){x=0;y+=16;}
        if(y>MAX_CHAR_POSY){y=x=0;OLED_Clear();}
        OLED_ShowChar(x,y,*p,12,1);     
        x+=8;
        p++;
    }  
}    
/**************************************************************************
函数功能:初始化OLED
入口参数:无
返 回 值:无
**************************************************************************/ 
void OLED_Init(void)
{           
  GPIO_InitTypeDef GPIO_InitStruct;
                                                      //使能GPIOA GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE); 

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;        //PB3 4 5推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB,&GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;       //PA15 推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);


    OLED_RST_Clr();
    delay_ms(100);
    OLED_RST_Set();   //OLED复位

    OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
    OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率
    OLED_WR_Byte(80,OLED_CMD);   //[3:0],分频因子;[7:4],震荡频率
    OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
    OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) 
    OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
    OLED_WR_Byte(0X00,OLED_CMD); //默认为0

    OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.

    OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置
    OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭
    OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式
    OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
    OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
    OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
    OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置
    OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置

    OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
    OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
    OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期
    OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
    OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率
    OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;

    OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
    OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示                                   
    OLED_WR_Byte(0xAF,OLED_CMD); //开启显示     
    OLED_Clear();                //清屏
}

oled.h

#ifndef __OLED_H
#define __OLED_H                   
#include "sys.h"
//-----------------OLED端口定义---------------- 
#define OLED_RST_Clr() PBout(3)=0   //RES
#define OLED_RST_Set() PBout(3)=1   //RES

#define OLED_RS_Clr() PAout(15)=0    //DC
#define OLED_RS_Set() PAout(15)=1    //DC

#define OLED_SCLK_Clr()  PBout(5)=0  //SCL
#define OLED_SCLK_Set()  PBout(5)=1   //SCL

#define OLED_SDIN_Clr()  PBout(4)=0   //SDA
#define OLED_SDIN_Set()  PBout(4)=1   //SDA

#define OLED_CMD  0    //写命令
#define OLED_DATA 1    //写数据
//OLED控制用函数
void OLED_WR_Byte(u8 dat,u8 cmd);             //向OLED写入一个字节
void OLED_Display_On(void);                //开启OLED显示
void OLED_Display_Off(void);               //关闭OLED显示
void OLED_Refresh_Gram(void);                            //刷新显示            
void OLED_Init(void);                      //初始化OLED
void OLED_Clear(void);                     //OLED清屏,清空后整个屏幕都是黑色
void OLED_DrawPoint(u8 x,u8 y,u8 t);       //OLED画点
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode); //在指定的位置显示一个字符
void OLED_ShowNumber(u8 x,u8 y,u32 num,u8 len,u8 size); //在指定的位置显示数字
void OLED_ShowString(u8 x,u8 y,const u8 *p);              //在指定的位置显示字符串
#endif

key.c

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

void KEY_Init(void)
{
  GPIO_InitTypeDef GPIO5_InitStructure; //定义一个引脚初始化的类
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟,在STM32中使用IO口前都要使能对应时钟

    GPIO5_InitStructure.GPIO_Pin=GPIO_Pin_5; //引脚5
    GPIO5_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //引脚输入输出模式为上拉输入模式,默认为高电平,外接地后为低电平
    GPIO_Init(GPIOA, &GPIO5_InitStructure); //根据上面设置好的GPIO_InitStructure参数,初始化引脚GPIOA_PIN4
}

u8 KEY_Scan()
{
    static u8 flag_key=1;//按键松开标志,static使flag_key在函数执行完后依然存在,值依然不变
    if(flag_key==1&&KEY==0) //flag_key==1代表按键状态为松开,KEY==0代表检测到按键处于按下状态,两者结合代表一次按下动作
    {                      //若flag_key==0&&KEY==0,则表示按键一直处于按下、没有松开的状态
        delay_ms(1); //防抖
        if(KEY==0)
      {
            flag_key=0; //确认按键按下,按键松开标志置0
        return 1;      //按键按下,函数返回值1
        }
    }
  else if(KEY==1) flag_key=1; //检测到按键松开,按键松开标志置1
    return 0;//无按键按下,函数返回值0
}

key.h

#ifndef __KEY_H
#define __KEY_H    
#include "sys.h"
#include "stm32f10x_gpio.h" 

#define KEY GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)//宏定义KEY为读取按键状态,宏定义可以提高程序可读性
//#define KEY PAin(5) //引用sys.h头文件后,可以直接使用PAin(5)读取按键状态

void KEY_Init(void);    //KEY键初始化
u8 KEY_Scan(void);      //按键扫描函数    

#endif