Bootstrap

华南理工大学基地二轮作品制作——蓝牙电子时钟

目录

一、前言

二、总体的设计思路

(一)使用器材

(二)前期软件准备

(三)制作流程简述

三、队伍分工

四、相关模块的原理和代码

(一)单片机模块

(二)仿真电路及电路模块

(三)闹钟及日历逻辑模块

(四)手机app模块

(五)取模模块

六、个人总结及感想


一、前言

我们小组参加了华南理工大学自动化学院基地组织的二轮创新项目——蓝牙电子时钟的制作,可以说是受益匪浅,故做此记录,也希望能够帮助到其他想要制作蓝牙电子时钟或是学习51单片机、蓝牙串口通信和简易手机软件的编写的相关爱好者。

本次设计的参考资料(顺便相当于做个安利了):

1、CSDN——CSDN - 专业开发者社区(参考内容较多不再一一赘述,有兴趣的可以自查)。

2、bilibili51单片机入门教程——51单片机入门教程-2020版 程序全程纯手打 从零开始入门哔哩哔哩bilibili

及蓝牙APP制作——蓝牙APP制作(App inventor开发APP、AT指令配置蓝牙模块)哔哩哔哩bilibili

3、简书(同CSDN,有兴趣自查即可)

二、总体的设计思路

(一)使用器材

本次蓝牙电子时钟的使用的器材包块:DS1302时钟芯片,STC89C52RC,LCD1602,OLED显示屏,HC-06蓝牙模块,DS18B20温度传感器、5V无源蜂鸣器、三极管Q9012、纽扣电池及其插座、11.0592Hz晶振、37.768kHz晶振、杜邦线若干、10k、2.2k、1k电阻若干等。

o9GNHP.jpg

o9Gsjs.jpg

o9JjZq.jpg

(二)前期软件准备

  1. STC-ISP——负责烧录程序和发送/接受简单数据以及获得简单延时函数及晶振相关函数。

  2. keil uVsion4——主要的单片机程序编写。

  3. Proteus——仿真电路图制作。

  4. PCtoLCD2002——OLED显示汉字及数字的取模。

  5. VS Studio 2019——编写内部部分程序并运行测试实际效果。

  6. app inventor——用最简单的可视化编程方法编写手机app。

(三)制作流程简述

  • 首先,学习所有的相关知识并购买两份器材(一份预备),利用最小开发板分别实现各个功能;

  • 其次,进行app开发(后期对接)和电路仿真运行,设计并确定电路图,进行焊接。

  • 然后,逐步分布实现各个功能:显示精确时间,蓝牙通信,蜂鸣器闹钟,日历及汉字取模,时间修改,app对接。

  • 最后,调试bug以及完善外部显现功能。

三、队伍分工

队员A队员B
学习单片机相关知识,学习手机软件制作学习单片机相关知识,学习仿真电路图
进行闹钟设计和日历设计,学习oled进行基础功能模块设计,学习led,编写基础程序
读懂队员B编写的基础程序和完成电路,准备app对接焊接电路,完成硬件基础设置
进行app、蓝牙串口通信与单片机的对接根据电路出现的实际问题进行调试和修改
调整软件问题,整合程序完成外部形象优化

四、相关模块的原理和代码

(一)单片机模块

1.DS1302时钟芯片

(1)概念及优势

DS1302是由DALLAS公司生产,具有涓细电流充电能力的实时时钟电路,采用串行数据传输,可由掉电电池提供可编程的充电能力,它采用的是独有的32.768K晶振,具有很强的时钟功能,是一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周、时、分、秒进行计时, 具有闰年补偿功能,工作电压为2.5V~5.5V。 DS1302内部有一个31字节的用于临时性存放数据的静态RAM寄存器。

(2) DS1302引脚功能

image-20211124191859049

image-20211124191929700

(3)DS1302内部结构图

image-20211124192051606

(4)工作原理

由以上图可以知道,DS1302与单片机的连线只需3 条,即SCL(7)、I/O(6)、和 RST(5)。 接在CON2上的备用电池通过DS1302的第8脚为 DS1302提供低功耗的电池备份。VCC2在双电源系统中提供主电源,在这种方式下VCC1连接备用电源,当系统没有主电源的情况下,能保持时间信息及数据不丢失。DS1302由VCC1或 VCC2两者中较大者供电。当VCC2大于VCC1 0.2V时,VCC2给DS1302供电。当 VCC2小于VCC1时,DS1302由VCC1供电

  • DS1302读数据工作时序

  • 单字节写入

上升沿置高电平,下降沿置低电平。

image-20211124192500812

从时序图上看,大家可以看得到DS1302是串行驱动的。通过I/O口先写入控制字, 还需要读取相应寄存器的数据。每次在对1302操作前都要对1302进行初始化,需要将RST置为高电平, 并将8位地址和命令信息装入移位寄存器。数据在SCLK的上升沿输入, 前8位指定访问地址命令, 在之后的时钟周期, 读操作时输出的数据, 写操作时输入数据。 时钟脉冲的个数在单字节方式下为8个地址加8位数据。

DS1302写数据的时序操作

image-20211124193244650

数据在SCLK的上升沿输入, 前8位指定访问地址命令, 在之后的时钟周期, 读操作时输出的数据, 写操作时输入数据。时钟脉冲的个数在单字节方式下为8个地址加8位数据。

日历,时钟寄存器配置

image-20211124193614253

BCD码与十进制转换

DS1302输出的是8421编码,8421编码就是我们常说的BCD码。所以写入与读出时均使用BCD码而不是十进制。

最常用的BCD编码,就是使用"0"至"9"这十个数值的二进码来表示。其对应的编码如下: 十进制BCD码 0=0000 ;1=0001 ;2=0010 ;3=0011; 4=0100 ;5=0101 ;6=0110; 7=0111 ;8=1000; 9=1001

以下是BCD码和十进制的转化的举例

十进制转BCD码: 32/10 = 3*16= 48(十进制)= 30(16进制) 32%10 = 2 30+2 = 32 * 16 + 32 % 10 = 32(BCD码)

BCD码转十进制: 51 / 16 = 5 * 10(16进制) = 50(十进制) 51 % 16 = 1 50 + 1 = 51 / 16 + 51 % 16 = 51(十进制)

部分代码如下

