单片机实验报告
课程名称 单片机技术与应用
实验名称 串口通信实验
专 业 光电信息工程
班 级 光电121班
学 号 050312113
姓 名 蒯玄
实验地点 躬行楼528
实验日期 2015.5.28
一、实验目的
1、掌握8051单片机串行口工作原理;
2、掌握串口编程与调试方法;
3、了解Modbus协议及其应用;
4、了解数据传输的可靠性措施与CRC校验实现方法;
5、掌握8051单片机的Modbus编程;
二、实验仪器
三、实验内容
串口功能验证:PC机与8051单片机通信实验:若PC机发送数据a,则单片机接收a后向PC机发送a+1;用串口工具软件(sscom32)观察通信结果。
设计思路: PC机采用主动方式,单片机为被动方式,因单片机端不知道PC何时发数据,若单片机采用查询方式接收,会产生接收不到PC数据而“死等”的现象。因此为了不影响单片机端的主程序运行,单片机应采用中断方式接收;
参考源码:
//中断服务程序
unsigned char Rxd_Data;//串口接收数据
unsigned char Rxd_Over;//串口接收完成标志
//串口初始化函数9600bps
void Sbuf_Init(void)
{
SCON=0x50; //10位方式
//波特率设置
PCON=0x00; //波特率不倍增
TMOD=(TMOD&0x0f)|0x20;//T1方式2
TH1=0xfd; //T1计数初值
TL1=0xfd;
ES=1;EA=1; //串口中断使能
TR1=1; //启动定时器
}
void UART_ISR(void) interrupt 4
{
if(RI)//接收产生的中断
{
RI=0;//清接收标志
Rxd_Data=SBUF; //接收数据
Rxd_Over=1; //置Rxd_Over标志
}
}
void main(void)
{
Sbuf_Init(); //串口初始化
while(1)
{
if(Rxd_Over) //若接收完成
{
//串口数据发送
SBUF=a+1; //串口发送
while(TI==0); //等待发送完成
TI=0;//清发送标志
Rxd_Over=0; //清Rxd_Over标志
}
}
}
利用此程序可以判断串口通信是否正常。串口通信失败的原因有如下几种情况:
(1)串口线未连接,用一根导线将串口线2,3脚短接,scomm32工具发送数据,根据能否接收数据判断串口线连接是否正常;
(2)最小系统板硬件故障:借助示波器观察单片机的串口接收与发送CMOS电平与RS232电平可排查硬件故障;
(3)软件问题。
四、预习要求
完成实验源码的预编写。
五、实验步骤
使用串口前,应对它进行初始化。
设置串行口工作方式控制(SCON);
设置串口通信波特率:T1(TMOD、TH1、TL1、ET1、EA、TR1 、PCON);
串口中断使能(ES、EA)。具体步骤如下:
(1)设置串行口工作方式控制(SCON)
如11位波特率可变的异步通信方式:
SCON=0xd0;(11010000)
(2)借助T1初使化通信波特率(TMOD)
Baud=2^SMOD×(T1溢出率/32)
令SMOD=0,则9600=1/(n*12/fosc*32) ,若fosc=11.0592则n=3
T1:采用方式2(8位自动载入)
即:PCON=0x00;
TMOD=(TMOD&0x0f)|0x20;
TH1=0xfd;
TL1=0xfd;
TR1=1;
六、实验过程
程序部分:
#include "reg51.h"
#include "crc16.h"
#define MODBUS_ADDR 0x01
#define MODBUS_RD 0x03
#define MODBUS_WDATA1 0x02
unsigned char RxD_buf[8],TxD_buf[8];
unsigned char RX_OVER; //全局变量:1:串口接收完成标志
//////////////////////////////////////////////////////
////// 中断服务程序///////////////////////////////////
//////////////////////////////////////////////////////
void UART_ISR(void) interrupt 4
{
static unsigned char count=0;
static unsigned char pre_data=0;
static unsigned char mid_data=0;
static unsigned char now_data=0;
//////////////////////////////////////////////
if(RI)
{
now_data=SBUF;
if(pre_data==MODBUS_ADDR && mid_data==MODBUS_RD && now_data==MODBUS_WDATA1)
{
RxD_buf[0] = pre_data;
RxD_buf[1] = mid_data;
RxD_buf[2] = now_data;
count=3;
}
else
{
RxD_buf[count] = now_data;
count++;
if(count==8)
{
count=0;
RX_OVER=1; //置接收完成标志
}
}
pre_data = mid_data;
mid_data = now_data;
RI=0;
}
}
//////////////////////////////////////////////////////
////////串口初始化函数:波特率为9600bps////////////////
//////////////////////////////////////////////////////
void Sbuf_Init(void)
{
SCON=0x50; //10位方式
//波特率设置
PCON=0x00; //波特率不倍增
TMOD=(TMOD&0x0f)|0x20;//T1方式2
TH1=0xfd; //T1计数初值
TL1=0xfd;
ES=1;EA=1; //串口中断使能
TR1=1; //启动定时器
}
//////////////////////////////////////////////////////
//////主程序//////////////////////////////////////////
//////////////////////////////////////////////////////
void main(void)
{
unsigned short int crc_value;
//串口初始化函数调用
unsigned int i;
while(1)
{
if(RX_OVER) //若接收完成
{
//先求CRC16校验
crc_value=CRC16_C(RxD_buf,6); //对接收到的前6个字节求CRC16校验
//低字节在前,高字节在后
if((crc_value%256)==TxD_buf[6] && crc_value/256==TxD_buf[7])
{
//CRC16校验成功
//回传:字节1 字节2 字节3 字节4 字节5 字节6 字节7 字节8
// 0x01 0x03 0x02 a+1 数据2 0(成功)CRC16_L CRC16_H
//此处添加TxD_buf[0]~TxD_buf[5]赋值,计算CRC16,串口查询方式发送代码
TxD_buf[0] = 0x01;
TxD_buf[1] = 0x03;
TxD_buf[2] = 0x02;
TxD_buf[3] = RxD_buf[3]+1;
TxD_buf[4] = 0x00;
TxD_buf[5] = 0x00;
crc_value = CRC16_C(RxD_buf,6);
TxD_buf[6] = crc_value%256;
TxD_buf[7] = crc_value/256;
for(i=0;i<6;i++)
{
SBUF = TxD_buf[i];
while(TI==0);
TI = 0;
}
}
else
{
//CRC校验失败
//回传:字节1 字节2 字节3 字节4 字节5 字节6 字节7 字节8
// 0x01 0x03 0x02 * 数据2 0xff(成功)CRC16_L CRC16_H
//此处添加TxD_buf[0]~TxD_buf[5]赋值,计算CRC16,串口查询方式发送代码
TxD_buf[0] = 0x01;
TxD_buf[1] = 0x03;
TxD_buf[2] = 0x02;
TxD_buf[3] = RxD_buf[3];
TxD_buf[4] = 0x00;
TxD_buf[5] = 0xff;
crc_value = CRC16_C(RxD_buf,6);
TxD_buf[6] = crc_value%256;
TxD_buf[7] = crc_value/256;
for(i=0;i<6;i++)
{
SBUF = TxD_buf[i];
while(TI==0);
TI = 0;
}
}
RX_OVER=0; //清接收完成标志,避免多次执行
}
}
}
运行结果:
正确串口输出
错误串口输出
七、实验小结
通过此次实验,我们了解到单片机的工作原理,以及串口通信的工作方式,加深了对单片机的掌握。利用单片机进行串口的连接,输入与输出。编写程序,实现加一功能,当输入错误时,串口输出FF,当输入正确时,输出自动加一,并且显示输出00,表示输入的结果是正确的。这样就实现了串口之间的连接与信息的传递。掌握8051单片机串行口工作原理,掌握串口编程与调试方法,了解Modbus协议及其应用,了解数据传输的可靠性措施与CRC校验实现方法,掌握8051单片机的Modbus编程,本次试验我们学到的有很多,试验相对来说较为简单,能够独立完成。本次实验过程中还运用到一个新的校验软件,操作起来并没有什么难度,而且还能很准确的校验出我们的结果是否正确,既方便又快捷。
第二篇:uart串口通信实验报告
串口通信实验报告
基本实验:16位的乘法器
设计思想:乘法器根据以往学过数电的设计经验,应该是移位相加的方法,设被乘数为[15:0]a,乘数为[15:0]b,则从b的最高位开始算起,c初值为0,为b最高位为1,则c就等于c+a;接下来,若b的次高位为1,则c左移一位加a,若为0则c左移一位就可以了,这样的步骤做到b的最低位那么c的值就是a*b,当然最好c是中间寄存器,这样结果才不会出现中间值。
实验的源码:
module muti(clk,rst,ready,a,b,c);
input clk;
input rst;
input [15:0]a;
input [15:0]b;
output [31:0]c;
output ready;
reg [31:0]c;
reg ready;
reg [31:0]temp;
reg [5:0]n;
always @(posedge clk or posedge rst)
begin
if(rst)
begin
c<=0;
ready<=1;
temp<=0;
n<=32;
end
else
if(ready)
begin
temp<=0;
n<=32;
ready<=0;
end
else
if(n)
begin
if(b[n-1])
begin
temp<=(temp<<1)+a;
n<=n-1;
end
else
begin
temp<=temp<<1;
n<=n-1;
end
end
else
begin
c<=temp;
n<=32;
ready<=1;
end
end
endmodul
测试代码:
`timescale 1ns/1ns
module tb;
reg clk;
reg [15:0]a;
reg [15:0]b;
reg rst;
wire ready;
wire [31:0]c;
always #10 clk=~clk;
initial
begin
rst<=1;
clk<=0;
a=0;
b=0;
#10 rst=0;
#21 a=21;b=32;
#650 a=3;b=4;
#700 $stop;
end
muti muti_unit(
.a(a),
.b(b),
.rst(rst),
.clk(clk),
.ready(ready),
.c(c));
endmodule
仿真结果:
这边a被乘数,b是乘数,当rst为高时,则将c置0,ready置一,ready信号为高表示此时空闲可以计算,rst为低时则开始计算,21*32为672,3*4为12,在乘法操作时,ready信号为低电平表示在工作中不能再输入进行计算,当 计算结束则变为高电平。乘法功能得以实现。
进阶实验:串口通信实验
基本要求:了解UART 串口通信协议
设计思想:主要是分成3个模块,一个是波特率产生模块,一个是发送模块,一个是接收模块,波特率产生模块产生一个9600Hz的采样脉冲,由于开发板上的固有频率为50MHz,所以我需要分频为50000000/9600=5208.3,即从0记到5207,当记到2503时产生一个时钟周期的高电平即可。发送模块受到接受模块的控制,接收模块的输出RXD连发送的TXD,RXD为高表示正在接收不能发送数据,发送时采用并转串发送,接收时为串转并接收。
实验代码:
1. top文件
module top(F50M,rst,datain,dataout,seg);
input F50M;/////50MHz的时钟信号
input rst;///////////////复位信号
input [7:0]datain;///////////输入数据
output [7:0]dataout;////////////////输出数据
output [3:0]seg;/////////////////板子上面选一个led
wire bps_start1;/////////////////当正在发送时为高电平
wire bps_start2;///////////////正在接收时为高电平
wire caiyang1;//////////////////////采样的波特率信号
wire caiyang2;
wire RXD_TXD;
wire dataout;
wire rx232_tx;///////////////////////中间的串行通信线
brate1 brate1(
.F50M(F50M),
.rst(rst),
.bps_start1(bps_start1),
.caiyang1(caiyang1)
);
brate2 brate2(
.F50M(F50M),
.rst(rst),
.bps_start2(bps_start2),
.caiyang2(caiyang2)
);
rev rev_u(
.F50M(F50M),
.rx232_tx(rx232_tx),
.rst(rst),
.caiyang2(caiyang2),
.RXD(RXD_TXD),
.dataout(dataout),
.bps_start2(bps_start2)
);
send send_u(
.F50M(F50M),
.datain(datain),
.rst(rst),
.caiyang1(caiyang1),
.TXD(RXD_TXD),
.bps_start1(bps_start1),
.rx232_tx(rx232_tx)
);
seg seg_u(.F50M(F50M),
.seg(seg));
endmodule
波特率产生模块:
A.发送波特率产生模块:
module brate1( F50M,rst,bps_start1,caiyang1);
input F50M;
input rst;
input bps_start1;
output caiyang1;
reg [12:0]n;//50MHz/9600hz=5208,n count 0 to 5207
reg caiyang1;//a 9600 bound rate sign
always @(posedge F50M or negedge rst)
begin
if(!rst)
begin
n<=0;
caiyang1<=0;
end
else
if((n==5207) || !bps_start1) ////////////如果记满5207或者发送数据结束就将caiyang2清0
begin
n<=0;
caiyang1<=0;
end
else
if(n==2603)//////////////////////////////计数满2603就产生一个时钟周期的高电平
begin
n<=n+1;
caiyang1<=1;
end
else
begin
n<=n+1;
caiyang1<=0;
end
end
endmodule
B.接收波特率产生模块
module brate2( F50M,rst,bps_start2,caiyang2);
input F50M;
input rst;
input bps_start2;
output caiyang2;
reg [12:0]n;//50MHz/9600hz=5208,n count 0 to 5207
reg caiyang2;//a 9600 bound rate sign
always @(posedge F50M or negedge rst)
begin
if(!rst)
begin
n<=0;
caiyang2<=0;
end
else
if((n==5207) || !bps_start2)////////////如果记满5207或者发送数据结束就将caiyang2清0
begin
n<=0;
caiyang2<=0;
end
else
if(n==2603)
begin
n<=n+1;
caiyang2<=1;
end
else
begin
n<=n+1;
caiyang2<=0;
end
end
endmodule
发送模块:
module send(F50M,datain,caiyang1,TXD,rst,bps_start1,rx232_tx);
input F50M;
input [7:0]datain;////输入要发送的数据
input rst;
input caiyang1;
input TXD; /////接收接收端的状态数据,若收到为1,表示接收端正在接收不能发送数据,采样其下降沿作为发送模块的启动时间点
output bps_start1;
output rx232_tx; 输出一个串行数据
reg rx232_tx;
reg bps_start1;
reg a;
reg b;
reg c;
wire neg_TXD;
////////////////////////////////////////////
//////捕捉TXD下降沿表示可以开始发送数据,
always @ (posedge F50M or negedge rst)
begin
if(!rst)
begin
a<=1;
b<=1;
end
else
begin
b<=a;
a<=TXD;
end
end
assign neg_TXD=~a&b;
////////////////////////////////////////////////
/////////将状态置位和发送信号
reg [4:0]n;
reg txd_en;
reg [7:0]temp_data;
always @ (posedge F50M or negedge rst)
begin
if(!rst)
begin
n<=0;
bps_start1<=0;
txd_en<=0;
temp_data<=0;
rx232_tx<=1;
end
else
if(neg_TXD)
begin
txd_en<=1;
bps_start1<=1;
temp_data<=datain;
end
else
if(n==12) /////////////////记到12表示发送结束,将所有状态重置
begin
txd_en<=0;
bps_start1<=0;
n<=0;
end
else
if(txd_en)//////////////////发送使能,内部的一个使能信号
begin
if(caiyang1)///////////////采样波特率
begin
case(n)
4'd0: begin rx232_tx<=0;n<=n+1; end////////加起始位0
4'd1: begin rx232_tx<=temp_data[0];n<=n+1; end////1到8为数据位
4'd2: begin rx232_tx<=temp_data[1];n<=n+1; end
4'd3: begin rx232_tx<=temp_data[2];n<=n+1; end
4'd4: begin rx232_tx<=temp_data[3];n<=n+1; end
4'd5: begin rx232_tx<=temp_data[4];n<=n+1; end
4'd6: begin rx232_tx<=temp_data[5];n<=n+1; end
4'd7: begin rx232_tx<=temp_data[6];n<=n+1; end
4'd8: begin rx232_tx<=temp_data[7];n<=n+1; end
4'd9: begin rx232_tx<=1;n<=n+1; end////////////加奇偶校验位
4'd10: begin rx232_tx<=1;n<=n+1; end///////////////截止位
default: begin rx232_tx<=1;n<=n+1; end/////////其余时候均为1
endcase
end
end
end
endmodule
接收模块:
module rev(F50M,rx232_tx,rst,caiyang2,RXD,dataout,bps_start2);
input F50M;
input rx232_tx;////////发送过来的串行数据
input rst;
input caiyang2;
output RXD;////////////当正在接收时,RXD为1,接收完毕产生一个周期低电平表示接受完,连接发送端的TXD输入
output [7:0]dataout;
output bps_start2;
reg RXD;
reg [7:0]dataout;
reg bps_start2;
//////////////捕捉rx232_tx的下降沿
reg a;
reg b;
wire neg_rx232;
always @ (posedge F50M or negedge rst)
begin
if(!rst)
begin
a<=1;
b<=1;
end
else
begin
b<=a;
a<=rx232_tx;
end
end
assign neg_rx232=~a&b;////////////////捕捉到下降沿时置1
/////////////////////////////////////////////////////////
/////////////标志位置位和串转并从数据线到接收端的寄存器
reg [7:0]temp;
reg [3:0]n;
reg rxd_en;
always @(posedge F50M or negedge rst)
begin
if(!rst)
begin
n<=0;
dataout<=0;
RXD<=0;
bps_start2<=0;
rxd_en<=0;
temp<=0;///////////////////////复位信号
end
else
if(n==12)////////////当计数到12的时候表示已经接受完毕,将状态全部初始到未传时
begin
n<=0;
rxd_en<=0;
RXD<=0;
bps_start2<=0;
dataout<=temp;
end
else
if(neg_rx232)/////////////////如果捕捉到了信号的下降沿,表示得到了起始位0,则将状态置一表示启动接收
begin
bps_start2<=1;
RXD<=1;
rxd_en<=1;
end
else
if(rxd_en)//////////////////接收使能
begin
if(caiyang2)/////////////////按照9600波特率采样
begin
if(n!=12)
begin
case(n)
4'd1: begin temp[0]<=rx232_tx;n<=n+1; end //////从第二位开始接收数据
4'd2: begin temp[1]<=rx232_tx;n<=n+1; end
4'd3: begin temp[2]<=rx232_tx;n<=n+1; end
4'd4: begin temp[3]<=rx232_tx;n<=n+1; end
4'd5: begin temp[4]<=rx232_tx;n<=n+1; end
4'd6: begin temp[5]<=rx232_tx;n<=n+1; end
4'd7: begin temp[6]<=rx232_tx;n<=n+1; end
4'd8: begin temp[7]<=rx232_tx;n<=n+1; end
default:n<=n+1;///////////////////////////////////其余时刻不置位
endcase
end
end
end
end
endmodule
定义了一个seg模块用来选择哪个led灯
module seg(F50M,seg);
input F50M;
output [3:0]seg;
reg [3:0]seg;
always @ (posedge F50M)
begin
seg<=4'b0111;
end
endmodule
约束条件
NET"F50M" LOC="B8";
NET"rst" LOC="H13";
NET"datain[0]" LOC="R17";
NET"datain[1]" LOC="N17";
NET"datain[2]" LOC="L13";
NET"datain[3]" LOC="L14";
NET"datain[4]" LOC="K17";
NET"datain[5]" LOC="K18";
NET"datain[6]" LOC="H18";
NET"datain[7]" LOC="G18";
NET"dataout[0]" LOC="L18";
NET"dataout[1]" LOC="F18";
NET"dataout[2]" LOC="D17";
NET"dataout[3]" LOC="D16";
NET"dataout[4]" LOC="G14";
NET"dataout[5]" LOC="J17";
NET"dataout[6]" LOC="H14";
NET"dataout[7]" LOC="C17";
NET"seg[0]" LOC="F17";
NET"seg[1]" LOC="H17";
NET"seg[2]" LOC="C18";
NET"seg[3]" LOC="F15";
modsim上面的仿真结果:
从图上可以看出一开始就有两个数据需要传输,一个是13,一个是45,则在下一个周期,发送模块的内部寄存器置为13,一直到发送模块将13全部发出,这个时候内部寄存器才接受45,这是接收模块检测到数据线上有一个下降沿,既检测到起始位0,则开始工作接受13,等到接受完13,则RXD变为低电平,发送端继续发送下一个数据45.
Ise验证
烧到板子上进行验证,由于没有办法进行电脑和板子之间的通信,我把发送的datain置为8个开关,dataout连置一个led灯。复位为H13。
当rst没有按下为低电平时,则dataout为全高,即LED灯全亮,当将K17,K18推高,即datain的第5,6位置高则显示3.,和下面的只将R17,N17,L13置低则显示7,这些基本说明串行通信基本算是成功的。