蓝桥杯(单片机组)学习笔记(四)---------PWM/EEPROM

1.PWM

用一个定时器的实现PWM的方法。以周期为1ms(1kHZ)为例,要产生其它频率的PWM波,程序中只需作简单修改即可。用一个定时器时(如定时器T0),首先要确定PWM的周期T和占空比D,确定了这些以后,就可以用定时器产生一个时间基准t,比如定时器溢出n次的时间是PWM的高电平的时间,则DT=nt,类似的可以求出PWM低电平时间需要多少个时间基准n。

因为这里我们是产生周期为1ms(1kHZ)的PWM,所以可设置中断的时间基准为0.01ms,,然后中断100次即为1ms。在中断子程序内,可设置一个变量如time,在中断子程序内,有三条重要的语句:

1、当time>=100时,time清零(此语句保证频率为1kHZ);

2、当time>n时(n应该在0-100之间变化开),让单片相应的I/O口输出低电平;

3、当time<=n时,让单片相应的I/O口输出高电平,此时占空比就为%n。
所以基本上就是设一个基准,调整占空比和频率就好了。


sbit PWM=P2^0;//  P2.0输出pwm
uchar time;  // 定义占空比的变量
void tim0() interrupt 1
{
    TR0=0;//赋初值时,关闭定时器
    TH0=0xff;//(65536-10)/256;//赋初值定时
    TL0=0xf7;//(65536-10)%256;//0.01ms
    TR0=1;//打开定时器
 
    time++;
    if(time>=100)  //1khz
      time=0;   
    if(time<=30)   //占空比%30,可改
      PWM=1;  
    else PWM=0;
}

如果要使占空比可调,比如做一个呼吸灯,就要多一些基准变量
全部代码(之前的不是标准的呼吸灯,改了之后好多了)

#include "STC12C5A60S2.H"
typedef unsigned char uchar;
typedef unsigned int uint;
/*10Khz --- 100us----10ms更新一次pwm_n*/
int pwm_conut =0;//实时次数
int pwm_n = 0;//pwm的辉度
int pwm_n_time  =0;//PWM辉度时间 
bit turn_flag = 1;//方向标志位

void Timer0Init();

void All_Init()
{
	P2 = ((P2&0X1F)|0X80);
	P0 = 0XFF;
	
	P2 = ((P2&0X1F)|0XA0);
	P0 = 0X00;
	P2 = ((P2&0X1F)|0XC0);
	P0 = 0X00;
	
	P2 = ((P2&0X1F)|0XE0);
	P0 = 0XFF;
}


void main(void)
{
		All_Init();
		Timer0Init();
    while (1)
    {
        if(pwm_conut> pwm_n)
        {
          P2 = (P2&0X1F|0X80);
	        P0 = 0XFE;
        }
        else
        {
          P2 = (P2&0X1F|0X80);
	        P0 = 0XFF;

        }
        

    }
    
}


void Timer0Init(void)		//1微秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
  EA = 1;
  ET0 = 1;
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}


void Time0Inter() interrupt 1//1us
{
 
    if(++pwm_conut == 100)//100us为一个周期
    {
       pwm_conut = 0; 
    }
		
    if(++pwm_n_time == 10000)//10ms更新一次pwm_n
    {
        pwm_n_time = 0;
        if(turn_flag)//如果为正向
        {
            pwm_n+= 10;
            if(pwm_n == 100)
            {
                turn_flag =0;
            }
        }
        else//如果为负向
        {
            pwm_n -= 10;
            if(pwm_n == 20)//不直接灭了,不好看
            {
                turn_flag =1;
            }
        }
        

    }
}






































接下来是对I2C通信的一个学习,包括
·EEPROM存储芯片AT24C02的使用
·AD/DA转换芯片PCF8591的使用
·I2C通信的应用

2.EEPROM

一.基础了解
EPROM (Electrically Erasable Programmable Read-Only Memory),带电可擦可编程只读存储器–一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般即插即用。(lqb板子上就是AT24C02芯片)。一根为SCL时钟信号线,另一根为SDA数据信号线(传输数据和地址也包括开始结束和ACK信号)这两根线必须接10k的上拉电阻。所以一开始SCL,SDA都是高电平。这两根线与EEPROM的SCL,SDA线相连。iic总线每次传输8bit(一个字节)的数据。而且iic总线是串行主从结构,读和写都由主机操作,从机只能接受。
24C02中有一个IP地址,它的目的就是为了iic总线能识别挂在总线上的从机。因为IIC总线可以挂载不止一个的设备。

根据24c04的数据手册可知,它有7位地址,还有一位读/写位(R/W)。1为读操作0为写操作。
但是24C04的前四位被厂家固化为1010,所以芯片管脚上只剩3个地址管脚(A0,A1,A2)。
24C04它还有一位写保护位WP(write protect)。一般接低电平(接地),用于可读可写。
A0 A1 A3如果都接地,那么24C04的地址为1010 000,也就是0xA1/0XA0

A0 A1 A3如果接高电平,那么从机设备地址为1010 111,也就是0xAF
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
要对EEPROM的内容进行修改,主要用到下面的程序:
①读取
②写入
③删除扇区

