3 Star 15 Fork 4

建伟F4nniu / FN1895E-MCU101

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
MCU087.md 39.38 KB
一键复制 编辑 原始数据 按行查看 历史
建伟F4nniu 提交于 2016-12-24 10:44 . 整理完 70-90节。

#第八十七节:郑文显捐赠的工控项目源代码。

开场白:

根据上一节的预告,本来这节要讲关于串口的一个小项目,但是今天中午的时候,有个厦门客户的出现,让我决定先插入这节内容。

他叫郑文显,是做PLC开发的。今天中午他要我帮他写一个工控程序让他来学习,也是基于朱兆祺51单片机学习板的,他想把这个源代码经过自己修改后移植到他自己做的工控板上。我一开始报价4000元,被他砍价到1000元,我看一下也不算很难就答应了下来。刚才下午花了3个小时终于做好了。郑文显爽快的付了款,并且在电话那里跟我讲,他说独乐乐不如众乐乐,资源只有分享才能发挥它的最大价值,因此他决定要把这个源代码捐赠出来给大家一起学。非常感谢他的慈善壮举。种善因,得善果。好人一生平安。他的这个项目不难,跟我第25节内容很类似,略加修改就可以了。具体功能需求请看以下第(2)点。

  • (1) 硬件平台:
  • 基于朱兆祺51单片机学习板。
  • (2) 实现功能:
  • 他的系统要控制2个气缸,没有任何传感器。第1个气缸先伸出去,1秒钟后再收回来。然后第2个气缸再伸出去,1秒钟后再收回来,算完成一个过程,然后重头开始循环下去。每一个过程要计数加1显示在右边的4位数码管上,左边的4位数码管显示设定的最大计数上限,一旦超过这个计数上限就自动停止。有4个按键,一个按键用来启动,一个按键用来急停。另外两个按键是加减按键,用来设置左边显示的最大计数上限。断电要求数据不丢失。如果同时按下加减两个按键,可以清零当前计数的内容。
  • 这4个按键都是独立按键。S1键是加键,S5键是减键,S9键是启动键,S13键是急停键。其中74HC595驱动丝印为D1的LED灯模拟第1个气缸,丝印为D2的LED灯模拟第2个气缸。
  • (3) 源代码讲解如下:
#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_key_time1    20    //按键去抖动延时的时间
#define const_key_time2    20    //按键去抖动延时的时间
#define const_key_time3    20    //按键去抖动延时的时间
#define const_key_time4    20    //按键去抖动延时的时间
#define const_key_time12   20   //按键去抖动延时的时间

#define const_1s  500  //1秒钟大概的定时中断次数



void start24(void);  //开始位
void ack24(void);  //确认位
void stop24(void);  //停止位
unsigned char read24(void);  //读取一个字节的时序
void write24(unsigned char dd); //发送一个字节的时序
unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
void write_eeprom(unsigned int address, unsigned char dd); //往一个地址存入一个字节数据
unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
void write_eeprom_int(unsigned int address, unsigned int uiWriteData); //往一个地址存入一个int类型的数据


void initial_myself();
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01);
void display_drive(); //显示数码管字模的驱动函数
void display_service(); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09, unsigned char ucLedStatusTemp08_01);
void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里

void run(); //设备自动控制程序
void left_to_right();  //从左边移动到右边
void right_to_left(); //从右边返回到左边
void up_to_down();   //从上边移动到下边
void down_to_up();    //从下边返回到上边
void led_update();  //LED更新函数

void delay_timer(unsigned int uiDelayTimerTemp);

sbit key_sr1 = P0 ^ 0; //对应朱兆祺学习板的S1键
sbit key_sr2 = P0 ^ 1; //对应朱兆祺学习板的S5键
sbit key_sr3 = P0 ^ 2; //对应朱兆祺学习板的S9键
sbit key_sr4 = P0 ^ 3; //对应朱兆祺学习板的S13键

sbit key_gnd_dr = P0 ^ 4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr = P2 ^ 7; //蜂鸣器的驱动IO口
sbit led_dr = P3 ^ 5; //作为中途暂停指示灯 亮的时候表示中途暂停

