目录

一,初始化

二、按键

三、数码管

四、单总线协议(onewire.c 的使用)

官方提供的代码:

​编辑需自己添加的部分:

五、SPI协议(ds1302.c 的使用)

 官方提供的代码:

需自己添加的部分:

六、IIC协议(iic.c 的使用)

官方提供的代码:

需自己添加的代码:

1、PCB8591:DA/AD转换

2、AT24C02:EEPROM读写

七、超声波

八、NE555

九、串口

我个人的代码编写习惯或者说模板

一,初始化

         Y4控制LED;Y5控制蜂鸣器和继电器;Y6控制数码管位选;Y7控制数码管段选。

void init74hc138(unsigned char n){
    P2=(P2&0x1f)|(n<<5);
    P2&=0x1f;
}//选通LED、蜂鸣器和继电器、数码管段选和位选

void init(){
    P0=0x00;
    init74hc138(5);
    //初始关闭蜂鸣器、继电器
    P0=0xff;
    init74hc138(4);
    //初始熄灭LED
}

二、按键

         COL2与P42相连

//---------矩阵按键---------
sbit row1=P3^0;
sbit row2=P3^1;
sbit row3=P3^2;
sbit row4=P3^3;
sbit col1=P4^4;
sbit col2=P4^2;
sbit col3=P3^5;
sbit col4=P3^4;
unsigned char ScanKey()
{
	col1=0;col2=col3=col4=1;
	if(row1==0)return 7;
	if(row2==0)return 6;
	if(row3==0)return 5;
	if(row4==0)return 4;
	col2=0;col1=col3=col4=1;
	if(row1==0)return 11;
	if(row2==0)return 10;
	if(row3==0)return 9;
	if(row4==0)return 8;
	col3=0;col2=col1=col4=1;
	if(row1==0)return 15;
	if(row2==0)return 14;
	if(row3==0)return 13;
	if(row4==0)return 12;
	col4=0;col2=col3=col1=1;
	if(row1==0)return 19;
	if(row2==0)return 18;
	if(row3==0)return 17;
	if(row4==0)return 16;
	return 0;
}

//---------独立按键---------
sbit S4=P3^3;
sbit S5=P3^2;
sbit S6=P3^1;
sbit S7=P3^0;
unsigned char ScanKey()
{
	if(S4==0) return 4;
	if(S5==0) return 5;
	if(S6==0) return 6;
	if(S7==0) return 7;
    return 0;
}

//---------公共部分---------
unsigned char keyVal=0,keyold=0,keyUp=0,keyDown=0;

void Key_Loop()
{
	keyVal=ScanKey();
	keyDown=keyVal&(keyold^keyVal);
	keyUp=~keyVal&(keyold^keyVal);

    //if(keyval = n && keyold != n) {按键操作};
    //可以实现按下一次操作一次

	keyold=keyVal;
}

三、数码管

        Y6控制数码管位选;Y7控制数码管段选。 

code unsigned char Seg_Table[] = {
		0xc0, //0
		0xf9, //1
		0xa4, //2
		0xb0, //3
		0x99, //4
		0x92, //5
		0x82, //6
		0xf8, //7
		0x80, //8
		0x90, //9
		0x88, //A 10
		0x83, //b 11
		0xc6, //C 12 
		0xa1, //d 13
		0x86, //E 14
		0x8e, //F 15
        0xff  //熄灭 16
};//共阳数码管段码表
//考试会给,即使不给也能推,使用电脑计算器的程序员模式很方便

unsigned char SegBuff[8]={16,16,16,16,16,16,16,16};
//每位数码管动态值

unsigned char point[8]={0,0,0,0,0,0,0,0};
//每位数码管小数点情况

void Seg(unsigned char addr,number,dot)
{
	P0=0xff;                //消隐
	init74hc138(7);
	P0=0x01<<addr;          //数码管段选
	init74hc138(6);
	P0=Seg_Table[number];   //要显示的数
	if(dot)                 //是否带小数点
		P0&=0x7f;
	init74hc138(7);
}//数码管显示

void Seg_Loop()
{
	static unsigned char i=0;
	Seg(i,SegBuff[i],point[i]);
	i++;
	if(i==8)i=0;
}//数码管刷新

四、单总线协议(onewire.c 的使用)

        使用DS18B20获取温度数据,其采用单总线协议。

官方提供的代码:

