郭天祥51单片机SCI通讯的程序一直跑不通,实在是想不明白问题是出在哪里了。第二段代码是书上一模一样的也跑不通,有碰到或解决了这个问题的同学帮我看看么!
```c
#include<reg52.h>
#include<intrins.h> //用到的机器周期延时函数_nop_()需要用到此库
#include<stdio.h> //需要用到printf()函数和puts()函数
#define uint unsigned int
#define uchar unsigned char
#define ul unsigned long
sbit dula=P2^6; //声明锁存器U1的锁存端
sbit wela=P2^7; //声明锁存器U2的锁存端
sbit adwr=P3^6; //定义A/D的WR端口
sbit adrd=P3^7; //定义A/D的RD端口
uchar code dula_table[]={ //段选数码库
0x3f,0x06,0x5b,0x4f, // 0,1,2,3
0x66,0x6d,0x7d,0x07, //4,5,6,7
0x7f,0x6f,0x77,0x7c, //8,9,10,11
0x39,0x5e,0x79,0x71 //12,13,14,15
};
uchar code wela_table[]= //片选数码库
{
0xdf/*最低位*/,0xef,0xf7,0x7b,0x7d,0x7e/*最高位*/ //从右向左数第一到六位
/*特别地,这里最高位为0x7bde而非0xfbde是由于段选公用U2锁存器,而AD的CS端需要一直保持低电平不变*/
};
uchar a, flag_uart, flag_time, flag_on, num_timer0, ad_val, bai, shi, ge;
void main()
{
void init_AD(); //AD转换器初始化函数
void init_wedu(); //数码管位选段选初始化函数
void init_timer0(); //定时器0初始化函数
void init_timer1(); //定时器1初始化函数
void init_alltimer(); //所有定时器初始化函数
void delayxms(uint xms); //延时函数 ms为单位
void display(uchar num_bai,uchar num_shi,uchar num_ge); //数码管显示函数
void SCI_mode(); //SCI通讯模式选择
void SCI_ad(); //SCI-采样
init_AD(); //AD转换器初始化
init_wedu(); //数码管位选段选初始化
init_alltimer(); //所有定时器初始化
init_timer0(); //定时器0初始化
init_timer1(); //定时器1初始化
bai=0;
shi=0;
ge=0;
while(1)
{
if(flag_uart==1)
{
SCI_mode();
}
if(flag_time==1)
{
SCI_ad();
}
display(bai,shi,ge);
}
}
void init_AD() //AD转换器初始化函数
{
wela=1;
P0=0x7f; //置CSAD为0,选通ADCS以后不必再管ADCS
wela=0;
delayxms(50);
}
void init_wedu() //数码管位选段选初始化函数
{
dula=0; //初始化,段选置0
wela=0; //初始化,片选置0
}
void init_timer0() //定时器0初始化函数
{
TH0=(65536-45872)/256; //装初值 50ms一次中断
TL0=(65536-45872)%256; //装初值 50ms一次中断
ET0=1; //打开定时器0中断
num_timer0=0; //定时器0的计数初值置为0
}
void init_timer1() //定时器1初始化函数
{
TH1=0xfd; //T1定时器装初值,对应波特率9600
TL1=0xfd; //T1定时器装初值,对应波特率9600,TH1与TL1相同,工作方式2自动重装
TR1=1; //启动T1定时器
}
void init_alltimer() //所有定时器初始化函数
{
TMOD=0x21; //设置定时器1的工作方式为2, 定时器0工作方式为1
SCON=0x50; //实际上是设置SM0和SM1,设定方式1时SCON实则为01010000,即0x50
/*等价于位操作:
SM0=0;
SM1=1;
REN=1;
注意语句顺序!
*/
EA=1; //打开总中断
ES=1; //打开串口中断
flag_on=0;
}
void delayxms(uint xms) //延时函数 ms为单位
{
uint x,y;
for(x=xms;x>0;x--)
for(y=124;y>0;y--);
}
void SCI_mode() //SCI通讯模式选择
{
flag_uart=0;
ES=0;
TI=1;
switch(flag_on)
{
case 0: puts("Turn on ad!\n");TR0=1;break;
case 1: puts("Turn off ad!\n");TR0=0;break;
case 2: puts("Error!\n");break;
default: break;
}
while(!TI) // 等待上次发送完成
TI=0; // 清除发送完成标志
ES=1;
}
void SCI_ad() //SCI-采样
{
void get_ad(); //AD采样函数
float ad_vo;
flag_time=0;
get_ad();
ad_vo=(float)ad_val*5.0/256.0;
ES=0;
TI=1;
printf("The voltage is %fV\n",ad_vo);
while(!TI)
TI=0;
ES=1;
}
void get_ad() //AD采样函数
{
uchar adval;
adwr=1; //关闭A/D转换
_nop_(); //硬件延时一个机器周期
adwr=0; //启动A/D转换
_nop_();
adwr=1;
P1=0Xff; //读取P1口前先给其全写1
adrd=1; //选通ADCS
_nop_();
adrd=0; //AD读使能
_nop_();
adval=P1; //读取赋予P1口的AD数据
adrd=1;
ad_val=adval;
bai=ad_val/100;//百位
shi=ad_val%100/10;//十位
ge=ad_val%10;//个位
}
void display(uchar num_bai,uchar num_shi,uchar num_ge) //数码管显示函数
{
uint wei; //位选数字
void wedu(uchar dula_num,uint wela_num); //数码管位选段选函数
for(wei=3;wei<6;wei++)
{
switch(wei)
{
case 3: wedu(num_ge,wei);break;
case 4: wedu(num_shi,wei);break;
case 5: wedu(num_bai,wei);break;
}
dula=1; //打开U1锁存端
P0=0x00; //防止最高位数码管过亮
dula=0; //关闭U1锁存端
}
}
void wedu(uchar dula_num,uint wela_num) //数码管位选段选函数
{
void delayxms(uint xms); //延时函数 ms为单位
wela=1; //打开U2锁存端
P0=wela_table[wela_num]; //送入U2锁存端
wela=0; //关闭U2锁存端
P0=0xc0; //消影,防止P0残留电位信号干扰段选
dula=1; //打开U1锁存端
P0=dula_table[dula_num]; //送入段选信号
dula=0; //关闭U1锁存端
P0=0xff; //消影,防止P0残留电位信号干扰片选
delayxms(10);
}
void T0_time() interrupt 1 //中断程序
{
TH0=(65536-45872)/256; //装初值 50ms一次中断(45872)
TL0=(65536-45872)%256; //装初值 50ms一次中断(45872)
num_timer0++;
if(num_timer0==20)
{
num_timer0=0;
flag_time=1;
}
}
void ser() interrupt 4 //串口中断服务程序
{
RI=0;
a=SBUF;
flag_uart=1;
if(a==0x01) flag_on=0;
else if(a==0x02) flag_on=1;
else flag_on=2;
}
另外一个是书上原码,也不行。。。
#include <reg52.h>
#include <intrins.h>
#include <stdio.h>
#define uchar unsigned char
#define uint unsigned int
sbit dula = P2^6; //申请U1锁存器的锁存端
sbit wela = P2^7; //申请U1锁存器的锁存端
sbit adwr = P3^6; //定义AD的WR端口
sbit adrd = P3^7; //定义AD的RD端口
uchar flag, flag_uart, flag_time, flag_on;
uchar a, i, t0_num, ad_val;
float ad_vo;
//数码管的编码数组
uchar code table[] = {
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71
};
void delayms(uint xms);
void init();
void display(uchar value);
uchar get_ad();
void main()
{
//初始化
init();
//为了节省IO端口,ADC0804的片选端CS连接U2锁存器的Q7输出端
//即位选时,P0的最高位
//于是通过位选时对P0赋值置CSAD为0,选通后,以后不必再管
wela = 1;
P0 = 0x7f;
wela = 0;
while (1)
{
//若检测到flag_uart为1,说明串口已经执行过串口中断服务程序, 即收到了数据
if (flag_uart == 1)
{
//手动将flag_uart清0,方便标志位检测
flag_uart = 0;
//检测到收到数据后,先将ES清0,原因是接下来要发送数据,若不关闭串口,
//发送完数据后,单片机会重新申请串口中断flag_uart又为1,又再次发送,一直反复
ES = 0;
//由于下面switch中需要调用puts,printf,在单片机的puts,printf中需要对TI是否为1
//进行检测,就是需要等待TI为1才会将字符发送出去,否则一直等待下去
//所以这里需要先将TI置为1
TI = 1;
//根据执行串口中断后获取的flag_on的值执行相应的动作
switch(flag_on)
{
case 0:puts("Turn on ad!\n");TR0 = 1; //开启计数器0
break;
case 1:printf("Turn off ad!\n");TR0 = 0; //关闭计数器0
break;
case 2:puts("Fuck!\n");break;
}
//等待发送完毕,因为发送完毕后TI会由硬件清0
while (!TI);
//手动TI清0
TI = 0;
//重新开启串口中断
ES = 1;
}
//通过flag_time判断时间是否到1s, 从而去获取AD转换的值
if (flag_time == 1)
{
//手动将flag_time清0,方便下次检测
flag_time = 0;
//获取AD转换的值
ad_val = get_ad();
//获取浮点数表示的AD实际采集到的电压值
ad_vo = (float)ad_val*5.0/256.0;
//关闭串口中断
ES = 0;
//下面要调用printf,所以将TI置1, 原因见上面
TI = 1;
printf("The voltage is %fV\n", ad_vo);
//检测发送是否完成
while (!TI);
//手动TI清0
TI = 0;
//开启串口中断
ES = 1;
}
//数码管上显示AD转化的值
display(ad_val);
}
}
void delayms(uint xms)
{
uint i, j;
for (i = xms; i > 0; i--)
for (j = 110; j > 0; j--);
}
void init()
{
//设定T1定时器工作方式2, T0定时器工作方式1
TMOD = 0x21;
//为T0定时器装入初值
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
//为T1定时器装入初值
TH1 = 0xfd;
TL1 = 0xfd;
//ET1 = 1; 这里不需要开启定时器1中断,因为定时器1工作在方式2,为8位自动重装方式,进入中断也无事可做
//启动T1定时器
TR1 = 1;
//开启定时器0中断
ET0 = 1;
//启动定时器0
//TR0 = 1; TR0的初始化放在主函数的while中,方便检测到串口发送数据后的1s延时,即延时1s从串口发送完数据开始
//设定串口工作方式
//11位异步收发,含9位数据,波特率可变,且由定时器1溢出率控制
SM0 = 0; //
SM1 = 1;
//容许串口中断
REN = 1;
//开启总中断
EA = 1;
//开启串口中断
ES = 1;
}
void display(uchar num)
{
uchar bai, shi, ge;
bai = num / 100;
shi = num % 100 / 10;
ge = num % 10;
dula = 1;
P0 = table[bai];
dula = 0;
P0 = 0xff;
wela = 1;
//为了节省IO端口,ADC0804的片选端CS连接U2锁存器的Q7输出端
//即位选时,P0的最高位需要为0
P0 = 0x7e;
wela = 0;
delayms(5);
dula = 1;
P0 = table[shi];
dula = 0;
P0 = 0xff;
wela = 1;
P0 = 0x7d;
wela = 0;
delayms(5);
dula = 1;
P0 = table[ge];
dula = 0;
P0 = 0xff;
wela = 1;
P0 = 0x7b;
wela = 0;
delayms(5);
}
uchar get_ad()
{
uchar adval;
adwr = 1;
_nop_();
adwr = 0; //启动AD转换
_nop_();
adwr = 1;
P1 = 0xff;
adrd = 1;
_nop_();_nop_();_nop_();_nop_();
adrd = 0; //AD读使能
_nop_();
adval = P1; //AD数据读赋给P1
adrd = 1;
return adval;
}
void timer0() interrupt 1
{
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
t0_num++;
if (t0_num == 20)
{
t0_num = 0;
flag_time = 1; //flag_time置1,便于主程序检测是否到1s
}
}
void ser() interrupt 4
{
//RI为接收中断标志位, 在方式0时, 当串行接收第8位数据结束时, 或在其他方式, 串行接收停止位的
//中间时, 由内部硬件使RI置1, 向CPU发出中断申请, 也必须在中断服务程序中, 用软件将其清0,取消
//此中断申请, 以方便下一次中断申请检测, 即这样才能产生下一次中断.
//这里RI清0, 因为程序既然产生了串口中断, 肯定是收到或发送了数据, 在开始时没有发送任何数据
//那必然是收到了数据, 此时RI会被硬件置1, 所以进入串口中断服务程序后必须由软件清0, 这样才能
//产生下一次中断.
RI = 0;
//将SBUF中的数据读走给a, 这是此中断服务程序最重要的目的
a = SBUF;
//将串口中断标志位设置为1,便于主程序检测
flag_uart = 1;
if (a == 1) flag_on = 0;
else if (a == 2) flag_on = 1;
else flag_on = 2;
}
```