0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

分析一下串口发送与接收模块的设计代码

电子工程师 来源:电子工程世界 作者:小梅哥 2021-04-19 11:22 次阅读

串口发送与接收模块设计代码分析

1.1Tx_Bps_Gen

Tx_Bps_Gen为发送波特率生成模块,每当有Byte_En信号到来时,即开始产生发送一个完整字节的数据需要的完整波特率时钟信号。

本设计,波特率支持9600bps到921600bps。例如,需要产生的波特率时钟为9600bps,即波特率时钟频率为9600Hz,周期为104.17us。生成9600Hz波特率时钟的核心思想就是对系统时钟进行计数,这里设定系统时钟为50MHz,则一个时钟的周期为20ns,我们只需要对系统时钟计数5208次,每计数5208次产生一个时钟周期的高电平脉冲,即可实现生成9600Hz波特率时钟的功能。相应代码如下所示:

018 parameter system_clk = 50_000_000; /*输入时钟频率设定,默认50M*/

019

020 /*根据输入时钟频率计算生成各波特率时分频计数器的计数最大值*/

021 localparam bps9600 = system_clk/9600 - 1;

022 localparam bps19200 = system_clk/19200 - 1;

023 localparam bps38400 = system_clk/38400 - 1;

024 localparam bps57600 = system_clk/57600 - 1;

025 localparam bps115200 = system_clk/115200 - 1;

026 localparam bps230400 = system_clk/230400 - 1;

027 localparam bps460800 = system_clk/460800 - 1;

028 localparam bps921600 = system_clk/921600 - 1;

029

030 reg [31:0]BPS_PARA;/*波特率分频计数器的计数最大值*/

031

032 always@(posedge Clk or negedge Rst_n)

033 if(!Rst_n)begin

034 BPS_PARA 《= bps9600;/*复位时波特率默认为9600bps*/

035 end

036 else begin

037 case(Baud_Set)/*根据波特率控制信号选择不同的波特率计数器计数最大值*/

038 3‘d0: BPS_PARA 《= bps9600;

039 3’d1: BPS_PARA 《= bps19200;

040 3‘d2: BPS_PARA 《= bps38400;

041 3’d3: BPS_PARA 《= bps57600;

042 3‘d4: BPS_PARA 《= bps115200;

043 3’d5: BPS_PARA 《= bps230400;

044 3‘d6: BPS_PARA 《= bps460800;

045 3’d7: BPS_PARA 《= bps921600;

046 default: BPS_PARA 《= bps9600;

047 endcase

048 end

049

050 //=========================================================

051 reg[12:0]Count;

052

053 reg n_state;

054 localparam IDEL_1 = 1‘b0,

055 SEND = 1’b1;

056

057 reg BPS_EN;

058

059 /*-------波特率时钟生成控制逻辑--------------*/

060 always@(posedge Clk or negedge Rst_n)

061 if(!Rst_n)begin

062 BPS_EN 《= 1‘b0;

063 n_state 《= IDEL_1;

064 end

065 else begin

066 case(n_state)

067 IDEL_1:

068 if(Byte_En)begin/*检测到字节发送使能信号,则启动波特率生成进程,同时进入发送状态*/

069 BPS_EN 《= 1’b1;

070 n_state 《= SEND;

071 end

072 else begin

073 n_state 《= IDEL_1;

074 BPS_EN 《= 1‘b0;

075 end

076 SEND:

077 if(Tx_Done == 1)begin/*发送完成,关闭波特率生成进程,回到空闲状态*/

078 BPS_EN 《= 1’b0;

079 n_state 《= IDEL_1;

080 end

081 else begin

082 n_state 《= SEND;

083 BPS_EN 《= 1‘b1;

084 end

085 default:n_state 《= IDEL_1;

086 endcase

087 end

088

089 /*-------波特率时钟生成定时器--------------*/

090 always@(posedge Clk or negedge Rst_n)

091 if(!Rst_n)

092 Count 《= 13’d0;

093 else if(BPS_EN == 1‘b0)

094 Count 《= 13’d0;

095 else begin

096 if(Count == BPS_PARA)

097 Count 《= 13‘d0;

098 else

099 Count 《= Count + 1’b1;

100 end

101

102 /*输出数据接收采样时钟*/

103 //-----------------------------------------------

104 always @(posedge Clk or negedge Rst_n)

105 if(!Rst_n)

106 Bps_Clk 《= 1‘b0;

107 else if(Count== 1)

108 Bps_Clk 《= 1’b1;

109 else

110 Bps_Clk 《= 1‘b0;

