Управление шаговым мотором по сети Ethernet

m3gw2 step motor control udp

В предыдущей статье я писал, что у нас появилась новая плата расширения Ethernet 1Gbit и к ней был сделан относительно простой тестовый проект для платы Марсоход3GW2. В этой работе FPGA плата могла принимать UDP пакеты и отправлять UDP пакеты. Этим проектом мы проверили работоспособность нашего шилда Ethernet. Однако, существенный недостаток был в том, что передать пакет в плату можно было только отправляя broadcast широковещательные пакеты. Это пакеты "всем", что создает излишний трафик во всей локальной сети. Дело в том, что компьютер с которого отправляются пакеты не знает физического адреса платы, MAC адреса. Даже если я знаю IP адрес своего устройства этого недостаточно, чтобы осуществлять прямую передачу от клиента к серверу.

Чтобы сделать передачу UDP пакета с компьютера на нашу FPGA плату по всем правилам нужно реализовать ARP протокол, Address Resolution Protocol. Любое устройство в локальной сети имеет в своих драйверах таблицу ARP. В этой таблице хранится соответствие IP адреса его MAC адресу. Когда какая ни будь программа посылает пакет в сеть, стек драйверов ОС прежде всего проверяет в своей таблице известен ли MAC адресата. Если известен, то пакет тут же отправляется на известный MAC адрес. А если MAC неизвестен, то сперва отправляется широковещательный запрос ARP всем устройствам в сети "У кого есть вот такой IP?" и устройство с этим IP адресом должно откликнуться и прислать ARP ответ "Это мой IP, а вот мой MAC адрес". После этого, компьютер, получив ответ уже знает MAC и может действительно послать пакет адрасату. Новый известный MAC адрес помещается в таблицу MAC адресов, и при повторной передаче следующих пакетов ARP протокол уже не используется, ведь физический адрес клиента уже известен.

Сейчас я хочу усовершенствовать наш Ethernet проект для FPGA платы Марсоход3GW2 и добавить поддержку ARP протокола.
Наша плата FPGA теперь будет делать следующее: на неё (конкретно на её статический IP платы) теперь можно будет послать UDP пакет и этот пакет

  • зажигает светодиоды платы согласно принятому байту из UDP пакета;
  • управляет вращением шагового двигателя с заданной скоростью согласно принятому значению из UDP пакета.

Стенд для испытаний этого проекта показан на фото в начале статьи. На плате расширения Ethernet есть 4 выхода io16, io17, io18 и io19 они идут на драйвер двигателя L298N к которому уже подключен шаговый моторчик.

Далее расскажу чуть подробнее о проекте.

Весь проект реализован на Verilog HDL. Его исходники можно взять на github.

Модуль самого верхнего уровня это gig_e. Вот его код:

// 1G Ethernet rec&send example 
module gig_e(
    input  clk,key0,key1,
    input Rx_dv,
    input [3:0]Rx_D,
    input Rx_clk,
    output Tx_clk,
    output [3:0]Tx_D,
    output Tx_ena,
    output PHYRST,
    output MDC,
    inout MDIO,
    output XTAL,
    output [7:0] led,

    output wire io16,
    output wire io17,
    output wire io18,
    output wire io19,

    input [7:0]ADC_D,
    output ADC_CLK
);

assign MDC = 1'b0;
assign PHYRST = 1'b1;

reg [25:0] cnt;
always @(posedge clk)
    cnt <=  cnt + 1'b1 ;

/*
assign ADC_CLK = cnt[2];
reg [7:0]adc_data;
always @(posedge ADC_CLK)
    adc_data <= ADC_D;
*/
assign ADC_CLK = 1'b0;

assign XTAL = cnt[1];

reg [25:0] R_cnt;
always @(posedge Rx_clk)
    R_cnt <=  R_cnt + 1'b1 ;

wire clkout;
wire clkoutp;
Gowin_rPLL inst1(
    .clkin(Rx_clk), //input clkin
    .clkout(clkout), //output clkout
    .clkoutp(clkoutp) //output clkout
);

