Detailed serial communication protocol and FPGA Implementation

Foreword

Long time did not update the blog, article writing and go, I spent nearly a week, finally finished. This article describes, the serial data frame format protocol, serial communication mode of operation, the standard level, Verilog realize coding and serial transmission and reception of a data byte a data byte.

MCU for serial transmission and reception, a line of code that may be able to achieve serial transmission and reception:

Receive and transmit serial STM32

//STM32发送1个字节
USART_SendData(USART1, 'A'); 
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);     

//STM32接收1个字节:
uint8_t Res;
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
Res = USART_ReceiveData(USART1);

Microcontroller 51 to send and receive

//51单片机发送1个字节
SBUF = 'A;
while(!TI);
TI=0;


//51单片机接收1个字节:
char Res;
if(RI)
{
   Res = SBUF;
   RI = 0;
}

A little more convenient, by rewriting the C library functions and fput fgetc function can also be achieved printf directly redirected to the serial port to output some debugging information and then easily enough.

STM32 realize input and output redirection to the serial port transmit and receive

//可重定向printf函数
int fputc(int ch, FILE *f)
{
        USART_SendData(DEBUG_USARTx, (uint8_t) ch);
        while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);     
        return (ch);
}
//可重定向scanf函数
int fgetc(FILE *f)
{
        while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
        return (int)USART_ReceiveData(DEBUG_USARTx);
}

The serial port on the MCU is pre-designed semiconductor manufacturers, the MCU is almost standard, highly integrated, very convenient to use, but the serial port pin is essentially fixed and can not be changed. For a hardware plasticine --FPGA, the need to implement the use of HDL from the underlying serial data frame can be directly implemented in any of a pin serial port function. In order to achieve the standard serial communication protocol with Verilog HDL, we need to first take a closer look at serial communication protocol.

Serial data frame format

Baud Rate

Baud rate, that bit rate (Baud rate), that is, communicating parties, "the language of communication," communicating parties to be set to the same baud rate can communicate properly. Represents the number of bits transmitted per second, i.e., the transmission time is 1: 1 / second baud rate, e.g., baud rate 9600bps, the transmission 9600bit per second, then each bit time: 1/9600 s = 104.1666us, commonly used baud rates are: 4800/9600/115200/12800 and so on, but also can customize the size of the baud rate, such as 1M or 3M, but some PC or USB-TTL module does not support high speed as needed baud rate, commonly used USB-TTL chip: CH340, CP2102, PL2103, FT232, of which FT232HL chip maximum support 12M baud rate , of course, the price is higher than some of the other chips.

Start and stop bits

Data frame starts with a start bit, stop bit end. A logic 0 represented by the start signal and the stop bit is a logical 1, generally 0.5, 1, 1.5 or 2 stop bits, used is generally 1 stop bit, as long as the communication parties can be coincided.

Data bits

After the start bit, followed by the data bits, low-order (LSB) first, the high (MSB) in the post, generally 5, 6, 7 and 8 data bits, common 8-bit data bits , exactly as a byte is 8 bits.

Check Digit

Usually parity bit is used to determine the received data bits with no errors, check method are: odd (ODD), even parity (even), 0 check (space), 1 parity (Mark) and non-school test (noparity). Odd parity check bit and valid data is required in the number of "1" is an odd number, such as an 8-bit data is valid: 01101001, then a total of four "1", in order to achieve an odd number of "1" effect , the parity bit is "1", so that the number of "1" into five (an odd number). Opposite parity, parity and valid data is required to "1" is an even number, in this case to achieve the effect of even parity, the parity bit is "0." And 0 parity, i.e., the parity bit is always "0", a parity check bit is always "1." Instead parity logic 01 opposite validation logic. Usually parity or no parity.

Verilog achieve parity

In Verilog parity calculation is very simple, in accordance with the principles of parity, data bits even parity XOR Members, odd parity is inverted, by using the unary operator of the reduced functionality, can be very a simple calculation of parity bits:

input [7:0] data_in,       //需要发送的8位数据

wire even_bit;  //偶校验位 = 各位异或
wire odd_bit;   //奇校验位 = ~偶校验位

assign even_bit = ^data_in; //一元约简运算符,等效于data_in[0] ^ data_in[1] ^ .....
assign odd_bit = ~even_bit; 

wire POLARITY_BIT = even_bit;  //偶校验

The baud rate on the allowable error

