基于52单片机的小型电扇控制系统

前言

为本人微机原理与系统课程的课程设计,将详细实现和心得分享给大家,如果有纰漏和改进建议欢迎提出

单片机程序,上位机程序工程(visual studio 2019) ,Proteus 工程( Proteus 8.6) ,AD 原理图以及蓝牙 APP 的 MIP APP Inventor 文件和安装包已上传CSDN资源:

https://download.csdn.net/download/weixin_45715159/85248654

基本功能

  • 设置三个功能分别为风速、类型和停止开关,LED指示灯六个,指示风速强、中、弱,类型为睡眠、自然和正常。指标如下:

    • 电扇处于停转状态时,所有指示灯不亮,只有按下“风速”键时,才会响应,进入起始工作状态;电扇在任何状态,只要按停止键,则进入停转状态

    • 处于工作状态时,有:

      • 初始状态为:风速-“弱”,类型-“正常”
      • 按“风速”键,其状态由 “弱” -> “中” -> “强” -> “弱” …… 往复循环改变,每按一下按键改变一次
      • 按“类型”键,其状态由 “正常” -> “睡眠” -> “自然” -> “正常” …… 往复循环改变
    • 风速的弱、中、强对应于电扇的转动由慢到快

    • 类型的不同选择,分别为:

      • 正常 电扇连续运转
      • 自然 电扇模拟自然风,即转4s,停8s
      • 睡眠 电扇慢转,产生轻柔的微风,运转 8s,停转8s
    • 按照风速与类型的设置输出相应的控制信号

    • 数码管和液晶屏显示当前电扇状态及参数,简单故障诊断与报警

  • 上位机监控功能:通过串口或USB口实现上/下位机通信,能够显示电扇当前状态,能够从上位机控制下位机之电扇的工作

器件

  • STC89C52 单片机
  • 130直流电机以及风扇叶
  • PCF8591 模块
  • ULN2003A 电机驱动模块
  • LCD1602附PCF8574T模块
  • 按钮
  • LED灯
  • JDY-16 蓝牙模块

结构

系统结构图

在这里插入图片描述

Proteus 原理图

(蓝牙模块未画出)
在这里插入图片描述

Altium Designer 原理图

(暂时没有相应的 PCB 电路图)
在这里插入图片描述

程序流程图

主函数

在这里插入图片描述

中断

在这里插入图片描述

硬件资源分配

  • 定时器 0 作为系统服务管理器

  • 定时器 1 作为波特率发生器

  • 定时器 2 作为 PWM 发生器

  • 风速键和模式键按键调整风扇工作模式和输出 PWM 占空比

  • 外部中断 0 处理停止键

  • 采用串口中断处理上位机下达的指令

  • 每隔 1 秒通过 ADC 芯片采集电机电压传输到单片机判断电机是否正常运转

    出现故障后电机停转等待检查故障和复位,蜂鸣器发出响声

  • 数码管显示当前电扇状态及参数

  • LCD1602显示当前风扇状态及参数

  • 通过上位机软件实现双向通信,显示风扇状态信息和控制风扇运作

  • 通过手机 APP 连接蓝牙模块控制风扇工作

原理详述

直流电机控制

用单片机控制直流电机时,需要加驱动电路,为直流电机提供足够大的驱动电流。直流电机通过调制 PWM 占空比控制电机转速,这里采用52单片机的定时器2作为 PWM 发生器控制占空比的大小,定时器2的工作模式为16位自动重装,每100us溢出一次触发定时器2中断请求,一个PWM周期进行100次计数,在接通段内电机输出引脚置为高电平,其余置为低电平。PWM 的频率为100Hz

按键检测

在主函数循环进行按键检测,将单片机 IO 口置为输入模式检测是否有按键按下(低电平),输入理想波形是方波,但实际波形在按下和释放的瞬间都有抖动现象,如图所示。抖动时间的长短和按键的机械特性有关,一般为 5~10ms。通常我们手动按下键后立即释放,这个动作稳定闭合的时间超过 20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作。按键检测函数采用延时 10ms 的去前沿抖动
在这里插入图片描述