#include <REGX52.H>
​
​
sbit DS1302_SCLK = P1^0;
sbit DS1302_IO = P1^1;
sbit DS1302_CE = P1^2;
​
#define DS1302_SECOND           0X80        //定义寄存器的位置
#define DS1302_MINUTE           0X82
#define DS1302_HOUR               0X84
#define DS1302_DATE                 0X86
#define DS1302_MONTH                0X88
#define DS1302_DAY                  0X8A
#define DS1302_YEAR                 0X8C
#define DS1302_WP                       0X8E
​
​
​
​
unsigned char DS1302_Time[]={21,11,21,9,26,20,7};           //定义一个数组用来存时间
​
​
void DS1302_Init(void)          //初始化
{
        DS1302_CE=0;
        DS1302_SCLK=0;
}
​
void DS1302_writebyte(unsigned char Command,Data)           //向1302中写入数据与指令
{       unsigned char i;
        DS1302_CE=1;
        
    for(i=0;i<8;i++)
    {   DS1302_IO=Command&(0x01<<i);
        DS1302_SCLK=1;
        DS1302_SCLK=0;
    }
​
    for(i=0;i<8;i++)
    {   DS1302_IO=Data&(0x01<<i);
        DS1302_SCLK=1;
        DS1302_SCLK=0;
    }
    
    DS1302_CE=0;
        
​
}
unsigned char DS1302_readbyte(unsigned char Command)            //从1302中读指令与数据
{       unsigned char i,Data=0x00;
        Command |=0x01;
        
        DS1302_CE=1;
        
        for(i=0;i<8;i++)
    {   DS1302_IO=Command&(0x01<<i);
        DS1302_SCLK=0;
        DS1302_SCLK=1;
    }
        for(i=0;i<8;i++)
    {   DS1302_SCLK=1;
        DS1302_SCLK=0;
        if(DS1302_IO){Data|=(0x01<<i);}
    }
    
        DS1302_CE=0;
        DS1302_IO=0;
        return Data;
​
}
void DS1302_SetTime(void)               //设置时间的函数
{
        DS1302_writebyte(DS1302_WP,0x00);       //关闭写保护
        DS1302_writebyte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);            //调用了几个写函数来写入时间
        DS1302_writebyte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);                  //写入时data先转化为BDC码的形式
        DS1302_writebyte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
        DS1302_writebyte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
        DS1302_writebyte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
        DS1302_writebyte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
        DS1302_writebyte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
        DS1302_writebyte(DS1302_WP,0x80);       //打开写保护
}
​
void DS1302_ReadTime(void)                  //读取时间函数
{       unsigned char Temp;                         //定义一个中间变量来暂存时间
        Temp=DS1302_readbyte(DS1302_YEAR);
        DS1302_Time[0]=Temp/16*10+Temp%16;      //BDC码转化为十进制,存在数组里
    
        Temp=DS1302_readbyte(DS1302_MONTH);
        DS1302_Time[1]=Temp/16*10+Temp%16;
        
        Temp=DS1302_readbyte(DS1302_DATE);
        DS1302_Time[2]=Temp/16*10+Temp%16;
        
        Temp=DS1302_readbyte(DS1302_HOUR);
        DS1302_Time[3]=Temp/16*10+Temp%16;
    
        Temp=DS1302_readbyte(DS1302_MINUTE);
        DS1302_Time[4]=Temp/16*10+Temp%16;
        
        Temp=DS1302_readbyte(DS1302_SECOND);
        DS1302_Time[5]=Temp/16*10+Temp%16;
        
        Temp=DS1302_readbyte(DS1302_DAY);
        DS1302_Time[6]=Temp/16*10+Temp%16;
}

  1. DS18B20温度传感器

(1)概念及优势

  • 独特的单线接口仅需一个端口引脚进行通讯 (单总线通讯协议)

  • 每个器件有唯一的 64位的序列号存储在内部存储器中

  • 测温范围为-55~+125℃(-67~+ 257℉)

  • 可通过数据线供电。供电范围为 3.0V 到 5.5V。

(2)引脚排列及内部结构

image-20211124195156079

GND ——地

DQ ——数据 I/O

VDD ——可选电源电压

image-20211124195440773

(3)工作原理

  • 操作流程图

image-20211124195855066

image-20211124195942768

  • 读/写时序

写时序:写时序有两种写时序:写 1 时序和写 0 时序。总线控制器通过写 1 时序写逻辑 1 到 DS18B20,写 0 时序写逻辑 0 到 DS18B20。所有写时序必须最少持续 60us,包括 两个写周期之间至少 1us 的恢复时间。当总线控制器把数据线从逻辑高电平拉到 低电平的时候,写时序开始(见图 14)。 总线控制器要生产一个写时序,必须把数据线拉到低电平然后释放,在写时序开 始后的 15us 释放总线。当总线被释放的时候,5K 的上拉电阻将拉高总线。总控 制器要生成一个写 0 时序,必须把数据线拉到低电平并持续保持(至少 60us)。 总线控制器初始化写时序后,DS18B20 在一个 15us 到 60us 的窗口内对 I/O 线采 样。如果线上是高电平,就是写 1。如果线上是低电平,就是写 0。

image-20211124200304385

读时序:总线控制器发起读时序时,DS18B20 仅被用来传输数据给控制器。因此,总线控 制器在发出读暂存器指令[BEh]或读电源模式指令[B4H]后必须立刻开始读时序, DS18B20可以提供请求信息。所有读时序必须最少 60us,包括两个读周期间至少 1us 的恢复时间。当总线控制 器把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持 1us,然 后总线被释放(见图 14)。在总线控制器发出读时序后,DS18B20 通过拉高或拉 低总线上来传输 1 或 0。当传输逻辑 0 结束后,总线将被释放,通过上拉电阻回 到上升沿状态。从 DS18B20 输出的数据在读时序的下降沿出现后 15us 内有效。 因此,总线控制器在读时序开始后必须停止把 I/O 脚驱动为低电平 15us,以读取 I/O 脚状态。

image-20211124200401992

部分代码如下:

#include <REGX52.H>
​
//引脚定义
sbit OneWire_DQ=P3^7;
​
​
unsigned char OneWire_Init(void)
{
    unsigned char i;
    unsigned char AckBit;
    OneWire_DQ=1;
    OneWire_DQ=0;
    i = 247;while (--i);        //Delay 500us
    OneWire_DQ=1;
    i = 32;while (--i);         //Delay 70us
    AckBit=OneWire_DQ;
    i = 247;while (--i);        //Delay 500us
    return AckBit;
}
​
​
void OneWire_SendBit(unsigned char Bit)
{
    unsigned char i;
    OneWire_DQ=0;
    i = 4;while (--i);          //Delay 10us
    OneWire_DQ=Bit;
    i = 24;while (--i);         //Delay 50us
    OneWire_DQ=1;
}
​
unsigned char OneWire_ReceiveBit(void)
{
    unsigned char i;
    unsigned char Bit;
    OneWire_DQ=0;
    i = 2;while (--i);          //Delay 5us
    OneWire_DQ=1;
    i = 2;while (--i);          //Delay 5us
    Bit=OneWire_DQ;
    i = 24;while (--i);         //Delay 50us
    return Bit;
}
​
​
void OneWire_SendByte(unsigned char Byte)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
        OneWire_SendBit(Byte&(0x01<<i));
    }
}
​
​
unsigned char OneWire_ReceiveByte(void)
{
    unsigned char i;
    unsigned char Byte=0x00;
    for(i=0;i<8;i++)
    {
        if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
    }
    return Byte;
}
​
#define DS18B20_SKIP_ROM            0xCC
#define DS18B20_CONVERT_T           0x44
#define DS18B20_READ_SCRATCHPAD     0xBE
​
void DS18B20_ConvertT(void)   //DS18B20开始温度变换
{
    OneWire_Init();
    OneWire_SendByte(DS18B20_SKIP_ROM);
    OneWire_SendByte(DS18B20_CONVERT_T);
}
​
​
float DS18B20_ReadT(void)   //DS18B20读取温度
{
    unsigned char TLSB,TMSB;
    int Temp;
    float T;
    OneWire_Init();
    OneWire_SendByte(DS18B20_SKIP_ROM);
    OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
    TLSB=OneWire_ReceiveByte();
    TMSB=OneWire_ReceiveByte();
    Temp=(TMSB<<8)|TLSB;
    T=Temp/16.0;
    return T;
}
​

3.LCD及OLED模块

[1]LCD模块:LCD1602

(1)引脚介绍

image-20211124202002641

image-20211124202055373

(2)初始化及显示

image-20211124202447496

LCD1602内置国际标准ASCALL码字库,可直接调用显示字符,缺点是不能显示中文。

部分代码如下:

#include <REGX52.H>
​
//引脚配置:
//sbit LCD_RS=P2^6;
//sbit LCD_RW=P2^5;
sbit LCD_RS=P2^5;
sbit LCD_RW=P2^6;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0                 //定义P0引脚
​
​
void LCD_Delay()        //@11.0592MHz
{
    unsigned char i, j;
​
    
    i = 2;
    j = 199;
    do
    {
        while (--j);
    } while (--i);
}
​
​
​
void LCD_WriteCommand(unsigned char Command) //LCD1602写命令, Command 要写入的命令
{
    LCD_RS=0;                               //0为写入指令
    LCD_RW=0;                               //RW=1为写入,RS=0为读出
    LCD_DataPort=Command;
    LCD_EN=1;                               //E=1,使能,使数据有效
    LCD_Delay();
    LCD_EN=0;
    LCD_Delay();
}
​
void LCD_WriteData(unsigned char Data)  //LCD1602写数据,Data 要写入的数据
{
    LCD_RS=1;                           //1为写入数据
    LCD_RW=0;                           //RW=1为写入,RS=0为读出
    LCD_DataPort=Data;
    LCD_EN=1;                           //E=1,使能,使数据有效
    LCD_Delay();
    LCD_EN=0;
    LCD_Delay();
}
​
​
void LCD_SetCursor(unsigned char Line,unsigned char Column)   //LCD1602设置光标位置,Line 行位置,范围:1~2,Column 列位置,范围:1~16
{
    if(Line==1)
    {
        LCD_WriteCommand(0x80|(Column-1));
    }
    else if(Line==2)
    {
        LCD_WriteCommand(0x80|(Column-1+0x40));
    }
}
​
​
void LCD_Init()     //LCD1602初始化函数
{
    LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
    LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
    LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
    LCD_WriteCommand(0x01);//光标复位,清屏
}
​
/**
  *
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)        //在LCD1602指定位置上显示一个字符,范围:1~16要显示的字符
{
    LCD_SetCursor(Line,Column);
    LCD_WriteData(Char);
}
​
​
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)       //在LCD1602指定位置开始显示所给字符串,String 要显示的字符串
{
    unsigned char i;
    LCD_SetCursor(Line,Column);
    for(i=0;String[i]!='\0';i++)
    {
        LCD_WriteData(String[i]);
    }
}
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)  //在LCD1602指定位置开始显示所给数字,Number 要显示的数字,范围:0~65535,Length 要显示数字的长度,范围:1~5
{
    unsigned char i;
    LCD_SetCursor(Line,Column);
    for(i=Length;i>0;i--)
    {
        LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
    }
}
​

[2]OLED模块

oC9orR.jpg

oCmN9I.jpg

(1)原理:

①概念及优势:

OLED,又称为有机电激光显示、有机发光半导体。OLED属于一种电流型的有机发光器件,是通过载流子 的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和 阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相 遇时,产生能量激子,从而激发发光分子最终产生可见光。OLED显示屏比LCD更轻薄、亮度高、功耗低、 响应快、清晰度高、柔性好、发光效率高,能满足消费者对显示技术的新需求。全球越来越多的显示器厂 家纷纷投入研发,大大的推动了OLED的产业化进程。

②SPI接口方式、IIC接口方式:

参考CSDN博客UART, SPI, IIC的详解及三者的区别和联系_小凡的专栏-CSDN博客

本次制作采用SPI接口方式。

③OLED的显示原理:

oCm0u8.jpg

本次制作采用8080接口方式,其对应的并行接口图如上所示:

并行接口的各个信号线的含义:

GND

VCC 电源,3.3V~5V

D0 4 线 ISP 接口模式:时钟线(CLK) PA4

D1 4 线 ISP 接口模式:串行数据线(MOSI)PA3

RES 4 线 ISP 接口模式:命令/数据标志位(RET复位)PA2

DC 命令/数据标志位 A1

CS OLED 片选

模块的8080并口读/写的过程为:

——将数据放到数据口;

——根据要写入/读取的数据的类型,设置DC(RS)为高(数据)/低(命令);

——拉低片选,选中SSD1306;

——接着我们根据是读数据,还是要写数据置RD/WR为低;

——读数据过程:在RD的上升沿, 使数据锁存到数据线(D[7:0])上;

——写数据过程:在WR的上升沿,使数据写入到SSD1306里面;

——拉高CS和DC(RS)。

[

oPCUHI.png

]

④OLED模块显存:

OLED本身是没有显存的,它的显存是依赖于SSD1306提供的

SSD1306显存为128*64bit大小, SSD1306将全部显存分为8页,每页128字节。

OLED相当于64行128列点阵,每个像素点,0点亮,1熄灭。

OLED将纵向64行分为8页,每页8行。

(2)代码(不提供main函数内容):

  1. oled.h

#include <REGX52.H>
#ifndef __OLED_H
#define __OLED_H                 
//#include "sys.h"
//#include "stdlib.h"      
#define OLED_CMD  0 //写命令
#define OLED_DATA 1 //写数据
#define OLED_MODE 0
​
//根据实际引脚位置修改
sbit OLED_CS=P2^4; //片选
sbit OLED_RST =P2^2;//复位
sbit OLED_DC =P2^3;//数据/命令控制
sbit OLED_SCL=P2^0;//时钟 D0(SCLK)
sbit OLED_SDIN=P2^1;//D1(MOSI) 数据
​
//定义命令
#define OLED_CS_Clr()  OLED_CS=0
#define OLED_CS_Set()  OLED_CS=1
​
#define OLED_RST_Clr() OLED_RST=0
#define OLED_RST_Set() OLED_RST=1
​
#define OLED_DC_Clr() OLED_DC=0
#define OLED_DC_Set() OLED_DC=1
​
#define OLED_SCLK_Clr() OLED_SCL=0
#define OLED_SCLK_Set() OLED_SCL=1
​
#define OLED_SDIN_Clr() OLED_SDIN=0
#define OLED_SDIN_Set() OLED_SDIN=1;
​
​
​
​
​
//OLED模式设置
//0:4线串行模式
//1:并行8080模式
​
#define SIZE 16
#define XLevelL     0x02
#define XLevelH     0x10
#define Max_Column  128
#define Max_Row     64
#define Brightness  0xFF 
#define X_WIDTH     128
#define Y_WIDTH     64                                
//-----------------OLED端口定义----------------                        
​

2.oledfont.h(后面会讲解)

#ifndef __OLEDFONT_H
#define __OLEDFONT_H       
unsigned char xdata sjb[][12]={
​
{0x00,0x20,0x18,0xCC,0x48,0x48,0xF8,0x48,0x48,0x48,0x08,0x00},
{0x00,0x04,0x04,0x07,0x04,0x04,0x7F,0x04,0x04,0x04,0x04,0x04},/*"年",0*/
​
{0x00,0x00,0x00,0xF8,0x48,0x48,0x48,0x48,0x48,0xF8,0x00,0x00},
{0x00,0x40,0x60,0x1F,0x02,0x02,0x02,0x42,0x42,0x7F,0x00,0x00},/*"月",1*/
​
{0x00,0x00,0xF8,0x08,0x08,0x08,0x08,0x08,0x08,0xF8,0x00,0x00},
{0x00,0x00,0x7F,0x11,0x11,0x11,0x11,0x11,0x11,0x3F,0x00,0x00},/*"日",2*/
​
{0x00,0xF8,0x88,0x88,0xF8,0x20,0x20,0x20,0x20,0xFC,0x20,0x20},
{0x00,0x3F,0x08,0x08,0x1F,0x00,0x01,0x42,0x40,0x7F,0x00,0x00},/*"时",3*/
​
{0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x04,0x18,0x60,0x80,0x00},
{0x00,0x01,0x40,0x21,0x11,0x0F,0x01,0x41,0x41,0x3F,0x00,0x00},/*"分",4*/
​
{0x00,0x48,0xC8,0xF8,0x44,0x44,0xE0,0x10,0xFC,0x10,0x60,0x80},
{0x00,0x0C,0x03,0x7F,0x01,0x47,0x40,0x20,0x37,0x18,0x06,0x00},/*"秒",5*/
​
{0x00,0x00,0x00,0xF8,0xA8,0xA8,0xA8,0xA8,0xA8,0xF8,0x00,0x00},
{0x00,0x48,0x47,0x4A,0x4A,0x4A,0x7F,0x4A,0x4A,0x4A,0x42,0x40},/*"星",6*/
​
{0x00,0x10,0xFC,0x50,0x50,0xFC,0x10,0xF8,0x48,0x48,0xF8,0x00},
{0x00,0x64,0x1F,0x15,0x15,0x37,0x64,0x1F,0x02,0x42,0x7F,0x00},/*"期",7*/
​
};
​
unsigned char xdata xq[][24]={
    
{0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"一",0*/
​
{0x00,0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00},
{0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20},/*"二",1*/
​
{0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00},
{0x00,0x20,0x20,0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x20,0x20},/*"三",2*/
​
{0x00,0x00,0xF8,0x08,0x08,0xF8,0x08,0xF8,0x08,0x08,0xF8,0x00},
{0x00,0x00,0x7F,0x14,0x13,0x10,0x10,0x17,0x14,0x14,0x7F,0x00},/*"四",3*/
​
{0x00,0x08,0x88,0x88,0xE8,0x98,0x88,0x88,0x88,0x08,0x00,0x00},
{0x20,0x20,0x20,0x30,0x2F,0x20,0x20,0x20,0x3F,0x20,0x20,0x00},/*"五",4*/
​
{0x00,0x40,0x40,0x40,0x40,0x4C,0x70,0x40,0x40,0x40,0x40,0x40},
{0x00,0x20,0x30,0x0C,0x03,0x00,0x00,0x00,0x06,0x08,0x30,0x00},/*"六",5*/
​
{0x00,0x00,0x00,0x00,0x00,0xFC,0x80,0x80,0x80,0x40,0x40,0x40},
{0x00,0x01,0x01,0x01,0x01,0x7F,0x40,0x40,0x40,0x40,0x40,0x70},/*"七",6*/
​
{0x00,0x00,0xF8,0x08,0x08,0x08,0x08,0x08,0x08,0xF8,0x00,0x00},
{0x00,0x00,0x7F,0x11,0x11,0x11,0x11,0x11,0x11,0x3F,0x00,0x00},/*"日",7*/
​
​
​
};
​
​
unsigned char code sj[][16]={
{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00},
{0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},/*"0",0*/
​
{0x00,0x00,0x10,0x10,0xF8,0x00,0x00,0x00},
{0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00},/*"1",1*/
​
{0x00,0x70,0x08,0x08,0x08,0x08,0xF0,0x00},
{0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},/*"2",2*/
​
{0x00,0x30,0x08,0x08,0x08,0x88,0x70,0x00},
{0x00,0x18,0x20,0x21,0x21,0x22,0x1C,0x00},/*"3",3*/
​
{0x00,0x00,0x80,0x40,0x30,0xF8,0x00,0x00},
{0x00,0x06,0x05,0x24,0x24,0x3F,0x24,0x24},/*"4",4*/
​
{0x00,0xF8,0x88,0x88,0x88,0x08,0x08,0x00},
{0x00,0x19,0x20,0x20,0x20,0x11,0x0E,0x00},/*"5",5*/
​
{0x00,0xE0,0x10,0x88,0x88,0x90,0x00,0x00},
{0x00,0x0F,0x11,0x20,0x20,0x20,0x1F,0x00},/*"6",6*/
​
{0x00,0x18,0x08,0x08,0x88,0x68,0x18,0x00},
{0x00,0x00,0x00,0x3E,0x01,0x00,0x00,0x00},/*"7",7*/
​
{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00},
{0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},/*"8",8*/
​
{0x00,0xF0,0x08,0x08,0x08,0x10,0xE0,0x00},
{0x00,0x01,0x12,0x22,0x22,0x11,0x0F,0x00},/*"9",9*/
​
​
​
};
unsigned char xdata sj2[][8]={
{0x00,0x3C,0x44,0x44,0x44,0x44,0x3C,0x00},/*"0",0*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x00,0x08,0x04,0x7C,0x00,0x00,0x00},/*"1",1*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x4C,0x64,0x64,0x54,0x54,0x4C,0x00},/*"2",2*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x64,0x44,0x54,0x54,0x5C,0x2C,0x00},/*"3",3*/
/* (8 X 8 , 方正姚体 )*/
{0x20,0x30,0x30,0x28,0x24,0x7C,0x20,0x00},/*"4",4*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x5C,0x54,0x54,0x54,0x54,0x30,0x00},/*"5",5*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x7C,0x54,0x54,0x54,0x54,0x74,0x00},/*"6",6*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x00,0x44,0x34,0x1C,0x0C,0x04,0x00},/*"7",7*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x6C,0x5C,0x54,0x54,0x5C,0x6C,0x00},/*"8",8*/
/* (8 X 8 , 方正姚体 )*/
{0x00,0x5C,0x54,0x64,0x64,0x54,0x3C,0x00},/*"9",9*/
/* (8 X 8 , 方正姚体 )*/
};
​
unsigned char xdata bd[][2]={
{0x36,0x36},/*":"*/
{0x60,0x60},/*"."*/
};
#endif

3.oled.c(初始化在最下方)

#include "oled.h"
//#include "stdlib.h"
#include "oledfont.h"  
#include "math.h"
       
void delay_ms(unsigned int ms)
{                         
    unsigned int a;
    while(ms)
    {
        a=1800;
        while(a--);
        ms--;
    }
    return;
}
#if OLED_MODE==1
//向SSD1106写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(unsigned char dat,unsigned char cmd)
{
    DATAOUT(dat);       
    if(cmd)
      OLED_DC_Set();
    else 
      OLED_DC_Clr();           
    OLED_CS_Clr();
    OLED_WR_Clr();   
    OLED_WR_Set();
    OLED_CS_Set();    
    OLED_DC_Set();   
}               
#else
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(unsigned char dat,unsigned char cmd)
{   
    unsigned char i;              
    if(cmd)
      OLED_DC_Set();
    else 
      OLED_DC_Clr();          
    OLED_CS_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_CS_Set();
    OLED_DC_Set();        
} 
#endif
void OLED_Set_Pos(unsigned char x, unsigned char y) 
{ 
    OLED_WR_Byte(0xb0+y,OLED_CMD);
    OLED_WR_Byte((((x+2)&0xf0)>>4)|0x10,OLED_CMD);
    OLED_WR_Byte(((x+2)&0x0f),OLED_CMD); 
}   
//开启OLED显示    
​
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!     
void OLED_Clear(void)  
{  
    unsigned char i,n;          
    for(i=0;i<8;i++)  
    {  
        OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
        OLED_WR_Byte (0x02,OLED_CMD);      //设置显示位置—列低地址
        OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
        for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 
    } //更新显示
}
​
​
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示                 
//size:选择字体 16/12 
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr)
{       
    unsigned char c=0,i=0;  
        c=chr-' ';//得到偏移后的值         
        if(x>Max_Column-1){x=0;y=y+2;}
        if(SIZE ==16)
            {
            OLED_Set_Pos(x,y);  
            for(i=0;i<8;i++)
            OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
            OLED_Set_Pos(x,y+1);
            for(i=0;i<8;i++)
            OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
            }
            else {  
                OLED_Set_Pos(x,y+1);
                for(i=0;i<6;i++)
                OLED_WR_Byte(F6x8[c][i],OLED_DATA);
                
            }
}
//m^n函数
unsigned int oled_pow(unsigned char m,unsigned char n)
{
    unsigned int result=1;   
    while(n--)result*=m;    
    return result;
}                 
//显示2个数字
//x,y :起点坐标  
//len :数字的位数
//size:字体大小
//mode:模式   0,填充模式;1,叠加模式
//num:数值(0~4294967295);           
/*void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size2)
{           
    unsigned char t,temp;
    unsigned char 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+(size2/2)*t,y,' ');
                continue;
            }else enshow=1; 
             
        }
        OLED_ShowChar(x+(size2/2)*t,y,temp+'0'); 
    }
} */
//显示一个字符号串
void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr)
{
    unsigned char j=0;
    while (chr[j]!='\0')
    {       OLED_ShowChar(x,y,chr[j]);
            x+=8;
        if(x>120){x=0;y+=2;}
            j++;
    }
}
//显示汉字数组:年、月、日、时、分、秒、星、期
void OLED_ShowCHinese1(unsigned char x,unsigned char y,unsigned char no)
{                   
    unsigned char t,adder=0;
    OLED_Set_Pos(x,y);  
    for(t=0;t<12;t++)
        {
                OLED_WR_Byte(sjb[2*no][t],OLED_DATA);
                adder+=1;
     }  
        OLED_Set_Pos(x,y+1);    
    for(t=0;t<12;t++)
            {   
                OLED_WR_Byte(sjb[2*no+1][t],OLED_DATA);
                adder+=1;
      }                 
}
void OLED_ShowCHinese2(unsigned char x,unsigned char y,unsigned char no)
{                   
    unsigned char t,adder=0;
    OLED_Set_Pos(x,y);  
    for(t=0;t<12;t++)
        {
                OLED_WR_Byte(xq[2*no][t],OLED_DATA);
                adder+=1;
     }  
        OLED_Set_Pos(x,y+1);    
    for(t=0;t<12;t++)
            {   
                OLED_WR_Byte(xq[2*no+1][t],OLED_DATA);
                adder+=1;
      }                 
}
void OLED_ShowNumber(unsigned char x,unsigned char y,unsigned char no)
{                   
    unsigned char t,adder=0;
    OLED_Set_Pos(x,y);  
    for(t=0;t<8;t++)
        {
                OLED_WR_Byte(sj[2*no][t],OLED_DATA);
                adder+=1;
     }  
        OLED_Set_Pos(x,y+1);    
    for(t=0;t<8;t++)
            {   
                OLED_WR_Byte(sj[2*no+1][t],OLED_DATA);
                adder+=1;
      }                 
}
void OLED_ShowNumber2(unsigned char x,unsigned char y,unsigned char no)
{                   
    unsigned char t,adder=0;
    OLED_Set_Pos(x,y);  
    for(t=0;t<8;t++)
        {
                OLED_WR_Byte(sj2[no][t],OLED_DATA);
                adder+=1;
    }   
}
​
void OLED_Showbd(unsigned char x,unsigned char y,unsigned char no)
{
    unsigned char t,adder=0;
    OLED_Set_Pos(x,y);  
    for(t=0;t<2;t++)
        {
                OLED_WR_Byte(bd[no][t],OLED_DATA);
                adder+=1;
    }   
}
​
unsigned char xdata OLED_GRAM[8][128]; //8行128列---->8页128列
/*
函数功能: 画点函数
函数参数:
unsigned char x 横坐标 0~127
unsigned char y 纵坐标 0~63
unsigned char c 显示值(0灭  1亮)
*/
void OLED_DisplayPoint(unsigned char x,unsigned char y,unsigned char c)
{
    unsigned char page=y/8;//0~7
    y=y%8;
    if(c)
    {
       OLED_GRAM[page][x]|=1<<y; 
    }
    else
    {
       OLED_GRAM[page][x]&=~(1<<y);
    } 
}
​
/*
函数功能:画直线
参    数:
x1,y1:起点坐标
x2,y2:终点坐标 
*/
void OLED_DrawLine(unsigned short int x1, unsigned short int y1, unsigned short int x2, unsigned short int y2)
{
    int xerr=0,yerr=0,delta_x,delta_y,distance; 
    int incx,incy,uRow,uCol; 
    unsigned short int t; 
    delta_x=x2-x1; //计算坐标增量 
    delta_y=y2-y1; 
    uRow=x1; 
    uCol=y1; 
    if(delta_x>0)incx=1; //设置单步方向 
    else if(delta_x==0)incx=0;//垂直线 
    else {incx=-1;delta_x=-delta_x;} 
    if(delta_y>0)incy=1; 
    else if(delta_y==0)incy=0;//水平线 
    else{incy=-1;delta_y=-delta_y;} 
    if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴 
    else distance=delta_y; 
    for(t=0;t<=distance+1;t++ )//画线输出 
    {  
        OLED_DisplayPoint(uRow,uCol,1);//画点 
        xerr+=delta_x ; 
        yerr+=delta_y ; 
        if(xerr>distance) 
        { 
            xerr-=distance; 
            uRow+=incx; 
        } 
        if(yerr>distance) 
        { 
            yerr-=distance; 
            uCol+=incy; 
        } 
    }  
} 
​
​
 
