PC程控小脚丫FPGA板的彩色LED
该项目使用了小脚丫FPGA,实现了PC程控彩色LED的设计,它的主要功能为:通过上位机控制小脚丫FPGA RGB LED。
标签
FPGA
verilog
C++
2024寒假在家一起练
QT
bigzhu
更新2024-04-01
苏州大学
257

项目需求

  1. 设计一个UI控制程序
  2. 通过UART进行数据传输
  3. 数码管显示
  4. 拨动开关设计
  5. 按键调节颜色值
  6. 最后进行功能整合


需求分析

1. 界面设计要求

  • 设计一个用户友好的PC界面,用于控制RGB三种颜色的数值范围。
  • 界面应包含三个滑动条或输入框,分别对应R、G、B的数值范围(0-255)进行调节。

2. 数据传递要求

  • 调节好的RGB颜色数值通过UART传递到小脚丫FPGA上,用于控制三色LED的状态。

3. 数码管显示要求

  • 根据拨动开关SW1-SW3的选择,将对应颜色值显示在数码管上。
  • 数码管应能够显示0-255范围内的数值。

4. 拨动开关和颜色选择要求

  • 使用FPGA上的拨动开关SW1~SW3选择要在数码管上显示的颜色值。
  • 拨动开关与颜色对应关系:
    1. SW1:红色R
    2. SW2:绿色G
    3. SW3:蓝色B

5. 颜色值调节要求

  • 使用FPGA上的轻触开关K1和K2,控制拨动开关选择的颜色值的调节。
  • 调节方式:
    1. K1: 颜色值+1
    2. K2: 颜色值-1

6. 功能整合要求

  • 通过PC界面控制任意一种颜色,改变FPGA上三色LED的显示。
  • 数码管显示的颜色值根据拨动开关SW1-3的状态选择。
  • 实现通过R、G、B三种颜色的调节生成白色效果,并记录生成白色时的R、G、B三种颜色数值。


UART交互协议

为了和上位机通信,设计了一个简单的UART通信协议。每包固定7个字节。UART通信参数:115200, 8N1

1234567
> or <CMDDATA1DATA2DATA3DATA4AA
帧头命令数据1数据2数据3数据4帧尾

说明:帧头 > 0x3E 表示FPGA接收数据,< 0x3C 表示FPGA发送数据

1. RGB LED控制

方向:上位机 -> 下位机

1234567
>CMDDATA1DATA2DATA3DATA4AA
帧头0x3E0x01Red(0x00-0xFF)GreenBlue0x00帧尾


2. 状态同步

下位机将自己的状态实时同步给上位机,比如通过按键设置了RGB LED的值需要同步给上位机,上位机需要同步更新UI界面。

方向:下位机 -> 上位机

1234567
<CMDDATA1DATA2DATA3DATA4AA
帧头0x3C0x71Red(0x00-0xFF)GreenBlue0x00帧尾


上位机和FPGA通信示意图



FPGA实现

开源地址:https://gitee.com/bigstep/step-fpga.git


功能框图


UART硬件连接

UART使用了三根线GNDGPIO15(N8 TX)GPIO14(P8 RX),如下图所示



TOP RTL视图


主要分为

  1. LED呼吸灯作为程序运行状态指示
  2. UART数据接收模块
  3. UART指令发送模块
  4. 核心业务控制模块
  5. RGB显示核心控制模块


仿真

虽然ModelSim 功能更强大,但是用起来还是有点麻烦。一般情况下我们可以选择轻量级的开源免费软件 Icarus Verilog 进行仿真。

hdlbits在线仿真

使用hdlbits在线进行测试更方便 https://hdlbits.01xz.net/wiki/Iverilog

网页测试简单的代码比较方便,但是一旦仿真时间长就会显示不全,所以我们还需要在本地安装

Icarus Verilog

Windows电脑安装Icarus Verilog,下载地址 https://bleyer.org/icarus/

注意:安装的时候勾选将bin加入系统PATH中,方便在命令行中直接调用可执行文件。


下面的仿真都是通过这个工具实现的。


呼吸灯

作为程序运行的状态指示,通过调节PWM占空比控制灯的明亮程度。

仿真测试

# 编译,默认生成a.out文件
iverilog breath_led_tb.v breath_led.v
# 生成vcd文件
vvp .\a.out
# 使用gtkwave软件打开vcd波形图
gtkwave.exe .\breath_led.vcd

