文章目录
八、PWM脉宽调制
任务:在CT107D单片机综合训练平台上,利用PWM脉宽信号实现独立按键S7对L1指示灯亮度变化的控制。
具体要求如下:
1.PWM脉宽信号的频率为100Hz。
2.系统上电后L1指示灯处在熄灭状态。
3.L1指示灯有4种亮度模式,分别是完全熄灭、10%的亮度、
50%的亮度和90%的亮度。
4.按下S7按键,循环切换L1指示灯的四种亮度模式。
基本概念
脉冲宽度调制:是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。可以通过改变脉冲列的周期进行调频,改变脉冲的宽度或占空比进行调压。(采用适当控制方法可使电压与频率协调变化)
占空比是指在一个脉冲循环内,通电时间相对于总时间所占的比例。占空比为30%,则接高电平时间占30%,低电平占70%。
实现:
#include "reg52.h"
sbit L1 = P0^0;
sbit S7 = P3^0;
void SelectHC573()
{
P2 = (P2 & 0x1f) |0x80; //LED
}
unsigned char count = 0; //频率
unsigned char pwm_duty = 0; //占空比
void InitTimer0()
{
TMOD = 0x01;
TH0 = (65535 - 100) / 256;
TL0 = (65535 - 100) % 256;
ET0 = 1;
EA = 1;
}
void ServiceTimer0() interrupt 1
{
TH0 = (65535 - 100) / 256;
TL0 = (65535 - 100) % 256;
count++;
if(count == pwm_duty) //计时达到占空比则由低电平变为高电平
{
L1 = 1;
}
else if(count == 100) //频率达到100
{
L1 = 0;
count = 0;
}
}
void Delay(unsigned int t)
{
while(t--);
}
unsigned char stat = 0; //状态参数
void ScanKeys()
{
if(S7 == 0)
{
Delay(100);
if(S7==0)
{
switch(stat)
{
case 0: //10亮度
L1 = 0;
TR0 = 1;
pwm_duty = 10;
stat = 1;
break;
case 1:
pwm_duty = 50; //50
stat = 2;
break;
case 2:
pwm_duty = 90; //90
stat = 3;
break;
case 3:
L1 = 1; //关灯
TR0 = 0;
stat = 0;
break;
}
while(S7 == 0); //使按键灵敏
}
}
}
void main()
{
SelectHC573();
L1 = 0; //刚上电L1熄灭
InitTimer0();
while(1)
{
ScanKeys();
}
}
九、串口通信
原理
微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:
并行通信:指数据的各位同时发送或接收,每个数据位使用一条导线。
串行通信:指数据一位接一位地顺序发送或接收。
串行通信有°SPL、IC、UART等多种,最常见最通用的是指UART,大多数情况下,串口通信指的就是UART。
串行通信的制式有:单工、半双工、全双工三种。
RS485总线是半双工的通信制式。
串行通信的主要方式有两种:同步和异步。
同步串行通信:需要使用同一个时钟,以数据块为单位传送数据。
异步串行通信:每个设备都有自己的时钟信号,通信中双方的波特率要保持一致,以字符为单位进行数据帧传送,一次传送一个帧。
波特率:串口每秒钟传输的位数。
在51单片机的串口通信中,模式1与模式3的波特率是可变的,取决于定时器1的溢出率。
(定时器每溢出一次就发送一次数据)
通常使用定时器1的工作模式2(8位自动重装)来产生波特率。(不用改计数初值)
TL1作为脉冲计数寄存器,TH1作为自动重装寄存器,当计数达到最大值溢出时,TH1的值会自动装到TL1中。
波特率的计算公式//
波特率=(2^SMoD / 32)×T1的溢出率
SMOD=0时,TH1参数=256 - fosc / 12 / 32 / 波特率
SMOD=1时,TH1参数=256 - fosc / 12 / 16 / 波特率 (翻倍)
12M晶振或11.0592M晶振的情况下,要产生9600BPS的波特率,
SMOD=0时,参数为0xfd;
SMOD=1时,参数为0xfa。
下面以UART口为例介绍串口数据发送接收。
串行口中有两个缓冲寄存器SBUF,一个是发送寄存器,一个是接收寄存器,在物理结构上是完全独立的。它们都是字节寻址的寄存器,字节地址均为99H。
这个重叠的地址靠读/写指令区分:
串行发送时,CPU向SBUF写入数据,此时99H表示发送缓存SBUF。
串行接收时,CPU从SBUF读出数据,此时99H表示接收缓存SBUF。
数据发送,把数据扔进SBUF后,内核会自动将数据发送出去,内容发生完成后,会将TI标志位置1.
发送数据程序:SBUF=数据/变量; 如:SBUF=0x58;
数据接收,内核从串口接收到一个完整的数据后,会将RI标志位置1,用户用SBUF直接读取即可。
接收数据程序:变量=SBUF; 如:dat=SBUF;(实际上就是赋值)
SCON寄存器
最常用的是模式1,SM0、SM1最常用是”01“,RB8、TB8一般在奇偶校验使用,不用则输入0即可。
异步8位UART并且允许接收:SCON=0x50;(大多数时候SCON直接输这个就行
)
对于IAP15F2K61S2单片机,你还要对辅助寄存器AUXR(0x8e)进行设置。
任务1
任务1:在CT107D单片机综合训练平台上,利用51单片机的串行接口与上位机建
立传输信道进行数据的收发。采用8位的UART模式,即模式1,波特率为9600BPS。数据发送采用查询方式,数据接收采用中断方式。系统上电初始化之后,单片机向上位机发送两个字节:0x5a和0xa5,然后等待接收上位机的数据,每接收到一个字节后,在该字节的基础上加1,然后返回给上位机。
#include "reg52.h"
sfr AUXR = 0x8e; //IAP15F2K61S2需要的
unsigned char urdat;
void SendByte(unsigned char dat);
void InitUart() //采用的是定时器1
{
TMOD = 0x20; //设置自动重装计数器
TH1 = 0xfd; //设置重新装入值
TL1 = 0xfd;
TR1 = 1; //启动定时器
SCON = 0x50; //设置串口
AUXR = 0x00;
//IE寄存器
ES = 1; //打开串口
EA = 1; //打开总开关
}
void ServiceUart() interrupt 4
{
if(RI == 1)
{
RI = 0;
urdat = SBUF; //发送
SendByte(urdat+1);
}
}
void SendByte(unsigned char dat)
{
SBUF = dat; //接收
while(TI == 0);
TI = 0;
}
void main()
{
InitUart();
SendByte(0x5a);
SendByte(0xa5);
while(1);
}
结果:
任务2
任务2:在CT107D单片机综合训练平台上,利用51单片机的串行接口与上位机建立传数据输信道。采用8位的UART模式,即模式1,波特率为9600BPS。数据发送采用查询方式,数据接收采用中断方式。
1.系统上电初始化之后,关闭蜂鸣器和继电器等无关设备,并向上位机发送字符串:“Welcome to XMF system!”,回车换行。
2.上位机通过串口发送单字节命令可以控制下位机的8个LED灯开关。
3.上位机通过串口发送单字节命令可以读取下位机运行信息。
4.通信规约如下表:
实现:
#include "reg52.h"
sfr AUXR =0x8e;
unsigned char command=0x00;
void SelectHC573(unsigned char channel)
{
switch(channel)
{
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;
case 0:
P2=P2&0x1f|0x00;
break;
}
}
void InitSystem()
{
SelectHC573(5);
P0=0x00;
SelectHC573(4); //4要在5后边,保证后边LED会亮
P0=0xff;
}
void InitUART()
{
TMOD=0x20;
TH1=0xfd;
TL1=0xfd;
TR1=1;
SCON=0x50;
ES=1;
EA=1;
AUXR=0x00;
}
void UartInit(void) //9600bps@11.0592MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器时钟1T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TL1 = 0xE0; //设置定时初始值
TH1 = 0xFE; //设置定时初始值
ET1 = 0; //禁止定时器%d中断
TR1 = 1; //定时器1开始计时
ES=1;
EA=1;
}
void ServiceUART() interrupt 4
{
if(RI==1)
{
command=SBUF;
RI=0;
}
}
void SendByte(unsigned char dat)
{
SBUF=dat; //command接收dat或者*str
while(TI==0);
TI=0;
}
void SendString(unsigned char *str)
{
while(*str != '\0')
{
SendByte(*str++);
}
}
void Working()
{
if(command!=0x00)
{
switch (command&0xf0) //取高四位
{
case 0xa0: //第一位是0
P0=(P0&0xf0)|((~command)&0x0f);
command=0x00;
break;
case 0xb0: //第二位是0
P0=(P0&0x0f)|((~command<<4) &0xf0);
command=0x00;
break;
case 0xc0: //第三位是0
SendString("The System is running!\r\n");
command=0x00;
break;
}
}
}
void main()
{
InitSystem();
// InitUART();
UartInit();
SendString("Welcome to blue bridge cup!\r\n");
while(1)
{
Working();
}
}
P0=(P0&0xf0)|((~command)&0x0f);
将P0的高四位和取反后的command的低四位合并,并赋值给P0。
P0=(P0&0x0f)|((~command<<4) &0xf0);
同上,不过command要先左移再取反。
十、IO扩展与存储器映射扩展
任务:在CT107D单片机综合训练平台上,分别用/O扩展方式与存储器扩展方式对LED灯和数码管进行基本的控制。
1.首先点亮指示灯低4位,关闭高4位,延时片刻,点亮指示灯的高4位,关闭低4位,延时片刻,关闭所有指示灯。
2.然后依次逐个点亮数码管的所有段码,每次只点亮一个数码管。
3.循环执行上述功能。
外部资源的地址映射关系:
0x8000–指示灯
0xa000–蜂鸣器与继电器
0xc000-数码管位选
0xe000–数码管段码
通俗而言,IO扩展就是直接用HC138译码器进行输入输出。
存储器映射扩展就是通过外扩RAM和ROM空间,进行外设的操作。
实现:
如果是IO扩展:
#include "reg52.h"
void Delay(unsigned int t)
{
while(t--);
while(t--);
}
void SelectHC573(unsigned char channel)
{
switch(channel)
{
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 LEDRunning()
{
SelectHC573(4);
P0 = 0xf0;
Delay(60000);
Delay(60000);
P0 = 0x0f;
Delay(60000);
Delay(60000);
P0 = 0xff;
Delay(60000);
Delay(60000);
}
void SMGRunning()
{
unsigned char i;
for(i = 0;i < 8;i++)
{
SelectHC573(6);
P0 = 0x01 << i;
SelectHC573(7);
P0 = 0x00;
Delay(60000);
Delay(60000);
}
P0 = 0xff;
Delay(60000);
Delay(60000);
}
void main()
{
while(1)
{
LEDRunning();
SMGRunning();
}
}
存储器扩展:
#include "reg52.h"
#include "absacc.h"
void Delay(unsigned int t)
{
while(t--);
while(t--);
}
void LEDRunning()
{
XBYTE[0x8000]=0xf0;
Delay(65535);
Delay(65535);
XBYTE[0x8000]=0x0f;
Delay(65535);
Delay(65535);
XBYTE[0X8000]=0xff;
}
void DigitalTubeRunning()
{
unsigned char i;
for(i=0;i<8;i++)
{
XBYTE[0xc000]=0x01<<i;
XBYTE[0xe000]=0x00;
Delay(65535);
Delay(65535);
}
XBYTE[0xe000]=0xff;
Delay(65535);
Delay(65535);
}
void main()
{
while(1)
{
DigitalTubeRunning();
LEDRunning();
}
}
*注意:1.进行存储器映射扩展,CT107D平台的J13要将1-2脚短接。
2.引入“absacc.h”,通过XBYTE关键字来直接操作扩展资源。
3.存储器映射扩展方式要占用单片机的P3.6引脚。
十一、DS18B20温度传感器
基础知识
DS18B20是一款常用的高精度的单总线数字温度测量芯片。
温度转换与读取流程:
【1】DS18B20复位。
【2】写入字节0xCC,跳过ROM指令。(忽略ID匹配过程)
【3】写入字节0x44,开始温度转换。
【4】延时700~900ms。
【5】DS18B20复位。
【6】写入字节0xCC,跳过ROM指令。
【7】写入字节0xBE,读取高速暂存器。
【8】读取暂存器的第0字节,即温度数据的LSB。
【9】读取暂存器的第1字节,即温度数据的MSB。
【10】DS18B20复位。表示读取数据结束。
【11】将LSB和MSB整合成为一个16位数据。
【12】判断读取结果的符号,进行正负温度的数据处理。(DS18B20以16位带符号位扩展的二进制补码形式读出。)
由于一个本单片机只有一个DS18B20,可以直接用SKIP RAM无需发送地址信息实现功能。
数据处理:
DS18B20的分辨率为0.0625。读出数据为正温度时,将LSB和MSB整合成的16位整数,直接乘以0.0625即可。读出数据为负温度时,则需要将LSB和MSB整合成的16位整数,取反加1后,再乘以0.0625。
注意:在上电复位的时候,温度寄存器中的值为0x0550,即+85摄氏度。
完成室温传感
#include "reg52.h"
#include "onewire.h"
#include "absacc.h"
unsigned char code DTNumber[10] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //数字带点
unsigned char code DTNumberWithDot[10]={0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10}; //不带点
int temp=0;
unsigned char LSB,MSB;
void DTDynDisplay(void);
void Delay(unsigned int t)
{
while(t--);
}
void DelayWithDT(unsigned int t)
{
while(t--)
{
DTDynDisplay();
}
}
void DTStaticDisplay(unsigned char pos,value)
{
XBYTE[0xe000]=0xff;
XBYTE[0xc000]=0x01<<pos;
XBYTE[0xe000]=value;
}
void DTDynDisplay()
{
XBYTE[0xc000]=0xff;
XBYTE[0xe000]=0xff;
DTStaticDisplay(0,0xff);
DTStaticDisplay(1,0xff);
DTStaticDisplay(2,0xff);
DTStaticDisplay(3,0xff);
//如果温度是负数的时候,这个要改
DTStaticDisplay(4,0xff);
DTStaticDisplay(5,DTNumber[temp/100]); //第一位
Delay(5000);
DTStaticDisplay(6,DTNumberWithDot[temp%100/10]); //第二位
Delay(5000);
DTStaticDisplay(7,DTNumber[temp%10]); //小数点后一位
Delay(5000);
}
void ReadTemp()
{
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0x44);
DelayWithDT(100);
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE);
LSB=Read_DS18B20();
MSB=Read_DS18B20();
// init_ds18b20();
temp=MSB;
temp=(temp << 8) | LSB;
if((temp&0xf800) == 0x0000) //读取符号位,说明是正数
{
temp>>=4;
temp=temp*10;
temp=(LSB&0x0f)*0.0625*10+temp; //这一行为小数部分的换算,&0x0f将高四位变成0,
//低四位不变,*0.0625即乘上2^(-4),将整数表示的小数部分真正变成小数,后*10用于将小数部分写入temp的个位
}
// if((temp&0xf800) == 0xf800) //当温度是负的时候
// {
// temp=~temp+1; //由于读到的是补码,所以取反加1,由于后边运算跟符号位无关,所以不用管前五位,直接取反。
// temp=(temp>>4) * 10;
// temp=((~LSB+1)&0x0f)*0.625+temp;
// }
}
void main()
{
XBYTE[0x8000]=0xff;
while (1)
{
ReadTemp();
DTDynDisplay();
}
}
其中onewire模块:
#include "reg52.h"
sbit DQ = P1^4; //单总线接口
//单总线延时函数
void Delay_OneWire(unsigned int t) //STC89C52RC
{
while(t--);
}
//通过单总线向DS18B20写一个字节
void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
DQ = 0;
DQ = dat&0x01;
Delay_OneWire(60);
DQ = 1;
dat >>= 1;
}
Delay_OneWire(60);
}
//从DS18B20读取一个字节
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(60);
}
return dat;
}
//DS18B20设备初始化
bit init_ds18b20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(144);
DQ = 0;
Delay_OneWire(960);
DQ = 1;
Delay_OneWire(120);
initflag = DQ;
Delay_OneWire(60);
return initflag;
}
十二、DS1302
采用SPI三线接口与MCU进行同步通信,并可采用突发方式一次传送多个字节的时钟参数和RAM数据。
在DS1302中有两块存储器:日历时钟寄存器和静态RAM存储器。前者用于记录实时时间,后者用于记录其他数据。
DS1302介绍
注: 要记得在操作 DS1302 之前关闭写保护;不然指令是无法进入控制寄存器的
下面进行实操说明:
#include "reg52.h"
#include "absacc.h"
#include "ds1302.h"
void Delay(unsigned int t)
{
while(t--);
}
code unsigned char SMG_number[18]={0xc0,0xf9,0xa4,0xb0,0x99,
0x92,0x82,0xf8,0x80,0x90,
0x88,0x80,0xc6,0xc0,0x86,
0x8e,0xbf,0x7f};
void WriteDS()
{
char i;
Write_Ds1302_Byte(0x8e,0x00); // 禁止写保护
for(i=0;i<7;i++)
{
Write_Ds1302_Byte(WriteDS_Addr[i],Time[i]); // 将时间数据写入DS1302的指定地址
}
Write_Ds1302_Byte(0x8e,0x80); // 开启写保护
}
void ReadDS()
{
char i;
for(i=0;i<7;i++)
{
Time[i]=Read_Ds1302_Byte(ReadDS_Addr[i]); 从DS1302的指定地址读取时间数据
}
}
void DTDrive(unsigned char pos, value)
{
XBYTE[0xc000]=0x01<<pos; // 选择数码管的位置
XBYTE[0xe000]=SMG_number[value]; // 显示对应的数码管编码
Delay(1000); // 延时
}
void DTDisplay()
{
XBYTE[0xc000]=0xff;
XBYTE[0xe000]=0xff;
DTDrive(0,Time[5]/16);
DTDrive(1,Time[5]%16);
DTDrive(2,16);
DTDrive(3,Time[4]/16);
DTDrive(4,Time[4]%16);
DTDrive(5,16);
DTDrive(6,Time[3]/16);
DTDrive(7,Time[3]%16);
}
void main()
{
WriteDS(); // 初始化DS1302的时间
while(1)
{
ReadDS(); // 读取DS1302的时间
DTDisplay(); // 显示时间
}
}
十三、NE555定时器
整个NE555的电路没有与单片机的其它引脚相通,是一个完整电路,可自发生成方波。
在NE555内部,有3个5K的电阻分压,故称555定时器。
555定时器的基本原理:
低电平触发端TRIG和高电平触发端THR:
两者电压均小于各自的参考电压时,U0=1,放电管截至。
两者电压均大于各自的参考电压时,U0=0,放电管导通。
通过AUXR第七位确定是否分频。
计数器就是计传入方波的数量,定时器通过固定频率计脉冲数来计算经过时间。
十四、PCF8591
A/D将模拟信号(正弦波)转为数字信号(方波),D/A数转模。
PCF8591是一款单芯片、单电源、低功耗8位CMOS数据采集设备具有四个模拟输入、一个模拟输出和一个串行12c总线接口。
串行数据线SDA——负责在设备间传输串行数据
串行时钟线SCL——负责产生同步时钟脉冲
SCL\SDA是I2C总线的信号线。
IIC
I2C总线具有两根双向信号线,一根是数据线SDA,另一根是时钟线SCL。
多主机会产生总线裁决问题。当多个主机同时想占用总线时,企图启动总线传输数据,就叫做总线竞争。I2C通过总线仲裁,以决定哪台主机控制总线.
对于其中的address
前4位固定,最后一位低电平“写”,高电平“读”。(读取地址:0x91,写入地址:0x90)
对于“control byte”
第三第四位一般用00。
控制光敏电阻设置0x41,可调电阻0x43。
十五、AT24C02
EEPROM与RAM:
EEPROM:带点可擦只读存储器
RAM:随机存储器
运作模式:
十六、超声波传感器
超声波传感器主要用于测距。原理还是s=(2/t)*v(声速)
发出信号,整个U18相当于起信号放大作用
接收信号
J2处跳线帽连接1、3,2、4则选用超声波模式,选用3、5,4、6则选用红外模式