FPGA初学记录——可乐机系统搭建

FPGA初学记录——可乐机系统搭建

野火征途Pro开发板教程——状态机拓展训练,可乐机系统搭建



前言

最近在学习FPGA,因为研究生需要用到,所用的开发板是野火的征途Pro,也是在跟着野火的教程学习,学到了状态机,看到拓展训练,是一个比较综合的练习,可以回顾前面学的按键消抖和计数器的一些知识,还有一些基本的语法,感觉有必要记录下来,说不定以后会有用。

一、问题简述

以状态机为核心搭建一个小系统:以可乐机为背景,一瓶可乐的价格是2.5元。用按键控制投币(加入按键消抖功能),可以投0.5元硬币和1元硬币,投入0.5元后亮一个灯,投入1元后亮2个灯,投入1.5元亮3个灯,投入2元亮4个灯,如果投币后10s不再继续进行投币操作则可乐机回到初始状态。投入2.5元后可乐不找零,此时led灯实现双向流水操作,流水10s后自动停止。这里也有复位键,其功能是终止本次投币操作,使可乐机立刻回到初始状态。

二、功能解析和源代码

1.功能解析

1、首先要求是以状态机为核心,那么可能得写一个基本的状态机,分析题目可知有5个状态,分别是IDLE-HALF-ONE-ONE_HALF-TWO,具体解析可以参考野火的教程,有详细的解析,这里就不再过多赘述。
2、然后要求用按键控制投币,不同价格对应不同的灯亮,其实就是按键控制led的亮灭(加入按键消抖功能),这个好解决,不同的状态控制不同组合的led灯就行。
3、led灯组合形式有7种,初始状态-全灭、0.5元-亮1个灯、1元-亮2个灯、1.5元-亮三个灯、2元-亮四个灯,2.5元-单向流水灯,3元-双向流水灯。比较难的点主要在流水灯,单向流水灯野火教程里有详细解析,双向流水灯是流水灯那一章的拓展训练,搞懂原理分析之后可以解决。
4、流水10s后自动停止,虽然题干中说了挺多个10s后停止,但思路可以简单点,就是10s没操作就回到初始状态。只要没有投币,即按键没有按下,开始计数,计数期间只要有按键按下就计数清零,如果10s没操作,就设置一个标志位,利用这个标志位使得状态返回初始状态,灯全灭即可。因为10s有点长,为了提高调试的效率,所以改成了5s。

2.源代码——整体代码

先把整体代码奉上。

module complex_fsm_system
#(
    parameter CNT_MAX = 20'd999_999, // 按键消抖计数器
    parameter CNT_MAX_WATER = 25'd24_999_999, // 流水灯计数器
    parameter CNT_MAX_INIT = 28'd249_999_999
)
(
    input wire sys_clk, //系统时钟50MHz
    input wire sys_rst_n, //全局复位
    input wire pi_money_one, //投币1元
    input wire pi_money_half, //投币0.5元
   
    output wire [3:0] led_out // 输出控制led灯
);

//只有6种状态,使用独热码
parameter IDLE = 5'b00001;
parameter HALF = 5'b00010;
parameter ONE = 5'b00100;
parameter ONE_HALF = 5'b01000;
parameter TWO = 5'b10000;
/*10s归零 定义中间参数*/
reg [27:0] cnt_init;
reg cnt_init_flag;
/*流水灯 定义中间参数*/
reg     [24:0]  cnt         ;
reg             cnt_flag    ;
reg             cnt_flag_left;
/*按键消抖 定义中间参数*/
reg [19:0] cnt_20ms; // 按键消抖计数器
reg key_flag;//1:表示消抖后检测到按键被按下;0:表示没有检测到按键被按下

/*状态机 定义中间参数*/
reg [4:0] state;
reg [3:0] led_out_reg;
reg po_money; //1:表示找零;0:表示不找零
reg po_cola; //1:出可乐;0:不出可乐
wire [1:0] pi_money;
/**********************10s归零代码****************************/
//cnt_init:计数器计数10s
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_init <= 28'b0;
    else if(pi_money_half == 1'b0 || pi_money_one == 1'b0)
        cnt_init <= 28'b0;
    else if(cnt_init == CNT_MAX_INIT)
        cnt_init <= cnt_init;
    else 
        cnt_init <= cnt_init + 1'b1;
        
