The previous project has the BASYS3 send a digitized voltage to the RPi by pushing a button on the board. In this project we will have the transfer completely controlled by the RPi, which means we will have to enhance the FPGA interface to be able to accept "commands" from the RPi to latch the voltage and send it along.
Make a new Vivado project, call it something like "voltmeter2". The top level (call it top.v) will have the same inputs as in the previous project, however this time we will use the LEDs to show what is being transmitted to the RPi, so they do not have to be declared as "reg" (we will use the assign). Also, let's input the slide switches as sw so that we can have the RPi read them, that way we can check if the transfer is happening ok.
The instantiation will be:
module top( input clock, // system clock input reset, // BTNR input version, // BTNU input adc_n, adc_p, // VCAUX 6, P and N input [15:0] sw, // slide switches output [15:0] led, // 16 onboard LEDs above the switches output [6:0] segment, // 4 digit LED display output dp, // "." next to each LED digit output [3:0] digit, // which of the 4 digits to drive output [7:0] JB, // PMOD for debugging input rx, // UART receive output tx // UART transmit ); parameter VERSION = 'h0011;
For clocking, we will need the same as in the previous project: a 25MHz clock for the uart modules and state machines, and a 104MHz clock for the XADC. So go ahead and generate a clock_104 module just like in the previous project and instantiate like this:
// // make a slower clock for the UART transmitter and for the FSM // below that controls it. We do this because the logic analyzer // is slow too, it will only look at 8 wires at 25MHz. So if we // divide the system clock by 4, that will give us a 25MHz clock // and that should do nicelly, and it will make clocks_per_bit come // out as an integer // reg [1:0] count2 = 0; always @ (posedge clock) count2 <= count2 + 1; wire clock25; BUFG clk25buf (.I(count2[1]), .O(clock25) ); // // generate a 104MHz clock for the XADC so we can get samples at 1MHz // wire locked, clock104; clock_104 XADC_CLOCK( .reset(reset), .locked(locked), .clk_in1(clock), .clk_out1(clock104) );
And drive the 4 7-segment LEDs like before:
// // next drive the 4 7-segment displays // wire [15:0] display_this; display4 DISPLAY ( .clk100(clock), .number(display_this), .digit(digit), .segments(segment), .period(dp) );
Next instantiate the uart tx and rx modules:
// // instantiate the UART receiver. run with the 50MHz clock so that // we can run the transmitter FSM at 100MHz and not get lost // wire dv; wire [7:0] rx_data; uart_rx RECEIVER ( .i_Clocks_per_Bit('d25), .i_Clock(clock25), .i_Reset(reset), .i_Rx_Serial(rx), // tied to FPGA rx output .o_Rx_DV(dv), .o_Rx_Byte(rx_data) ); // // instantiate the UART transmitter // wire tx_active, tx_done; wire do_transmit; wire [7:0] transmit_byte; uart_tx TRANSMITTER ( .i_Clocks_per_Bit('d25), .i_Clock(clock25), .i_Reset(reset), .i_Tx_DV(do_transmit), .i_Tx_Byte(transmit_byte), .o_Tx_Active(tx_active), .o_Tx_Serial(tx), // tied to FPGA tx output .o_Tx_Done(tx_done) );
Note that dv will go high when the uart_rx module has a new byte received, and do_transmit will go high when the state machines wants to send something to the RPi.
Next comes the XADC block, which you should make by new (see Using the FPGA ADC) even if you have it from copying the previous project Voltmeter: FPGA and RPi), since I've seen weird things from just using the copy. Call this new one myxadc in the "Components Name" text field. Be sure that the input clock is clock_104:
// here is the XADC block // wire [6:0] daddr_in = 7'h16; wire adc_ready, isbusy, adc_data_ready, eos_out, alarm; wire [15:0] adc_data; wire [4:0] channel_out; myxadc XADC_INST ( .daddr_in(7'h16), // specifies vcaux6 pints to digitize .dclk_in(clock104), // 50MHz clock .den_in(adc_ready), // tied to adc_ready, tells adc to convert, tieing causes continuous conversions .di_in(16'h0), // to set the data to something, not used here .dwe_in(1'b0), // set to enable writing to di_in, which we don't want to do .vauxp6(adc_p), // positive input to digitize .vauxn6(adc_n), // negative input to digitize .busy_out(isbusy), // tells you the adc is busy converting .channel_out(channel_out[4:0]), // for using more than 1 channel, tells you which one. not used here .do_out(adc_data), // adc value from conversion .drdy_out(adc_data_ready), //tells you valid data is ready to be latched .eoc_out(adc_ready), // specifies that the ADC is ready (conversion complete) .eos_out(eos_out), // specifies that conversion sequence is complete .alarm_out(alarm), // OR's output of all internal alarms, not used here .vp_in(1'b0), // dedicated analog input pair for differential, tied to 0 if not used .vn_in(1'b0) ); // // wait for XADC to tell you something is ready to latch. note this means continuous latching // reg [15:0] r_adc_data; always @ (negedge isbusy) begin if (reset) r_adc_data <= 16'h0; else r_adc_data <= adc_data; end // // make a ~1Hz clock so we can run the LED display slower // reg [26:0] counter; reg [15:0] s_adc_data; always @ (posedge clock) begin if (reset) counter <= 0; else counter <= counter + 1; end wire clock_1hz = counter[26]; always @ (posedge clock_1hz) s_adc_data <= r_adc_data; assign display_this = version ? VERSION : s_adc_data;
You can see that we are latching the XADC ADC value adc_data into r_adc_data using the isbusy neagative edge, and we have a 1Hz clock to latch r_adc_data into s_adc_data, which goes into display_this for the 7-segment displays.
We will use the same state machine as in the Voltmeter: FPGA and RPi project to send data to the RPi, which goes like this:
// // now make a state machine to deal with transmitting 2 bytes // reg [2:0] tx_state; // 7 states so 3 bits will do localparam [2:0] TX_WAIT=0, TX_BYTE1=1, TX_DO1=2, TX_WAIT1=3, TX_BYTE2=4, TX_DO2=5, TX_WAIT2=6; reg doit; wire [16:0] transmit_word; wire begin_transfer; reg [7:0] tx_data; always @ (posedge clock25) begin if (reset) begin tx_state <= TX_WAIT; doit <= 0; tx_data <= 0; end else case (tx_state) TX_WAIT: begin if (begin_transfer) tx_state <= TX_BYTE1; else tx_state <= TX_WAIT; doit <= 0; tx_data <= 0; end TX_BYTE1: begin tx_data <= transmit_word[7:0]; tx_state <= TX_DO1; end TX_DO1: begin doit <= 1; tx_state <= TX_WAIT1; end TX_WAIT1: begin doit <= 0; if (tx_done) tx_state <= TX_BYTE2; else tx_state <= TX_WAIT1; end TX_BYTE2: begin tx_data <= transmit_word[15:8]; tx_state <= TX_DO2; end TX_DO2: begin doit <= 1; tx_state <= TX_WAIT2; end TX_WAIT2: begin doit <= 0; if (tx_done) tx_state <= TX_WAIT; else tx_state <= TX_WAIT2; end endcase end assign do_transmit = doit; assign transmit_byte = tx_data;
Note that for this state machine, we use begin_transfer as the trigger to start transfering the 2 bytes contained in transmit_word. These are both controlled in the next state machine.
When the RPi sends a byte to the FPGA, it will send 8 bits, so we can use those 8 bits as commands that the FPGA will receive and respond to. This is common in data acquisition systems - data can also be commands. The first thing you have to do is figure out what commands you want to send, and assign bits (or combination of bits) from the 8 bit data word. The main command that we want the RPi to send will be to latch the data from the XADC block and send the 2 bytes along, so let's assign this to bit 0 (LSB) of the data word. That means that if that bit is asserted, the FPGA will send the data from r_adc_data. Let's also make a command that tells the FPGA to send the data on the 16 slide switches, so that we can be sure things are working properly. We can also have the FPGA send the firmware version. So we have 3 possible values for the FPGA to send, which means we need 2 bits to encode those 3, so we can arbitrarily use the lower 3 bits to have the following meaning:
Now all we will need to do is add a state machine that will receive uart data, decode it, and decide what to do with it. This new state machine will also drive the transmit state machine that is triggered by the transmit button in the voltmeter project.
The new state machine should look like this:
and will have the following states:
// // next comes the uart receive state machine // reg [2:0] rx_state; localparam [2:0] RX_WAIT=0, RX_RPI_LATCH=1, RX_Transfer=2, RX_PAUSE=3, RX_WAIT_Done1=4, RX_WAIT_Done2=5; reg start_tx; wire [1:0] latch_what = {rx_data[2],rx_data[1]}; reg [15:0] transmit_this; always @ (posedge clock25) begin if (reset) begin start_tx <= 0; rx_state <= RX_WAIT; transmit_this <= 0; end else case (rx_state) RX_WAIT: begin start_tx <= 0; transmit_this <= 0; if (dv && rx_data[0]) rx_state <= RX_RPI_LATCH; else rx_state <= RX_WAIT; end RX_RPI_LATCH: begin rx_state <= RX_Transfer; case (latch_what) 2'b00: transmit_this <= 'hEEEE; // error! 2'b01: transmit_this <= r_adc_data; 2'b10: transmit_this <= sw; 2'b11: transmit_this <= VERSION; endcase end RX_Transfer: begin start_tx <= 1; rx_state <= RX_WAIT_Done1; end RX_WAIT_Done1: begin if (tx_done) rx_state <= RX_PAUSE; else rx_state <= RX_WAIT_Done1; end RX_PAUSE: begin rx_state <= RX_WAIT_Done2; end RX_WAIT_Done2: begin if (tx_done) rx_state <= RX_WAIT_Done2; else rx_state <= RX_WAIT; end endcase end
We have a 3-bit state rx_state (6 states so we need 3 bits), a register called start_tx that ties into the TX state machine, this will be its trigger. Then a 2-bit wire called latch_what that is made of a concatenation of bits 1 and 2 of the data sent as a command, used by the state machine in the RX_RPI_LATCH state to know what to grab. Then a register called transmit_this that is used by the TX state machine to send to the RPi, one byte at a time. One thing to always watch out for: when you declare latch_what, if you forget to declare it as a 2-bit wire and instead make the following mistake leaving out the wire [1:0] and just saying wire:
wire latch_what = {rx_data[2],rx_data[1]};
then the synthesized code will only look at the lower bit and you won't get what you want in the RX_RPI_LATCH stage. It's not a bug in the system, it's more of a weakness.
The full code should look something like this:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 09/23/2023 03:41:41 PM // Design Name: // Module Name: top // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module top( input clock, // system clock input reset, // BTNR input version, // BTNU input adc_n, adc_p, // VCAUX 6, P and N input [15:0] sw, // slide switches output [15:0] led, // 16 onboard LEDs above the switches output [6:0] segment, // 4 digit LED display output dp, // "." next to each LED digit output [3:0] digit, // which of the 4 digits to drive output [7:0] JB, // PMOD for debugging input rx, // UART receive output tx // UART transmit ); parameter VERSION = 'h0011; // // make a slower clock for the UART transmitter and for the FSM // below that controls it. We do this because the logic analyzer // is slow too, it will only look at 8 wires at 25MHz. So if we // divide the system clock by 4, that will give us a 25MHz clock // and that should do nicelly, and it will make clocks_per_bit come // out as an integer // reg [1:0] count2 = 0; always @ (posedge clock) count2 <= count2 + 1; wire clock25; BUFG clk25buf (.I(count2[1]), .O(clock25) ); // // generate a 104MHz clock for the XADC so we can get samples at 1MHz // wire locked, clock104; clock_104 XADC_CLOCK( .reset(reset), .locked(locked), .clk_in1(clock), .clk_out1(clock104) ); // // next drive the 4 7-segment displays // wire [15:0] display_this; display4 DISPLAY ( .clk100(clock), .number(display_this), .digit(digit), .segments(segment), .period(dp) ); // // instantiate the UART receiver. run with the 50MHz clock so that // we can run the transmitter FSM at 100MHz and not get lost // wire dv; wire [7:0] rx_data; uart_rx RECEIVER ( .i_Clocks_per_Bit('d25), .i_Clock(clock25), .i_Reset(reset), .i_Rx_Serial(rx), // tied to FPGA rx output .o_Rx_DV(dv), .o_Rx_Byte(rx_data) ); // // instantiate the UART transmitter // wire tx_active, tx_done; wire do_transmit; wire [7:0] transmit_byte; uart_tx TRANSMITTER ( .i_Clocks_per_Bit('d25), .i_Clock(clock25), .i_Reset(reset), .i_Tx_DV(do_transmit), .i_Tx_Byte(transmit_byte), .o_Tx_Active(tx_active), .o_Tx_Serial(tx), // tied to FPGA tx output .o_Tx_Done(tx_done) ); // // here is the XADC block // wire [6:0] daddr_in = 7'h16; wire adc_ready, isbusy, adc_data_ready, eos_out, alarm; wire [15:0] adc_data; wire [4:0] channel_out; myxadc XADC_INST ( .daddr_in(7'h16), // specifies vcaux6 pints to digitize .dclk_in(clock104), // 50MHz clock .den_in(adc_ready), // tied to adc_ready, tells adc to convert, tieing causes continuous conversions .di_in(16'h0), // to set the data to something, not used here .dwe_in(1'b0), // set to enable writing to di_in, which we don't want to do .vauxp6(adc_p), // positive input to digitize .vauxn6(adc_n), // negative input to digitize .busy_out(isbusy), // tells you the adc is busy converting .channel_out(channel_out[4:0]), // for using more than 1 channel, tells you which one. not used here .do_out(adc_data), // adc value from conversion .drdy_out(adc_data_ready), //tells you valid data is ready to be latched .eoc_out(adc_ready), // specifies that the ADC is ready (conversion complete) .eos_out(eos_out), // specifies that conversion sequence is complete .alarm_out(alarm), // OR's output of all internal alarms, not used here .vp_in(1'b0), // dedicated analog input pair for differential, tied to 0 if not used .vn_in(1'b0) ); // // wait for XADC to tell you something is ready to latch. note this means continuous latching // reg [15:0] r_adc_data; always @ (negedge isbusy) begin if (reset) r_adc_data <= 16'h0; else r_adc_data <= adc_data; end // // make a ~1Hz clock so we can run the LED display slower // reg [26:0] counter; reg [15:0] s_adc_data; always @ (posedge clock) begin if (reset) counter <= 0; else counter <= counter + 1; end wire clock_1hz = counter[26]; always @ (posedge clock_1hz) s_adc_data <= r_adc_data; assign display_this = version ? VERSION : s_adc_data; // // now make a state machine to deal with transmitting 2 bytes // reg [2:0] tx_state; // 7 states so 3 bits will do localparam [2:0] TX_WAIT=0, TX_BYTE1=1, TX_DO1=2, TX_WAIT1=3, TX_BYTE2=4, TX_DO2=5, TX_WAIT2=6; reg doit; wire [16:0] transmit_word; wire begin_transfer; reg [7:0] tx_data; always @ (posedge clock25) begin if (reset) begin tx_state <= TX_WAIT; doit <= 0; tx_data <= 0; end else case (tx_state) TX_WAIT: begin if (begin_transfer) tx_state <= TX_BYTE1; else tx_state <= TX_WAIT; doit <= 0; tx_data <= 0; end TX_BYTE1: begin tx_data <= transmit_word[7:0]; tx_state <= TX_DO1; end TX_DO1: begin doit <= 1; tx_state <= TX_WAIT1; end TX_WAIT1: begin doit <= 0; if (tx_done) tx_state <= TX_BYTE2; else tx_state <= TX_WAIT1; end TX_BYTE2: begin tx_data <= transmit_word[15:8]; tx_state <= TX_DO2; end TX_DO2: begin doit <= 1; tx_state <= TX_WAIT2; end TX_WAIT2: begin doit <= 0; if (tx_done) tx_state <= TX_WAIT; else tx_state <= TX_WAIT2; end endcase end assign do_transmit = doit; assign transmit_byte = tx_data; // // next comes the uart receive state machine // reg [2:0] rx_state; localparam [2:0] RX_WAIT=0, RX_RPI_LATCH=1, RX_Transfer=2, RX_PAUSE=3, RX_WAIT_Done1=4, RX_WAIT_Done2=5; reg start_tx; wire [1:0] latch_what = {rx_data[2],rx_data[1]}; reg [15:0] transmit_this; always @ (posedge clock25) begin if (reset) begin start_tx <= 0; rx_state <= RX_WAIT; transmit_this <= 0; end else case (rx_state) RX_WAIT: begin start_tx <= 0; transmit_this <= 0; if (dv && rx_data[0]) rx_state <= RX_RPI_LATCH; else rx_state <= RX_WAIT; end RX_RPI_LATCH: begin rx_state <= RX_Transfer; case (latch_what) 2'b00: transmit_this <= 'hEEEE; // error! 2'b01: transmit_this <= r_adc_data; 2'b10: transmit_this <= sw; 2'b11: transmit_this <= VERSION; endcase end RX_Transfer: begin start_tx <= 1; rx_state <= RX_WAIT_Done1; end RX_WAIT_Done1: begin if (tx_done) rx_state <= RX_PAUSE; else rx_state <= RX_WAIT_Done1; end RX_PAUSE: begin rx_state <= RX_WAIT_Done2; end RX_WAIT_Done2: begin if (tx_done) rx_state <= RX_WAIT_Done2; else rx_state <= RX_WAIT; end endcase end assign transmit_word = transmit_this; assign begin_transfer = start_tx; assign led = transmit_this; assign JB = {doit,do_transmit,tx_active,tx_done,latch_what[1:0],dv,clock25}; endmodule
The constraints file should be the same as in the previous project, but you will have to add the slide switches:
## clock set_property PACKAGE_PIN W5 [get_ports clock] set_property IOSTANDARD LVCMOS33 [get_ports clock] # rx is pin 4 on JC set_property PACKAGE_PIN P18 [get_ports rx] set_property IOSTANDARD LVCMOS33 [get_ports rx] # tx is pin 1 on JC set_property PACKAGE_PIN K17 [get_ports tx] set_property IOSTANDARD LVCMOS33 [get_ports tx] ## Switches set_property PACKAGE_PIN V17 [get_ports {sw[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[0]}] set_property PACKAGE_PIN V16 [get_ports {sw[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[1]}] set_property PACKAGE_PIN W16 [get_ports {sw[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[2]}] set_property PACKAGE_PIN W17 [get_ports {sw[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[3]}] set_property PACKAGE_PIN W15 [get_ports {sw[4]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[4]}] set_property PACKAGE_PIN V15 [get_ports {sw[5]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[5]}] set_property PACKAGE_PIN W14 [get_ports {sw[6]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[6]}] set_property PACKAGE_PIN W13 [get_ports {sw[7]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[7]}] set_property PACKAGE_PIN V2 [get_ports {sw[8]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[8]}] set_property PACKAGE_PIN T3 [get_ports {sw[9]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[9]}] set_property PACKAGE_PIN T2 [get_ports {sw[10]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[10]}] set_property PACKAGE_PIN R3 [get_ports {sw[11]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[11]}] set_property PACKAGE_PIN W2 [get_ports {sw[12]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[12]}] set_property PACKAGE_PIN U1 [get_ports {sw[13]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[13]}] set_property PACKAGE_PIN T1 [get_ports {sw[14]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[14]}] set_property PACKAGE_PIN R2 [get_ports {sw[15]}] set_property IOSTANDARD LVCMOS33 [get_ports {sw[15]}] # push buttons set_property PACKAGE_PIN T17 [get_ports reset] set_property IOSTANDARD LVCMOS33 [get_ports reset] set_property PACKAGE_PIN T18 [get_ports version] set_property IOSTANDARD LVCMOS33 [get_ports version] # LEDs set_property PACKAGE_PIN U16 [get_ports {led[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}] set_property PACKAGE_PIN E19 [get_ports {led[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}] set_property PACKAGE_PIN U19 [get_ports {led[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}] set_property PACKAGE_PIN V19 [get_ports {led[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}] set_property PACKAGE_PIN W18 [get_ports {led[4]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[4]}] set_property PACKAGE_PIN U15 [get_ports {led[5]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[5]}] set_property PACKAGE_PIN U14 [get_ports {led[6]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[6]}] set_property PACKAGE_PIN V14 [get_ports {led[7]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[7]}] set_property PACKAGE_PIN V13 [get_ports {led[8]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[8]}] set_property PACKAGE_PIN V3 [get_ports {led[9]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[9]}] set_property PACKAGE_PIN W3 [get_ports {led[10]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[10]}] set_property PACKAGE_PIN U3 [get_ports {led[11]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[11]}] set_property PACKAGE_PIN P3 [get_ports {led[12]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[12]}] set_property PACKAGE_PIN N3 [get_ports {led[13]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[13]}] set_property PACKAGE_PIN P1 [get_ports {led[14]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[14]}] set_property PACKAGE_PIN L1 [get_ports {led[15]}] set_property IOSTANDARD LVCMOS33 [get_ports {led[15]}] ## ## 7 segment display set_property PACKAGE_PIN W7 [get_ports {segment[0]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[0]} ] set_property PACKAGE_PIN W6 [get_ports {segment[1]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[1]} ] set_property PACKAGE_PIN U8 [get_ports {segment[2]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[2]} ] set_property PACKAGE_PIN V8 [get_ports {segment[3]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[3]} ] set_property PACKAGE_PIN U5 [get_ports {segment[4]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[4]} ] set_property PACKAGE_PIN V5 [get_ports {segment[5]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[5]} ] set_property PACKAGE_PIN U7 [get_ports {segment[6]} ] set_property IOSTANDARD LVCMOS33 [get_ports {segment[6]} ] ## ## LED period (dot) set_property PACKAGE_PIN V7 [get_ports {dp}] set_property IOSTANDARD LVCMOS33 [get_ports {dp}] ## ## digit select set_property PACKAGE_PIN U2 [get_ports {digit[0]} ] set_property IOSTANDARD LVCMOS33 [get_ports {digit[0]} ] set_property PACKAGE_PIN U4 [get_ports {digit[1]} ] set_property IOSTANDARD LVCMOS33 [get_ports {digit[1]} ] set_property PACKAGE_PIN V4 [get_ports {digit[2]} ] set_property IOSTANDARD LVCMOS33 [get_ports {digit[2]} ] set_property PACKAGE_PIN W4 [get_ports {digit[3]} ] set_property IOSTANDARD LVCMOS33 [get_ports {digit[3]} ] ## Buttons set_property PACKAGE_PIN T18 [get_ports version] set_property IOSTANDARD LVCMOS33 [get_ports version] set_property PACKAGE_PIN W19 [get_ports latch] set_property IOSTANDARD LVCMOS33 [get_ports latch] set_property PACKAGE_PIN T17 [get_ports reset] set_property IOSTANDARD LVCMOS33 [get_ports reset] ## Pmod Header JXADC ## Schematic name = XA1_P set_property PACKAGE_PIN J3 [get_ports adc_p ] set_property IOSTANDARD LVCMOS33 [get_ports adc_p ] ## Schematic name = XA1_N set_property PACKAGE_PIN K3 [get_ports adc_n ] set_property IOSTANDARD LVCMOS33 [get_ports adc_n ] ##Pmod Header JB ##Sch name = JB1 set_property PACKAGE_PIN A14 [get_ports {JB[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[0]}] ##Sch name = JB2 set_property PACKAGE_PIN A16 [get_ports {JB[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[1]}] ##Sch name = JB3 set_property PACKAGE_PIN B15 [get_ports {JB[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[2]}] ##Sch name = JB4 set_property PACKAGE_PIN B16 [get_ports {JB[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[3]}] ##Sch name = JB7 set_property PACKAGE_PIN A15 [get_ports {JB[4]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[4]}] ##Sch name = JB8 set_property PACKAGE_PIN A17 [get_ports {JB[5]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[5]}] ##Sch name = JB9 set_property PACKAGE_PIN C15 [get_ports {JB[6]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[6]}] ##Sch name = JB10 set_property PACKAGE_PIN C16 [get_ports {JB[7]}] set_property IOSTANDARD LVCMOS33 [get_ports {JB[7]}]The above project archive can be found here.
The RPi program will be similar to the voltmeter program made for the previous lab, but now we have to add the ability to transmit information. The code that works is called voltmeter2 and looks like this:
from tkinter import * import serial import serial.tools.list_ports import codecs import time root = Tk() class Application(Frame): """ Create the window and populate with widgets """ def __init__(self,parent): """ initializes the frame """ Frame.__init__(self,parent,background="white") self.parent = parent self.grid() self.create_widgets() self.isopen = 0 self.openPort() def create_widgets(self): self.buttonQ = Button(self, text="Quit") self.buttonQ["command"] = self.quitit self.buttonQ.grid(row=0,column=0, sticky=W) self.fetchlabel = Label(self, text="Fetch:") self.fetchlabel.grid(row=0, column=1, sticky=W) # # set up the fetch buttons to send the address as an argument # self.buttonD = Button(self,text="Data") self.buttonD.grid(row=0,column=4, sticky=W) self.buttonD["command"] = lambda: self.getdata(3) self.buttonV = Button(self,text="Version") self.buttonV.grid(row=0,column=2, sticky=W) self.buttonV["command"] = lambda: self.getdata(7) self.buttonS = Button(self,text="Test") self.buttonS.grid(row=0,column=3, sticky=W) self.buttonS["command"] = lambda: self.getdata(5) self.slabel = Label(self, text="Status:") self.slabel.grid(row=1, column=0, sticky=W) self.status = Text(self,height=1,width=40) self.status.grid(row=1, column=1, columnspan=4, sticky=W) self.status.delete("1.0",END) self.rlabel = Label(self, text="Result:") self.rlabel.grid(row=2, column=0, sticky=W) self.answer = Text(self,height=1, width=40) self.answer.grid(row=2,column=1,columnspan=4, sticky=W) self.answer.delete("1.0",END) def quitit(self): print("That's all folks!") quit() def openPort(self): if self.isopen == 1: self.status.insert(END,"Port is already open!\n") self.ser.close() #return # # defaults # port = "/dev/ttyS0" sbaud = "1000000" baud = int(sbaud) timeout = 2 print("port="+port+" baud="+sbaud) self.ser = serial.Serial(port,sbaud,timeout=timeout) if self.ser.isOpen(): self.status.insert(END,self.ser.name + " is now open\n") print(self.ser.name + " is now open...") self.isopen = 1 else: self.status.insert(END,self.ser.name + " is NOT open!!!\n") print("sorry, problem trying to open port "+port+"\n") def getdata(self,which): # # check to see if any port has been opened or not # if self.isopen == 0: self.status.insert(END,"Sorry but you MUST open a port first!") return # # fetch what? # addr = which; if which == 0: self.status.insert(END,"??? invalid address!!!") return # # send the command # print("sending "+str(addr)) self.status.delete(1.0,END) self.status.insert(1.0,"Sending...") sendb = addr.to_bytes(1,"little") self.ser.write(sendb) self.status.insert(END,"done!") # # now wait a short time and then look for input # nbytes = 2 self.status.insert(END," Waiting...") time.sleep(0.1) tdata = self.ser.read(nbytes) ld = len(tdata) print(ld) if ld > 0: # # flag input has arrived and print out in hex # noinput = 0 idata = int.from_bytes(tdata,byteorder="little") print("idata1: "+str(idata)) self.status.insert(END," done!") self.answer.delete(1.0,END) if addr == 3: val = idata/16 print("idata: ",str(val)) # # now calculate the votage read # voltage = val * 0.244E-3 self.answer.delete(1.0,END) self.answer.insert(1.0,hex(idata) + " = " + str(voltage)+" volts") else: self.answer.insert(1.0,hex(idata)) else: self.status.insert(END," timeout!") def main(): # modify the window root.title("Python/BASYS3 Voltmeter - RPi control") root.wm_title("Python/BASYS3 Voltmeter - RPi control") root.geometry("400x200+800+400") root.update() #create the frame that holds other widgets app = Application(root) #kick off event loop root.mainloop() if __name__ == '__main__': main()What it does is to define buttons for transmitting the data for what the FPGA should latch: "Data" sends a binary 011, "Test" sends a binary 101, and "Version" sends a binary 111. Notice that the LSB is always 1, which is the signal to the FPGA that we are wanting it to latch something. The next 2 bits (upper 2) are 01 for data, 10 for test, and 11 for version, in agreement with what is in the firmware.
Any modern DAQ system should be via a graphical user interface that can show things more graphically than voltmeter2.py. This is easy to implement in python with tkinter. All you need to do is define a canvas in the class definition, and use create_oval, create_line, and create_text to draw. The code to do this is shown next. Note the use of frames in the class instantiation, we connect the buttons to a particular frame and then put frames in rows on the screen. This allows the buttons to conform to the local frame and not have everything spaced globally. Run this and you will see. The code is available here.
from tkinter import * import serial import serial.tools.list_ports import codecs import math import time root = Tk() class Application(Frame): """ Create the window and populate with widgets """ def __init__(self,parent): """ initializes the frame """ Frame.__init__(self,parent,background="white") # # frame1 will hold the first row, which are all the buttons # self.frame1 = Frame(parent) self.frame1.grid(row=0,column=0,sticky=W) self.frame1.config(highlightthickness=1,highlightbackground="black") # # frame 2 holds the next 2 rows, which are the status and answer text # widgets # self.frame2 = Frame(parent) self.frame2.grid(row=1,column=0,sticky=W) self.frame2.config(highlightthickness=1,highlightbackground="black") self.parent = parent self.canvas_width = 500 self.canvas_height = 500 self.grid() self.create_widgets() self.isopen = 0 self.openPort() def create_widgets(self): self.buttonQ = Button(self.frame1, text="Quit") self.buttonQ["command"] = self.quitit self.buttonQ.grid(row=0,column=0, sticky=W) self.fetchlabel = Label(self.frame1, text="Fetch:") self.fetchlabel.grid(row=0, column=1, sticky=W) # # set up the fetch buttons to send the address as an argument # self.buttonD = Button(self.frame1,text="Data") self.buttonD.grid(row=0,column=4, sticky=W) self.buttonD["command"] = lambda: self.getdata(3) self.buttonV = Button(self.frame1,text="Version") self.buttonV.grid(row=0,column=2, sticky=W) self.buttonV["command"] = lambda: self.getdata(7) self.buttonS = Button(self.frame1,text="Test") self.buttonS.grid(row=0,column=3, sticky=W) self.buttonS["command"] = lambda: self.getdata(5) self.slabel = Label(self.frame2, text="Status:") self.slabel.grid(row=0, column=0, sticky=W) self.status = Text(self.frame2,height=1,width=40) self.status.grid(row=0, column=1, columnspan=4, sticky=W) self.status.delete("1.0",END) self.rlabel = Label(self.frame2, text="Result:") self.rlabel.grid(row=1, column=0, sticky=W) self.answer = Text(self.frame2,height=1, width=40) self.answer.grid(row=1,column=1,columnspan=4, sticky=W) self.answer.delete("1.0",END) # # here is the tkinter canvas that we will write to # self.C = Canvas(self,bg="white", height=self.canvas_height,width=self.canvas_width) self.C.grid(row=2,column=0,columnspan=5) def quitit(self): print("That's all folks!") quit() def openPort(self): if self.isopen == 1: self.status.insert(END,"Port is already open!\n") self.ser.close() #return # # defaults # port = "/dev/ttyS0" sbaud = "1000000" baud = int(sbaud) timeout = 5 print("port="+port+" baud="+sbaud) self.ser = serial.Serial(port,sbaud,timeout=timeout) if self.ser.isOpen(): self.status.insert(END,self.ser.name + " is now open\n") print(self.ser.name + " is now open...") self.isopen = 1 else: self.status.insert(END,self.ser.name + " is NOT open!!!\n") print("sorry, problem trying to open port "+port+"\n") def getdata(self,which): # # check to see if any port has been opened or not # if self.isopen == 0: self.status.insert(END,"Sorry but you MUST open a port first!") return # # fetch what? # addr = which; if which == 0: self.status.insert(END,"??? invalid address!!!") return # # send the command # print("sending "+str(addr)) self.status.delete(1.0,END) self.status.insert(1.0,"Sending...") sendb = addr.to_bytes(1,"little") self.ser.write(sendb) self.status.insert(END,"done!") # # now wait a short time and then look for input # nbytes = 2 self.status.insert(END," Waiting...") time.sleep(0.1) tdata = self.ser.read(nbytes) ld = len(tdata) print(ld) if ld > 0: # # flag input has arrived and print out in hex # noinput = 0 idata = int.from_bytes(tdata,byteorder="little") print("idata1: "+str(idata)) self.status.insert(END," done!") self.answer.delete(1.0,END) if addr == 3: val = idata/16 print("idata: ",str(val)) # # now calculate the votage read # voltage = val * 0.244E-3 self.answer.delete(1.0,END) self.answer.insert(1.0,hex(idata) + " = " + str(voltage)+" volts") # # now display the voltage in the canvas graphically # self.display(voltage) else: self.answer.insert(1.0,hex(idata)) else: self.status.insert(END," timeout!") def display(self,volts): # # initialize the canvas # self.C.delete("all") # # draw a circle centered at half the canvas width and height # with radius circle_radius # circle_radius = 100 circle_center_x = self.canvas_width/2 circle_center_y = circle_radius + 50 left_x = circle_center_x - 100 right_x = circle_center_x + 100 left_y = circle_center_y - 100 right_y = circle_center_y + 100 self.C.create_oval(left_x,left_y,right_x,right_y) # # voltage goes from 0 to 1, so scale the angle of # the meter line # angle = math.pi * volts # # linex and liney will be the coordinate on the circle # corresponding to the voltage. we use left horizontal as 0 volts # and right horizontal as 1 volt # linex = circle_radius * math.cos(angle) liney = circle_radius * math.sin(angle) x0 = circle_center_x y0 = circle_center_y # # draw the line representing the voltage # self.C.create_line(x0,y0,x0-linex,y0-liney) # # label the voltage scale, use 0 to 1 by 0.1 volts # for i in range(0,11): val = float(i)/10 sval = str(val) ang = math.pi * val x1 = x0 - (circle_radius+10) * math.cos(ang) y1 = y0 - (circle_radius+10) * math.sin(ang) self.C.create_text(x1,y1,text=sval) def main(): # modify the window root.title("Python/BASYS3 Voltmeter - RPi control") root.wm_title("Python/BASYS3 Voltmeter - RPi control") root.geometry("500x500+800+400") root.update() #create the frame that holds other widgets app = Application(root) #kick off event loop root.mainloop() if __name__ == '__main__': main()