二、EEPROM单字节读写操作时序
1、EEPROM写数据流程
(1)第一步,首先写IIC的起始信号,紧接着写上首字节,即我们前边讲的IIC的器件地址,并且在读写方向上选择“写”操作。
(2)第二步,发送数据的存储地址。例如24C02芯片一共有256个字节的存储空间,地址从0x00~0xFF,我们想把数据存储在芯片的哪个位置,此刻写入的就是对应的那个地址。
(3)第三步,发送要存储的数据第一个字节、第二个字节……,注意在写数据的过程中,EEPROM每个字节都会回应一个“应答位0”,来告诉我们在EEPROM芯片中写入数据成功,如果没有回应答位,说明写入数据不成功。
在写数据的过程中,每成功写入一个字节数据,EEPROM芯片的存储地址就会自动加1,当加到0xFF后,再写一个字节数据,地址就会溢出又变成了0x00。

2、EEPROM读数据流程
(1)第一步,首先写IIC的起始信号,紧接着写上首字节,即我们前边讲的IIC的器件地址,并且在读写方向上选择“写”操作。
这个地方可能有人会感到很诧异,我们明明是读数据为何方向也要选“写”呢?刚才说过了,24C02芯片一共有256个地址,我们选择写操作,是为了把所要读的数据的存储地址先写进去,告诉EEPROM我们要读取哪个地址的数据。这就如同我们打电话,先拨总机号码(EEPROM的器件地址),而后还要继续拨分机号码(EEPROM的数据地址),而拨分机号码这个动作,主机仍然是发送方,方向依然是“写”。
(2)第二步,发送要读取的数据的地址,注意是地址而非存储在EEPROM中的数据,通知EEPROM我要读取哪个分机的信息。
(3)第三步,重新发送IIC的起始信号和器件地址,并且在读写方向位上选择“读”操作。
这三步当中,每一个字节实际上都是在“写”,所以每一个字节EEPROM都会回应一个“应答位0”。
(4)第四步,读取从器件发回的数据,读一个字节,如果还想继续读下一个字节,就发送一个“应答位ACK(0)”,如果不想读了,告诉EEPROM,我不想要数据了,别再发数据了,那就发送一个“非应答位NAK(1)”。
和写操作规则一样,每成功读取一个字节数据,EEPROM芯片的存储地址就会自动加1,那如果我们想继续往下读,给EEPROM一个ACK(0)低电平,然后再继续给SCL时钟线完整的时序,EEPROM会继续往外送数据。如果我们不想读了,要告诉EEPROM不要数据了,那我们直接给一个NAK(1)高电平即可。这个地方大家要从逻辑上理解透彻,不能简单的靠死记硬背了,一定要理解明白。

3、总结
(1)在通常的EEPROM应用中,单片机是主机,24C02是从机。
(2)无论是读操作还是写操作,SCL时钟线始终都是由主机控制的。
(3)写数据的时候应答信号由从机给出,表示从机是否正确接收了数据。
(4)读数据的时候应答信号则由主机给出,表示是否继续读下去。
在这里插入图片描述
在这里插入图片描述
部分代码块

void Write_eeprom(uchar addr,uchar val);
uchar Read_eeprom(uchar addr);
void Display(uint dat);

void main(void)
{
	uchar reset_cnt = 0;  //开机次数存储 (最大存储值255)
	All_Init();
	reset_cnt = Read_eeprom(0x00);  //从AT24C02地址0x00中读取数据
	reset_cnt++;  //掉电不消失
	Write_eeprom(0x00,reset_cnt);  //向AT24C02地址0x00中写入数据 最大256
	while(1)
	{
		Display(reset_cnt);
	}
}

void Display(uint dat)
{
	uchar i,j = 0;
	uchar str[3];
	uchar d = dat;
	
	for (i = 0; i < 3; i++)
	{
		str[i] = d % 10;
		d /= 10;       //不断取出个位的数
	}
	
	for(j = 0; j<3;j++)//要显示几位就循几次
	{
		
			P2 = (P2&0X1F|0XC0);//位
			P0 = Wei_Data[7-j];
			P2= (P2&0X1F|0XE0);//段
			P0 = Duan_Data[str[j]];
			Delay_ms(3);
			P0 = 0XFF;//消影
	}
}

void Write_eeprom(uchar addr,uchar val)
{
	IIC_Start();//总线开始
	IIC_SendByte(0xa0);//iic发送起始地址
	IIC_WaitAck();
	IIC_SendByte(addr);//发送寄存器地址(写)
	IIC_WaitAck();
	IIC_SendByte(val);//数据
	IIC_WaitAck();
	IIC_Stop();//总线结束
	Delay_ms(10);//延迟
}

uchar Read_eeprom(uchar addr)
{
	uchar da;
	
	IIC_Start();
	IIC_SendByte(0xa0);  //发送IIC起始地址  也可以理解为写命令
	IIC_WaitAck();   //等待ACK应答
	IIC_SendByte(addr);  //发送寄存器地址
	IIC_WaitAck();
	IIC_Start();       //重启IIC总线
	IIC_SendByte(0xa1); //发送IIC起始地址 + 1表示读 发送读命令
	IIC_WaitAck();
	da = IIC_RecByte();
	IIC_Ack(0);  //发送ACK应答 不读了
	IIC_Stop();  
	
	return da;
}

总之根据figure8,11就可以利用I2C的驱动函数自己写出读写字节的函数。

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值