仿真测试文件

`timescale 1ns/1ns

module top_module ();
reg clk=0;
always #10 clk = ~clk; // Create clock with period=20

// A testbench
reg rst_n=0;
wire led_out;
initial begin
#50 rst_n <= 1'b1;
$display ("The current time is (%0d ns)", $time);
#10000000 $finish; // Quit the simulation
end

breath_led breath_led (
.clk(clk),
.rst_n(rst_n),
.led_out(led_out)
);

initial begin
$dumpfile("breath_led.vcd");
$dumpvars(0, top_module);
end

endmodule


可以看出随着时间的增长,`led_out`输出时间越来越长,也就是占空比越来越高。


UART命令解析模块



带指令接收超时重置功能,通过 recv_cnt 计数,100毫秒没接收到完整包即认为数据出错,重置参数为下一次接收作准备。

  reg [3:0] recv_state;
reg [31:0] recv_cnt; // recv timeout timer
always @(posedge clk, negedge rst_n) begin
if (!rst_n) begin
recv_state <= 4'd0;
recv_cnt <= 32'd0;
cmd_recv <= 1'b0;
end else if (recv_cnt > 32'd4_999_999) begin
recv_state <= 4'd0;
recv_cnt <= 32'd0;
cmd_recv <= 1'b0;
end else begin
recv_cnt <= recv_cnt + 1'b1;
case (recv_state)
4'd0: begin // > 0x3E
cmd_recv <= 1'b0;
if (uart_rx_done) begin
if (uart_rx_byte == 8'h3E) begin
recv_state <= 4'd1;
recv_cnt <= 32'd0;
end else begin
recv_state <= 4'd0;
end
end else begin
recv_state <= 4'd0;
recv_cnt <= 32'd0;
end
end
4'd1: begin // cmd
if (uart_rx_done) begin
rx_cmd_data[7:0] <= uart_rx_byte;
recv_state <= 4'd2;
recv_cnt <= 32'd0;
end else recv_state <= recv_state;
end
4'd2: begin // data1
if (uart_rx_done) begin
rx_cmd_data[15:8] <= uart_rx_byte;
recv_state <= 4'd3;
recv_cnt <= 32'd0;
end else recv_state <= recv_state;
end
4'd3: begin // data2
if (uart_rx_done) begin
rx_cmd_data[23:16] <= uart_rx_byte;
recv_state <= 4'd4;
recv_cnt <= 32'd0;
end else recv_state <= recv_state;
end
4'd4: begin // data3
if (uart_rx_done) begin
rx_cmd_data[31:24] <= uart_rx_byte;
recv_state <= 4'd5;
recv_cnt <= 32'd0;
end else recv_state <= recv_state;
end
4'd5: begin // data4
if (uart_rx_done) begin
rx_cmd_data[39:32] <= uart_rx_byte;
recv_state <= 4'd6;
recv_cnt <= 32'd0;
end else recv_state <= recv_state;
end
4'd6: begin // AA
if (uart_rx_done) begin
if (uart_rx_byte == 8'hAA) begin
// recv cmd success
cmd_recv <= 1'b1;
end
recv_state <= 4'd0;
end else recv_state <= recv_state;
end
default: recv_state <= 4'd0;
endcase
end
end



仿真测试

进入 tb/uart_rx_cmd 目录,在命令行中执行如下命令

# 编译,默认生成a.out文件
iverilog uart_rx_cmd_tb.v uart_rx.v uart_rx_cmd.v
# 生成uart_tx.vcd文件
vvp .\a.out
# 使用gtkwave软件打开uart_tx.vcd波形图
gtkwave.exe .\uart_rx_cmd.vcd

模拟一条执行的正常接收:3E01020304055AA,从仿真结果可以看到 cmd_recv 最后拉高一个时钟,代表接收到合法的数据帧。结果保存在rx_cmd_data

`timescale 1ns/1ns

module top_module ();
reg clk=0;
always #10 clk = ~clk; // Create clock with period=20

