diff --git a/public/doc/03/doc.md b/public/doc/03/doc.md new file mode 100644 index 0000000..33e9fb3 --- /dev/null +++ b/public/doc/03/doc.md @@ -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 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 拓展训练 + +结合流水灯实验和数码管实验:数码管显示数字,标识出当前流水到了哪一个灯 \ No newline at end of file diff --git a/public/doc/04/doc.md b/public/doc/04/doc.md new file mode 100644 index 0000000..7729710 --- /dev/null +++ b/public/doc/04/doc.md @@ -0,0 +1,212 @@ +# 基础-4-矩阵键盘实验 + +## 4.1 章节导读 +本章将介绍**矩阵键盘检测电路的设计与实现方法**,通过Verilog HDL语言完成4×4矩阵键盘的扫描识别模块,掌握**多键输入设备的行列扫描原理、消抖机制以及按键编码处理方式**。 + +矩阵键盘作为常见的人机交互接口之一,广泛应用于嵌入式系统、数字电路和微控制器项目中。与独立按键不同,矩阵键盘在节省IO资源的同时,对扫描逻辑和时序处理提出了更高的要求。实验中我们将采用**逐行扫描法**,结合状态机与延时消抖手段,确保按键信息的准确采集。 + + +## 4.2 理论学习 +### 4.2.1 矩阵键盘结构 +
+
+ 无法显示图片时显示的文字 +
+ 图1.矩阵键盘原理图 +
+
+ +实验板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 +
+ 无法显示图片时显示的文字 +
+ 图1.LED扩展板 +
+ + +通过原理图可以得知,本试验箱的LED灯为高电平时点亮。 + +
+
+ 无法显示图片时显示的文字 +
+ 图2.LED扩展板原理图 +
+
+### 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对模块进行仿真,仿真波形如下: + +
+
+ 无法显示图片时显示的文字 +
+ 图3.呼吸灯仿真波形(一) +
+
+ +
+
+ 无法显示图片时显示的文字 +
+ 图4.呼吸灯仿真波形(二) +
+
+ +通过观察波形我们发现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 同步/异步呼吸控制,实现更加炫酷的视觉效果。 \ No newline at end of file diff --git a/public/doc/05/images/1.png b/public/doc/05/images/1.png new file mode 100644 index 0000000..45094e0 Binary files /dev/null and b/public/doc/05/images/1.png differ diff --git a/public/doc/05/images/2.png b/public/doc/05/images/2.png new file mode 100644 index 0000000..118cb90 Binary files /dev/null and b/public/doc/05/images/2.png differ diff --git a/public/doc/05/images/3.png b/public/doc/05/images/3.png new file mode 100644 index 0000000..e7194a7 Binary files /dev/null and b/public/doc/05/images/3.png differ diff --git a/public/doc/05/images/4.png b/public/doc/05/images/4.png new file mode 100644 index 0000000..cff8e59 Binary files /dev/null and b/public/doc/05/images/4.png differ diff --git a/public/doc/06/doc.md b/public/doc/06/doc.md new file mode 100644 index 0000000..c4d1cb6 --- /dev/null +++ b/public/doc/06/doc.md @@ -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扫描方式见下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图1.VGA扫描顺序 +
+
+ +如上图所示,每一帧图像都是从左上角开始,逐行扫描形成,所以规定最左上角的像素点为第一个像素点,坐标是(0,0),以这个像素为起点,向右x坐标逐渐增大,向下y坐标逐渐增大,重复若干次后扫描到右下角完成一帧图像的扫描,扫描完成后进行图像消隐,随后指针跳回左上角重新进行新一帧的扫描。 + + 在扫描的过程中会对每一个像素点进行单独赋值,使每个像素点显示对应色彩信息,当扫描速度足够快,加之人眼的视觉暂留特性,我们会看到一幅完整的图片,这就是VGA 显示的原理。 + +VGA显示除了要有像素点的信息,还需要有行同步(HSync)和场同步(VSync)两个信号辅助显示。行同步信号规定了一行像素的开始与结束,场同步信号规定了一帧图像的开始与结束。在VESA DMT 1.12版本的标准文档中给出的VGA时序图如下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图2.VGA标准时序 +
+
+ +行同步时序如下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图3.行同步时序 +
+
+ +行同步的一个扫周期要经过6个部分,分别是Sync(同步)、 Back Porch(后沿)、 Left Border(左边框)、 “Addressable” Video(有效图像)、 Right Border(右边框)、 Front Porch(前沿),这些过程的长度都是以像素为单位的,也就是以像素时钟为单位,例如Sync的值为96,也就意味着Sync阶段要经历96个像素时钟。HSync信号会在Sync同步阶段拉高(不同的芯片可能有不同标准)以确定新一行的开始与上一行的结束。而完整的一行像素很多,但有效的真正能显示在屏幕上的像素只有 “Addressable” Video(有效图像)部分的像素,其他阶段的像素均无效,无法显示在屏幕中。 + +场同步时序如下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图4.场同步时序 +
+
+ +场同步时序与行同步时序相同,也是分为6个部分,在Sync同步阶段拉高,标志着一帧的结束和新一帧的开始,其中像素只有在“Addressable” Video(有效图像)阶段才有效,其他阶段均无效。而场同步信号的基本单位是行,比如Sync的值为2,也就意味着Sync同步阶段要经历两行。 + +那么我们将行同步和场同步信号结合起来,遍可以得到一帧图像的样貌,如下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图5.一帧图像组成示意图 +
+
+ +可以看到在行场同步信号构成了一个二维坐标系,原点在左上方,中间遍形成了一帧图像,而真正能显示在屏幕中的图像只有 “Addressable” Video(有效图像)部分。 + +现在我们知道了行同步和场同步都要经历6个部分,那么这些部分的长度都是如何规定的呢?VGA 行时序对行同步时间、 消隐时间、 行视频有效时间和行前肩时间有特定的规范, 场时序也是如此。 常用VGA 分辨率时序参数如下表所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图6.常用VGA分辨率时序参数 +
+
+ +### 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。 + +
+
+ 无法显示图片时显示的文字 +
+ 图7.MS7210芯片 +
+
+ +
+
+ 无法显示图片时显示的文字 +
+ 图8.MS7210功能框图 +
+
+ +MS7210芯片可以通过IIC协议对内部寄存器进行配置,有关芯片寄存器配置需要向芯片厂家进行申请。 + +## 6.3 实战演练 + +### 6.3.1实验目标 + +### 6.3.2硬件资源 + +实验板共有一个HDMI-OUT接口,由MS7210驱动,一个HDMI-IN接口,由MS7200驱动。 + +
+
+ 无法显示图片时显示的文字 +
+ 图9.板载HDMI芯片 +
+
+ + +实验箱配备一个小型HDMI显示器,该显示器HDMI接口与HDMI-OUT接口连接,图像可以显示在显示屏中,通过摄像头可以在网站观察现象 + +
+
+ 实验箱显示器 +
+ 图10.实验箱显示器 +
+
+ + +### 6.3.3程序设计 + +在设计程序时,我们先对本实验工程有一个整体认知,首先来看一下HDMI彩条显示实验的整体框图。 + +
+
+ 实验箱显示器 +
+ 图11.HDMI彩条显示整体框图 +
+
+ +可见整个实验一共由好多个模块组成,下面是各个模块简介 +| 模块名称 |功能描述| 备注 | +|:----:|:----:|:----:| +| 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对模块进行仿真,仿真波形如下: + +
+
+ 无法显示图片时显示的文字 +
+ 图10.仿真波形(一) +
+
+ +从上图我们可以发现vsync信号拉高了两个行同步信号的长度,与设计相符 + +
+
+ 无法显示图片时显示的文字 +
+ 图11.仿真波形(二) +
+
+ +
+
+ 无法显示图片时显示的文字 +
+ 图12.仿真波形(三) +
+
+ +从图11和12中我们可以看到当cnt_h信号计数结束后会恢复0,cnt_v会加一,hsync信号会拉高96个像素时钟(0~95)cnt_h和hsync与设计相符。 + +
+
+ 无法显示图片时显示的文字 +
+ 图13.仿真波形(四) +
+
+ +如图13所示,当cnt_h计数到H_SYNC + H_BACK + H_LEFT,也就是144时,rgb_valid拉高,xy轴坐标比rgb_valid提前一个时钟周期,以便pix_data准备好数据,符合设计。 + +
+
+ 无法显示图片时显示的文字 +
+ 图14.仿真波形(五) +
+
+ +从每一行看,每一行被分成了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显示其他图像。 \ No newline at end of file diff --git a/public/doc/06/images/1.png b/public/doc/06/images/1.png new file mode 100644 index 0000000..0cda126 Binary files /dev/null and b/public/doc/06/images/1.png differ diff --git a/public/doc/06/images/10.png b/public/doc/06/images/10.png new file mode 100644 index 0000000..e4b55a1 Binary files /dev/null and b/public/doc/06/images/10.png differ diff --git a/public/doc/06/images/11.png b/public/doc/06/images/11.png new file mode 100644 index 0000000..5fb828b Binary files /dev/null and b/public/doc/06/images/11.png differ diff --git a/public/doc/06/images/12.png b/public/doc/06/images/12.png new file mode 100644 index 0000000..40cfdfb Binary files /dev/null and b/public/doc/06/images/12.png differ diff --git a/public/doc/06/images/13.png b/public/doc/06/images/13.png new file mode 100644 index 0000000..598bd7d Binary files /dev/null and b/public/doc/06/images/13.png differ diff --git a/public/doc/06/images/14.png b/public/doc/06/images/14.png new file mode 100644 index 0000000..c61f2e3 Binary files /dev/null and b/public/doc/06/images/14.png differ diff --git a/public/doc/06/images/2.png b/public/doc/06/images/2.png new file mode 100644 index 0000000..f7a8809 Binary files /dev/null and b/public/doc/06/images/2.png differ diff --git a/public/doc/06/images/3.png b/public/doc/06/images/3.png new file mode 100644 index 0000000..9ead8c5 Binary files /dev/null and b/public/doc/06/images/3.png differ diff --git a/public/doc/06/images/4.png b/public/doc/06/images/4.png new file mode 100644 index 0000000..3e604bb Binary files /dev/null and b/public/doc/06/images/4.png differ diff --git a/public/doc/06/images/5.png b/public/doc/06/images/5.png new file mode 100644 index 0000000..d12f7c2 Binary files /dev/null and b/public/doc/06/images/5.png differ diff --git a/public/doc/06/images/6.png b/public/doc/06/images/6.png new file mode 100644 index 0000000..03cb1d0 Binary files /dev/null and b/public/doc/06/images/6.png differ diff --git a/public/doc/06/images/7.png b/public/doc/06/images/7.png new file mode 100644 index 0000000..28aaab0 Binary files /dev/null and b/public/doc/06/images/7.png differ diff --git a/public/doc/06/images/8.png b/public/doc/06/images/8.png new file mode 100644 index 0000000..dcabefd Binary files /dev/null and b/public/doc/06/images/8.png differ diff --git a/public/doc/06/images/9.png b/public/doc/06/images/9.png new file mode 100644 index 0000000..144fd6f Binary files /dev/null and b/public/doc/06/images/9.png differ diff --git a/public/doc/11/doc.md b/public/doc/11/doc.md index fefa7c7..cea5bfd 100644 --- a/public/doc/11/doc.md +++ b/public/doc/11/doc.md @@ -42,7 +42,7 @@ FSM 通常包含以下几个组成部分:
- 无法显示图片时显示的文字
diff --git a/public/doc/12/doc.md b/public/doc/12/doc.md new file mode 100644 index 0000000..889d17b --- /dev/null +++ b/public/doc/12/doc.md @@ -0,0 +1,736 @@ +# 2.光纤通信 + +## 2.1 章节导读 + +在现代高速通信系统中,光纤通信作为一种重要的信息传输手段,广泛应用于数据中心、城域网、广域网以及各种嵌入式高速数据传输场景中。相比传统电缆传输,光纤具有带宽高、传输距离远、抗电磁干扰能力强、保密性好等显著优势,是实现高速、高可靠性通信的关键技术。 + +随着FPGA在通信领域的深入应用,基于FPGA的光纤通信系统设计成为一项非常实用的技能。在本实验中,我们将基于开发板配套的光纤接口模块,设计并实现一个基础的光纤通信收发系统。通过本实验,同学们将掌握光纤收发器(SFP模块)的基本使用方法,了解高速串行通信,8b10b编码等概念,并学会如何通过FPGA完成数据的高速发送与接收。 + +## 2.2 理论学习 + +### 2.2.1 8B10B编码 + +8b/10b 编码常用于光纤通信和 LVDS 信号中,其主要目的是解决信号传输过程中的直流不平衡问题。由于串行电路通常采用交流耦合的方式(串接电容)我们知道理想电容的[阻抗](https://so.csdn.net/so/search?q=阻抗&spm=1001.2101.3001.7020)公式![Zc=\frac{1}{2pif*C}](https://latex.csdn.net/eq?Zc%3D%5Cfrac%7B1%7D%7B2pif*C%7D) ,通过这个公式可以知道,频率f越高,阻抗越低,反之,频率越低,阻抗越高。 + +
+
+ 无法显示图片时显示的文字 +
+ 图1.交流耦合 +
+
+ +如上图所示,当码型是高频的时候,基本可以不损耗的传输过去,但是当码型为连续的0或者1的时候,电容的损耗就很大,导致幅度不断降低,最严重的后果就是无法识别到底是0还是1,因此8b/10b编码就是为了尽量把低频的码型优化成较高频率的码型,从而降低阻抗带来的损耗。以光模块为例,它只能传输“亮”或“不亮”两种状态,即二进制的 1 和 0。当传输数据中出现长时间连续的 1 或 0(如 111111100000000)时,线路中电容的损耗就会很大,从而无法准确采样,进而造成误判。 + +同样,在 LVDS 高速传输中也存在类似问题。如果传输的 0 和 1 数量长期不均衡,线路上的基准电压会发生偏移,影响信号的正确识别。 + +因此,8b/10b 编码通过对每 8 位数据编码成 10 位信号,实现传输数据的直流平衡和足够的码流转换,也就是保证串行数据不会连续出现1和0,从而提升系统的可靠性和抗干扰能力。 + +那么8b10b编码是如何将8bit数据编码成10bit数据的呢?假设原始8位数据从高到低用HGFEDCBA表示,将8位数据分成高3位HGF和低5位EDCBA两个子组。经过5B/6B编码,将低5位EDCBA映射成abcdei;高3位经过3B/4B编码,映射成fghj,最后合成abcdeifghj发送。 + +
+
+ 无法显示图片时显示的文字 +
+ 图2.8b10b编码原理 + +
+
+ +可见8b10b编码是基于5b/6b和3b/4b之上的。 + +

