FPGA开发--串口接收模块


文章摘要
本文主要描述了串口接收模块的实现;

设计思路:
1.通过检测数据线上的下降沿来确定起始位;
2.检测每一位的中间位置的电平值,来确定该位的数据;

知识要点: 边沿检测,有限状态机;


硬件平台: EP4CE6F17C8
开发环境: Quartus II 13.1


/*
 * 功能描述:串口接收模块
 */
module uart_recv(
    input clk,              // 模块时钟
    input rst_n,            // 模块复位,低电平有效
    input rxd,              // 串口接收引脚    
    output reg[7:0] rdata,  // 接收到的数据
    output reg rxdone       // 接收完成标志,高电平有效  
);

parameter BAUD_CNT = 5208;      // 9600的波特率
parameter BAUD_CNT_HALF = 2604; // 中间值

parameter S_IDLE    = 4'd0;
parameter S_START   = 4'd1;
parameter S_BIT0    = 4'd2;
parameter S_BIT1    = 4'd3;
parameter S_BIT2    = 4'd4;
parameter S_BIT3    = 4'd5;
parameter S_BIT4    = 4'd6;
parameter S_BIT5    = 4'd7;
parameter S_BIT6    = 4'd8;
parameter S_BIT7    = 4'd9;
parameter S_STOP    = 4'd10;

reg [3:0] status;   // 状态机当前状态

reg  rxd_last;      // 接收引脚前一次电平
reg  rxd_curr;      // 接收引脚当前电平
wire rxd_start;     // 接收起始位标志(高电平有效)

reg [31:0] timer;   // 波特率计时器

//--------------------------------------------------
// 接收起始位处理
always @(posedge clk)
begin
    rxd_last <= rxd_curr;
    rxd_curr <= rxd;
end

// 前一次为高,当前值为低,视做下降沿
assign rxd_start = rxd_last & ~rxd_curr;

//--------------------------------------------------

always @(posedge clk or negedge rst_n)
begin
    if(~rst_n)
    begin
        status = S_IDLE;
    end    
    else
    begin    

        timer = timer + 1'b1;        // 波特率计算
        
        case(status)
        S_IDLE:
        begin
            rxdone <= 1'b0;         
            if(rxd_start)            // 空闲状态检测到下降沿   
            begin
                timer  <= 0;
                status <= S_START;   // 接收起始位    
            end
        end
        
        S_START:
        begin
            // 中间时刻仍为低电平,视做有效起始位
            if(timer == BAUD_CNT_HALF)          
            begin
                if(~rxd)
                begin
                    timer  <= 0;
                    status <= S_BIT0;               
                end
                else
                    status <= S_IDLE;   // 起始位无效,重新接收                   
            end     
        end
        
        S_BIT0:
        begin
            // 因为上一次是在中间位置重置timer,所以这里也是中间位置
            if(timer == BAUD_CNT)       
            begin
                timer  <= 0;        // 重置定时器
               rdata[0]<= rxd;      // 当前值即为有效数据
                status <= S_BIT1;   // 准备接收下一bit            
            end
            else
                status <= S_BIT0;   // 这一句纯粹是编程习惯
        end
        
        S_BIT1:
        begin
            if(timer == BAUD_CNT) 
            begin
                timer  <= 0;
                rdata[1] <= rxd;    
                status   <= S_BIT2;             
            end
            else
                status <= S_BIT1;
        end
        
        S_BIT2:
        begin
            if(timer == BAUD_CNT)   
            begin
                timer  <= 0;
               rdata[2] <= rxd;
                status   <= S_BIT3;              
            end
            else
                status <= S_BIT2;
        end

        S_BIT3:
        begin
            if(timer == BAUD_CNT)
            begin
                timer  <= 0;
               rdata[3] <= rxd;
                status   <= S_BIT4;              
            end
            else
                status <= S_BIT3;
        end
        
        S_BIT4:
        begin
            if(timer == BAUD_CNT) 
            begin
                timer  <= 0;
                rdata[4] <= rxd;
                status   <= S_BIT5;              
            end
            else
                status <= S_BIT4;
        end
        
        S_BIT5:
        begin
            if(timer == BAUD_CNT)
            begin
                timer  <= 0;
                rdata[5] <= rxd;
                status   <= S_BIT6;              
            end
            else
                status <= S_BIT5;
        end
        
        S_BIT6:
        begin
            if(timer == BAUD_CNT)    
            begin
                timer  <= 0;
                rdata[6] <= rxd;
                status   <= S_BIT7;              
            end
            else
                status <= S_BIT6;
        end
        
        S_BIT7:
        begin
            if(timer == BAUD_CNT)  
            begin
                timer  <= 0;
                rdata[7] <= rxd;
                status   <= S_STOP;     // 处理停止位             
            end
            else
                status <= S_BIT7;
        end
        
        S_STOP:
            // 因为上一次是在中间置0的,所以这里也是中点
            // 但是不等到接收完再跳转了,因为如果连续发送的话,可能会错过起始位
            if(timer == BAUD_CNT)   
            begin
                if(rxd)
                begin                   
                    rxdone <= 1'b1;     // 设置接收完成标置
                end               
                
                status <= S_IDLE;       // 无论如何,均进入空闲状态
                timer  <= 0;                    
            end 
        endcase
    end