// A testbench
reg rst_n=0;
reg rx=1;
wire [7:0] rx_data;
wire cmd_recv;
wire [39:0] rx_cmd_data;
initial begin
#50 rst_n <= 1'b1;
#50 rx=0; // START
#8680 rx=0; // bit0 0x3E
#8680 rx=1; // bit1
#8680 rx=1; // bit2
#8680 rx=1; // bit3
#8680 rx=1; // bit4
#8680 rx=1; // bit5
#8680 rx=0; // bit6
#8680 rx=0; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
#50 rx=0; // START
#8680 rx=1; // bit0 0x01
#8680 rx=0; // bit1
#8680 rx=0; // bit2
#8680 rx=0; // bit3
#8680 rx=0; // bit4
#8680 rx=0; // bit5
#8680 rx=0; // bit6
#8680 rx=0; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
#50 rx=0; // START
#8680 rx=0; // bit0 0x02
#8680 rx=1; // bit1
#8680 rx=0; // bit2
#8680 rx=0; // bit3
#8680 rx=0; // bit4
#8680 rx=0; // bit5
#8680 rx=0; // bit6
#8680 rx=0; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
#50 rx=0; // START
#8680 rx=1; // bit0 0x03
#8680 rx=1; // bit1
#8680 rx=0; // bit2
#8680 rx=0; // bit3
#8680 rx=0; // bit4
#8680 rx=0; // bit5
#8680 rx=0; // bit6
#8680 rx=0; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
#50 rx=0; // START
#8680 rx=0; // bit0 0x04
#8680 rx=0; // bit1
#8680 rx=1; // bit2
#8680 rx=0; // bit3
#8680 rx=0; // bit4
#8680 rx=0; // bit5
#8680 rx=0; // bit6
#8680 rx=0; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
#50 rx=0; // START
#8680 rx=1; // bit0 0x05
#8680 rx=0; // bit1
#8680 rx=1; // bit2
#8680 rx=0; // bit3
#8680 rx=0; // bit4
#8680 rx=0; // bit5
#8680 rx=0; // bit6
#8680 rx=0; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
#50 rx=0; // START
#8680 rx=0; // bit0 0xAA
#8680 rx=1; // bit1
#8680 rx=0; // bit2
#8680 rx=1; // bit3
#8680 rx=0; // bit4
#8680 rx=1; // bit5
#8680 rx=0; // bit6
#8680 rx=1; // bit7
#8680 rx=1; // STOP
#8680 rx=1; // IDLE
$display ("The current time is (%0d ns)", $time);
#100000 $finish; // Quit the simulation
end

uart_rx_cmd uart_rx_cmd_inst (
.clk(clk),
.rst_n(rst_n),
.uart_rx_pin(rx),
.cmd_recv(cmd_recv),
.rx_cmd_data(rx_cmd_data)
);

initial begin
$dumpfile("uart_rx_cmd.vcd");
$dumpvars(0, top_module);
end

endmodule



UART指令发送模块



发送模块逻辑稍微简单点。需要注意的是如果在数据包发送过程中又接收到发送请求会被忽略,必须等待上一次数据包全部发送完成才能进行下一次发送。