第18行“parameter system_clk = 50_000_000;”,这里用一个全局参数定义了系统时钟,暂时设定为50M,可根据实际使用的板卡上的工作时钟进行修改。

所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信号。例如,我们使用波特率为115200bps,则我们需要产生一个频率为115200Hz的时钟信号。那么如何产生这样一个115200Hz的时钟信号呢?这里,我们首先将115200Hz时钟信号的周期计算出来,1秒钟为1000_000_000ns,因此波特率时钟的周期Tb= 1000000000/115200 =8680.6ns,即115200信号的一个周期为8680.6ns,那么,我们只需要设定我们的定时器定时时间为8680.6ns,每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。系统时钟频率为50MHz,即周期为20ns,那么,我们只需要计数8680/20个系统时钟,就可获得8680ns的定时,即bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此类似,这里小梅哥就不再一一分析。20行至28行为波特率定时器定时值的计算部分。

为了能够通过外部控制波特率,设计中使用了一个3位的波特率选择端口:Baud_Set。通过给此端口不同的值,就能选择不同的波特率,此端口控制不同波特率的原理很简单,就是一个多路选择器,第32行至第48行即为此多路选择器的控制代码, Baud_Set的值与各波特率的对应关系如下:

000 :9600bps;

001 :19200bps;

010 :38400bps;

011 :57600bps;

100 :115200bps;

101 :230400bps;

110 :460800bps;

111 :921600bps;

1.2Uart_Byte_Tx

Uart_Byte_Tx为字节发送模块,该模块在波特率时钟的节拍下,依照UART通信协议发送一个完整的字节的数据。当一个字节发送完毕后,Tx_Done产生一个高脉冲信号,以告知其它模块或逻辑一个字节的数据已经传输完成,可以开始下一个字节的发送了。其发送一个字节数据的实现代码如下:

33 /*计数波特率时钟,11个波特率时钟为一次完整的数据发送过程*/

34 always@(posedge Clk or negedge Rst_n)

35 if(!Rst_n)

36 Bps_Clk_Cnt 《= 4’b0;

37 else if(Bps_Clk_Cnt == 4‘d11)

38 Bps_Clk_Cnt 《= 4’b0;

39 else if(Bps_Clk)

40 Bps_Clk_Cnt 《= Bps_Clk_Cnt + 1‘b1;

41 else

42 Bps_Clk_Cnt 《= Bps_Clk_Cnt;

43

44 /*生成数据发送完成标志信号*/

45 always@(posedge Clk or negedge Rst_n)

46 if(!Rst_n)

47 Tx_Done 《= 1’b0;

48 else if(Bps_Clk_Cnt == 4‘d11)

49 Tx_Done 《= 1’b1;

50 else

51 Tx_Done 《= 1‘b0;

52

53 /*在开始发送起始位的时候就读取并寄存Data_Byte,以免Data_Byte变化导致数据的丢失*/

54 always@(posedge Clk or negedge Rst_n)

55 if(!Rst_n)

56 Data = 8’d0;

57 else if(Bps_Clk & Bps_Clk_Cnt == 4‘d1)

58 Data 《= Data_Byte;

59 else

60 Data 《= Data;

61

62 /*发送数据序列机*/

63 always@(posedge Clk or negedge Rst_n)

64 if(!Rst_n)

65 Rs232_Tx 《= 1’b1;

66 else begin

67 case(Bps_Clk_Cnt)

68 4‘d1: Rs232_Tx 《= 1’b0;

69 4‘d2: Rs232_Tx 《= Data[0];

70 4’d3: Rs232_Tx 《= Data[1];

71 4‘d4: Rs232_Tx 《= Data[2];

72 4’d5: Rs232_Tx 《= Data[3];

73 4‘d6: Rs232_Tx 《= Data[4];

74 4’d7: Rs232_Tx 《= Data[5];

75 4‘d8: Rs232_Tx 《= Data[6];

76 4’d9: Rs232_Tx 《= Data[7];

77 4‘d10: Rs232_Tx 《= 1’b1;

78 default:Rs232_Tx 《= 1‘b1;

79 endcase

80 end

在UART协议中,一个完整的字节包括一位起始位、8位数据位、一位停止位即总共十位数据,那么,要想完整的实现这十位数据的发送,就需要11个波特率时钟脉冲,如下所示:

分析一下串口发送与接收模块的设计代码

BPS_CLK信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的2到9个上升沿,发送8个数据位,第10个上升沿到第11个上升沿为停止位的发送。

单个串口接收模块中实现串口数据接收的主要代码如下所示:

025 always @ (posedge Clk or negedge Rst_n)

026 if(!Rst_n) begin

