🎉欢迎来到FPGA专栏~搭建串口收发与储存双口RAM系统
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
🎉 目录-串口收发与储存双口RAM系统
- 一、效果演示
- 二、基础知识
- 2.1 实现目标
- 2.2 所需基础模块
- 三、系统分析
- 四、代码编写
- 4.1 控制模块
- 4.2 顶层模块
- 五、仿真测试激励文件
- 5.1 key_model
- 5.2 testbench编写
- 5.3 仿真结果
- 六、板级验证
一、效果演示
🥝输入数据:
🥝输出数据:
🥝串口助手分析:
按下第一次按键,FPGA开始连续发送数据,按下第二次按键,FPGA停止发送数据。
二、基础知识
2.1 实现目标
使用按键消抖、串口发送与接收模块,以及双端口RAM模块实现串口发送数据到FPGA中,FPGA接收到数据后将数据存储在双口RAM中,当按下按键时FPGA将RAM中存储的数据再通过串口发送出去,再次按下按键后,FPGA停止发送数据。
2.2 所需基础模块
相关模块学习文章:
🥝【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现);
🥝【FPGA零基础学习之旅#13】串口发送模块设计与验证;
🥝【FPGA零基础学习之旅#14】串口发送字符串;
🥝【FPGA零基础学习之旅#15】串口接收模块设计与验证(工业环境);
🥝【FPGA零基础学习之旅#16】嵌入式块RAM-双口ram的使用。
三、系统分析
参考小梅哥FPGA设计的系统框图:
通过串口发送数据到FPGA中,FPGA接收到数据后将数据存储在双口RAM的一段连续空间中。当需要时,按下按键S0,则FPGA将RAM中存储的数据通过串口发送出去;再次按下S0,则停止数据发送。
进行功能划分:串口接收模块、按键消抖模块、RAM模块、串口发送模块以及控制模块。
在此给出所需使用的模块代码:
按键消抖模块:
//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
//
module KeyFilter(input Clk,input Rst_n,input key_in,output reg key_flag,output reg key_state
);//按键的四个状态localparamIDLE = 4'b0001,FILTER1 = 4'b0010,DOWN = 4'b0100,FILTER2 = 4'b1000;//状态寄存器reg [3:0] curr_st;//边沿检测输出上升沿或下降沿wire pedge;wire nedge;//计数寄存器reg [19:0]cnt;//使能计数寄存器reg en_cnt;//计数满标志信号reg cnt_full;//计数满寄存器//------<边沿检测电路的实现>------//边沿检测电路寄存器reg key_tmp0;reg key_tmp1;//边沿检测always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginkey_tmp0 <= 1'b0;key_tmp1 <= 1'b0;endelse beginkey_tmp0 <= key_in;key_tmp1 <= key_tmp0;end endassign nedge = (!key_tmp0) & (key_tmp1);assign pedge = (key_tmp0) & (!key_tmp1);//------<状态机主程序>------ //状态机主程序always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)begincurr_st <= IDLE;en_cnt <= 1'b0;key_flag <= 1'b0;key_state <= 1'b1;endelse begincase(curr_st)IDLE:beginkey_flag <= 1'b0;if(nedge)begincurr_st <= FILTER1;en_cnt <= 1'b1;endelsecurr_st <= IDLE;endFILTER1:beginif(cnt_full)beginkey_flag <= 1'b1;key_state <= 1'b0;curr_st <= DOWN;en_cnt <= 1'b0;end else if(pedge)begincurr_st <= IDLE;en_cnt <= 1'b0;endelsecurr_st <= FILTER1;endDOWN:beginkey_flag <= 1'b0;if(pedge)begincurr_st <= FILTER2;en_cnt <= 1'b1;endelsecurr_st <= DOWN;endFILTER2:beginif(cnt_full)beginkey_flag <= 1'b1;key_state <= 1'b1;curr_st <= IDLE;en_cnt <= 1'b0;end else if(nedge)begincurr_st <= DOWN;en_cnt <= 1'b0;endelsecurr_st <= FILTER2;enddefault:begincurr_st <= IDLE;en_cnt <= 1'b0;key_flag <= 1'b0;key_state <= 1'b1;endendcaseendend//------<20ms计数器>------ //20ms计数器//Clk 50_000_000Hz//一个时钟周期为20ns//需要计数20_000_000 / 20 = 1_000_000次always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)cnt <= 20'd0;else if(en_cnt)cnt <= cnt + 1'b1;elsecnt <= 20'd0;endalways@(posedge Clk or negedge Rst_n)beginif(!Rst_n)cnt_full <= 1'b0;else if(cnt == 999_999)cnt_full <= 1'b1;elsecnt_full <= 1'b0;endendmodule
串口接收模块:
//
//模块名称:串口接收模块(工业环境)
//
module uart_byte_rx(input Clk,//50Minput Rst_n,input [2:0] baud_set,input data_rx,output reg [7:0] data_byte,output reg Rx_Done
);reg s0_Rx,s1_Rx;//同步寄存器reg tmp0_Rx,tmp1_Rx;//数据寄存器reg [15:0]bps_DR;//分频计数器计数最大值reg [15:0]div_cnt;//分频计数器reg bps_clk;//波特率时钟reg [7:0]bps_cnt;reg uart_state;reg [2:0] r_data_byte [7:0];reg [2:0]START_BIT;reg [2:0]STOP_BIT;wire nedge;//--------<同步寄存器处理>--------
//用于消除亚稳态always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)begins0_Rx <= 1'b0;s1_Rx <= 1'b0;endelse begins0_Rx <= data_rx;s1_Rx <= s0_Rx;endend//--------<数据寄存器处理>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)begintmp0_Rx <= 1'b0;tmp1_Rx <= 1'b0;endelse begintmp0_Rx <= s1_Rx;tmp1_Rx <= tmp0_Rx;endend//--------<下降沿检测>-------- assign nedge = !tmp0_Rx & tmp1_Rx;//--------<div_cnt模块>--------
//得到不同计数周期的计数器always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)div_cnt <= 16'd0;else if(uart_state)beginif(div_cnt == bps_DR)div_cnt <= 16'd0;elsediv_cnt <= div_cnt + 1'b1;endelsediv_cnt <= 16'd0;end
//--------<bps_clk信号的产生>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)bps_clk <= 1'b0;else if(div_cnt == 16'd1)bps_clk <= 1'b1;elsebps_clk <= 1'b0;end//--------<bps_clk计数模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)bps_cnt <= 8'd0;else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2)))bps_cnt <= 8'd0;else if(bps_clk)bps_cnt <= bps_cnt + 1'b1;elsebps_cnt <= bps_cnt;end//--------<Rx_Done模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Rx_Done <= 1'b0;else if(bps_cnt == 8'd159)Rx_Done <= 1'b1;elseRx_Done <= 1'b0;end //--------<波特率查找表>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)bps_DR <= 16'd324;else begincase(baud_set)0:bps_DR <= 16'd324;1:bps_DR <= 16'd162;2:bps_DR <= 16'd80;3:bps_DR <= 16'd53;4:bps_DR <= 16'd26;default:bps_DR <= 16'd324;endcaseend end//--------<采样数据接收模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginSTART_BIT <= 3'd0;r_data_byte[0] <= 3'd0; r_data_byte[1] <= 3'd0;r_data_byte[2] <= 3'd0; r_data_byte[3] <= 3'd0;r_data_byte[4] <= 3'd0; r_data_byte[5] <= 3'd0;r_data_byte[6] <= 3'd0; r_data_byte[7] <= 3'd0;STOP_BIT <= 3'd0;endelse if(bps_clk)begincase(bps_cnt)0:beginSTART_BIT <= 3'd0;r_data_byte[0] <= 3'd0;r_data_byte[1] <= 3'd0;r_data_byte[2] <= 3'd0;r_data_byte[3] <= 3'd0;r_data_byte[4] <= 3'd0;r_data_byte[5] <= 3'd0;r_data_byte[6] <= 3'd0;r_data_byte[7] <= 3'd0;STOP_BIT <= 3'd0; end6,7,8,9,10,11:START_BIT <= START_BIT + s1_Rx;22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_Rx;38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_Rx;54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_Rx;70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_Rx;86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_Rx;102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_Rx;118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_Rx;134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_Rx;150,151,152,153,154,155:STOP_BIT <= STOP_BIT + s1_Rx;default:beginSTART_BIT <= START_BIT;r_data_byte[0] <= r_data_byte[0];r_data_byte[1] <= r_data_byte[1];r_data_byte[2] <= r_data_byte[2];r_data_byte[3] <= r_data_byte[3];r_data_byte[4] <= r_data_byte[4];r_data_byte[5] <= r_data_byte[5];r_data_byte[6] <= r_data_byte[6];r_data_byte[7] <= r_data_byte[7];STOP_BIT <= STOP_BIT;endendcaseendend//--------<数据状态判定模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)data_byte <= 8'd0;else if(bps_cnt == 8'd159)begindata_byte[0] <= r_data_byte[0][2];data_byte[1] <= r_data_byte[1][2];data_byte[2] <= r_data_byte[2][2];data_byte[3] <= r_data_byte[3][2];data_byte[4] <= r_data_byte[4][2];data_byte[5] <= r_data_byte[5][2];data_byte[6] <= r_data_byte[6][2];data_byte[7] <= r_data_byte[7][2];endelse;end//--------<uart_state模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)uart_state <= 1'b0;else if(nedge)uart_state <= 1'b1;else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2)))uart_state <= 1'b0;elseuart_state <= uart_state;endendmodule
串口发送模块:
//
//模块名称:串口发送模块
//
module uart_byte_tx(input Clk,input Rst_n,input [7:0] data_byte,input send_en,input [2:0] baud_set,output reg uart_tx,output reg Tx_Done,output reg uart_state
);reg bps_clk;//波特率时钟reg [15:0]div_cnt;//分频计数器reg [15:0]bps_DR;//分频计数最大值reg [3:0]bps_cnt;//波特率计数时钟//定义数据的起始位和停止位localparam START_BIT = 1'b0;localparam STOP_BIT = 1'b1;reg [7:0]r_data_byte;//数据寄存器//--------<uart状态模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)uart_state <= 1'b0;else if(send_en)uart_state <= 1'b1;else if(bps_cnt == 4'd11)//bps_cnt计数达到11次,即发送结束uart_state <= 1'b0;elseuart_state <= uart_state;end//--------<使能分频计数模块>-------
// assign en_cnt = uart_state;//--------<寄存待发送的数据,使数据保持稳定>--------always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)r_data_byte <= 8'd0;else if(send_en)r_data_byte <= data_byte;elser_data_byte <= r_data_byte;end//--------<波特率查找表>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)bps_DR <= 16'd5207;else begincase(baud_set)0:bps_DR <= 16'd5207;1:bps_DR <= 16'd2603;2:bps_DR <= 16'd1301;3:bps_DR <= 16'd867;4:bps_DR <= 16'd433;default:bps_DR <= 16'd5207;endcaseend end//--------<Div_Cnt模块>--------
//得到不同计数周期的计数器always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)div_cnt <= 16'd0;else if(uart_state)begin // assign en_cnt = uart_state;if(div_cnt == bps_DR)div_cnt <= 16'd0;elsediv_cnt <= div_cnt + 1'b1;endelsediv_cnt <= 16'd0;end
//--------<bps_clk信号的产生>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)bps_clk <= 1'b0;else if(div_cnt == 16'd1)bps_clk <= 1'b1;elsebps_clk <= 1'b0;end//--------<bps_cnt计数模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)bps_cnt <= 4'd0;else if(bps_cnt == 4'd11)//clr信号bps_cnt <= 4'd0;else if(bps_clk)bps_cnt <= bps_cnt + 1'b1;elsebps_cnt <= bps_cnt;end//--------<Tx_Done模块>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Tx_Done <= 1'b0;else if(bps_cnt == 4'd11)Tx_Done <= 1'b1;elseTx_Done <= 1'b0;end//--------<数据位输出模块-10选1多路器>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)uart_tx <= 1'b1;else begincase(bps_cnt)0:uart_tx <= 1'b1;1:uart_tx <= START_BIT;2:uart_tx <= r_data_byte[0];3:uart_tx <= r_data_byte[1];4:uart_tx <= r_data_byte[2];5:uart_tx <= r_data_byte[3];6:uart_tx <= r_data_byte[4];7:uart_tx <= r_data_byte[5];8:uart_tx <= r_data_byte[6];9:uart_tx <= r_data_byte[7];10:uart_tx <= STOP_BIT;default:uart_tx <= 1'b1;endcaseendendendmodule
四、代码编写
上述已给出按键消抖模块和串口收发模块,在本文中主要编写控制模块和顶层模块。
4.1 控制模块
为了实现FPGA将接收到的数据存储到双口RAM的一段连续空间中,就需要设计一个可以实现写地址数据自加的控制逻辑,且其控制信号为串口接收模块输出的Rx_Done信号。每来一个Rx_Done就表明接收成功一字节数,地址数进行加一:
assign wren = Rx_Done;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)wraddress <= 8'd0;else if(Rx_Done)wraddress <= wraddress + 1'b1;elsewraddress <= wraddress;
end
当按下按键S0,FPGA将RAM中存储的数据通过串口发送出去。需要实现按键按下即启动连续读操作,再次按下可暂停读操作:
reg do_send;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)do_send <= 1'd0;else if(Key_flag && !Key_state)do_send <= ~do_send;
endalways@(posedge Clk or negedge Rst_n)beginif(!Rst_n) rdaddress <= 8'd0;else if(do_send && Tx_Done)rdaddress <= rdaddress + 8'd1;elserdaddress <= rdaddress;
end
在仿真双端口RAM时发现其输出会延迟两个系统时钟周期。这是为了保证数据变化稳定之后才进行数据输出,所以在此将驱动Send_en的信号接两级寄存器进行延迟两拍。当按键按下后启动一次发送,然后判断上一字节是否发送结束,是则进行下一字节发送否则不进行下一次发送:
reg r0_send_done,r1_send_done;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginr0_send_done <= 1'b0;r1_send_done <= 1'b0;endelse beginr0_send_done <= (do_send && Tx_Done);r1_send_done <= r0_send_done; end
endalways@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Send_en <= 1'b0;else if(Key_flag && !Key_state)Send_en <= 1'b1;else if(r1_send_done)Send_en <= 1'b1;elseSend_en <= 1'b0;
end
为了保证RAM地址操作的有效性,在写地址和读地址代码部分加上范围限制:
//--------<dpram写地址加1>--------
always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)wraddress <= 8'd0;else if(Rx_Done)wraddress <= wraddress + 1'b1;else if(wraddress > 8'd255) //当写地址大于配置ip核时的值时,返回到0地址;wraddress <= 8'd0;elsewraddress <= wraddress;
end//--------<dpram读地址加1>--------
always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)rdaddress <= 8'd0;else if(do_send && Tx_Done)rdaddress <= rdaddress + 8'd1;else if(rdaddress > 8'd255) //当读地址大于255时,返回到0地址;rdaddress <= 8'd0;elserdaddress <= rdaddress;
end
完整的控制模块代码:system_ctrl.v
module system_ctrl(input Clk,input Rst_n,input Key_flag,input Key_state,input Rx_Done,input Tx_Done,output wren,output reg Send_en,output reg [7:0] rdaddress,output reg [7:0] wraddress
);assign wren = Rx_Done;reg do_send;reg r0_send_done;reg r1_send_done;//--------<dpram写地址加1>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)wraddress <= 8'd0;else if(Rx_Done)wraddress <= wraddress + 1'b1;else if(wraddress > 8'd255) //当写地址大于配置ip核时的值时,返回到0地址;wraddress <= 8'd0;elsewraddress <= wraddress;end//--------<翻转标志信号>--------
//按下一次按键开始连续发送数据,再按一次按键停止发送;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)do_send <= 1'b0;else if(Key_flag && !Key_state)do_send <= ~do_send;end//--------<dpram读地址加1>-------- always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)rdaddress <= 8'd0;else if(do_send && Tx_Done)rdaddress <= rdaddress + 8'd1;else if(rdaddress > 8'd255) //当读地址大于255时,返回到0地址;rdaddress <= 8'd0;elserdaddress <= rdaddress;end//--------<RAM的两拍延迟>--------
//双端口RAM的输出延迟两个系统时钟周期;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginr0_send_done <= 1'b0;r1_send_done <= 1'b1;endelse beginr0_send_done <= (do_send && Tx_Done);r1_send_done <= r0_send_done;endend//--------<按键控制与连续读操作>--------
//Send_en由按键信号和r1_send_done信号同时控制;
//r1_send_done信号使得串口连续读取dpram的数据;always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Send_en <= 1'b0;else if(Key_flag && !Key_state)Send_en <= 1'b1;else if(r1_send_done)Send_en <= 1'b1;elseSend_en <= 1'b0;endendmodule
控制模块的RTL视图:
4.2 顶层模块
将串口接收模块、按键消抖模块、RAM模块、串口发送模块以及控制模块例化到顶层模块中。
uart_system_top.v:
module uart_system_top(input Clk,input Rst_n,input key_in,input uart_rx,output uart_tx
);wire [7:0]rx_data;wire [7:0]tx_data;wire Key_flag;wire Key_state;wire Rx_Done;wire Tx_Done;wire wren;wire Send_en;wire [7:0]rdaddress;wire [7:0]wraddress;uart_byte_rx uart_byte_rx(.Clk(Clk),.Rst_n(Rst_n),.baud_set(3'd0),.data_rx(uart_rx),.data_byte(rx_data),.Rx_Done(Rx_Done));dpram dpram(.clock(Clk),.data(rx_data),.rdaddress(rdaddress),.wraddress(wraddress),.wren(wren),.q(tx_data));KeyFilter KeyFilter(.Clk(Clk),.Rst_n(Rst_n),.key_in(key_in),.key_flag(Key_flag),.key_state(Key_state));uart_byte_tx uart_byte_tx(.Clk(Clk),.Rst_n(Rst_n),.data_byte(tx_data),.send_en(Send_en),.baud_set(3'd0),.uart_tx(uart_tx),.Tx_Done(Tx_Done),.uart_state());system_ctrl system_ctrl(.Clk(Clk),.Rst_n(Rst_n),.Key_flag(Key_flag),.Key_state(Key_state),.Rx_Done(Rx_Done),.Tx_Done(Tx_Done),.wren(wren),.Send_en(Send_en),.rdaddress(rdaddress),.wraddress(wraddress));endmodule
顶层模块的RTL视图:
五、仿真测试激励文件
5.1 key_model
key_model仿真模型用于有按键控制信号的项目进行仿真测试,模拟实际情况中的按键抖动。 在仿真时将该模型也添加到工程中使用。
key_model.v:
`timescale 1ns/1nsmodule key_model(press,key);input press;output reg key;reg [15:0]myrand;initial beginkey = 1'b1; endalways@(posedge press)press_key;task press_key;beginrepeat(50)beginmyrand = {$random}%65536;//0~65535;#myrand key = ~key; endkey = 0;#25000000;repeat(50)beginmyrand = {$random}%65536;//0~65535;#myrand key = ~key; endkey = 1;#25000000; end endtaskendmodule
5.2 testbench编写
完整的仿真测试激励文件:
uart_system_top_tb.v:
`timescale 1ns/1ns
`define clock_period 20module uart_system_top_tb;reg Clk;reg Rst_n;wire Key_in;wire uart_rx;wire uart_tx;reg [7:0]data_byte_t;reg send_en;wire [2:0]baud_set;wire Tx_Done;reg press;assign baud_set = 3'd0;uart_system_top uart_system_top(.Clk(Clk),.Rst_n(Rst_n),.key_in(Key_in),.uart_rx(uart_tx),.uart_tx(uart_rx));uart_byte_tx uart_byte_tx(.Clk(Clk),.Rst_n(Rst_n),.data_byte(data_byte_t),.send_en(send_en),.baud_set(baud_set),.uart_tx(uart_tx),.Tx_Done(Tx_Done),.uart_state());key_model key_model(.press(press),.key(Key_in));initial Clk = 1;always#(`clock_period / 2) Clk = ~Clk;initial beginRst_n = 1'b0;press = 0;data_byte_t = 8'd0;send_en = 1'd0;#(`clock_period*20 + 1 );Rst_n = 1'b1;#(`clock_period*50);data_byte_t = 8'haa;send_en = 1'd1;#`clock_period;send_en = 1'd0; @(posedge Tx_Done)#(`clock_period*5000);data_byte_t = 8'h55;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)#(`clock_period*5000);data_byte_t = 8'h33;send_en = 1'd1;#`clock_period;send_en = 1'd0; @(posedge Tx_Done)#(`clock_period*5000);data_byte_t = 8'haf;send_en = 1'd1;#`clock_period;send_en = 1'd0;@(posedge Tx_Done)#(`clock_period*5000);press = 1;#(`clock_period*3)press = 0;#(`clock_period*2000000)$stop;endendmodule
5.3 仿真结果
六、板级验证
🥝输入数据储存到双口RAM中:
🥝输出RAM中的数据:
🧸结尾
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【FPGA零基础学习之旅#14】串口发送字符串
- 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制