void Delay_OneWire(unsigned int t)  
{
	unsigned char i;
	while(t--){
		for(i=0;i<12;i++);
	}
}
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		DQ = 0;					
		DQ = dat&0x01;      
		Delay_OneWire(5);	
		DQ = 1;				
		dat >>= 1;				
	}
	Delay_OneWire(5);
}
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;		
  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;					
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;		
		}	    
		Delay_OneWire(5);
	}
	return dat;
}
bit init_ds18b20(void)
{
  	bit initflag = 0;	
  	DQ = 1;						
  	Delay_OneWire(12);	
  	DQ = 0;					
  	Delay_OneWire(80);	
  	DQ = 1;						
  	Delay_OneWire(10);	
    initflag = DQ;     	
  	Delay_OneWire(5);		
  	return initflag;	
}

需自己添加的部分:

sbit DQ=P1^4;
//记得加上引脚定义
float getT(){
    unsigned int temp;
	float temperature;
	unsigned char teH,teL;
	init_ds18b20();
	Write_DS18B20(0xcc);//跳过ROM操作
	Write_DS18B20(0x44);//启动一次温度转换

	init_ds18b20();
	Write_DS18B20(0xcc);//跳过ROM操作
	Write_DS18B20(0xbe);//发送读指令

	teL=Read_DS18B20();//读取温度值的低字节
	teH=Read_DS18B20();//读取温度值的高字节
	temp=teH<<8|teL;//温度值合并为16位数
	//低四位为小数部分,故需要右移四位,即乘以1/16=0.0625
	temperature=temp*0.0625;
	return temperature;
}

        使用时,由于上电温度数值为85°C,可能影响测评结果,故可以在main()函数中进入while(1)循环之前添加语句:

while(getT()==85);

五、SPI协议(ds1302.c 的使用)

        使用DS1302获取日历时钟数据(BCD编码),其采用SPI协议。

 官方提供的代码:

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/10<<4|dat%10);	
//原代码为Write_Ds1302(dat);需要自己修改
 	RST=0; 
}
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
 	unsigned char i,temp=0x00;
//	unsigned char	dat1,dat2;
 	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_();
//下面这段以及上面的变量定义需要自己写
//	dat1=temp/16;
//	dat2=temp%16;
//	temp=dat1*10+dat2;
	return (temp);			
}

需自己添加的部分:

(原官方代码中亦有需要更改的地方)

unsigned char time[]={50,59,23,0,0,0,0};
//假设初始值为:0年-星期一-0月0日23时59分50秒
void w_ds1302(void){
	unsigned char i,addr=0x80;
	Write_Ds1302_Byte(0x8e,0x00);//关掉写保护
	for(i=0;i<7;i++){
		Write_Ds1302_Byte(addr,time[i]);
		addr=addr+2;
	}
	Write_Ds1302_Byte(0x8e,0x80);//开启写保护
}//写入数据,用于设置初始时间

void r_ds1302(void){
	unsigned char i,addr=0x81;
	for(i=0;i<7;i++){
		time[i]=Read_Ds1302_Byte(addr);
		addr=addr+2;
	}
}获取日历时钟数据

六、IIC协议(iic.c 的使用)

开始和停止条件

        数模/模数转换,以及EEPROM的读写均采用IIC协议。

官方提供的代码:

#define DELAY_TIME	10
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);//t>4.7us
  sda = 0;
	I2C_Delay(DELAY_TIME);
  scl = 0;    
}//SCL为高电平时,SDA由高电平向低电平变化
//总线停止
void I2CStop(void)
{
  sda = 0;
  scl = 1;
	I2C_Delay(DELAY_TIME);
  sda = 1;
	I2C_Delay(DELAY_TIME);
}//SCL为高电平时,SDA由低电平向高电平变化
//通过 I2C 总线发送一个字节(8 位)的数据
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;  
}
//通过 I2C 总线接收一个字节(8 位)的数据
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);
}

需自己添加的代码:

sbit sda=P2^1;
sbit scl=P2^0;

首先是引脚定义,其余代码根据需求不同进行编写,如下: 

1、PCB8591:DA/AD转换

地址字节:ADDRESS BYTE
设备地址为1001,即9

        使用时,0x90为写;0x91为读。 

控制字节:CONTRAL BYTE

        用作D/A转换时,高四为设为0100,低四位0000即可,写作0x40。        

        用作A/D转换时,高四位设置为0000,低四位根据需要,一般为0001和0011(光敏和RB2) ,直接写作1或3均可。

        AIN1输入光敏电阻的电压信号,AIN3输入RB2可调电阻的电压采集信号。

两者均需要实现AD模数转换,基本函数一致。 

需自己添加的代码:

读取模式的总线协议,A/D 转换。
//从设备读取模拟转换后的数据。
unsigned char adc(unsigned char addr)
{
    unsigned char date;
//	EA=0;
	I2CStart();            //S
	I2CSendByte(0x90);     //写入时最低为是0
	I2CWaitAck();         
    I2CSendByte(addr);
	I2CWaitAck();
		
	I2CStart();            //S
	I2CSendByte(0x91);     //ADDRESS+1;PCB8591的设备地址为1001=9
	I2CWaitAck();          //A;主机等待PCB8591的应答信号
	date=I2CReceiveByte(); //DATA BYTE
	I2CSendAck(1);		   //A;主机发送应答信号,由PCB8591接收
	I2CStop();             //P
//	EA=1;
	return date;
}

        adc(1);可实现对光敏电阻电压值的采集;同样的,adc(3);可实现RB2电压值的采集。

需注意,返回的date是unsigned char型,表示最小分压的个数,故电压值为:

V=(float)adc(n)/255*5.0;

15引脚OUT是模拟信号输出,故需要实现DA数模转换。

需自己添加代码:

写入模式的总线协议,D/A 转换。
//向 DAC 设备写入数据以输出模拟信号。
void DAC(unsigned char DAC_date)
{
//	EA=0;
	I2CStart();                //S
	I2CSendByte(0x90);         //ADDRESS+0;发送设备地址及读写状态,0为写
	I2CWaitAck();              //A;主机等待来自PCB8591的应答信号
	I2CSendByte(0x40);         //CONTROL BYTE;写入控制字,
    I2CWaitAck();              //A;主机等待来自PCB8591的应答信号
	I2CSendByte(DAC_date);     //DATA BYTE;写入数据
	I2CWaitAck();              //A;主机等待来自PCB8591的应答信号
                     //根据协议,可以多次写入数据
	I2CStop();                 //P
//	EA=1;
}

使用时,若要输出电压为V,则应令参数DAC_date为:

DAC_date=(unsigned char)(V/5.0)*255;

测量时,将电表表笔分别连接板子右侧D/A引脚和GND引脚即可。

2、AT24C02:EEPROM读写

        直接拿图:

设备地址为1010,即A

需自己添加的代码:

//向 EEPROM 写入数据
void w_eeprom(unsigned char addr,unsigned char date)
{
//	EA=0;
	I2CStart();
	I2CSendByte(0xA0);
	I2CWaitAck();
	I2CSendByte(addr);
    I2CWaitAck();
	I2CSendByte(date);
	I2CWaitAck();
	I2CStop();
//	EA=1;
}

//从 EEPROM 读取数据
unsigned char r_eeprom(unsigned char addr)
{
	unsigned char date;
//	EA=0;
	I2CStart();
	I2CSendByte(0xA0);
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
		
	I2CStart();
	I2CSendByte(0xA1);
	I2CWaitAck();
	date=I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
//	EA=1;
	return date;
}

        PCB8591和AT24C02均使用iic协议,代码类似,具体区别于设备地址。如记不住,考试时可以查两者的手册。手册里也包含了iic协议规定的读写时序,可自行查看。 

七、超声波

跳线帽接法

        使用超声波模块,首先要将J2跳线帽接1-3和2-4。 

#include "intrins.h"
float distance;//距离

//----------引脚定义----------
sbit TX=P1^0;
sbit RX=P1^1;

//-------超声波发送延时-------
void delay(){
    _nop_();_nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();_nop_();
}
发射超声波

//---------发送超声波脉冲---------
void send_wave(){
    unsigned char i=8;
    do{
        TX=1;
        delay();
        TX=0;
    }while(i--);
}
接收超声波

 时间为t=(TH1<<8|TL1)us,超声波速度为v=340m/s=340 00cm/1000 000us=0.034cm/us。则距离distance = t * v / 2 (cm);

//----------接收超声波并计算距离----------
void re_wave(){
    send_wave();
    TH1=0;
    TL1=0;

    TR1=1;        //启动定时器1,开始计时
    while((RX==1)&&(TF1==0));
    TR1=0;        //停止定时器1,结束计时

    // 检查定时器1是否溢出
    if(TF1){
        TF1=0;
        distance=0.0;
    }else{
        distance=(float)(TH1<<8|TL1)*0.017;
        if((int)distance>500) distance=0.0;
    }
}

在main()里需要添加语句:

TMOD|=0x10;
//如果使用定时器0做计数器,则TMOD|=0x01;

八、NE555

      注意使用时需要将跳线帽连接NET_SIG和SIG_OUT

                                        (SIG_OUT即P42,相当于用外部中断计数)

        ne555模块用到了定时器0的计数器模式,对ne555输出的脉冲进行计数。计数器可以利用stc-isp软件生成的代码进行修改。

因为计数器不需要时钟,所以我们删除 AUXR |= 0x80即可;

同时将TH0和TL0都清零。