数码管,LED显示

共阳极数码管其内部8个发光二极管的所有阳极全部连接在一起,电路连接时,公共端接高电平,给阴极送低电平点亮二极管

当多位一体时,他它们内部的公共端是独立的,而负责显示什么图案的段线是全部连接在一起,独立的公共端称为“位选线”,连接在一起的段线称为“段选线”。位选线控制多位一体的哪一位数码管点亮,段选线控制点亮的数码管显示的图案

数码管动态显示又叫做数码管的动态扫描显示,轮流向各位数码管送出字形码和相应的位选,利用发光管的余晖和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示,而实际上多位数码管是一位一位轮流显示的,只是轮流的速度非常快,人眼已经无法分辨出来。需要注意的是,在送入下一个位选数据之前,需要将段选线全置高电平,这个操作的专业名称叫做“消影”,可以消除上一个段选数据防止数码管出现显示混乱的情况

在主函数内进行数码管的动态显示,四位数码管其中两位分别显示风速和模式,风速:弱-1,中-2,强-3,模式:正常:A,睡眠:b,自然:C

在改变风扇状态后,LED灯根据风扇状态做出对应的显示

中断程序

程序启用四个中断:外部中断0,定时器0、2中断,串口中断

  • 外部中断0为下降沿触发方式,当检测到按下停止键后(低电平)或上位机下达的停止指令后(串口停止指令接收标志为1),进入外部中断0处理程序,数码管,LED全灭,电机停转,总中断关闭,循环检测风速键或上位机下达的风速指令,检测到后退出循环,置风扇状态为:风速-弱,模式-正常。总中断开启

  • 定时器0中断进行秒数计时,管理每一秒进行的故障检测服务和风扇运转周期。当风扇模式不为正常时,根据相应模式控制风扇运转的时间和停转的时间以周期运作

  • 串口中断当接收到上位机指令时触发,取指令根据相应指令(风速,类型,停止)进行相应的风扇状态改变。当接收到停止指令时置串口停止指令接收标志为1(告诉外部中断0为串口中断引发取消停止键按键去抖)再将外部中断0中断请求标志置为1进入外部中断0

  • 定时器2中断控制PWM的占空比

故障检测

当直流电机强制停转时,会产生堵转电流使电机一端电压升高,通过 ADC 检测那一端电压的变化幅度就可以知道电机是否出现故障。使用 I2C 总线对PCF8591 进行操作,读取并存储两种电压值:最小电压和次小电压。当新读出的电压值和次小电压的差值超出正常范围时,启动故障处理程序,即系统停止工作

LCD1602显示

通过 I2C 总线操作 PCF8574T 实现对 LCD1602 的驱动,根据相关驱动函数可以实现多个字符显示。I2C 总线的驱动和 LCD1602 的驱动见程序

上位机通信

利用 C# 编程语言开发上位机图形界面程序,程序中的指令按钮被点击则向串口发送数据,单片机接收到指令后进入串口中断处理指令。当单片机的工作状态改变后向上位机发送加密信息,上位机接收到后进行解密再显示风扇相关信息
在这里插入图片描述

蓝牙通信

在这里插入图片描述
蓝牙BLE是在Android4.3系统及以上引入的。Android BLE 使用的蓝牙协议是 GATT 协议。其中Service是服务,Characteristic是特征值。蓝牙里面有多个Service,一个Service里面又包括多个Characteristic,具体的关系如图:
在这里插入图片描述
蓝牙设备通过Characteristic来进行设备间的交互,使用的JDY-16 透传模块是基于蓝牙 4.2 协议标准,设置为UART通信方式,使用和单片机相同的波特率即可像上位机与单片机串口通信一样与单片机通信