sbit dig_hc595_sh_dr = P2 ^ 0; //数码管的74HC595程序
sbit dig_hc595_st_dr = P2 ^ 1;
sbit dig_hc595_ds_dr = P2 ^ 2;
sbit hc595_sh_dr = P2 ^ 3; //LED灯的74HC595程序
sbit hc595_st_dr = P2 ^ 4;
sbit hc595_ds_dr = P2 ^ 5;

sbit eeprom_scl_dr = P3 ^ 7; //时钟线
sbit eeprom_sda_dr_sr = P3 ^ 6; //数据的输出线和输入线


//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[] =
{
    0x3f,  //0       序号0
    0x06,  //1       序号1
    0x5b,  //2       序号2
    0x4f,  //3       序号3
    0x66,  //4       序号4
    0x6d,  //5       序号5
    0x7d,  //6       序号6
    0x07,  //7       序号7
    0x7f,  //8       序号8
    0x6f,  //9       序号9
    0x00,  //无      序号10
    0x40,  //-       序号11
    0x73,  //P       序号12
};

unsigned char ucKeySec = 0; //被触发的按键编号
unsigned int  uiVoiceCnt = 0; //蜂鸣器鸣叫的持续时间计数器


unsigned char ucDigShow8;  //第8位数码管要显示的内容
unsigned char ucDigShow7;  //第7位数码管要显示的内容
unsigned char ucDigShow6;  //第6位数码管要显示的内容
unsigned char ucDigShow5;  //第5位数码管要显示的内容
unsigned char ucDigShow4;  //第4位数码管要显示的内容
unsigned char ucDigShow3;  //第3位数码管要显示的内容
unsigned char ucDigShow2;  //第2位数码管要显示的内容
unsigned char ucDigShow1;  //第1位数码管要显示的内容

unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp = 0; //临时中间变量
unsigned char ucDisplayDriveStep = 1; //动态扫描数码管的步骤变量

unsigned char ucWd1Part1Update = 1; //左边4位数码管更新显示标志
unsigned char ucWd1Part2Update = 1; //右边4位数码管更新显示标志

unsigned int  uiSetData = 18; //需要被设置的计数上限
unsigned int  uiRunCnt = 0; //实际运行的计数值

unsigned char ucRunTimeFlag = 0; //延时计数器的开关
unsigned int  uiRunTimeCnt = 0; //运动中的时间延时计数器变量

unsigned char ucRunStep = 1; //运动控制的步骤变量
unsigned char ucRunFlag = 0; //是否启动运行的标志   1代表运行

unsigned char ucDelayTimerFlag = 0; //计时器的开关
unsigned int  uiDelayTimer = 0;


unsigned char ucLed_dr1 = 0; //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2 = 0;
unsigned char ucLed_dr3 = 0;
unsigned char ucLed_dr4 = 0;
unsigned char ucLed_dr5 = 0;
unsigned char ucLed_dr6 = 0;
unsigned char ucLed_dr7 = 0;
unsigned char ucLed_dr8 = 0;
unsigned char ucLed_dr9 = 0;
unsigned char ucLed_dr10 = 0;
unsigned char ucLed_dr11 = 0;
unsigned char ucLed_dr12 = 0;
unsigned char ucLed_dr13 = 0;
unsigned char ucLed_dr14 = 0;
unsigned char ucLed_dr15 = 0;
unsigned char ucLed_dr16 = 0;

unsigned char ucLed_update = 1; //刷新变量。每次更改LED灯的状态都要更新一次。


void main()
{
    initial_myself();
    delay_long(100);
    initial_peripheral();
    while(1)
    {
        key_service(); //按键服务的应用程序
        run(); //设备自动控制程序
        display_service(); //显示的窗口菜单服务程序
        led_update();  //LED更新函数
    }
}


void left_to_right()  //从左边移动到右边
{
    ucLed_dr1 = 1; // 1代表左右气缸从左边移动到右边

    ucLed_update = 1; //刷新变量。每次更改LED灯的状态都要更新一次。
}
void right_to_left() //从右边返回到左边
{
    ucLed_dr1 = 0; // 0代表左右气缸从右边返回到左边

    ucLed_update = 1; //刷新变量。每次更改LED灯的状态都要更新一次。
}
void up_to_down()   //从上边移动到下边
{
    ucLed_dr2 = 1; // 1代表上下气缸从上边移动到下边

    ucLed_update = 1; //刷新变量。每次更改LED灯的状态都要更新一次。
}
void down_to_up()    //从下边返回到上边
{
    ucLed_dr2 = 0; // 0代表上下气缸从下边返回到上边

    ucLed_update = 1; //刷新变量。每次更改LED灯的状态都要更新一次。
}