027 Rs232_Rx0 《= 1’b0;

028 Rs232_Rx1 《= 1‘b0;

029 Rs232_Rx2 《= 1’b0;

030 Rs232_Rx3 《= 1‘b0;

031 end

032 else begin

033 Rs232_Rx0 《= Rs232_Rx;

034 Rs232_Rx1 《= Rs232_Rx0;

035 Rs232_Rx2 《= Rs232_Rx1;

036 Rs232_Rx3 《= Rs232_Rx2;

037 end

038

039 wire neg_Rs232_Rx= Rs232_Rx3 & Rs232_Rx2 & ~Rs232_Rx1 & ~Rs232_Rx0;

040

041 assign Byte_En = neg_Rs232_Rx;

042

043 /*----------计数采样时钟--------------*/

044 /*9倍波特率采样时钟,故一个完整的接收过程有90个波特率时钟*/

045 reg[6:0]Sample_Clk_Cnt;

046 always @ (posedge Clk or negedge Rst_n)

047 if(!Rst_n)

048 Sample_Clk_Cnt 《= 7’d0;

049 else if(Sample_Clk)begin

050 if(Sample_Clk_Cnt == 7‘d89)

051 Sample_Clk_Cnt 《= 7’d0;

052 else

053 Sample_Clk_Cnt 《= Sample_Clk_Cnt + 1‘b1;

054 end

055 else

056 Sample_Clk_Cnt 《= Sample_Clk_Cnt;

057

058 reg [1:0]Start_Bit; /*起始位,这里虽然定义,但并未使用该位来判断接收数据的正确性,即默认接收都是成功的*/

059 reg [1:0]Stop_Bit; /*停止位,这里虽然定义,但并未使用该位来判断接收数据的正确性,即默认接收都是成功的*/

060 reg [1:0] Data_Tmp[7:0];/*此部分较为复杂,请参看说明文档中相关解释*/

061

062 always @ (posedge Clk or negedge Rst_n)

063 if(!Rst_n)begin

064 Data_Tmp[0] 《= 2’d0;

065 Data_Tmp[1] 《= 2‘d0;

066 Data_Tmp[2] 《= 2’d0;

067 Data_Tmp[3] 《= 2‘d0;

068 Data_Tmp[4] 《= 2’d0;

069 Data_Tmp[5] 《= 2‘d0;

070 Data_Tmp[6] 《= 2’d0;

071 Data_Tmp[7] 《= 2‘d0;

072 Start_Bit 《= 2’d0;

073 Stop_Bit 《= 2‘d0;

074 end

075 else if(Sample_Clk)begin

076 case(Sample_Clk_Cnt)

077 7’d0:

078 begin

079 Data_Tmp[0] 《= 2‘d0;

080 Data_Tmp[1] 《= 2’d0;

081 Data_Tmp[2] 《= 2‘d0;

082 Data_Tmp[3] 《= 2’d0;

083 Data_Tmp[4] 《= 2‘d0;

084 Data_Tmp[5] 《= 2’d0;

085 Data_Tmp[6] 《= 2‘d0;

086 Data_Tmp[7] 《= 2’d0;

087 Start_Bit 《= 2‘d0;

088 Stop_Bit 《= 2’d0;

089 end

090 7‘d3,7’d4,7‘d5: Start_Bit 《= Start_Bit + Rs232_Rx;

091 7’d12,7‘d13,7’d14:Data_Tmp[0] 《= Data_Tmp[0] + Rs232_Rx;

092 7‘d21,7’d22,7‘d23:Data_Tmp[1] 《= Data_Tmp[1] + Rs232_Rx;

093 7’d30,7‘d31,7’d32:Data_Tmp[2] 《= Data_Tmp[2] + Rs232_Rx;

094 7‘d39,7’d40,7‘d41:Data_Tmp[3] 《= Data_Tmp[3] + Rs232_Rx;

095 7’d48,7‘d49,7’d50:Data_Tmp[4] 《= Data_Tmp[4] + Rs232_Rx;

096 7‘d57,7’d58,7‘d59:Data_Tmp[5] 《= Data_Tmp[5] + Rs232_Rx;

097 7’d66,7‘d67,7’d68:Data_Tmp[6] 《= Data_Tmp[6] + Rs232_Rx;

098 7‘d75,7’d76,7‘d77:Data_Tmp[7] 《= Data_Tmp[7] + Rs232_Rx;

099 7’d84,7‘d85,7’d86:Stop_Bit 《= Stop_Bit + Rs232_Rx;

100 default:;

101 endcase

102 end

103 else ;

