2021寒假在家一起练项目【4】——沈皓
基于小脚丫FPGA开发板实现温度、OLED显示、实时时钟、串口通信等功能
标签
FPGA
数字逻辑
Stephan
更新2021-02-28
921

内容介绍

前言:

      很早就听说过FPGA,那时对FPGA的唯一印象就是用代码写硬件,然而这很让人费解。在上个学期的课程中一门数字逻辑设计的课程实验中便使用到了FPGA,通过几次数电实验,我渐渐地对FPGA有了初步的认识并产生了兴趣。在本次“寒假一起练活动中”,我对FPGA有了更近一步的理解,其不同于51,STM32等单片机开发,FPGA并行执行的逻辑与单片机的串行执行逻辑有很大区别,需要用新的思维和思路去完成本次的任务,然而本次项目对于几乎没有任何FPGA基础的我而言很困难,陌生的编程语言、开发环境不熟悉、各种项目要求都增加了这个项目的挑战性。

项目要求:

1.实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;

2.实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;

3.定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;

4.PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;

5.音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

 

设计思路:

      我把整个项目要求分为了以下几个模块进行:OLED显示、时钟信号、DS18B20温度采集、串口通信、蜂鸣器使能、按键输入。

FsvWRtZRrO4vfJGNzDeAG8Uz46Lj

      当然仅仅是做一个小小的模块规划是完全不够的,由于所有的代码都需要使用到Verilog这门全新的语言,所以我不得不花一些时间在Bilibili和CSDN上学习这门语言的基础语法,并且看各个博主的代码以理解其编程思路,从0开始完成项目。在学习过程中遇到了很多问题,比如阻塞赋值和非阻塞赋值,这二者在我初学的时候被弄混了很多次。此外,从串行执行的逻辑思维转换为并行执行的逻辑思维,我花了不少时间过渡去理解。

      对于这个项目,我的思路是以OLED显示为中心,其他功能围绕OLED展开设计,因为OLED是整个项目中最直观的能看出效果的模块。

      其次就是实时时钟,时钟这个素材也经常在蓝桥杯比赛中作为重点考察项目,然而区别就是,并没有DS1302或者STM32内部RTC这种现成的时钟,需要自己产生时钟信号去计数,这也是难点之一,尽管没有要求,我还是精确到秒进行显示,这样比较直观。

      接下来的温度显示,按键设置,都是基于OLED显示去进行操作的,所以我打算把串口通信和蜂鸣器报响的功能放在了最后去完成。

      串口通信的上位机我打算采用STC的串口助手,也就是51单片机编程烧录的那个软件。

      以上是我大体的项目完成思路。

资源占用:

资源报告图如下:

FmCcxTEx66F_VVK6OT-WxGiIxnv4

      显而易见的,作为一名初学者我还并不能够很好地利用资源,也能够看出资源占用很多,不过能完成项目,这也是达到了我的要求。

代码实现:

以下只列出部分代码:

(1)1KHz时钟信号以及秒钟时序

在获得1KHz的信号后,也就能够确定秒了,接下来分钟和小时的设定只需要按照时钟的进制去完成即可。