void run() //设备自动控制程序
{
    if(ucRunFlag == 1) //是否启动运行的标志
    {
        switch(ucRunStep)
        {

        case 1:    //机械手从左边往右边移动
            left_to_right();
            ucRunTimeFlag = 0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
            uiRunTimeCnt = 0; //时间计数器清零,为接下来延时1秒钟做准备
            ucRunTimeFlag = 1; //延时计数器开    感谢郑文显捐助本节源代码
            ucRunStep = 2; //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;
        case 2:    //延时1秒
            if(uiRunTimeCnt > const_1s) //延时1秒
            {
                ucRunStep = 3; //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
        case 3:    //机械手从右边往左边移动
            right_to_left();
            ucRunTimeFlag = 0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
            uiRunTimeCnt = 0; //时间计数器清零,为接下来延时1秒钟做准备
            ucRunTimeFlag = 1; //延时计数器开
            ucRunStep = 4; //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;
        case 4:    //延时1秒
            if(uiRunTimeCnt > const_1s) //延时1秒
            {
                ucRunStep = 5; //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
        case 5:    //机械手//从上边移动到下边
            up_to_down();
            ucRunTimeFlag = 0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
            uiRunTimeCnt = 0; //时间计数器清零,为接下来延时1秒钟做准备
            ucRunTimeFlag = 1; //延时计数器开
            ucRunStep = 6; //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;
        case 6:    //延时1秒
            if(uiRunTimeCnt > const_1s) //延时1秒
            {
                ucRunStep = 7; //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
        case 7:    //机械手从下边返回到上边
            down_to_up();
            ucRunTimeFlag = 0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
            uiRunTimeCnt = 0; //时间计数器清零,为接下来延时1秒钟做准备
            ucRunTimeFlag = 1; //延时计数器开  感谢郑文显捐助本节源代码
            ucRunStep = 8; //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;
        case 8:    //延时1秒
            if(uiRunTimeCnt > const_1s) //延时1秒
            {
                uiRunCnt++; //实际运行的计数值累加
                if(uiRunCnt > 9999) //数码管最大显示4位9999,如果超过了,继续默认为9999
                {
                    uiRunCnt = 9999;
                }
                ucWd1Part2Update = 1; //右边4位数码管更新显示

                write_eeprom_int(2, uiRunCnt); //及时把数据存进EEPROM,避免掉电丢失数据

                if(uiRunCnt >= uiSetData) //如果实际的计数大于或者等于设定上限,则停止
                {
                    ucRunFlag = 0; //停止
                    ucRunStep = 1; //切换到第一步为下一次准备
                }
                else
                {
                    ucRunStep = 1; //切换到第一步继续运行
                }
            }
            break;
        }
    }
}


void display_service() //显示的窗口菜单服务程序
{
    //加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样减少了一点指令消耗的时间。
    static unsigned char ucTemp4;   //中间过渡变量
    static unsigned char ucTemp3;   //中间过渡变量
    static unsigned char ucTemp2;   //中间过渡变量
    static unsigned char ucTemp1;   //中间过渡变量

    //左边4位数码管显示设置的计数上限
    if(ucWd1Part1Update == 1) //左边4位数码管要全部更新显示
    {
        ucWd1Part1Update = 0; //及时清零标志,避免一直进来扫描

        //先分解数据用来显示每一位
        ucTemp4 = uiSetData / 1000;
        ucTemp3 = uiSetData % 1000 / 100;
        ucTemp2 = uiSetData % 100 / 10;
        ucTemp1 = uiSetData % 10;


        if(uiSetData < 1000)
        {
            ucDigShow8 = 10; //如果小于1000,千位显示无
        }
        else
        {
            ucDigShow8 = ucTemp4; //第8位数码管要显示的内容
        }

        if(uiSetData < 100)
        {
            ucDigShow7 = 10; //如果小于100,百位显示无
        }
        else
        {
            ucDigShow7 = ucTemp3; //第7位数码管要显示的内容
        }

        if(uiSetData < 10)
        {
            ucDigShow6 = 10; //如果小于10,十位显示无
        }
        else
        {
            ucDigShow6 = ucTemp2; //第6位数码管要显示的内容
        }

        ucDigShow5 = ucTemp1; //第5位数码管要显示的内容
    }

    //右边4位数码管显示实际的计数
    if(ucWd1Part2Update == 1) //右边4位数码管要全部更新显示
    {
        ucWd1Part2Update = 0; //及时清零标志,避免一直进来扫描


        //先分解数据用来显示每一位
        ucTemp4 = uiRunCnt / 1000;
        ucTemp3 = uiRunCnt % 1000 / 100;
        ucTemp2 = uiRunCnt % 100 / 10;
        ucTemp1 = uiRunCnt % 10;


        if(uiRunCnt < 1000)
        {
            ucDigShow4 = 10; //如果小于1000,千位显示无
        }
        else
        {
            ucDigShow4 = ucTemp4; //第8位数码管要显示的内容
        }

        if(uiRunCnt < 100)
        {
            ucDigShow3 = 10; //如果小于100,百位显示无
        }
        else
        {
            ucDigShow3 = ucTemp3; //第7位数码管要显示的内容
        }

        if(uiRunCnt < 10)
        {
            ucDigShow2 = 10; //如果小于10,十位显示无
        }
        else
        {
            ucDigShow2 = ucTemp2; //第6位数码管要显示的内容
        }

        ucDigShow1 = ucTemp1; //第5位数码管要显示的内容
    }
}

void key_scan()//按键扫描函数 放在定时中断里
{
    //加了static关键字后,此局部变量不会每次进来函数都被初始化一次,这样可以记录保存上一次执行本函数后的数值
    static unsigned int  uiKeyTimeCnt1 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock1 = 0; //按键触发后自锁的变量标志
    static unsigned int  uiKeyTimeCnt2 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock2 = 0; //按键触发后自锁的变量标志
    static unsigned int  uiKeyTimeCnt3 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock3 = 0; //按键触发后自锁的变量标志
    static unsigned int  uiKeyTimeCnt4 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock4 = 0; //按键触发后自锁的变量标志

    static unsigned int  uiKeyTimeCnt12 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock12 = 0; //按键触发后自锁的变量标志

    if(key_sr1 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock1 = 0; //按键自锁标志清零
        uiKeyTimeCnt1 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock1 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt1++; //累加定时中断次数
        if(uiKeyTimeCnt1 > const_key_time1)
        {
            uiKeyTimeCnt1 = 0;
            ucKeyLock1 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 1;  //触发1号键
        }
    }
    if(key_sr2 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock2 = 0; //按键自锁标志清零
        uiKeyTimeCnt2 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock2 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt2++; //累加定时中断次数
        if(uiKeyTimeCnt2 > const_key_time2)
        {
            uiKeyTimeCnt2 = 0;
            ucKeyLock2 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 2;  //触发2号键
        }
    }
    if(key_sr3 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock3 = 0; //按键自锁标志清零
        uiKeyTimeCnt3 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock3 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt3++; //累加定时中断次数
        if(uiKeyTimeCnt3 > const_key_time3)
        {
            uiKeyTimeCnt3 = 0;
            ucKeyLock3 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 3;  //触发3号键
        }
    }

    if(key_sr4 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock4 = 0; //按键自锁标志清零
        uiKeyTimeCnt4 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock4 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt4++; //累加定时中断次数
        if(uiKeyTimeCnt4 > const_key_time4)
        {
            uiKeyTimeCnt4 = 0;
            ucKeyLock4 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 4;  //触发4号键
        }
    }

    //5号组合键
    if(key_sr1 == 1 || key_sr2 == 1) //IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
    {
        ucKeyLock12 = 0; //按键自锁标志清零
        uiKeyTimeCnt12 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock12 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt12++; //累加定时中断次数
        if(uiKeyTimeCnt12 > const_key_time12)
        {
            uiKeyTimeCnt12 = 0;
            ucKeyLock12 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 5;  //触发5号组合键

        }
    }


}

void key_service() //按键服务的应用程序
{
    switch(ucKeySec) //按键服务状态切换
    {
    case 1:// 加按键 对应朱兆祺学习板的S1键
        if(ucRunFlag == 0) //如果系统还没运行
        {
            uiSetData++;    //被设置的计数上限
            if(uiSetData > 9999) //最大值是9999
            {
                uiSetData = 9999;
            }
            ucWd1Part1Update = 1; //左边4位数码管更新显示

            write_eeprom_int(0, uiSetData); //及时保存数据进EEPROM,避免掉电丢失
            uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        }
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;

    case 2:// 减按键 对应朱兆祺学习板的S5键
        if(ucRunFlag == 0) //如果系统还没运行
        {
            uiSetData--;
            if(uiSetData > 9999) //unsigned int 类型的0减去1会变成65535(0xffff)
            {
                uiSetData = 0; //最小值是0
            }
            ucWd1Part1Update = 1; //左边4位数码管更新显示

            write_eeprom_int(0, uiSetData); //及时保存数据进EEPROM,避免掉电丢失
            uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        }
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;
    case 3://启动按键 对应朱兆祺学习板的S9键
        if(ucRunFlag == 0 && uiRunCnt < uiSetData) //如果系统还没运行,并且实际运行的次数小于设定的最大次数,则启动
        {
            ucRunFlag = 1;
            ucRunStep = 1;
            uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        }

        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;
    case 4://急停按键 对应朱兆祺学习板的S9键
        ucRunFlag = 0; //急停
        ucRunStep = 1;
        right_to_left(); //从右边返回到左边
        down_to_up();    //从下边返回到上边

        uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;

    case 5://清零的组合按键 对应朱兆祺学习板的(S1+S5)组合键
        if(ucRunFlag == 0) //如果系统还没运行
        {
            uiRunCnt = 0; //实际计数清零
            ucWd1Part2Update = 1; //右边4位数码管更新显示

            write_eeprom_int(2, uiRunCnt); //存入uiRunCnt,内部占用2个字节地址
            uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        }
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;
    }
}



void led_update()  //LED更新函数
{
    //加了static关键字后,此局部变量不会每次进来函数都被初始化一次,这样可以记录保存上一次执行本函数后的数值
    static unsigned char ucLedStatus16_09 = 0; //代表底层74HC595输出状态的中间变量
    static unsigned char ucLedStatus08_01 = 0; //代表底层74HC595输出状态的中间变量

    if(ucLed_update == 1)
    {
        ucLed_update = 0; //及时清零,让它产生只更新一次的效果,避免一直更新。

        if(ucLed_dr1 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x01;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xfe;
        }

        if(ucLed_dr2 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x02;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xfd;
        }

        if(ucLed_dr3 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x04;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xfb;
        }

        if(ucLed_dr4 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x08;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xf7;
        }


        if(ucLed_dr5 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x10;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xef;
        }


        if(ucLed_dr6 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x20;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xdf;
        }


        if(ucLed_dr7 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x40;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0xbf;
        }


        if(ucLed_dr8 == 1)
        {
            ucLedStatus08_01 = ucLedStatus08_01 | 0x80;
        }
        else
        {
            ucLedStatus08_01 = ucLedStatus08_01 & 0x7f;
        }

        if(ucLed_dr9 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x01;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xfe;
        }

        if(ucLed_dr10 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x02;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xfd;
        }

        if(ucLed_dr11 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x04;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xfb;
        }

        if(ucLed_dr12 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x08;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xf7;
        }


        if(ucLed_dr13 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x10;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xef;
        }


        if(ucLed_dr14 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x20;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xdf;
        }


        if(ucLed_dr15 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x40;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0xbf;
        }


        if(ucLed_dr16 == 1)
        {
            ucLedStatus16_09 = ucLedStatus16_09 | 0x80;
        }
        else
        {
            ucLedStatus16_09 = ucLedStatus16_09 & 0x7f;
        }

        hc595_drive(ucLedStatus16_09, ucLedStatus08_01); //74HC595底层驱动函数

    }
}


void display_drive()
{
    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
    switch(ucDisplayDriveStep)
    {
    case 1:  //显示第1位
        ucDigShowTemp = dig_table[ucDigShow1];
        if(ucDigDot1 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xfe);
        break;
    case 2:  //显示第2位
        ucDigShowTemp = dig_table[ucDigShow2];
        if(ucDigDot2 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xfd);
        break;
    case 3:  //显示第3位
        ucDigShowTemp = dig_table[ucDigShow3];
        if(ucDigDot3 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xfb);
        break;
    case 4:  //显示第4位
        ucDigShowTemp = dig_table[ucDigShow4];
        if(ucDigDot4 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xf7);
        break;
    case 5:  //显示第5位
        ucDigShowTemp = dig_table[ucDigShow5];
        if(ucDigDot5 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xef);
        break;
    case 6:  //显示第6位
        ucDigShowTemp = dig_table[ucDigShow6];
        if(ucDigDot6 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xdf);
        break;
    case 7:  //显示第7位
        ucDigShowTemp = dig_table[ucDigShow7];
        if(ucDigDot7 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0xbf);
        break;
    case 8:  //显示第8位
        ucDigShowTemp = dig_table[ucDigShow8];
        if(ucDigDot8 == 1)
        {
            ucDigShowTemp = ucDigShowTemp | 0x80; //显示小数点
        }
        dig_hc595_drive(ucDigShowTemp, 0x7f);
        break;
    }
    ucDisplayDriveStep++;
    if(ucDisplayDriveStep > 8) //扫描完8个数码管后,重新从第一个开始扫描
    {
        ucDisplayDriveStep = 1;
    }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01)
{
    unsigned char i;
    unsigned char ucTempData;
    dig_hc595_sh_dr = 0;
    dig_hc595_st_dr = 0;
    ucTempData = ucDigStatusTemp16_09; //先送高8位
    for(i = 0; i < 8; i++)
    {
        if(ucTempData >= 0x80)dig_hc595_ds_dr = 1;
        else dig_hc595_ds_dr = 0;
        dig_hc595_sh_dr = 0;   //SH引脚的上升沿把数据送入寄存器
        delay_short(1);
        dig_hc595_sh_dr = 1;
        delay_short(1);
        ucTempData = ucTempData << 1;
    }
    ucTempData = ucDigStatusTemp08_01; //再先送低8位
    for(i = 0; i < 8; i++)
    {
        if(ucTempData >= 0x80)dig_hc595_ds_dr = 1;
        else dig_hc595_ds_dr = 0;
        dig_hc595_sh_dr = 0;   //SH引脚的上升沿把数据送入寄存器
        delay_short(1);
        dig_hc595_sh_dr = 1;
        delay_short(1);
        ucTempData = ucTempData << 1;
    }
    dig_hc595_st_dr = 0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
    delay_short(1);
    dig_hc595_st_dr = 1;
    delay_short(1);
    dig_hc595_sh_dr = 0;  //拉低,抗干扰就增强
    dig_hc595_st_dr = 0;
    dig_hc595_ds_dr = 0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09, unsigned char ucLedStatusTemp08_01)
{
    unsigned char i;
    unsigned char ucTempData;
    hc595_sh_dr = 0;
    hc595_st_dr = 0;
    ucTempData = ucLedStatusTemp16_09; //先送高8位
    for(i = 0; i < 8; i++)
    {
        if(ucTempData >= 0x80)hc595_ds_dr = 1;
        else hc595_ds_dr = 0;
        hc595_sh_dr = 0;   //SH引脚的上升沿把数据送入寄存器
        delay_short(1);
        hc595_sh_dr = 1;
        delay_short(1);
        ucTempData = ucTempData << 1;
    }
    ucTempData = ucLedStatusTemp08_01; //再先送低8位
    for(i = 0; i < 8; i++)
    {
        if(ucTempData >= 0x80)hc595_ds_dr = 1;
        else hc595_ds_dr = 0;
        hc595_sh_dr = 0;   //SH引脚的上升沿把数据送入寄存器
        delay_short(1);
        hc595_sh_dr = 1;
        delay_short(1);
        ucTempData = ucTempData << 1;
    }
    hc595_st_dr = 0; //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
    delay_short(1);
    hc595_st_dr = 1;
    delay_short(1);
    hc595_sh_dr = 0;  //拉低,抗干扰就增强
    hc595_st_dr = 0;
    hc595_ds_dr = 0;
}



//AT24C02驱动程序
void start24(void)  //开始位
{

    eeprom_sda_dr_sr = 1;
    eeprom_scl_dr = 1;
    delay_short(15);
    eeprom_sda_dr_sr = 0;
    delay_short(15);
    eeprom_scl_dr = 0;
}


void ack24(void)  //确认位时序
{
    eeprom_sda_dr_sr = 1; //51单片机在读取数据之前要先置一,表示数据输入

    eeprom_scl_dr = 1;
    delay_short(15);
    eeprom_scl_dr = 0;
    delay_short(15);

    //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
    //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
}

void stop24(void)  //停止位
{
    eeprom_sda_dr_sr = 0;
    eeprom_scl_dr = 1;
    delay_short(15);
    eeprom_sda_dr_sr = 1;
}



unsigned char read24(void)  //读取一个字节的时序
{
    unsigned char outdata, tempdata;


    outdata = 0;
    eeprom_sda_dr_sr = 1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
    delay_short(2);
    for(tempdata = 0; tempdata < 8; tempdata++)
    {
        eeprom_scl_dr = 0;
        delay_short(2);
        eeprom_scl_dr = 1;
        delay_short(2);
        outdata <<= 1;
        if(eeprom_sda_dr_sr == 1)outdata++;
        eeprom_sda_dr_sr = 1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
        delay_short(2);
    }
    return(outdata);

}

void write24(unsigned char dd) //发送一个字节的时序
{

    unsigned char tempdata;
    for(tempdata = 0; tempdata < 8; tempdata++)
    {
        if(dd >= 0x80)eeprom_sda_dr_sr = 1;
        else eeprom_sda_dr_sr = 0;
        dd <<= 1;
        delay_short(2);
        eeprom_scl_dr = 1;
        delay_short(4);
        eeprom_scl_dr = 0;
    }


}



unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
{

    unsigned char dd, cAddress;

    cAddress = address; //把低字节地址传递给一个字节变量。

    EA = 0; //禁止中断

    start24(); //IIC通讯开始

    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
    //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

    ack24(); //发送应答信号
    write24(cAddress); //发送读取的存储地址(范围是0至255)
    ack24(); //发送应答信号

    start24(); //开始
    write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
    //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
    ack24(); //发送应答信号
    dd = read24(); //读取一个字节
    ack24(); //发送应答信号
    stop24();  //停止
    EA = 1; //允许中断
    delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

    return(dd);
}

void write_eeprom(unsigned int address, unsigned char dd) //往一个地址存入一个字节数据
{
    unsigned char cAddress;

    cAddress = address; //把低字节地址传递给一个字节变量。


    EA = 0; //禁止中断

    start24(); //IIC通讯开始

    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
    //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
    ack24(); //发送应答信号
    write24(cAddress);   //发送写入的存储地址(范围是0至255)
    ack24(); //发送应答信号
    write24(dd);  //写入存储的数据
    ack24(); //发送应答信号
    stop24();  //停止
    EA = 1; //允许中断
    delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

}


unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
{
    unsigned char ucReadDataH;
    unsigned char ucReadDataL;
    unsigned int  uiReadDate;

    ucReadDataH = read_eeprom(address);  //读取高字节
    ucReadDataL = read_eeprom(address + 1); //读取低字节

    uiReadDate = ucReadDataH; //把两个字节合并成一个int类型数据
    uiReadDate = uiReadDate << 8;
    uiReadDate = uiReadDate + ucReadDataL;

    return uiReadDate;

}

void write_eeprom_int(unsigned int address, unsigned int uiWriteData) //往一个地址存入一个int类型的数据
{
    unsigned char ucWriteDataH;
    unsigned char ucWriteDataL;

    ucWriteDataH = uiWriteData >> 8;
    ucWriteDataL = uiWriteData;

    write_eeprom(address, ucWriteDataH); //存入高字节
    write_eeprom(address + 1, ucWriteDataL); //存入低字节

}


void T0_time() interrupt 1
{
    TF0 = 0; //清除中断标志
    TR0 = 0; //关中断

    if(ucRunTimeFlag == 1) //void run函数中的延时计数器开关
    {
        uiRunTimeCnt++; //延时计数器
    }

    if(ucDelayTimerFlag == 1) //delay_timer函数中的延时计数器开关
    {
        if(uiDelayTimer > 0)
        {
            uiDelayTimer--;   //一气呵成的定时器延时方式的计时器
        }

    }

    key_scan(); //按键扫描函数
    if(uiVoiceCnt != 0)
    {
        uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
        beep_dr = 0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
        //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
    }
    else
    {
        ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
        beep_dr = 1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
        //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
    }
    display_drive();  //数码管字模的驱动函数

    TH0 = 0xfe; //重装初始值(65535-500)=65035=0xfe0b
    TL0 = 0x0b;
    TR0 = 1; //开中断
}

void delay_timer(unsigned int uiDelayTimerTemp)
{
    ucDelayTimerFlag = 0; //延时计时器关  在设置参数前,先关闭计时器
    uiDelayTimer = uiDelayTimerTemp;
    ucDelayTimerFlag = 1; //延时计时器开

    while(uiDelayTimer != 0); //一气呵成的定时器方式延时等待

}

void delay_short(unsigned int uiDelayShort)
{
    unsigned int i;
    for(i = 0; i < uiDelayShort; i++)
    {
        ;   //一个分号相当于执行一条空语句
    }
}

void delay_long(unsigned int uiDelayLong)
{
    unsigned int i;
    unsigned int j;
    for(i = 0; i < uiDelayLong; i++)
    {
        for(j = 0; j < 500; j++) //内嵌循环的空指令数量
        {
            ; //一个分号相当于执行一条空语句
        }
    }
}

void initial_myself()  //第一区 初始化单片机
{
    /* 注释一:
    * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
    * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
    * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
    */
    key_gnd_dr = 0; //模拟独立按键的地GND,因此必须一直输出低电平
    led_dr = 0; //关闭独立LED灯 感谢郑文显捐助本节源代码
    beep_dr = 1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
    hc595_drive(0x00, 0x00); //关闭所有经过另外两个74HC595驱动的LED灯
    TMOD = 0x01; //设置定时器0为工作方式1
    TH0 = 0xfe; //重装初始值(65535-500)=65035=0xfe0b
    TL0 = 0x0b;
}
void initial_peripheral() //第二区 初始化外围
{

    ucDigDot8 = 0; //小数点全部不显示
    ucDigDot7 = 0;
    ucDigDot6 = 0;
    ucDigDot5 = 0;
    ucDigDot4 = 0;
    ucDigDot3 = 0;
    ucDigDot2 = 0;
    ucDigDot1 = 0;
    EA = 1;   //开总中断
    ET0 = 1;  //允许定时中断
    TR0 = 1;  //启动定时中断

    uiSetData = read_eeprom_int(0); //读取uiSetData,内部占用2个字节地址
    if(uiSetData > 9999) //不在范围内
    {
        uiSetData = 0; //填入一个初始化数据
        write_eeprom_int(0, uiSetData); //存入uiSetData,内部占用2个字节地址
    }

    uiRunCnt = read_eeprom_int(2); //读取uiRunCnt,内部占用2个字节地址
    if(uiRunCnt > 9999) //不在范围内
    {
        uiRunCnt = 0; //填入一个初始化数据
        write_eeprom_int(2, uiRunCnt); //存入uiRunCnt,内部占用2个字节地址
    }

}

总结陈词:

再次感谢郑文显的无私奉献。前面第38节到第45节是讲串口的,我的串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,只要第41节内容不是靠时间来判断,而是根据特定关键字来快速识别数据串是否接收完毕,下一节我打算结合我最新的一个项目经验,继续讲一个这方面的例子。欲知详情,请听下回分解----当主机连续不断地发送一串串数据给从机时,从机串口如何快速截取有效数据串。

(未完待续,下节更精彩,不要走开哦)

C
1
https://gitee.com/F4NNIU/FN1895E-MCU101.git
git@gitee.com:F4NNIU/FN1895E-MCU101.git
F4NNIU
FN1895E-MCU101
FN1895E-MCU101
Development

搜索帮助