根据串口发送协议,一个字节的数据传输是以一个波特率周期的低电平作为起始位的,因此,成功接收UART串口数据的核心就是准确检测起始位。由于外部串口发送过来的数据与接收系统不在同一个时钟域,因此不能直接使用该信号的下降沿来作为检测标志,我们需要在fpga中,采用专用的边沿检测电路来实现,第25行至37行通过四个移位寄存器,存储连续四个时钟上升沿时外部发送数据线的状态,第39行通过比较前两个时钟时数据线的状态与后两个时钟时数据线的状态,来得到该数据线的准确下降沿,以此保证起始位的准确检测。

在简单的串口接收中,我们通常选取一位数据的中间时刻进行采样,因为此时数据最稳定,但是在工业环境中,存在着各种干扰,在干扰存在的情况下,如果采用传统的中间时刻采样一次的方式,采样结果就有可能受到干扰而出错。为了滤除这种干扰,这里采用多次采样求概率的方式。如下图,将一位数据平均分成9个时间段,对位于中间的三个时间段进行采样。然后对三个采样结果进行统计判断,如果某种电平状态在三次采样结果中占到了两次及以上,则可以判定此电平状态即为正确的数据电平。例如4、5、6时刻采样结果分别为1、1、0,那么就取此位解码结果为1,否则,若三次采样结果为0、1、0,则解码结果就为0。

分析一下串口发送与接收模块的设计代码

因为采样一位需要9个时钟上升沿,因此,采样一个完整的数据需要10*9,即90个时钟上升沿,这里,采样时钟为波特率时钟的9倍。产生采样时钟的部分代码如下所示:

089 /*-------波特率时钟生成定时器--------------*/

090 always@(posedge Clk or negedge Rst_n)

091 if(!Rst_n)

092 Count 《= 10‘d0;

093 else if(BPS_EN == 1’b0)

094 Count 《= 10‘d0;

095 else begin

096 if(Count == BPS_PARA)

097 Count 《= 10’d0;

098 else

099 Count 《= Count + 1‘b1;

100 end

101

102 //=====================================================

103 /*输出数据接收采样时钟*/

104 always @(posedge Clk or negedge Rst_n)

105 if(!Rst_n)

106 Sample_Clk 《= 1’b0;

107 else if(Count== 1)

108 Sample_Clk 《= 1‘b1;

109 else

110 Sample_Clk 《= 1’b0;

这里,BPS_PARA的计算原理和前面Tx_Bps_Gen模块中的BPS_PARA的计算原理一致,不过这里,因为采样时钟为波特率时钟的9倍,所以,BPS_PARA为Tx_Bps_Gen模块中的BPS_PARA的1/9。计算BPS_PARA的相关代码如下:

018 parameter system_clk = 50_000_000; /*输入时钟频率设定,默认50M*/

019

020 /*根据输入时钟频率计算生成各波特率时分频计数器的计数最大值*/

021 localparam bps9600 = system_clk/9600/9 - 1;

022 localparam bps19200 = system_clk/19200/9 - 1;

023 localparam bps38400 = system_clk/38400/9 - 1;

024 localparam bps57600 = system_clk/57600/9 - 1;

025 localparam bps115200 = system_clk/115200/9 - 1;

026 localparam bps230400 = system_clk/230400/9 - 1;

027 localparam bps460800 = system_clk/460800/9 - 1;

028 localparam bps921600 = system_clk/921600/9 - 1;

029

030 reg [31:0]BPS_PARA;/*波特率分频计数器的计数最大值*/

031

032 always@(posedge Clk or negedge Rst_n)

033 if(!Rst_n)begin

034 BPS_PARA 《= bps9600; /*复位时波特率默认为9600bps*/

035 end

036 else begin

037 case(Baud_Set) /*根据波特率控制信号选择不同的波特率计数器计数最大值*/

038 3‘d0: BPS_PARA 《= bps9600;

039 3’d1: BPS_PARA 《= bps19200;

040 3‘d2: BPS_PARA 《= bps38400;

041 3’d3: BPS_PARA 《= bps57600;

042 3‘d4: BPS_PARA 《= bps115200;

043 3’d5: BPS_PARA 《= bps230400;

044 3‘d6: BPS_PARA 《= bps460800;

045 3’d7: BPS_PARA 《= bps921600;

046 default: BPS_PARA 《= bps9600;/*异常情况,恢复到9600的波特率*/

047 endcase

048 end

责任编辑:lq6

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接收模块
    +关注

    关注

    1

    文章

    20

    浏览量

    10441
  • 时钟信号
    +关注

    关注

    4

    文章

    443

    浏览量

    28498
  • 分频计数器
    +关注

    关注

    0

    文章

    4

    浏览量

    7919