After my actual test, the baud rate is a certain error tolerance, e.g., the STM32 configured to 115200 baud rate, every 10ms to send a 30-byte character string, with the serial chip CH340, PC baud rate setting to 113000-121000 can also receive, no distortion, almost positive and negative baud rate of 2000, this fault-tolerance range is too big, of course, if the transmission frequency is too fast, too much data, the error rate will be considerably increased, they still He suggested that both sides use the same communication baud rate to reduce errors.

Actual waveform of the serial data

PC connection using the serial port USB-TTL module, send a byte of data: 1 stop bit + 8 data bits + 1 bit odd parity bit + 1 stop bit, using a single triggering the oscilloscope, can USB- TX TTL module pin serial protocol measured actual waveform data, it sends you know what characters?

The actual waveform of a character

The actual waveform of two characters

Simplex, half-duplex, full-duplex, asynchronous and synchronous difference

Prior to the level of the standard serial introduction, let's look at the work of serial communication, ie simplex, half-duplex, full-duplex, asynchronous and synchronous difference.

Simplex

Simplex, namely data transmission in only one direction of transmission, can you send me or I'll give you send, the direction is fixed and can not achieve the two-way communication, such as: outdoor antenna TV, FM radio and so on.

Half-duplex

Ratio can be switched half duplex simplex little advanced transmission direction, allowing data transmission in both directions, but at some point, data transmission only in one direction, may be substantially two-way communication, such as: walkie-talkie, IIC communication.

Full-duplex

More advanced than the half-duplex full duplex, allowing simultaneous transmission of data in both directions. Send and receive completely independent, you can receive the signals transmitted at the same time, or may be transmitted simultaneously received. It requires sending and receiving devices must have separate transmit and receive capabilities, such as: telephone communication, SPI communication, serial communication.

The difference between synchronous and asynchronous

Serial communications can be divided into two types, called synchronous communication, another is called asynchronous communication.

Simply put, the clock signal is synchronous communication needs, and no clock signal is asynchronous communication.

  • Synchronization: after sending the sender data, and other communication only issued after receiving a data packet to send back the response.
  • Asynchronous: after sending a data sender, the receiver sends back the response range, then the next transmission of communication packets.

SPI and IIC synchronous communication, UART communication is asynchronous, and asynchronous USART & synchronous communication.

  • USART: Universal Synchronous Asynchronous Receiver Transmitter and
  • UART: Universal Asynchronous Receiver Transmitter

That USART supports synchronous and asynchronous receiver, but only supports asynchronous UART transceiver.

The STM32 serial operating in synchronous mode, i.e., the smart card mode, you need to connect synchronous clock pin.

Common serial communication protocol / standard level

TTL level

I.e. common MCU chip serial level output, as each MCU serial signal output is TTL level. Low level to 0-GND, the high level to 1-VCC, standard digital logic circuits. It characterized by speed, low latency, but high power consumption. A substantially short-range communication between the two chips inner panel.

RS232

RS232是工业上常用的串口标准,无论是PLC的232接口,还是工控机上的串口,输出的串口电平都是232电平标准,232标准采用负逻辑电平,即-15~-3v为逻辑1,+3~+15为逻辑0,这里的电平是指RX和TX相对于GND的电压,可见无论在电压范围还是电压极性上都和TTL不同,显然这两种电平不能直接连接,需要使用MAX232类似的电平转换芯片,对两种电平进行互相转换,全双工,传输距离一般控制在20m以内,原因是RS-232属单端信号传送,存在共地噪声和不能抑制共模干扰等问题。

RS485

在要求通信距离为几十米到上千米时,广泛采用RS-485 串行总线标准。RS-485采用平衡发送和差分接收,因此具有抑制共模干扰的能力。加上总线收发器具有高灵敏度,能检测低至200mV的电压,故传输信号能在千米以外得到恢复。 RS-485采用半双工工作方式,任何时候只能有一点处于发送状态,因此,发送电路须由使能信号加以控制。RS-485用于多点互连时非常方便,可以省掉许多信号线。应用RS-485 可以联网构成分布式系统,其允许最多并联32台驱动器和32台接收器。

RS422

RS-422和RS-485电路原理基本相同,都是以差分方式发送和接受,不需要数字地线。RS-422通过两对双绞线可以全双工工作收发互不影响,而RS485只能半双工工作,发收不能同时进行,但它只需要一对双绞线。RS422和RS485在19kpbs下能传输1200米。RS-422的电气性能与RS-485完全一样。主要的区别在于:RS-422有4根信号线:两根发送(Y、Z)、两根接收(A、B)。由于RS-422的收与发是分开的所以可以同时收和发(全双工)。