/*
函数功能:任意角度画直线 
参    数:
    x,y:坐标
    du :度数
    len:半径
    w  :线段的长度
    c  :颜色值 0或者1
例如:OLED_DrawAngleLine(60,30,45,20,20,1);//角度画线
*/
​
void OLED_DrawAngleLine(unsigned int x,unsigned int y,float du,unsigned int len,unsigned int w,unsigned char c)
{
    int i;
    int x0,y0;
    double k=du*(3.1415926535/180); 
    for(i=len-w;i<len;i++)
    {
        x0=cos(k)*i;
        y0=sin(k)*i;
        OLED_DisplayPoint(x+x0,y+y0,c);
    }
}
​
​
/*函数功能:任意角度画直线 
参    数:
    x,y:坐标
    du :度数
    len :线段的长度
    c  :颜色值 0或者1
例如:OLED_DrawAngleLine(60,30,45,20,20,1);//角度画线
*/
void OLED_DrawAngleLine2(unsigned int x,unsigned int y,int du,unsigned int len,unsigned char c)
{
    int i;
    int x0,y0;
    double k=du*(3.1415926535L/180);
   
    for(i=0;i<len;i++)
    {
        x0=cos(k)*i;
        y0=sin(k)*i;
        OLED_DisplayPoint(x+x0,y+y0,c);
    }
}
​
//初始化SSD1306                        
void OLED_Init(void)
{
 
  OLED_RST_Set();
    delay_ms(100);
    OLED_RST_Clr();
    delay_ms(100);
    OLED_RST_Set(); 
    /*                
    OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
    OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
    OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
    OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
    OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
    OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
    OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
    OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
    OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset   Shift Mapping RAM Counter (0x00~0x3F)
    OLED_WR_Byte(0x00,OLED_CMD);//-not offset
    OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
    OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
    OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
    OLED_WR_Byte(0x12,OLED_CMD);
    OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
    OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
    OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_WR_Byte(0x02,OLED_CMD);//
    OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
    OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
    OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
    OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
    OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
    */
​
    OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
    OLED_WR_Byte(0x02,OLED_CMD);//---set low column address
    OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
    OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
    OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
    OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
    OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
    OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
    OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset   Shift Mapping RAM Counter (0x00~0x3F)
    OLED_WR_Byte(0x00,OLED_CMD);//-not offset
    OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
    OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
    OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
    OLED_WR_Byte(0x12,OLED_CMD);
    OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
    OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
    OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_WR_Byte(0x02,OLED_CMD);//
    OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
    OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
    OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
    OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
    OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
    
    OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/ 
    OLED_Clear();
    OLED_Set_Pos(0,0);  
}  
​
​