//cnt_init_flag:计数器计数满10s标志信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_init_flag <= 1'b0;
    else    if(cnt_init == CNT_MAX_INIT)
        cnt_init_flag <= 1'b1;
    else
        cnt_init_flag <= 1'b0;
/************************************************************************流水灯代码****************************************************************************/
//cnt:计数器计数500ms
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 25'b0;
    else    if(cnt == CNT_MAX_WATER)
        cnt <= 25'b0;
    else  
        cnt <= cnt + 1'b1;

//cnt_flag:计数器计数满500ms标志信号
//cnt_flag_left:左移的标志信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else    if(cnt == CNT_MAX_WATER - 1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag_left <= 1'b0;
    else if((led_out_reg == 4'b1000 || led_out_reg == 4'b1111) && cnt_flag == 1'b1)
        cnt_flag_left <= 1'b1;
    else if(led_out_reg == 4'b0001 && cnt_flag == 1'b1 && cnt_flag_left == 1'b1)
        cnt_flag_left <= 1'b0;
        
/************************************************************************按键消抖代码****************************************************************************/
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else if(pi_money_half == 1'b1 && pi_money_one == 1'b1)
        cnt_20ms <= 20'b0;
    else if((cnt_20ms == CNT_MAX) && ((pi_money_half == 1'b0) || (pi_money_one == 1'b0)))
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else if(cnt_20ms == CNT_MAX-1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

/************************************************************************状态机代码****************************************************************************/
//pi_money:为了减少变量的个数,我们使用位拼接把输入的两个1bit信号拼接成1个2bit信号
//投币方式可以为:不投币(00)、投0.5元(01)、投1元(10),每次只投一个币
assign pi_money = {!pi_money_one&&key_flag,!pi_money_half&&key_flag};
//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state <= IDLE; 
    else    case(state)
                IDLE  : if(pi_money == 2'b01)
                            state <= HALF;
                        else if(pi_money == 2'b10)
                            state <= ONE;
                        else if(cnt_init_flag == 1'b1)
                            state <= IDLE;
                        else
                            state <= IDLE;
                
                HALF  : if(pi_money == 2'b01)
                            state <= ONE;
                        else if(pi_money == 2'b10)
                            state <= ONE_HALF;
                        else if(cnt_init_flag == 1'b1)
                            state <= IDLE;
                        else
                            state <= HALF;
                
                ONE   : if(pi_money == 2'b01)
                            state <= ONE_HALF;
                        else if(pi_money == 2'b10)
                            state <= TWO;
                        else if(cnt_init_flag == 1'b1)
                            state <= IDLE;
                        else
                            state <= ONE;
                
                ONE_HALF  : if(pi_money == 2'b01)
                            state <= TWO;
                        else if(pi_money == 2'b10)
                            state <= IDLE;
                        else if(cnt_init_flag == 1'b1)
                            state <= IDLE;
                        else
                            state <= ONE_HALF;
                            
                TWO  :  if(pi_money == 2'b01)
                            state <= IDLE;
                        else if(pi_money == 2'b10)
                            state <= IDLE;
                        else if(cnt_init_flag == 1'b1)
                            state <= IDLE;
                        else
                            state <= TWO;
                            
                default :   state <= IDLE;
            endcase
//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else if(cnt_init_flag == 1'b1)
        po_cola <= 1'b0;
    else if((state == TWO && pi_money == 2'b01)||(state == TWO && pi_money == 2'b10)||(state == ONE_HALF && pi_money == 2'b10)||(state == IDLE && po_cola == 1'b1))
        po_cola <= 1'b1;
    else 
        po_cola <= 1'b0;
        
//第二段状态机,描述当前状态state和输入pi_money如何影响po_money输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_money <= 1'b0;
    else if (cnt_init_flag == 1'b1)
        po_money <= 1'b0;
    else if ((state == TWO && pi_money == 2'b10) || (state == IDLE && po_money == 1'b1))
        po_money <= 1'b1;
    else if ((state == TWO && pi_money == 2'b01) || (state == IDLE && po_money == 1'b0))
        po_money <= 1'b0;
    
//第二段状态机,描述当前状态state和输入pi_money如何影响led_out输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out_reg <= 4'b0000; 
    else if (state == IDLE && po_cola == 1'b0)
        led_out_reg <= 4'b0000;
    else if (state == HALF)
        led_out_reg <= 4'b0001;   
    else if (state == ONE)
        led_out_reg <= 4'b0011;
    else if (state == ONE_HALF)
        led_out_reg <= 4'b0111;
    else if (state == TWO && po_cola == 1'b0)
        led_out_reg <= 4'b1111;
    else if (po_cola == 1'b1 && po_money == 1'b0)
    // 单向流水
        begin
            if((led_out_reg == 4'b1000||led_out_reg == 4'b1111) && cnt_flag == 1'b1)
                led_out_reg <=  4'b0001;
            else    if(cnt_flag == 1'b1)
                led_out_reg <=  led_out_reg << 1'b1; //左移  
        end
    else if (po_cola == 1'b1 && po_money == 1'b1)
    // 双向流水
        begin
            if((led_out_reg == 4'b1000||led_out_reg == 4'b1111) && cnt_flag == 1'b1)
                led_out_reg <=  4'b0100;
            else if(led_out_reg == 4'b0001 && cnt_flag == 1'b1 && cnt_flag_left == 1'b1)
                led_out_reg <= 4'b0010;
            else if(cnt_flag == 1'b1 && cnt_flag_left == 1'b0)
                led_out_reg <= led_out_reg << 1'b1;
            else if(cnt_flag == 1'b1 && cnt_flag_left == 1'b1)
                led_out_reg <= led_out_reg >> 1'b1;  
        end
assign led_out = ~led_out_reg;        
    
endmodule

2.源代码——状态机

同样先把代码奉上:非整体代码里的内容,只是一个基础,整体代码是在基础代码的基础上修改的

module complex_fsm
(
    input wire sys_clk, //系统时钟50MHz
    input wire sys_rst_n, //全局复位
    input wire pi_money_one, //投币1元
    input wire pi_money_half, //投币0.5元
    
    output reg po_money, //1:表示找零;0:表示不找零
    output reg po_cola //1:出可乐;0:不出可乐
);

//只有5种状态,使用独热码
parameter IDLE = 5'b00001;
parameter HALF = 5'b00010;
parameter ONE = 5'b00100;
parameter ONE_HALF = 5'b01000;
parameter TWO = 5'b10000;

reg [4:0] state;

wire [1:0] pi_money;

//pi_money:为了减少变量的个数,我们使用位拼接把输入的两个1bit信号拼接成1个2bit信号
//投币方式可以为:不投币(00)、投0.5元(01)、投1元(10),每次只投一个币
assign pi_money = {pi_money_one,pi_money_half};
//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state <= IDLE;
    else    case(state)
                IDLE  : if(pi_money == 2'b01)
                            state <= HALF;
                        else if(pi_money == 2'b10)
                            state <= ONE;
                        else
                            state <= IDLE;
                
                HALF  : if(pi_money == 2'b01)
                            state <= ONE;
                        else if(pi_money == 2'b10)
                            state <= ONE_HALF;
                        else
                            state <= HALF;
                
                ONE   : if(pi_money == 2'b01)
                            state <= ONE_HALF;
                        else if(pi_money == 2'b10)
                            state <= TWO;
                        else
                            state <= ONE;
                
                ONE_HALF  : if(pi_money == 2'b01)
                            state <= TWO;
                        else if(pi_money == 2'b10)
                            state <= IDLE;
                        else
                            state <= ONE_HALF;
                            
                TWO  : if(pi_money == 2'b01)
                            state <= IDLE;
                        else if(pi_money == 2'b10)
                            state <= IDLE;
                        else
                            state <= TWO;
                default :   state <= IDLE;
            endcase
//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else if((state == TWO && pi_money == 2'b01)||(state == TWO && pi_money == 2'b10)||(state == ONE_HALF && pi_money == 2'b10))
        po_cola <= 1'b1;
    else
        po_cola <= 1'b0;
        
//第二段状态机,描述当前状态state和输入pi_money如何影响po_money输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_money <= 1'b0;
    else if ((state == TWO)&&(pi_money == 2'b10))
        po_money <= 1'b1;
    else
        po_money <= 1'b0;
endmodule

野火状态机的套路写法是新的二段式状态机,第一段是用时序逻辑描述当前状态并用组合逻辑描述下一状态;第二段描述状态输出。
第一个always块描述状态的转移为第一段状态机,第二个always块描述数据的输出为第二段状态机(如果我们遵循一个always块只描述一个变量的原则,如果有多个输出时第二段状态机就可以分为多个always块来表达,但理论上仍属于新二段状态机,多以几段式状态机并不是由always块的数量简单决定的)。

3.源代码——按键消抖控制灯

同样先把代码奉上:非整体代码里的内容,只是一个基础,整体代码是在基础代码的基础上修改的


module key_filter
#(
    parameter CNT_MAX = 20'd999_999
)
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire key_in1,
    input wire key_in2,
    
    output wire [1:0] led_out

    
);

reg [19:0] cnt_20ms;
reg key_flag; //key_flag为1时表示消抖后检测到按键被按下;key_flag为0时表示没有检测到按键被按下
reg [1:0] led_out_reg;
wire [1:0] key_in;
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else if(key_in1 == 1'b1 && key_in2 == 1'b1)
        cnt_20ms <= 20'b0;
    else if(cnt_20ms == CNT_MAX && (key_in1 == 1'b0 || key_in2 == 1'b0))
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else if(cnt_20ms == CNT_MAX)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;
assign key_in = {!key_in1&&key_flag,!key_in2&&key_flag};
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out_reg <= 2'b00;
    else if(key_in == 2'b10)
        led_out_reg <= 2'b10;
    else if(key_in == 2'b01)
        led_out_reg <= 2'b01;
    else
        led_out_reg <= 2'b00;


assign led_out = ~led_out_reg;
endmodule

基础代码是两个按键控制两个灯,主要是想尝试这个的写法

assign key_in = {!key_in1&&key_flag,!key_in2&&key_flag};

把两个按键的状态结合按键消抖结合在了一起,有点像合成一个整体,给人一种集成的感觉,写的时候感觉舒服些哈哈哈哈哈,整体代码里面没有直接按键控制灯,而是按键控制状态,状态控制灯。

4.源代码——流水灯

同样先把代码奉上:非整体代码里的内容,只是一个基础,整体代码是在基础代码的基础上修改的


单向流水灯

module  water_led
#(
    parameter CNT_MAX = 25'd24_999_999
)
(
    input   wire            sys_clk     ,   //系统时钟50Mh
    input   wire            sys_rst_n   ,  //全局复位

    output  wire    [3:0]   led_out        //输出控制led灯

);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg     [24:0]  cnt         ;
reg             cnt_flag    ;
reg     [3:0]   led_out_reg ;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt:计数器计数500ms
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 25'b0;
    else    if(cnt == CNT_MAX)
        cnt <= 25'b0;
    else
        cnt <= cnt + 1'b1;

//cnt_flag:计数器计数满500ms标志信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else    if(cnt == CNT_MAX - 1)
        cnt_flag <= 1'b1;
    else
        cnt_flag <= 1'b0;

//led_out_reg:led循环流水
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out_reg <=  4'b0001;
    else    if(led_out_reg == 4'b1000 && cnt_flag == 1'b1)
        led_out_reg <=  4'b0001;
    else    if(cnt_flag == 1'b1)
        led_out_reg <=  led_out_reg << 1'b1; //左移

assign  led_out = ~led_out_reg;

endmodule

双向流水灯

module water_led
#(
    parameter CNT_MAX = 25'd24_999_999
)
(
    input wire sys_clk, //系统时钟50MHz
    input wire sys_rst_n, //全局复位
    
    output wire [3:0] led_out // 输出控制led灯
);

reg [24:0] cnt;
reg cnt_flag;
reg cnt_flag_left;
reg [3:0] led_out_reg;


//cnt:计数器计数500ms
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt <= 25'b0;
    else if(cnt == CNT_MAX)
        cnt <= 25'b0;
    else 
        cnt <= cnt + 1'b1;

//cnt_flag:计数器计数满500ms标志信号
//cnt_flag_left:左移的标志信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag <= 1'b0;
    else if(cnt == CNT_MAX - 1)
        cnt_flag <= 1'b1;
    else   
        cnt_flag <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_flag_left <= 1'b0;
    else if(led_out_reg == 4'b1000 && cnt_flag == 1'b1)
        cnt_flag_left <= 1'b1;
    else if(led_out_reg == 4'b0001 && cnt_flag == 1'b1 && cnt_flag_left == 1'b1)
        cnt_flag_left <= 1'b0;
//led_out_reg:led循环流水
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led_out_reg <= 4'b0001;
    else if(led_out_reg == 4'b1000 && cnt_flag == 1'b1)
        led_out_reg <= 4'b0100;
    else if(led_out_reg == 4'b0001 && cnt_flag == 1'b1 && cnt_flag_left == 1'b1)
        led_out_reg <= 4'b0010;
    else if(cnt_flag == 1'b1 && cnt_flag_left == 1'b0)
        led_out_reg <= led_out_reg << 1'b1;
    else if(cnt_flag == 1'b1 && cnt_flag_left == 1'b1)
        led_out_reg <= led_out_reg >> 1'b1;
    
        
assign led_out = ~led_out_reg;
        
endmodule

双向流水灯自己是利用了标志位。左循环到第四个灯的时候就让标志位为1,改变流水方向,如果有更简单的写法可以一起讨论吖,初学老感觉自己写的代码有些冗余。

5.源代码——10s归零(5s)

这个就没啥基础代码了,就是设定个计数器,计数满了设定标志位就行,可能要注意的就是计数的条件,得是按键没有操作的时候再进行计数,代码里的描述是给计数清零写了条件,然后else就是计数啦


/*10s归零 定义中间参数*/
reg [27:0] cnt_init;
reg cnt_init_flag;
/**********************10s归零代码****************************/
//cnt_init:计数器计数10s
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_init <= 28'b0;
    else if(pi_money_half == 1'b0 || pi_money_one == 1'b0)
        cnt_init <= 28'b0;
    else if(cnt_init == CNT_MAX_INIT)
        cnt_init <= cnt_init;
    else 
        cnt_init <= cnt_init + 1'b1;

这里在调试的时候遇到一个小问题,在判断是否出可乐的代码上,我开始是这么写的

//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else if((state == TWO && pi_money == 2'b01)||(state == TWO && pi_money == 2'b10)||(state == ONE_HALF && pi_money == 2'b10)||(state == IDLE && po_cola == 1'b1))
        po_cola <= 1'b1;
    else if(cnt_init_flag == 1'b1)
        po_cola <= 1'b0;
    else 
        po_cola <= 1'b0;

这样运行代码的时候发现,在进行流水灯的时候,无操作等待5s,并没有回到初始状态(即灯全灭),一直在循环,后来我改成了这样就行了。

//第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_cola <= 1'b0;
    else if(cnt_init_flag == 1'b1)
        po_cola <= 1'b0;
    else if((state == TWO && pi_money == 2'b01)||(state == TWO && pi_money == 2'b10)||(state == ONE_HALF && pi_money == 2'b10)||(state == IDLE && po_cola == 1'b1))
        po_cola <= 1'b1;
    else 
        po_cola <= 1'b0;

哈哈哈哈哈,比较神奇,就是换了一个顺序,这其实是个比较简单的问题,类似于优先级,算是比较细节吧,也不知道怎么说,等我以后组织组织语言,细节决定成败。

三、总结

大体内容就是这样啦,因为不是很熟练,整个功能的全部实现花了比较多的时间,每个功能或许能单独实现,但是整合在一起总会出现一些小问题,不过问题解决了心情还是很好滴,代码格式得注意可读性,一开始代码太乱了,给自己看糊涂了,最后整理下代码,定义定义放一块,功能功能放一块,看起来舒服多了,然后就是尽量不要用循环,一开始我看到10s我想要不循环算了,后来查了资料发现循环用多路,电路也会更加复杂,想了想就算了。
一开始我还想用以前写python的思路,把每个功能写成模块或者函数,直接调用就行,后来尝试了,问题好多,等学了更多的时候再试试这种思路吧,这种模块化设计的思路还挺重要的。

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值