end

endmodule

- 阅读全文 -

FPGA开发--串口发送模块


文章摘要:
串口应该是计算机最古老的通信接口之一,早期的应用无处不在,虽然目前有一点被USB、以太网以及无线接口等这些后浪拍死在沙滩上的苗头,但由于其协议的简单、简单、简单(重要的事说三遍)及可靠,所以在某些领域仍具有相当广泛的应用。

当然这都不重要,本文的重点是通过描述串口发送模块的方法,来简单说明一下基于FPGA的通信协议的实现,并对后续的其他接口通信达到抛砖引玉、触类旁通的效果;

知识重点: 状态机,边沿检测,串并转换;


硬件平台: EP4CE6F17C8
开发环境: Quartus II 13.1


时序图:

串口时序

对于一个合格的老司机来说,串口时序图应该早已烂熟于心了,不过,子又曾经曰过:“有图有真相”,所以为了对照代码,看图说话,还是放上来充数罢;


/* 
 * 模块说明:串口发送模块(9600-N-8-1)
 */
module uart_send(
    input clk,          // 时钟
    input rst_n,        // 复位,低电平有效
    output reg txd,     // 发送引脚         
    input [7:0]data,    // 发送数据
    input  en,          // 发送使能,高电平有效
    output busy         // 发送忙标志(高电平表示忙)
);

// 发送时序状态机
parameter S_IDLE    = 4'd0;
parameter S_START   = 4'd1;
parameter S_BIT0    = 4'd2;
parameter S_BIT1    = 4'd3;
parameter S_BIT2    = 4'd4;
parameter S_BIT3    = 4'd5;
parameter S_BIT4    = 4'd6;
parameter S_BIT5    = 4'd7;
parameter S_BIT6    = 4'd8;
parameter S_BIT7    = 4'd9;
parameter S_STOP    = 4'd10;

reg [3:0] status;       // 当前状态

// 波特率计算 9600bps即每bit时间为1/9600 S
// 50MHz时钟下,计数值为50_000_000/9600 = 5208
parameter BAUD_CNT = 32'd5208;

reg [31:0]timer;        // 波特率定时器
reg [7:0]rdata;         // 数据寄存器,防止发送过程中修改了数据端口

// 发送忙标志位
assign busy = (status == S_IDLE)?1'b0:1'b1;