wire r_clk = clkoutp;
wire Rx_dv_p,Rx_dv_pp;
wire [7:0] rx_ddr_in;
Gowin_DDR  inst2(
  .din({Rx_dv,Rx_D}),
  .clk(r_clk),
  .q({Rx_dv_p,rx_ddr_in[7:4],Rx_dv_pp,rx_ddr_in[3:0]})
);

wire [10:0]byte_cnt;
wire [2:0]pkt_cnt;
wire [7:0]rx_crc32_err_cnt;
wire rx_crc32_ok;
wire [12:0]memwr_addr_;
wire [15:0]memwr_data_;
wire memwr_;
rx_crc32 rx_crc32_inst(
	.clk(r_clk),
	.data_valid(Rx_dv_p),
	.data(rx_ddr_in),
    .byte_count(byte_cnt),
    .pkt_head(pkt_cnt),
    .memwr_addr(memwr_addr_),
    .memwr_data(memwr_data_),
    .memwr(memwr_),
	.crc32_ok(rx_crc32_ok),
    .err_cnt(rx_crc32_err_cnt)
);

wire [8:0]rd_addr;
wire [255:0]rd_data;
Gowin_DPB dpram_inst(
        .reseta( 1'b0 ),    //input reseta
        .clka(r_clk),       //input clka
        .douta(),           //output [7:0] douta
        .ocea(1'b1),        //input ocea
        .cea(1'b1),         //input cea
        .wrea(memwr_),      //input wrea
        .ada(memwr_addr_),  //input [12:0] ada
        .dina(memwr_data_), //input [15:0] dina

        .resetb( 1'b0 ),    //input resetb
        .clkb(r_clk),       //input clkb
        .oceb(1'b1),        //input oceb
        .ceb(1'b1),         //input ceb
        .wreb( 1'b0 ),      //input wreb
        .dinb(256'd0),      //input [255:0] dinb
        .adb(rd_addr),      //input [8:0] adb
        .doutb(rd_data)     //output [255:0] doutb
    );

wire [6:0]ad_i;
wire [11:0]dout_o;
Gowin_pROM prom_inst(
        .reset(1'b0),   //input reset
        .clk(r_clk),    //input clk
        .oce(1'b1),     //input oce
        .ce(1'b1),      //input ce
        .ad(ad_i),      //input [11:0] ad
        .dout(dout_o)  //output [11:0] dout
    );

wire [7:0]wr_fifo_data;
wire wr_fifo;
wire [63:0]udp_payload;
pkt_reader pkt_reader_inst(
	.clk(r_clk),
	.raddr(rd_addr),
	.data(rd_data),
	.head(pkt_cnt),
	.rom_addr(ad_i),
    .rom_byte(dout_o),
	.send_byte(wr_fifo_data),
	.send(wr_fifo),
	.broadcast(),
	.led(),
	.crc32(),
    .udp_payload(udp_payload)
);

//udp_payload is data received from UDP command
assign led[7:0] = udp_payload[7:0];
wire [23:0]motor_step_time;
assign motor_step_time = udp_payload[55:32];

assign Tx_clk = clkout;

wire Empty_o;
wire [7:0]t_data;
reg fifo_rd_req = 1'b0;
reg t_ena_ = 1'b0;
reg t_ena  = 1'b0;
always @(posedge Tx_clk)
begin
    fifo_rd_req <= !Empty_o;
    t_ena_ <= fifo_rd_req;
    t_ena  <= t_ena_ & fifo_rd_req;
end
    
FIFO_HS_Top fifo_inst(
		.WrClk(r_clk),          //input WrClk
		.Data(wr_fifo_data),    //input [7:0] Data
		.WrEn(wr_fifo),         //input WrEn
		.RdClk(Tx_clk),         //input RdClk
		.RdEn(fifo_rd_req),     //input RdEn
		.Rnum(),                //output [11:0] Rnum
		.Almost_Empty(),        //output Almost_Empty
		.Almost_Full(),         //output Almost_Full
		.Q(t_data),             //output [7:0] Q
		.Empty(Empty_o),        //output Empty
		.Full()                 //output Full
	);

//////////////////////////////////////////////////////////////

ddr_out inst5(
	.din({t_ena,t_data[7:4],t_ena,t_data[3:0]}), //input [9:0] din
	.clk(Tx_clk), //input clk
	.q({Tx_ena,Tx_D}) //output [4:0] q
);

//////////////////////////////////////////////////////////////
reg [23:0]mcnt = 24'd0;
reg [2:0]mcnt8 = 3'b000;

always @(posedge clk)
begin
    if( mcnt==motor_step_time )
        mcnt<=0;
    else
        mcnt<=mcnt+1;

    if( motor_step_time==24'd0 )
        mcnt8<=24'd0;
    else
    if( mcnt==motor_step_time )
        mcnt8<=mcnt8+1;
end

motor m(
    .clk( clk ),
	.enable( 1'b1 ),
	.dir( 1'b0 ),
	.cnt8( mcnt8 ),
	.f0( io16 ),
	.f1( io17 ),
	.f2( io18 ),
	.f3( io19 )
);

endmodule

 

Чтобы лучше объяснить его я создал вот такую блок схему:

block schema

Три розовых овала вверху схемы это сигналы которые связывают микросхему Ethernet PHY Realtek 8211E с микросхемой FPGA. Например, сигнал Rx_clk из микросхемы Ethernet PHY задаёт тактовую частоту, которую нужно использовать для приёма данных. Я использую Gowin_rPLL чтобы задать фазовый сдвиг этой частоты для надёжной фиксации приходящих данных на линиях Rx_D[3:0] (это принимаемые данные, четыре бита) и Rx_dv (этот сигнал активен во время приёма пакета и неактивен, когда данных нет). Поскольку используется передача данных и по фронту и по спаду частоты Rx_clk, то нужен модуль DDR (Double Data Rate) приёмника. У меня это модуль типа Gowin_DDR и на его выходе данные имеют уже в два раза большую разрядность 8 бит. Эти принятые байты идут на модуль rx_crc32. Он выделен серым цветом. Все блоки серого цвета это модули написанные нами. А блоки выделенные голубым цветом это модули которые созданы в Gowin IDE IP Core Generator. Это всё модули, для которых Gowin предлагает в FPGA готовые аппаратные блоки, а еще к ним есть Gowin IP Core.

Главная задача модуля rx_crc32 принять все байты пакета и записать их в блочную память Gowin_DPB. Блочная память является двухпортовым ОЗУ 16 килобайт и логически эта память разделена на 8 блоков по 2 килобайта. Один блок памяти 2К хранит один принятый Ethernet пакет. Модуль rx_crc32 хранит трёхбитный указатель head (голова) на текущий принимаемый пакет. Принятые байты записываются внуть блока, и одновременно подсчитывается контрольная сумма CRC32 для принимаемого Ethernet пакета. Контрольная сумма передается в его конце и её можно принять и проверить верная ли она, соответствует ли она посчитанной. Если CRC32 корректная, то модуль rx_crc32 увеличивает указатель head на единицу. Следующий пакет примется в следующий блок памяти. Таким образом, получается циклический буффер на 8 принимаемых пакетов.

Модуль pkt_reader пожалуй самое сложное устройство. Вот его код:

module pkt_reader(
	input wire clk,
	input wire [255:0]data,
	input wire [2:0]head,
	input wire [11:0]rom_byte,
	output wire [8:0]raddr,
	output reg broadcast,
	output wire [3:0]led,
	output wire [7:0]send_byte,
	output wire send,
	output reg [6:0]rom_addr,
	output reg [31:0]crc32,
    output reg [63:0]udp_payload
);

`include "crc32.v"

localparam MY_MAC = 48'hA6A5A4A3A1A0;
localparam MY_IP  = 32'h0900080A; // my fixed IP is 10.8.0.9

localparam ETH_TYPE_ARP  = 16'h0608;
localparam ETH_TYPE_IP   = 16'h0008;
localparam IP_PROTO_TCP  = 8'h06;
localparam IP_PROTO_UDP  = 8'd17;
localparam TCP_SRV_PORT  = 16'h5000; //http = 80
localparam UDP_SRV_PORT  = 16'h6969;

localparam STATE_WAIT_PKT = 0;
localparam STATE_CHK_MAC  = 1;
localparam STATE_CHK_UDP0 = 2;
localparam STATE_CHK_UDP  = 3;
localparam STATE_CHK_ARP0 = 4;
localparam STATE_CHK_ARP  = 5;
localparam STATE_CHK_ARP_IP0 = 6;
localparam STATE_CHK_ARP_IP  = 7;
localparam STATE_ARP_REPLY = 8;
localparam STATE_END_PKT_PROC = 9;
localparam STATE_END_PKT_PROC1 = 10;

//decode packet data bus lines
wire [47:0]L0_dst_mac_addr;  assign L0_dst_mac_addr = data[111:64];
wire [47:0]L0_src_mac_addr;  assign L0_src_mac_addr = data[159:112];
wire [15:0]L0_eth_type; 	 assign L0_eth_type     = data[175:160];
wire [ 7:0]L0_arp_opcode; 	 assign L0_arp_opcode   = data[239:232];
wire [ 7:0]L0_ip_proto; 	 assign L0_ip_proto     = data[255:248];
wire [31:0]L1_arp_req_ip; 	 assign L1_arp_req_ip   = data[143:112];
wire [31:0]L1_arp_rem_ip; 	 assign L1_arp_rem_ip   = data[63:32];

wire [31:0]L1_udp_src_ip; 	 assign L1_udp_src_ip   = data[47:16];
wire [31:0]L1_udp_dst_ip; 	 assign L1_udp_dst_ip   = data[79:48];
wire [15:0]L1_udp_src_port;  assign L1_udp_src_port = data[95:80];
wire [15:0]L1_udp_dst_port;  assign L1_udp_dst_port = data[111:96];
wire [15:0]L1_udp_data_len;  assign L1_udp_data_len = data[127:112];
wire [15:0]L1_udp_ch_sum;    assign L1_udp_ch_sum   = data[143:128];
wire [31:0]L1_udp_payload0;  assign L1_udp_payload0 = data[175:144];
wire [31:0]L1_udp_payload1;  assign L1_udp_payload1 = data[207:176];

reg [2:0]tail = 0;

reg [3:0]state = STATE_WAIT_PKT;
always @(posedge clk)
begin
	case(state)
	STATE_WAIT_PKT: begin
		if( tail != head )	//just wait receiver head goes forward
			state <= STATE_CHK_MAC;
		end
	STATE_CHK_MAC:  begin
		if( L0_dst_mac_addr==48'hFFFFFFFFFFFF && L0_eth_type==ETH_TYPE_ARP && L0_arp_opcode==8'h01 ) //accept broadcast only for ARP req
			state <= STATE_CHK_ARP0;
		else
		if( L0_dst_mac_addr==MY_MAC && L0_eth_type==ETH_TYPE_IP && L0_ip_proto==IP_PROTO_UDP) //accept my MAC for TCP/IP
			state <= STATE_CHK_UDP0;
		else
			state <= STATE_END_PKT_PROC; //ignore packet because of wrong MAC
		end
	STATE_CHK_UDP0: begin
		state <= STATE_CHK_UDP;
		end
	STATE_CHK_UDP:  begin 
		//if( L1_udp_dst_ip==MY_IP && L1_udp_dst_port==UDP_SRV_PORT ) //accept UDP packet if true
		//begin
        //end
		state <= STATE_END_PKT_PROC;
        end
	STATE_CHK_ARP0:  begin
			state <= STATE_CHK_ARP_IP;
		end
	STATE_CHK_ARP_IP: begin
		if( L1_arp_req_ip==MY_IP ) //accept ARP request for me only
			state <= STATE_ARP_REPLY;
		else
			state <= STATE_END_PKT_PROC;
		end
	STATE_ARP_REPLY: begin
		if( rom_byte_==12'hF02 )
			state <= STATE_END_PKT_PROC;
		end
	STATE_END_PKT_PROC1: begin
		state <= STATE_END_PKT_PROC;
		end
	STATE_END_PKT_PROC: begin
		state <= STATE_WAIT_PKT;
		end
	endcase;
end

always @(posedge clk)
	if( state==STATE_CHK_UDP && L1_udp_dst_ip==MY_IP && L1_udp_dst_port==UDP_SRV_PORT )
    begin
        udp_payload <= { L1_udp_payload1, L1_udp_payload0 };
    end

always @(posedge clk)
	if( state==STATE_END_PKT_PROC ) //assume packet processed and need to go forward
	begin
		tail <= tail+1;
	end

wire [1:0]pkt_line; 
assign pkt_line = 
	(state==STATE_WAIT_PKT)	? 0 :
	(state==STATE_CHK_MAC)	? 1 : 2;
	
assign raddr = { tail, 4'h0, pkt_line };
assign led = { broadcast, head };

always @*
	broadcast = (data[112:64]==48'hFFFFFFFFFFFF);

reg [47:0]remote_mac;
reg [31:0]remote_ip;
always @(posedge clk)
begin
	if(state==STATE_CHK_MAC)
		remote_mac <= L0_src_mac_addr;
	if(state==STATE_CHK_ARP_IP)
		remote_ip <= L1_arp_rem_ip;
end

wire sending; assign sending = state==STATE_ARP_REPLY;
always @(posedge clk)
	if(state==STATE_CHK_ARP_IP)
		rom_addr <= 0;			//addr of ARP reply packet in rom
	else
	if( sending )
		rom_addr <= rom_addr + 1;

reg [7:0]sbyte0;
reg [7:0]sbyte1;
reg [7:0]sbyte2;
reg [7:0]sbyte3;
reg [7:0]sbyte4;
reg [7:0]sbyteF;
reg [7:0]sbyte_;
reg [11:0]rom_byte_;
reg [11:0]rom_byte__;
wire [3:0]field_sel; assign field_sel = rom_byte_[11:8];
always @(posedge clk)
begin
    if(state==STATE_WAIT_PKT)
    begin
        rom_byte__ <= 12'h000;
        rom_byte_  <= 12'h000;
    end
    else
    begin
        rom_byte__ <= rom_byte_;
        rom_byte_  <= rom_byte;
    end
    sbyte0 <= rom_byte[7:0];
    sbyte1 <= remote_mac>>(rom_byte[2:0]*8);
    sbyte2 <= remote_ip >>(rom_byte[1:0]*8);
    sbyte3 <= MY_MAC>>(rom_byte[2:0]*8);
    sbyte4 <= MY_IP >>(rom_byte[1:0]*8);
    sbyteF <= crc32>>(rom_byte[1:0]*8);
    sbyte_ <=
        (field_sel==4'h0) ? sbyte0 :
        (field_sel==4'h1) ? sbyte1 :
        (field_sel==4'h2) ? sbyte2 :
        (field_sel==4'h3) ? sbyte3 :
        (field_sel==4'h4) ? sbyte4 :
        (field_sel==4'hF) ? sbyteF : 8'h00;
end

assign send_byte[7:0] = sbyte_;

reg [2:0]send_delay;
reg send0 = 1'b0;
reg send1 = 1'b0;
always @(posedge clk)
begin
	send_delay <= { send_delay[1:0], sending };
    send0 <= send_delay[2] & (rom_byte_[11:8]!=4'hE);
    send1 <= send_delay[2] & send_delay[0] & (rom_byte_[11:7]!=5'h1F) & (rom_byte_[11:8]!=4'hE);
end

assign send = send1;

//count number of bytes sent (to skip crc calc for preamble)
reg [3:0]sbyte_cnt;
always @(posedge clk)
	if(state==STATE_WAIT_PKT)
		sbyte_cnt <= 0;
	else
	if(send0 && sbyte_cnt<8)
		sbyte_cnt <= sbyte_cnt + 1;
	
reg [31:0]crc32_;
always @(posedge clk)
	if(send0 && sbyte_cnt==8 )
	begin
        if( rom_byte__[11:8]!=4'hF )
			crc32_ <= nextCRC32_D8( 
				{
				send_byte[0], send_byte[1], send_byte[2], send_byte[3], 
				send_byte[4], send_byte[5], send_byte[6], send_byte[7] }
				, crc32_ );
	end
	else
		crc32_ <= 32'hFFFFFFFF;

//reverse bits and inverse of CRC32
integer i;
always @*
begin
	for ( i=0; i < 32; i=i+1 )
		crc32[i] = crc32_[31-i]^1'b1;
end

endmodule


У этого модуля внутри есть свой указатель tail, хвост. Если голова-head ушла вперед, то модуль легко это обнаружит сравнивая её с хвостом-tail. Внутри есть машина состояний которая в этот момент переходит из состояния ожидания прихода нового пакета STATE_WAIT_PKT в состояние проверки адреса MAC - STATE_CHK_MAC. У нас появился принятый пакет и мы хотим убедиться, что это сообщение вообще предназначено нам. Для этого проверяем MAC адрес.

В состоянии STATE_CHK_MAC мы проверяем сейчас два случая: нас интересует либо широковещательный пакет типа ARP запроса либо пакет точно на наш MAC адрес и точно Ethernet типа IP и протокол UDP. В зависимости от результата этой проверки мы перейдем либо к обработчику ARP запроса STATE_CHK_ARP0, либо к обработчику UDP пакета STATE_CHK_UDP0 либо вообще проигнорируем пакет STATE_END_PKT_PROC.

Обработчик ARP запроса конечно должен отвечать не всегда, ведь запросы широковещательные, может мы приняли запрос, который нас не касается вообще. Поэтому в состоянии STATE_CHK_ARP_IP мы и проверяем, запрос на наш IP адрес или нет. Ответ начнётся только если это запрос к нам.

В начале модуля pkt_reader задаются константы, которые определяют MAC адрес и статический IP адрес нашего устройства в ПЛИС:
localparam MY_MAC = 48'hA6A5A4A3A1A0;
localparam MY_IP = 32'h0900080A; // my fixed IP is 10.8.0.9

Вот проверка полей MAC и IP принятых пакетов идет на эти константы. Вы можете изменить эти константы и задать своему устройству свой IP адрес или MAC.

Если ARP запрос предназначен для нашего устройства, то машина состояний модуля pkt_reader переходит в STATE_ARP_REPLY. Теперь мне нужно отдать передатчику специальный пакет ARP ответ. Проблема в том, что я не могу задать его в коде программы просто массивом констант. Мне нужно сформировать этот пакет на лету используя различные значения MAC и IP источника и приемника. Именно поэтому я приготовил шаблон пакета ARP ответа и поместил его в блочную память Gowin_pROM. В шаблоне пакета слова 12-ти битные. Нужно передать пакет из байт находящихся внутри этой памяти, но некоторые байты нужно на лету подменить. Четыре старших бита в каждом слове из памяти Gowin_pROM как раз определяют, на что заменять этот конкретный байт. Например, если старшие 4 бита равны трём, то сюда нужно вставить байт из моего MAC адреса. Если старшие 4 бита равны 1, то сюда нужно вставить MAC адрес компьютера, который делал запрос и которому мы сейчас отвечаем. Блочная память Gowin_pROM инициализируется значениями из файла rom.mif.

Так же стоит заметить, что формируя пакет на отправку я одновременно считаю его контрольную сумму CRC32 и она так же будет отправлена в конце пакета, вставлена в шаблон, когда старшие 4 бита равны 4'hF.

Байты отправляемого пакета записываются в выходное FIFO FIFO_HS_Top.

Дальше следует логика, которая забирает байты из FIFO и выдаёт их в модуль ddr_out и далее в микросхему Ethernet PHY через сигналы Tx_D[3:0] и Tx_dv (data valid).

Что касается принятых UDP пакетов, то идет проверка, чтобы пакет был на мой IP адрес и на мой UDP порт. Номер порта сейчас задан 26985, это в шестнадцатеричном виде 16'h6969. Из принятого пакета я сейчас беру только два 32х битных слова и формирую из них сигнал udp_payload[63:0].

Модуль верхнего уровня gig_e берет из сигнала udp_payload[63:0] младший байт и выводит его на светодиоды платы. А старшее слово используется для формирования длительности одного полушага для шагового двигателя подключенного к плате на выходы io16, io17, io18 и io19.

Чтобы проверить работоспособность FPGA проекта я подготовил 3 тестовые питоновские программы.

1) Посылает на адрес 10.8.0.9 один или несколько (количестко задаётся вторым аргументом программы) одинаковых UDP пакетов, которые установят светодиоды в 0x55
>python send_pkt.py 10.8.0.9 1 0x55

2) Посылает периодические UDP пакеты, которые управляют светодиодами вот так:
>send_pkt_leds.py 10.8.0.9

*-------
**------
***-----
****----
*****---
******--
*******-
********
*******-
******--
*****---
....

3) Tkinter тестовая программа со слайдером, позволяет менять скорость вращения шагового двигателя, подключенного к плате
>python udp_send_ui.py 10.8.0.9

step motor ctrl

Программа на питоне использует библиотеки Tkitner и выводит графическое окно со слайдором, который можно двигать и таким образом задавать скорость вращения шагового двигателя подключенного к плате.

Ниже представлена видеодемонстрация проекта:

Здесь в начале видео показано, как я очищаю ARP таблицу на компьютере командой "arp -d *" и ARP таблица становится почти пустая. А потом, после запуска тестовой программы send_pkt.py в ARP таблице появляется новая запись, соответствующая моему устройству: "10.8.0.9 a0-a1-a2-a3-a4-a5".

Так же видно, что я запускаю на компьютере программу анализа сетевого трафика Wireshark и в момент запуска тестовой программы send_pkt.py там регистрируются 3 пакета: желтым выделены ARP запрос-ответ и черным UDP на моё устройство.

Обратите внимание на светодиоды платы - они загораются согласно байту переданному питоновсим скриптом send_pkt.py.

Потом запускается скрипт send_pkt_leds.py и светодиоды уже потом сами загораются в цикле.

И в завершении видео показано, как я управляю скоростью вращения шагового двигателя из питоновской программы udp_send_ui.py.

Должен сказать, что этот проект сделан по мотивам другого моего проекта web-server в плате Марсоход2. Там правда использовалась плата расширения с Ethernet 100Мбит. Но к сожалению, той работой я не вполне удовлетворён. Там была попытка реализовать аппаратный протокол TCP, а это скажем прямо довольно сложно. Логика TCP довольно сложна. Чтобы правильно всё написать лучше всего заменить в этом проекте модуль pkt_reader каким ни будь процессором, хоть RISC-V, хоть AVR или Z80, но процессором. И тогда уже всю логику выписать на ассемблере или C/C++.

Можно ли как-то усовершенствовать конкретно этот проект? Несомненно можно и нужно. Чего здесь явно не хватает так это какой-то минимальной поддержки ICMP протокола, а именно хотя бы ответа на ping. Это очень полезная функция для сетевого устройства. Может когда нибудь и сделаю это.

Если вы захотите повторить или просто испытать этот проект, то проще всего приобрести плату FPGA Марсоход3GW2 в нашем интернет магазине!

buy button

 

 


Добавить комментарий