add: more exp markdown
							
								
								
									
										244
									
								
								public/doc/03/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,244 @@
 | 
			
		||||
# 基础-3-数码管实验
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
在许多项目设计中,我们通常需要一些显示设备来显示我们需要的信息,可以选择的显示设备有很多,而数码管是使用最多,最简单的显示设备之一。
 | 
			
		||||
 | 
			
		||||
## 3.1 章节导读
 | 
			
		||||
本章将通过数码管驱动实验讲解FPGA数字系统中重要的"选通控制"概念。读者将学习到:
 | 
			
		||||
1. 数码管工作原理与动态扫描技术
 | 
			
		||||
2. 多路复用(Multiplexing)设计方法
 | 
			
		||||
3. 参数化模块设计技巧
 | 
			
		||||
4. 外设驱动时序规划
 | 
			
		||||
5. ASCII到段码的转换原理
 | 
			
		||||
 | 
			
		||||
实验将使用Verilog HDL实现一个支持8位数码管显示、包含字符动态滚动和选通控制的完整系统。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 3.2 理论学习
 | 
			
		||||
### 3.2.1 数码管结构
 | 
			
		||||
- 7段数码管组成:A-G段+DP小数点
 | 
			
		||||
- 共阳/共阴类型区分(本实验采用共阳型,低电平有效)
 | 
			
		||||
 | 
			
		||||
### 3.2.2 动态扫描原理
 | 
			
		||||
```
 | 
			
		||||
显示周期 = 刷新周期 × 数码管数量
 | 
			
		||||
人眼视觉暂留效应(>60Hz)
 | 
			
		||||
扫描频率计算公式:f_scan = f_clk / CLK_CYCLE
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 3.2.3 关键技术
 | 
			
		||||
- 时分复用:分时选通数码管
 | 
			
		||||
- 段码生成:ASCII字符到七段码转换
 | 
			
		||||
- 消隐处理:消除切换时的视觉残留
 | 
			
		||||
 | 
			
		||||
### 3.2.4 设计指标
 | 
			
		||||
| 参数 | 值 | 说明 |
 | 
			
		||||
|-------|-----|-------------------|
 | 
			
		||||
| 位数 | 8   | 数码管数量        |
 | 
			
		||||
| 频率 | 200Hz | 单管刷新频率     |
 | 
			
		||||
| 分辨率 | 8bit | 段码控制(含小数点)|
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 3.2 实战演练
 | 
			
		||||
### 3.3.1 系统架构
 | 
			
		||||
```verilog
 | 
			
		||||
系统框图:
 | 
			
		||||
[Top模块] → [显示驱动模块] → [选通控制模块]
 | 
			
		||||
           ↖ ASCII数据生成 ↙ 时钟分频
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 3.3.2 模块设计
 | 
			
		||||
#### led_display_selector
 | 
			
		||||
```verilog
 | 
			
		||||
module led_display_selector #(
 | 
			
		||||
    parameter NUM = 4,
 | 
			
		||||
    parameter VALID_SIGNAL = 1'b0,
 | 
			
		||||
    parameter CLK_CYCLE = 1000
 | 
			
		||||
)(
 | 
			
		||||
    input  wire             clk,
 | 
			
		||||
    input  wire             rstn,
 | 
			
		||||
    input  wire [NUM*8-1:0] led_in,
 | 
			
		||||
    output  reg [7:0]       led_display_seg,//[DP,G,F,E,D,C,B,A]
 | 
			
		||||
    output  reg [NUM-1:0]   led_display_sel
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
reg [31:0] clk_cnt;
 | 
			
		||||
always @(posedge clk or negedge rstn) begin
 | 
			
		||||
    if (!rstn) clk_cnt <= 0;
 | 
			
		||||
    else if(clk_cnt == CLK_CYCLE) clk_cnt <= 0;
 | 
			
		||||
    else clk_cnt <= clk_cnt + 1;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
wire seg_change = (clk_cnt == CLK_CYCLE) ? 1'b1 : 1'b0;
 | 
			
		||||
 | 
			
		||||
always @(posedge clk or negedge rstn) begin
 | 
			
		||||
    if(!rstn) led_display_sel <= {{(NUM-1){~VALID_SIGNAL}}, VALID_SIGNAL};
 | 
			
		||||
    else if (seg_change) led_display_sel <= {led_display_sel[NUM-2:0], led_display_sel[NUM-1]};
 | 
			
		||||
    else led_display_sel <= led_display_sel;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
integer i;
 | 
			
		||||
always @(*) begin
 | 
			
		||||
    for(i=0;i<NUM;i=i+1) begin
 | 
			
		||||
        if(led_display_sel[NUM-1-i] == VALID_SIGNAL)
 | 
			
		||||
            led_display_seg = led_in[i*8 +: 8] ^ ({8{~VALID_SIGNAL}});
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
endmodule //led_display_ctrl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### led_display_driver
 | 
			
		||||
```verilog
 | 
			
		||||