TMOD寄存器中的低四位是用来控制定时器0的模式的,我们需要将TMOD.1/TMOD.0置为01,将TMOD.2置为0。即在 TMOD &= 0xf0; 后添加TMOD |= 0x05;

在代码最后加上 ET0 = 0关中断;

void Timer0_Init(void)		//100微秒@12.000MHz
{
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x05;
	TL0 = 0x00;				//设置定时初始值
	TH0 = 0x00;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 0;				//禁止使能定时器0中断
}

 读取计数器的数值

//读计数器计数值
unsigned int get_count(){
	unsigned int temp;
	temp=TH0<<8|TL0;
	TH0=0X00;
	TL0=0X00;
	if(TF0){//如果检测到T0溢出,证明了频率超过了65535
		return 0xffff;
	}else{
		return temp;
	}
}

 使用T1作为计时器,每秒读取一次,则读取的数值即为频率。

unsigned int ne555_count;

void Timer1_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x40;			//定时器时钟1T模式
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x20;				//设置定时初始值
	TH1 = 0xD1;				//设置定时初始值
	TF1 = 0;				//清除TF1标志
	TR1 = 1;				//定时器1开始计时
	ET1 = 1;				//使能定时器1中断
	EA=1;
}
void Timer1_Isr(void) interrupt 3
{
	static unsigned int count1=0;
	count1++;
	if(count1==1000){
		ne555_count=get_count();
		count1=0;
	}
}

九、串口

直接使用烧录软件生成即可

//串口初始化设置
void UartInit(void)		//9600bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x40;		//定时器时钟1T模式
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0xC7;			//设置定时初始值
	TH1 = 0xFE;			//设置定时初始值
	ET1 = 0;			//禁止定时器中断
	TR1 = 1;			//定时器1开始计时
    //下面需要自行添加
//	ES =1;  //中断入口
	EA =1;
}
unsigned char rdat;

//串口发送一个字符
void uart_send_byte(unsigned char date){
		SBUF = date;//发送数据
		while(TI==0);//等待数据发送完成
		TI=0;
}
//串口发送一个字符串
void uart_send_str(unsigned char *str){
	while(*str != '\0'){
		uart_send_byte(*str++);
	}
}

//串口中断服务函数
void isr_uart() interrupt 4
{
	//如果接收到数据
	if(RI){
		RI=0;
		rdat=SBUF;
	}
}

还有另一种发送字符串的写法:

使用了stdio.h的printf()标准输出函数,由于其与库中的putchar()相关联,故需要在main.c里对putchar()函数进行重载:

//添加头文件
#include "stdio.h"

char putchar(char ch){
    SBUF=ch;
    while(!TI);
    TI=0;
    return ch;
}

 使用示例:

float Vrb2;
...
void main(){
    ...
    while(1){
        ...
        printf("电压值为:%.2f \r\n",Vrb2);
        //串口发送电压值,保留两位小数
        ...
    }
}

我个人的代码编写习惯或者说模板

sys.h

#ifndef __SYS_H__
#define __SYS_H__

#include <STC15F2K61S2>

//sys.c
//seg_key.c
//iic.c
//ds1302.c
//onewire.c
//以上.c文件的变量和函数均在sys.h中声明一编

...//extern 声明外部变量
...//函数声明

#endif

main.c

#include "sys.h"

...//相关变量或标志定义

...//中断、串口等初始化函数

void main(){
    ...//初始化
    while(1){
        ...
    }
}

...//中断服务函数和串口服务

 sys.c

#include "sys.h"

...//整个项目需要用到的一些变量


void init74hc138(unsigned char n){
    ...
}//74HC138译码器选通控制

void init(){
    ...
}//系统初始化

void led(unsigned char n){
    ...
}//选择LED指示灯点亮

void buzz(bit flag){
    ...
}//蜂鸣器

void relay(bit flag){
    ...
}//继电器

...

seg_key.c

#include "sys.h"

code unsigned char Seg_Table[]={};//数码表
unsigned char Seg_Buff[8]={};//数码管缓存
unsigned char keyval=0,keyold=0,keyup,keydown;//按键相关

void key_scan(){
    ...
}

void Key_Loop(){
    ...
}

void seg(unsigned char addr,num){
    ...
}

void Seg_Loop(){
    ...
}

void ui0(){
    Seg_Buff[7]=N;
        ...
    Seg_Buff[0]=M;
}
      ........

void uin(){
    Seg_Buff[7]=N;
        ...
    Seg_Buff[0]=M;
}

void seg_ui(){
    switch(UI){
        case 0: ui0(); break;
                ...
        case n: uin(); break;
    }
}//界面选择

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