串行通信的编码方式

RZ编码

RZ编码也成为归零码,归零码的特性就是在一个周期内,用二进制传输数据位,在数据位脉冲结束后,需要维持一段时间的低电平。如图:

上图表示的是单极性归零码,即低电平表示0,正电平表示1。对于双极性归零码来说,则是高电平表示1,负电平表示0。如下图所示:

NRZ编码

NRZ编码也成为不归零编码,也是我们最常见的一种编码,即正电平表示1,低电平表示0。它与RZ码的区别就是它不用归零,也就是说,一个周期可以全部用来传输数据,这样传输的带宽就可以完全利用。

NRZI编码

NRZI编码的全称为反向不归零编码,这种编码方式集成了前两种编码的优点,即既能传输时钟信号,又能尽量不损失系统带宽。对于USB2.0通信的编码方式就是NRZI编码。其实NRZI编码方式非常的简单,即信号电平翻转表示0,信号电平不变表示1。例如想要表示00100010(B),则信号波形如下图所示:

例如有一段数据为:1111 1111 (B)要发送,则整个传输线上的电平状态是这样的:

Manchester编码

曼彻斯特编码,又称数字双向码、分相码或相位编码(PE),是一种常用的的二元码线路编码方式。常用在以太网通信,列车总线控制,工业总线等领域。在曼彻斯特编码中,每一位的中间有一跳变,位中间的跳变既作时钟信号,又作数据信号;从高到低跳变表示“0”,从低到高跳变表示“1”。其中非常值得注意的是,在每一位的"中间"必有一跳变,根据此规则,可以得出曼彻斯特编码波形图的画法。例如:传输二进制信息0,若将0看作一位,我们以0为中心,在两边用虚线界定这一位的范围,然后在这一位的中间画出一个电平由高到低的跳变。后面的每一位以此类推即可画出整个波形图。举个图例吧,若要表示数据1001 1010(B),则信号波形图如下图所示:

曼彻斯特编码方式也如前面所说,虽然传输了时钟信号,但也损失了一部分的带宽,主要表现在相邻相同数据上。但对于高速数据来说,这种编码方式无疑是这几种编码方式中最优的,相比NRZI编码,曼彻斯特编码不存在长时间信号状态不变导致的时钟信号丢失的情况,所以在这种编码方式在以太网通信中是十分常用的。

串行和并行哪个速度快?

串口,即串行通信接口,与之对应的是并行接口。在实际时钟频率比较低的情况下,并口因为可以同时传输若干比特,速率确实比串口快。但是,随着技术的发展,时钟频率越来越高,当时钟频率提高到一定的程度时,并行接口因为有多条并行且紧密的导线,导线之间的相互干扰越来越严重。而串口因为导线少,线间干扰容易控制,况且加上差分信号的加持,抗干扰性能大大提升,因此可以通过不断提高时钟频率来提高传输速率,这就是为什么现在高速传输都采用串行方式的原因。例如常见的USB、SATA、PCIe、以太网等。

如果有人问关于串行传输与并行传输谁更好的问题,你也许会脱口而出:串行通信好!但是,串行传输之所以走红,是由于将单端信号传输转变为差分信号传输,并提升了控制器工作频率的原因,而“在相同频率下并行通信速度更高”这个基本道理是永远不会错的,通过增加位宽来提高数据传输率的并行策略仍将发挥重要作用。当然,前提是有更好的措施来解决并行传输过程中的种种问题。

标准串口协议的Verilog实现

及FPGA实现,基于Verilog实现标准串口协议发送8位数据:起始位 + 8位数据位 + 校验位 + 停止位 = 11位,每1位的时间是16个时钟周期,所以输入时钟应该为:波特率*16,带Busy忙信号输出。实现方法比较简单,数据帧的拼接、计数器计时钟周期,每16个时钟周期输出一位数据即可。

串口发送1个字节实现

/*
串口协议发送:起始位 + 8位数据位 + 校验位 + 停止位 = 11位 * 16 = 176个时钟周期
clk频率 = 波特率 * 16
*/

module uart_tx_8bit(

//input
input clk,                //UART时钟=16*波特率
input rst_n,
input [7:0] data_in,       //需要发送的数据
input trig,                //上升沿发送数据

//output
output busy,                 //高电平忙:数据正在发送中
output reg tx                //发送数据信号

);