always @(posedge clk or negedge rst_n)
begin
    if(~rst_n)
    begin
        status <= S_IDLE;   // 复位时设置为空闲状态
        txd <= 1'b1;        // 复位时发送引脚为高电平
        timer <= 0;
    end
    
    else
    begin       
        timer <= timer + 1'b1;  // 计时       
        
        // 发送状态机
        case(status)
        S_IDLE:
        begin       
            if(en)  // 只有空闲状态时,收到使能信号,才启动发送
            begin
                timer  <= 0;
                rdata  <= data;     // 保存待发数据
                status <= S_START;  // 启动发送
            end
            else
                status <= S_IDLE;       // 这里主要是设计风格
        end     
        S_START:
        begin
            if(timer == BAUD_CNT)   // 1bit起始位时间
            begin
                timer <= 0;
                status = S_BIT0;
            end
            else    
                txd <= 1'b0;    // 发送起始位    
        end
        S_BIT0:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT1;
            end
            else
                txd <= rdata[0];
        end
        S_BIT1:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT2;
            end
            else
                txd <= rdata[1];
        end
        S_BIT2:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT3;
            end
            else
                txd <= rdata[2];
        end
        S_BIT3:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT4;
            end
            else
                txd <= rdata[3];
        end
        S_BIT4:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT5;
            end
            else
                txd <= rdata[4];
        end
        S_BIT5:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT6;
            end
            else
                txd <= rdata[5];
        end
        S_BIT6:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_BIT7;
            end
            else
                txd <= rdata[6];
        end
        S_BIT7:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_STOP;
            end         
            else
                txd <= rdata[7];
        end
        S_STOP:
        begin
            if(timer == BAUD_CNT)
            begin
                timer <= 0;
                status = S_IDLE;
            end 
            else
                txd <= 1'b1;    // 发送停止位
        end
        default:
            status <= S_IDLE;
        endcase
    end
end

endmodule

- 阅读全文 -

FPGA开发--ModelSim仿真与Test Banch


文章摘要:
本文以PLL例程为例,简单描述了ModelSim的仿真测试及Test Banch的编写;

知识重点:ModelSim仿真流程;


硬件平台: EP4CE6F17C8
开发环境: Quartus 13.1
仿真软件: Modelsim SE 10.1c


1. 打开pll_test工程,通过建立Test Banch文件

Porcessing --> Start --> Start Test Banch Template Writer...

此操作将在simulation\modelsim目录下创建一个pll_test.vt的文件,模块名称为工程名称加vlg_tst后缀(verilog test),例如:pll_test_vlg_tst;


2.添加生成的Test Banch文件添加至工程

Assignments --> Setting...

EDA Tool Settings --> Simulation

Compile test bench: --> Test Benches...

Test bench name: 指定模块名称,必须与第一步中生成的模块名称一致,pll_test_vlg_tst

File name: 指定第一步中生成的Test Banch文件;


3.修改Test Banch

Test Banch模板生成器已经完成端口声明及模块例化,只需要添加相关内容即可;