(二)仿真电路及电路模块

1.实物图正面

2.部分原理图(手绘稿在写本日志时已被处理)

image-20211124204140816

3.仿真图

image-20211124204359116

(三)闹钟及日历逻辑模块

1.闹钟模块:

(1)基础设置:

(2)蓝牙拓展设置:

利用中断系统和二维数组存储闹钟信息,比较时间储存数组,相等时蜂鸣器响起(基础见上)。

//进入闹钟设定            
        if(SBUF==0x02)
            {do{
                int k1=0;
                int k2=0;
                int i;
                while(RI==0);
                UART_SendByte(SBUF);
                RI=0;//得到了第一次信息
​
                if(SBUF==0x20)//进入设定模式
                {
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第二次信息:用于选择第几个闹钟
                    switch(SBUF)
                        {
                            case 0x00:k1=0;break;
                            case 0x01:k1=1;break;
                            case 0x02:k1=2;break;
                            case 0x03:k1=3;break;
                            case 0x04:k1=4;break;
                            case 0x05:k1=5;break;
                            case 0x06:k1=6;break;
                            case 0x07:k1=7;break;
                        }
                    do
                    {
                        while(RI==0);
                        UART_SendByte(SBUF);
                        RI=0;//第三次信息:确定给到闹钟信息的哪位
                        switch(SBUF)
                        {
                            case 0x0a:k2=0;break;
                            case 0x0b:k2=1;break;
                            case 0x0c:k2=2;break;
                            case 0x0d:k2=3;break;
                            case 0x0e:k2=4;break;
                            default:break;
                        }
                        if(SBUF!=0x50)
                            {
                                while(RI==0);
                                UART_SendByte(SBUF);
                                RI=0;//第四次信息:确定闹钟内的数
                                Alarm_Time[k1][k2]=SBUF;
                            }
                    }while(SBUF!=0x50);
                    SBUF=0xff;
                }
​
                if(SBUF==0x30)//进入删除模式
                {
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第二次信息:选择闹钟
                    switch(SBUF)
                    {
                        case 0x00:k1=0;break;
                        case 0x01:k1=1;break;
                        case 0x02:k1=2;break;
                        case 0x03:k1=3;break;
                        case 0x04:k1=4;break;
                        case 0x05:k1=5;break;
                        case 0x06:k1=6;break;
                        case 0x07:k1=7;break;
                    }
                    for(i=0;i<5;i++)
                    {
                        Alarm_Time[k1][i]=0;
                    }
                    SBUF=0xff;
                }
                
                if(SBUF==0x41)//关闭闹钟
                {
                    if(Alarm_state==1)
                        {Alarm_state=0;}
                    SBUF=0xff;  
                }
                if(SBUF==0x40)//开启闹钟
                {
                    if(Alarm_state==0)
                        {Alarm_state=1;}
                    SBUF=0xff;
                }
                
                }while(SBUF!=0x60);//结束设定闹钟
            
            }