5B/6B code

input

RD = −1

RD = +1

input

RD = −1

RD = +1

EDCBA

abcdei

EDCBA

abcdei

D.00

00000

100111

011000

D.16

10000

011011

100100

D.01

00001

011101

100010

D.17

10001

100011

D.02

00010

101101

010010

D.18

10010

010011

D.03

00011

110001

D.19

10011

110010

D.04

00100

110101

001010

D.20

10100

001011

D.05

00101

101001

D.21

10101

101010

D.06

00110

011001

D.22

10110

011010

D.07

00111

111000

000111

D.23 †

10111

111010

000101

D.08

01000

111001

000110

D.24

11000

110011

001100

D.09

01001

100101

D.25

11001

100110

D.10

01010

010101

D.26

11010

010110

D.11

01011

110100

D.27 †

11011

110110

001001

D.12

01100

001101

D.28

11100

001110

D.13

01101

101100

D.29 †

11101

101110

010001

D.14

01110

011100

D.30 †

11110

011110

100001

D.15

01111

010111

101000

D.31

11111

101011

010100

K.28

11100

001111

110000

+ +

3b/4b code

input

RD = −1

RD = +1

input

RD = −1

RD = +1

HGF

fghj

HGF

fghj

D.x.0

000

1011

0100

K.x.0

