精华内容
下载资源
问答
  • 半双工通信
    千次阅读
    2022-05-01 09:09:03

    串行通信中包括有3种数据传送方式,传送方式研究的是 对象 和方向性。

    单工通信:单向传送,只能是一个方向传送。 比如:A传送到传送B,并且只能A传送给B。

    全双工通信:双向传送,在某一时刻,双方可以同时接收和发送数据。

    半双工通信:双向传送,在某一时刻,只能一方为发送,另一方为接收。例如:CAN通信、485通信。

    更多相关内容
  • stm32uart单线半双工通信 在使用数字舵机时,所用到的通信方式为uart通信,但舵机只有三根接线,出去vcc和gnd,只有一条通信线,也就是说要实现双向通信,只能使用单线半双工模式,本人在利用stm32标准库配置uart...
  • 单线半双工通信的官方资料 单线半双方模式通过设置USART_CR3寄存器的HDSEL位选择。在这个模式里,下面的位必须保持清零状态: ● USART_CR2寄存器的LINEN和CLKEN位 ● USART_CR3寄存器的SCEN和IREN位 USART可以配置...
  • 单线半双工通信.zip

    2020-04-04 14:33:26
    单线实现两块STM32通信,作为个人笔记的附带内容,涉及知识面窄。用正点原子STM32精英版与最小系统板通信通信协议模拟AX12数字舵机的通信规则。
  • 基于STM32F103串口2的单线半双工收发,内嵌Dynamixel新版通信协议(protoco 2.0),代码简单好用。
  • 【Verilog实战】异步FIFO设计(附源码RTL/TB) 【Verilog实战】UART通信协议,半双工通信方式(附源码RTL/TB) 【Verilog实战】SPI协议接口设计(附源码RTL/TB) 【Verilog实战】AMBA 3 APB接口设计(附源码RTL/TB)...

    脚 本:makefile(点击直达
    应用工具:vcs 和 verdi


    写在前面

    1. 这个专栏的内容记录的是个人学习过程,博文中贴出来的代码是调试前的代码,方便bug重现。
    2. 调试后的程序提供下载,【下载地址(待更新)】
    3. 发现了一个Verilog宝藏刷题网站,网站提供在线仿真环境(点击直达),同时新开了一个<刷题记录>专栏,持续打卡中…

    路 线:



    一、Overview

    (1)Theory

      UART(Universal Asynchronous Receiver/Transmitter),通用异步收发器,是一种异步全双工串行通信协议,可实现单工通信、半双工通信和全双工通信。
    在这里插入图片描述

    单工通信方式:主机只能发送数据,从机只能接收数据,数据流向始终由发送端流向接收端;
    半双工通信方式:同一时刻,只能由主机向从机或从机向主机发送数据;
    全双工通信方式:同一时刻,主机和从机都可以互发数据。


      UART由Tx和Rx两根数据线组成,因为没有参考时钟信号,所以通信的双方必须约定串口波特率、数据位宽、奇偶校验位、停止位等配置参数,从而按照相同的速率进行通信。

    UART协议相关概念:

    1. 波特率(baud rate)
      单位bps(bit per second),每秒传输的二进制位数
    2. 起始位(start bit)
      开始发起传输的标志
    3. 停止位(stop bit)
      结束传输的标志
    4. 校验位(check bit)
      为了保证传输可靠性增加的校验信息。奇校验: 数据+校验位中总的1的个数为奇数;偶校验反之。

    (2)Baud Rate

    • 每bit时间宽度及每bit时钟周期数
      bit_width = 1/(baud rate);
      bit_clk_num = bit_width/T,注意单位要统一

    (3)Check Bit

    • data中”1”个数计算
      奇校验:^data[7:0]==1’b1,1的个数为奇数;
      偶校验:^data[7:0]==1’b0,1的个数为偶数

    (4)Demand

    1. 接收上一级模块并行数据(cmd_i),将数据按照UART协议发送出去
    模块工作类型信号名方向位宽描述
    mastertxoutput1发送信号
    masterrxinput1接收信号
    slavetxinput1接收数据
    slaverxoutput1发送数据
    1. 采用奇校验
    2. 1bit停止位
    3. 波特率115200,波特率可配置
    4. 工作时钟50MHz
    5. 当接收到rx数据时,将并行数据反馈给上一级模块(半双工通信)
    6. 规定数据包一帧数据是8bit,两次数据传输之间要求有delay,且delay可配置
      在这里插入图片描述

    二、Interface Description

    Signal NameWidthDirectionDescription
    clk1inputSystem clk signal, 50Mhz
    rst_n1inputSystem reset signal,negedge
    cmd_i16input[15]:读写指示;1:写,0:读
    [14:8]:地址位
    [7:0]:数据位
    cmd_rdy1output握手信号ready
    cmd_vld1input握手信号valid
    tx1outputuart发送数据端
    rx1inputuart接收数据端
    read_vld1output读数据valid
    read_data8output读到的数据

    三、Block Diagram

    在这里插入图片描述


    四、Timeing

    (1)write timing

    在这里插入图片描述

    (2)read timing

    在这里插入图片描述


    五、Design and Functional Verification

    (1)RTL

    /*-- modified by xlinxdu, 2022/04/30
      -- cmd_i 16bit,rw_flag:1bit,data addr:7bit,data:8bit
      -- clock freq:50MHz
      -- band rate :115200bps
      -- check bit :odd
      -- stop bit  :1bit
      -- clock num of uart :freq/band_rate = 50M/115200 = 434clk
    */
    
    module uart#(
      parameter CMD_ADDR_WIDTH = 7,
      parameter CMD_DATA_WIDTH = 8,
      parameter RW_FLAG        = 1,
      parameter CMD_WIDTH      = CMD_ADDR_WIDTH +CMD_DATA_WIDTH+RW_FLAG,
      parameter DELAY_CLK      = 1
    
      )(
      input                       clk_i        ,
      input                       rst_n_i      ,
    //-- tx
      input       [CMD_WIDTH-1:0] cmd_data_i   ,
      input                       cmd_vld_i    ,
      output reg                  cmd_rdy_o    ,
      output reg                  tx_o         ,
    //-- rx
      input                       rx_i         ,
      output reg                  rd_vld_o     ,
      output reg  [CMD_DATA_WIDTH-1:0] rd_data_o
      );
      reg                    uart_en      ;
      reg    [          8:0] tx_bit_cnt   ;
      reg    [          3:0] tx_bit_num   ;
      reg    [CMD_WIDTH-1:0] cmd_data_buf ;
      wire   [          7:0] cmd_data_high;
      wire   [          7:0] cmd_data_low ;
      wire                   rw_flag      ;
      reg                    wr_data_flag ;
      reg                    tx_uart_done ;
    
      reg                    dly_cnt_en   ;
      reg    [          6:0] dly_cnt      ;
      reg                    rd_uart_en   ;
      reg    [          8:0] rx_bit_cnt   ;
      reg    [          3:0] rx_bit_num   ;
      reg    [CMD_DATA_WIDTH-1:0] rx_data_buf  ;
      reg    [          2:0] rx_dly       ;
      wire                   negedge_rx   ;
      wire                   rx_sync      ;
      wire                   rx_done      ;
      reg                    rx_uart_done ;
      
      wire                   rd_vld ;//assign --wire-->reg_o
      wire   [CMD_DATA_WIDTH-1:0] rd_data;
    //-- cmd_rdy_o
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        cmd_rdy_o <= 1'b1;
      end
      else if (tx_uart_done) begin
        cmd_rdy_o <= 1'b1;
      end
      else if(cmd_vld_i) begin
        cmd_rdy_o <= 'b0;
      end
    end
    
    //-- uart_en
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        uart_en <= 1'b0;
      end
      else if(rw_flag && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433 || dly_cnt_en) begin
        uart_en <= 1'b0;
      end
      else if(tx_uart_done) begin
        uart_en <= 1'b0;
      end
      else if(cmd_vld_i)begin
        uart_en <= 1'b1;
      end
    end
    
    //-- tx_bit_cnt
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        tx_bit_cnt <= 9'd0;
      end
      else if(uart_en) begin
        if(tx_bit_cnt == 9'd433) begin
          tx_bit_cnt <= 9'd0;
        end
        else begin
          tx_bit_cnt <= tx_bit_cnt + 1'd1;
        end
      end
      else if(dly_cnt_en) begin
        tx_bit_cnt <= 9'd0; 
      end
    end
    
    //-- tx_bit_num
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        tx_bit_num <= 4'd0;
      end
      else if(uart_en) begin 
        //--uart_en keep high,tx_bit_num=11~12-->delay 2bit
        if(tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433) begin
          tx_bit_num <= 4'd0;
        end
        else if(tx_bit_cnt == 9'd433)begin
          tx_bit_num <= tx_bit_num +1'd1;
        end  
      end
      else if(dly_cnt_en)begin
          tx_bit_num <= 4'd0;
      end
    end
    
    //-- cmd_data_buf
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        cmd_data_buf <= {CMD_WIDTH{1'B0}};
      end
      else if(cmd_vld_i && cmd_rdy_o)begin
        cmd_data_buf <= cmd_data_i;
      end
    end
    
    //-- 
    assign {cmd_data_high,cmd_data_low} = cmd_data_buf;
    assign rw_flag = cmd_data_buf[CMD_WIDTH-1];
    
    //-- wr_data_flag
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        wr_data_flag <= 1'b0;
      end
      else if (tx_uart_done)begin
        wr_data_flag <= 1'b0;
      end
      else if(rw_flag && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433) begin
        wr_data_flag <= 1'b1;
      end
    end
    
    //-- TX
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        tx_o <= 1'd1;
      end
      else if(uart_en) begin
        //-- send start bit
        if(tx_bit_num == 4'd0)begin
          tx_o <= 1'b0;
        end
        //-- send data bit
        else if(tx_bit_num >= 4'd1 && tx_bit_num <= 4'd8)begin
          //--1:send cmd_data_high and cm_data_low
          if(rw_flag)begin
            if(wr_data_flag)begin//1:low
              tx_o <= cmd_data_low[tx_bit_num-1];
            end
            else begin
              tx_o <= cmd_data_high[tx_bit_num-1];
            end
          end
          //--0:only send cmd_data_high
          else begin
            tx_o <= cmd_data_high[tx_bit_num-1];
          end
        end
        //-- send check bit
        else if(tx_bit_num == 4'd9) begin
          if(rw_flag)begin//-- 1:write
            tx_o <= wr_data_flag ? ~(^cmd_data_low) : ~(^cmd_data_high);
          end
          else begin
            tx_o <= ~(^cmd_data_high);
          end
        end
        //-- send stop bit
        else if(tx_bit_num == 4'd10) begin
          tx_o <= 1'b1;
        end
      end
      else begin
        if(dly_cnt_en) begin
          tx_o <= 1'b1;
        end
      end
    end
    
    //-- tx_uart_done
    assign tx_uart_done = (~rw_flag || wr_data_flag) && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433;
    
    //assign tx_uart_done = (~rw_flag && tx_bit_num == 4'd10&& tx_bit_cnt == 9'd433)||
    //                   ( rw_flag && tx_bit_num == 4'd10&& tx_bit_cnt == 9'd433 && wr_data_flag);
    
    
    //-- dly_cnt_en
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        dly_cnt_en <= 1'b0;
      end
      else if(rw_flag && tx_bit_num == 4'd10 && tx_bit_cnt == 9'd433)begin
        dly_cnt_en <= 1'b1;
      end
      else if(dly_cnt == 7'd99)begin
        dly_cnt_en <= 1'b0;
      end
    end
    
    //-- dly_cnt
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        dly_cnt <= 7'd0;
      end
      else if(dly_cnt_en)begin
        dly_cnt <= dly_cnt +1'b1;
      end
      else begin
        dly_cnt <= 7'd0;
      end
    end
    
    
    //-- RX
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rx_dly <= 3'b000;
      end
      else begin
        rx_dly <= {rx_dly[1:0],rx_i};
      end
    end
    assign negedge_rx = {rx_dly[2:1] == 2'b10};
    assign rx_sync = rx_dly[2];
    
    //-- rx_uart_done
    assign rx_done = ~rw_flag && rx_bit_num == 4'd10&& rx_bit_cnt == 9'd433;
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rx_uart_done <= 1'b0;
      end
      else begin
        rx_uart_done <= rx_done;
      end
    end
    //-- rd_uart_en
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rd_uart_en <= 1'b0;
      end
      else if(negedge_rx)begin
        rd_uart_en <= 1'b1;
      end
      else if(rx_done)begin
        rd_uart_en <= 1'b0;
      end
    end
    //-- rx_bit_cnt
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rx_bit_cnt <= 9'd0;
      end
      else if(rd_uart_en) begin
        if(rx_bit_cnt == 9'd433) begin
          rx_bit_cnt <= 1'd0;
        end
        else begin
          rx_bit_cnt <= rx_bit_cnt + 1'd1;
        end
      end
    end
    
    //-- rx_bit_num
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rx_bit_num <= 4'd0;
      end
      else if(rd_uart_en) begin 
        //--rd_uart_en keep high,tx_bit_num=11~12-->delay 2bit
        if(rx_bit_num == 4'd10 && rx_bit_cnt == 9'd433) begin
          rx_bit_num <= 4'd0;
        end
        else if(rx_bit_cnt == 9'd433)begin
          rx_bit_num <= rx_bit_num +1'd1;
        end  
      end
      else begin
          rx_bit_num <= 4'd0;
      end
    end
    //-- rx_data_buf
    
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rx_data_buf <= 8'd0;
      end
      else if(rx_bit_cnt == 9'd216 && rx_bit_num >= 4'd1 && rx_bit_num <= 4'd8)begin
        rx_data_buf <= {rx_sync,rx_data_buf[7:1]};
      end
    end
    
    //-- check bit
    assign check_flag = rx_bit_num == 4'd9 &&rx_bit_cnt ==9'd216 && rx_sync == ~^rx_data_buf;
    
    assign rd_vld = check_flag;
    assign rd_data = rd_vld?rx_data_buf:rd_data;
    
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rd_vld_o <= 1'b1;
      end
      else if(rd_vld)begin 
        rd_vld_o <= 1'b1;
      end
      else if(negedge_rx && rx_bit_num == 4'd0)begin
        rd_vld_o <= 1'b0;
      end
    end
    always @ (posedge clk_i or negedge rst_n_i) begin
      if (!rst_n_i) begin
        rd_data_o <= 8'd0;
      end
      else if(rx_done)begin 
        rd_data_o <= rd_data;
      end
    end
    endmodule
    
    

    (2)Test Bench

    module uart_tb;
      reg          clk_i      ; 
      reg          rst_n_i    ;
    
      reg          cmd_vld_i  ;  
      reg  [15:0]  cmd_data_i ; 
      wire         cmd_rdy_o  ;
      wire         tx_o       ;
      
      reg          rx_i       ;
      wire         rd_vld_o   ;
      wire [ 7:0]  rd_data_o  ;
    
    initial begin
      clk_i = 1;
    end
    
    always begin
      #10 clk_i = ~clk_i;
    end
    initial begin
      rst_n_i    = 1;
      cmd_vld_i  = 0;
      cmd_data_i= 16'b1000_0010_0011_0010;//16'h8232
      rx_i = 1; 
      #5 rst_n_i = 0;
      #5 rst_n_i = 1;
      #10 cmd_vld_i=1;
      #180000 cmd_data_i = 16'b0110_1011_0001_1000;//16'h6b14
      #90000 cmd_vld_i =0;
    end
    initial begin
      #360020 
      rx_i = 0;//start bit
      
      #8660 rx_i = 0;
      #8660 rx_i = 1;
      #8660 rx_i = 0;
      #8660 rx_i = 0;
      #8660 rx_i = 1;
      #8660 rx_i = 1;
      #8660 rx_i = 0;
      #8660 rx_i = 0;
      
      #8660 rx_i = 0;//check bit
      #8660 rx_i = 1;//stop bit
      #8660 rx_i = 1;//delay bit
      #8660;
    end
    
    
    uart u_uart
    (
      .clk_i     (clk_i     ),
      .rst_n_i   (rst_n_i   ),
      .cmd_vld_i (cmd_vld_i ),
      .cmd_data_i(cmd_data_i),
      .cmd_rdy_o (cmd_rdy_o ),
      .tx_o      (tx_o      ),
      .rx_i      (rx_i      ),
      .rd_vld_o  (rd_vld_o  ),
      .rd_data_o (rd_data_o )
    );
    
    initial begin
    	#700000 $finish;
      $fsdbDumpfile("uart.fsdb");
      $fsdbDumpvars             ;
      $fsdbDumpMDA              ;
    end
    
    endmodule
    
    

    六、Result

    (1)write

    在这里插入图片描述

    • 发送16’h8232(1000_0010_0011_0010)和16’h6B18(0110_1011_0001_1000)
      1、 M1-M3发送0100_0001;
      2、 M3-M4发送校验位1;
      3、 M4-M5为停止位,M5-M6为延时
      4、 M7-M9发送0100_1100;
      5、 M9-M10发送校验位0;
      6、 M13-M15发送1101_0110;只发送地址位。

    (2)read

    在这里插入图片描述

    • 接收数据8’h32(0011_0010)
      1、 M18下降沿来临,开始接收数据。
      2、 M19-M20接收到数据0100_1100;
      3、 M20-M21接收校验位0。
      4、 C1:在接收完stop位后,如果校验位和传过来的校验位一致,将数据输出到rd_data_o。此处数据为8’h32。校验成功。

    ✍✍☛ 题库入口

      经过一段时间的沉淀,发现入行IC行业,自己的底子还是很差,写的文章质量参差不齐,也没能解答大家的疑问。还是要实打实从基础学起,由浅入深。因此决定推倒重来,通过补充/完善基础知识的同时,通过题库刷题不断提高自己的设计水平,题库推荐给大家(点击直达),<题库记录>栏目不定期更新,欢迎前来讨论。2022.08.29 记



    作者:xlinxdu
    版权:本文是作者原创,版权归作者所有。
    转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。

    展开全文
  • 文章目录 TCP协议/UDP协议介绍 三种通信方式 实现TCP半双工通信 所用到的结构体与函数 源代码 运行结果 实现UDP半双工通信 源代码 运行结果 参考文章 TCP协议/UDP协议介绍 TCP/IP(Transmission Control Protocol/...

    TCP协议/UDP协议介绍

    • TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议
    • 下面我们来简要了解TCP/IP的四层模型:
      网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收。数据帧是网络信息传输的基本单元,ARP和RARP协议
      网络层:负责将数据帧封装成IP数据报,同时负责选择数据报的路径,即路由,IP和ICMP协议
      传输层:负责端到端之间的通信会话连接与建立,传输协议的选择根据数据传输方式而定,UDP和TCP协议
      应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程,FTP、TELNET、DNS、SMTP、POP3 协议

    • Internet 的传输层有两个主要协议,互为补充。无连接的是 UDP,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的事情。面向连接的是 TCP,该协议几乎做了所有的事情。

    • 传输控制协议(TCP,Transmission Control Protocol) 是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。

      TCP三次握手过程
      第一次握手:主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B,向主机B 请求建立连接,通过这个数据段, 主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
      第二次握手:主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用那个序列号作为起始数据段来回应我
      第三次握手:主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,我现在要开始传输实际数据了,这样3次握手就完成了,主机A和主机B 就可以传输数据了。

      TCP建立连接要进行3次握手,而断开连接要进行4次
      第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;
      第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;
      第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;
      第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。
      由TCP的三次握手和四次断开可以看出,TCP使用面向连接的通信方式, 大大提高了数据通信的可靠性,使发送数据端和接收端在数据正式传输前就有了交互, 为数据正式传输打下了可靠的基础。

    • UDP(User Datagram Protocol) 全称是用户数据报协议,是一种非面向连接的协议,这种协议并不能保证我们的网络程序的连接是可靠的,而TCP是面向连接的,提供可靠的字节流。然而,有些情况下更适合用UDP而不是TCP。有些流行的应用程序是用UDP实现的:DNS(域名系统)、NFS(网络文件系统)和SNMP(简单网络管理协议)就是这样的例子。
      1.UDP是一个非连接的协议,传输数据之前源端和终端不建立连接, 当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、 计算机的能力和传输带宽的限制; 在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

      2.由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等, 因此一台服务机可同时向多个客户机传输相同的消息。

      3.UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。

      4.吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、 源端和终端主机性能的限制。

      5.UDP使用尽最大努力交付,即不保证可靠交付, 因此主机不需要维持复杂的链接状态表(这里面有许多参数)。

      6.UDP是面向报文的。发送方的UDP对应用程序交下来的报文, 在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界, 因此,应用程序需要选择合适的报文大小。

      我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常, 其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包, 如果数据包是否到达的消息及时反馈回来,那么网络就是通的。

    • 小结TCP与UDP的区别:

      1、基于连接与无连接;

      2、对系统资源的要求(TCP较多,UDP少);

      3、UDP程序结构较简单;

      4、流模式与数据报模式 ;

      5、TCP保证数据正确性,UDP可能丢包;

      6、TCP保证数据顺序,UDP不保证。 ## 三种通信方式


    三种通信方式

    1、单向通信:又称为单工通信,即只能有一个方向的通信而没有反方向的交互。无线电广播或有线电广播以及电视广播就属于这种类型。
    单向通信只需要一条信道,而双向交替通信或双向同时通信则都需要两条信道(每个方向各一条)。显然,双向同时通信的传输效率最高。不过应当指出,虽然电信局为打电话的用户提供了双向同时通信的信道,但有效的电话交谈一般都还是双方交替通信。当双方发生争吵时往往就是采用双向同时通信的方式。

    2、半双工通信,是指数据可以沿两个方向传送.但同一时刻一个信道只允许单方向传送,因此义被称为双向交替通信。例如,无线对讲机就是一种半双工设备,在同一时间内只允许一方讲话。

    3、全双工通信,是指同时发生在两个方向上的一种数据传输方式,如图中©所示。电话机就是一种全双工设备,其通话双方可以同时进行对话。计算机之间的高速数据通信也是这种方式。

    双向交替通信又称为半双工通信,即通信的双方都可以发送信息,但不能双方同时发送(当然也就不能同时接收)。这种通信方式是一方发送另一方接收,过一段时间后再反过来。此次要实现的就是半双工通信


    实现TCP半双工通信

    基于TCP协议的socket的server端程序编程步骤:
    1、建立socket ,使用socket()
    2、绑定socket ,使用bind()
    3、打开listening socket,使用listen()
    4、等待client连接请求,使用accept()
    5、收到连接请求,确定连接成功后,使用输入,输出函数recv(),send()与client端互传信息
    6、关闭socket,使用close()

    基于TCP协议的socket的Client程序编程步骤:
    1、建立socket,使用socket()
    2、通知server请求连接,使用connect()
    3、若连接成功,就使用输入输出函数recv(),send()与server互传信息
    4、关闭socket,使用close()

    所用到的结构体与函数

    1.IPV4套接字地址结构体:

    struct sockaddr_in{
        uint8_t             sin_len;
        sa_famliy_t         sin_fanliy;/*协议家族*/
        in_port_t           sin_port;/*端口号*/
        struct in_addr      sin_addr;/*IP地址,struct in_addr{in_addr_t s_addr;}*/
        char                sin_zero[8];
    };
    

    2.通用套接字地址结构体:

    struct sockaddr{
        uint8_t       sa_len;
        sa_famliy     sa_famliy;
        char          sa_data[14];
    };
    

    3.socket():

    int socket(int domain,int type, int protocol);
    /*
    创建一个套接字:
    返回值:
        创建成功返回一个文件描述符(0,1,2已被stdin、stdout、stderr占用,所以从3开始)
        失败返回-1。
    参数:
        domain为协议家族,TCP属于AF_INET(IPV4);
        type为协议类型,TCP属于SOCK_STREAM(流式套接字);
        最后一个参数为具体的协议(IPPOOTO_TCP为TCP协议,前两个已经能确定该参数是TCP,所以也可以填0)
    */
    
    

    4.bind():

    int bind(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
    /*
    将创建的套接字与地址端口等绑定
    返回值:成功返回0,失败返回-1.
    参数:
        sockfd为socket函数返回接受的文件描述符,
        addr为新建的IPV4套接字结构体
        注意:定义若是使用struct sockaddr_in(IPV4结构体)定义,但是该参数需要将struct sockaddr_in *类型地址强转为struct sockaddr *类型(struct sockaddr是通用类型)。
        最后一个参数为该结构体所占字节数。
    */
    

    5.listen():

    int listen(int sockfd,int backlog);
    /*
    对创建的套接字进行监听,监听有无客户请求连接
    返回值:有客户请求连接时,返回从已完成连接的队列中第一个连接(即完成了TCP三次握手的的所有连接组成的队列),否则处于阻塞状态(blocking)。
    参数:
    sockfd依然为socket函数返回的文件描述符;
    blocklog为设定的监听队列的长度。可设为5、10等值但是不能大于SOMAXCONN(监听队列最大长度)
    */
    
    

    6.connect()和accept():

    int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
    /*
    客户端请求连接
    返回值:成功返回0,失败返回-1
    参数:客户端的socket文件描述符,客户端的socket结构体地址以及结构体变量长度
    */
    int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);
    /*
    从监听队列中接收已完成连接的第一个连接
    返回值:成功返回0,失败返回-1
    参数:服务器socket未见描述符,服务器的socket结构体地址以及结构体变量长度
    */
    

    7.send()和recv():

    ssize_t send(int sockfd,const void * buf,size_t len,int flags);
    /*
    发送数据
    返回值:成功返回发送的字符数,失败返回-1
    参数:buf为写缓冲区(send_buf),len为发送缓冲区的大小,flags为一个标志,如MSG_OOB表示有紧急带外数据等
    */
    ssize_t recv(int sockfd,void *buf, size_t len, int flags);
    /*
    接收数据
    返回值参数与send函数相似
    不过send是将buf中的数据向外发送,而recv是将接收到的数据写到buf缓冲区中。
    */
    

    8.close():

    int close(int fd);
    /*
    关闭套接字,类似于fclose,fd为要关闭的套接字文件描述符
    失败返回-1,成功返回0
    */
    

    源代码

    服务端:

    tcp_server.c

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <string.h>
    #include <stdio.h>
    
    int main( int argc, char **argv ) /*接收IP地址和端口号*/
    {
    	int sid = socket(AF_INET,SOCK_STREAM,0);
    	//创建socket,第一个参数使用IPv4协议,第二个参数使用流的方式传播,第三个参数默认为0
    	struct sockaddr_in addr = {0};  		//定义ip地址
    	addr.sin_family = AF_INET;      		//声明协议用的是IPv4
    	addr.sin_port = htons(atoi(argv[2])); 	//绑定到哪一个端口号当中,从第三个参数中获取
    	
    	addr.sin_addr.s_addr = inet_addr(argv[1]); //监听哪一个ip地址,从第二个参数中获取
    	int res = bind(sid,(struct sockaddr *)&addr,sizeof(struct sockaddr));//进行绑定,第二个参数为绑定的地址和端口号
    	printf("bind res = %d\n",res);	//绑定成功输出0,失败则输出-1
    	listen(sid,10);
    	//进行监听,第一个参数是监听哪一个socket,第二个是能接受的最大的连接数
    	char read_buf[128] = {0};  //储存客户端发来的信息
    	char write_buf[128] = {0}; //储存服务端发送的信息
    	int len,cid;
    	cid = accept(sid,NULL,NULL);//返回客户端的描述符,通过cid可以跟客户端通信
    	while(1)
    	{
    		bzero(read_buf,sizeof(read_buf)); //置字节字符串的前n个字节为零,达到清空之前的信息的目的
    		bzero(read_buf,sizeof(write_buf));
    		read(cid,read_buf,sizeof(read_buf)-1);
    		if(strcmp(read_buf,"quit\n") == 0) //如果收到的信息为quit便结束通信
    		{
    		   printf("client quit the communication!\n");
    		   break;
    		}
    		   printf("client:%s",read_buf); //输出客户端发送的信息
    		   printf("server:");			
    		fgets(write_buf,sizeof(write_buf),stdin);
    		write(cid,write_buf,strlen(write_buf));	
    		if(strcmp(write_buf,"quit\n") == 0) //发出信息quit,结束本次通信
    		{
    		   printf("sever quit the communication!\n");
    		   break;
    		}
    		
    	}
    	close(cid);
    	close(sid);	
    }
    

    客户端:

    tcp_client.c

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <string.h>
    #include <stdio.h>
    
    int main(int argc,char **argv) /*接收IP地址和端口号*/
    {
    	if(argc < 3)		//参数必须为三个
    	{
    		perror("param must >= 3");
    		return -1;
    	}
    
    	int cid = socket(AF_INET,SOCK_STREAM,0); 
    	struct sockaddr_in saddr = {0};//存放要连接的服务器的ip地址和端口号
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(atoi(argv[2]));
    	saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
    	int res = connect(cid,(struct sockaddr *)&saddr,sizeof(struct sockaddr)); //跟服务器连接,成功返回0,失败返回-1
    	printf("connect res = %d\n",res);
    
    	char read_buf[128] = {0};	//储存客户端发来的信息
    	char write_buf[128] = {0};	//储存服务端发送的信息
    	while(1)
    	{
    		bzero(read_buf,sizeof(read_buf));   //置字节字符串的前n个字节为零,达到清空之前的信息的目的
    		bzero(write_buf,sizeof(write_buf)); 
    		printf("client:");
    		fgets(write_buf,sizeof(write_buf),stdin);
    		send(cid,write_buf,strlen(write_buf),0);
    		if(strcmp(write_buf,"quit\n") == 0) //如果发送的信息为quit便结束本次通信  
    		{
    		   printf("client quit the communication!\n");
    		   break;
    		}
    		recv(cid,read_buf,sizeof(read_buf)-1,0);
    		if(strcmp(read_buf,"quit\n") == 0) //如果收到的信息为quit便结束通信
    		{
    		   printf("sever quit the communication!\n");
    		   break;
    		}
    		printf("server:%s",read_buf);	//输出客户端发送的信息
    	}
    	close(cid);
    }
    

    运行结果

    127.0.0.1是回送地址,指本地机,一般用来测试使用。回送地址(127.x.x.x)是本机回送地址(Loopback Address),即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。

    在Windows系统中,这个地址有一个别名“Localhost”。寻址这样一个地址,是不能发到网络接口的。除非出错,否则在传输介质上永远不应该出现目的地址为“127.0.0.1”的数据包。

    运行程序需要打开Linux中打开两个终端,分别编译运行服务端和客户端的源文件,IP地址选择本地地址,端口号建议选择8000以上比较稳定比如9999,首先客户端发送信息到服务端,服务端在接收到客户端的信息后才可以回复,双方都可以输出quit来结束本次通信

    客户端:

    服务端:

    实现UDP半双工通信

    基于UDP协议的socket的接收端编程步骤:
    1、建立socket,使用socket()
    2、绑定socket,使用bind()
    3、以recvfrom()函数接收发送端传来的数据
    4、关闭socket,使用close()

    基于UDP协议的socket的发送端编程步骤:
    1、建立Socket,使socket()
    2、用sendto()函数向接收端发送数据。
    3、关闭socket,使用close()函数

    源代码

    接收端:

    udp_recvpp.c

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
    
    int main(int argc,char **argv)
    {
    	if(argc < 3)
    	{
    		perror("param must >= 3");
    		return -1;
    	}
    
    	int cid = socket(AF_INET,SOCK_DGRAM,0);
    	if(cid < 0)
    	{
    		perror("socket error");
    		return -1;
    	}
    
    	struct sockaddr_in addr = {0},sendaddr = {0};
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(atoi(argv[2]));
    	addr.sin_addr.s_addr = inet_addr(argv[1]);
    
    	int res = bind(cid,(struct sockaddr *)&addr,sizeof(struct sockaddr));
    	printf("bind res = %d\n",res);
    
    	char read_buf[128] = {0};
    	char write_buf[128] = {0};
    	int size = sizeof(struct sockaddr);
    
    	while(1)
    	{
    		bzero(read_buf,sizeof(read_buf));
    		bzero(write_buf,sizeof(write_buf));
    		recvfrom(cid,read_buf,sizeof(read_buf) - 1,0,(struct sockaddr *)&sendaddr,&size);
    		if(strcmp(read_buf,"quit\n") == 0)
    		{
    		   printf("send quit the communication!\n");
    		   break;
    		}
    		char ipbuf[16] = {0};
    		inet_ntop(AF_INET,&sendaddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf));
    		printf("send:%s",read_buf);
    		printf("send addr: ip = %s,port = %d\n",ipbuf,ntohs(sendaddr.sin_port));
    		 printf("recv:");
    		fgets(write_buf,sizeof(write_buf),stdin);
    		sendto(cid,write_buf,strlen(write_buf),0,(struct sockaddr *)&sendaddr,sizeof(struct sockaddr));
    		if(strcmp(write_buf,"quit\n") == 0)
    		{
    		   printf("you quit the communication!\n");
    		   break;
    		}
    		
    	}
    	close(cid);
    }
    

    发送端:

    udp_sendpp.c

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
    int main(int argc,char **argv)
    {
    	if(argc < 3)
    	{
    		perror("param must >= 3");
    		return -1;
    	}
    
    	int cid = socket(AF_INET,SOCK_DGRAM,0);
    	if(cid < 0)
    	{
    		perror("socket error");
    		return -1;
    	}
    
    	struct sockaddr_in addr = {0},sendaddr = {0};
    	addr.sin_family = AF_INET;
    	addr.sin_port = htons(atoi(argv[2]));
    	addr.sin_addr.s_addr = inet_addr(argv[1]);
    
    	int size = sizeof(struct sockaddr);
       	char read_buf[128] = {0};
    	char write_buf[128] = {0};
    	int len;
    	while(1)
    	{
    		bzero(read_buf,sizeof(read_buf));
    		bzero(write_buf,sizeof(write_buf));
    		printf("send:");
    		fgets(write_buf,sizeof(write_buf),stdin);
    	 len = sendto(cid,write_buf,strlen(write_buf),0,(struct sockaddr *)&addr,sizeof(struct sockaddr));
    	   printf("send successful!!send len = %d\n",len);
    
    		if(strcmp(write_buf,"quit\n") == 0)
    		{
    		   printf("you quit the communication!\n");
    		   break;
    		}
    		recvfrom(cid,read_buf,sizeof(read_buf) - 1,0,(struct sockaddr *)&sendaddr,&size);
    		if(strcmp(read_buf,"quit\n") == 0)
    		{
    		   printf("recv quit the communication!\n");
    		   break;
    		}
    		printf("recv:%s",read_buf);
    	}
    
    	
    
    	close(cid);
    }
    

    运行结果

    发送端:

    接收端:


    参考文章

    https://blog.csdn.net/apollon_krj/article/details/53398448
    https://zhuanlan.zhihu.com/p/24860273
    https://www.cnblogs.com/lightice/p/12726717.html



    本文结束,感谢您的阅读!

    展开全文
  • 用51单片机写的一个单线半双工的模拟串口通信程序,同时支持两线全双工,目前只做了1位起始位,1、1.5、2位起始位,没做校验。上一个版本的代码已经用在产品上了,这个版本的还没怎么用过,就拿了块板子验证了一下,...
  • Socket是客户端进行在网络与服务器进行数据交互的一种基本通信方式,通信有三种通信,即单工,半双工,和全双工。所谓单工,就是只能够进行单向通信,如bb机。而半双工就是一来一回的通信方式,如对讲机。全双工也是...

    Socket是客户端进行在网络与服务器进行数据交互的一种基本通信方式,通信有三种通信,即单工,半双工,和全双工。

    所谓单工,就是只能够进行单向通信,如bb机。

    而半双工就是一来一回的通信方式,如对讲机。

    全双工也是最常用的,就是现实长连接实时通信,如QQ。

    如果理解了上面的三个工作方式,那下面就以半双工为例,分析一下其工作的流程:

    首先:在服务器端建立一个监听端口,没有多么复杂,就相当于一个等待连接的东西。

    第二步:这时候,如果在客户端的话,要想去连接上面的服务器,就要去用对应的方法发送一个请求,意思是让服务器把这个端口打开,建立通信。

    第三步:服务器接受到客户端发送的请求,会建立连接,这个非常重要,真正意义上的通信也就开始了。

    第四步:上面的连接完成之后,客服端开始向服务器端发送对应的数据信息。

    第五步:服务器接收到数据。

    第六步:接收到数据之后,进行处理。返回结果。

    第七步:客服端接收到结果信息。

    总而言之,上面的步骤按照我的总结就是:

    连接——–发送数据——-接收数据——处理——返回数据—–客户端接收—————循环下去。

    下面是一个实现半双工的简单实例:

    服务器端:

    package test1;

    import java.io.BufferedReader;

    import java.io.FileReader;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.io.PrintWriter;

    import java.net.*;

    public class Server1 {

    /**

    *@param args

    */

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    Server1 ms1=new Server1();

    }

    public Server1(){

    try {

    // 实例化

    ServerSocket p1=new ServerSocket(9999);

    // 等待接收一个连接

    Socket s=p1.accept();//返回一个socket连接

    // 接收数据

    InputStreamReader isr=new InputStreamReader(s.getInputStream());

    BufferedReader br=new BufferedReader(isr);

    String info=br.readLine();

    System.out.println("服务器接收到:"+info);

    // 服务器返回数据向客户端发送

    PrintWriter pw=new PrintWriter(s.getOutputStream(),true);

    pw.println("这是服务器返回的数据");

    } catch (IOException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    }

    客户端:

    package test1;

    import java.io.*;

    import java.net.Socket;

    import java.net.UnknownHostException;

    public class Client1 {

    /**

    *@param args

    */

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    Client1 p2=new Client1();

    }

    public Client1(){

    try {

    // 连接某一个服务器端ip/端口号

    Socket s=new Socket("127.0.0.1",9999);

    // 发送数据,true是即时刷新

    PrintWriter pw=new PrintWriter(s.getOutputStream(),true);

    pw.println("这是客户端发送的数据");

    // 客户端接收到返回的数据

    InputStreamReader isr=new InputStreamReader(s.getInputStream());

    BufferedReader br=new BufferedReader(isr);

    String response=br.readLine();

    System.out.println("客户端收到"+response);

    } catch (UnknownHostException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    } catch (IOException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    }

    展开全文
  • 无线半双工通信系统的设计
  • Arduino HardwareSerial 库的修改,用于与 AVR 中的内置 UART 执行半双工通信。 用法: #include void setup() { // you have to manually set the TX and RX lines of your chosen serial port to INPUT_PULL...
  • 单工通信:所谓单工通信,是指消息只能单方向传输的工作方式。例如遥控、遥测,就是单工通信方式单工通信信道是单向信道,发送端和接收端的身份是固定的,发送端只能发送信息,不能接收信息;接收端只能接收信息,不...
  • 目录一 回顾二 消息队列的半双工通信三 消息队列的全双工通信 一 回顾 1 通过 fgets函数实现输入指令 2 再通过 msgnsd 函数读取 值 3 最后通过 msgrcv函数获取 值 输出 本质 只是在读 键盘的值 最后结果是 输出...
  • 半双工通信和全双工通信的区别

    千次阅读 2019-09-22 14:15:53
    半双工通信和全双工通信的区别 对于点对点之间的通信,按照消息传送的方向与时间关系,通信方式可分为单工通信、半双工通信及全双工通信三种。 单工通信 单工通信(Simplex Communication)是指消息只能单方向...
  • RS-232/RS-485转换器依托RS-232电平与TTL电平间转换,TTL电平与RS-485...采用单稳态触发器实现对数据收发状态以及通信时间的控制,可以支持多种传输速率下的通信。实际应用表明,该转换器电路工作稳定,传输数据可靠准确。
  • 单片机软件模拟UART半双工通信的设计.pdf
  • 电信设备-基于兰姆波的半双工通信装置及其通信方法.zip
  • stm32---RS485半双工通信

    千次阅读 2019-08-13 15:01:00
    典型的串口通信标准有 RS232 和 RS485,RS232 是全双工点对点的通信,而 RS485 是半双工通信(2 线制),可以一点对多点进行组网,而且 RS485 是用缆 线两端的电压差值来表示传递信号,这与 RS232 电气特性大不一样...
  • 串行通信作为计算机通信方式之一,主要起到主机与外设以及主机之间的数据传输作用,串行通信具有传输线少、成本低的特点,适用于近距离的人-机交换、实时监控等系统。通信工作当中,借助于现有的电话网也能实现远...
  • 半双工通信

    千次阅读 2013-05-19 14:03:06
    半双工通信系统的通信控制协议设计  计算机网络中首先要解决的就是计算机之间的通信问题。通信控制协议为连接不同操作系统和不同硬件体系结构的互联网络提供通信支持,是一种网络通用语言。在许多控制系统中需要一...
  • 电信设备-半双工通信系统免提模式.zip
  • ps: 如果有任何问题可以评论留言,我看到后会及时解答,评论或关注,您的鼓励是我分享的最大动力 ... 单工通信 概念:只能从一边到另一边的通信,一边是发送端,另一边是接收端,不可逆,通道是...半双工通信 概念:...
  • 简单的说: 单工通信就是只能从A到B,如[广播]半双工通信是A到B,B到A都行,但不能同时进行.如[对讲机]全双工通信是A到B,B到A都行,可以同同时进行.如[电话] ----- 详细如下: 全双工 全双工是通讯的的传输的一个...
  • 电信设备-半双工通信系统中的碰撞避免.zip
  • 单工通信、半双工通信和全双工通信的区别
  • 电信设备-基于半双工通信的安全告警信息中继方法.zip
  • 电信设备-使用高级数据链路协议的半双工通信的传输设备.zip
  • 本程序可以实现24L01的半双工通信,并可从从串口读出收到的数据
  • 计算机网络课程设计,主机程序和客户端程序半双工通信 网络聊天程序的设计与实现 了解 Socket 通信的原理,在此基础上编写一个聊天程序。
  • 学习通信的时候,这几个名词单工通信/半双工通信/全双工通信,串行传输/并行传输,同步传输/异步传输我一直都很迷,现在我总结一下,也是加深我的记忆了 1.按照信息在信道中的传输方向分为: a.单工通信: 消息只能...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 35,185
精华内容 14,074
关键字:

半双工通信