2.日历模块:

利用手机app发送文本,单片机利用中断系统多次接受信息后在oled屏幕上显示日历。

代码及解释如下:

int xdata Rili1[][3]={0};
int xdata Rili2[][17]={0};
//进入日历模式
        if(SBUF==0x03)
            {do{
                int k1,k2,k3,i,i5,i6;
                int a1,a2,a3,a4,a5,a6,a7,a8,a9;
                int temp;
                i=0;
                i5=0;
                i6=1;
                while(RI==0);
                UART_SendByte(SBUF);
                RI=0;//得到第一次信息
                if(SBUF==0x01)//第一次信息:设定日历
                {do{
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第二次信息:选择日历数组位置
                    switch(SBUF)
                    {
                        case 0x11:k1=0;break;
                        case 0x12:k1=1;break;
                        case 0x13:k1=2;break;
                        default:break;
                    }
                    if(SBUF!=0x00)
                    {
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第三次信息:确定日历日期数据
                    while(Rili1[i][k1]!=0)
                    {
                        i++;
                    }
                    Rili1[i][k1]=SBUF;
                    cs=i;
                    }   
                    }while(SBUF!=0x00);//
                    do
                    {
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第四次信息:确定是哪个字
                    switch(SBUF)
                    {
                        case 0x01:k3=0;break;
                        case 0x02:k3=1;break;
                        case 0x03:k3=2;break;
                        case 0x04:k3=3;break;
                        case 0x05:k3=4;break;
                        case 0x06:k3=5;break;
                        case 0x07:k3=6;break;
                        case 0x08:k3=7;break;
                        case 0x09:k3=8;break;
                        case 0x0a:k3=9;break;
                        case 0x0b:k3=10;break;
                        case 0x0c:k3=11;break;                      
                        case 0x0d:k3=12;break;
                        case 0x0e:k3=13;break;
                        case 0x0f:k3=14;break;
                        case 0x10:k3=15;break;
                        default:break;
                    }
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第五次信息:确定字在哪里
                    switch(SBUF)
                    {
                        case 0x01:k2=0;break;
                        case 0x02:k2=1;break;
                        case 0x03:k2=2;break;
                        case 0x04:k2=3;break;
                        case 0x05:k2=4;break;
                        case 0x06:k2=5;break;
                        case 0x07:k2=6;break;
                        case 0x08:k2=7;break;
                        case 0x09:k2=8;break;
                        case 0x0a:k2=9;break;
                        case 0x0b:k2=10;break;
                        case 0x0c:k2=11;break;
                        case 0x0d:k2=12;break;
                        case 0x0e:k2=13;break;
                        case 0x0f:k2=14;break;
                        case 0x10:k2=15;break;
                    }
                    while(Rili2[i5][0]!=0)
                    {i5++;}
                    Rili2[i5][0]=Rili1[i][0]*385+Rili1[i][1]*32+Rili1[i][2];
                    while(Rili2[i5][i6]!=0)
                    {i6++;}
                    Rili2[i5][i6]=(k2+1)*16+k3;
                    i5=0;
                    i6=1;
                }while(SBUF!=0x80);
//排序日历:
                a2=0;
                a4=1;
                while(Rili2[a2][0]!=0)
                a2++;
                for(a1=0;a1<a2;a1++)
                {
                    for(a3=0;a3<16;a3++)
                    {
                        if(Rili2[a1][a4]!=0)
                        a4++;
                    }
                    for(a5=1;a5<a4;a5++)
                    {
                        for(a6=1;a6<a4-a5;a6++)
                        {
                            if(Rili2[a1][a6]>Rili2[a1][a6+1])
                            {
                                temp=Rili2[a1][a6+1];  
                                Rili2[a1][a6+1]=Rili2[a1][a6];  
                                Rili2[a1][a6]=temp;  
                                }
                        }
                    }
                    a4=0;
                }
                for(a7=0;a7<a2-1;a7++)
                {
                    for(a8=0;a8<a2-a7-1;a8++)
                    {
                        if(Rili2[a8][0]>Rili2[a8+1][0])
                        {
                            for(a9=0;a9<17;a9++)
                            {
                                temp=Rili2[a8+1][a9];  
                                Rili2[a8+1][a9]=Rili2[a8][a9];  
                                Rili2[a8][a9]=temp;
                            }
                        }  
                    }
                }
            }
                if(SBUF==0x02)//第一次信息:删除日历
                {do{
                    int i1=0;
                    int i2=0;
                    int i3=0;
                    while(RI==0);
                    UART_SendByte(SBUF);
                    RI=0;//第二次信息:选择要删除的项数
                    while(Rili2[i1][0]!=0)
                        i1++;
                    for(i2=0;i2<17;i2++)
                    {
                        Rili2[SBUF-1][i2]=0;
                    }       
                    for(i3=SBUF-1;i3<i1-2;i3++)
                    {
                        for(i2=0;i2<17;i2++)
                        {
                            Rili2[i3][i2]=Rili2[i3+1][i2];
                            Rili2[i1-1][i2]=0;
                        }
                    }
                        }while(SBUF!=0x71);
                }
                }while(SBUF!=0x61);//退出日历设定
            }
    }RI=0;          
}

