蓝桥杯单片机组备赛指南请查看这篇文章:戳此跳转蓝桥杯备赛指南文章
本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。
型号:国信天长4T开发板(绿板),芯片:IAP15F2K61S2
(使用国信天长蓝板也可以完美兼容,与绿板几乎无差别)
使用iic通信的外设有两个:PCF8951,AT24C02。另外一个的讲解文章参考该专栏上一篇
1. 代码目的
通过对AT24C02的正确设置,可以将单片机开机次数记录下来,并显示在数码管上。并设置独立按键S7为重启模拟按键,即按下S7时,开机次数也会加1。
即,最终实现自增的方式有:拔插电源、按下开关机键、按下松开下载按键S2、按下松开S7
显示格式为:
当开机次数为一位时,显示:-------X
当开机次数为两位时,显示:------XX
当开机次数为三位时,显示:-----XXX
更多位数我们不做考虑,按照前面的逻辑继续即可
2. iic基础知识讲解
IIC总线全称:Inter-Integrated Circuit,是由飞利浦公司开发出来的一种串行总线协议,它是一种多主机的总线,当发生主机竞争时,有总线仲裁机制
IIC总线只有2根信号线,一根是数据线SDA,一根是时钟线SCL。SDA和SCL均为双向信号线,通过上拉电阻接正电源。当总线空闲时,两根线都是高电平。连接到总线上的任一器件,输出低电平,都将使总线的信号变低。
连接总线的器件输出级必须是集电极或漏极开路,以形成线“与”功能。
每个具有IIC接口的设备都有一个唯一的地址,也叫做设备地址。
3. AT24C02芯片讲解
该器件简单来说,就是一个掉电不会丢失数据的存储器
我们将上方的芯片原理图,与开发板硬件原理图进行对比分析。开发板硬件原理图如下:
读写地址:
蓝桥杯开发板采用的是2Kbit的存储器,因此我们只看第一行
对比上方两个原理图,发现A0,A1,A2全部接地,为000,则读与写只由lsb的最后一位决定,当R/W赋值1则读取数据,赋值0则写入数据。结合msb与lsb内容,当我们需要读取数据时,则写入0xA1;需要写入数据时,则写入0xA0
内存地址字节
24C02的末尾是02,因此是一个2K Bit的串行EEPROM存储器;2KBit为21024Bit,除以8等于256,因此内部含有256个字节;又因为=256,因此在24C02里面有8个大小为8字节的页写缓冲器
因此我们可以对其进行的操作内存地址有:0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07共8页地址可选择,每页8个字节,可存储以内的数据大小
若使用指针寻址,会在查询到页尾时自动跳转到页首继续读取
4. iic底层文件处理(添加头文件方式)
官方提供了底层iic.c文件,我们可以直接采用,对PCF8951进行正确的通信设置。但是,从16年开始为了增加难度,代码会故意出现错误与遗漏,我们以2023年官方提供的底层文件为参考
下载链接:链接:https://pan.baidu.com/s/1LfixDiinqsOhYbhrAptbMQ 提取码:1111
步骤一:创建头文件iic.h
在keil5左侧工程导航栏中,在source group处右键点击“add new items”到工程:
在新建的iic.h文件中,添加以下代码:
#ifndef __IIC_H__
#define __IIC_H__
void I2CStart(void);
void I2CStop(void);
void I2CSendByte(unsigned char byt);
unsigned char I2CReceiveByte(void);
unsigned char I2CWaitAck(void);
void I2CSendAck(unsigned char ackbit);
#endif
步骤二:定义iic.c原函数文件
在keil5左侧工程导航栏中,在source group处右键点击“add existing items”到工程,并选中官方提供的底层文件:
此时我们的左侧工程栏就会出现一个新的文件,我们双击打开,并查看原理图:
因此我们需要在iic.c文件中添加如下代码:
步骤三:在主函数中添加我们新建的头文件
至此,对于头文件的操作结束。最终我们的iic.c代码的全部展示如下:
/* # I2C代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <reg52.h>
#include <intrins.h>
sbit sda = P2^1;
sbit scl = P2^0;
#define DELAY_TIME 5
//
static void I2C_Delay(unsigned char n)
{
do
{
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
}
while(n--);
}
//
void I2CStart(void)
{
sda = 1;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 0;
I2C_Delay(DELAY_TIME);
scl = 0;
}
//
void I2CStop(void)
{
sda = 0;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 1;
I2C_Delay(DELAY_TIME);
}
//
void I2CSendByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++){
scl = 0;
I2C_Delay(DELAY_TIME);
if(byt & 0x80){
sda = 1;
}
else{
sda = 0;
}
I2C_Delay(DELAY_TIME);
scl = 1;
byt <<= 1;
I2C_Delay(DELAY_TIME);
}
scl = 0;
}
//
unsigned char I2CReceiveByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++){
scl = 1;
I2C_Delay(DELAY_TIME);
da <<= 1;
if(sda)
da |= 0x01;
scl = 0;
I2C_Delay(DELAY_TIME);
}
return da;
}
//
unsigned char I2CWaitAck(void)
{
unsigned char ackbit;
scl = 1;
I2C_Delay(DELAY_TIME);
ackbit = sda;
scl = 0;
I2C_Delay(DELAY_TIME);
return ackbit;
}
//
void I2CSendAck(unsigned char ackbit)
{
scl = 0;
sda = ackbit;
I2C_Delay(DELAY_TIME);
scl = 1;
I2C_Delay(DELAY_TIME);
scl = 0;
sda = 1;
I2C_Delay(DELAY_TIME);
}
5. 程序时序流程
不管是写入数据或是读取数据,时序操作的前5步都是相同的。有时,我们把读取数据的前5步称为“伪写操作”。在编程时,我们需要做的其实就是在主函数文件中,按照顺序将上述过程写出来。
我们只需要记住1~11个步骤,在比赛时利用官方提供的驱动文件,自己写出调用函数即可。
操作时,时序错误可能发生在一个地方,即第10步产生非应答信号处。不同的底层代码产生非应答信号的实参不同,我们去观察源程序:
根据iic时序要求,当scl拉低为低电平时,将sda保持到高电平产生非应答信号,保持到低电平时产生应答信号。因此,
产生非应答信号:ackbit=1
产生应答信号:ackbit=0
具体比赛时需要根据官方给的源码,选择不同的传入实参
6. 外设代码参考
7. 代码参考
按下松开S2下载按键、拔插电源、重启开关,开发板自身会产生类似于重启的效果
按下松开S7独立按键,为程序定义的伪重启按键
#include <reg52.h>
#include <intrins.h>
#include "iic.h"
sbit S7 = P3^0;
unsigned char power_count = 0;
unsigned char code duanma [18]={ 0xc0 , 0xf9 , 0xa4 , 0xb0 , 0x99 , 0x92 , 0x82 , 0xf8 , 0x80 , 0x90 , 0x88 ,
0x80 , 0xc6 , 0xc0 , 0x86 , 0x8e ,0xbf , 0x7f };
void SMGrunning ();
void write_at24c02 ( unsigned char addr , unsigned char value );
unsigned char read_at24c02 ( unsigned char addr );
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 init_sys ()
{
select_HC573 ( 4 );
P0 = 0xff;
select_HC573 ( 5 );
P0 = 0x00;
}
void state_SMG ( unsigned char pos_SMG , unsigned char value_SMG )
{
select_HC573 ( 7 );
P0 = 0xff;
select_HC573 ( 6 );
P0 = 0x01 << pos_SMG;
select_HC573 ( 7 );
P0 = value_SMG;
}
void state_SMG_all ( unsigned char value_SMG_all )
{
select_HC573 ( 6 );
P0 = 0xff;
select_HC573 ( 7 );
P0 = value_SMG_all;
}
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
void Delay20ms() //@11.0592MHz
{
unsigned char i, j;
i = 216;
j = 37;
do
{
while (--j);
} while (--i);
}
void keyrunning ()
{
if ( S7 == 0 )
{
Delay20ms();
if ( S7 == 0 )
{
while ( S7 == 0 )
{
SMGrunning ();
}
power_count ++;
write_at24c02 ( 0x01 , power_count );
}
}
}
void write_at24c02 ( unsigned char addr_write , unsigned char value_write )
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr_write);
I2CWaitAck();
I2CSendByte(value_write);
I2CWaitAck();
I2CStop();
}
unsigned char read_at24c02 ( unsigned char addr_read )
{
unsigned char power_count_temp;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr_read);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
power_count_temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return power_count_temp;
}
void SMGrunning ()
{
state_SMG ( 0 , duanma[16] );
Delay1ms();
state_SMG ( 1 , duanma[16] );
Delay1ms();
state_SMG ( 2 , duanma[16] );
Delay1ms();
state_SMG ( 3 , duanma[16] );
Delay1ms();
state_SMG ( 4 , duanma[16] );
Delay1ms();
if ( power_count/100 != 0 )
{
state_SMG ( 5 , duanma[power_count/100] );
}
else
{
state_SMG ( 5 , duanma[16] );
}
Delay1ms();
if ( power_count/10 != 0 )
{
state_SMG ( 6 , duanma[power_count/10] );
}
else
{
state_SMG ( 6 , duanma[16] );
}
Delay1ms();
state_SMG ( 7 , duanma[power_count%10] );
Delay1ms();
state_SMG_all ( 0xff );
}
void main ()
{
init_sys ();
power_count = read_at24c02 ( 0x01 );
power_count ++;
write_at24c02 ( 0x01 , power_count );
while ( 1 )
{
SMGrunning ();
keyrunning ();
}
}