reg[7:0] cnt;             //计数器
reg trig_buf;
reg trig_posedge_flag;
// reg trig_negedge_flag;
reg send;

reg [10:0] data_in_buf;     //trig上升沿读取输入的字节,拼接数据帧

wire odd_bit;   //奇校验位 = ~偶校验位
wire even_bit;  //偶校验位 = 各位异或
wire POLARITY_BIT = even_bit;  //偶校验
// wire POLARITY_BIT = odd_bit;   //奇校验

assign even_bit = ^data_in; //一元约简,= data_in[0] ^ data_in[1] ^ .....
assign odd_bit = ~even_bit;
assign busy = send;     //输出的忙信号

//起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期
parameter CNT_MAX = 176;

always @(posedge clk)
begin
    if(!rst_n)
    begin
        trig_buf <= 0;
        trig_posedge_flag <= 0;
        // trig_negedge_flag <= 0;
    end
    else 
    begin
        trig_buf <= trig;
        trig_posedge_flag <= (~trig_buf) & trig; //在trig信号上升沿时产生1个时钟周期的高电平
        // trig_negedge_flag <= trig_buf & (~trig); //在trig信号下降沿时产生1个时钟周期的高电平    
    end
end

always @(posedge clk)
begin
    if(!rst_n)
        send <= 0;
    else if (trig_posedge_flag &  (~busy))  //当发送命令有效且线路为空闲时,启动新的数据发送进程
        send <= 1;
    else if(cnt == CNT_MAX)      //一帧资料发送结束
        send <= 0;
end

always @ (posedge clk)
begin
    if(!rst_n)
        data_in_buf <= 11'b0;
    else if(trig_posedge_flag & (~busy))    //只读取一次数据,一帧数据发送过程中,改变输入无效
        data_in_buf <= {1'b1, POLARITY_BIT, data_in[7:0], 1'b0};   //数据帧拼接
end

always @ (posedge clk)
begin
    if(!rst_n)
        cnt <= 0;
    else if(!send || cnt >= CNT_MAX)
        cnt <= 0;
    else if(send)
        cnt <= cnt + 1;
end

always @(posedge clk)
begin
    if(!rst_n)
        tx <= 1;
    else if(send)  
    begin
        case(cnt)                 //1位占用16个时钟周期
            0: tx <= data_in_buf[0];           //低位在前,高位在后
            16: tx <= data_in_buf[1];    //bit0,占用第16~31个时钟
            32: tx <= data_in_buf[2];    //bit1,占用第47~32个时钟
            48: tx <= data_in_buf[3];    //bit2,占用第63~48个时钟
            64: tx <= data_in_buf[4];    //bit3,占用第79~64个时钟
            80: tx <= data_in_buf[5];    //bit4,占用第95~80个时钟
            96: tx <= data_in_buf[6];    //bit5,占用第111~96个时钟
            112: tx <= data_in_buf[7];   //bit6,占用第127~112个时钟
            128: tx <= data_in_buf[8];   //bit7,占用第143~128个时钟
            144: tx <= data_in_buf[9];   //发送奇偶校验位,占用第159~144个时钟
            160: tx <= data_in_buf[10];  //发送停止位,占用第160~167个时钟            
            CNT_MAX: tx <= 1;            //无空闲位
            default:;
        endcase
    end
    else if(!send) 
        tx <= 1;
end

endmodule

仿真波形

串口接收1个字节实现

串口接收部分的实现,涉及到串口数据的采样,对于MCU来说,不同单片机集成外设的处理方式有所不同,具体采样原理可以参考内核的Reference Manual。以传统51内核为例,按照所设置的波特率,每个位时间被分为16个时间片。UART接收器会在第7、8、9三个时间片进行采样,按照三取二的逻辑获得该位时间内的采样结果。其它一些类型的单片机则可能会更加严苛,例如有些工业单片机会五取三甚至七取五(设置成抗干扰模式时)。

本程序中采用的中间值采样,即取16个时钟周期中的中间位作为当前的采样值。

//Verilog实现串口协议接收,带错误指示,校验错误和停止位错误

/*
16个时钟周期接收1位,中间采样
*/
module my_uart_rx(

input clk,             //采样时钟
input rst_n,
input rx,              //UART数据输入
output reg [7:0] dataout,        //接收数据输出
output reg rx_ok,          //接收数据有效,高说明接收到一个字节
output reg err_check,      //数据出错指示
output reg err_frame     //帧出错指示

);

