项目简述

本次项目在计算机将图像数据信息通过千兆网发送给FPGA后,由于接收到的数据可能混乱和无效,需要对数据CRC校验和无效包过滤。

项目原理及框图

对iddr_ctrl模块的输入数据和使能信号,分成两部分处理:第一部分数据通过包有效检验,CRC32校验(单独建一个模块例化使用),包长度统计(通过rx_en和一个计数器来实现)后,把这三部分处理后的标志信号放在一个status_fifo中缓存,用一个16位位宽的status_value寄存器缓存;第二部分,将数据和使能信号缓存在一个数据FIFO中data_fifo模块中,待第一部分的status_fifo中缓存好了数据的状态信息如包有效标志,包长度,CRC32校验正确标志。通过判断status_value寄存器中的包有效标志,CRC32校验正确标志拉高,则数据校验和过滤完成,就将数据frx_data和使能信号frx_en输出。

大概的设计思路,先设计CRC32校验模块,然后再设计上面的rx_filter_buffer用来对数据进行过滤和有效包检验。

CRC32校验原理是本次设计的核心,对于CRC-32的理解:第一步,刚开始先看看CRC的原理,第二步看书上对CRC-8的两种推导,一是实际手算推导,二是移位寄存器方式实现CRC(硬件的实现方式)。第三步,则是在理解CRC-8的基础上,CRC-32与它类似,主要是理解移位寄存器方式和对应的CRC校验码。

1.CRC的原理是通过网上博客讲解,我的理解如下:

根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端。当然,这个附加的数不是随意的,它要使所生成的新帧能与发送端和接收端共同选定的某个特定数整除(注意,这里不是直接采用二进制除法,而是采用一种称之为“模2除法”)。到达接收端后,再把接收到的新帧除以(同样采用“模2除法”)这个选定的除数。因为在发送端发送数据帧之前就已通过附加一个数,做了“去余”处理(也就已经能整除了),所以结果应该是没有余数。如果有余数,则表明该帧在传输过程中出现了差错。

CRC校验原理就是以下几个步骤:

(1)先选择(可以随机选择,也可按标准选择,具体在后面介绍)一个用于在接收端进行校验时,对接收的帧进行除法运算的除数(是二进制比较特串,通常是以多项方式表示,所以CRC又称多项式编码方法,这个多项式也称之为“生成多项式”)。

(2)看所选定的除数二进制位数(假设为k位),然后在要发送的数据帧(假设为m位)后面加上k-1位“0”,然后以这个加了k-1个“0“的新帧(一共是m+k-1位)以“模2除法”方式除以上面这个除数,所得到的余数(也是二进制的比特串)就是该帧的CRC校验码,也称之为FCS(帧校验序列)。但要注意的是,余数的位数一定要是比除数位数只能少一位,哪怕前面位是0,甚至是全为0(附带好整除时)也都不能省略

(3)再把这个校验码附加在原数据帧(就是m位的帧,注意不是在后面形成的m+k-1位的帧)后面,构建一个新帧发送到接收端,最后在接收端再把这个新帧以“模2除法”方式除以前面选择的除数,如果没有余数,则表明该帧在传输过程中没出错,否则出现了差错。

通过以上介绍,大家一定可以理解CRC校验的原理,并且不再认为很复杂吧。

从上面可以看出,CRC校验中有两个关键点:一是要预先确定一个发送端和接收端都用来作为除数的二进制比特串(或多项式);二是把原始帧与上面选定的除进行二进制除法运算,计算出FCS。前者可以随机选择,也可按国际上通行的标准选择,但最高位和最低位必须均为“1”,如在IBM的SDLC(同步数据链路控制)规程中使用的CRC-16(也就是这个除数一共是17位)生成多项式g(x)= x16 + x15 + x2 +1(对应二进制比特串为:11000000000000101);而在ISO HDLC(高级数据链路控制)规程、ITU的SDLC、X.25、V.34、V.41、V.42等中使用CCITT-16生成多项式g(x)=x16 + x15 + x5 +1(对应二进制比特串为:11000000000100001)。

2.CRC-8的实现

对于FPGA来言,主要是通过移位寄存器实现CRC校验码。

对于CRC-32的原理,我们直接给出它的校验码的化简结果,原理和CRC-8类似