000

1011

0100

D.x.1

001

1001

K.x.1 ‡

001

0110

1001

D.x.2

010

0101

K.x.2 ‡

010

1010

0101

D.x.3

011

1100

0011

K.x.3

011

1100

0011

D.x.4

100

1101

0010

K.x.4

100

1101

0010

D.x.5

101

1010

K.x.5 ‡

101

0101

1010

D.x.6

110

0110

K.x.6 ‡

110

1001

0110

D.x.P7 †

111

1110

0001

D.x.A7 †

111

0111

1000

K.x.7 † ‡

111

0111

1000

+ +上述两张表格就是5b/6b和3b/4b编码表,那我们先来看懂这两张表格。 + +首先是D.x.y,将低5位EDCBA按其十进制数值记为x,将高3位按其十进制数值记为y,将原始8bit数据记为D.x.y。例如8bit数据最大为255,也就是111_11111,低五位是11111,也就是十进制31,高三位111,是十进制7,所对应的8b10b就是D.31.7。这也说明了上述5b/6b编码最大为D.31,3b/4b编码最大为D.x.7。 + +至于RD = - 1和RD = + 1。+ -分别表示0比1多,1比0多,多的个数是一样的,而且最多多两个。当0和1一样多时便没有+1-1之分。编码的初始化状态是-1。 + +对于D.x.7† ,当和5B/6B组合时D.x.P7和D.x.A7编码必须选择一个来避免连续的5个0或1。D.x.A7用在:x=17 x=18 x=20当RD=-1时; x=11 x=13 x=14 当RD=+1时。 其他情况下x.A7码不能被使用。 + +表里面D.23,D.27,D.29,D.30后面带 †,它有自己的特殊配对规则,当x=23 x=27 x=29 x=30时,3b/4b这边使用K.x.7进行编码。 + + 除了上面的一些编码,8b10b编码还规定了一些特殊的字符,也称为K码。K码表如下所示: + +

控制符(Control symbols)

input

RD = −1

RD = +1

HGF EDCBA

abcdei fghj

abcdei fghj

K.28.0

000 11100

001111 0100

110000 1011

K.28.1 †

001 11100

001111 1001

110000 0110

K.28.2 

010 11100

001111 0101

110000 1010

K.28.3 

011 11100

001111 0011

110000 1100

K.28.4 

100 11100

001111 0010

110000 1101

K.28.5 †

101 11100

001111 1010

110000 0101

K.28.6 

110 11100

001111 0110

110000 1001

K.28.7 ‡

111 11100

001111 1000

110000 0111

K.23.7 

111 10111

111010 1000

000101 0111