reg [7:0] cnt;
reg [10:0] dataout_buf;

reg rx_buf;
reg rx_negedge_flag;
reg receive;

wire busy;
wire odd_bit;   //奇校验位 = ~偶校验位
wire even_bit;  //偶校验位 = 各位异或
wire POLARITY_BIT;   //本地计算的奇偶校验
// wire polarity_ok;
// assign polarity_ok = (POLARITY_BIT == dataout_buf[9]) ? 1 : 0; //校验正确=1,否则=0

assign busy = rx_ok;
assign even_bit = ^dataout;     //一元约简,= data_in[0] ^ data_in[1] ^ .....
assign odd_bit = ~even_bit;
assign POLARITY_BIT = even_bit;  //偶校验
// assign POLARITY_BIT = odd_bit;  //奇校验

parameter CNT_MAX = 176;

//rx信号下降沿标志位
always @(posedge clk)   
begin
    if(!rst_n)
    begin
        rx_buf <= 0;
        rx_negedge_flag <= 0;
    end
    else
    begin
        rx_buf <= rx;
        rx_negedge_flag <= rx_buf & (~rx);
    end
end
//在接收期间,保持高电平
always @(posedge clk)
begin
    if(!rst_n)
        receive <= 0;
    else if (rx_negedge_flag && (~busy))  //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程
        receive <= 1;      //开始接收数据
    else if(cnt == CNT_MAX)  //接收数据完成
        receive <= 0;
end
//起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期
always @ (posedge clk)
begin
    if(!rst_n)
        cnt <= 0;
    else if(!receive || cnt >= CNT_MAX)
        cnt <= 0;
    else if(receive)
        cnt <= cnt + 1;
end
//校验错误:奇偶校验不一致
always @ (posedge clk)
begin
    if(!rst_n)
        err_check <= 0;
    else if(cnt == 152)
    begin
        // if(POLARITY_BIT == rx)
        if(POLARITY_BIT != dataout_buf[9])      //奇偶校验正确
            err_check <= 1;         //锁存
        // else
            // err_check <= 1;       
    end
end
//帧错误:停止位不为1
always @ (posedge clk)
begin
    if(!rst_n)
        err_frame <= 0;
    else if(cnt == CNT_MAX)
    begin
        if(dataout_buf[10] != 1)        //停止位
            err_frame <= 1;
        // else
            // err_frame <= 1;      //如果没有接收到停止位,表示帧出错
    end
end

always @ (posedge clk)
begin
    if(!rst_n)
        dataout <= 11'h00;
    else if(receive)
    begin
        // if(rx_ok)
        if(cnt >= 137)
            dataout <= dataout_buf[8:1];        //数据位:8-1位
        // else if(!rx_ok)
            // dataout <= 0;
    end
end

always @ (posedge clk)
begin
    if(!rst_n)
        rx_ok <= 0;
    else if(receive)
    begin
        if(cnt >= 137)   //137-169
            rx_ok <= 1;
        else 
            rx_ok <= 0;
    end
    else 
        rx_ok <= 0;
end


//起始位+8位数据+奇偶校验位+停止位 = 11 * 16 = 176位

always @(posedge clk)
begin
    if(!rst_n)
        dataout_buf <= 8'h00;
    else if(receive)
    begin
        case (cnt)      //中间采样
            8'd8: dataout_buf[0] <= rx;         //起始位=0
            8'd24: dataout_buf[1] <= rx;        //LSB低位在前
            8'd40: dataout_buf[2] <= rx;
            8'd56: dataout_buf[3] <= rx;
            8'd72: dataout_buf[4] <= rx;
            8'd88: dataout_buf[5] <= rx;
            8'd104: dataout_buf[6] <= rx;
            8'd120: dataout_buf[7] <= rx;
            8'd136: dataout_buf[8] <= rx;       //MSB高位在后
            8'd152: dataout_buf[9] <= rx;       //奇偶校验位
            8'd168: dataout_buf[10] <= rx;      //停止位=1
            default:;
        endcase
    end
end

endmodule

代码工程下载

工程包含:

  • my_uart_rx:串口接收1个字节示例程序
  • uart_tx_8bit:串口发送1个字节示例程序
  • uart_tx_demo:串口每隔500ms循环发送0-9字符

参考资料:

推荐阅读:


我的博客:www.wangchaochao.top

或微信扫码关注我的公众号:mcu149

Guess you like

Origin www.cnblogs.com/whik/p/11402340.html
Recommended