利用 MIT App Inventor 进行APP开发,其分为组件设计和逻辑设计。组件设计负责应用的图形界面,逻辑设计负责应用的内在程序逻辑。其中逻辑设计可以通过逻辑图形块的拼接完成程序的设计,大大减少了App的开发时间和难度

MIT App Inventor 的低功耗蓝牙组件采用 UUID 进行通信,所以在开发自己的蓝牙通信时要先知道自己蓝牙模块的 UUID,可使用蓝牙串口软件查询或翻阅蓝牙模块的技术文档

MIT App Inventor 中国网址:http://app.gzjkw.net/login/

MIT App Inventor 低功耗蓝牙扩展组件下载网址:https://mit-cml.github.io/extensions/

代码

main.c

#include <reg52.h>
#include <I2C.h>
#include <PCF8574.h>

#define uint unsigned int
#define ucahr unsigned char

#define PCF8591 0x90 //PCF8591写地址

#define NORMAL 0 //正常
#define SLEEP 1 //睡眠
#define NATURAL 2 //自然

#define LOW 0 //低速
#define MEDIUM 1 //中速
#define HIGH 2 //高速

bit serial_flag = 0; //串口接收标志
uchar FanMode = NORMAL; //风扇模式
uchar FanSpeed = LOW; //风扇转速
uchar sec = 0;//当前秒速
float MinVol = 5.0; //检测到的最小电压
float Min2ndVol = 5.0; //检测到的次小电压

sbit SpeedButton = P3 ^ 3; //加速键
sbit FormButton = P3 ^ 4; //类型键
sbit StopButton = P3 ^ 2; //停止键
sbit tube_w1 = P2 ^ 0; //数码管位选端1
sbit tube_w2 = P2 ^ 1; //数码管位选端2
sbit tube_w3 = P2 ^ 2; //数码管位选端3
sbit tube_w4 = P2 ^ 3; //数码管位选端4
sbit MotorPin = P2 ^ 4; //电机信号输入端
sbit BuzzerPin = P2 ^ 5; //蜂鸣器端

uchar code RunSecs[3] = {
    
    0, 8, 4}; //电机运行秒数
uchar code StopSecs[3] = {
    
    0, 8, 8}; //电机停转秒数
uchar code speed_array[3] = {
    
    40, 65, 90}; //电机速度
float code error_vol_array[3] = {
    
    0.13, 0.23, 0.23}; //异常电机电压
uchar code speed_led[3] = {
    
    0xFE, 0xFD, 0xFB}; //LED速度显示
uchar code form_led[3] = {
    
    0xEF, 0xDF, 0xBF}; //LED模式显示
uchar code speed_table[3] = {
    
    0xf9, 0xa4, 0xb0}; //数码管速度显示图案
uchar code mode_table[3] = {
    
    0x88, 0x83, 0xc6}; //数码管模式显示图案
uchar code *SpeedStr[3] = {
    
    "LOW", "MEDIUM", "HIGH"}; //LCD1602显示字符
uchar code *FormStr[3] = {
    
    "NORMAL", "SLEEP", "NATURAL"}; //LCD1602显示字符

void delay_ms(uint ms) //毫秒延时函数
{
    
    
    uint i, j;
    for(i = ms; i > 0; i--)
        for(j = 110; j > 0; j--);
}