原文标题:基于ZX-2型FPGA开发板的串口示波器(二)

文章出处:【微信号:gh_9d70b445f494,微信公众号:FPGA设计论坛】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    ESP8266模块接收数据最大效率是多少呢?

    的结论,最高115200的速度,是没问题的。接收速度也是可以的,转成字节也在几KB。说我这里现在的状态,是有些慢。建议我用AT的程序试一下;也使用手机做AP测试一下。 问题: 1. ESP8266
    发表于 07-08 06:08

    ESP32串口接收发送数据,会有延迟是怎么回事?

    直接打印,而不是每次进来就打印次; 同时接收完数据在立即发送时,会有2S的延时,串口才会发送接收到的数据。 求大神帮忙参考
    发表于 06-26 08:07

    开发板使用ASCLIN Master通信,串口发送的数据,开发板收不到是怎么回事?

    开发板使用ASCLIN Master通信,通过LIN调试器和电脑的上位机通信,目前开发板发送的8byte数据,上位机的串口接收到,但是串口发送
    发表于 06-04 13:09

    STM32F746g-disco开发板串口配置串口助手无法接收到数据是怎么回事?

    使用此开发板配置usart6实现串口发送数据,串口助手接收不到,不知道问题是代码不对还是硬件驱动没有安装。尝试下载别的
    发表于 05-20 06:21

    单片机在串口发送数据时可以接收串口助手发的数据,为什么只能接收两字节?

    最近做串口通信,单片机在串口发送数据时可以接收串口助手发的数据,不过只能接收两字节。当
    发表于 05-08 07:52

    HAL库STM32串口2发送数组给串口1异常的原因?

    刚结束STM32,所以想请教一下。就是我想用串口2接收数据然后发送串口1,串口1
    发表于 04-25 06:01

    freertos串口接收数据后如何发送给任务?

    正在学freertos。串口中断接收帧数据后,放到数组里,如何将数据发送给任务呢? 如果用消息队列,是否建立的消息队列需要是数组类型的?还是说消息列表建立成uint8类型的,列表长
    发表于 04-18 06:36

    TSMaster 序列发送模块在汽车开发测试中的应用

    。本文重点和大家分享一下关于TSMaster中序列发送模块的使用。本文关键字:序列发送、无代码发送
    的头像 发表于 02-19 14:00 612次阅读
    TSMaster 序列<b class='flag-5'>发送</b><b class='flag-5'>模块</b>在汽车开发测试中的应用

    SPI全双工模式数据接收异常的个原因

    前面给小伙伴讲过串口发送接收异常的可能原因,今天我们讲SPI全双工模式数据接收异常的
    的头像 发表于 01-23 09:31 1230次阅读
    SPI全双工模式<b class='flag-5'>下</b>数据<b class='flag-5'>接收</b>异常的<b class='flag-5'>一</b>个原因

    labview串口接收数据怎么不丢帧

    LabVIEW是款功能强大的图形化开发环境,广泛应用于数据采集与处理、自动化控制系统等领域。在串口通信应用中,如何保证数据的稳定传输是个重要的问题。本文将详细介绍LabVIEW串口
    的头像 发表于 01-08 11:38 1985次阅读

    dma和串口直接发送的区别

    ,我们先来介绍一下DMA和串口直接发送的原理和工作方式。 DMA是种高速数据传输技术,它允许外设直接与内存进行数据交换,而不需要CPU的介入。有了DMA技术,CPU在进行数据传输时可
    的头像 发表于 01-07 17:43 2629次阅读

    单片机串口通信的接收发送

    单片机串口通信是种常见的通信方式,它可以实现单片机与外部设备的数据交换。在实际应用中,单片机串口通信常用于与电脑、传感器、LCD显示屏等外设进行数据传输。 首先,我们来了解一下单片机
    的头像 发表于 12-20 14:03 3428次阅读

    单片机串口通信的接收发送程序

    单片机串口通信的接收发送程序 、引言 单片机串口通信是种常见的通信方式,广泛应用于各种嵌入
    的头像 发表于 12-19 13:57 5544次阅读

    智能车ROS与STM32串口通信代码

    这里以个智能车代码工程为例,抽取串口通信部分代码 在头文件中,进行串口头文件的包含 # include 在类的定义中,什么
    的头像 发表于 11-26 17:47 1116次阅读

    ROS与STM32串口通信代码

    这里以个智能车代码工程为例,抽取串口通信部分代码 在头文件中,进行串口头文件的包含 # include 在类的定义中,什么
    的头像 发表于 11-17 18:10 1024次阅读