module uart_tx_cmd (
   input clk,
   input rst_n,
   input uart_tx_en,
   input [39:0] tx_cmd_data,       // cmd data
   output uart_tx_pin,             // uart tx pin
   output reg tx_status            // uart work status
);

 reg [39:0] cmd_data_temp;
 reg [2:0] uart_baud_set;
 reg [7:0] uart_tx_byte;
 reg send_en;
 wire uart_tx_status;
 reg [1:0] tx_flow;
 reg [3:0] tx_index;
 wire uart_tx_done;

 // uart tx
 uart_tx uart_tx(
  .clk(clk),
  .rst_n(rst_n),
  .tx_data(uart_tx_byte),
  .send_en(send_en),
  .baud_set(uart_baud_set),
  .uart_tx(uart_tx_pin),
  .tx_done(uart_tx_done),
  .uart_state(uart_tx_status)
 );

 always @(posedge clk, negedge rst_n) begin
   if (!rst_n) begin
     uart_baud_set <= 3'd4; // 115200
   end else begin
     uart_baud_set <= uart_baud_set;
   end
 end

 always @(posedge clk, negedge rst_n) begin
   if (!rst_n) begin
     tx_flow <= 2'b00;
     send_en <= 1'b0;
     tx_index <= 4'd0;
     tx_status <= 1'b0;
   end else begin
     case (tx_flow)
       2'd0: begin
         send_en <= 1'b0;
         if (uart_tx_en) begin
           tx_status <= 1'b1;
           tx_flow <= 2'd1;
           cmd_data_temp <= tx_cmd_data;
           tx_index <= 4'd0;
         end else begin
           tx_flow <= tx_flow;
           tx_status <= 1'b0;
         end
       end
       2'd1: begin
         send_en <= 1'b1;
         uart_tx_byte <= 8'h3C;
         tx_flow <= 2'd2;
       end
       2'd2: begin   // uart send status
         if (uart_tx_done) begin
           send_en <= 1'b1;
           if (tx_index == 4'd0) begin
             uart_tx_byte <= cmd_data_temp[7:0];
             tx_index <= 4'd1;
           end else if (tx_index == 4'd1) begin
             uart_tx_byte <= cmd_data_temp[15:8];
             tx_index <= 4'd2;
           end else if (tx_index == 4'd2) begin
             uart_tx_byte <= cmd_data_temp[23:16];
             tx_index <= 4'd3;
           end else if (tx_index == 4'd3) begin
             uart_tx_byte <= cmd_data_temp[31:24];
             tx_index <= 4'd4;
           end else if (tx_index == 4'd4) begin
             uart_tx_byte <= cmd_data_temp[39:32];
             tx_index <= 4'd5;
           end else if (tx_index == 4'd5) begin
             uart_tx_byte <= 8'hAA;
             tx_index <= 4'd6;
           end else if (tx_index == 4'd6) begin
             send_en <= 1'b0;
             tx_index <= 4'd0;
             tx_flow <= 2'd0;
           end else begin
             tx_flow <= 2'd0;
           end
         end else begin
           send_en <= 1'b0;
           tx_flow <= tx_flow;
         end
       end
       2'd3: begin
         send_en <= 1'b0;
         tx_flow <= 2'd3;
         tx_index <= 4'd0;
       end
       default: tx_flow <= 2'd0;
     endcase
   end
 end

endmodule

仿真测试

# 编译,默认生成a.out文件
iverilog uart_tx_cmd_tb.v uart_tx.v uart_tx_cmd.v
# 生成vcd文件
vvp .\a.out
# 使用gtkwave软件打开波形图
gtkwave.exe .\uart_tx_cmd.vcd


`timescale 1ns/1ns

module top_module ();
reg clk=0;
always #10 clk = ~clk; // Create clock with period=20

// A testbench
reg rst_n=0;
reg uart_tx_en=0;
wire [7:0] rx_data;
wire uart_tx_pin;
reg [39:0] tx_cmd_data=40'h0504030201;
wire tx_status;
initial begin
#50 rst_n <= 1'b1;
#100 uart_tx_en=1;
#100 uart_tx_en=0;
$display ("The current time is (%0d ns)", $time);
#2000000 $finish; // Quit the simulation
end

uart_tx_cmd uart_tx_cmd_inst (
.clk(clk),
.rst_n(rst_n),
.uart_tx_en(uart_tx_en),
.tx_cmd_data(tx_cmd_data),
.uart_tx_pin(uart_tx_pin),
.tx_status(tx_status)
);

initial begin
$dumpfile("uart_tx_cmd.vcd");
$dumpvars(0, top_module);
end

endmodule


从仿真结果可以看出:数据从低位开始发送,最前面是自动添加的0x3C,后面依次发送 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,符合设计要求


核心应用控制模块

将应用封装成一个独立的模块step_ctrl


仿真测试

# 编译,默认生成a.out文件
iverilog step_ctrl_tb.v step_ctrl.v key_debounce.v pwm.v rgb.v led_seg.v
# 生成vcd文件
vvp .\a.out
# 使用gtkwave软件打开vcd波形图
gtkwave.exe .\step_ctrl.vcd

模拟一次按键操作,RGB值发生变化,UART会发送数据

`timescale 1ns/1ns

module top_module ();
reg clk=0;
always #10 clk = ~clk;  // Create clock with period=20

// A testbench
   reg rst_n=0;
reg key_k1=1;
reg key_k2=1;
reg key_k3=1;
wire [2:0] sw_key=1;
wire [2:0] rgb1;
wire [2:0] rgb2;
wire [8:0] seg1_out_pins;
wire [8:0] seg2_out_pins;
reg uart_rx_cmd_status=0;
reg [39:0] uart_rx_cmd_data=0;
wire uart_tx_cmd_status;
wire [39:0] uart_tx_cmd_data;
wire [6:0] led;
initial begin
       #50 rst_n <= 1'b1;
#50 key_k1=0;
#10000000 key_k1=1;

$display ("The current time is (%0d ns)", $time);
#5000000 $finish;            // Quit the simulation
end

   setp_ctrl setp_rgb_ctrl(
      .clk(clk),
      .rst_n(rst_n),
      .key_k1(key_k1),
      .key_k2(key_k2),
      .key_k3(key_k3),
      .sw_key(sw_key),
      .led(led),
      .rgb1(rgb1),
      .rgb2(rgb2),
      .seg1_out_pins(seg1_out_pins),
      .seg2_out_pins(seg2_out_pins),

      .uart_rx_cmd_status(uart_rx_cmd_status),
      .uart_rx_cmd_data(uart_rx_cmd_data),
      .uart_tx_cmd_status(uart_tx_cmd_status),
      .uart_tx_cmd_data(uart_tx_cmd_data)
 );

initial begin
$dumpfile("step_ctrl.vcd");
$dumpvars(0, top_module);
end

endmodule

从仿真可以看到,按键操作后生成了指令包。


资源使用情况


上位机设计

上位机比较简单,主要是通过串口收发数据。这里采用QT编写。

RGB三个通道可以单独调节控制,在FPGA上通过按键调节的值也可以实时同步到QT程序中。

RGB三种颜色混合值使用QLabel控件显示了出来,可以和FPGA三色灯显示的颜色进行对比测试。

开源地址:https://gitee.com/bigstep/step-fpga-ctrl



注意:本项目依赖QT串口库 SerialBus,需要提前下载安装好。

target_link_libraries(step-ctrl PRIVATE Qt${QT_VERSION_MAJOR}::SerialBus)

QT控制页面C++源代码

#include "mainwindow.h"
#include "./ui_mainwindow.h"

#include <QDateTime>

MainWindow::MainWindow(QWidget *parent)
  : QMainWindow(parent)
  , ui(new Ui::MainWindow)
{
   ui->setupUi(this);
   m_r = 0;
   m_g = 0;
   m_b = 0;

   QPalette lcdpat = ui->redNumber->palette();
   lcdpat.setColor(QPalette::Normal, QPalette::WindowText, Qt::red);
   ui->redNumber->setPalette(lcdpat);
   lcdpat = ui->greenNumber->palette();
   lcdpat.setColor(QPalette::Normal, QPalette::WindowText, Qt::green);
   ui->greenNumber->setPalette(lcdpat);
   lcdpat = ui->blueNumber->palette();
   lcdpat.setColor(QPalette::Normal, QPalette::WindowText, Qt::blue);
   ui->blueNumber->setPalette(lcdpat);

   ui->rgbLabel->setStyleSheet("QLabel{background-color:rgb(0,0,0);}");

   ui->horizontalSliderR->setEnabled(false);
   ui->horizontalSliderG->setEnabled(false);
   ui->horizontalSliderB->setEnabled(false);

   serialStrList = m_serial.scanSerial();
   for (int i = 0; i < serialStrList.size(); i++) {
       ui->portComboBox->addItem(serialStrList[i]);
  }

   QFont font("Courier", 10);
   ui->textEdit->setFont(font);

   // 默认设置波特率为115200
   ui->baudComboBox->setCurrentIndex(5);

   connect(&m_serial, SIGNAL(readSignal()), this, SLOT(readSerialData()));

   m_timer.setSingleShot(true);
   connect(&m_timer, &QTimer::timeout, this, QOverload<>::of(&MainWindow::updateRGBToFPGA));
}

MainWindow::~MainWindow()
{
   m_timer.stop();
}

void MainWindow::updateEditText(QString text) {
   QString formattedDate = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
   QString originStr = ui->textEdit->toPlainText();
   originStr += formattedDate + " " + text + "\n";
   ui->textEdit->clear();
   ui->textEdit->setText(originStr);
   ui->textEdit->moveCursor(QTextCursor::End);
}

// 读取从自定义串口类获得的数据
void MainWindow::readSerialData()
{
   QByteArray recvArray = m_serial.getReadBuf();
   qDebug() << "Serial RECV:" << recvArray.toHex();
   updateEditText("-> " + recvArray.toHex());

   if (recvArray.size() == 7 && recvArray[0] == 0x3C && (unsigned char)recvArray[6] == 0xAA) { // <
       if (recvArray[1] == 0x71) { // RGB value update
           m_r = recvArray[2];
           m_g = recvArray[3];
           m_b = recvArray[4];
           // update UI value
           ui->redNumber->display(m_r);
           ui->greenNumber->display(m_g);
           ui->blueNumber->display(m_b);
           ui->horizontalSliderR->setValue(m_r);
           ui->horizontalSliderG->setValue(m_g);
           ui->horizontalSliderB->setValue(m_b);
           QString style = QString("QLabel{background-color:rgb(%1,%2,%3);}").arg(m_r).arg(m_g).arg(m_b);
           ui->rgbLabel->setStyleSheet(style);
      }
  }
   m_serial.clearReadBuf(); // 读取完后,清空数据缓冲区
}

// 打开 关闭串口
void MainWindow::on_openPortButton_clicked()
{
   if (ui->openPortButton->text() == tr("打开串口")) {
       if (m_serial.open(ui->portComboBox->currentText(), ui->baudComboBox->currentText().toInt())) {
           ui->portComboBox->setEnabled(false);
           ui->baudComboBox->setEnabled(false);
           ui->horizontalSliderR->setEnabled(true);
           ui->horizontalSliderG->setEnabled(true);
           ui->horizontalSliderB->setEnabled(true);
           ui->openPortButton->setText(tr("关闭串口"));
           updateEditText("串口打开成功");
      } else {
           QMessageBox::warning(this, "警告", "串口打开失败!");
           updateEditText("串口打开失败");
      }
  } else {
       m_serial.close();
       ui->portComboBox->setEnabled(true);
       ui->baudComboBox->setEnabled(true);
       ui->horizontalSliderR->setEnabled(false);
       ui->horizontalSliderG->setEnabled(false);
       ui->horizontalSliderB->setEnabled(false);
       ui->openPortButton->setText(tr("打开串口"));
  }
}

void MainWindow::updateRGBToFPGA() {
   QString style = QString("QLabel{background-color:rgb(%1,%2,%3);}").arg(m_r).arg(m_g).arg(m_b);
   ui->rgbLabel->setStyleSheet(style);
   QByteArray rgbArray;
   rgbArray.resize(7);
   rgbArray[0] = 0x3E; // >
   rgbArray[1] = 0x01;
   rgbArray[2] = m_r;
   rgbArray[3] = m_g;
   rgbArray[4] = m_b;
   rgbArray[5] = 0x00;
   rgbArray[6] = 0xAA;
   updateEditText("<- " + rgbArray.toHex());
   m_serial.sendData(rgbArray);
}

void MainWindow::on_horizontalSliderR_valueChanged(int value)
{
   if (m_r == value) return;
   m_r = (unsigned char)value;
   m_timer.start(100);
   ui->redNumber->display(value);
}

void MainWindow::on_horizontalSliderG_valueChanged(int value)
{
   if (m_g == value) return;
   m_g = (unsigned char)value;
   m_timer.start(100);
   ui->greenNumber->display(value);
}

void MainWindow::on_horizontalSliderB_valueChanged(int value)
{
   if (m_b == value) return;
   m_b = (unsigned char)value;
   m_timer.start(100);
   ui->blueNumber->display(value);
}


整体效果

两个RGB灯是相同的状态。

拨码开关需要拨到正确位置数码管和LED才能正确显示,按键才能操作。如果拨码开关设置错误,按键是无法操作的。



总结

第一次接触FPGA Verilog,之前都是做的软件方面的工作。

学习实践下来发现Verilog语法虽然不多,但是和软件编程还是有很大区别。Verilog写出的代码其实都是在描述电路,所以写代码的时候最好能清晰的知道具体的代码对应的电路,这个电路怎么运行的。而软件编程几乎都是顺序执行,和我们的大脑思考较为一致,相对比较容易容易。

有了这次的活动经验,知道FPGA的基本工作原理,以后的学习也有了方向。

最后非常感谢电子森林举办寒假练活动。以后有机会还要参加!




附件下载
add4_bin_impl1.jed
FPGA固件
step-ctrl-fpga.zip.temp
FPGA源代码
step-fpga-ctrl-master.zip
QT 上位机源代码
小脚丫学习笔记.pdf
自己整理的学习笔记
团队介绍
苏州大学电子信息
团队成员
bigzhu
电子爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号