void initial(void) //初始化系统
{
    
    
    SpeedButton = 1; //设置为输入模式
    FormButton = 1; //设置为输入模式
    StopButton = 1; //设置为输入模式

    IT0 = 1; //设置外部中断0为边沿触发方式

    TMOD = 0x21; //定时器1工作方式2,定时器0工作方式1
    TH1 = 0xfd; //波特率9600
    TL1 = 0xfd;
    TH0 = (65536 - 46080) / 256; //定时器0 50ms溢出一次,作为系统服务管理器
    TL0 = (65536 - 46080) % 256;

    T2CON = 0x00; //设置定时器2为16位自动重装模式,100us溢出一次,作为PWM发生器
    RCAP2H = 0xff;
    RCAP2L = (65536 - 92) % 256;;
    TH2 = RCAP2H; //定时器2赋初值
    TL2 = RCAP2L;

    SM0 = 0; //串口工作方式1
    SM1 = 1;
    REN = 1; //允许串行接收

    EX0 = 1; //开启外部中断0
    ET0 = 1; //开启定时器0中断
    ES = 1; //开启串口中断
    ET2 = 1; //开启定时器2中断
    EA = 1; //开启总中断

    TR0 = 1;
    TR1 = 1;
    TR2 = 1;
}
void Report2PC(void) //向上位机汇报状态信息,信息经过加密
{
    
    
    ES = 0;
    SBUF = FanMode * 16 + FanSpeed;
    while(!TI);
    TI = 0;
    ES = 1;
}
void LCD1602Display(void) //LCD1602显示
{
    
    
    uchar *str1 = "FanSpeed:";
    uchar *str2 = "FanMode:";
    write_com(0x01); //清屏
    ShowString(1, 1, str1);
    ShowString(1, 10, SpeedStr[FanSpeed]); //显示风扇速度
    ShowString(2, 1, str2);
    ShowString(2, 9, FormStr[FanMode]); //显示风扇模式
}
void ChangeMode(uchar Mode) //切换电扇模式
{
    
    
    FanMode = Mode;
    MinVol = 5;
    Min2ndVol = 5;
    sec = 0; //重新计时
    TR2 = 1; //定时器2启动
    P1 = (P1 | 0xF0) & form_led[FanMode]; //LED显示
    Report2PC();
    LCD1602Display();
}
void ChangeSpeed(uchar Speed) //切换风扇速度
{
    
    
    FanSpeed = Speed;
    MinVol = 5;
    Min2ndVol = 5;
    P1 = (P1 | 0x0F) & speed_led[FanSpeed]; //LED显示
    Report2PC();
    LCD1602Display();
}

void InitFan(void) //初始化风扇状态
{
    
    
    FanSpeed = LOW;
    FanMode = NORMAL;
    MinVol = 5;
    Min2ndVol = 5;
    sec = 0; //重新计时
    TR2 = 1; //定时器2启动
    P1 = (P1 | 0x0F) & speed_led[FanSpeed]; //LED显示
    P1 = (P1 | 0xF0) & form_led[FanMode]; //LED显示
    Report2PC();
    LCD1602Display();
}
void DealError(void) //处理故障
{
    
    
    uchar *str = "ERROR!";
    EA = 0; //关闭总中断
    TR2 = 0;
    MotorPin = 0; //电机停转
    P1 = 0xff; //LED灯熄灭
    P2 = P2 | 0x0f; //数码管熄灭
    BuzzerPin = 0; //蜂鸣器响
    SBUF = 0x44; //向上位机发送错误信息
    while(!TI);
    TI = 0;
    write_com(0x01); //清屏
    ShowString(1, 6, str);
    while(1); //进入死循环
}

void ExamineMotor(void) //检测电机故障 系统服务
{
    
    
    uchar voltage;
    float RealVoltage;
    if(TR2) //如果电机在运行
    {
    
    
        Rcvnbyte(PCF8591, PCF8591 + 1, 0x02, &voltage, 1);
        RealVoltage = (int)voltage / 255.0 * 5.0;
        if(RealVoltage > 2.0 && RealVoltage < 4.0) //滤掉AD转换的异常值
        {
    
    
            if(RealVoltage < MinVol)
            {
    
    
                Min2ndVol = MinVol;
                MinVol = RealVoltage;
            }
            else if(RealVoltage < Min2ndVol)
            {
    
    
                Min2ndVol = RealVoltage;
            }
            else if(RealVoltage - Min2ndVol > error_vol_array[FanSpeed])//出现大电压
            {
    
    
                DealError();
            }
        }
    }
}