always @(posedge Clk_1s, negedge Rst_n)//Second
        if (!Rst_n)
            sec_l <= 4'd0;
        else if (sec_l == 4'd9)
            sec_l <= 4'd0;
		  else if (key1)
            sec_l <= timer_sec_l;
        else
            sec_l <= sec_l + 1'b1;

always @(posedge Clk, negedge Rst_n)//1Hz信号
        if (!Rst_n)begin
            Clk_1s <= 1'd0;
            cnt_1s <= 24'd0;
        end
        else if (cnt_1s == 24'd12_000_000 - 1) begin
            Clk_1s <= 1'd1;
            cnt_1s <= 24'd0;
        end
        else begin
            Clk_1s <= 1'd0;
            cnt_1s <= cnt_1s + 1'b1;
        end

 

OLED显示部分:

INIT:begin	//初始化状态
     case(cnt_init)
	5'd0:	begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end	//复位有效
        5'd1:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于3us
	5'd2:	begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end//复位恢复
	5'd3:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于220us
	5'd4:	begin if(cnt>=INIT_DEPTH) begin	
			cnt <= 1'b0;
			cnt_init <= cnt_init + 1'b1;
		      end 
                      else begin	
		        cnt <= cnt + 1'b1; num_delay <= 16'd5;
		        oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; 
                        state_back <= INIT;
			end
		end
	5'd5:	begin cnt_init <= 1'b0; state <= MAIN; end	//初始化完成,返回MAIN状态
	default: state <= IDLE;
     endcase
end

SCAN:begin	//刷屏状态,从RAM中读取数据刷屏
	if(cnt_scan == 5'd11) begin
	    if(num) cnt_scan <= 5'd3;
	    else cnt_scan <= cnt_scan + 1'b1;
	end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
	else cnt_scan <= cnt_scan + 1'b1;
	case(cnt_scan)
	5'd0:	begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end	//定位列页地址
	5'd1:	begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end	//定位行地址低位
	5'd2:	begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end	//定位行地址高位
        5'd3:	begin num <= num - 1'b1;end
	5'd4:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
	5'd5:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
	5'd6:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
	5'd7:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
        5'd8:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
	5'd9:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
	5'd10:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
	5'd11:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
	5'd12:	begin state <= MAIN; end
	default: state <= IDLE;
        endcase
end

//我的OLED显示//
 case(cnt_main)	//MAIN状态
	5'd0: begin state <= INIT; end
	5'd1: begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; 
              char <= "      MAIN      ";state <= SCAN; end
	5'd2: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; 
              char <= "     :   :      ";state <= SCAN; end
	5'd3: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; 
              char <= " TEMP  :   .  C ";state <= SCAN; end
        5'd4: begin y_p <= 8'hb2; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd 1; 
              char <= sec_l; state <= SCAN; end
	5'd5: begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; 
              char <= sec_h; state <= SCAN; end
	5'd6: begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; 
              char <= min_l; state <= SCAN; end
	5'd7: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; 
              char <= min_h; state <= SCAN; end
	5'd8: begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; 
              char <= hour_l; state <= SCAN; end
	5'd9: begin y_p <= 8'hb2; x_ph <= 8'h11; x_pl <= 8'h00; num <= 5'd 1; 
              char <= hour_h; state <= SCAN; end
	5'd10:begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; 
              char <= temp_h; state <= SCAN; end
	5'd11:begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1;
              char <= temp_l; state <= SCAN; end
	5'd12:begin y_p <= 8'hb3; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd 1; 
              char <= temp_s; state <= SCAN; end   		
        default: state <= IDLE;
															  
endcase

DS18B20温度采集:

采用One Wire协议(底层过于冗余不放出):

module temperature(Clk,Rst_n,one_wire,temp_h,temp_l,temp_s);
    input Clk;
    input Rst_n;
    inout one_wire;
    output [3:0]temp_h;
    output [3:0]temp_l;
    output [3:0]temp_s;
    
    wire [15:0]data_out;

    DS18B20 DS18B20(
        .clk(Clk),			      // system clock
        .rst_n(Rst_n),			   // system reset, active low
        .one_wire(one_wire),		// ds18b20z one-wire-bus
        .data_out(data_out)		// ds18b20z data_out
    );   

    wire temperature_flag = data_out[15:11]? 1'b0:1'b1;
    wire [10:0] temperature_code = temperature_flag? data_out[10:0]:(~data_out[10:0])+1'b1; 
    wire [20:0] bin_code = temperature_code * 16'd625;
    wire [24:0] bcd_code; //十位[23:20],个位[19:16],小数位[14:12]    
    reg [45:0]shift_reg;
    
    bin_to_bcd bin_to_bcd_uut(
        .rst_n(Rst_n),	      // system reset, active low
        .bin_code(bin_code),	// binary code
        .bcd_code(bcd_code)	// bcd code
    );
    
    assign temp_h[3:0] = bcd_code[23:20];
    assign temp_l[3:0] = bcd_code[19:16];
    assign temp_s[3:0] = bcd_code[15:12];

endmodule

此外还涉及到二进制转BCD码:

module bin_to_bcd #
(
parameter B_SIZE = 21
)
(
input				rst_n,			// system reset, active low
input		[B_SIZE-1:0]	bin_code,		// binary code
output	reg	[B_SIZE+3:0]	bcd_code		// bcd code
);

reg		[2*B_SIZE+3:0]		shift_reg;   
always@(bin_code or rst_n)begin      
	shift_reg= {25'h0,bin_code};          
	if(!rst_n) bcd_code <= 0;      
	else begin         
	repeat(B_SIZE)//repeat B_SIZE times
		begin                                
		if (shift_reg[24:21] >= 5) shift_reg[24:21] = shift_reg[24:21] + 2'b11;
		if (shift_reg[28:25] >= 5) shift_reg[28:25] = shift_reg[28:25] + 2'b11;
		if (shift_reg[32:29] >= 5) shift_reg[32:29] = shift_reg[32:29] + 2'b11;
		if (shift_reg[36:33] >= 5) shift_reg[36:33] = shift_reg[36:33] + 2'b11;
		if (shift_reg[40:37] >= 5) shift_reg[40:37] = shift_reg[40:37] + 2'b11;
		if (shift_reg[44:41] >= 5) shift_reg[44:41] = shift_reg[44:41] + 2'b11;
		shift_reg = shift_reg << 1; 
		end         
		bcd_code<=shift_reg[45:21];   
	end  
end

endmodule

在单片机编程中由于底层都是事先给好的,所以几乎没有研究过底层,报错进制转换等,深入了解后受益匪浅。

串口通信:

串口通信发送的音频文件为字符串  !!%%&&%$$##""!

直接在STC串口助手中发送到FPGA上即可。

波特率为9600,多次测试发现9600波特率的误码率很低。

上报温度信息:

    always @(posedge Clk, negedge Rst_n)
        if (!Rst_n)
            timeout_flag <= 1'b0;
        else if (timeout)
            timeout_flag <= 1'b1;
        else if (send_out)
            timeout_flag <= 1'b0;
        else
            timeout_flag <= timeout_flag;
            
    always @(posedge Clk, negedge Rst_n)
        if (!Rst_n)
            cnt <= 6'd0;
        else if (cnt == 6'd45)
            cnt <= 6'd0;
        else if (Tx_Done)
            cnt <= cnt + 1'b1;        
        else
            cnt <= cnt;
          
    always @(posedge Clk, negedge Rst_n)
        if (!Rst_n)
            Send_En <= 1'b0;
        else if (timeout_flag && !send_out)
            Send_En <= 1'b1;        
        else 
            Send_En <= 1'b0;
        
    always @(posedge Clk, negedge Rst_n)
        if (!Rst_n) begin
            tx_data <= 8'h00;
            send_out <= 1'b0;
        end
        else if (timeout_flag)
            case (cnt)
                 0: tx_data <= "T";
                 1: tx_data <= "E";
                 2: tx_data <= "M";
                 3: tx_data <= "P"; 
                 4: tx_data <= 8'hA3;//:
                 5: tx_data <= 8'hBA;
                 6: tx_data <= 8'h0D;//
                 7: tx_data <= 8'h30 + temp_h;
                 8: tx_data <= 8'h30 + temp_l;
                 9: tx_data <= 8'h2E;//.
                10: tx_data <= 8'h30 + temp_s;
                11: tx_data <= 8'h0D;//
                12: tx_data <= " ";
                13: tx_data <= "C";
                14: tx_data <= 8'h0D;//\r
                15: tx_data <= 8'h0A;//\n
                16: begin tx_data <= 8'h00; send_out <= 1'b1;end
                default: tx_data <= 8'h00;
            endcase
        else
            tx_data <= 8'h00;

数据接收:

//数据处理,将异步输入信号转化为同步输入信号//

    reg s0_Rs232_Rx,s1_Rs232_Rx; //同步寄存器
    always@(posedge Clk or negedge Rst_n)
        if(!Rst_n)begin
            s0_Rs232_Rx <= 1'b0;
            s1_Rs232_Rx <= 1'b0; 
        end
        else begin
            s0_Rs232_Rx <= Rs232_Rx;
            s1_Rs232_Rx <= s0_Rs232_Rx;
        end
        
    reg tmp0_Rs232_Rx,tmp1_Rs232_Rx; //数据寄存器     
    always@(posedge Clk or negedge Rst_n)//数据寄存器
        if(!Rst_n)begin
            tmp0_Rs232_Rx <= 1'b0;
            tmp1_Rs232_Rx <= 1'b0; 
        end
        else begin
            tmp0_Rs232_Rx <= s1_Rs232_Rx;
            tmp1_Rs232_Rx <= tmp0_Rs232_Rx;
        end

//数据接收状态的判断

  assign nedege = !tmp0_Rs232_Rx && tmp1_Rs232_Rx;

  always@(posedge Clk or negedge Rst_n)//数据接收状态
	if(!Rst_n) begin
		uart_state <= 1'b0;
		display_Flag <= 1'b0;
		end
	else if(nedege) begin
		uart_state <= 1'b1;
		display_Flag <= 1'b1;
		end
	else if(Rx_Done || (bps_cnt == 8'd12 && (START_BIT > 2))) begin
		uart_state <= 1'b0;
		display_Flag <= 1'b0;
		end
	else
		uart_state <= uart_state;

蜂鸣器音调:

调整PWM高电平的占空比,就能够达到改变音调的效果。

module Buzzer(Clk,Rst_n,data_in,buzz_en,pwm_out,
sec_l,
sec_h,
min_l,
min_h,
hour_l,
hour_h

);
    input Clk;
    input Rst_n;
    input [7:0]data_in;
    input buzz_en;
    
    output reg pwm_out;
    
    input [3:0]sec_l;
    input [3:0]sec_h;
    input [3:0]min_l;
    input [3:0]min_h;
    input [3:0]hour_l;
    input [3:0]hour_h;
    
   
    
    parameter L1 = 8'b0001_0001;
    parameter L2 = 8'b0001_0010;
    parameter L3 = 8'b0001_0011;
    parameter L4 = 8'b0001_0100;
    parameter L5 = 8'b0001_0101;
    parameter L6 = 8'b0001_0110;
    parameter L7 = 8'b0001_0111;
    parameter M1 = 8'b0010_0001;
    parameter M2 = 8'b0010_0010;
    parameter M3 = 8'b0010_0011;
    parameter M4 = 8'b0010_0100;
    parameter M5 = 8'b0010_0101;
    parameter M6 = 8'b0010_0110;
    parameter M7 = 8'b0010_0111;
    parameter H1 = 8'b0100_0001;
    parameter H2 = 8'b0100_0010;
    parameter H3 = 8'b0100_0011;
    parameter H4 = 8'b0100_0100;
    parameter H5 = 8'b0100_0101;
    parameter H6 = 8'b0100_0110;
    parameter H7 = 8'b0100_0111;
    
    reg [15:0]tone;
    reg [15:0]cnt;
    
    always@(data_in) begin
                case (data_in)
                    L1: tone = 16'd45872;
                    L2: tone = 16'd40858;
                    L3: tone = 16'd36408;
                    L4: tone = 16'd34364;
                    L5: tone = 16'd30612;
                    L6: tone = 16'd27273;
                    L7: tone = 16'd24296;
                    M1: tone = 16'd22931;
                    M2: tone = 16'd20432;
                    M3: tone = 16'd18201;
                    M4: tone = 16'd17180;
                    M5: tone = 16'd15306;
                    M6: tone = 16'd13636;
                    M7: tone = 16'd12148;
                    H1: tone = 16'd11478;
                    H2: tone = 16'd10215;
                    H3: tone = 16'd9101;
                    H4: tone = 16'd8590;
                    H5: tone = 16'd7653;
                    H6: tone = 16'd6818;
                    H7: tone = 16'd6074;
                    default: tone = 16'd0;
                endcase          
        end
            
    
    always @(posedge Clk, negedge Rst_n)
        if (!Rst_n)
            cnt <= 16'd0;
        else if (cnt == tone - 1)
            cnt <= 16'd0;
        else
            cnt <= cnt + 1'b1;
            
    always @(posedge Clk, negedge Rst_n)
        if (!Rst_n)
            pwm_out <= 1'b0;
        else if(sec_h==4'b0000 && 
                min_h==4'b0000 && min_l==4'b0000 &&
                hour_h==4'b0000 && hour_l==4'b0000)begin
            if (cnt <= (tone/2) && buzz_en)
                pwm_out <= 1'b1;
            else
                pwm_out <= 1'b0;
        end
    

endmodule

 

 

 

按键部分:

参考了很多别人的代码和官方例程:

always @(posedge Clk,negedge Rst_n)
    if (!Rst_n)begin
        state = IDEL;
        key_flag <= 1'b0;
        key_state <= 1'b1;
        en_cnt = 1'b1;
    end
    else begin
        case(state)
            IDEL: begin
                key_flag <= 1'b0;
                if (nedge)begin
                    state <= FILTER0;
                    en_cnt <= 1'b1;
                end
                else
                    state <= IDEL;
            end
            FILTER0: begin
                if (cnt_full)begin
                    key_flag <= 1'b1;
                    key_state <= 1'b0;
                    en_cnt <=1'b0;
                    state <= DOWN;
                end
                else if (pedge)begin
                    state <= IDEL;
                    en_cnt <= 1'b0;
                end
                else
                    state <= FILTER0;
            end
            DOWN: begin
                key_flag = 1'b0;
                if (pedge)begin
                    state <= FILTER1;
                    en_cnt <= 1'b1;
                end
                else
                    state = DOWN;
            end
            FILTER1: begin
                if (cnt_full) begin
                    key_flag <= 1'b1;
                    key_state <= 1'b1;
                    state <= IDEL;
                    en_cnt <= 1'b0;
                end
                else if(nedge)begin
                    en_cnt <= 1'b0;
                    state = DOWN;
                end                    
                else
                    state <= FILTER1;
            end        
            default: begin
                state <= IDEL;
                en_cnt <= 1'b0;
                key_flag = 1'b0;
                key_state = 1'b1;
            end
        endcase
    end

以下为按键对边界值的设置:

此处列出“加”按键,“减”按键同理

initial begin
        timer[0] = 4'd1;
        timer[1] = 4'd3;
        timer[2] = 4'd5;
        timer[3] = 4'd9;
        timer[4] = 4'd5;
        timer[5] = 4'd0;
    end

    always @(posedge Clk or negedge Rst_n)
        if (!Rst_n)
            i <= 0;
        else if (key4_in)
            if (i == 5)
                i <= 0;
            else 
                i <= i + 1;
        else
            i <= i;
        
    always @(posedge Clk)
        if (key3_in)
            case (i)
                0: 
                if (timer[i] == 4'd2)
                    timer[i] <= 4'd0;
                else
                    timer[i] <= timer[i] + 1'b1; 
                1:
                if (timer[0] == 4'd2)begin
                  if (timer[i] == 4'd3)
                        timer[i] <= 4'd0;
                  else
                    timer[i] <= timer[i] + 1'b1; 
                end
                else begin
                  if (timer[i] == 4'd9)
                        timer[i] <= 4'd0;
                     else
                         timer[i] <= timer[i] + 1'b1; 
                end
                2:
                if (timer[i] == 4'd5)
                    timer[i] <= 4'd0;
                else
                    timer[i] <= timer[i] + 1'b1; 
                4:
                if (timer[i] == 4'd5)
                    timer[i] <= 4'd0;
                else
                    timer[i] <= timer[i] + 1'b1; 
                default: 
                    if (timer[i] == 4'd9)
                        timer[i] <= 4'd0;
                     else
                         timer[i] <= timer[i] + 1'b1; 
            endcase   

 

难点和问题解决办法:

(1)从单片机的编程思路转变到FPGA的编程思路。

-> 这需要进行很多练习,同时借鉴别人的代码,看看别人对同一个功能是什么样的思路。

(2)时序问题

-> 时序就是FPGA的心脏,一个稳定可靠的时序将会大大提高代码的稳定性,我通过查阅资料解决。

(3)各种意想不到的报错

-> 在编程过程中经常出现各种报错,我通过复制错误在网络搜索,后发现多为语法问题以及代码不规范。

 

项目心得:

     第一次接触FPGA,感触颇多,在整个项目中我不断在想这些功能如果是在51单片机或者STM32上实现,那么项目会轻松很多,不过这也是该项目的挑战性所在,在这个全新的平台,让曾经广泛使用的功能也变得不会用,变得陌生,这不得不每个功能,求我需要深入去了解每个功能的原理,这对我们作为学生来说是很重要也很有意义的一步。

      此外硬件的代码逻辑完全不同于C语言逻辑,尽管Verilog是一门类C语言。还有就是时序,极其重要,也是FPGA的核心部分。

      总而言之,通过本次项目,我对FPGA有了进一步的认识,学会了很多知识。

附件下载

STEP_Project.sof
sof下载文件,由于整个工程超过10M,所以只上传下载文件。
STEP_Project.pof
pof下载文件,由于整个工程超过10M,所以只上传下载文件。

团队介绍

CUIT电子工程学院
团队成员
沈皓
一个大二的电子人~

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号