编程题目:
1.AD/DA
a.锯齿波
ORG 2000H
START:
MOV R0,#0FEH;P0端口地址
MOV A,#00H
LOOP:
MOVX @R0,A
INC A ;数据递增
SJMP LOOP
b.三角波
ORG 2000H
START:
MOV R0,#0FEH
MOV A,#00H
MOVX @R0,A
UP:
MOVX @R0,A ;数据递增
INC A
JNZ UP
DOWN:
DEC A ;;数据递减
MOVX @R0,A
JNZ DOWN
SJMP UP
c.矩形波
ORG 2000H
START:
MOV R0,#0FEH ;;FE为**P0**口的地址
LOOP:
MOV A,#data1 ;;第一个电平
MOVX @R0,A
LCALL DELAY1
MOV A,#data2 ;;第二个电平
MOVX @R0,A
LCALL DELAY2
SJMP LOOP
技术指标:
(1)分辨率;(2)建立时间;(3)精度
(1)分辨率指输入给AD转换器的单位数字量变化引起的模拟量输出变化。
(2)描述AD转换器转换快慢的一个参数
(3)位数越多精度越高
D/A的两种工作方式:(硬件链接方式)
a.单缓冲方式:指DAC0832内部的两个数据缓冲器有一个处于直通方式,另外一个处于受C51控制的锁存方式。在实际应用当中,如果只有一路模拟量输出,或者不要求多路同步输出时,可采用单缓冲方式。
b.双缓冲方式:对于DA转换,要求同步输出时,必须采用双缓冲同步方式,这种方式下,数据量的输入和AD转换是分两步进行的。单片机必须通过LE1来锁存待转换数字量,通过LE2来启动D/A转换
ADC0809硬件联接
技术指标:
(1)转换时间和转换效率(转换时间的倒数) ;(2)分辨率:1/2nx100%; n为量化时的位数
数码管
1.段码的定义:
代码位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
显示段 | dp | g | f | e | d | c | b | a |
a.静态显示
b.动态显示
原理:在某一时刻,只让某一位选线处于选通状态,而其他各个位选线处于关闭状态,同时段码线上输出相应要显示的字符的段选信号,这样就可以使得多个数码管中的一个发光显示,而其他的数码管处于熄灭状态,在下一个时刻同样的来显示另外的数码管。虽然字符在不同的时间显示,但由于LED显示器的余晖和人眼的视觉暂留效应作用,只要每一位显示的时间间隔足够短,就会出现多个数码管同时显示的假象!!!
#include <reg52.h>
typedef unsigned int u16;
typedef unsigned char u8;
#define seg P0
#define sel P2
u8 smgduan[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};// 段选
u8 d1=0,d2=0,d3=0,d4=0;
bit flag=0;
u8 i=0;
void delay5ms(){
unsigned char a,b;
for(b=19;b>0;b--)
for(a=130;a>0;a--);
}
void InitTimer0(void){
TH0 = 0x0D8;
TL0 = 0x0F0;
EA = 1;
ET0 = 1;
TR0 = 1;
}
void InitTimer1(void){
TH1 = 0x0EC;
TL1 = 0x78;
EA = 1;
ET1 = 1;
TR1 = 1;
}
void InitExT0(void){
EA = 1;
EX0 = 1;
IT0 = 1; //“0”时,为电平触发,为“1”时,为下降沿触发
}
void InitExT1(void){
EA = 1;
EX1 = 1;
IT1 = 1; //“0”时,为电平触发,为“1”时,为下降沿触发
}
void key_clear()interrupt 0 { //外中断0控制清楚(P3^2)
d1=d2=d3=d4=0;
flag=0;
}
void key_stop()interrupt 2 { //外中断1控制暂停(P3^3)
flag = ~flag;
}
void cnt()interrupt 1{ //定时器中断 10ms计数
TH0 = 0x0D8;
TL0 = 0x0F0;
if(flag==0){
if(d1==9){
d1 = 0;
if(d2 == 9){
d2 = 0;
if(d3==9){
d3=0;
if(d4==5)
d4=0;
else
d4++;
}
else
d3++;
}
else
d2++;
}
else
d1++;
}
else
;
}
void delay()interrupt 3{ //定时器中断 5ms数码管扫描速率
TH1 = 0x0EC;
TL1 = 0x78;
i++;
seg = 0x00;
if(i==4)
i=0;
}
void main(){
TMOD=0x11;
//0001_0001(<GATE><C/T><M1><M0>)//定义方式1,不启动多控制中断方式,计数模式(用内部时钟信号)
IP=1;
//IP默认值为00H,此时优先级顺序为:外部中断0 > 定时/计数器0 > 外部中断1 > 定时/计数器1 > 串行中断
//<--><--><PT2><PS:串口><PT1:T1中断><PX1:ET1中断><PT0:T0中断><PX0:ET0中断>
InitExT0();
InitExT1();
InitTimer0();
InitTimer1();
while(1){
sel =i;
switch(i){
case 0: seg = smgduan[d4];break;
case 1: seg = smgduan[d3]+128;break;
case 2: seg = smgduan[d2];break;
case 3: seg = smgduan[d1];break;
default: seg = 0;
}
}
}
矩阵键盘
2.按键消抖原理
常用软件方法去抖,即检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。当检测到按键释放后,也要给5ms~10ms的延时,待后沿抖动消失后才能转入该键的处理程序。>
一般来说,软件消抖的方法是不断检测按键值,直到按键值稳定。实现方法:假设未按键时输入1,按键后输入为0,抖动时不定。可以做以下检测:检测到按键输入为0之后,延时5ms~10ms,再次检测,如果按键还为0,那么就认为有按键输入。延时的5ms~10ms恰好避开了抖动期。
3.矩阵键盘:
#include <reg51.h>
#define uchar unsigned char
#define uint unsigned int
void matrixkeyscan()
{
uchar temp,key;
P3=0xfe;
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
delayms(10);
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xee:
key=0;
break;
case 0xde:
key=1;
break;
case 0xbe:
key=2;
break;
case 0x7e:
key=3;
break;
}
while(temp!=0xf0)
{
temp=P3;
temp=temp&0xf0;
}
display(key);
}
}
P3=0xfd;
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
delayms(10);
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xed:
key=4;
break;
case 0xdd:
key=5;
break;
case 0xbd:
key=6;
break;
case 0x7d:
key=7;
break;
}
while(temp!=0xf0)
{
temp=P3;
temp=temp&0xf0;
}
display(key);
}
}
P3=0xfb;
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
delayms(10);
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xeb:
key=8;
break;
case 0xdb:
key=9;
break;
case 0xbb:
key=10;
break;
case 0x7b:
key=11;
break;
}
while(temp!=0xf0)
{
temp=P3;
temp=temp&0xf0;
}
display(key);
}
}
P3=0xf7;
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
delayms(10);
temp=P3;
temp=temp&0xf0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xe7:
key=12;
break;
case 0xd7:
key=13;
break;
case 0xb7:
key=14;
break;
case 0x77:
key=15;
break;
}
while(temp!=0xf0)
{
temp=P3;
temp=temp&0xf0;
}
display(key);
}
}
}
串口
1.相关寄存器:
a.中断允许寄存器(IE)
EA | – | – | ES | ET1 | EX1 | ET0 | Ex0 |
---|---|---|---|---|---|---|---|
AFH | – | – | ACH | ABH | AAH | A9H | A8H |
c.串行口中断标志寄存器(SCON)
SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
---|---|---|---|---|---|---|---|
控制模式 | 控制模式 | 控制多机通讯 | 使能接收 | 发送的第8位数据 | j接收的第8位数据 | 发送中断标志位 | 接收中断标志位 |
d.定时器/计数器控制寄存器(TMOD)
GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
---|---|---|---|---|---|---|---|
T1 控制段 | 1:计数/0:计时 | 模式控制(H) | 模式控制(L) | T0控制段 | 1:计数/0:计时 | 模式控制(H) | 模式控制(L) |
e. 串口波特率控制寄存器(PCON)
SMOD |
---|
波特率控制 |
2.初始化程序设计:(波特率只和定时器/计数器1有关)
void init_serialcomm(void)
{
SCON = 0x50;
//SCON: mode 1, 8-bit【无多机通讯】 REN <M0><M1><M2><REN><TB8><RB8><TI><RI>
TMOD |= 0x20;
//TMOD: timer 1, mode 2,8-bit <GATE><C/T><M1><M0><GATE><C/T><M1><M0>==[计数器1][计数器0]
PCON |= 0x80;
//SMOD=1;加倍波特率 <SMOD><--><--><--><--><--><-->
TH1 = 0xF4;
//Baud:4800 fosc=11.0592MHz <计数器方式2,为8位计数器,自动装载,由TH1--TL1>
IE |= 0x90;
//Enable Serial Interrupt <EA> <X> <X> <ES> <ET1> <EX1> <ET0> <EX0>
TR1 = 1;
// timer 1 run 驱动计数器
}
3.多机通讯过程:
a.原理:
在串行口以方式2或者方式3接收时,若SM2=1,表示设置为多及通信,这时:
1.接收到的第9为数据为1时,数据才装入SBUF并且置中断标志RI=1,向CPU发出中断请求
2.接收到的第9为数据为0时,则不会产生中断标志,信息被抛弃。
b.多机通讯的工作过程
主机的RXD与所有从机的TXD相连接。
(1)从机初始化程序串行口中断,将串行口编程为方式2或者方式3接收,即9为数据异步通信方式,且SM2和REN位置1,使得从机只处于多及通讯且接收地址帧的状态。
(2)在主机和某一个从机通信之前,先将对应的设备地址通过串口发送出去,地址信息第9位为“1”,【和传送数据的区别在于数据的第9位为“0”】。当主机向各从机发送地址时,各从机的串口接收到的第9为数据为RB8=1;且由于SM2=1,则中断标志位RI置“1”,从机响应串口中断,执行中断服务程序,在中断服务程序中,判断主机发送的地址是否是本机的地址,若是本机地址,则使该从机SM2=0(即关闭多机通讯方式),准备接收主机发送的数据;若不相符合,则仍然保持SM2=1;【因为数据的第9位为0,SM2=0时对这一位不敏感;但是当SM2=1时,只对这一位的高电平敏感,从而实现定向传数据【多机通讯】】。
4.实验程序(AD采样串口传输)
#include <reg51.h>
#define uchar unsigned char
#define uint unsigned int
#define sel P1
#define seg P0
uchar i;
uchar code smgduan[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};// 段选
sbit cs= P2^4;
sbit dclk = P2^7;
sbit din = P2^6;
sbit dout = P2^5;
void delay(uint i){
while(i--);
}
void XPT2046_writebyte(uchar wx) //串行输入1个字节的控制字
{ uchar i;
dclk = 0;
for(i=0;i<8;i++)
{ din=wx>>7;
wx<<=1;
dclk=0;
dclk=1; //上跳沿输入
}
}
uint XPT2046_read() //读出12位的转换数据
{ uint rx=0;
uchar i;
for(i=0;i<12;i++)
{ rx<<=1;
dclk=1;
dclk=0;
rx|=dout; //下跳沿输出
}
return(rx);
}
uint getdata(uchar cmd)
{
uchar i;
uint value;
dclk = 0;
cs = 0;
XPT2046_writebyte(cmd);
for(i=6; i>0; i--); //延时等待转换结果
dclk = 1;
dclk = 0;
value=XPT2046_read();
cs = 1;
return value;
}
void initcomm(void)
{
SCON=0x50; //设置为工作方式1 0101 0000
TMOD=0x20; //设置计数器工作方式2
PCON=0x80; //波特率加倍
TH1=0xF3; //计数器初始值,波特率为4800
TL1=0xF3;
EA=1; //打开总中断
ES=1; //打开串口中断
TR1=1; //打开计数器
}
void uart_rx(uchar ch)
{
SBUF=ch;
while(TI==0);
TI=0;
}
void main(){
uint send_data = 0;
uchar d1,d2,d3,d4;
uchar i,j;
initcomm();
while(1){
send_data=getdata(0xa4);
d4=send_data/1000;
send_data = send_data%1000;
d3=send_data/100;
send_data = send_data%100;
d2=send_data/10;
send_data = send_data%10;
d1=send_data/1;
for(j=0;j<10;j++){
for(i=0;i<4;i++){
delay(300);
sel =i;
switch(i){
case 0: seg = smgduan[d4];break;
case 1: seg = smgduan[d3];break;
case 2: seg = smgduan[d2];break;
case 3: seg = smgduan[d1];break;
default: seg = 0;
}
}
}
uart_rx(d4+48);
uart_rx(d3+48);
uart_rx(d2+48);
uart_rx(d1+48);
uart_rx(0x0A);
}
}
四种工作方式(方式1、3的波特率与T1计数器有关)
方式0:波特率固定 fosc/12[^串行方式0]
方式1:波特率 = 2SMOD/32*定时器T1溢出率[^串行方式1]
方式2:波特率 = 2SMOD/64*fosc [^串行方式2]
方式3:波特率 = 2SMOD/32*定时器T1的溢出率 [^串行方式3]
定时器溢出率 = 计 数 速 率 256 − X = f o s c / 12 256 − X {\frac{计数速率}{256-X}=\frac{f_{osc}/12}{256-X}} 256−X计数速率=256−Xfosc/12
波 特 率 = 2 S M O D 32 ∗ f o s c 12 ∗ ( 256 − X ) {波特率 = \frac{2^{SMOD}}{32}*\frac{f_{osc}}{12*(256-X)}} 波特率=322SMOD∗12∗(256−X)fosc
若晶振11.0592MHz,选用T1方式2定时器作为波特率发生器,波特率为2400bps
因为只有方式1、3得波特率是可调的,所以方式选择这两个。
设T1方式2定时,选择SMOD=0;
波 特 率 = 2 S M O D 32 ∗ f o s c 12 ∗ ( 256 − X ) {波特率 = \frac{2^{SMOD}}{32}*\frac{f_{osc}}{12*(256-X)}} 波特率=322SMOD∗12∗(256−X)fosc = 2400
得:X=244=F4H
解:先求溢出率:2400 = 溢出率/32; 得:溢出率=76800
求初值:76800 = 计数速率/(256-X)= f o s c / 12 256 − X {\frac{f_{osc}/12}{256-X}} 256−Xfosc/12 得:计数速率 = 244 【晶振用11.0592MHz来做】
定时器/计数器
1.相关得寄存器
a.中断允许寄存器(IE)
EA | – | – | ES | ET1 | EX1 | ET0 | Ex0 |
---|---|---|---|---|---|---|---|
AFH | – | – | ACH | ABH | AAH | A9H | A8H |
b.中断请求标志寄存器(TCON)
TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
---|---|---|---|---|---|---|---|
T1溢出中断 | T0溢出中断 | 外部中断1 | 定时器1中断 | 外部中断0 | 定时器中断0 |
d.定时器/计数器控制寄存器(TMOD)
GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
---|---|---|---|---|---|---|---|
T1 控制段 | 1:计数/0:计时 | 模式控制(H) | 模式控制(L) | T0控制段 | 1:计数/0:计时 | 模式控制(H) | 模式控制(L) |
2.定时器相关
在C51中我们可以利用的定时器/计数器只有两个:T0、T1
存在四种工作方式(12MHz):
方式0:计数器/定时器 为13位的1
方式1:计数器/定时器 为16位的 2
方式2:计数器/定时器为8位的【由硬件自动装载】3
方式3:计数器/定时器为两个8位的(只有T0能够工作在方式3,且此时T1停止计数)[^方式3]
装载值的计算:对于12MHz的晶振
例如:我们要计时100us,采用T0\T1的方式0、1、2都可以
方式0: (213 - X)*1us =100us; 可得:X = 8091 = 1F9B
方式1: (216 - X)*1us = 100us 可得:X = 65435 = FF9B
方式2: (28 - X)*1us = 100us; 可得:X = 155 = 9B
;;;;;;;;;;;;;;;;;;;;; 中断流水灯【T0方式1】 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0000H
AJMP START
ORG 000BH;
LJMP TIMER0;
ORG 0100H;
START:
MOV TMOD,#01H;
MOV TH0,#3CH;
MOV TL0,#B0H;
MOV A,#FEH;
MOV R0,#20H;
MOV IE,#10000010B;
MOV IP,#10B;
SETB TR0;
MOV P1,A;
SJMP $;
TIMER0:
DJNZ R0,LOOP;
MOV R0,#20;
MOV P1,A;
RL A;
LOOP:
MOV TH0,#3CH;
MOV TL0,#B0H;
RETI
END
********************************* 1s定时 *********************************
ORG 0000H
RESET:
LJMP MAIN
ORG 000BH
LJMP IT0P
ORG 1000H
MAIN:
MOV SP.#60
MOV B,#0AH
MOV TMOD,#01H
MOV TL0,#0B0H
MOV TH0,#3CH
SETB TR0
SETB ET0
SETB EA
HERE:
SJMP HERE
IT0P:
MOV TL0,#0B0H
MOV TH0,#3CH
DJNZ B,LOOP
CLR TR0
LOOP:
RETI
- 最大可定时213x1us=8191us ~= 8.191ms ↩︎
- 最大可定时时间 216x1us=65535us ~= 65.535ms ↩︎
- 自动装载TH–>TL中重载,每次在定时器中断触发的时候,最大计时:28x1us=255us ↩︎