Die Übertragung erfolgt im Format 8 Datenbits, keine Parity, 1 Stop-Bit. Die Baudrate kann generisch angegeben werden, die nötige Zählerbreite und der Zählerwert wird dann aus aus der Taktfrequenz berechnet.
Senden:
Erst muß abgewartet werden bis das Signal TX_Busy inaktiv (='0') ist. Dann können am Port TX_Data 8 Datenbits angelegt und mindestens 1 Takt lang das Signal TX_Start aktiviert werden. Eine Flankenerkennung im Sendeteil erkennt die steigende Flanke von TX_Start und beginnt sofort mit der Übertragung. Während der Übertragung der Daten geht TX_Busy auf '1'.
Empfangen:
Der (asynchrone) Eingangspin RXD wird über ein 4-Bit-Schieberegister (rxd_sr) einsynchronisiert. Die letzten beiden Bits werden zur Flankenerkennung verwendet rxd_sr(3 downto 2). Eine fallende Flanke wird als Startbit erkannt, danach wird eine halbe Bitzeit gewartet, und dann 9 Bits in das 8 Bit Empfangsdaten-Schieberegister rxsr eingetaktet. Damit wird das Startbit eingelesen, dann aber einfach durchgetaktet und fällt vorne wieder aus dem Schieberegister heraus. Nch dem Erkennen des Start-Bits und während des Empfang der Daten geht das Signal RX_Busy auf '1'. Die fallende Flanke von RX_Busy zeigt also den Empfang eines Zeichens an.
Dieses Zeichen sollte dann gleich abgeholt und weggespeichert werden, denn mit dem Erkennen des nächsten Start-Bits werden die Daten einfach überschrieben.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity RS232 is Generic ( Quarz_Taktfrequenz : integer := 50000000; -- Hertz Baudrate : integer := 9600 -- Bits/Sec ); Port ( RXD : in STD_LOGIC; RX_Data : out STD_LOGIC_VECTOR (7 downto 0); RX_Busy : out STD_LOGIC; TXD : out STD_LOGIC; TX_Data : in STD_LOGIC_VECTOR (7 downto 0); TX_Start : in STD_LOGIC; TX_Busy : out STD_LOGIC; CLK : in STD_LOGIC ); end RS232; architecture Behavioral of RS232 is signal txstart : std_logic := '0'; signal txsr : std_logic_vector (9 downto 0) := "1111111111"; -- Startbit, 8 Datenbits, Stopbit signal txbitcnt : integer range 0 to 10 := 10; signal txcnt : integer range 0 to (Quarz_Taktfrequenz/Baudrate)-1 := 0; signal rxd_sr : std_logic_vector (3 downto 0) := "1111"; -- Flankenerkennung und Eintakten signal rxsr : std_logic_vector (7 downto 0) := "00000000"; -- 8 Datenbits signal rxbitcnt : integer range 0 to 9 := 9; signal rxcnt : integer range 0 to (Quarz_Taktfrequenz/Baudrate)-1 := 0; begin -- Senden process begin wait until rising_edge(CLK); txstart <= TX_Start; if (TX_Start='1' and txstart='0') then -- steigende Flanke, los gehts txcnt <= 0; -- Zähler initialisieren txbitcnt <= 0; txsr <= '1' & TX_Data & '0'; -- Stopbit, 8 Datenbits, Startbit, rechts gehts los else if(txcnt<(Quarz_Taktfrequenz/Baudrate)-1) then txcnt <= txcnt+1; else -- nächstes Bit ausgeben if (txbitcnt<10) then txcnt <= 0; txbitcnt <= txbitcnt+1; txsr <= '1' & txsr(txsr'left downto 1); end if; end if; end if; end process; TXD <= txsr(0); -- LSB first TX_Busy <= '1' when (TX_Start='1' or txbitcnt<10) else '0'; -- Empfangen process begin wait until rising_edge(CLK); rxd_sr <= rxd_sr(rxd_sr'left-1 downto 0) & RXD; if (rxbitcnt<9) then -- Empfang läuft if(rxcnt<(Quarz_Taktfrequenz/Baudrate)-1) then rxcnt <= rxcnt+1; else rxcnt <= 0; rxbitcnt <= rxbitcnt+1; rxsr <= rxd_sr(rxd_sr'left-1) & rxsr(rxsr'left downto 1); -- rechts schieben, weil LSB first end if; else -- warten auf Startbit if (rxd_sr(3 downto 2) = "10") then -- fallende Flanke Startbit rxcnt <= ((Quarz_Taktfrequenz/Baudrate)-1)/2; -- erst mal nur halbe Bitzeit abwarten rxbitcnt <= 0; end if; end if; end process; RX_Data <= rxsr; RX_Busy <= '1' when (rxbitcnt<9) else '0'; end Behavioral;
In diesem Code ist der Übersichtlichkeit zuliebe keinerlei Fehlerverwaltung eingebaut. Man könnte also mitten im Senden eines Zeichens eine neue Übertragung starten. Genauso wird einfach ein nicht abgeholtes Zeichen überschrieben.
In der Testbench werden zwei Zeichen versendet (0xAA und 0xCC). Parallel dazu werden 2 Zeichen empfangen (0xAA und 0xC3).
Hier die Waveform:
Testbench: Gleichzeitig Senden und Empfangen