void TubeDisplay(void) //数码管显示
{
    
    
    tube_w1 = 0;
    P0 = 0xbf;
    delay_ms(1);
    tube_w1 = 1;
    P0 = 0xff; //消影

    tube_w2 = 0;
    P0 = speed_table[FanSpeed];
    delay_ms(1);
    tube_w2 = 1;
    P0 = 0xff; //消影

    tube_w3 = 0;
    P0 = 0xbf;
    delay_ms(1);
    tube_w3 = 1;
    P0 = 0xff; //消影

    tube_w4 = 0;
    P0 = mode_table[FanMode];
    delay_ms(1);
    tube_w4 = 1;
    P0 = 0xff; //消影
}


void ButtonScan(void) //按键扫描
{
    
    
    if(SpeedButton == 0)
    {
    
    
        delay_ms(10);
        if(SpeedButton == 0)
        {
    
    
            while(!SpeedButton); //等待放下按键
            ChangeSpeed((FanSpeed + 1) % 3);
        }
    }
    if(FormButton == 0)
    {
    
    
        delay_ms(10);
        if(FormButton == 0)
        {
    
    
            while(!FormButton); //等待放下按键
            ChangeMode((FanMode + 1) % 3);
        }
    }
}

void main(void) //主函数
{
    
    
    initial();
    init_lcd();
    InitFan();
    while(1)
    {
    
    
        ButtonScan(); //扫描按键
        TubeDisplay(); //显示数码管
    }
}

void int_ext0() interrupt 0 //外部中断0处理停止键
{
    
    
    uchar UartRcv;
    uchar *str = "STOP";
    EA = 0; //关闭所有中断
    if(!serial_flag) delay_ms(10); //按键去抖,防止外部干扰
    if(StopButton == 0 || serial_flag)
    {
    
    
        while(!StopButton); //等待放下按键
        P1 = 0xff; //LED灯熄灭
        P2 = P2 | 0x0f; //数码管熄灭
        MotorPin = 0; //电机停转
        SBUF = 0x33; //向上位机发送信息
        while(!TI);
        TI = 0;
        write_com(0x01); //清屏
        ShowString(1, 7, str);
        while(1) //等待按下速度键
        {
    
    
            if(RI) //接收到串口指令
            {
    
    
                RI = 0;
                UartRcv = SBUF; //取指令
                if(UartRcv == '1') break;
            }
            if(SpeedButton == 0) //按下速度键
            {
    
    
                delay_ms(10);
                if(SpeedButton == 0)
                {
    
    
                    while(!SpeedButton); //等待放下按键
                    break;
                }
            }
        }
        InitFan(); //恢复初始状态
    }
    serial_flag = 0;
    EA = 1; //开启所有中断
}

void int_timer0() interrupt 1 //定时器0中断管理系统服务
{
    
    
    static uchar T0count = 0; //定时器0溢出次数
    TH0 = (65536 - 46080) / 256; //50毫秒溢出一次
    TL0 = (65536 - 46080) % 256;
    if(++T0count == 20) //一秒
    {
    
    
        sec++;
        T0count = 0;
        ExamineMotor(); //每一秒检测一次电机故障
        if(FanMode != NORMAL) //非正常模式,管理风扇运行周期
        {
    
    
            if(sec >= RunSecs[FanMode] + StopSecs[FanMode])
            {
    
    
                sec = 0;
            }
            if(sec < RunSecs[FanMode])
            {
    
    
                TR2 = 1; //电机启动
            }
            else
            {
    
    
                TR2 = 0; //电机停转
                MotorPin = 0;
            }
        }
        else if(sec == 20) sec = 0;
    }
}

