蓝桥杯单片机组备赛指南请查看 :本专栏第1篇文章
本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。
型号:国信天长4T开发板(绿板),芯片:IAP15F2K61S2
(使用国信天长蓝板也可以完美兼容,与绿板几乎无差别)
万幸,在蓝桥杯比赛开发板上使用SPI通信的只有DS1302
1. 代码目的
正确设置官方开发板外设DS1302,并结合数码管实现电子表的效果。
注意:ds1302外设可以得到:年、周几、月、日、时、分、秒
2. 头文件设置
对于需要进行通信的外设,官方提供了底层ds1302.c文件,我们可以直接采用该文件中已经定义好的函数,来对DS1302进行正确的通信设置。但是,从16年开始为了增加难度,官方提供的代码会故意出现一些错误与遗漏,我们以2023年官方提供的底层文件为参考
下载链接:链接:https://pan.baidu.com/s/1LfixDiinqsOhYbhrAptbMQ 提取码:1111
下面开始介绍如何正确添加DS1302的头文件到主函数文件中。如果采用直接复制代码到主函数文件,则以下步骤可以直接跳过。
步骤一:创建头文件ds1302.h
在keil5中项目中,左侧工程导航栏中,在source group处点击添加现有文件到工程:
将官方提供的ds1302.c文件添加到项目中:
此时我们的左侧工程栏就会出现一个新的文件,我们双击打开。
然后在左侧工程导航栏中,在source group处右键,点击添加新文件到工程:
接下来我们在新建的ds1302.h文件中,键入以下内容:
#ifndef __DS1302_H__
#define __DS1302_H__
void Write_Ds1302_Byte( unsigned char address,unsigned char dat );
unsigned char Read_Ds1302_Byte ( unsigned char address );
#endif
步骤二:修改函数文件ds1302.c
此时我们去点击编译,可以看到以下报错信息:
他这是提醒我们,在ds1302.c文件中,有4个特殊位变量没有进行定义
因此我们需要打开原理图,找到ds1302这个外设:
先看,P17引脚,图上为我们标注了SCK,正对应了ds1302底层文件中的SCK,时钟信号
再看,P23引脚,图上芯片标注了I/O,表示数据输入与输出端口,在spi通信中SDA表示数据引脚,因此P23正对应了ds1302底层文件中的SDA,数据信号
后看,P13引脚,图上芯片标注了RST#,正对应了ds1302底层文件中的RST,复位信号
因此我们需要打开ds1302.c文件,并在文件首部添加以下语句:
此时编译代码,便不会再出现底层文件的报错问题
步骤三:添加头文件ds1302.h
在我们的主函数程序中,添加我们自己新建的头文件:
至此,调用官方代码的环节结束。可以开始编写源程序
源程序全部代码如下:
/* # DS1302代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
//
#include <reg52.h>
#include <intrins.h>
sbit SCK = P1^7;
sbit SDA = P2^3;
sbit RST = P1^3;
void Write_Ds1302(unsigned char temp)
{
unsigned char i;
for (i=0;i<8;i++)
{
SCK = 0;
SDA = temp&0x01;
temp>>=1;
SCK=1;
}
}
//
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )
{
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
Write_Ds1302(dat);
RST=0;
}
//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
unsigned char i,temp=0x00;
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0; _nop_();
SCK=0; _nop_();
SCK=1; _nop_();
SDA=0; _nop_();
SDA=1; _nop_();
return (temp);
}
3. 原理图介绍
对于外设原理图,我们重点需要关注的其实就是各通信相关引脚的接线关系,从而正确的设置引脚,添加头文件。
4. SPI通信简介
SPI (Serial Peripheral interface)是串行外围设备接口
SPI通信对时序的要求,相比ONEWIRE要弱一些。在控制字指令输入后的下一个SCLK时钟信号的上升沿,数据被写入DS1302;在控制字指令输入后的下一个SCLK时钟信号的下降沿,数据从DS1302读出。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。
SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps
SPI接口一般使用四条信号线通信:
SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
5. DS1302操作流程
5.1 spi通信操作时序
我们查看ds1302的各个寄存器值:
我们先看BIT3~BIT0列,可以看出从上到下,依次为:秒、分、时、日、月、周几、年
第一列为READ读地址列,从0x81到0x8D的奇数地址,为我们需要用到的,在从ds1302里面进行数据读取时,通过这些地址我们便可以将其中的数据读出来。
第二列为WRITE写地址列,从0x80到0x8C的偶数地址,为我们需要用到的,在从ds1302里面进行初始数据写入,通过这些地址我们便可以将当前的时间写进去。其中,0x8E是写入控制地址,她的BIT7位WP为写保护控制位,当赋值为1时禁止写入,赋值为0时允许写入。其中,0x80地址,她的BIT7位CH为暂停控制位,当赋值为1时时钟震荡开始,赋值为0时时钟震荡暂停。
编程时,可以先建立三个数组,分别存储需要用到的读地址、写地址、初始时间
然后赋值0打开0x8E地址的WP写保护位,利用循环写入初始时间,最后赋值1关闭WP位
最后利用循环将当前时间读取出来,写入时间数组中
5.2 spi通信程序实现
开发板上的spi通信只有ds1302,因此我们不需要设置ss片选信号。此外开发板上并不需要我们去设置spi通信,我们只需要了解,用于客观题使用。在编程时,我们只需要设置两个环节:
一、ds1302初始化函数
用于向ds1302中写入当前时间,也就是初始时间
二、ds1302读取函数
就是不断将当前时间,从ds1302中读取出来
三、数码管的转换关系
ds1302寄存器中,是将数据按照16进制的关系存储的,因此我们读取出数据后,需要除以16得到十位,取余16得到个位
6. 代码参考
代码效果:将读取ds1302的时间,按照格式 “时时-分分-秒秒” ,在数码管上显示出来
#include <reg52.h>
#include <intrins.h>
#include "ds1302.h"
unsigned char code duanma [18]=
{ 0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 ,
0x80 , 0x90 , 0x88 , 0x80 , 0xc6 , 0xc0 , 0x86 , 0x8e ,
0xbf , 0x7f };
unsigned char code write_ds1302_addr[7] = { 0x80 , 0x82 , 0x84 , 0x86 , 0x88 , 0x8a , 0x8c };
unsigned char code read_ds1302_addr[7] = { 0x81 , 0x83 , 0x85 , 0x87 , 0x89 , 0x8b , 0x8d };
unsigned char ds1302_time[7] = { 0x56 , 0x12 , 0x16 , 0x31 , 0x03 , 0x07 , 0x24 };
void select_HC573 ( unsigned char channal )
{
switch (channal)
{
case 4:
P2 = ( P2 & 0x1f ) | 0x80;
break;
case 5:
P2 = ( P2 & 0x1f ) | 0xa0;
break;
case 6:
P2 = ( P2 & 0x1f ) | 0xc0;
break;
case 7:
P2 = ( P2 & 0x1f ) | 0xe0;
break;
}
}
void state_SMG ( unsigned char pos_SMG , unsigned char value_SMG )
{
select_HC573 ( 6 );
P0 = 0x01 << pos_SMG;
select_HC573 ( 7 );
P0 = value_SMG;
}
void state_SMG_all ( value_SMG )
{
select_HC573 ( 6 );
P0 = 0xff;
select_HC573 ( 7 );
P0 = value_SMG;
}
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
void SMGrunning ()
{
state_SMG ( 0 , duanma[ds1302_time[2]/16] );
Delay1ms();
state_SMG ( 1 , duanma[ds1302_time[2]%16] );
Delay1ms();
state_SMG ( 2 , duanma[16] );
Delay1ms();
state_SMG ( 3 , duanma[ds1302_time[1]/16] );
Delay1ms();
state_SMG ( 4 , duanma[ds1302_time[1]%16] );
Delay1ms();
state_SMG ( 5 , duanma[16] );
Delay1ms();
state_SMG ( 6 , duanma[ds1302_time[0]/16] );
Delay1ms();
state_SMG ( 7 , duanma[ds1302_time[0]%16] );
Delay1ms();
state_SMG_all ( 0xff );
}
void init_sys ()
{
select_HC573 ( 4 );
P0 = 0xff;
select_HC573 ( 5 );
P0 = 0x00;
}
void init_ds1302()
{
unsigned char i;
Write_Ds1302_Byte( 0x8e , 0x00 );
for ( i=0 ; i<7 ; i++ )
{
Write_Ds1302_Byte( write_ds1302_addr[i] , ds1302_time[i] );
}
Write_Ds1302_Byte( 0x8e , 0x80 );
}
void ds1302running ()
{
unsigned char i;
for ( i=0 ; i<7 ; i++ )
{
ds1302_time[i] = Read_Ds1302_Byte ( read_ds1302_addr[i] );
}
}
void main ()
{
init_sys ();
init_ds1302();
while( 1 )
{
ds1302running();
SMGrunning();
}
}