module led_display_driver(// 8个数码管显示,阳极管(在selector中已经做了阴阳处理)
 | 
			
		||||
    input wire           clk,
 | 
			
		||||
    input wire           rstn,
 | 
			
		||||
    input wire [8*8-1:0] assic_seg, //ASSIC coding
 | 
			
		||||
    input wire [7:0]     seg_point, //显示小数点
 | 
			
		||||
 | 
			
		||||
    output wire [7:0]    led_display_seg,
 | 
			
		||||
    output wire [7:0]    led_display_sel
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
reg [8*8-1:0] led_in;
 | 
			
		||||
 | 
			
		||||
integer i;
 | 
			
		||||
always @(*) begin
 | 
			
		||||
    led_in = 0;
 | 
			
		||||
    for(i=0;i<8;i=i+1) begin //led_in[i*8 +: 8] <---> assic_seg[i*8 +: 8]
 | 
			
		||||
        case (assic_seg[i*8 +: 8])
 | 
			
		||||
            "0":     led_in[i*8 +: 8] = (8'h3f) | {seg_point[i],7'b0};
 | 
			
		||||
            "1":     led_in[i*8 +: 8] = (8'h06) | {seg_point[i],7'b0};
 | 
			
		||||
            "2":     led_in[i*8 +: 8] = (8'h5b) | {seg_point[i],7'b0};
 | 
			
		||||
            "3":     led_in[i*8 +: 8] = (8'h4f) | {seg_point[i],7'b0};
 | 
			
		||||
            "4":     led_in[i*8 +: 8] = (8'h66) | {seg_point[i],7'b0};
 | 
			
		||||
            "5":     led_in[i*8 +: 8] = (8'h6d) | {seg_point[i],7'b0};
 | 
			
		||||
            "6":     led_in[i*8 +: 8] = (8'h7d) | {seg_point[i],7'b0};
 | 
			
		||||
            "7":     led_in[i*8 +: 8] = (8'h07) | {seg_point[i],7'b0};
 | 
			
		||||
            "8":     led_in[i*8 +: 8] = (8'h7f) | {seg_point[i],7'b0};
 | 
			
		||||
            "9":     led_in[i*8 +: 8] = (8'h6f) | {seg_point[i],7'b0};
 | 
			
		||||
            "A","a": led_in[i*8 +: 8] = (8'h77) | {seg_point[i],7'b0};
 | 
			
		||||
            "B","b": led_in[i*8 +: 8] = (8'h7c) | {seg_point[i],7'b0};
 | 
			
		||||
            "C","c": led_in[i*8 +: 8] = (8'h39) | {seg_point[i],7'b0};
 | 
			
		||||
            "D","d": led_in[i*8 +: 8] = (8'h5e) | {seg_point[i],7'b0};
 | 
			
		||||
            "E","e": led_in[i*8 +: 8] = (8'h79) | {seg_point[i],7'b0};
 | 
			
		||||
            "F","f": led_in[i*8 +: 8] = (8'h71) | {seg_point[i],7'b0};
 | 
			
		||||
            "G","g": led_in[i*8 +: 8] = (8'h3d) | {seg_point[i],7'b0};
 | 
			
		||||
            "H","h": led_in[i*8 +: 8] = (8'h76) | {seg_point[i],7'b0};
 | 
			
		||||
            "I","i": led_in[i*8 +: 8] = (8'h0f) | {seg_point[i],7'b0};
 | 
			
		||||
            "J","j": led_in[i*8 +: 8] = (8'h0e) | {seg_point[i],7'b0};
 | 
			
		||||
            "K","k": led_in[i*8 +: 8] = (8'h75) | {seg_point[i],7'b0};
 | 
			
		||||
            "L","l": led_in[i*8 +: 8] = (8'h38) | {seg_point[i],7'b0};
 | 
			
		||||
            "M","m": led_in[i*8 +: 8] = (8'h37) | {seg_point[i],7'b0};
 | 
			
		||||
            "N","n": led_in[i*8 +: 8] = (8'h54) | {seg_point[i],7'b0};
 | 
			
		||||
            "O","o": led_in[i*8 +: 8] = (8'h5c) | {seg_point[i],7'b0};
 | 
			
		||||
            "P","p": led_in[i*8 +: 8] = (8'h73) | {seg_point[i],7'b0};
 | 
			
		||||
            "Q","q": led_in[i*8 +: 8] = (8'h67) | {seg_point[i],7'b0};
 | 
			
		||||
            "R","r": led_in[i*8 +: 8] = (8'h31) | {seg_point[i],7'b0};
 | 
			
		||||
            "S","s": led_in[i*8 +: 8] = (8'h49) | {seg_point[i],7'b0};
 | 
			
		||||
            "T","t": led_in[i*8 +: 8] = (8'h78) | {seg_point[i],7'b0};
 | 
			
		||||
            "U","u": led_in[i*8 +: 8] = (8'h3e) | {seg_point[i],7'b0};
 | 
			
		||||
            "V","v": led_in[i*8 +: 8] = (8'h1c) | {seg_point[i],7'b0};
 | 
			
		||||
            "W","w": led_in[i*8 +: 8] = (8'h7e) | {seg_point[i],7'b0};
 | 
			
		||||
            "X","x": led_in[i*8 +: 8] = (8'h64) | {seg_point[i],7'b0};
 | 
			
		||||
            "Y","y": led_in[i*8 +: 8] = (8'h6e) | {seg_point[i],7'b0};
 | 
			
		||||
            "Z","z": led_in[i*8 +: 8] = (8'h59) | {seg_point[i],7'b0};
 | 
			
		||||
            " ":     led_in[i*8 +: 8] = (8'h00) | {seg_point[i],7'b0};
 | 
			
		||||
            "-":     led_in[i*8 +: 8] = (8'h40) | {seg_point[i],7'b0};
 | 
			
		||||
            "_":     led_in[i*8 +: 8] = (8'h08) | {seg_point[i],7'b0};
 | 
			
		||||
            "=":     led_in[i*8 +: 8] = (8'h48) | {seg_point[i],7'b0};
 | 
			
		||||
            "+":     led_in[i*8 +: 8] = (8'h5c) | {seg_point[i],7'b0};
 | 
			
		||||
            "(":     led_in[i*8 +: 8] = (8'h39) | {seg_point[i],7'b0};
 | 
			
		||||
            ")":     led_in[i*8 +: 8] = (8'h0F) | {seg_point[i],7'b0};
 | 
			
		||||
            default: led_in[i*8 +: 8] = (8'h00) | {seg_point[i],7'b0};
 | 
			
		||||
        endcase
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
led_display_selector #(
 | 
			
		||||
	.NUM          	( 8     ),
 | 
			
		||||
	.VALID_SIGNAL 	( 1'b0  ), //阳极管,低电平亮
 | 
			
		||||
	.CLK_CYCLE    	( 5000 ))
 | 
			
		||||
u_led_display_selector(
 | 
			
		||||
	.clk             	( clk              ),
 | 
			
		||||
	.rstn            	( rstn             ),
 | 
			
		||||
	.led_in          	( led_in           ),
 | 
			
		||||
	.led_display_seg 	( led_display_seg  ),
 | 
			
		||||
	.led_display_sel 	( led_display_sel  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
endmodule //moduleName
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### led_display_top
 | 
			
		||||
```verilog
 | 
			
		||||
 | 
			
		||||
module led_diaplay_top(
 | 
			
		||||
    //system io
 | 
			
		||||
    input  wire       external_clk ,
 | 
			
		||||
    input  wire       external_rstn,
 | 
			
		||||
    //led display io
 | 
			
		||||
    output wire [7:0] led_display_seg,
 | 
			
		||||
    output wire [7:0] led_display_sel
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
reg [43*8-1:0] assic_seg;
 | 
			
		||||
reg [7:0]     seg_point;
 | 
			
		||||
 | 
			
		||||
reg [31:0] clk_cnt;
 | 
			
		||||
always @(posedge external_clk or negedge external_rstn) begin
 | 
			
		||||
    if(!external_rstn) clk_cnt <= 0;
 | 
			
		||||
    else clk_cnt <= clk_cnt + 1;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
always @(posedge external_clk or negedge external_rstn) begin
 | 
			
		||||
    if(!external_rstn) begin
 | 
			
		||||
        assic_seg <= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -_=+()";
 | 
			
		||||
        seg_point <= 8'b00000001;
 | 
			
		||||
    end else if({clk_cnt[24]==1'b1} && (clk_cnt[23:0]==25'b0))begin
 | 
			
		||||
        assic_seg <= {assic_seg[8*43-8-1:0], assic_seg[8*43-1 -: 8]};
 | 
			
		||||
        seg_point <= {seg_point[6:0], seg_point[7]};
 | 
			
		||||
    end else begin
 | 
			
		||||
        assic_seg <= assic_seg;
 | 
			
		||||
        seg_point <= seg_point;
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
led_display_driver u_led_display_driver(
 | 
			
		||||
	.clk             	( external_clk             ),
 | 
			
		||||
	.rstn            	( external_rstn            ),
 | 
			
		||||
	.assic_seg       	( assic_seg[8*43-1 -: 8*8] ),
 | 
			
		||||
	.seg_point       	( seg_point                ),
 | 
			
		||||
	.led_display_seg 	( led_display_seg          ),
 | 
			
		||||
	.led_display_sel 	( led_display_sel          )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
endmodule //led_diaplay_top
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 3.3.3 上板验证步骤
 | 
			
		||||
1. 设置参数:CLK_CYCLE=5000(对应200Hz扫描频率)
 | 
			
		||||
2. 绑定管脚:连接数码管段选/位选信号
 | 
			
		||||
3. 观察现象:字符"01234567"应稳定显示
 | 
			
		||||
4. 修改assic_seg初始值验证滚动功能
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 3.4 章末总结
 | 
			
		||||
**关键收获:**
 | 
			
		||||
1. 掌握动态扫描消除器件闪烁的原理
 | 
			
		||||
2. 理解参数化设计(NUM/VALID_SIGNAL)的优势
 | 
			
		||||
3. 学习时序控制中计数器的重要作用
 | 
			
		||||
4. 实践ASCII到硬件编码的转换方法
 | 
			
		||||
 | 
			
		||||
**设计亮点:**
 | 
			
		||||
- 支持阴阳极自动适配(通过VALID_SIGNAL参数)
 | 
			
		||||
- 字符环形缓冲区实现无缝滚动
 | 
			
		||||
- 参数化设计增强模块复用性
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 3.5 拓展训练
 | 
			
		||||
 | 
			
		||||
结合流水灯实验和数码管实验:数码管显示数字,标识出当前流水到了哪一个灯
 | 
			
		||||
							
								
								
									
										212
									
								
								public/doc/04/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,212 @@
 | 
			
		||||
# 基础-4-矩阵键盘实验
 | 
			
		||||
 | 
			
		||||
## 4.1 章节导读
 | 
			
		||||
本章将介绍**矩阵键盘检测电路的设计与实现方法**,通过Verilog HDL语言完成4×4矩阵键盘的扫描识别模块,掌握**多键输入设备的行列扫描原理、消抖机制以及按键编码处理方式**。
 | 
			
		||||
 | 
			
		||||
矩阵键盘作为常见的人机交互接口之一,广泛应用于嵌入式系统、数字电路和微控制器项目中。与独立按键不同,矩阵键盘在节省IO资源的同时,对扫描逻辑和时序处理提出了更高的要求。实验中我们将采用**逐行扫描法**,结合状态机与延时消抖手段,确保按键信息的准确采集。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 4.2 理论学习
 | 
			
		||||
### 4.2.1 矩阵键盘结构
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/1.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:80%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
    图1.矩阵键盘原理图 <!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
实验板8个引脚分别连接矩阵键盘的KEY1~KEY8,该矩阵键盘的原理如下:将KEY1~KEY4脚设置为输出引脚,KEY5~KEY8设置为输入引脚。以KEY1和KEY5为例,当没有按键按下时,KEY1和VCC之间是断路,此时R1为上拉电阻,电路几乎没有电流流过,KEY5检测到的电压恰好是VCC,为1。**所以按键不按下,KEY5~KEY8检测到1。**
 | 
			
		||||
 | 
			
		||||
如果按键按下。此时KEY5~KEY8检测到的值与KEY1~KEY4的输出电压有关。以KEY1和KEY5为例,如果KEY1输出为0,按键1按下,VCC和KEY1之间形成通路,KEY5检测到0。但如果KEY1输出为1,此时即使按键按下,VCC和KEY1之间也几乎没有电流,此时KEY5检测到高阻态,也就是1。所以,**如果行输出电平为0,并且按键按下,KEY5~8会检测到0;如果行输出电平为1,按键按下,KEY5~8检测到1。**
 | 
			
		||||
 | 
			
		||||
现在我们看懂了原理图就可以开始设计verilog,根据原理图我们知道,只有行电平(KEY1~4的输出电平)为0时,按键按下,KEY5~8才会检测到0。那么我们可以用行扫描的逻辑设计:
 | 
			
		||||
 | 
			
		||||
1. FPGA按顺序将1到4行中的一行输出为低电平,其余3行为高电平(或高阻态)。
 | 
			
		||||
2. FPGA逐个读取每列引脚(KEY5~8)的电平,若某列为低电平,则说明该行和该列交汇处的按键被按下。
 | 
			
		||||
3. 可以在没有按键按下时,把所有行的输出电平都拉低,直到有按键按下时,重复1~2的步骤扫描。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 4.2 实战演练
 | 
			
		||||
### 4.3.1 系统架构
 | 
			
		||||
``` verilog
 | 
			
		||||
系统框图:
 | 
			
		||||
[Top模块] = {矩阵键盘扫描模块 → 按键上升沿检测模块}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 4.3.2 模块设计
 | 
			
		||||
 | 
			
		||||
根据上述原理,设计行扫描矩阵键盘检测模块如下:
 | 
			
		||||
 | 
			
		||||
#### matrix_key
 | 
			
		||||
```verilog
 | 
			
		||||
module matrix_key #(
 | 
			
		||||
    parameter ROW_NUM = 4,
 | 
			
		||||
    parameter COL_NUM = 4,
 | 
			
		||||
    parameter DEBOUNCE_TIME = 2000,
 | 
			
		||||
    parameter DELAY_TIME = 200
 | 
			
		||||
) (
 | 
			
		||||
    input  wire clk,
 | 
			
		||||
    input  wire rstn,
 | 
			
		||||
    output  reg [ROW_NUM-1:0] row,
 | 
			
		||||
    input  wire [COL_NUM-1:0] col,
 | 
			
		||||
    output  reg [ROW_NUM*COL_NUM-1:0] key_out
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
    localparam ROW_ACTIVE = 1'b0;   // 行有效电平
 | 
			
		||||
    localparam ROW_INACTIVE = 1'b1; // 行无效电平
 | 
			
		||||
    localparam COL_PRESSED = 1'b0;  // 列按下电平
 | 
			
		||||
    localparam COL_RELEASED = 1'b1; // 列释放电平
 | 
			
		||||
    
 | 
			
		||||
    reg [ROW_NUM-1:0][COL_NUM-1:0] key; // 按键状态寄存器
 | 
			
		||||
 | 
			
		||||
    reg [2:0] cu_st, nt_st;
 | 
			
		||||
    localparam [2:0] ST_IDLE = 3'b001;
 | 
			
		||||
    localparam [2:0] ST_SCAN = 3'b010;
 | 
			
		||||
    localparam [2:0] ST_DEBOUNCE = 3'b100;
 | 
			
		||||
 | 
			
		||||
    wire btn_pressed = ((|(~(col ^ {COL_NUM{COL_PRESSED}}))) && (cu_st == ST_IDLE)) || (key_out != 0); // 只要有一个按键按下,btn_pressed为1
 | 
			
		||||
    reg [31:0] delay_cnt; // 延时计数器
 | 
			
		||||
    reg [31:0] debounce_cnt; // 消抖计数器
 | 
			
		||||
    reg [ROW_NUM-1:0] row_cnt; // 行计数器
 | 
			
		||||
 | 
			
		||||
    always @(posedge clk or negedge rstn) begin
 | 
			
		||||
        if(!rstn) delay_cnt <= 0;
 | 
			
		||||
        else if(cu_st == ST_SCAN) begin
 | 
			
		||||
            if(delay_cnt == DELAY_TIME) delay_cnt <= 0;
 | 
			
		||||
            else delay_cnt <= delay_cnt + 1;
 | 
			
		||||
        end else delay_cnt <= 0;
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    always @(posedge clk or negedge rstn) begin
 | 
			
		||||
        if(!rstn) row_cnt <= 0;
 | 
			
		||||
        else if(cu_st == ST_SCAN) begin
 | 
			
		||||
            if(delay_cnt == DELAY_TIME) row_cnt <= row_cnt + 1;
 | 
			
		||||
            else row_cnt <= row_cnt;
 | 
			
		||||
        end else row_cnt <= 0;
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    always @(posedge clk or negedge rstn) begin
 | 
			
		||||
        if(!rstn) debounce_cnt <= 0;
 | 
			
		||||
        else if(cu_st == ST_DEBOUNCE) begin
 | 
			
		||||
            if(debounce_cnt == DEBOUNCE_TIME) debounce_cnt <= 0;
 | 
			
		||||
            else debounce_cnt <= debounce_cnt + 1;
 | 
			
		||||
        end else debounce_cnt <= 0;
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    处理逻辑
 | 
			
		||||
    ROW作为输出,COL作为输入
 | 
			
		||||
    1. ST_IDLE状态,所有ROW都拉至有效电平
 | 
			
		||||
    2. 若没有按键按下,所有COL都为释放电平
 | 
			
		||||
    3. 若有按键按下,按下的按键所在的COL会变为按下电平
 | 
			
		||||
    4. 进入ST_SCAN状态,启动扫描,ROW全部置为无效电平,并逐次改变为有效电平。(此时,COL会都变成列释放电平)
 | 
			
		||||
    5. 如果某一个ROW行有效电平时,COL变成了列按下电平,则说明该ROW和COL交点的按键被按下
 | 
			
		||||
    6. 每一行都扫描一遍。
 | 
			
		||||
    7. 进入ST_DEBOUNCE状态,所有ROW都拉至行有效电平,在此期间不进行扫描。
 | 
			
		||||
    8. DEBOUNCE时间到后,进入IDLE状态。
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    always @(posedge clk or negedge rstn) begin
 | 
			
		||||
        if(!rstn) cu_st <= ST_IDLE;
 | 
			
		||||
        else cu_st <= nt_st;
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    always @(*) begin
 | 
			
		||||
        if(!rstn) nt_st <= ST_IDLE;
 | 
			
		||||
        else case(cu_st)
 | 
			
		||||
            ST_IDLE: begin
 | 
			
		||||
                if(btn_pressed) nt_st <= ST_SCAN;
 | 
			
		||||
                else nt_st <= ST_IDLE;
 | 
			
		||||
            end
 | 
			
		||||
            ST_SCAN: begin
 | 
			
		||||
                if((delay_cnt == DELAY_TIME) && (row_cnt == ROW_NUM-1)) nt_st <= ST_DEBOUNCE;
 | 
			
		||||
                else nt_st <= ST_SCAN;
 | 
			
		||||
            end
 | 
			
		||||
            ST_DEBOUNCE: begin
 | 
			
		||||
                if(debounce_cnt == DEBOUNCE_TIME) nt_st <= ST_IDLE;
 | 
			
		||||
                else nt_st <= ST_DEBOUNCE;
 | 
			
		||||
            end
 | 
			
		||||
            default: nt_st <= ST_IDLE;
 | 
			
		||||
        endcase
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    integer i, j;
 | 
			
		||||
    always @(posedge clk or negedge rstn) begin
 | 
			
		||||
        if(!rstn) key <= 0;
 | 
			
		||||
        else for(i=0; i<ROW_NUM; i=i+1) 
 | 
			
		||||
                for(j=0; j<COL_NUM; j=j+1)
 | 
			
		||||
                    if((cu_st == ST_SCAN) && (delay_cnt == DELAY_TIME) && (row_cnt == i)) key[i][j] <= (col[j] == COL_PRESSED)?(1'b1):(1'b0);
 | 
			
		||||
                    else key[i][j] <= key[i][j]; // 其他情况不变
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    always @(*) begin
 | 
			
		||||
        for(i=0;i<ROW_NUM;i=i+1) begin
 | 
			
		||||
            for(j=0;j<COL_NUM;j=j+1) begin
 | 
			
		||||
                key_out[i*COL_NUM+j] <= key[i][j];
 | 
			
		||||
            end
 | 
			
		||||
        end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    always @(posedge clk or negedge rstn) begin
 | 
			
		||||
        if(!rstn) row <= {ROW_NUM{ROW_ACTIVE}};
 | 
			
		||||
        else if(cu_st == ST_IDLE && nt_st == ST_SCAN) row <= {{(ROW_NUM-1){ROW_INACTIVE}}, ROW_ACTIVE};
 | 
			
		||||
        else if(cu_st == ST_SCAN) begin
 | 
			
		||||
            if(delay_cnt == DELAY_TIME) row <= {row[ROW_NUM-1:0],ROW_INACTIVE};
 | 
			
		||||
            else row <= row;
 | 
			
		||||
        end else row <= {ROW_NUM{ROW_ACTIVE}};
 | 
			
		||||
    end
 | 
			
		||||
endmodule //matrix_key
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
为了能够观察到现象,使用板载8个led和实验箱8个led进行显示,按下矩阵键盘的按键,对应led就会亮,顶层文件如下所示:
 | 
			
		||||
 | 
			
		||||
#### matrix_key_top
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
module matrix_key_top(
 | 
			
		||||
//system io
 | 
			
		||||
input  wire       external_clk ,
 | 
			
		||||
input  wire       external_rstn,
 | 
			
		||||
 | 
			
		||||
input  wire [ 3:0] col,
 | 
			
		||||
output wire [ 3:0] row,
 | 
			
		||||
output wire [15:0] led
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
wire [15:0] key_out;
 | 
			
		||||
 | 
			
		||||
assign led = key_out;
 | 
			
		||||
matrix_key #(
 | 
			
		||||
	.ROW_NUM       	( 4     ),
 | 
			
		||||
	.COL_NUM       	( 4     ),
 | 
			
		||||
	.DEBOUNCE_TIME 	( 10000 ),
 | 
			
		||||
	.DELAY_TIME    	( 2000  ))
 | 
			
		||||
u_matrix_key(
 | 
			
		||||
	.clk     	( external_clk  ),
 | 
			
		||||
	.rstn    	( external_rstn ),
 | 
			
		||||
	.row     	( row           ),
 | 
			
		||||
	.col     	( col           ),
 | 
			
		||||
	.key_out 	( key_out       )
 | 
			
		||||
);
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 4.3.3 上板验证步骤
 | 
			
		||||
1. 设置参数:CLK_CYCLE=5000(对应200Hz扫描频率)
 | 
			
		||||
2. 绑定管脚:连接led和矩阵键盘管脚
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 4.4 章末总结
 | 
			
		||||
**关键收获:**
 | 
			
		||||
1. 掌握矩阵键盘行扫描原理,能看懂原理图
 | 
			
		||||
3. 学习时序控制中计数器的重要作用
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 4.5 拓展训练
 | 
			
		||||
 | 
			
		||||
可以将数码管与矩阵键盘相结合
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/doc/04/images/1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 85 KiB  | 
							
								
								
									
										194
									
								
								public/doc/05/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,194 @@
 | 
			
		||||
# 基础-5-PWM呼吸灯
 | 
			
		||||
 | 
			
		||||
## 5.1 章节导读
 | 
			
		||||
 | 
			
		||||
本章将实现 PWM(脉宽调制)呼吸灯效果,即控制 LED 灯的亮度在一个周期内从暗到亮再从亮到暗,形成如人呼吸般的灯光变化。通过该实验可以掌握 PWM 占空比调节以及 FPGA 控制 LED 的基本方法。
 | 
			
		||||
 | 
			
		||||
## 5.2 理论学习
 | 
			
		||||
 | 
			
		||||
呼吸灯在我们的生活中很常见,在电脑上多作为消息提醒指示灯而被广泛使用,其效果是小灯在一段时间内从完全熄灭的状态逐渐变到最亮,再在同样的时间段内逐渐达到完全熄灭的状态,并循环往复。这种效果就像“呼吸”一样。而实现”呼吸“的方法就是PWM技术。
 | 
			
		||||
 | 
			
		||||
PWM(Pulse Width Modulation)是一种常用的控制技术,其核心思想是通过控制一个周期内信号为高电平的时间比例(占空比)来实现输出电压或亮度的变化。也就是说只要我们在小时间段内,led灯的亮度依次增加,然后依次减小,即可实现”呼吸“的效果。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 5.3 实战演练
 | 
			
		||||
 | 
			
		||||
### 5.3.1 实验目标
 | 
			
		||||
 | 
			
		||||
实现 LED 呼吸灯效果,亮度逐渐变亮再逐渐变暗,周而复始,整体周期约为2秒,视觉上更加自然流畅。
 | 
			
		||||
 | 
			
		||||
### 5.3.2 硬件资源
 | 
			
		||||
 | 
			
		||||
实验板提供 32 颗 LED 灯,本实验选用其中的 1 颗绿色 LED 进行 PWM 控制
 | 
			
		||||
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/1.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:30%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
    图1.LED扩展板   <!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
通过原理图可以得知,本试验箱的LED灯为高电平时点亮。
 | 
			
		||||
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/2.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:40%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
    图2.LED扩展板原理图    <!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
### 5.3.3 程序设计
 | 
			
		||||
 | 
			
		||||
本模块的设计事实上是两个计数器,所以肯定需要时钟信号sysclk,也需要一个rstn复位信号,同时需要一个IO口驱动LED。所以模块的端口如下表所示:
 | 
			
		||||
 | 
			
		||||
| 端口名称 | 端口位宽               | 端口类型           |功能描述|
 | 
			
		||||
|:----------:|:----:|:----:|:--------------------:|
 | 
			
		||||
| sysclk | 1Bit | Input | 输入时钟,频率27M |
 | 
			
		||||
| rstn | 1Bit | Input | 复位信号,低电平有效 |
 | 
			
		||||
| led | 1Bit | Output | LED控制信号 |
 | 
			
		||||
 | 
			
		||||
为了实现一个视觉上柔和自然的 LED 呼吸效果,我们设定完整的呼吸周期为 2 秒,即 LED 亮度在 1 秒内逐渐增强,接着在另 1 秒内逐渐减弱。整个过程由占空比(duty cycle)的变化来控制 PWM 输出的高电平持续时间。
 | 
			
		||||
 | 
			
		||||
在本设计中,使用实验板的 27MHz 系统时钟。为了获得合适的 PWM 控制精度,我们将一个 PWM 周期设定为 1ms,这对应 27000 个时钟周期(27M ÷ 1000)。通过一个名为 `pwm_cnt` 的计数器来实现这一周期性计数,当 `pwm_cnt` 小于占空比 `duty` 的值时,LED 输出高电平,从而控制亮度。
 | 
			
		||||
 | 
			
		||||
为了实现“呼吸”变化,我们再设计另一个计数器 `duty`,它每 1ms(即 `pwm_cnt` 计满一次)更新一次。前 1000ms 内占空比逐渐增加,即 `duty` 每次增加,从而输出高电平的时间逐步变长,LED 亮度逐渐增强;后 1000ms 内占空比逐渐减小,每次减小,LED 亮度逐渐变弱。如此循环往复,即可实现 LED 的“柔和呼吸”效果。
 | 
			
		||||
 | 
			
		||||
那么,占空比 duty 的变化步长如何选择?考虑到:一个 1ms是 27000 个时钟;如果我们希望1ms内led亮的时间为1us的倍数,那么我们可以将27000分成1000份,一份是27。如果duty的每次增减是27,那么也就对应了led每次亮灭的时间增减了1us。也就是说当duty为27时,led亮的时间为1us,1ms过后,duty变为54,led亮的时间为2us,以此类推,当duty为27000时,led亮满1ms。这样就实现了led亮的时间逐渐增加的效果。
 | 
			
		||||
 | 
			
		||||
模块的参考代码如下所示(`pwm.v`):
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
module pwm(
 | 
			
		||||
    input wire sysclk,     // 27MHz 系统时钟
 | 
			
		||||
    input wire rstn,    // 低有效复位
 | 
			
		||||
    output wire led     // PWM 控制LED输出
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
parameter PWM_PERIOD = 16'd27000;//1ms
 | 
			
		||||
// 单一PWM周期,1ms
 | 
			
		||||
// duty上升的次数是1000次,下降的次数也是1000次,说明pwm的半周期是 1ms * 1000 = 1s
 | 
			
		||||
// pwm的一次全周期是 1s * 2 = 2s
 | 
			
		||||
reg [15:0] pwm_cnt;
 | 
			
		||||
reg [15:0] duty;
 | 
			
		||||
reg inc_dec_flag;//0表示duty+ ,1表示duty-
 | 
			
		||||
//计数器1,不断累加
 | 
			
		||||
always @(posedge sysclk or negedge rstn) begin
 | 
			
		||||
    if (!rstn)
 | 
			
		||||
        pwm_cnt <= 0;
 | 
			
		||||
    else if (pwm_cnt < PWM_PERIOD - 1)
 | 
			
		||||
        pwm_cnt <= pwm_cnt + 1;
 | 
			
		||||
    else
 | 
			
		||||
        pwm_cnt <= 0;
 | 
			
		||||
end
 | 
			
		||||
//计数器2,控制占空比,单一周期结束进行一次累加或者减
 | 
			
		||||
always @(posedge sysclk or negedge rstn) begin
 | 
			
		||||
    if (!rstn)
 | 
			
		||||
        duty <= 0;
 | 
			
		||||
    else if (pwm_cnt == PWM_PERIOD - 1)begin
 | 
			
		||||
        if(inc_dec_flag == 0)
 | 
			
		||||
            duty <= duty + 27;
 | 
			
		||||
        else 
 | 
			
		||||
            duty <= duty - 27;
 | 
			
		||||
    end
 | 
			
		||||
    else duty <= duty;
 | 
			
		||||
end
 | 
			
		||||
//加减的标志位,半周期结束后反转。
 | 
			
		||||
always @(posedge sysclk or negedge rstn) begin
 | 
			
		||||
    if(~rstn)
 | 
			
		||||
        inc_dec_flag <= 0;
 | 
			
		||||
    else if(duty == PWM_PERIOD)
 | 
			
		||||
        inc_dec_flag <= 1;
 | 
			
		||||
    else if(duty == 0)
 | 
			
		||||
        inc_dec_flag <= 0;
 | 
			
		||||
    else 
 | 
			
		||||
        inc_dec_flag <= inc_dec_flag;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
assign led = (pwm_cnt < duty) ? 1'b1 : 1'b0;
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 5.3.4 仿真验证
 | 
			
		||||
 | 
			
		||||
为了验证模块功能,我们可以编写仿真模块,并将 `PWM_PERIOD` 等比例缩小为270,以便快速验证。以下为仿真文件(`pwm_tb.v`):
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
`timescale 1ns/1ns
 | 
			
		||||
module pwm_tb;
 | 
			
		||||
 | 
			
		||||
    reg sysclk;
 | 
			
		||||
    reg rstn;
 | 
			
		||||
    wire led;
 | 
			
		||||
 | 
			
		||||
    // 实例化待测试模块
 | 
			
		||||
    pwm #(
 | 
			
		||||
        .PWM_PERIOD(270)//为了减少仿真时间,将单一pwm周期从27000等比例缩小为270
 | 
			
		||||
    ) pwm_inst (
 | 
			
		||||
        .sysclk(sysclk),
 | 
			
		||||
        .rstn(rstn),
 | 
			
		||||
        .led(led)
 | 
			
		||||
      );
 | 
			
		||||
    // 产生系统时钟:周期约为 27Mhz
 | 
			
		||||
    initial begin
 | 
			
		||||
        sysclk = 0;
 | 
			
		||||
        forever #(500/27) sysclk = ~sysclk;
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    // 初始化和复位过程
 | 
			
		||||
    initial begin
 | 
			
		||||
        // 初始化
 | 
			
		||||
        rstn = 0;
 | 
			
		||||
        #100;           // 保持复位100ns
 | 
			
		||||
        rstn = 1;       // 释放复位
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
同时为了便于仿真,可以直接点击sim文件夹下hebav文件夹中的do.bat文件即可利用ModuleSim对模块进行仿真,仿真波形如下:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/3.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:60%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图3.呼吸灯仿真波形(一)	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/4.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:60%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
    图4.呼吸灯仿真波形(二)   <!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
通过观察波形我们发现led输出为1的时间在逐步增加之后逐步减小,duty的值从0增加到270后减小,符合设计预期,可以进行下一步上板验证。
 | 
			
		||||
 | 
			
		||||
### 5.3.5 上板验证
 | 
			
		||||
 | 
			
		||||
仿真验证通过后,即可进行上板测试。在实际使用时需要进行管脚约束。以下为参考端口与分配示例:
 | 
			
		||||
 | 
			
		||||
| 端口名称 | 信号类型 | 对应管脚 | 功能               |
 | 
			
		||||
| -------- | -------- | -------- | ------------------ |
 | 
			
		||||
| clk      | Input    |          | 27MHz时钟          |
 | 
			
		||||
| rstn     | Input    |          | 复位               |
 | 
			
		||||
| led      | Output   |          | 输出PWM信号连接LED |
 | 
			
		||||
 | 
			
		||||
完成管脚绑定后生成 `.sbit` 文件,上传到实验平台后进行烧录,即可在摄像头画面中看到 LED 呼吸闪烁效果。
 | 
			
		||||
 | 
			
		||||
## 5.4 章末总结
 | 
			
		||||
 | 
			
		||||
本章我们学习了 PWM 控制的基本原理及其在 LED 呼吸灯上的应用,同时通过不断改变PWM占空比方式使呼吸过程更加平滑自然。该方法不仅适用于视觉灯效控制,还广泛应用于马达调速、音量控制等模拟量调节领域。你可以进一步尝试调整占空比范围、节奏速度,甚至扩展到多个 LED 同步/异步呼吸控制,实现更加炫酷的视觉效果。
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/doc/05/images/1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/05/images/2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.9 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/05/images/3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 858 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/05/images/4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 MiB  | 
							
								
								
									
										650
									
								
								public/doc/06/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,650 @@
 | 
			
		||||
# 基础-6-HDMI显示
 | 
			
		||||
 | 
			
		||||
## 6.1 章节导读
 | 
			
		||||
 | 
			
		||||
随着多媒体技术的快速发展,高清显示已成为嵌入式系统与FPGA应用中不可或缺的一部分。HDMI(High-Definition Multimedia Interface)作为目前最主流的视频数字传输标准,广泛应用于电视、显示器、笔记本、摄像头等各类终端设备中。相比传统的模拟VGA接口,HDMI具有传输带宽高、支持音视频同步、无压缩信号传输等优点,能更好地满足现代图像处理和显示系统的需求。
 | 
			
		||||
 | 
			
		||||
在FPGA开发中,掌握HDMI显示技术不仅是实现图像/视频输出的基础能力,更是后续图像识别、视频监控、图形用户界面(GUI)等复杂系统设计的前提。因此,本实验以HDMI显示为核心内容,带领大家从零开始构建一个完整的视频输出链路。通过配置显示参数、生成时序控制信号、输出RGB图像数据等关键步骤,最终实现在HDMI接口上稳定输出画面。
 | 
			
		||||
 | 
			
		||||
在本次实验中我们将学习利用实验板的HDMI接口和MS7210芯片,进行HDMI显示实验的设计。
 | 
			
		||||
 | 
			
		||||
## 6.2 理论学习
 | 
			
		||||
 | 
			
		||||
### 6.2.1 VGA时序
 | 
			
		||||
 | 
			
		||||
VGA显示是在行同步和帧同步(场同步)的信号同步下,按照从上到下,从左到右的顺序,扫描到显示屏上。VGA扫描方式见下图所示:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/1.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:50%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图1.VGA扫描顺序	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
如上图所示,每一帧图像都是从左上角开始,逐行扫描形成,所以规定最左上角的像素点为第一个像素点,坐标是(0,0),以这个像素为起点,向右x坐标逐渐增大,向下y坐标逐渐增大,重复若干次后扫描到右下角完成一帧图像的扫描,扫描完成后进行图像消隐,随后指针跳回左上角重新进行新一帧的扫描。
 | 
			
		||||
 | 
			
		||||
 在扫描的过程中会对每一个像素点进行单独赋值,使每个像素点显示对应色彩信息,当扫描速度足够快,加之人眼的视觉暂留特性,我们会看到一幅完整的图片,这就是VGA 显示的原理。
 | 
			
		||||
 | 
			
		||||
VGA显示除了要有像素点的信息,还需要有行同步(HSync)和场同步(VSync)两个信号辅助显示。行同步信号规定了一行像素的开始与结束,场同步信号规定了一帧图像的开始与结束。在VESA DMT 1.12版本的标准文档中给出的VGA时序图如下图所示:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/2.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图2.VGA标准时序	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
行同步时序如下图所示:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/3.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图3.行同步时序	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
行同步的一个扫周期要经过6个部分,分别是Sync(同步)、 Back Porch(后沿)、 Left Border(左边框)、 “Addressable” Video(有效图像)、 Right Border(右边框)、 Front Porch(前沿),这些过程的长度都是以像素为单位的,也就是以像素时钟为单位,例如Sync的值为96,也就意味着Sync阶段要经历96个像素时钟。HSync信号会在Sync同步阶段拉高(不同的芯片可能有不同标准)以确定新一行的开始与上一行的结束。而完整的一行像素很多,但有效的真正能显示在屏幕上的像素只有 “Addressable” Video(有效图像)部分的像素,其他阶段的像素均无效,无法显示在屏幕中。
 | 
			
		||||
 | 
			
		||||
场同步时序如下图所示:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/4.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图4.场同步时序	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
场同步时序与行同步时序相同,也是分为6个部分,在Sync同步阶段拉高,标志着一帧的结束和新一帧的开始,其中像素只有在“Addressable” Video(有效图像)阶段才有效,其他阶段均无效。而场同步信号的基本单位是行,比如Sync的值为2,也就意味着Sync同步阶段要经历两行。
 | 
			
		||||
 | 
			
		||||
那么我们将行同步和场同步信号结合起来,遍可以得到一帧图像的样貌,如下图所示:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/5.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图5.一帧图像组成示意图	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
可以看到在行场同步信号构成了一个二维坐标系,原点在左上方,中间遍形成了一帧图像,而真正能显示在屏幕中的图像只有 “Addressable” Video(有效图像)部分。
 | 
			
		||||
 | 
			
		||||
现在我们知道了行同步和场同步都要经历6个部分,那么这些部分的长度都是如何规定的呢?VGA 行时序对行同步时间、 消隐时间、 行视频有效时间和行前肩时间有特定的规范, 场时序也是如此。 常用VGA 分辨率时序参数如下表所示:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/6.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图6.常用VGA分辨率时序参数	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
### 6.2.2 MS7210芯片
 | 
			
		||||
 | 
			
		||||
MS7210是一款HD发送芯片,支持4K@30Hz的视频3D传输格式。可以支持的最高分辨率高达4K@30Hz,最高采样率达到300MHz。MS7210支持YUV和RGB 之间的色彩空间转换,数字接口支持YUV以及RGB格式输入。MS7210的IIS接口以及S/PDIF 接口支持高清音频的传输,其中S/PDIF接口既可以兼容IEC61937标准下的压缩音频传输,同时还支持高比特音频(HBR)的传输,在高比特音频(HBR)模式下,音频采样率最高为768KHz。MS7210的IIC 地址可以根据SA引脚进行选择。当 SA引脚上拉到电源电压或者悬空时,地址为 OxB2。当 SA 引脚连接到 GND 时,地址为0x56。
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/7.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图7.MS7210芯片	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/8.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:50%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图8.MS7210功能框图	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
MS7210芯片可以通过IIC协议对内部寄存器进行配置,有关芯片寄存器配置需要向芯片厂家进行申请。
 | 
			
		||||
 | 
			
		||||
## 6.3 实战演练
 | 
			
		||||
 | 
			
		||||
### 6.3.1实验目标
 | 
			
		||||
 | 
			
		||||
### 6.3.2硬件资源
 | 
			
		||||
 | 
			
		||||
实验板共有一个HDMI-OUT接口,由MS7210驱动,一个HDMI-IN接口,由MS7200驱动。
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/9.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:30%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图9.板载HDMI芯片	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
实验箱配备一个小型HDMI显示器,该显示器HDMI接口与HDMI-OUT接口连接,图像可以显示在显示屏中,通过摄像头可以在网站观察现象
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/xxx.png"
 | 
			
		||||
         alt="实验箱显示器"
 | 
			
		||||
         style="zoom:40%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图10.实验箱显示器	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 6.3.3程序设计
 | 
			
		||||
 | 
			
		||||
在设计程序时,我们先对本实验工程有一个整体认知,首先来看一下HDMI彩条显示实验的整体框图。
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/xxxx.png"
 | 
			
		||||
         alt="实验箱显示器"
 | 
			
		||||
         style="zoom:40%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图11.HDMI彩条显示整体框图	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
可见整个实验一共由好多个模块组成,下面是各个模块简介
 | 
			
		||||
| 模块名称 |功能描述| 备注 |
 | 
			
		||||
|:----:|:----:|:----:|
 | 
			
		||||
| hdmi_top | 顶层模块 ||
 | 
			
		||||
| ms7210_ctrl_iic_top | ms7210芯片配置和iic顶层模块 |参考小眼睛例程|
 | 
			
		||||
| ms7210_ctl | ms7210芯片配置和时序控制模块 |使用小眼睛例程|
 | 
			
		||||
| iic_dri | iic驱动模块 |使用小眼睛例程|
 | 
			
		||||
| vga_ctrl | vga时序信号生成模块 |参考野火例程|
 | 
			
		||||
| vga_pic | vga像素数据生成模块 |参考野火例程|
 | 
			
		||||
 | 
			
		||||
本次实验主要完成vga_ctrl和vga_pic模块的设计。
 | 
			
		||||
 | 
			
		||||
对于vga_ctrl模块,我们主要完成hsync,vsync信号,xy坐标,数据有效rgb_valid信号的设计。经过我们前面的学习已经对vga时序有了一定的了解,我们可以想象到这几个信号也只是一种计数器而已。
 | 
			
		||||
 | 
			
		||||
本实验要实现640x480的彩条显示,相关参数如下所示:
 | 
			
		||||
 | 
			
		||||
```Verilog
 | 
			
		||||
//parameter define
 | 
			
		||||
parameter H_SYNC    =   10'd96  ,   //行同步
 | 
			
		||||
          H_BACK    =   10'd40  ,   //行时序后沿
 | 
			
		||||
          H_LEFT    =   10'd8   ,   //行时序左边框
 | 
			
		||||
          H_VALID   =   10'd640 ,   //行有效数据
 | 
			
		||||
          H_RIGHT   =   10'd8   ,   //行时序右边框
 | 
			
		||||
          H_FRONT   =   10'd8   ,   //行时序前沿
 | 
			
		||||
          H_TOTAL   =   10'd800 ;   //行扫描周期
 | 
			
		||||
parameter V_SYNC    =   10'd2   ,   //场同步
 | 
			
		||||
          V_BACK    =   10'd25  ,   //场时序后沿
 | 
			
		||||
          V_TOP     =   10'd8   ,   //场时序上边框
 | 
			
		||||
          V_VALID   =   10'd480 ,   //场有效数据
 | 
			
		||||
          V_BOTTOM  =   10'd8   ,   //场时序下边框
 | 
			
		||||
          V_FRONT   =   10'd2   ,   //场时序前沿
 | 
			
		||||
          V_TOTAL   =   10'd525 ;   //场扫描周期
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
首先设计两个计数器`cnt_h`和`cnt_v`分别对像素和行进行计数,一个像素时钟过后`cnt_h`加一,一行过后`cnt_v`加一,扫描完一帧之后,计数器归零。
 | 
			
		||||
 | 
			
		||||
而其他的状态信号则可以根据计数器的计数进行设计。hsync信号只要`cnt_h < H_SYNC`就拉高,vsync信号类似。当计数到有效数据部分数据有效信号rgb_valid就可以拉高,注意,由于时序逻辑有一个时钟周期的反应时间,所以xy的坐标变化比rgb_valid提前一个时钟周期。参考代码如下所示:
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
`timescale  1ns/1ns
 | 
			
		||||
////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Author        : EmbedFire
 | 
			
		||||
// 实验平台: 野火FPGA系列开发板
 | 
			
		||||
// 公司    : http://www.embedfire.com
 | 
			
		||||
// 论坛    : http://www.firebbs.cn
 | 
			
		||||
// 淘宝    : https://fire-stm32.taobao.com
 | 
			
		||||
////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
module  vga_ctrl
 | 
			
		||||
(
 | 
			
		||||
    input   wire            vga_clk     ,   //输入工作时钟,频率25MHz
 | 
			
		||||
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
 | 
			
		||||
    input   wire    [15:0]  pix_data    ,   //输入像素点色彩信息
 | 
			
		||||
 | 
			
		||||
    output  wire    [11:0]  pix_x       ,   //输出VGA有效显示区域像素点X轴坐标
 | 
			
		||||
    output  wire    [11:0]  pix_y       ,   //输出VGA有效显示区域像素点Y轴坐标
 | 
			
		||||
    output  wire            hsync       ,   //输出行同步信号
 | 
			
		||||
    output  wire            vsync       ,   //输出场同步信号
 | 
			
		||||
    output  wire            rgb_valid   ,
 | 
			
		||||
    output  wire    [15:0]  rgb             //输出像素点色彩信息
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//****************** Parameter and Internal Signal *******************//
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//parameter define
 | 
			
		||||
parameter H_SYNC    =   10'd96  ,   //行同步
 | 
			
		||||
          H_BACK    =   10'd40  ,   //行时序后沿
 | 
			
		||||
          H_LEFT    =   10'd8   ,   //行时序左边框
 | 
			
		||||
          H_VALID   =   10'd640 ,   //行有效数据
 | 
			
		||||
          H_RIGHT   =   10'd8   ,   //行时序右边框
 | 
			
		||||
          H_FRONT   =   10'd8   ,   //行时序前沿
 | 
			
		||||
          H_TOTAL   =   10'd800 ;   //行扫描周期
 | 
			
		||||
parameter V_SYNC    =   10'd2   ,   //场同步
 | 
			
		||||
          V_BACK    =   10'd25  ,   //场时序后沿
 | 
			
		||||
          V_TOP     =   10'd8   ,   //场时序上边框
 | 
			
		||||
          V_VALID   =   10'd480 ,   //场有效数据
 | 
			
		||||
          V_BOTTOM  =   10'd8   ,   //场时序下边框
 | 
			
		||||
          V_FRONT   =   10'd2   ,   //场时序前沿
 | 
			
		||||
          V_TOTAL   =   10'd525 ;   //场扫描周期
 | 
			
		||||
 | 
			
		||||
//wire  define
 | 
			
		||||
wire            pix_data_req    ;   //像素点色彩信息请求信号
 | 
			
		||||
 | 
			
		||||
//reg   define
 | 
			
		||||
reg     [11:0]  cnt_h           ;   //行同步信号计数器
 | 
			
		||||
reg     [11:0]  cnt_v           ;   //场同步信号计数器
 | 
			
		||||
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//***************************** Main Code ****************************//
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
 | 
			
		||||
//cnt_h:行同步信号计数器
 | 
			
		||||
always@(posedge vga_clk or negedge sys_rst_n)
 | 
			
		||||
    if(sys_rst_n == 1'b0)
 | 
			
		||||
        cnt_h   <=  12'd0   ;
 | 
			
		||||
    else    if(cnt_h == H_TOTAL - 1'd1)
 | 
			
		||||
        cnt_h   <=  12'd0   ;
 | 
			
		||||
    else
 | 
			
		||||
        cnt_h   <=  cnt_h + 1'd1   ;
 | 
			
		||||
 | 
			
		||||
//hsync:行同步信号
 | 
			
		||||
assign  hsync = (cnt_h  <=  H_SYNC - 1'd1) ? 1'b1 : 1'b0  ;
 | 
			
		||||
 | 
			
		||||
//cnt_v:场同步信号计数器
 | 
			
		||||
always@(posedge vga_clk or negedge sys_rst_n)
 | 
			
		||||
    if(sys_rst_n == 1'b0)
 | 
			
		||||
        cnt_v   <=  12'd0 ;
 | 
			
		||||
    else    if((cnt_v == V_TOTAL - 1'd1) &&  (cnt_h == H_TOTAL-1'd1))
 | 
			
		||||
        cnt_v   <=  12'd0 ;
 | 
			
		||||
    else    if(cnt_h == H_TOTAL - 1'd1)
 | 
			
		||||
        cnt_v   <=  cnt_v + 1'd1 ;
 | 
			
		||||
    else
 | 
			
		||||
        cnt_v   <=  cnt_v ;
 | 
			
		||||
 | 
			
		||||
//vsync:场同步信号
 | 
			
		||||
assign  vsync = (cnt_v  <=  V_SYNC - 1'd1) ? 1'b1 : 1'b0  ;
 | 
			
		||||
 | 
			
		||||
//rgb_valid:VGA有效显示区域
 | 
			
		||||
assign  rgb_valid = (((cnt_h >= H_SYNC + H_BACK + H_LEFT)
 | 
			
		||||
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID))
 | 
			
		||||
                    &&((cnt_v >= V_SYNC + V_BACK + V_TOP)
 | 
			
		||||
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)))
 | 
			
		||||
                    ? 1'b1 : 1'b0;
 | 
			
		||||
 | 
			
		||||
//pix_data_req:像素点色彩信息请求信号,超前rgb_valid信号一个时钟周期
 | 
			
		||||
assign  pix_data_req = (((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1)
 | 
			
		||||
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1))
 | 
			
		||||
                    &&((cnt_v >= V_SYNC + V_BACK + V_TOP)
 | 
			
		||||
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)))
 | 
			
		||||
                    ? 1'b1 : 1'b0;
 | 
			
		||||
 | 
			
		||||
//pix_x,pix_y:VGA有效显示区域像素点坐标
 | 
			
		||||
assign  pix_x = (pix_data_req == 1'b1)
 | 
			
		||||
                ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 12'hfff;
 | 
			
		||||
assign  pix_y = (pix_data_req == 1'b1)
 | 
			
		||||
                ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 12'hfff;
 | 
			
		||||
 | 
			
		||||
//rgb:输出像素点色彩信息
 | 
			
		||||
assign  rgb = (rgb_valid == 1'b1) ? pix_data : 16'b0 ;
 | 
			
		||||
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
对于vga_pic模块,我们可以根据x坐标范围(0~639)分成十份,每一份输出不同的颜色。参考代码如下所示:
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
`timescale  1ns/1ns
 | 
			
		||||
////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Author        : EmbedFire
 | 
			
		||||
// 实验平台: 野火FPGA系列开发板
 | 
			
		||||
// 公司    : http://www.embedfire.com
 | 
			
		||||
// 论坛    : http://www.firebbs.cn
 | 
			
		||||
// 淘宝    : https://fire-stm32.taobao.com
 | 
			
		||||
////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
module  vga_pic
 | 
			
		||||
(
 | 
			
		||||
    input   wire            vga_clk     ,   //输入工作时钟,频率25MHz
 | 
			
		||||
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
 | 
			
		||||
    input   wire    [11:0]  pix_x       ,   //输入VGA有效显示区域像素点X轴坐标
 | 
			
		||||
    input   wire    [11:0]  pix_y       ,   //输入VGA有效显示区域像素点Y轴坐标
 | 
			
		||||
 | 
			
		||||
    output  reg     [15:0]  pix_data        //输出像素点色彩信息
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//****************** Parameter and Internal Signal *******************//
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//parameter define
 | 
			
		||||
parameter   H_VALID =   12'd640 ,   //行有效数据
 | 
			
		||||
            V_VALID =   12'd480 ;   //场有效数据
 | 
			
		||||
 | 
			
		||||
parameter   RED     =   16'hF800,   //红色
 | 
			
		||||
            ORANGE  =   16'hFC00,   //橙色
 | 
			
		||||
            YELLOW  =   16'hFFE0,   //黄色
 | 
			
		||||
            GREEN   =   16'h07E0,   //绿色
 | 
			
		||||
            CYAN    =   16'h07FF,   //青色
 | 
			
		||||
            BLUE    =   16'h001F,   //蓝色
 | 
			
		||||
            PURPPLE =   16'hF81F,   //紫色
 | 
			
		||||
            BLACK   =   16'h0000,   //黑色
 | 
			
		||||
            WHITE   =   16'hFFFF,   //白色
 | 
			
		||||
            GRAY    =   16'hD69A;   //灰色
 | 
			
		||||
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//***************************** Main Code ****************************//
 | 
			
		||||
//********************************************************************//
 | 
			
		||||
//pix_data:输出像素点色彩信息,根据当前像素点坐标指定当前像素点颜色数据
 | 
			
		||||
always@(posedge vga_clk or negedge sys_rst_n)
 | 
			
		||||
    if(sys_rst_n == 1'b0)
 | 
			
		||||
        pix_data    <= 16'd0;
 | 
			
		||||
    else    if((pix_x >= 0) && (pix_x < (H_VALID/10)*1))
 | 
			
		||||
        pix_data    <=  RED;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*1) && (pix_x < (H_VALID/10)*2))
 | 
			
		||||
        pix_data    <=  ORANGE;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*2) && (pix_x < (H_VALID/10)*3))
 | 
			
		||||
        pix_data    <=  YELLOW;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*3) && (pix_x < (H_VALID/10)*4))
 | 
			
		||||
        pix_data    <=  GREEN;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*4) && (pix_x < (H_VALID/10)*5))
 | 
			
		||||
        pix_data    <=  CYAN;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*5) && (pix_x < (H_VALID/10)*6))
 | 
			
		||||
        pix_data    <=  BLUE;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*6) && (pix_x < (H_VALID/10)*7))
 | 
			
		||||
        pix_data    <=  PURPPLE;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*7) && (pix_x < (H_VALID/10)*8))
 | 
			
		||||
        pix_data    <=  BLACK;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*8) && (pix_x < (H_VALID/10)*9))
 | 
			
		||||
        pix_data    <=  WHITE;
 | 
			
		||||
    else    if((pix_x >= (H_VALID/10)*9) && (pix_x < H_VALID))
 | 
			
		||||
        pix_data    <=  GRAY;
 | 
			
		||||
    else
 | 
			
		||||
        pix_data    <=  BLACK;
 | 
			
		||||
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
在顶层模块,我们首先要利用PLL ip核生成iic的驱动时钟进行初始化,由于ms7210芯片的需要,我们通过计数设置一个延迟复位信号,由于我们的彩条颜色是按照RGB565格式生成的,所以需要向RGB888进行转换,只需要填0补位即可,同时由于板载时钟是27M与25.175M相差不大,所以直接使用板载时钟作为像素时钟输出。然后我们将输出的行场同步信号,像素时钟,像素数据,像素数据有效信号等与模块相连接即可完成设计。顶层模块参考代码如下:
 | 
			
		||||
 | 
			
		||||
```Verilog
 | 
			
		||||
`timescale 1ns / 1ns
 | 
			
		||||
module hdmi_top(
 | 
			
		||||
    input wire          sys_clk       ,// input system clock 50MHz   
 | 
			
		||||
    input               rstn_in       , 
 | 
			
		||||
    output              rstn_out      ,
 | 
			
		||||
    output              hd_scl        ,
 | 
			
		||||
    inout               hd_sda        ,
 | 
			
		||||
    output              led_int       ,
 | 
			
		||||
 | 
			
		||||
//hdmi_out 
 | 
			
		||||
    output              pixclk_out    ,//pixclk                           
 | 
			
		||||
    output  wire        vs_out        , 
 | 
			
		||||
    output  wire        hs_out        , 
 | 
			
		||||
    output  wire        de_out        ,
 | 
			
		||||
    output  wire [7:0]  r_out         , 
 | 
			
		||||
    output  wire [7:0]  g_out         , 
 | 
			
		||||
    output  wire [7:0]  b_out         
 | 
			
		||||
 | 
			
		||||
);
 | 
			
		||||
wire                        cfg_clk    ;
 | 
			
		||||
wire                        locked     ;
 | 
			
		||||
wire                        rstn       ;
 | 
			
		||||
wire                        init_over  ;
 | 
			
		||||
reg  [15:0]                 rstn_1ms   ;
 | 
			
		||||
//**********************************************//
 | 
			
		||||
//*****************MS7210初始化******************//
 | 
			
		||||
//**********************************************//
 | 
			
		||||
//**************仿真时不编译此部分***************//
 | 
			
		||||
`ifndef SIM
 | 
			
		||||
//初始化成功标志
 | 
			
		||||
assign    led_int    =     init_over;
 | 
			
		||||
//生成10M IIC时钟
 | 
			
		||||
PLL u_pll (
 | 
			
		||||
  .clkout0(cfg_clk),    // output
 | 
			
		||||
  .lock(locked),          // output
 | 
			
		||||
  .clkin1(sys_clk)       // input
 | 
			
		||||
);
 | 
			
		||||
//ms7210初始化模块
 | 
			
		||||
ms7210_ctrl_iic_top ms7210_ctrl_iic_top_inst(
 | 
			
		||||
    .clk         (  cfg_clk    ), //input       clk,
 | 
			
		||||
    .rst_n       (  rstn_out   ), //input       rstn,
 | 
			
		||||
                        
 | 
			
		||||
    .init_over   (  init_over  ), //output      init_over,
 | 
			
		||||
    .iic_scl     (  hd_scl    ), //output      iic_scl,
 | 
			
		||||
    .iic_sda     (  hd_sda    )  //inout       iic_sda
 | 
			
		||||
);
 | 
			
		||||
//延迟复位
 | 
			
		||||
always @(posedge cfg_clk)
 | 
			
		||||
begin
 | 
			
		||||
	if(!locked)
 | 
			
		||||
	    rstn_1ms <= 16'd0;
 | 
			
		||||
	else
 | 
			
		||||
	begin
 | 
			
		||||
		if(rstn_1ms == 16'h2710)
 | 
			
		||||
		    rstn_1ms <= rstn_1ms;
 | 
			
		||||
		else
 | 
			
		||||
		    rstn_1ms <= rstn_1ms + 1'b1;
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
assign rstn_out = (rstn_1ms == 16'h2710) && rstn_in;
 | 
			
		||||
//**********************************************//
 | 
			
		||||
`else
 | 
			
		||||
assign led_int  =     1;
 | 
			
		||||
assign rstn_out = rstn_in;
 | 
			
		||||
 | 
			
		||||
`endif
 | 
			
		||||
//**********************************************//
 | 
			
		||||
//**********************************************//
 | 
			
		||||
//**********************************************//
 | 
			
		||||
//**********************************************//
 | 
			
		||||
wire [15:0] rgb565;
 | 
			
		||||
wire [15:0] pix_data ;
 | 
			
		||||
wire [11:0] pix_x;
 | 
			
		||||
wire [11:0] pix_y;
 | 
			
		||||
//vga行场同步控制模块
 | 
			
		||||
vga_ctrl  vga_ctrl_inst (
 | 
			
		||||
    .vga_clk        (sys_clk        ),
 | 
			
		||||
    .sys_rst_n      (rstn_out       ),
 | 
			
		||||
    .pix_data       (pix_data       ),
 | 
			
		||||
    .pix_x          (pix_x          ),
 | 
			
		||||
    .pix_y          (pix_y          ),
 | 
			
		||||
    .hsync          (hs_out         ),
 | 
			
		||||
    .vsync          (vs_out         ),
 | 
			
		||||
    .rgb_valid      (de_out         ),
 | 
			
		||||
    .rgb            (rgb565         )
 | 
			
		||||
  );
 | 
			
		||||
//彩条数据生成模块
 | 
			
		||||
vga_pic  vga_pic_inst (
 | 
			
		||||
    .vga_clk        (sys_clk        ),
 | 
			
		||||
    .sys_rst_n      (rstn_out       ),
 | 
			
		||||
    .pix_x          (pix_x          ),
 | 
			
		||||
    .pix_y          (pix_y          ),
 | 
			
		||||
    .pix_data_out   (pix_data       )
 | 
			
		||||
  );
 | 
			
		||||
//RGB565转RGB888
 | 
			
		||||
    assign pixclk_out   =  sys_clk    ;//直接使用27M时钟,与25.175相差不大
 | 
			
		||||
    assign r_out = {rgb565[15:11],3'b0};
 | 
			
		||||
    assign g_out = {rgb565[10: 5],2'b0};
 | 
			
		||||
    assign b_out = {rgb565[ 4: 0],3'b0};
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 6.3.4仿真验证
 | 
			
		||||
 | 
			
		||||
由于仿真不需要对MS7210芯片进行初始化,所以我们在top文件中加入条件编译指令,并且在仿真文件中定义SIM宏,那么就可以在仿真中不编译ms7210初始化相关代码,只对vga时序进行仿真。我们只需要提供时钟和复位,即可对模块进行仿真。仿真文件如下所示:
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
`timescale 1ns / 1ns
 | 
			
		||||
`define SIM
 | 
			
		||||
module hdmi_top_tb;
 | 
			
		||||
 | 
			
		||||
  // Parameters
 | 
			
		||||
 | 
			
		||||
  //Ports
 | 
			
		||||
  reg  sys_clk;
 | 
			
		||||
  reg rstn_in;
 | 
			
		||||
  wire rstn_out;
 | 
			
		||||
  wire hd_scl;
 | 
			
		||||
  wire hd_sda;
 | 
			
		||||
  wire led_int;
 | 
			
		||||
  wire pixclk_out;
 | 
			
		||||
  wire  vs_out;
 | 
			
		||||
  wire  hs_out;
 | 
			
		||||
  wire  de_out;
 | 
			
		||||
  wire [7:0] r_out;
 | 
			
		||||
  wire [7:0] g_out;
 | 
			
		||||
  wire [7:0] b_out;
 | 
			
		||||
 | 
			
		||||
  initial begin
 | 
			
		||||
    sys_clk = 0;
 | 
			
		||||
    rstn_in = 0;
 | 
			
		||||
    #100
 | 
			
		||||
    rstn_in = 1;
 | 
			
		||||
  end
 | 
			
		||||
  always #(500/27) sys_clk = ~sys_clk;
 | 
			
		||||
  hdmi_top  hdmi_top_inst (
 | 
			
		||||
    .sys_clk(sys_clk),
 | 
			
		||||
    .rstn_in(rstn_in),
 | 
			
		||||
    .rstn_out(rstn_out),
 | 
			
		||||
    .hd_scl(hd_scl),
 | 
			
		||||
    .hd_sda(hd_sda),
 | 
			
		||||
    .led_int(led_int),
 | 
			
		||||
    .pixclk_out(pixclk_out),
 | 
			
		||||
    .vs_out(vs_out),
 | 
			
		||||
    .hs_out(hs_out),
 | 
			
		||||
    .de_out(de_out),
 | 
			
		||||
    .r_out(r_out),
 | 
			
		||||
    .g_out(g_out),
 | 
			
		||||
    .b_out(b_out)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
直接点击sim文件夹下hebav文件夹中的do.bat文件即可利用ModuleSim对模块进行仿真,仿真波形如下:
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/10.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图10.仿真波形(一)	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
从上图我们可以发现vsync信号拉高了两个行同步信号的长度,与设计相符
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/11.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图11.仿真波形(二)	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/12.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图12.仿真波形(三)	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
从图11和12中我们可以看到当cnt_h信号计数结束后会恢复0,cnt_v会加一,hsync信号会拉高96个像素时钟(0~95)cnt_h和hsync与设计相符。
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/13.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图13.仿真波形(四)	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
如图13所示,当cnt_h计数到H_SYNC + H_BACK + H_LEFT,也就是144时,rgb_valid拉高,xy轴坐标比rgb_valid提前一个时钟周期,以便pix_data准备好数据,符合设计。
 | 
			
		||||
 | 
			
		||||
<div>			<!--块级封装-->
 | 
			
		||||
    <center>	<!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/14.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>		<!--换行-->
 | 
			
		||||
    图14.仿真波形(五)	<!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
从每一行看,每一行被分成了10个部分,每部分像素数据分别对应不同颜色,符合设计要求。可以进行下一步上板验证。
 | 
			
		||||
 | 
			
		||||
### 6.3.5上板验证
 | 
			
		||||
 | 
			
		||||
仿真已经通过,可以进行上板验证,上板前要先进行管脚约束。端口与对应管脚如下表所示:
 | 
			
		||||
| 端口名称 |信号类型| 对应管脚|功能|
 | 
			
		||||
|:----:|:----:|:----:|:----:|
 | 
			
		||||
| sysclk | Input | D18 | 27M时钟 |
 | 
			
		||||
|  rstn_in   | Input | C22 | 外部输入复位 |
 | 
			
		||||
| rstn_out | Output | G25 | 输出ms7210复位 |
 | 
			
		||||
| hd_scl | Output | K22 | iic SCL信号 |
 | 
			
		||||
| hd_sda | Output | K23 | iic SDA信号 |
 | 
			
		||||
| led_int | Output | A20 | 配置完成信号 |
 | 
			
		||||
| pixclk_out | Output | G25 | 像素时钟输出 |
 | 
			
		||||
| vs_out | Output | R21 | Vsync输出 |
 | 
			
		||||
| hs_out | Output | R20 | Hsync输出 |
 | 
			
		||||
| de_out | Output | N19 | RGB_valid输出 |
 | 
			
		||||
| r_out[0] | Output | N21 | RGB888输出 |
 | 
			
		||||
| r_out[1] | Output | L23 | RGB888输出 |
 | 
			
		||||
| r_out[2] | Output | L22 | RGB888输出 |
 | 
			
		||||
| r_out[3] | Output | L25 | RGB888输出 |
 | 
			
		||||
| r_out[4] | Output | L24 | RGB888输出 |
 | 
			
		||||
| r_out[5] | Output | K26 | RGB888输出 |
 | 
			
		||||
| r_out[6] | Output | K25 | RGB888输出 |
 | 
			
		||||
| r_out[7] | Output | P16 | RGB888输出 |
 | 
			
		||||
| g_out[0] | Output | T25 | RGB888输出 |
 | 
			
		||||
| g_out[1] | Output | P25 | RGB888输出 |
 | 
			
		||||
| g_out[2] | Output | R25 | RGB888输出 |
 | 
			
		||||
| g_out[3] | Output | P24 | RGB888输出 |
 | 
			
		||||
| g_out[4] | Output | P23 | RGB888输出 |
 | 
			
		||||
| g_out[5] | Output | N24 | RGB888输出 |
 | 
			
		||||
| g_out[6] | Output | N23 | RGB888输出 |
 | 
			
		||||
| g_out[7] | Output | N22 | RGB888输出 |
 | 
			
		||||
| b_out[0] | Output | P19 | RGB888输出 |
 | 
			
		||||
| b_out[1] | Output | P21 | RGB888输出 |
 | 
			
		||||
| b_out[2] | Output | P20 | RGB888输出 |
 | 
			
		||||
| b_out[3] | Output | M22 | RGB888输出 |
 | 
			
		||||
| b_out[4] | Output | M21 | RGB888输出 |
 | 
			
		||||
| b_out[5] | Output | N18 | RGB888输出 |
 | 
			
		||||
| b_out[6] | Output | R22 | RGB888输出 |
 | 
			
		||||
| b_out[7] | Output | T22 | RGB888输出 |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
管脚分配可以直接编写.fdc文件,也可以使用PDS内置的工具进行分配。完成管脚分配之后就可以生成sbit文件,将文件提交到网站后点击烧录,即可将sbit下载到实验板中,在摄像头页面即可观察到显示屏中显示出彩条。
 | 
			
		||||
 | 
			
		||||
## 6.4 章末总结
 | 
			
		||||
 | 
			
		||||
本次实验主要学习VGA时序的相关知识,并使用HD硬核进行HDMI显示,感兴趣的同学可以尝试使用HDMI显示其他图像。
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/10.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 176 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/11.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 620 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/12.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 614 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/13.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 942 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/14.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 543 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 745 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/3.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 18 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/5.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/6.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 118 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 203 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.0 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/06/images/9.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 MiB  | 
@@ -42,7 +42,7 @@ FSM 通常包含以下几个组成部分:
 | 
			
		||||
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="images/1.png"
 | 
			
		||||
    <img src="./images/1.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:100%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										736
									
								
								public/doc/12/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/1.jfif
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 7.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/3.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/4.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 653 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/5.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/6.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/12/images/8.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 MiB  | 
							
								
								
									
										350
									
								
								public/doc/13/doc.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,350 @@
 | 
			
		||||
# 进阶-3-频率计
 | 
			
		||||
 | 
			
		||||
## 3.1 章节导读
 | 
			
		||||
 | 
			
		||||
本实验将基于实验平台设计并实现一个简易频率计,用于测量输入信号的频率值,并通过数码管进行实时显示。实验核心是掌握ADC模块的使用方法,被测信号频率的获取方法及其在数字系统中的处理流程。
 | 
			
		||||
 | 
			
		||||
## 3.2 理论学习
 | 
			
		||||
 | 
			
		||||
### 3.2.1 ADC模块
 | 
			
		||||
 | 
			
		||||
实验平台有一块8bit高速ADDA模块,其中ADC模块使用AD9280芯片,支持最高32MSPS的速率,模拟电压输入范围为-5~+5V,ADC模块可以根据输入电压的大小将其转换为0~255(2的8次方)的数值。模块有一个clk管脚和8个data管脚,data的输入速率和驱动时钟有关,给clk管脚的驱动时钟越快,采样率越高,data的输入速率越高。
 | 
			
		||||
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/1.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:30%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
    图1.ADDA模块示意图   <!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
### 3.2.2 数码管模块
 | 
			
		||||
 | 
			
		||||
数码管模块在前面基础实验3已经介绍过,这里不再赘述。
 | 
			
		||||
 | 
			
		||||
## 3.3 实战演练
 | 
			
		||||
 | 
			
		||||
### 3.3.1实验目标
 | 
			
		||||
 | 
			
		||||
能够驱动板载ADC模块,对ADC模块的输入数据进行测试,计算输入信号的频率值,并在数码管模块中显示。
 | 
			
		||||
 | 
			
		||||
### 3.3.2硬件资源
 | 
			
		||||
 | 
			
		||||
实验所需的信号源来自我们的实验平台,实验平台集成一个以FPGA为基础的dds信号发生器,该dds信号发生器可以输出频率可调的方波,正弦波,三角波,锯齿波等,用户可以在web平台使用并且改变输出波形和频率。
 | 
			
		||||
 | 
			
		||||
<div>           <!--块级封装-->
 | 
			
		||||
    <center>    <!--将图片和文字居中-->
 | 
			
		||||
    <img src="./images/2.png"
 | 
			
		||||
         alt="无法显示图片时显示的文字"
 | 
			
		||||
         style="zoom:30%"/>
 | 
			
		||||
    <br>        <!--换行-->
 | 
			
		||||
    图2.ADDA实验示意图   <!--标题-->
 | 
			
		||||
    </center>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
用户接收到信号源之后需自行设计逻辑处理数据并显示。
 | 
			
		||||
 | 
			
		||||
### 3.3.3程序设计
 | 
			
		||||
 | 
			
		||||
#### pulse_gen.v
 | 
			
		||||
 | 
			
		||||
首先用户接收到的是8bit波形数据,要直接利用波形数据计算频率不是很方便,计算频率数据,我们只需要计算其脉冲的个数即可,所以我们设计一个模块,通过设计一个脉冲阈值trig_level,高于阈值的就计算为一次脉冲,输出一个周期的高电平方便后续模块计数,模块代码如下:
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
module pulse_gen(
 | 
			
		||||
    input           rstn,      //系统复位,低电平有效
 | 
			
		||||
    
 | 
			
		||||
    input  [7:0]    trig_level,
 | 
			
		||||
    input           ad_clk,     //AD9280驱动时钟
 | 
			
		||||
    input  [7:0]    ad_data,    //AD输入数据
 | 
			
		||||
    
 | 
			
		||||
    output          ad_pulse    //输出的脉冲信号
 | 
			
		||||
);
 | 
			
		||||
//因为可能会有抖动,设置一个范围值避免反复触发
 | 
			
		||||
parameter THR_DATA = 3;
 | 
			
		||||
 | 
			
		||||
//reg define
 | 
			
		||||
reg          pulse;
 | 
			
		||||
reg          pulse_delay;
 | 
			
		||||
 | 
			
		||||
//*****************************************************
 | 
			
		||||
//**                    main code
 | 
			
		||||
//*****************************************************
 | 
			
		||||
 | 
			
		||||
assign ad_pulse = pulse & pulse_delay;
 | 
			
		||||
 | 
			
		||||
//根据触发电平,将输入的AD采样值转换成高低电平
 | 
			
		||||
always @ (posedge ad_clk or negedge rstn)begin
 | 
			
		||||
    if(!rstn)
 | 
			
		||||
        pulse <= 1'b0;
 | 
			
		||||
    else begin
 | 
			
		||||
        if((trig_level >= THR_DATA) && (ad_data < trig_level - THR_DATA))
 | 
			
		||||
            pulse <= 1'b0;
 | 
			
		||||
        else if(ad_data > trig_level + THR_DATA)
 | 
			
		||||
            pulse <= 1'b1;
 | 
			
		||||
    end    
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
//延时一个时钟周期,用于消除抖动
 | 
			
		||||
always @ (posedge ad_clk or negedge rstn)begin
 | 
			
		||||
    if(!rstn)
 | 
			
		||||
        pulse_delay <= 1'b0;
 | 
			
		||||
    else
 | 
			
		||||
        pulse_delay <= pulse;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
endmodule 
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### cymometer.v
 | 
			
		||||
 | 
			
		||||
下面根据pulse_gen信号生成的脉冲数据进行计数,计算其频率。我们这里采用门控时钟法,用 `clk_fs`(参考时钟)作为时间基准,测量 `clk_fx`(被测信号)的频率。
 | 
			
		||||
 | 
			
		||||
门控时钟法的原理很简单,也就是在一个**固定时间窗内**(即门控时间 `GATE_TIME`),**计数被测时钟 clk_fx 的上升沿次数**,再结合参考时钟 `clk_fs` 的计数值,就可以算出频率:
 | 
			
		||||
$$
 | 
			
		||||
\text{频率} = \frac{\text{被测脉冲数量}}{\text{门控时间(秒)}} = \frac{fx\_cnt}{fs\_cnt / \text{CLK\_FS}} = \frac{\text{CLK\_FS} \times fx\_cnt}{fs\_cnt}
 | 
			
		||||
$$
 | 
			
		||||
 | 
			
		||||
| 步骤 | 描述                                                         |
 | 
			
		||||
| ---- | ------------------------------------------------------------ |
 | 
			
		||||
| **1**    | 使用 `clk_fx` 作为计数时钟,控制一个门控时间 `gate` 信号     |
 | 
			
		||||
|  **2**     | 当 `gate` 为高电平时,`fx_cnt_temp` 开始统计 `clk_fx` 的脉冲个数 |
 | 
			
		||||
|  **3**      | 同时将 `gate` 同步到参考时钟 `clk_fs`,并计数 `fs_cnt_temp`,记录 `gate` 高电平持续期间 `clk_fs` 的个数 |
 | 
			
		||||
|  **4**     | 一旦 `gate` 下降沿到来(通过打拍检测),将计数值冻结到 `fx_cnt` 和 `fs_cnt` 中 |
 | 
			
		||||
|  **5**     | 最后用上述表达式计算频率输出。                          |
 | 
			
		||||
 | 
			
		||||
代码设计如下:
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
module cymometer
 | 
			
		||||
   #(parameter    CLK_FS = 26'd50_000_000) // 基准时钟频率值
 | 
			
		||||
    (   //system clock
 | 
			
		||||
        input                 clk_fs ,     // 基准时钟信号
 | 
			
		||||
        input                 rstn  ,     // 复位信号
 | 
			
		||||
 | 
			
		||||
        //cymometer interface
 | 
			
		||||
        input                 clk_fx ,     // 被测时钟信号
 | 
			
		||||
        output   reg [19:0]   data_fx      // 被测时钟频率输出
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
//parameter define
 | 
			
		||||
localparam   MAX       =  30;              // 定义fs_cnt、fx_cnt的最大位宽
 | 
			
		||||
localparam   GATE_TIME = 16'd2_000;        // 门控时间设置
 | 
			
		||||
 | 
			
		||||
//reg define
 | 
			
		||||
reg                gate        ;           // 门控信号
 | 
			
		||||
reg                gate_fs     ;           // 同步到基准时钟的门控信号
 | 
			
		||||
reg                gate_fs_r   ;           // 用于同步gate信号的寄存器
 | 
			
		||||
reg                gate_fs_d0  ;           // 用于采集基准时钟下gate下降沿
 | 
			
		||||
reg                gate_fs_d1  ;           // 
 | 
			
		||||
reg                gate_fx_d0  ;           // 用于采集被测时钟下gate下降沿
 | 
			
		||||
reg                gate_fx_d1  ;           // 
 | 
			
		||||
reg    [   58:0]   data_fx_t    ;          // 
 | 
			
		||||
reg    [   15:0]   gate_cnt    ;           // 门控计数
 | 
			
		||||
reg    [MAX-1:0]   fs_cnt      ;           // 门控时间内基准时钟的计数值
 | 
			
		||||
reg    [MAX-1:0]   fs_cnt_temp ;           // fs_cnt 临时值
 | 
			
		||||
reg    [MAX-1:0]   fx_cnt      ;           // 门控时间内被测时钟的计数值
 | 
			
		||||
reg    [MAX-1:0]   fx_cnt_temp ;           // fx_cnt 临时值
 | 
			
		||||
 | 
			
		||||
//wire define
 | 
			
		||||
wire               neg_gate_fs;            // 基准时钟下门控信号下降沿
 | 
			
		||||
wire               neg_gate_fx;            // 被测时钟下门控信号下降沿
 | 
			
		||||
 | 
			
		||||
//*****************************************************
 | 
			
		||||
//**                    main code
 | 
			
		||||
//*****************************************************
 | 
			
		||||
 | 
			
		||||
//边沿检测,捕获信号下降沿
 | 
			
		||||
assign neg_gate_fs = gate_fs_d1 & (~gate_fs_d0);
 | 
			
		||||
assign neg_gate_fx = gate_fx_d1 & (~gate_fx_d0);
 | 
			
		||||
 | 
			
		||||
//门控信号计数器,使用被测时钟计数
 | 
			
		||||
always @(posedge clk_fx or negedge rstn) begin
 | 
			
		||||
    if(!rstn)
 | 
			
		||||
        gate_cnt <= 16'd0; 
 | 
			
		||||
    else if(gate_cnt == GATE_TIME + 5'd20)
 | 
			
		||||
        gate_cnt <= 16'd0;
 | 
			
		||||
    else
 | 
			
		||||
        gate_cnt <= gate_cnt + 1'b1;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
//门控信号,拉高时间为GATE_TIME个实测时钟周期
 | 
			
		||||
always @(posedge clk_fx or negedge rstn) begin
 | 
			
		||||
    if(!rstn)
 | 
			
		||||
        gate <= 1'b0;
 | 
			
		||||
    else if(gate_cnt < 4'd10)
 | 
			
		||||
        gate <= 1'b0;     
 | 
			
		||||
    else if(gate_cnt < GATE_TIME + 4'd10)
 | 
			
		||||
        gate <= 1'b1;
 | 
			
		||||
    else if(gate_cnt <= GATE_TIME + 5'd20)
 | 
			
		||||
        gate <= 1'b0;
 | 
			
		||||
    else 
 | 
			
		||||
        gate <= 1'b0;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
//将门控信号同步到基准时钟下
 | 
			
		||||
always @(posedge clk_fs or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        gate_fs_r <= 1'b0;
 | 
			
		||||
        gate_fs   <= 1'b0;
 | 
			
		||||
    end
 | 
			
		||||
    else begin
 | 
			
		||||
        gate_fs_r <= gate;
 | 
			
		||||
        gate_fs   <= gate_fs_r;
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
//打拍采门控信号的下降沿(被测时钟下)
 | 
			
		||||
always @(posedge clk_fx or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        gate_fx_d0 <= 1'b0;
 | 
			
		||||
        gate_fx_d1 <= 1'b0;
 | 
			
		||||
    end
 | 
			
		||||
    else begin
 | 
			
		||||
        gate_fx_d0 <= gate;
 | 
			
		||||
        gate_fx_d1 <= gate_fx_d0;
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
//打拍采门控信号的下降沿(基准时钟下)
 | 
			
		||||
always @(posedge clk_fs or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        gate_fs_d0 <= 1'b0;
 | 
			
		||||
        gate_fs_d1 <= 1'b0;
 | 
			
		||||
    end
 | 
			
		||||
    else begin
 | 
			
		||||
        gate_fs_d0 <= gate_fs;
 | 
			
		||||
        gate_fs_d1 <= gate_fs_d0;
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 //门控时间内对被测时钟计数
 | 
			
		||||
always @(posedge clk_fx or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        fx_cnt_temp <= 32'd0;
 | 
			
		||||
        fx_cnt <= 32'd0;
 | 
			
		||||
    end
 | 
			
		||||
    else if(gate)
 | 
			
		||||
        fx_cnt_temp <= fx_cnt_temp + 1'b1;
 | 
			
		||||
    else if(neg_gate_fx) begin
 | 
			
		||||
        fx_cnt_temp <= 32'd0;
 | 
			
		||||
        fx_cnt   <= fx_cnt_temp;
 | 
			
		||||
    end
 | 
			
		||||
end 
 | 
			
		||||
 | 
			
		||||
//门控时间内对基准时钟计数
 | 
			
		||||
always @(posedge clk_fs or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        fs_cnt_temp <= 32'd0;
 | 
			
		||||
        fs_cnt <= 32'd0;
 | 
			
		||||
    end
 | 
			
		||||
    else if(gate_fs)
 | 
			
		||||
        fs_cnt_temp <= fs_cnt_temp + 1'b1;
 | 
			
		||||
    else if(neg_gate_fs) begin
 | 
			
		||||
        fs_cnt_temp <= 32'd0;
 | 
			
		||||
        fs_cnt <= fs_cnt_temp;
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
//计算被测信号频率
 | 
			
		||||
always @(posedge clk_fs or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        data_fx_t <= 1'b0;
 | 
			
		||||
    end
 | 
			
		||||
    else if(gate_fs == 1'b0)
 | 
			
		||||
        data_fx_t <= CLK_FS * fx_cnt ;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
always @(posedge clk_fs or negedge rstn) begin
 | 
			
		||||
    if(!rstn) begin
 | 
			
		||||
        data_fx <= 20'd0;
 | 
			
		||||
    end
 | 
			
		||||
    else if(gate_fs == 1'b0)
 | 
			
		||||
        data_fx <= data_fx_t / fs_cnt ;
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
endmodule 
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### frequency_meter.v
 | 
			
		||||
 | 
			
		||||
由于之前基础实验设计过数码管显示模块,本次实验不在赘述,但因为数码管模块是输入ascii码进行显示的,而现在输出频率数据是一个20bit的二进制数,所以我们应该先想办法将二进制转成ascii码再连接数码管模块进行显示。BCD转ascii码通过查表的方式即可完成。但二进制转BCD码的算法不是特别简单,之后会在基础实验部分讲解。
 | 
			
		||||
 | 
			
		||||
顶层模块代码如下:
 | 
			
		||||
 | 
			
		||||
```verilog
 | 
			
		||||
module frequency_meter(
 | 
			
		||||
    input         clk,
 | 
			
		||||
    input         rstn,       // 复位信号
 | 
			
		||||
    output        ad_clk,     // AD时钟
 | 
			
		||||
    input  [7:0]  ad_data,    // AD输入数据
 | 
			
		||||
    output [7:0]  led_display_seg,
 | 
			
		||||
    output wire [7:0] led_display_sel
 | 
			
		||||
);
 | 
			
		||||
wire ad_pulse;
 | 
			
		||||
wire [19:0] data_fx;
 | 
			
		||||
wire [25:0] bcd;
 | 
			
		||||
wire [31:0] data_bcd;
 | 
			
		||||
wire [63:0] asciidata;
 | 
			
		||||
assign data_bcd = {6'b00,bcd};
 | 
			
		||||
//生成ad驱动时钟,由于使用杜邦线连接,ad_clk不要超过10M
 | 
			
		||||
PLL PLLinst(
 | 
			
		||||
    .clkout0(ad_clk),    // output 10M
 | 
			
		||||
    .lock(),
 | 
			
		||||
    .clkin1(clk)       // input
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
pulse_gen  pulse_gen_inst (
 | 
			
		||||
    .rstn(rstn),
 | 
			
		||||
    .trig_level(8'd128),
 | 
			
		||||
    .ad_clk(ad_clk),
 | 
			
		||||
    .ad_data(ad_data),
 | 
			
		||||
    .ad_pulse(ad_pulse)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
cymometer # (
 | 
			
		||||
    .CLK_FS(32'd27_000_000)
 | 
			
		||||
  )
 | 
			
		||||
  cymometer_inst (
 | 
			
		||||
    .clk_fs(clk),
 | 
			
		||||
    .rstn(rstn),
 | 
			
		||||
    .clk_fx(ad_pulse),
 | 
			
		||||
    .data_fx(data_fx)
 | 
			
		||||
  );
 | 
			
		||||
//二进制转bcd码模块
 | 
			
		||||
bin2bcd # (
 | 
			
		||||
    .W(20)
 | 
			
		||||
  )
 | 
			
		||||
  bin2bcd_inst (
 | 
			
		||||
    .bin(data_fx),
 | 
			
		||||
    .bcd(bcd)
 | 
			
		||||
  );
 | 
			
		||||
//4位BCD码转ascii模块,例化8次使8个bcd同时输出ascii
 | 
			
		||||
genvar i;
 | 
			
		||||
generate
 | 
			
		||||
  for (i = 0; i < 8; i = i + 1) begin : generate_module
 | 
			
		||||
    bcd2ascii  bcd2ascii_inst (
 | 
			
		||||
        .bcd(data_bcd[i*4 +:4]),
 | 
			
		||||
        .asciidata(asciidata[i*8 +: 8])
 | 
			
		||||
      );
 | 
			
		||||
  end
 | 
			
		||||
endgenerate
 | 
			
		||||
//数码管显示模块
 | 
			
		||||
led_display_driver  led_display_driver_inst (
 | 
			
		||||
    .clk(clk),
 | 
			
		||||
    .rstn(rstn),
 | 
			
		||||
    .assic_seg(asciidata),
 | 
			
		||||
    .seg_point(8'b00000000),
 | 
			
		||||
    .led_display_seg(led_display_seg),
 | 
			
		||||
    .led_display_sel(led_display_sel)
 | 
			
		||||
  );
 | 
			
		||||
endmodule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 3.3.4仿真验证
 | 
			
		||||
 | 
			
		||||
### 3.3.5上板验证
 | 
			
		||||
 | 
			
		||||
## 3.4 章末总结
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								public/doc/13/images/1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 57 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/13/images/1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 88 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/doc/13/images/2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 149 KiB  | 
@@ -104,7 +104,10 @@ onMounted(async () => {
 | 
			
		||||
    
 | 
			
		||||
    // 如果API调用失败或返回空列表,使用默认值
 | 
			
		||||
    if (tutorialIds.length === 0) {
 | 
			
		||||
      tutorialIds = ['01', '02', '11']; // 默认例程
 | 
			
		||||
      console.log('使用默认教程列表');
 | 
			
		||||
      tutorialIds = ['01', '02', '03', '04', '05', '06', '11', '12', '13']; // 默认例程
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log('使用API获取的教程列表:', tutorialIds);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // 为每个例程创建对象并尝试获取文档标题
 | 
			
		||||
 
 | 
			
		||||
@@ -6,52 +6,17 @@ import ProjectView from '../views/ProjectView.vue'
 | 
			
		||||
import TestView from '../views/TestView.vue'
 | 
			
		||||
import UserView from '../views/UserView.vue'
 | 
			
		||||
import AdminView from '../views/AdminView.vue'
 | 
			
		||||
import MarkdownTestView from '../views/MarkdownTestView.vue'
 | 
			
		||||
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
  history: createWebHistory(import.meta.env.BASE_URL),
 | 
			
		||||
  routes: [
 | 
			
		||||
    {
 | 
			
		||||
      path: '/',
 | 
			
		||||
      name: 'home',
 | 
			
		||||
      component: HomeView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/login',
 | 
			
		||||
      name: 'login',
 | 
			
		||||
      component: LoginView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/lab/:id',
 | 
			
		||||
      name: 'lab',
 | 
			
		||||
      component: LabView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/project',
 | 
			
		||||
      name: 'project',
 | 
			
		||||
      component: ProjectView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/test',
 | 
			
		||||
      name: 'test',
 | 
			
		||||
      component: TestView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/markdown-test',
 | 
			
		||||
      name: 'markdown-test',
 | 
			
		||||
      component: MarkdownTestView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/user',
 | 
			
		||||
      name: 'user',
 | 
			
		||||
      component: UserView
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/admin',
 | 
			
		||||
      name: 'admin',
 | 
			
		||||
      component: AdminView
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
    {path: '/',       name: 'home',   component: HomeView},
 | 
			
		||||
    {path: '/login',  name: 'login',  component: LoginView},
 | 
			
		||||
    {path: '/lab/:id',name: 'lab',    component: LabView},
 | 
			
		||||
    {path: '/project',name: 'project',component: ProjectView},
 | 
			
		||||
    {path: '/test',   name: 'test',   component: TestView},
 | 
			
		||||
    {path: '/user',   name: 'user',   component: UserView},
 | 
			
		||||
    {path: '/admin',  name: 'admin',  component: AdminView}]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default router
 | 
			
		||||
 
 | 
			
		||||
@@ -1,445 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="p-8 max-w-5xl mx-auto">
 | 
			
		||||
    <h1 class="text-2xl font-bold mb-6">Markdown 渲染器语法高亮测试</h1>
 | 
			
		||||
    
 | 
			
		||||
    <div class="grid grid-cols-1 gap-8">
 | 
			
		||||
      <!-- 测试控制面板 -->
 | 
			
		||||
      <div class="bg-base-200 p-4 rounded-lg">
 | 
			
		||||
        <div class="flex gap-4 mb-4">
 | 
			
		||||
          <button @click="currentTheme = 'light'" class="btn btn-primary" :class="{'btn-outline': currentTheme !== 'light'}">
 | 
			
		||||
            亮色主题
 | 
			
		||||
          </button>
 | 
			
		||||
          <button @click="currentTheme = 'dark'" class="btn btn-primary" :class="{'btn-outline': currentTheme !== 'dark'}">
 | 
			
		||||
            暗色主题
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
      <!-- 示例展示 -->
 | 
			
		||||
      <div class="bg-base-100 rounded-lg shadow-lg overflow-hidden" :data-theme="currentTheme">
 | 
			
		||||
        <div class="p-4 bg-base-200 font-semibold">Markdown 渲染结果</div>
 | 
			
		||||
        <div class="px-1">
 | 
			
		||||
          <MarkdownRenderer :content="sampleContent" :remove-first-h1="false" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from 'vue';
 | 
			
		||||
import MarkdownRenderer from '@/components/MarkdownRenderer.vue';
 | 
			
		||||
 | 
			
		||||
const currentTheme = ref('light');
 | 
			
		||||
 | 
			
		||||
// 包含各种代码示例的 Markdown 示例内容
 | 
			
		||||
const sampleContent = ref(`
 | 
			
		||||
# Markdown 语法高亮测试
 | 
			
		||||
 | 
			
		||||
这是一个用于测试 Markdown 渲染器语法高亮功能的页面。下面是一些代码示例。
 | 
			
		||||
 | 
			
		||||
## JavaScript 代码示例
 | 
			
		||||
 | 
			
		||||
\`\`\`javascript
 | 
			
		||||
// 一个简单的 JavaScript 函数
 | 
			
		||||
function calculateSum(a, b) {
 | 
			
		||||
  // 这是一个注释
 | 
			
		||||
  const sum = a + b;
 | 
			
		||||
  console.log(\`计算结果: \${sum}\`);
 | 
			
		||||
  return sum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 类示例
 | 
			
		||||
class Person {
 | 
			
		||||
  constructor(name, age) {
 | 
			
		||||
    this.name = name;
 | 
			
		||||
    this.age = age;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  greet() {
 | 
			
		||||
    return \`你好,我是 \${this.name},我 \${this.age} 岁了。\`;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 使用 async/await
 | 
			
		||||
async function fetchData() {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await fetch('https://api.example.com/data');
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    return data;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('获取数据出错:', error);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## TypeScript 代码示例
 | 
			
		||||
 | 
			
		||||
\`\`\`typescript
 | 
			
		||||
// TypeScript 接口
 | 
			
		||||
interface User {
 | 
			
		||||
  id: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
  email: string;
 | 
			
		||||
  age?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 泛型函数
 | 
			
		||||
function getFirst<T>(array: T[]): T | undefined {
 | 
			
		||||
  return array.length > 0 ? array[0] : undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 类型别名和联合类型
 | 
			
		||||
type Result<T> = 
 | 
			
		||||
  | { success: true; value: T }
 | 
			
		||||
  | { success: false; error: Error };
 | 
			
		||||
 | 
			
		||||
// 装饰器
 | 
			
		||||
function log(target: any, key: string) {
 | 
			
		||||
  const originalMethod = target[key];
 | 
			
		||||
  
 | 
			
		||||
  target[key] = function(...args: any[]) {
 | 
			
		||||
    console.log(\`调用方法 \${key} 参数:, args);
 | 
			
		||||
    return originalMethod.apply(this, args);
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  return target;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Calculator {
 | 
			
		||||
  @log
 | 
			
		||||
  add(a: number, b: number): number {
 | 
			
		||||
    return a + b;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## HTML & CSS 示例
 | 
			
		||||
 | 
			
		||||
\`\`\`html
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="zh-CN">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="UTF-8">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
  <title>示例页面</title>
 | 
			
		||||
  <style>
 | 
			
		||||
    .container {
 | 
			
		||||
      max-width: 1200px;
 | 
			
		||||
      margin: 0 auto;
 | 
			
		||||
      padding: 20px;
 | 
			
		||||
      font-family: Arial, sans-serif;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .card {
 | 
			
		||||
      border-radius: 8px;
 | 
			
		||||
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .card-header {
 | 
			
		||||
      background-color: #4a6cf7;
 | 
			
		||||
      color: white;
 | 
			
		||||
      padding: 15px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .card-body {
 | 
			
		||||
      padding: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <div class="card">
 | 
			
		||||
      <div class="card-header">
 | 
			
		||||
        <h2>欢迎使用我们的应用</h2>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="card-body">
 | 
			
		||||
        <p>这是卡片内容区域。</p>
 | 
			
		||||
        <button onclick="alert('你点击了按钮!')">点击我</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## C# 代码示例
 | 
			
		||||
 | 
			
		||||
\`\`\`csharp
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Demo
 | 
			
		||||
{
 | 
			
		||||
    // 简单的用户类
 | 
			
		||||
    public class User
 | 
			
		||||
    {
 | 
			
		||||
        public int Id { get; set; }
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public string Email { get; set; }
 | 
			
		||||
        
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
        {
 | 
			
		||||
            return $"User(Id={Id}, Name={Name}, Email={Email})";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public class Program
 | 
			
		||||
    {
 | 
			
		||||
        public static async Task<List<User>> GetUsersAsync()
 | 
			
		||||
        {
 | 
			
		||||
            // 模拟异步操作
 | 
			
		||||
            await Task.Delay(1000);
 | 
			
		||||
            
 | 
			
		||||
            return new List<User>
 | 
			
		||||
            {
 | 
			
		||||
                new User { Id = 1, Name = "张三", Email = "zhangsan@example.com" },
 | 
			
		||||
                new User { Id = 2, Name = "李四", Email = "lisi@example.com" },
 | 
			
		||||
                new User { Id = 3, Name = "王五", Email = "wangwu@example.com" }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public static void Main(string[] args)
 | 
			
		||||
        {
 | 
			
		||||
            Console.WriteLine("获取用户列表中...");
 | 
			
		||||
            
 | 
			
		||||
            var users = GetUsersAsync().GetAwaiter().GetResult();
 | 
			
		||||
            
 | 
			
		||||
            foreach (var user in users)
 | 
			
		||||
            {
 | 
			
		||||
                Console.WriteLine(user);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Console.WriteLine("完成!");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## Python 代码示例
 | 
			
		||||
 | 
			
		||||
\`\`\`python
 | 
			
		||||
import os
 | 
			
		||||
import json
 | 
			
		||||
from typing import List, Dict, Any, Optional
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class Person:
 | 
			
		||||
    name: str
 | 
			
		||||
    age: int
 | 
			
		||||
    email: Optional[str] = None
 | 
			
		||||
    
 | 
			
		||||
    def greet(self) -> str:
 | 
			
		||||
        return f"你好,我是 {self.name},我 {self.age} 岁了。"
 | 
			
		||||
    
 | 
			
		||||
    def to_dict(self) -> Dict[str, Any]:
 | 
			
		||||
        return {
 | 
			
		||||
            "name": self.name,
 | 
			
		||||
            "age": self.age,
 | 
			
		||||
            "email": self.email
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# 一些常用的 Python 函数
 | 
			
		||||
def read_json_file(file_path: str) -> Dict[str, Any]:
 | 
			
		||||
    """从JSON文件读取数据
 | 
			
		||||
    
 | 
			
		||||
    Args:
 | 
			
		||||
        file_path: JSON文件路径
 | 
			
		||||
        
 | 
			
		||||
    Returns:
 | 
			
		||||
        解析后的JSON数据
 | 
			
		||||
        
 | 
			
		||||
    Raises:
 | 
			
		||||
        FileNotFoundError: 如果文件不存在
 | 
			
		||||
        json.JSONDecodeError: 如果JSON格式不正确
 | 
			
		||||
    """
 | 
			
		||||
    if not os.path.exists(file_path):
 | 
			
		||||
        raise FileNotFoundError(f"文件不存在: {file_path}")
 | 
			
		||||
    
 | 
			
		||||
    with open(file_path, 'r', encoding='utf-8') as f:
 | 
			
		||||
        return json.load(f)
 | 
			
		||||
 | 
			
		||||
# 列表推导式与生成器
 | 
			
		||||
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 | 
			
		||||
even_numbers = [n for n in numbers if n % 2 == 0]
 | 
			
		||||
squared = (n**2 for n in even_numbers)  # 生成器表达式
 | 
			
		||||
 | 
			
		||||
# 使用装饰器
 | 
			
		||||
def log_function_call(func):
 | 
			
		||||
    def wrapper(*args, **kwargs):
 | 
			
		||||
        print(f"调用函数: {func.__name__}")
 | 
			
		||||
        result = func(*args, **kwargs)
 | 
			
		||||
        print(f"函数返回: {result}")
 | 
			
		||||
        return result
 | 
			
		||||
    return wrapper
 | 
			
		||||
 | 
			
		||||
@log_function_call
 | 
			
		||||
def add(a: int, b: int) -> int:
 | 
			
		||||
    return a + b
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    person = Person("张三", 30, "zhangsan@example.com")
 | 
			
		||||
    print(person.greet())
 | 
			
		||||
    print(add(5, 3))
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## VHDL 代码示例
 | 
			
		||||
 | 
			
		||||
\`\`\`vhdl
 | 
			
		||||
library IEEE;
 | 
			
		||||
use IEEE.STD_LOGIC_1164.ALL;
 | 
			
		||||
use IEEE.NUMERIC_STD.ALL;
 | 
			
		||||
 | 
			
		||||
entity Counter is
 | 
			
		||||
    Port ( 
 | 
			
		||||
        clk     : in  STD_LOGIC;
 | 
			
		||||
        reset   : in  STD_LOGIC;
 | 
			
		||||
        enable  : in  STD_LOGIC;
 | 
			
		||||
        count   : out STD_LOGIC_VECTOR(7 downto 0)
 | 
			
		||||
    );
 | 
			
		||||
end Counter;
 | 
			
		||||
 | 
			
		||||
architecture Behavioral of Counter is
 | 
			
		||||
    signal count_reg : unsigned(7 downto 0) := (others => '0');
 | 
			
		||||
begin
 | 
			
		||||
    process(clk, reset)
 | 
			
		||||
    begin
 | 
			
		||||
        if reset = '1' then
 | 
			
		||||
            count_reg <= (others => '0');
 | 
			
		||||
        elsif rising_edge(clk) then
 | 
			
		||||
            if enable = '1' then
 | 
			
		||||
                count_reg <= count_reg + 1;
 | 
			
		||||
            end if;
 | 
			
		||||
        end if;
 | 
			
		||||
    end process;
 | 
			
		||||
    
 | 
			
		||||
    count <= STD_LOGIC_VECTOR(count_reg);
 | 
			
		||||
end Behavioral;
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## Verilog 代码示例
 | 
			
		||||
 | 
			
		||||
\`\`\`verilog
 | 
			
		||||
module counter(
 | 
			
		||||
    input wire clk,
 | 
			
		||||
    input wire reset,
 | 
			
		||||
    input wire enable,
 | 
			
		||||
    output reg [7:0] count
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
    // 初始化计数器
 | 
			
		||||
    initial begin
 | 
			
		||||
        count = 8'b0;
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    // 在时钟上升沿处理
 | 
			
		||||
    always @(posedge clk or posedge reset) begin
 | 
			
		||||
        if (reset) begin
 | 
			
		||||
            count <= 8'b0;
 | 
			
		||||
        end else if (enable) begin
 | 
			
		||||
            count <= count + 1'b1;
 | 
			
		||||
        end
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
    // 显示当前计数值
 | 
			
		||||
    always @(count) begin
 | 
			
		||||
        $display("当前计数值: %d", count);
 | 
			
		||||
    end
 | 
			
		||||
endmodule
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## JSON 示例
 | 
			
		||||
 | 
			
		||||
\`\`\`json
 | 
			
		||||
{
 | 
			
		||||
  "name": "fpga-weblab",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "FPGA WebLab 项目配置",
 | 
			
		||||
  "settings": {
 | 
			
		||||
    "theme": {
 | 
			
		||||
      "light": {
 | 
			
		||||
        "primary": "#4a6cf7",
 | 
			
		||||
        "secondary": "#f79e1b",
 | 
			
		||||
        "background": "#ffffff"
 | 
			
		||||
      },
 | 
			
		||||
      "dark": {
 | 
			
		||||
        "primary": "#6d8aff",
 | 
			
		||||
        "secondary": "#ffb74d",
 | 
			
		||||
        "background": "#121212"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "features": [
 | 
			
		||||
      "代码高亮",
 | 
			
		||||
      "实时预览",
 | 
			
		||||
      "项目管理",
 | 
			
		||||
      "远程硬件访问"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "vue": "^3.5.0",
 | 
			
		||||
    "marked": "^12.0.0",
 | 
			
		||||
    "highlight.js": "^11.9.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## Shell 脚本示例
 | 
			
		||||
 | 
			
		||||
\`\`\`bash
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# 定义变量
 | 
			
		||||
PROJECT_DIR=$(pwd)
 | 
			
		||||
OUTPUT_DIR="$PROJECT_DIR/build"
 | 
			
		||||
LOG_FILE="$PROJECT_DIR/build.log"
 | 
			
		||||
 | 
			
		||||
# 创建输出目录
 | 
			
		||||
mkdir -p "$OUTPUT_DIR"
 | 
			
		||||
 | 
			
		||||
# 定义函数
 | 
			
		||||
function log_message() {
 | 
			
		||||
  local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
 | 
			
		||||
  echo "[$timestamp] $1" | tee -a "$LOG_FILE"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# 清理旧构建
 | 
			
		||||
log_message "清理旧构建文件..."
 | 
			
		||||
rm -rf "$OUTPUT_DIR/*"
 | 
			
		||||
 | 
			
		||||
# 执行构建
 | 
			
		||||
log_message "开始构建项目..."
 | 
			
		||||
npm run build
 | 
			
		||||
 | 
			
		||||
# 检查构建结果
 | 
			
		||||
if [ $? -eq 0 ]; then
 | 
			
		||||
  log_message "构建成功!输出文件位于: $OUTPUT_DIR"
 | 
			
		||||
else
 | 
			
		||||
  log_message "构建失败,请检查错误信息"
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# 统计文件数量
 | 
			
		||||
file_count=$(find "$OUTPUT_DIR" -type f | wc -l)
 | 
			
		||||
log_message "共构建了 $file_count 个文件"
 | 
			
		||||
 | 
			
		||||
# 显示环境信息
 | 
			
		||||
echo "系统信息:"
 | 
			
		||||
echo "----------------------"
 | 
			
		||||
echo "操作系统: $(uname -s)"
 | 
			
		||||
echo "Node 版本: $(node -v)"
 | 
			
		||||
echo "NPM 版本: $(npm -v)"
 | 
			
		||||
echo "磁盘空间: $(df -h | grep -E '^/dev')"
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
## 其他示例
 | 
			
		||||
 | 
			
		||||
这里是一个内联代码示例:\`const value = calculate(x, y);\`
 | 
			
		||||
 | 
			
		||||
`);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
</style>
 | 
			
		||||