void int_serial() interrupt 4 //串口中断,接收上位机指令
{
    
    
    uchar UartRcv; //接收到的指令
    RI = 0;
    UartRcv = SBUF; //取指令
    switch(UartRcv)
    {
    
    
    case '1': //执行速度键
    {
    
    
        ChangeSpeed((FanSpeed + 1) % 3);
        break;
    }
    case '2': //执行模式键
    {
    
    
        ChangeMode((FanMode + 1) % 3);
        break;
    }
    case '3': //执行停止键
    {
    
    
        serial_flag = 1; //告诉中断0为串口中断引发,取消按键去抖
        IE0 = 1; //请求外部中断0停止
        break;
    }
    }
}

void int_timer2() interrupt 5 //定时器2中断,100Hz PWM 发生器
{
    
    
    static uchar T2count = 0; //定时器2溢出次数
    TF2 = 0;
    T2count++;
    if(T2count >= 100) //100hz
        T2count = 0;
    if(T2count <= speed_array[FanSpeed]) //占空比跟随风扇设置速度
        MotorPin = 1;
    else MotorPin = 0;
}

I2C.c

/*51单片机I2C总线基础操作驱动*/
#include <reg52.h>
#include <intrins.h>
#include <I2C.h>

sbit SDA = P2 ^ 6;
sbit SCL = P2 ^ 7;
bit ack;             //应答标志位,有应答为1,无应答为0
#define DELAY5US  _nop_();_nop_();_nop_();_nop_();_nop_(); //延时5us
#define uint unsigned int
#define uchar unsigned char

void Start_I2C( )
{
    
    
    SDA = 1;     	         /*将SDA、SCL置为1 */
    SCL = 1;
    DELAY5US;              /*延时5us*/
    SDA = 0;               /*SCL为高时,SDA由高变低*/
    DELAY5US;
    SCL = 0;   	         /*SCL变低,准备发送或接收数据 */
}

void Stop_I2C( )
{
    
    
    SDA = 0;           /*将SDA清0, SCL置1 */
    SCL = 1;
    DELAY5US;
    SDA = 1;  		 /*当SCL为高电平时,SDA由低变高 */
    DELAY5US;
    SCL = 0;
}

void yack(void)       /*产生应答信号*/
{
    
    
    SDA = 0;            /*SDA先清0,发应答信号 */
    SCL = 1;      	  /*SCL由低变高,产生一个时钟*/
    DELAY5US;           /*延时5us */
    SCL = 0;            /*SCL变低,以便继续接收*/
    SDA = 1;
}

void nack(void)
{
    
    
    SDA = 1;          /*DA先置1,发非应答信号 */
    SCL = 1;    	      /*SCL由低变高,产生一个时钟*/
    DELAY5US;
    SCL = 0;          /*时钟线SCL恢复到低电平*/
    SDA = 0;
}

void cack(void)
{
    
    
    SDA = 1;           /*SDA先置1,发非应答信号 */
    SCL = 1;           /*SCL由低变高,产生一个时钟*/
    DELAY5US;
    ack = 0;
    if(SDA == 1) ack = 1;
    SCL = 0;       /*时钟线SCL恢复到低电平*/
}

void SendByte(uchar p)
{
    
    
    uchar  n, temp;
    temp = p;
    for(n = 0; n < 8; n++)         /*一字节为8位,循环8次*/
    {
    
    
        if(temp & 0x80) SDA = 1;    /*将数据线SDA置1或清0*/
        else SDA = 0;
        SCL = 1;                  /*置SCL为高,通知从机开始接收数据*/
        DELAY5US;
        SCL = 0;                  /*SCL变低,准备发送下一位数据*/
        temp = temp << 1;          /*准备下一位要发送的数据*/
    }
}

void RcvByte (uchar *p)
{
    
    
    uchar n, temp;
    for(n = 0; n < 8; n++) 	 /*一字节为8位,循环8次*/
    {
    
    
        SDA = 1;                /*置数据线SDA为高,进入接收方式*/
        SCL = 1;                /*SCL由低变高,产生一个时钟*/
        DELAY5US;
        temp = temp << 1;
        if(SDA == 1) temp = temp | 0x01;
        else temp = temp & 0xfe;
        SCL = 0;                /*时钟线SCL清0*/
    }
    *p = temp;
}