// 定义测试时钟,修改为1ns
`timescale 1 ns/1 ps

// 模块名称,以vlg_tst为后缀(verilog test)
module pll_test_vlg_tst();    

// 被测模块的input端口,连接测试模块中reg
reg clk;
reg rst_n;

// 被测模块的ouput端口,连接测试模块中的wires                                             
wire clk1;
wire clk2;

// 顶层模块例化(自动生成,如果修改了模块接口,则需要修改此处)
// assign statements (if any)                          
pll_test i1 (
    .clk(clk),
    .clk1(clk1),
    .clk2(clk2),
    .rst_n(rst_n)
);


initial                                                
begin                                                  
// code that executes only once(只运行一次的代码)                      
// insert code here(在此添加代码) --> begin    

     clk = 0;    
     rst_n = 0;    // 复位100ns     
     #100;
     rst_n = 1;         
     #2000
                                                       
// --> end                                             
$display("Running testbench");                       
end     

// 正常运行时,一直执行的代码                                               
always                                   
begin                                                  
// code executes for every event on sensitivity list   
// insert code here(在此添加代码) --> begin   
                   
    #10 clk =~clk;     // 每10ns反转一次(产生50MHz频率)                                              
                                           
// --> end                                             
end   

// 可以根据需要添加多个always语句
                                             
endmodule

4. 运行仿真

执行RTL Simulation,自动打开ModelSim并进行编译,编译完成后,自动运行,可点击Stop来停止执行,以查看波型;

请输入图片描述

- 阅读全文 -

FPGA开发--锁相环(PLL)


文章摘要:
本文描述了基于IP Core的锁相环(PLL)倍频器的实现方法;

知识重点:IP Core的使用;


硬件平台: EP4CE6F17C8
开发环境: Quartus 13.1


新建工程
工程名称为:pll_test


添加PLL IP Core

Tools-->MegaWizard Plug-In Manager

选择ALTPLL模块
输出文件格式:Verilog
输出文件名为:pll


配置PLL模块参数

PLL基本配置

PLL输出配置
c0~c4最多可以配置5路输出,如果使能某一路,则选中对应的Use this clock;
可以手动指定输出频率,则系统通过上一步配置的时钟,自动计算参数;
也可以指定参数,系统自动算出输出的频率;


添加顶层文件

/*
 * 功能描述: PLL测试模块
 */
module pll_test(
    input rst_n,      // 复位引脚
    input clk,        // 输入时钟
    output clkout0,   // 倍频输出
    output clkout1    // 倍频输出
);

// 连接线,用于检测PLL锁定状态
wire locked;

// PLL模块例化
pll p1(
    .areset(~rst_n),    // 复位引脚,高电平有效
    .inclk0(clk),       // PLL输入时钟
    .c0(clkout0),       // PLL输出 
    .c1(clkout1),       // PLL输出
    .locked(locked)     // PLL锁定状态
);
    
endmodule

分析综合


配置引脚


全编译


下载测试
下载后自动动行,可通过示波器观察输出引脚波形;

- 阅读全文 -

FPGA开发--数码管扫描


文章摘要:
本文主要描述了数码管扫描电路的实现方案;

知识要点:有限状态机,模块例化;


硬件平台: EP4CE6F17C8
开发环境: Quartus II 13.1


数码管扫描模块:

/*
 *  功能描述:6位数码管扫描模块
 */
module smg_scan(
    input clk,                  // 时钟信号
    input rst_n,                // 复位信号
    output reg[7:0]data_pin,    // 数据引脚,共阳数码管
    output reg[5:0]sel_pin,     // 选通引脚,低电平导通   
    input  [7:0]seg0,           // 数据0
    input  [7:0]seg1,           // 数据1
    input  [7:0]seg2,           // 数据2
    input  [7:0]seg3,           // 数据3
    input  [7:0]seg4,           // 数据4
    input  [7:0]seg5            // 数据5
);
    
//--------------------------------------------------    
// 有限状态机
parameter S_SEG0 = 4'd1;
parameter S_SEG1 = 4'd2;
parameter S_SEG2 = 4'd3;
parameter S_SEG3 = 4'd4;
parameter S_SEG4 = 4'd5;
parameter S_SEG5 = 4'd6;
reg[3:0] state; // 记录当前状态

/*
 * 全部刷新时间不能超过10ms,否则人眼可以感觉到闪烁
 * 如果刷新时间缩短至5ms以内,则低速摄像机也扑捉不到闪烁
 */
parameter FLUSH_COUNT = 50_000; // 单个刷新时间1ms,全部刷新时间为6ms
reg[31:0] count;    // 刷新记数器

always @(posedge clk, negedge rst_n) begin
    if(!rst_n) begin
        sel_pin <= 6'b111111;   // 复位时关闭显示
        state <= S_SEG0;        // 回到初始状态
    end
    
    else if(count >= FLUSH_COUNT) begin  // 定时时间到       
        count <= 0;     // 重新计数      
    
        case(state)     
        S_SEG0: begin
            state    <= S_SEG1;
            sel_pin  <= 6'b111110;  // 片选
            data_pin <= seg0;       // 数据
        end
        
        S_SEG1: begin
            state    <= S_SEG2;
            sel_pin  <= 6'b111101;
            data_pin <= seg1;
        end
        S_SEG2: begin
            state    <= S_SEG3;
            sel_pin  <= 6'b111011;      
            data_pin <= seg2;   
        end
        
        S_SEG3: begin
            state    <= S_SEG4;
            sel_pin  <= 6'b11_0111;     
            data_pin <= seg3;
        end
        
        S_SEG4: begin
            state    <= S_SEG5;
            sel_pin  <= 6'b10_1111;     
            data_pin <= seg4;
        end
        
        S_SEG5: begin
            state    <= S_SEG0; // 最后一个显示完,下一次该显示示第一个了
            sel_pin  <= 6'b01_1111;
            data_pin <= seg5;
        end 
        
        default: begin
            state <= S_SEG0;    // 未知状态,返回第一个状态
        end
        endcase
    end
    
    else begin
        count <= count + 1'b1;     // 最后一个时钟沿其实是没计数的;
    end
    
end
endmodule

译码器计数器模块:

/*
 *  功能描述:异步十进制译码计数器
 */
module decoder(    
    clk,    // 计数时钟
    rst_n,  // 异步复位    
    seg,    // 译码输出
    carry   // 进位(做为下级时钟)
);

input  clk;
input  rst_n;
output reg [7:0] seg; 
output reg carry; 
//------------------------------------------
reg [3:0] count;   // 计数器

// 十进制计数器
always @(posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        count <= 0;
        carry <= 1'b0;  
    end
    else if(count < 9) begin
        count <= count + 1'b1;
        carry <= 1'b0;  
    end
    else begin
        count <= 0;
        carry <= 1'b1;   // 产生进位      
    end        
end

// 组合逻辑
always @(*) begin
    if(~rst_n) begin
        seg <= 8'b1111_1111;    // 全灭
    end
    else begin
        case(count)
        4'd0:
            seg <= 8'b1100_0000;    
        4'd1:
            seg <= 8'b1111_1001;
        4'd2:
            seg <= 8'b1010_0100;
        4'd3:
            seg <= 8'b1011_0000;
        4'd4:
            seg <= 8'b1001_1001;
        4'd5:
            seg <= 8'b1001_0010;
        4'd6:
            seg <= 8'b1000_0010;
        4'd7:
            seg <= 8'b1111_1000;
        4'd8:
            seg <= 8'b1000_0000;
        4'd9:
            seg <= 8'b1001_0000;
        4'd10:
            seg <= 8'b1000_1000;
        default:
            seg <= 8'hff;
        endcase
    end
end
endmodule

建立顶层模块(测试用)

/*
 *  功能描述:数码管测试程序
 */
module smg_test(
    input clk,              // 系统时钟
    input rst_n,            // 系统复位
    output [7:0]data_pin,   // 数据管数据脚
    output [5:0]sel_pin     // 数码管片选脚
);

reg[31:0] timer;    // 定时器
reg tick;

// 模块连接线
wire[7:0]seg0;
wire[7:0]seg1;
wire[7:0]seg2;
wire[7:0]seg3;
wire[7:0]seg4;
wire[7:0]seg5;
wire[7:0]seg6;

wire c0;    // 计数器进位
wire c1;
wire c2;
wire c3;
wire c4;
//-----------------------------------------------    
always @(posedge clk or negedge rst_n) begin
    if(~rst_n)
    begin
        timer <= 0;
        tick  <= 0;
    end    
    else if(timer == 32'd25_000_000)
    begin
        tick  <= 1'b1;
        timer <= 0;         
    end    
    else begin
        tick  <= 0;
        timer <= timer + 1'b1;
    end
end 
// 例化数码管显示模块
smg_scan s0(
    .clk(clk),              // 时钟信号
    .rst_n(rst_n),          // 复位信号
    .data_pin(data_pin),    // 数据引脚,共阳数码管
    .sel_pin(sel_pin),      // 选通信号,低电平导通   
    .seg0(seg0),            // 数据
    .seg1(seg1),            // 数据
    .seg2(seg2),            // 数据
    .seg3(seg3),            // 数据
    .seg4(seg4),            // 数据
    .seg5(seg5)             // 数据
);

// 例化6个显示译码器,用于将数字转化为显示段码;
decoder u0(
    .clk(tick),    // 计数时钟
    .rst_n(rst_n), // 异步复位
    .seg(seg0),    // 译码输出
    .carry(c0)     // 进位
);
decoder u1(
    .clk(c0),      // 计数时钟
    .rst_n(rst_n), // 异步复位
    .seg(seg1),    // 译码输出
    .carry(c1)     // 进位
);
decoder u2(
    .clk(c1),      // 计数时钟
    .rst_n(rst_n), // 异步复位
    .seg(seg2),    // 译码输出
    .carry(c2)     // 进位
);
decoder u3(
    .clk(c2),      // 计数时钟
    .rst_n(rst_n), // 异步复位
    .seg(seg3),    // 译码输出
    .carry(c3)     // 进位
);
decoder u4(
    .clk(c3),      // 计数时钟
    .rst_n(rst_n), // 异步复位
    .seg(seg4),    // 译码输出
    .carry(c4)     // 进位
);
decoder u5(
    .clk(c4),      // 计数时钟
    .rst_n(rst_n), // 异步复位
    .seg(seg5),    // 译码输出
    .carry()       // 进位
);
endmodule

- 阅读全文 -


Copyright©2025 春天花会开, All Rights Reserved. Email: webmaster@oroct.com