对于以上原理的分析,设计的CRC-32的Verilog代码如下:主要是对校验码生成原理的理解,而校验结果直接通过查找表实现即可。

module crc32_d8_rec_02(
input wire restb,
input wire sclk,
input wire dsin, //输入数据有效标志
input wire[7:0] din,
output wire crc32_cal_end,
output wire crc_err

);

reg dsin_r,crc32_end_r,crc_err_r;
wire [7:0]d;
reg [31:0]crc32_value;
wire[31:0]c;

assign d = din;
always @(posedge sclk ) begin
begin
// reset
dsin_r<=dsin;
end

end
assign c =crc32_value ;

assign crc32_cal_end = crc32_end_r;
assign crc_err = crc_err_r;

always@(posedge sclk)begin
if(dsin==1'b0)begin
crc32_value <=32'hffffffff;

end
else begin
crc32_value[0]<=c[24]^c[30]^d[1]^d[7];
crc32_value[1]<=c[25]^c[31]^d[0]^d[6]^c[24]^c[30]^d[1]^d[7];

crc32_value[2]<=c[26]^d[5]^c[25]^c[31]^d[0]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[3]<=c[27]^d[4]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[4]<=c[28]^d[3]^c[27]^d[4]^c[26]^d[5]^c[24]^c[30]^d[1]^d[7];
crc32_value[5]<=c[29]^d[2]^c[28]^d[3]^c[27]^d[4]^c[25]^c[31]^d[0]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[6]<=c[30]^d[1]^c[29]^d[2]^c[28]^d[3]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[7]<=c[31]^d[0]^c[29]^d[2]^c[27]^d[4]^c[26]^d[5]^c[24]^d[7];
crc32_value[8]<=c[0]^c[28]^d[3]^c[27]^d[4]^c[25]^d[6]^c[24]^d[7];
crc32_value[9]<=c[1]^c[29]^d[2]^c[28]^d[3]^c[26]^d[5]^c[25]^d[6];
crc32_value[10]<=c[2]^c[29]^d[2]^c[27]^d[4]^c[26]^d[5]^c[24]^d[7];
crc32_value[11]<=c[3]^c[28]^d[3]^c[27]^d[4]^c[25]^d[6]^c[24]^d[7];
crc32_value[12]<=c[4]^c[29]^d[2]^c[28]^d[3]^c[26]^d[5]^c[25]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[13]<=c[5]^c[30]^d[1]^c[29]^d[2]^c[27]^d[4]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[14]<=c[6]^c[31]^d[0]^c[30]^d[1]^c[28]^d[3]^c[27]^d[4]^c[26]^d[5];
crc32_value[15]<=c[7]^c[31]^d[0]^c[29]^d[2]^c[28]^d[3]^c[27]^d[4];
crc32_value[16]<=c[8]^c[29]^d[2]^c[28]^d[3]^c[24]^d[7];
crc32_value[17]<=c[9]^c[30]^d[1]^c[29]^d[2]^c[25]^d[6];
crc32_value[18]<=c[10]^c[31]^d[0]^c[30]^d[1]^c[26]^d[5];
crc32_value[19]<=c[11]^c[31]^d[0]^c[27]^d[4];
crc32_value[20]<=c[12]^c[28]^d[3];
crc32_value[21]<=c[13]^c[29]^d[2];
crc32_value[22]<=c[14]^c[24]^d[7];
crc32_value[23]<=c[15]^c[25]^d[6]^c[24]^c[30]^d[1]^d[7];
crc32_value[24]<=c[16]^c[26]^d[5]^c[25]^c[31]^d[0]^d[6];
crc32_value[25]<=c[17]^c[27]^d[4]^c[26]^d[5];
crc32_value[26]<=c[18]^c[28]^d[3]^c[27]^d[4]^c[24]^c[30]^d[1]^d[7];
crc32_value[27]<=c[19]^c[29]^d[2]^c[28]^d[3]^c[25]^c[31]^d[0]^d[6];
crc32_value[28]<=c[20]^c[30]^d[1]^c[29]^d[2]^c[26]^d[5];
crc32_value[29]<=c[21]^c[31]^d[0]^c[30]^d[1]^c[27]^d[4];
crc32_value[30]<=c[22]^c[31]^d[0]^c[28]^d[3];
crc32_value[31]<=c[23]^c[29]^d[2];

end

end

//数据有效标志下降沿标志数据结束
always@(dsin or dsin_r)begin
if(!dsin && dsin_r)
crc32_end_r<=1'b1;
else begin
crc32_end_r<=1'b0;
end
end

//数据校验出错标志
always @(posedge sclk or negedge restb) begin
if (restb==1'b0) begin
// reset
crc_err_r<=1'b0;
end
else if (crc32_end_r==1'b1 && crc32_value!=32'hc704dd7b) begin
crc_err_r<=1'b1;
end
else
crc_err_r<=1'b0;
end

endmodule

第二个模块,数据的包有效检验和过滤模块的设计

1、检测有效包,目前我们定义上位机软件设置源端口为 1 234 ,目的端口 1 23
U DP 协议字段为 8 ’h11 这样可以证明此包为来自于我们需要的应用 。 我们
在代码中需要检测这三个字段 检测到了后需要把此包的 vld_pkg 有效 这
样将来读取此包数据时会下发到下一级 。针对上图就是红色圈着的五个字节,只需要和标准值匹配则表明包有效。

关键代码如下

always @(posedge sclk) begin
if(rst) begin
pkg_value <= 40'd0;
end
else if(PHY_rx_cnt==31 || (PHY_rx_cnt>=42 && PHY_rx_cnt<=45)) begin
pkg_value <= {pkg_value[31:0],PHY_rxd};
end
end

always @(posedge sclk) begin
if(rst) begin
pkg_vld <= 1'b0;
end
else if(PHY_rx_neg && pkg_value==40'h11_04d2_007b) begin
pkg_vld <= 1'b1;
end
else begin
pkg_vld <= 1'b0;
end
end

2、C RC 是为了证明一个数据包是否出错 此 C RC 为 32 位校验码 只能用于验证
数据是否出错不能纠正错误 。 CRC 校验的区域为去掉 一帧 前 8 字节的所有数
据(去掉 7 个 0x 55 和 1 个 0xd 5

关键代码如下:rx_en_new为CRC校验模块的使能信号,是在去掉帧头后驱动这个模块

reg rx_en_new;
always @(posedge sclk or negedge rx_en) begin
if (rx_en == 1'b0) begin
rx_en_new <= 1'b0;
end
else if (PHY_rx_cnt == 'd7) begin
rx_en_new <= 1'b1;
end
end

rx_filter_buffer模块的时序图如上所示,根据时序图和原理框图可以很快设计出本模块代码如下所示:

module rx_filter_buffer(
input wire sclk,
input wire rst,
input wire [7:0]PHY_rxd,
input wire rx_en,
output reg frx_en,
output reg [7:0]frx_data

);

reg data_rd_en_r1;
wire [6 : 0] rd_data_count;
wire status_fifo_empty;
wire status_rd_en;
wire [15:0]status_dout;
wire [7:0]data_dout;
reg [15:0] status_value;

wire crc32_ok;
wire crc_err;
wire crc32_end;

assign crc32_ok =~crc_err ;
reg pkg_vld;

wire pkg_end;
reg [15:0] pkg_status;
assign pkg_end= pkg_vld;
assign status_rd_en = ~status_fifo_empty;
parameter PKG_LENTH = 1077;

reg [13:0] data_rd_en_cnt;
wire data_rd_en;
reg data_rd_en_r;
assign data_rd_en = data_rd_en_r1;

reg [39:0] pkg_value;
reg [10:0] pkg_len;
reg [12:0] PHY_rx_cnt;
reg rx_en_new;
always @(posedge sclk or negedge rx_en) begin
if (rx_en == 1'b0) begin
rx_en_new <= 1'b0;
end
else if (PHY_rx_cnt == 'd7) begin
rx_en_new <= 1'b1;
end
end
//status_fifo
status_fifo status_wr16rd16_8192 (
.wr_clk(sclk), // input wire wr_clk
.rd_clk(sclk), // input wire rd_clk
.din(pkg_status), // input wire [15 : 0] din
.wr_en(pkg_end), // input wire wr_en
.rd_en(status_rd_en), // input wire rd_en
.dout(status_dout), // output wire [15 : 0] dout
.full(full), // output wire full
.empty(status_fifo_empty), // output wire empty
.rd_data_count(rd_data_count) // output wire [6 : 0] rd_data_count
);

crc32_d8_rec_02 inst_crc32_d8_rec_02
(
.restb (1'b1),
.sclk (sclk),
.dsin (rx_en_new),
.din (PHY_rxd),
.crc32_cal_end (crc32_end),
.crc_err (crc_err)
);

//data_rd_cnt;dataFIFO读出的数据个数
always @(posedge sclk )
if(rst==1'b1) begin
// reset
data_rd_en_cnt<='d0;
end

else if(data_rd_en)begin
data_rd_en_cnt<=data_rd_en_cnt+'d1;
end
else begin//datafifo刚好读完
data_rd_en_cnt<='d0;
end

always @(posedge sclk )
data_rd_en_r1<=data_rd_en_r;

//data_rd_en
always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
data_rd_en_r<='d0;
end
else if(status_rd_en)begin
data_rd_en_r<='b1;
end
else if((data_rd_en_cnt==PKG_LENTH-1) && data_rd_en)begin
data_rd_en_r<='d0;
end
end

always @(posedge sclk ) begin
if (rst==1'b1) begin
// reset
PHY_rx_cnt<=13'd0;
end
else if (rx_en==1'b1) begin
PHY_rx_cnt<=PHY_rx_cnt+1'b1;
end

end

// always @(posedge sclk ) begin
// if (rst==1'b1) begin
// // reset
// PHY_rx_cnt<=11'd0;
// end
// else if (rx_en==1'b1) begin
// if(PHY_rx_cnt==PKG_LENTH )
// PHY_rx_cnt<=11'd0;
// else
// PHY_rx_cnt<=PHY_rx_cnt+1'b1;
// end
// else begin
// PHY_rx_cnt<=11'd0;
// end
// end

reg phy_rx_ctrl_r;
always @(posedge sclk ) begin
begin
// reset
phy_rx_ctrl_r<=rx_en;
end
end

reg PHY_rx_neg;
always @(posedge sclk ) begin
if(!rx_en &&phy_rx_ctrl_r ) begin
// reset
PHY_rx_neg<=1'b1;
end
else begin
PHY_rx_neg<=1'b0;
end
end
//pkg_len
always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
pkg_len<=11'd0;
end
else if(PHY_rx_neg) begin
pkg_len<=PKG_LENTH;
end
end

//包有效字段检验
//5byte-40bit数据和标准值一样
always @(posedge sclk) begin
if(rst) begin
pkg_value <= 40'd0;
end
else if(PHY_rx_cnt==31 || (PHY_rx_cnt>=42 && PHY_rx_cnt<=45)) begin
pkg_value <= {pkg_value[31:0],PHY_rxd};
end
end

always @(posedge sclk) begin
if(rst) begin
pkg_vld <= 1'b0;
end
else if(PHY_rx_neg && pkg_value==40'h11_04d2_007b) begin
pkg_vld <= 1'b1;
end
else begin
pkg_vld <= 1'b0;
end
end

always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
pkg_status<=16'd0;
end
else if(pkg_vld) begin
pkg_status<={crc32_ok,pkg_vld,pkg_len};
end
else begin
pkg_status<=16'd0;
end
end

//status_value
always @(posedge sclk ) begin
if(rst==1'b1 ) begin
// reset
status_value<=16'd0;
end
else if(status_rd_en) begin
status_value<=status_dout;
end

end

//data_fifo
data_fifo asfifo_wr8X8192_rd8X8192 (
.wr_clk(sclk), // input wire wr_clk
.rd_clk(sclk), // input wire rd_clk
.din(PHY_rxd), // input wire [7 : 0] din
.wr_en(rx_en), // input wire wr_en
.rd_en(data_rd_en), // input wire rd_en
.dout(data_dout), // output wire [7 : 0] dout
.full(full), // output wire full
.empty(empty) // output wire empty
);

always @(posedge sclk) begin
if(rst==1'b1) begin
frx_data <= 'd0;
frx_en <=1'b0;
end
else if(data_rd_en) begin
frx_data <= data_dout;
frx_en <=data_rd_en;
end
// else begin
// frx_data <= 'd0;
// frx_en <=1'b0;
// end
end

endmodule

由于没有A7的开发板,故本次设计了仿真文件验证:

仿真程序如下:

`timescale 1ns / 1ps
`define CLOCK 8

module tb_gigbit;

reg rst_n ;
reg gb_tx_data_en ;
reg gb_tx_clk ;
reg [ 7:0] gb_tx_data ;
wire [7:0] frx_data;
wire frx_en;

reg [7:0] mem[1073:0];
initial begin
$readmemb("./data.txt",mem);
end
// //D:\FPGA_class\3ZQ\10qianz
// initial begin
// $readmemb("D:/FPGA_class/3ZQ/10qianz/data.txt",mem);
// end

initial begin
rst_n = 1'b0;
gb_tx_clk = 1'b0;
gb_tx_data = 8'd0;
gb_tx_data_en = 1'b0;
#1000;
rst_n = 1'b1;
gen_data();
#100;
gb_tx_data_en = 1'b0;

end

always #(`CLOCK/2) gb_tx_clk = ~gb_tx_clk;
// task gen_data;
// integer i;
// integer j;
// begin
// @(posedge gb_tx_clk);
// gb_tx_data_en = 1'b1;
// for(j=0;j<1073;j=j+1)begin
// for (i=0;i<1073;i=i+1)begin
// gb_tx_data=mem[i];
// @(posedge gb_tx_clk);
// end
// end
// gb_tx_data = 8'h0;
// gb_tx_data_en = 1'b0;
// #(1000*`CLOCK);
// @(posedge gb_tx_clk);
// end
// endtask

task gen_data;
integer i;
begin
@(posedge gb_tx_clk);
gb_tx_data_en = 1'b1;
for (i=0;i<1073;i=i+1)begin
gb_tx_data=mem[i];
@(posedge gb_tx_clk);
end
@(posedge gb_tx_clk);
gb_tx_data = 8'h0;
gb_tx_data_en = 1'b0;
#(100*`CLOCK);
@(posedge gb_tx_clk);
end
endtask

rx_filter_buffer inst_rx_filter_buffer (
.sclk (gb_tx_clk),
.rst (~rst_n),
.PHY_rxd (gb_tx_data),
.rx_en (gb_tx_data_en),
.frx_en (frx_en),
.frx_data (frx_data)
);

endmodule

仿真波形如下:

这次千兆网的校验和数据包有效的测验,我领悟到,针对一个新的设计,关键是两点:第一是拆分。第二是针对同一个知识点,用多种方式去理解。

参考资料:

V3学院

CSDN博客

https://www.cnblogs.com/liushui-sky/p/9962123.html

咸鱼FPGA

https://www.cnblogs.com/xianyufpga/p/12147156.html

千兆网数据CRC检验和过滤的更多相关文章

  1. linux fedora 14(内核2.6.35.6) PF_RING+libpcap 极速捕获千兆网数据包,不丢包

    前面讲到了libpcap 捕获数据包,尤其在千兆网的条件下,大量的丢包,网上搜索好久,大概都是PF_PACKET +MMAP,NAPI,PF_RING之类的方法,我对PF_RING+libpcap进行 ...

  2. FPGA千兆网UDP协议实现

    接着上一篇百兆网接口的设计与使用,我们接着来进行FPGA百兆网UDP(User Datagram Protocol)协议的设计. 1)UDP简介 在此,参考博主夜雨翛然的博文“https://www. ...

  3. Dalsa Sherlock 直连千兆网相机(通用驱动)

    支持 Sherlock 7.1.7.2,用于千兆网相机与 Sherlock 的连接. 可适用于很多厂商的相机,如:巴斯勒(Basler),JAI,堡盟相机(Baumer),灰点相机(Point Gre ...

  4. 010 FPGA千兆网UDP通信【转载】

    一.以太网帧格式 图8‑12以太网帧格式 表8‑5以太网帧格式说明 类别 字节数 说明 前导码(Preamble) 8 连续 7 个 8'h55 加 1 个 8'hd5,表示一个帧的开始,用于双方设备 ...

  5. 华为S5700S-52P-LI-AC千兆网管交换机web登录界面配置

    研究一下午,包装附的说明书根本就是错误的,通过技术售后和官方的文档结合,总算可以登录交换机的web管理界面. 首先需要使用通讯控制线缆(包装中附)连接电脑和交换机,一头接交换机的Console口,一头 ...

  6. 迅为双核imx6DL核心板_ARM定制专家_Cortex SATA 千兆网 4G GPS

    核心板参数 尺寸:51mm*61mm CPU:Freescale Cortex-A9 双核精简版 i.MX6DL,主频 1.2 GHz 内存:1GB DDR3 存储:8GB EMMC 存储 EEPRO ...

  7. 011 FPGA千兆网TCP通信【转载】

    一.LWIP 首先通过上面的简单分析,我们应该很清楚一件事:TCP协议很复杂,光握手过程就需要"三次握手.四次挥手"的复杂过程,不是特别适合FPGA的纯逻辑实现,因为用FPGA实现 ...

  8. 【转】简谈基于FPGA的千兆以太网

    原文地址: http://blog.chinaaet.com/luhui/p/5100052903 大家好,又到了学习时间了,学习使人快乐.今天我们来简单的聊一聊以太网,以太网在FPGA学习中属于比较 ...

  9. FPGA设计千兆以太网MAC(2)——以太网协议及设计规划

    上篇该系列博文中通过MDIO接口实现了PHY芯片的状态检测,验证其已处于1000M 全双工工作模式.在设计MAC逻辑之前,要先清楚MAC与PHY之间的接口以及以太网协议细节,这样才能保证网络的兼容性. ...

随机推荐

  1. 基于GDAL库海洋表温日平均计算工具设计与实现 C++版

    技术背景 在对物理海洋数据处理过程中,表层温度是众多要素中的一种,本文书要是针对海洋表温数据批量日平均处理的一个工具设计.首先要在对当前的SST数据文件作一下简要的说明,SST全称为sea surfe ...

  2. mysql对属性的增删改

    修改表 alter table 创建表db 查看表 desc与describe desc table 查看建表语句show create table t1; 修改表名 alter table t1 r ...

  3. linux 利用python模块实现格式化json

    非json格式示例 {"name": "chen2ha", "where": {"country": "Chi ...

  4. CobaltStrike逆向学习系列(12):RDI 任务发布流程分析

    这是[信安成长计划]的第 12 篇文章 0x00 目录 0x01 任务构建 0x02 结果处理 0x03 功能 DLL 分析 之前的分析都是针对整个 CS 的框架来进行的,但是功能也是整个 C2 中相 ...

  5. ctf.show-misc31

    (感谢阿姨)这个misc还是属于比较阴间的,并且学到了一个新的编码形式,直接开搞 下载附件得到压缩包,解压需要密码,可以看到没有输入密码解压也是得到了一个"file"文件,以txt ...

  6. k8s初面考点ReplicaSet副本集极限9连击你懂了吗?

    k8s初面考点ReplicaSet副本集极限9连击你懂了吗? k8s考点灵魂拷问9连击 考点之简单描述一下k8s副本集ReplicaSet有什么作用? 考点之为什么ReplicaSet将取代Repli ...

  7. 【windows 访问控制】五、访问权限和访问掩码AcessMask

    访问掩码格式 所有安全对象都使用下图所示的访问掩码格式来安排其访问权限. 在这种格式中,低16位用于特定对象的访问权限,后8位用于标准访问权限,这些权限适用于大多数类型的对象,而4个高位用于指定通用访 ...

  8. 自定义 serializers.ValidationError 的错误返回

    在使用DRF进行反序列过程中,总是需要校验字段,然后返回错误结果.可以使用默认的自定义校验项,也可以自定义校验项.而默认的自定义校验项总是差强人意. 版本 Django 2.2.3 Python 3. ...

  9. tp 实现定时任务

    这里我是用tp6进行测试的:适合做本地项目 博客参考:: https://www.thinkphp.cn/topic/64455.html 1:composer  安装workman插件 compos ...

  10. RhaPHP 微信公众号管理系统

    框架网址https://www.rhaphp.com/ 开发手册网址:: https://www.kancloud.cn/langleigelang/rhaphp/588488 git 克隆,如果克隆 ...