/*多字节写操作子程序WNBYTE
入口参数:n写入的字节数,S0写入数据的首地址,S3从器件地址,S2从器件地址内部地址*/
void Sendnbyte(uchar s3, uchar s2, uchar *s0, uchar n)
{
    
    
    uchar i;
loop:
    Start_I2C( );              	/*发起始信号,启动总线*/
    SendByte(s3);            	/*发送从器件地址*/
    cack( );
    if(ack)
    {
    
    
        goto loop;
    }
    SendByte(s2);           	/*发送器件子地址*/
    cack( );
    if(ack)
    {
    
    
        goto loop;
    }
    for(i = 0; i < n; i++) 		 /*循环n次*/
    {
    
    
        SendByte(*s0);            /*发送一个字节数据*/
        cack( );
        if(ack)
        {
    
    
            goto loop;
        }
        s0++;		                /*指向下一个字节*/
    }
    Stop_I2C( );                     /*发结束信号,结束本次数据传送 */
}

/*多字节读操作子程序RNBYTE
/*入口参数:n写入的字节数,s0读数据存放的首地址,s2从器件地址内部地址,s3从器件写地址,s4从器件读地址*/
void Rcvnbyte(uchar s3, uchar s4, uchar s2, uchar *s0, uchar n)
{
    
    
    uchar i;
loop:
    Start_I2C( );                /*发起始信号,启动总线*/
    SendByte(s3);            /*发送从器件地址*/
    cack( );                  /*应答检测*/
    if(ack)                   /*如果没能应答,重新开始 */
    {
    
    
        goto loop;
    }
    SendByte(s2);            /*发送器件子地址*/
    cack( );                  /*应答检测*/
    if(ack)                   /*如果没能应答,重新开始 */
    {
    
    
        goto loop;
    }
    Start_I2C();                  /*再次发起始信号*/
    SendByte(s4); 	           /*sla+1表示进行读操作*/
    cack();                   /*应答检测*/
    if(ack)                    /*如果没能应答,重新开始 */
    {
    
    
        goto loop;
    }
    for(i = 0; i < n - 1; i++) /*对前n-1个字节发应答信号*/
    {
    
    
        RcvByte(s0);           /*接收数据*/
        yack( );                /*发送应答信号*/
        s0++;
    }
    RcvByte(s0);            /*接收最后一个字节*/
    nack( );                /*发送非应答信号*/
    Stop_I2C( );                /*发结束信号,结束本次数据传送*/
}

I2C.h

#ifndef __I2C_H
#define __I2C_H

#define uint unsigned int
#define uchar unsigned char

extern bit ack;

void Start_I2C( );
void Stop_I2C( );
void yack(void);
void nack(void);
void cack(void);
void SendByte(uchar p);
void RcvByte (uchar *p);
void Sendnbyte(uchar s3, uchar s2, uchar *s0, uchar n);
void Rcvnbyte(uchar s3, uchar s4, uchar s2, uchar *s0, uchar n);

#endif

PCF8574.c

#include <I2C.h>
#include <PCF8574.h>
#define uchar unsigned char
#define uint unsigned int

void delay1(uchar x)
{
    
    
    uchar a, b;
    for(a = x; a > 0; a--)
        for(b = 200; b > 0; b--);
}

bit write_add(uchar date1)//写入数据到IO
{
    
    
    Start_I2C();
    //write_byte(0x7e); 0X3F<<1   0x7e 8574A芯片
    SendByte(0x4e); //bin(0x27<<1) 0x4e 8574 芯片
    cack();
    if(ack == 1) return(0);
    SendByte(date1);
    cack();
    if(ack == 1) return(0);
    Stop_I2C();
    return(0);
}

