资讯专栏INFORMATION COLUMN

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

不知名网友 / 3837人阅读

摘要:当小于时,由供电读数据工作时序单字节写入上升沿置高电平,下降沿置低电平。当总线控制器把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持然后总线被释放见图。因此,总线控制器在读时序开始后必须停止把脚驱动为低电平以读取脚状态。

目录

一、前言

二、总体的设计思路

(一)使用器材

(二)前期软件准备

(三)制作流程简述

三、队伍分工

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

(一)单片机模块

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

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

(四)手机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电阻若干等。

(二)前期软件准备

  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引脚功能

(3)DS1302内部结构图

(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读数据工作时序

  • 单字节写入

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

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

DS1302写数据的时序操作

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

日历,时钟寄存器配置

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 ​​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< 

  1. DS18B20温度传感器

(1)概念及优势

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

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

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

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

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

GND ——地

DQ ——数据 I/O

VDD ——可选电源电压

(3)工作原理

  • 操作流程图

  • 读/写时序

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

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

部分代码如下:

#include ​//引脚定义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< 

3.LCD及OLED模块

[1]LCD模块:LCD1602

(1)引脚介绍

(2)初始化及显示

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

部分代码如下:

#include ​//引脚配置://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模块

(1)原理:

①概念及优势:

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

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

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

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

③OLED的显示原理:

本次制作采用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)。

[

]

④OLED模块显存:

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

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

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

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

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

  1. oled.h

#include #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();        } #endifvoid 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;t120){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~127unsigned char y 纵坐标 0~63unsigned 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<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 

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

1.实物图正面

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

3.仿真图

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

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;a1Rili2[a1][a6+1])                            {                                temp=Rili2[a1][a6+1];                                  Rili2[a1][a6+1]=Rili2[a1][a6];                                  Rili2[a1][a6]=temp;                                  }                        }                    }                    a4=0;                }                for(a7=0;a7Rili2[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 

利用手机发送数据在单片机中储存,用二维数组保存信息,随后解释信息,按时间排序日历顺序并将其显示在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.代码展示及界面简介:

oPQ0IS.png

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/125578.html

相关文章

  • 基于stm32作品设计:懒人蓝牙彩灯、手机APP无线控制ws2812,MCU无线升级程序

    摘要:文章目录一作品背景二功能要求三实现基础功能一首先是要选材二原理图设计二第一版本设计三焊接板四编写单片机程序五下载程序验证四外壳设计一图纸设计二磨砂亚克力板五重新设计六安卓设计一界面设计二程序设计三功能设计作品哔哩哔哩视 ...

    CarlBenjamin 评论0 收藏0
  • 开普勒佛山大数据中心六月底将投入运营

    摘要:园区项目一期机房规划建设万个机架,今年月底中国电信开普勒佛山大数据中心首批机架个将投入运营。近年来,以云计算、大数据为代表的互联网等电子信息产业正呈现出良好发展势头,创新企业层出不穷,聚焦电子信息产业的产业园区也在全国各地遍地开花在这样的大背景之下,佛山市互联网+创新创业产业园应运而生。总投资约80亿元, 总建设规模为40000个机架。佛山市互联网+创新创业产业园选址佛山居住、产业和生态的新...

    megatron 评论0 收藏0
  • app inventor制作蓝牙遥控器

    摘要:本文搭建了一个蓝牙遥控器,可配合等蓝牙模块,对小车实现八方向遥控。其他七个按键同理,很快,一个蓝牙遥控就做好了,修改蓝色框的数字值,可以更改发送命令。将蓝牙模块接上转,用串口助手可以对他进行测试。 ...

    不知名网友 评论0 收藏0
  • 高晓松:区块链也可以有诗与远方

    摘要:如果使用区块链技术则可以降低使用版权的门槛。价值化,艺人利益得到保证高晓松在谈到区块链时,也赞成艺人发行来跳过娱乐公司。通过区块链技术艺人可以更加贴合粉丝,创造出更高效的内容推荐和特色化的消费产品与服务体验。 2019年1月3日,高晓松的《晓说》在朋友圈刷屏了。 这次高晓松没有谈风花雪月、诗与远方,而是在其母校清华大学的教室里,跟学弟学妹们深入浅出地聊起了区块链在文娱产业的革命。 在传...

    frank_fun 评论0 收藏0
  • 高晓松:区块链也可以有诗与远方

    摘要:如果使用区块链技术则可以降低使用版权的门槛。价值化,艺人利益得到保证高晓松在谈到区块链时,也赞成艺人发行来跳过娱乐公司。通过区块链技术艺人可以更加贴合粉丝,创造出更高效的内容推荐和特色化的消费产品与服务体验。 2019年1月3日,高晓松的《晓说》在朋友圈刷屏了。 这次高晓松没有谈风花雪月、诗与远方,而是在其母校清华大学的教室里,跟学弟学妹们深入浅出地聊起了区块链在文娱产业的革命。 在传...

    DDreach 评论0 收藏0

发表评论

0条评论

不知名网友

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<