K.27.7 

111 11011

110110 1000

001001 0111

K.29.7 

111 11101

101110 1000

010001 0111

K.30.7 

111 11110

011110 1000

100001 0111

+ +8B/10B标准中使用了12个特殊的控制代码,他们能在数据中被发送,利用这些控制字符,可以组成各种控制语句,其中K.28.1 K.28.5 K.28.7被称为逗号字符,利用序列检测状态机检测逗号字符,以保证在串行数据接收过程中接收到数据的唯一性。如果没有逗号字符,串行接收设备将不知道数据是从哪一位开始,当序列检测器检测到逗号字符时就确定了字符的开始。 + +### 2.2.2 hsstlp ip核 + +实验板小眼睛盘古pg2l100h有2个hsst硬核,共8路全双工高速串行收发接口,每个接口最高支持6.6Gbps的速度,其中4路用于PCIE,2路用于SMA接口,2路用于sfp光纤接口。有关hsstlp的内容可以参考官方手册。 + +
+
+ 无法显示图片时显示的文字 +
+ 图3.HSSP ip核界面 +
+
+ +## 2.3 实战演练 + +### 2.3.1实验目标 + +利用板载一路sfp光纤通道接收来自ctrlFPGA的数据实现光纤通信。 + +### 2.3.2硬件资源 + +实验板上有2路sfp接口全部与ctrl-FPGA相连。 + +
+
+ 无法显示图片时显示的文字 +
+ 图4.板载sfp接口 +
+
+ + + + +### 2.3.3程序设计 + +首先我们应该学会配置并使用hsst ip核。 + +在ip核的配置界面可以看到可以配置4个通道的状态,分别是全双工,仅接收,仅发送和不使能。同时还可以给每路通道选择不同的标准协议例如GE,XAUI等,勾选Protocol Default Setting后可以使用该协议的默认配置。也可以自定义协议传输。 + +在本次实验中,我们将通道3设置成全双工,自定义协议,线速率5Gbps,编码选择8b10b,传输和接收的数据长度选择32位,PLL的参考时钟为125M。配置图如下: + +
+
+ 无法显示图片时显示的文字 +
+ 图5.hsst ip核 +
+
+ +在Alignment界面,可以配置对齐(word alignment)的模式,对齐所用的K码,多通道绑定(channel bonding)的模式,以及高速传输时出现的时钟偏差补偿(Clock Tolerance Compensation),具体使用可以参考hsst手册。 + +本次实验配置字节对齐模式为用户自定义,K码选择K28.5,不使用通道对齐和时钟偏差补偿。配置如下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图6.hsst ip核 +
+
+ +在Misc界面选择使用官方复位程序,参考时钟27M(板载时钟),复位时钟个数为32767,接收模式(RX Termination Mode)选择外部AC,内部DC。配置如下图所示: + +
+
+ 无法显示图片时显示的文字 +
+ 图7.hsst ip核 +
+
+ +生成ip核后,我们将ip核例化在顶层文件中,目前配置的ip核包括复位所需要的信号,重要的状态信号,传输时钟i_p_tx3_clk_fr_core,传输的32位数据i_txd_3,传输的数据类型i_txk_3,接收时钟i_p_rx3_clk_fr_core,接收的32位数据o_rxd_3,接收的数据类型o_rxk_3。这里解释一下txk和rxk,这个信号是4位的,每一位对应txd和rxd信号的8位数据,1表示该8位数据是k码,0表示改8位数据是普通数据。参考代码如下: + +```verilog +module hsst_top ( + input clk, + input rstn, + input i_p_refckn_0, + input i_p_refckp_0, + output [7:0] led +); +wire i_free_clk; +wire i_pll_rst_0; +wire o_pll_done_0; +wire o_txlane_done_3; +wire o_rxlane_done_3; +wire o_p_clk2core_tx_3; +wire i_p_tx3_clk_fr_core; +wire o_p_clk2core_rx_3; +wire i_p_rx3_clk_fr_core; +wire o_p_pll_lock_0; +wire o_p_rx_sigdet_sta_3; +wire o_p_lx_cdr_align_3; +wire i_p_l3rxn; +wire i_p_l3rxp; +wire o_p_l3txn; +wire o_p_l3txp; +wire [3:0] i_tdispsel_3; +wire [3:0] i_tdispctrl_3; +wire [2:0] o_rxstatus_3; +wire [3:0] o_rdisper_3; +wire [3:0] o_rdecer_3; +wire txclk; +reg [31:0] i_txd_3; +reg [ 3:0] i_txk_3; +wire rxclk; +wire [31:0] o_rxd_3; +wire [ 3:0] o_rxk_3; +assign i_free_clk = clk; +assign i_pll_rst_0 = rstn; +assign i_p_tx3_clk_fr_core = o_p_clk2core_tx_3; +assign i_p_rx3_clk_fr_core = o_p_clk2core_rx_3; +assign txclk = i_p_tx3_clk_fr_core; +assign rxclk = i_p_rx3_clk_fr_core; +hsst the_instance_name ( + .i_free_clk (i_free_clk ), //复位序列参考时钟 + .i_pll_rst_0 (i_pll_rst_0 ), //pll复位 + .i_wtchdg_clr_0 (), // + .o_wtchdg_st_0 (), // + .i_p_refckn_0 (i_p_refckn_0 ), //pll参考差分时钟 + .i_p_refckp_0 (i_p_refckp_0 ), // + .o_pll_done_0 (o_pll_done_0 ), //pll复位完成 + .o_txlane_done_3 (o_txlane_done_3 ), //tx通道初始化完成 + .o_rxlane_done_3 (o_rxlane_done_3 ), //rx通道初始化完成 + .o_p_pll_lock_0 (o_p_pll_lock_0 ), //pll lock信号 + .o_p_rx_sigdet_sta_3 (o_p_rx_sigdet_sta_3), //sigdet_sta状态信号 + .o_p_lx_cdr_align_3 (o_p_lx_cdr_align_3 ), //cdr_align状态信号 + .o_p_clk2core_tx_3 (o_p_clk2core_tx_3 ), //output传输时钟 + .i_p_tx3_clk_fr_core (i_p_tx3_clk_fr_core), //input + .o_p_clk2core_rx_3 (o_p_clk2core_rx_3 ), //output接收时钟 + .i_p_rx3_clk_fr_core (i_p_rx3_clk_fr_core), //input + .i_p_pcs_word_align_en_3 (1'b1 ), //使能word_align + .i_p_l3rxn (i_p_l3rxn ), //差分数据线 + .i_p_l3rxp (i_p_l3rxp ), //差分数据线 + .o_p_l3txn (o_p_l3txn ), //差分数据线 + .o_p_l3txp (o_p_l3txp ), //差分数据线 + .i_txd_3 (i_txd_3 ), //传输的32位数据 + .i_tdispsel_3 (i_tdispsel_3 ), // + .i_tdispctrl_3 (i_tdispctrl_3 ), // + .i_txk_3 (i_txk_3 ), //传输的数据类型,0:普通数据,1:K码 + .o_rxstatus_3 (o_rxstatus_3 ), // + .o_rxd_3 (o_rxd_3 ), //接收的32位数据 + .o_rdisper_3 (o_rdisper_3 ), // + .o_rdecer_3 (o_rdecer_3 ), // + .o_rxk_3 (o_rxk_3 ) //接收的数据类型,0:普通数据,1:K码 +); +//*******************************************************************// +assign led = {1'b0,1'b0,o_pll_done_0,o_p_pll_lock_0,o_txlane_done_3,o_p_rx_sigdet_sta_3, o_p_lx_cdr_align_3,o_rxlane_done_3}; + +reg [ 2:0] tx_state; +reg [15:0] txcnt; +wire tx_rstn = rstn && o_txlane_done_3 && o_rxlane_done_3; +always @(posedge txclk or negedge tx_rstn)begin + if(~tx_rstn)begin + i_txd_3 <= 32'hBCBCBCBC; + i_txk_3 <= 4'b1111; + end + else if(tx_state == 0)begin + i_txd_3 <= 32'hBCBCBCBC; + i_txk_3 <= 4'b1111; + end + else if(tx_state == 1)begin + i_txd_3 <= 32'h00000000; + i_txk_3 <= 4'b0000; + end + else if(tx_state == 2)begin + i_txd_3 <= i_txd_3 + 1; + i_txk_3 <= 4'b0000; + end +end +always @(posedge txclk or negedge tx_rstn)begin + if(~tx_rstn)begin + tx_state <= 0; + end + else if(tx_state == 0)begin + if(txcnt == 7) + tx_state <= 1; + else + tx_state <= 0; + end + else if(tx_state == 1)begin + tx_state <= 2; + end + else if(tx_state == 2)begin + if(txcnt == 1032) + tx_state <= 0; + else + tx_state <= 2; + end +end +always @(posedge txclk or negedge tx_rstn)begin + if(~tx_rstn) + txcnt <= 0; + else if(txcnt == 1032) + txcnt <= 0; + else + txcnt <= txcnt + 1; +end + +endmodule +``` + +到现在为止,我们已经可以接收和发送数据了,但使用的时候会发现一个问题,如下图所示,我们发送的数据是0xc5bcc5bc,但接收到的数据却是0xbcc5bc5,数据出现了移位,这是为什么呢?这是因为发送编码是8b10b,是以8位数据为一个基本单位进行发送的,虽然发送数据是0xc5bcc5bc,但是在串行通信中,他并不知道你发送的32位数据是以c5开头还是以bc开头,前面在ip核中设置的word alignment正确的对齐了BC(k28.5)这个字符,正确的接收了字节数据,但32位数据中字节顺序需要我们自己去调整。(因为设置ip时用的自定义模式)。 + +
+
+ 无法显示图片时显示的文字 +
+ 图8.debugger抓取波形 +
+
+为了解决这一问题,我们也要给他设计一个模块,使他自动实现接收数据与发送数据一致,不会出现错位。在本实验中空闲时发送逗号字符K28.5 (BC),有数据要发送时发送32位数据。根据此格式设计对齐模块参考代码如下: + +```Verilog +module Word_Alignment_32bit ( + input wire clk , + input wire rstn , + input wire [31:0] data_bf_align /* synthesis PAP_MARK_DEBUG="1" */, + input wire [ 3:0] rxk /* synthesis PAP_MARK_DEBUG="1" */, + output reg data_valid /* synthesis PAP_MARK_DEBUG="1" */, + output reg [31:0] data_af_align /* synthesis PAP_MARK_DEBUG="1" */, + output reg data_done/* synthesis PAP_MARK_DEBUG="1" */ +); +//************************ 8b10b K_Code ******************************************** +//K28.0 1C +//K28.1 3C +//K28.2 5C +//K28.3 7C +//K28.4 9C +//K28.5 BC +//K28.6 DC +//K28.7 FC +//K23.7 F7 +//K27.7 FB +//K29.7 FD +//K30.7 FE +//********************** Pattern Controller ******************************************** +// txdata format +// data_x = {data_x_1,data_x_2,data_x_3,data_x_4} +// 假如发送数据是data_1,data_2,data_3 +// 接收数据很可能会出现 +// {data_1} +// +// +// +// +// +// +// data Format: +// +// __ ________ ________ ________ ________ ________ ________ ________ ________ ________ +// __X_ idle__X__idle__X__idle__X__data__x ……………… x__data__X__idle__X__idle__X__idle__X___idle_ +// +// idle <= K28.5 +// 空闲时发送K28.5,有数据时发送数据, +// 本模块根据以上格式实现32位数据对齐,使得接收数据与发送数据一致,不会出现错位。 +// +// +//************************************************************************************** + +//parameter +localparam IDLE = 0; +localparam ALIGN1 = 1; +localparam ALIGN2 = 2; +localparam ALIGN3 = 3; +localparam ALIGN4 = 4; +reg [4:0] state; +reg [4:0] nextstate; +reg skip; +reg rxcnt; +reg [ 7:0] datareg8; +reg [15:0] datareg16; +reg [23:0] datareg24; +reg [31:0] datareg32; +reg error; +// always @(negedge clk or negedge rstn)begin +// if(~rstn)begin +// datareg8 <= 0; +// datareg16 <= 0; +// datareg24 <= 0; +// datareg32 <= 0; +// end +// else begin +// datareg8 <= data_bf_align[31:24]; +// datareg16 <= data_bf_align[31:16]; +// datareg24 <= data_bf_align[31:8]; +// datareg32 <= data_bf_align; +// end +// end +always @(posedge clk or negedge rstn)begin + if(~rstn)begin + state <= IDLE; + end + else begin + state <= nextstate; + end +end +always @(*)begin + case(state) + IDLE : begin + if (rxk == 4'b0111) nextstate <= ALIGN1; + else if(rxk == 4'b0011) nextstate <= ALIGN2; + else if(rxk == 4'b0001) nextstate <= ALIGN3; + else if(rxk == 4'b0000) nextstate <= ALIGN4; + else nextstate <= IDLE; + end + ALIGN1 : begin + if(skip || error) begin + if (rxk == 4'b0111) nextstate <= ALIGN1; + else if(rxk == 4'b0011) nextstate <= ALIGN2; + else if(rxk == 4'b0001) nextstate <= ALIGN3; + else if(rxk == 4'b0000) nextstate <= ALIGN4; + else nextstate <= IDLE; + end + else + nextstate <= ALIGN1; + end + ALIGN2 : begin + if(skip || error) begin + if (rxk == 4'b0111) nextstate <= ALIGN1; + else if(rxk == 4'b0011) nextstate <= ALIGN2; + else if(rxk == 4'b0001) nextstate <= ALIGN3; + else if(rxk == 4'b0000) nextstate <= ALIGN4; + else nextstate <= IDLE; + end + else + nextstate <= ALIGN2; + end + ALIGN3 : begin + if(skip || error) begin + if (rxk == 4'b0111) nextstate <= ALIGN1; + else if(rxk == 4'b0011) nextstate <= ALIGN2; + else if(rxk == 4'b0001) nextstate <= ALIGN3; + else if(rxk == 4'b0000) nextstate <= ALIGN4; + else nextstate <= IDLE; + end + else + nextstate <= ALIGN3; + end + ALIGN4 : begin + if(skip || error) begin + if (rxk == 4'b0111) nextstate <= ALIGN1; + else if(rxk == 4'b0011) nextstate <= ALIGN2; + else if(rxk == 4'b0001) nextstate <= ALIGN3; + else if(rxk == 4'b0000) nextstate <= ALIGN4; + else nextstate <= IDLE; + end + // if(skip) + // nextstate <= IDLE; + // else if(error) + // nextstate <= IDLE; + else + nextstate <= ALIGN4; + end + endcase +end +always @(posedge clk or negedge rstn) begin + if(~rstn)begin + data_valid <= 0; + data_af_align <= 0; + data_done <= 0; + rxcnt <= 0; + skip <= 0; + datareg8 <= 0; + datareg16 <= 0; + datareg24 <= 0; + datareg32 <= 0; + error <= 0; + end + else begin + // data_valid <= 0; + // data_done <= 0; + case(state) + IDLE : begin + rxcnt <= 0; + skip <= 0; + data_valid <= 0; + data_done <= 0; + if(rxk == 4'b1111)begin + data_af_align <= data_bf_align; + error <= 0; + end + else begin + if(rxk == 4'b0111)begin + datareg8 <= data_bf_align[31:24]; + error <= 0; + end + else if(rxk == 4'b0011)begin + datareg16 <= data_bf_align[31:16]; + error <= 0; + end + else if(rxk == 4'b0001)begin + datareg24 <= data_bf_align[31: 8]; + error <= 0; + end + else if(rxk == 4'b0000)begin + datareg32 <= data_bf_align; + error <= 0; + end + else begin + error <= 1; + end + end + end + ALIGN1 : begin + data_af_align <= {data_bf_align[23:0],datareg8}; + datareg8 <= data_bf_align[31:24]; + if(skip) skip <= 0; + else if(rxk == 4'b1000) skip <= 1; + else if(rxk == 4'b0000) skip <= 0; + else error <= 1; + + if(skip) data_done <= 0; + else if(rxk == 4'b1000) data_done <= 1; + else data_done <= 0; + + if(skip) data_valid <= 0; + else data_valid <= 1; + end + ALIGN2 : begin + data_af_align <= {data_bf_align[15:0],datareg16}; + datareg16 <= data_bf_align[31:16]; + if(skip) skip <= 0; + else if(rxk == 4'b1100) skip <= 1; + else if(rxk == 4'b0000) skip <= 0; + else error <= 1; + + if(skip) data_done <= 0; + else if(rxk == 4'b1100) data_done <= 1; + else data_done <= 0; + + if(skip) data_valid <= 0; + else data_valid <= 1; + end + ALIGN3 : begin + data_af_align <= {data_bf_align[7:0],datareg24}; + datareg24 <= data_bf_align[31:8]; + if(skip) skip <= 0; + else if(rxk == 4'b1110) skip <= 1; + else if(rxk == 4'b0000) skip <= 0; + else error <= 1; + + if(skip) data_done <= 0; + else if(rxk == 4'b1110) data_done <= 1; + else data_done <= 0; + + if(skip) data_valid <= 0; + else data_valid <= 1; + end + ALIGN4 : begin + data_af_align[31:0] <= datareg32; + datareg32 <= data_bf_align; + + if(skip) skip <= 0; + else if(rxk == 4'b1111) skip <= 1; + else if(rxk == 4'b0000) skip <= 0; + else error <= 1; + + if(skip) data_done <= 0; + else if(rxk == 4'b1111) data_done <= 1; + else data_done <= 0; + + if(skip) data_valid <= 0; + else data_valid <= 1; + end + endcase + end +end + +endmodule +``` + +我们将对齐模块例化到顶层,并且为了观察接收数据与发送数据是否一致,我们将接收到的数据从0开始比较,如果相同,led0就会亮,如果不同led0就不亮,同时把未经32位对齐模块的数据与发送数据对比,相同led1亮,不同led1不亮,新的顶层模块如下所示: + +```verilog +module hsst_top ( + input clk, + input rstn, + input i_p_refckn_0, + input i_p_refckp_0, + output [7:0] led +); +wire i_free_clk; +wire i_pll_rst_0; +wire o_pll_done_0; +wire o_txlane_done_3; +wire o_rxlane_done_3; +wire o_p_clk2core_tx_3; +wire i_p_tx3_clk_fr_core; +wire o_p_clk2core_rx_3; +wire i_p_rx3_clk_fr_core; +wire o_p_pll_lock_0; +wire o_p_rx_sigdet_sta_3; +wire o_p_lx_cdr_align_3; +wire i_p_l3rxn; +wire i_p_l3rxp; +wire o_p_l3txn; +wire o_p_l3txp; +wire [3:0] i_tdispsel_3; +wire [3:0] i_tdispctrl_3; +wire [2:0] o_rxstatus_3; +wire [3:0] o_rdisper_3; +wire [3:0] o_rdecer_3; +wire txclk; +reg [31:0] i_txd_3; +reg [ 3:0] i_txk_3; +wire rxclk; +wire [31:0] o_rxd_3; +wire [ 3:0] o_rxk_3; +assign i_free_clk = clk; +assign i_pll_rst_0 = rstn; +assign i_p_tx3_clk_fr_core = o_p_clk2core_tx_3; +assign i_p_rx3_clk_fr_core = o_p_clk2core_rx_3; +assign txclk = i_p_tx3_clk_fr_core; +assign rxclk = i_p_rx3_clk_fr_core; +hsst the_instance_name ( + .i_free_clk (i_free_clk ), //复位序列参考时钟 + .i_pll_rst_0 (i_pll_rst_0 ), //pll复位 + .i_wtchdg_clr_0 (), // + .o_wtchdg_st_0 (), // + .i_p_refckn_0 (i_p_refckn_0 ), //pll参考差分时钟 + .i_p_refckp_0 (i_p_refckp_0 ), // + .o_pll_done_0 (o_pll_done_0 ), //pll复位完成 + .o_txlane_done_3 (o_txlane_done_3 ), //tx通道初始化完成 + .o_rxlane_done_3 (o_rxlane_done_3 ), //rx通道初始化完成 + .o_p_pll_lock_0 (o_p_pll_lock_0 ), //pll lock信号 + .o_p_rx_sigdet_sta_3 (o_p_rx_sigdet_sta_3), //sigdet_sta状态信号 + .o_p_lx_cdr_align_3 (o_p_lx_cdr_align_3 ), //cdr_align状态信号 + .o_p_clk2core_tx_3 (o_p_clk2core_tx_3 ), //output传输时钟 + .i_p_tx3_clk_fr_core (i_p_tx3_clk_fr_core), //input + .o_p_clk2core_rx_3 (o_p_clk2core_rx_3 ), //output接收时钟 + .i_p_rx3_clk_fr_core (i_p_rx3_clk_fr_core), //input + .i_p_pcs_word_align_en_3 (1'b1 ), //使能word_align + .i_p_l3rxn (i_p_l3rxn ), //差分数据线 + .i_p_l3rxp (i_p_l3rxp ), //差分数据线 + .o_p_l3txn (o_p_l3txn ), //差分数据线 + .o_p_l3txp (o_p_l3txp ), //差分数据线 + .i_txd_3 (i_txd_3 ), //传输的32位数据 + .i_tdispsel_3 (i_tdispsel_3 ), // + .i_tdispctrl_3 (i_tdispctrl_3 ), // + .i_txk_3 (i_txk_3 ), //传输的数据类型,0:普通数据,1:K码 + .o_rxstatus_3 (o_rxstatus_3 ), // + .o_rxd_3 (o_rxd_3 ), //接收的32位数据 + .o_rdisper_3 (o_rdisper_3 ), // + .o_rdecer_3 (o_rdecer_3 ), // + .o_rxk_3 (o_rxk_3 ) //接收的数据类型,0:普通数据,1:K码 +); +//*******************************************************************// + +reg [ 2:0] tx_state; +reg [15:0] txcnt; +wire tx_rstn = rstn && o_txlane_done_3 && o_rxlane_done_3; +always @(posedge txclk or negedge tx_rstn)begin + if(~tx_rstn)begin + i_txd_3 <= 32'hBCBCBCBC; + i_txk_3 <= 4'b1111; + end + else if(tx_state == 0)begin + i_txd_3 <= 32'hBCBCBCBC; + i_txk_3 <= 4'b1111; + end + else if(tx_state == 1)begin + i_txd_3 <= 32'h00000000; + i_txk_3 <= 4'b0000; + end + else if(tx_state == 2)begin + i_txd_3 <= i_txd_3 + 1; + i_txk_3 <= 4'b0000; + end +end +always @(posedge txclk or negedge tx_rstn)begin + if(~tx_rstn)begin + tx_state <= 0; + end + else if(tx_state == 0)begin + if(txcnt == 7) + tx_state <= 1; + else + tx_state <= 0; + end + else if(tx_state == 1)begin + tx_state <= 2; + end + else if(tx_state == 2)begin + if(txcnt == 1032) + tx_state <= 0; + else + tx_state <= 2; + end +end +always @(posedge txclk or negedge tx_rstn)begin + if(~tx_rstn) + txcnt <= 0; + else if(txcnt == 1032) + txcnt <= 0; + else + txcnt <= txcnt + 1; +end +wire Word_Alignment_rstn = rstn && o_txlane_done_3 && o_rxlane_done_3; +wire data_valid; +wire [31:0] data_af_align; +wire data_last; +Word_Alignment_32bit Word_Alignment_32bit_inst ( + .clk (rxclk ), + .rstn (Word_Alignment_rstn), + .data_bf_align (o_rxd_3 ), + .rxk (o_rxk_3 ), + .data_valid (data_valid ), + .data_af_align (data_af_align ), + .data_done (data_last ) + ); +//**********************************// + +reg [31:0] data_bf_Alignment_judge; +reg [31:0] data_af_Alignment_judge; + +always@(posedge rxclk or negedge Word_Alignment_rstn)begin + if(~Word_Alignment_rstn)data_bf_Alignment_judge <= 0; + else if(o_rxk_3 == 4'b0000) data_bf_Alignment_judge <= data_bf_Alignment_judge + 1; + else data_bf_Alignment_judge <= 0; +end +always@(posedge rxclk or negedge Word_Alignment_rstn)begin + if(~Word_Alignment_rstn)data_af_Alignment_judge <= 0; + else if(data_valid) data_af_Alignment_judge <= data_af_Alignment_judge + 1; + else data_af_Alignment_judge <= 0; +end + +assign led = {o_pll_done_0,o_p_pll_lock_0,o_txlane_done_3,o_rxlane_done_3,1'b0,1'b0,data_af_Alignment_judge == data_af_align,data_bf_Alignment_judge == o_rxd_3}; +endmodule +``` + +### 2.3.4仿真验证 + +本实验不进行仿真,可以点击工程目录下ipcore文件夹下,hsst参考设计中sim文件夹的sim.bat文件进行仿真。 + +### 2.3.5上板验证 + +上板前要先进行管脚约束。端口除了板载时钟,复位以及led,还需要添加以下约束: + +```fdc +define_attribute {p:i_p_refckn_0} {PAP_IO_DIRECTION} {INPUT} +define_attribute {p:i_p_refckn_0} {PAP_IO_LOC} {AB11} +define_attribute {p:i_p_refckn_0} {PAP_IO_UNUSED} {TRUE} +define_attribute {p:i_p_refckp_0} {PAP_IO_DIRECTION} {INPUT} +define_attribute {p:i_p_refckp_0} {PAP_IO_LOC} {AA11} +define_attribute {p:i_p_refckp_0} {PAP_IO_UNUSED} {TRUE} + +define_attribute {i:hsst_inst.U_GTP_HSSTLP_WRAPPER.CHANNEL3_ENABLE.U_GTP_HSSTLP_LANE3} {PAP_LOC} {HSSTLP_364_0:U3_HSSTLP_LANE} +define_attribute {i:hsst_inst.U_GTP_HSSTLP_WRAPPER.PLL0_ENABLE.U_GTP_HSSTLP_PLL0} {PAP_LOC} {HSSTLP_364_0:U0_HSSTLP_PLL} +``` + +该语句主要约束hsst参考时钟管脚,hsst所使用PLL(PLL0),以及hsst使用的通道。其他约束可以参考工程目录下ipcore文件夹中hsst内的参考例程。 + +完成管脚分配之后就可以生成sbit文件,将文件提交到网站后点击烧录,即可将sbit下载到实验板中,在摄像头页面即可观察到8个led中前四个亮,后四个分别为不亮,不亮,亮,不亮的状态。前四个亮表示hsst中pll和rx,tx通道初始化成功,最后两个led,led1亮表示接收的与发送的数据相同,led0不亮表示未经32位对齐模块处理的接收数据与发送数据不同。 + +## 2.4 章末总结 + +本次实验主要学习了高速串行通信的相关知识,初步学会使用hsst ip核。 \ No newline at end of file diff --git a/public/doc/12/images/1.jfif b/public/doc/12/images/1.jfif new file mode 100644 index 0000000..2cbe8c8 Binary files /dev/null and b/public/doc/12/images/1.jfif differ diff --git a/public/doc/12/images/2.png b/public/doc/12/images/2.png new file mode 100644 index 0000000..98c419d Binary files /dev/null and b/public/doc/12/images/2.png differ diff --git a/public/doc/12/images/3.jpg b/public/doc/12/images/3.jpg new file mode 100644 index 0000000..4c01c21 Binary files /dev/null and b/public/doc/12/images/3.jpg differ diff --git a/public/doc/12/images/4.png b/public/doc/12/images/4.png new file mode 100644 index 0000000..0e5c5e7 Binary files /dev/null and b/public/doc/12/images/4.png differ diff --git a/public/doc/12/images/5.jpg b/public/doc/12/images/5.jpg new file mode 100644 index 0000000..7c5139c Binary files /dev/null and b/public/doc/12/images/5.jpg differ diff --git a/public/doc/12/images/6.png b/public/doc/12/images/6.png new file mode 100644 index 0000000..2701de4 Binary files /dev/null and b/public/doc/12/images/6.png differ diff --git a/public/doc/12/images/7.png b/public/doc/12/images/7.png new file mode 100644 index 0000000..ff6904e Binary files /dev/null and b/public/doc/12/images/7.png differ diff --git a/public/doc/12/images/8.png b/public/doc/12/images/8.png new file mode 100644 index 0000000..3664a6a Binary files /dev/null and b/public/doc/12/images/8.png differ diff --git a/public/doc/13/doc.md b/public/doc/13/doc.md new file mode 100644 index 0000000..de0b3ff --- /dev/null +++ b/public/doc/13/doc.md @@ -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的输入速率越高。 + +
+
+ 无法显示图片时显示的文字 +
+ 图1.ADDA模块示意图 +
+
+ +### 3.2.2 数码管模块 + +数码管模块在前面基础实验3已经介绍过,这里不再赘述。 + +## 3.3 实战演练 + +### 3.3.1实验目标 + +能够驱动板载ADC模块,对ADC模块的输入数据进行测试,计算输入信号的频率值,并在数码管模块中显示。 + +### 3.3.2硬件资源 + +实验所需的信号源来自我们的实验平台,实验平台集成一个以FPGA为基础的dds信号发生器,该dds信号发生器可以输出频率可调的方波,正弦波,三角波,锯齿波等,用户可以在web平台使用并且改变输出波形和频率。 + +
+
+ 无法显示图片时显示的文字 +
+ 图2.ADDA实验示意图 +
+
+ +用户接收到信号源之后需自行设计逻辑处理数据并显示。 + +### 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 章末总结 diff --git a/public/doc/13/images/1.jpg b/public/doc/13/images/1.jpg new file mode 100644 index 0000000..89cc265 Binary files /dev/null and b/public/doc/13/images/1.jpg differ diff --git a/public/doc/13/images/1.png b/public/doc/13/images/1.png new file mode 100644 index 0000000..bd4fd44 Binary files /dev/null and b/public/doc/13/images/1.png differ diff --git a/public/doc/13/images/2.png b/public/doc/13/images/2.png new file mode 100644 index 0000000..a744645 Binary files /dev/null and b/public/doc/13/images/2.png differ diff --git a/src/components/TutorialCarousel.vue b/src/components/TutorialCarousel.vue index 590e33c..befc7d4 100644 --- a/src/components/TutorialCarousel.vue +++ b/src/components/TutorialCarousel.vue @@ -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); } // 为每个例程创建对象并尝试获取文档标题 diff --git a/src/router/index.ts b/src/router/index.ts index 5403c59..8ac1f9c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -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 diff --git a/src/views/MarkdownTestView.vue b/src/views/MarkdownTestView.vue deleted file mode 100644 index fed4848..0000000 --- a/src/views/MarkdownTestView.vue +++ /dev/null @@ -1,445 +0,0 @@ - - - - -