void write_com(uchar com)                //写命令函数
{
    
    
    uchar com1, com2;
    com1 = com | 0x0f;
    write_add(com1 & 0xfc);
    delay1(2);
    write_add(com1 & 0xf8);
    com2 = com << 4;
    com2 = com2 | 0x0f;
    write_add(com2 & 0xfc);
    delay1(2);
    write_add(com2 & 0xf8);
}

void write_date(uchar date)                //写数据函数
{
    
    
    uchar date1, date2;
    date1 = date | 0x0f;
    write_add(date1 & 0xfd);
    delay1(2);
    write_add(date1 & 0xf9);
    date2 = date << 4;
    date2 = date2 | 0x0f;
    write_add(date2 & 0xfd);
    delay1(2);
    write_add(date2 & 0xf9);
}

void init_lcd()                                        //初始化函数
{
    
    
    write_com(0x33); //显示模式设置
    delay1(6);
    write_com(0x32); //显示模式设置
    delay1(6);
    write_com(0x28); //4位总线,双行显示,显示5×7的点阵字符
    delay1(6);
    write_com(0x01); //清屏
    delay1(6);
    write_com(0x06); //字符进入模式:屏幕不动,字符后移
    delay1(6);
    write_com(0x0c); //显示开,关光标
    //write_LCD_Command(0x0f); //显示开,开光标,光标闪烁
    delay1(6);
}

//显示字符串:第x行第y列显示什么内容
void ShowString(unsigned char x, unsigned char y, unsigned char *str)
{
    
    
    if(x == 1)
    {
    
    
        write_com(0x80 | y - 1);
    }
    if(x == 2)
    {
    
    
        write_com(0xc0 | y - 1);
    }
    //输出字符串
    while(*str != '\0')
    {
    
    
        write_date(*str);
        str++;
    }
}

PCF8574.h

#ifndef __PCF8574_H
#define __PCF8574_H
#define uchar unsigned char
#define uint unsigned int

void delay1(uchar x);
bit write_add(uchar date1);//写入数据到IO
void write_com(uchar com);//写命令函数
void write_date(uchar date);//写数据函数
void init_lcd();//初始化函数
//显示字符串:第x行第y列显示什么内容
void ShowString(unsigned char x, unsigned char y, unsigned char *str);

#endif

App 逻辑图

在这里插入图片描述

开发心得

通过定时器中断可以实现秒数的计时,波特率的发生,以及 PWM 的调制,利用串口中断来进行上位机信息的接收和处理。每个中断的优先级不同,所以如何处理每个中断之间的关系,调度好每个中断处理程序的执行时间对于系统的稳定运行具有重要的意义

就比如说,如果中断处理程序执行的时间过长,就会影响主函数中按键的扫描和数码管的动态显示。所以对于定时器中断的溢出时间可以适当延长来使中断的请求不会过于频繁,本系统的定时器2的溢出时间设置为100us,如果设置过短,在实际测试中会发现数码管的显示会断断续续,按键的检测也会变得不灵敏,这是因为在主函数进行时被过于频繁地中断了

在向上位机发送状态信息时,之所以不采用printf函数发送字符串

  • 一方面是发现 printf 函数被编译进 hex 文件后 hex 文件足足增大了 4KB,如果在多个函数里使用 printf 函数则很有可能造成系统内存资源的耗尽
  • 另一方面是观察到 printf 函数花费的时间比 SBUF=byte 语句要长的多,如果频繁地向上位机发送信息肯定是不利于系统的流畅运行
  • 更重要的是,在C51里printf 是一个公有的函数,如果在多个中断程序里使用 printf 函数,当一个中断程序执行 printf 函数时被更高级的中断程序中断时,恢复断点后很有可能导致 printf 函数执行出错。所以采用 SBUF=byte 发送加密后的单字节信息,将解码的工作交给性能更高的上位机

猜你喜欢

转载自blog.csdn.net/weixin_45715159/article/details/107404798