FlyingDutch Napisano Marzec 30, 2022 Udostępnij Napisano Marzec 30, 2022 (edytowany) Cześć, jestem w trakcie prac nad zbieraniem sampli sygnału audio na zestawie FPGA "Sipeed Tang Nano 4K" i potrzebuje w celu debugowania układu jakiegoś kanału do komunikacji z portem szeregowym, lub Arduino. Chciałem użyć któregoś z IP Cores z "Gowin EDA" ale zarówno UART jak i "SPI Master" są tak przekombinowane, że ich użycie wymaga napisania sporej ilości kodu. Postanowiłem więc użyć zestawu FPGA "Elbert v.2" w celu napisania prostego kodu do komunikacji za pomocą protokołu SPI . Powstał więc bardzo prosty "SPI Master", który jest uruchomiony na zestawie FPGA i połączony za pomocą protokołu SPI z "Arduino UNO", które odbiera dane i wyświetla je za pomocą "Monitora portu szeregowego". Do samego kodu "SPI Master'a" został dołączony "debouncer" do przycisku i generator pojedynczego impulsu (o długości okresu zegara), który inicjuje wysłanie jednego bajtu danych. W celu wygenerowania jednego bardzo krótkiego impulsu napisałem (za pomocą kilku przerzutników typu D) i bramki AND układ generatora monostabilnego. Tutaj wynik symulacji działania generatora monostabilnego: Układ działa w ten sposób, że naciśnięcie przycisku (SW1 z Elbert'a) powoduje wysłanie jednego bajtu danych do Arduino za pomocą SPI. Tak wygląda układ testowy (potrzebny był poza FPGA i Arduino 4-liniowy konwerter poziomów logicznych): Tutaj kod w VHDL głównego modułu (a właściwie entity) projektu - plik "lw_spi_master.vhd ": library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values --use IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx primitives in this code. --library UNISIM; --use UNISIM.VComponents.all; entity lw_spi_master is generic ( c_clkfreq : integer := 12_000_000; c_sclkfreq : integer := 5_000_000; c_cpol : std_logic := '0'; c_cpha : std_logic := '0' ); Port ( clk_i : in STD_LOGIC; en_i_Low : in STD_LOGIC; mosi_data_i : in STD_LOGIC_VECTOR (7 downto 0); miso_data_o : out STD_LOGIC_VECTOR (7 downto 0); data_ready_o : out STD_LOGIC; cs_o : out STD_LOGIC; sclk_o : out STD_LOGIC; mosi_o : out STD_LOGIC; miso_i : in STD_LOGIC ); end lw_spi_master; architecture Behavioral of lw_spi_master is component debounce is port ( clk : IN STD_LOGIC; --input clock button : IN STD_LOGIC; --input signal to be debounced result : OUT STD_LOGIC); --debounced signal end component; component monostable_ff is port( mono_pulse : out std_logic; clk : in std_logic; reset: in std_logic; key :in std_logic ); end component; signal write_reg : std_logic_vector (7 downto 0) := (others => '0'); signal read_reg : std_logic_vector (7 downto 0) := (others => '0'); signal en_i : std_logic := '0'; signal sclk_en : std_logic := '0'; signal sclk : std_logic := '0'; signal sclk_prev : std_logic := '0'; signal sclk_rise : std_logic := '0'; signal sclk_fall : std_logic := '0'; signal pol_phase : std_logic_vector (1 downto 0) := (others => '0'); signal mosi_en : std_logic := '0'; signal miso_en : std_logic := '0'; signal en_i_DB : std_logic; signal cnt : std_logic_vector (10 downto 0) := (others => '0'); constant c_edgecntrlimdiv2 : integer := c_clkfreq/(c_sclkfreq*2); signal edgecntr : integer range 0 to c_edgecntrlimdiv2 := 0; signal cntr : integer range 0 to 15 := 0; signal small_cnt : integer range 0 to 1000 := 0; signal blokada : integer range 0 to 3 := 0; type states is (S_IDLE, S_TRANSFER); signal state : states := S_IDLE; -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- begin C1: debounce port map (clk_i,en_i_Low, en_i_DB); C2: monostable_ff port map ( mono_pulse => en_i, clk => clk_i, reset => '1', key => not en_i_DB ); --en_i <= not(en_i_DB); pol_phase <= c_cpol & c_cpha; P_SAMPLE_EN : process (pol_phase, sclk_fall, sclk_rise) begin case pol_phase is when "00" => mosi_en <= sclk_fall; miso_en <= sclk_rise; when "01" => mosi_en <= sclk_rise; miso_en <= sclk_fall; when "10" => mosi_en <= sclk_rise; miso_en <= sclk_fall; when "11" => mosi_en <= sclk_fall; miso_en <= sclk_rise; when others => end case; end process; P_RISEFALL_DETECT : process (sclk, sclk_prev) begin if (sclk = '1' and sclk_prev = '0') then sclk_rise <= '1'; else sclk_rise <= '0'; end if; if (sclk = '0' and sclk_prev = '1') then sclk_fall <= '1'; else sclk_fall <= '0'; end if; end process; P_MAIN : process (clk_i) begin if (rising_edge(clk_i)) then sclk_prev <= sclk; case state is when S_IDLE => cs_o <= '1'; mosi_o <= '0'; data_ready_o <= '0'; sclk_en <= '0'; cntr <= 0; if (c_cpol = '0') then sclk_o <= '0'; else sclk_o <= '1'; end if; if (en_i = '1') then state <= S_TRANSFER; sclk_en <= '1'; write_reg <= mosi_data_i; mosi_o <= mosi_data_i(7); read_reg <= x"00"; end if; when S_TRANSFER => cs_o <= '0'; mosi_o <= write_reg(7); if (c_cpha = '1') then if (cntr = 0) then sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; elsif (cntr = 8) then data_ready_o <= '1'; miso_data_o <= read_reg; if (mosi_en = '1') then data_ready_o <= '0'; if (en_i = '1') then write_reg <= mosi_data_i; mosi_o <= mosi_data_i(7); sclk_o <= sclk; cntr <= 0; else state <= S_IDLE; cs_o <= '1'; end if; end if; elsif (cntr = 9) then if (miso_en = '1') then state <= S_IDLE; cs_o <= '1'; end if; else sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; if (mosi_en = '1') then mosi_o <= write_reg(7); write_reg(7 downto 1) <= write_reg(6 downto 0); end if; end if; else -- c_cpha = '0' if (cntr = 0) then sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; elsif (cntr = 8) then data_ready_o <= '1'; miso_data_o <= read_reg; sclk_o <= sclk; if (mosi_en = '1') then data_ready_o <= '0'; if (en_i = '1') then write_reg <= mosi_data_i; mosi_o <= mosi_data_i(7); cntr <= 0; else cntr <= cntr + 1; end if; if (miso_en = '1') then state <= S_IDLE; cs_o <= '1'; end if; end if; elsif (cntr = 9) then if (miso_en = '1') then state <= S_IDLE; cs_o <= '1'; end if; else sclk_o <= sclk; if (miso_en = '1') then read_reg(0) <= miso_i; read_reg(7 downto 1) <= read_reg(6 downto 0); cntr <= cntr + 1; end if; if (mosi_en = '1') then write_reg(7 downto 1) <= write_reg(6 downto 0); end if; end if; end if; end case; end if; end process; P_SCLK_GEN : process (clk_i) begin if (rising_edge(clk_i)) then if (sclk_en = '1') then if edgecntr = c_edgecntrlimdiv2-1 then sclk <= not sclk; edgecntr <= 0; else edgecntr <= edgecntr + 1; end if; else edgecntr <= 0; if (c_cpol = '0') then sclk <= '0'; else sclk <= '1'; end if; end if; end if; end process; end Behavioral; W tym pliku jest zawarta cała implementacja "SPI Master'a". Widać też użycia komponentów "debouncer'a" i generatora monostabilnego. Tutaj kod "deuboncer'a": LIBRARY ieee; USE ieee.std_logic_1164.all; USE ieee.std_logic_unsigned.all; ENTITY debounce IS GENERIC( counter_size : INTEGER := 20); --counter size (19 bits gives 10.5ms with 50MHz clock) PORT( clk : IN STD_LOGIC; --input clock button : IN STD_LOGIC; --input signal to be debounced result : OUT STD_LOGIC); --debounced signal END debounce; ARCHITECTURE logic OF debounce IS SIGNAL flipflops : STD_LOGIC_VECTOR(1 DOWNTO 0); --input flip flops SIGNAL counter_set : STD_LOGIC; --sync reset to zero SIGNAL counter_out : STD_LOGIC_VECTOR(counter_size DOWNTO 0) := (OTHERS => '0'); --counter output BEGIN counter_set <= flipflops(0) xor flipflops(1); --determine when to start/reset counter PROCESS(clk) BEGIN IF(clk'EVENT and clk = '1') THEN flipflops(0) <= button; flipflops(1) <= flipflops(0); If(counter_set = '1') THEN --reset counter because input is changing counter_out <= (OTHERS => '0'); ELSIF(counter_out(counter_size) = '0') THEN --stable input time is not yet met counter_out <= counter_out + 1; ELSE --stable input time is met result <= flipflops(1); END IF; END IF; END PROCESS; END logic; Tutaj kod VHDL generatora krótkiego impulsu "monostable_ff.vhd": library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity monostable_ff is port( mono_pulse : out std_logic; clk : in std_logic; reset: in std_logic; key :in std_logic ); end monostable_ff; architecture Behavioral of monostable_ff is component DFF_AsyncReset is port( Q : out std_logic; QNot : out std_logic; clk : in std_logic; reset: in std_logic; D :in std_logic ); end component; component nand2 is port(a,b : in std_logic; y : out std_logic); end component; signal Q1,Q2,Q3,Q4,Q5 : std_logic; signal Q1N,Q2N,Q3N,Q4N,Q5N : std_logic; signal s11 : std_logic; begin DFF1: DFF_AsyncReset port map ( Q => Q1, QNot => Q1N, clk => clk, reset => reset, D => key ); DFF2: DFF_AsyncReset port map ( Q => Q2, QNot => Q2N, clk => clk, reset => reset, D => Q1 ); DFF3: DFF_AsyncReset port map ( Q => Q3, QNot => Q3N, clk => clk, reset => reset, D => Q2 ); DFF4: DFF_AsyncReset port map ( Q => Q4, QNot => Q4N, clk => clk, reset => reset, D => Q3 ); DFF5: DFF_AsyncReset port map ( Q => Q5, QNot => Q5N, clk => clk, reset => reset, D => Q4 ); mono_pulse <= (Q1 and Q5N); end Behavioral; Układ jest zrobiony na "liniI opóźniającej" z kilku przerzutników typu D i dwu-wejściowej bramki "AND" (kiedyś w mojej przeszłości budowało się podobne układy na układach scalonych z serii TTL). Do budowy tego generatora monostabilnego użyłem przerzutników typu D (wejście zegarowe reaguje na zbocze narastające) z asynchronicznym resetem stanem niskim. Tutaj kod przerzutnika typu D ---------------------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; -- Uncomment the following library declaration if using -- arithmetic functions with Signed or Unsigned values --use IEEE.NUMERIC_STD.ALL; -- Uncomment the following library declaration if instantiating -- any Xilinx primitives in this code. --library UNISIM; --use UNISIM.VComponents.all; entity DFF_AsyncReset is port( Q : out std_logic; QNot : out std_logic; clk : in std_logic; reset: in std_logic; D :in std_logic ); end DFF_AsyncReset; architecture Behavioral of DFF_AsyncReset is begin process(clk,reset) begin if(reset='0') then Q <= '0'; QNot <= '1'; elsif(rising_edge(clk)) then Q <= D; QNot <= (not D); end if; end process; end Behavioral; A tutaj plik "user constraints" "lw-spi_master.ucf" dla zestawu Elbert v.2: CONFIG VCCAUX = "3.3" ; # Clock 12 MHz NET "clk_i" LOC = P129 | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz; NET "en_i_Low" LOC = P80 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #SW1 # mosi_data_i : in STD_LOGIC_VECTOR (7 downto 0); DIPSwitch NET "mosi_data_i[7]" LOC = P70 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[6]" LOC = P69 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[5]" LOC = P68 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[4]" LOC = P64 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[3]" LOC = P63 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[2]" LOC = P60 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[1]" LOC = P59 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "mosi_data_i[0]" LOC = P58 | PULLUP | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; # miso_data_o : out STD_LOGIC_VECTOR (7 downto 0); LEDs NET "miso_data_o[7]" LOC = P46 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[6]" LOC = P47 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[5]" LOC = P48 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[4]" LOC = P49 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[3]" LOC = P50 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[2]" LOC = P51 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[1]" LOC = P54 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; NET "miso_data_o[0]" LOC = P55 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #################################################################################################### # HEADER P1 #################################################################################################### # sclk_o : out STD_LOGIC; NET "sclk_o" LOC = P31 | IOSTANDARD = LVCMOS33 | SLEW = FAST | DRIVE = 12; #1 # mosi_o : out STD_LOGIC; NET "mosi_o" LOC = P32 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #2 # miso_i : in STD_LOGIC NET "miso_i" LOC = P28 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #3 # cs_o : out STD_LOGIC; NET "cs_o" LOC = P30 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #4 # data_ready_o : out STD_LOGIC; NET "data_ready_o" LOC = P27 | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12; #5 #Port ( # clk_i : in STD_LOGIC; # en_i_Low : in STD_LOGIC; # mosi_data_i : in STD_LOGIC_VECTOR (7 downto 0); # miso_data_o : out STD_LOGIC_VECTOR (7 downto 0); # data_ready_o : out STD_LOGIC; # cs_o : out STD_LOGIC; # sclk_o : out STD_LOGIC; # mosi_o : out STD_LOGIC; # miso_i : in STD_LOGIC #); Jak widać w pliku dane (bajt), które są wysyłane magistralą SPI do Arduino UNO, są podłączone do 8-miu DIP Switchy z zestawu Elbert, a przycisk "SW1" powoduje jednorazowa wysyłkę bajtu danych. Tutaj zrzut ekranu z "monitora portu szeregowego" z ARDUINO IDE z programem do odbioru danych po SPI.: Na koniec wklejam spakowany zip'em projekt dla Xilinx "ISE 14.7". Implementacja całego projektu zajmuje poniżej 100 LUT. Wklejam opis projektu bo takie funkcje komunikacyjne przydają się w przyszłości do bardziej skomplikowanych projektów. Teraz pozostaje uruchomienie kodu na zestawie FPGA "Sipeed Tang nano 4K". LW_SPI_master_01.zip Pozdrawiam Edytowano Marzec 30, 2022 przez FlyingDutch 2 Cytuj Link do komentarza Share on other sites More sharing options...
FlyingDutch Marzec 31, 2022 Autor tematu Udostępnij Marzec 31, 2022 Zapomniałem dodać kod programu na "Arduino UNO" (SPI Slave) do odbioru danych z FPGA - robię to teraz: // ==================================================================== // Arduino code example for SPI Slave Mode // Read unsigned short (two bytes) from SPI, send word to serial port // On 16 MHz Arduino, can work at > 500 words per second // J.Beale July 19 2011 // ==================================================================== #define SCK_PIN 13 // D13 = pin19 = PortB.5 #define MISO_PIN 12 // D12 = pin18 = PortB.4 #define MOSI_PIN 11 // D11 = pin17 = PortB.3 #define SS_PIN 10 // D10 = pin16 = PortB.2 #define UL unsigned long #define US unsigned short void SlaveInit(void) { // Set MISO output, all others input pinMode(SCK_PIN, INPUT); pinMode(MOSI_PIN, INPUT); pinMode(MISO_PIN, OUTPUT); // (only if bidirectional mode needed) pinMode(SS_PIN, INPUT); /* Setup SPI control register SPCR SPIE - Enables the SPI interrupt when 1 SPE - Enables the SPI when 1 DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0 MSTR - Sets the Arduino in master mode when 1, slave mode when 0 CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0 CPHA - Samples data on the trailing edge of the data clock when 1, leading edge when 0 SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz) */ // enable SPI subsystem and set correct SPI mode // SPCR = (1<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); } // SPI status register: SPSR // SPI data register: SPDR // ================================================================ // read in short as two bytes, with high-order byte coming in first // ================================================================ unsigned short Read2Bytes(void) { union { unsigned short svar; byte c[2]; } w; // allow access to 2-byte word, or separate bytes while(!(SPSR & (1<<SPIF))) ; // SPIF bit set when 8 bits received w.c[1] = SPDR; // store high-order byte while(!(SPSR & (1<<SPIF))) ; // SPIF bit set when 8 bits received w.c[0] = SPDR; // store low-order byte return (w.svar); // send back unsigned short value } void setup() { Serial.begin(115200); SlaveInit(); // set up SPI slave mode delay(10); Serial.println("SPI port reader v0.1"); } // ============================================================ // main loop: read in short word (2 bytes) from external SPI master // and send value out via serial port // On 16 MHz Arduino, works at > 500 words per second // ============================================================ void loop() { unsigned short word1; byte flag1; byte tab[2]; byte *B_ptr; // SS_PIN = Digital_10 = ATmega328 Pin 16 = PORTB.2 // Note: digitalRead() takes 4.1 microseconds // NOTE: SS_PIN cannot be properly read this way while SPI module is active! while (digitalRead(SS_PIN)==1) {} // wait until SlaveSelect goes low (active) SPCR = (1<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); // SPI on word1 = Read2Bytes(); // read unsigned short value SPCR = (0<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); // SPI off //--------------------------------------- byte low = (word1 & 0xFF); byte high = (word1 >> 8) & 0xFF; // float seconds = millis()/1000.0; // time stamp takes more serial time, of course // Serial.print(seconds,3); // Serial.print(","); Serial.print(high); Serial.print("\t"); Serial.print(low); Serial.println(); } // end loop() Pozdrawiam 1 Cytuj Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Dołącz do dyskusji, napisz odpowiedź!
Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!