利用手机发送数据在单片机中储存,用二维数组保存信息,随后解释信息,按时间排序日历顺序并将其显示在oled屏幕上(由于oled屏幕小无法全部显示,所以只有范例文字,oled详细代码不再展示,感兴趣的可以深入研究)。

(四)手机app模块

本次手机app开发使用了较为简单的app inventor进行开发,在此进行简单介绍

1.简介: App Inventor 原是Google实验室(Google Lab)的一个子计划,由一群Google工程师和勇于挑战的 Google使用者共同参与设计完成。Google App Inventor是一个完全在线开发的Android编程环境,抛弃复杂的 程式代码而使用积木式的堆叠法来完成您的Android程式。除此之外它也正式支持乐高NXT机器人,对Android 初学者或是机器人开发者来说是一大福音。因为对于想要用手机控制机器人的使用者而言,他们不大需要太华 丽的界面,只要使用基本元件例如按钮、文字输入输出即可。

2.下载:

(1)直接登录网站(免下载)

麻省理工学院的服务器登录地址:Redirecting…——这是英语版的。

推荐使用国内的广州服务器:MIT App Inventor——建议使用chrome浏览器打开

其他个人搭建的服务器也可查询csdn或简书

(2)离线版下载:

链接:百度网盘 请输入提取码 ​ 提取码:zzh6

3.内容学习:详情见上的b站教程(个人认为做的很好)。

4.代码展示及界面简介:

oPQ3Pe.png

oPQ88H.png

oPQQUO.png

oPQl5D.png

oPQMVK.png

oPQG2d.png

oPQJxA.png

oPQNrt.png

oPQdVf.png

oPQwa8.png

oPQ0IS.png

oPQDPg.png

5.部分注解:

(1)app inventor 可以直接打开其他软件。

(2)微数据库可以用来存储信息以在下次打开app时保存信息。

(3)蓝牙客户端和服务端可用于蓝牙通信。

(4)app inventor蓝牙通信使用spp蓝牙通信协议。

(5)不知什么原因,软件有小概率被识别为病毒(坑死我了)。

(五)取模模块

本次数字、汉字、图片取模使用软件是PCtoLCD2002。

PCtoLCD2002是专业的取字模软件,采用C语言和汇编语言两种格式,支持逐行、逐列、行列、列行四种取模

方式,可以选择字体、大小、文字的长宽,自动生成你想要的字符。

下载链接:文件 (lanzoui.com)

开始界面:

oP3bss.png

设置:

oP3zJU.png

列行式:按8行一列的顺序进行地址储存,一页结束后向后进行。

根据自己想要的大小改变《每行显示数据》的点阵、索引的大小设置

其他的设置可在开始界面调整,根据个人需要即可。

小字库的代码见上。

注:图片取模较难,且信息量较大,不建议储存多张图片

图片取模的学习链接——ST7920 12864液晶图片取模,显示你想显示的图片。_emouse的技术专栏-CSDN博客

六、个人总结及感想

1.队员A:学会了很多单片机的知识和蓝牙通信的相关知识,也第一次投入到了一个产品制作的全部实践过程中,掌握了项目制作时间安排、团队合作制作和项目制作优化的部分能力,开阔了自己的视野;也希望以后能够投入进行更多的相关实践,培养自己的能力并创造更多有趣的东西。

2.队员B:本次制作电子时钟历时一个月,从什么都不懂得小白,开始看网课,学习C语言和51单片机,学习到了非常非常多的知识。现如今,已能掌握VS2019、keil4、stc-isp和Proteus8.9的应用,可以说多了好几门技能。制作过程中印象最深刻的便是进行温度传感器和时钟模块程序合码时,代码分开就能运行的温度传感器在和时钟代码整合到了一块就失效了,因此我花了一个晚上来调整温度传感器的时序(单总线通讯协议对时序要求非常严格,很多操作都在微秒级),结果没有成功。后来,我觉得可能是因为断电太快导致DS18B20的暂存器中的数据丢失,因此无法读出温度,于是又花了一个网上排查,最后确认数据会储存在eeprom中,并没有丢失。最后一个网上,我将代码一行行注释掉才发现,只要有关DS1302的函数被调用,DS18B20就失效,经过艰难的纠错,得知DS1302芯片的数据使能会以某种奇怪的方式影响到单片机另一边的温度传感器。于是乎,我决定单独打开一个中断用来放置DS18B20有关的函数,保证其不会被干扰,但即使如此代码前头还是要加一行的“DS1302_CE=1;”才能让其工作。这个bug令我印象深刻,实在想不到两个引脚完全不同元器件如何互相影响。最后能把这个成品做出来,很有成就感,也学到了很多有用的知识。

;