Ein CPLD eignet sich glänzend, um eine serielle-IO Schnittstelle zu implementieren. Hier werden über ein Xilinx XC9536 8 Ausgänge und 8 Eingänge an die RS232 Schnittstelle z.B. eines PCs angeschlossen. Über ein Terminalprogramm oder ein selbst geschriebenes Programm kann dann auf diese Ports zugegriffen werden.
Die (von mir getesteten) Übertragungsdaten sind: Baudrate 9600, 8 Datenbits, keine Parity, 1 Stopbit.
Die Takterzeugung erfolgte bei Testaufbau recht rustikal über einen CMOS-Timer ICM7555, der auf einen Takt von 8*9600 = 76800 Hz eingestellt wird.
Schreiben der Ausgänge: jedes über RS232 gesendete Bit wird sofort nach der Übertragung auf den entsprechenden Ausgang abgebildet. So werden 8 FFs gespart, die sonst für die Zwischenpufferung in einem Schieberegister nötig wären.
Lesen der Eingänge: beim Erkennen eines Startbits auf der RXD-Leitung wird die Übertragung der Eingänge angestoßen. Die 8 Eingänge können also nur über das Senden eines Zeichens über die RS232 Schnittstelle abgefragt werden.
Hier der VHDL-Code:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity RS232IO is
Port ( clk : in STD_LOGIC;
TXD : out STD_LOGIC := '1';
RXD : in STD_LOGIC;
din : in STD_LOGIC_VECTOR (7 downto 0);
dout : out STD_LOGIC_VECTOR (7 downto 0) := "00000000");
end RS232IO;
architecture Behavioral of RS232IO is
signal prescaler : unsigned(2 downto 0) := "000"; -- clk = 8*Baudrate
signal txsr : unsigned(8 downto 0) := "111111111";
signal rxd_sr : std_logic_vector (3 downto 0) := "1111"; -- Flankenerkennung und Eintakten
signal start : std_logic := '0';
signal rxdat : std_logic_vector (8 downto 0) := "000000000";
signal rxbitcnt : integer range 0 to 9 := 9;
begin
-- Senden
process begin
wait until rising_edge(clk);
if (start = '1') then -- fallende Flanke Startbit erkannt
txsr <= unsigned(din) & '0'; -- MSB zuerst: Stopbit, 8 Datenbits, Startbit
elsif (prescaler="000") then
txsr <= '1' & txsr(8 downto 1); -- MSB zuerst --> nach rechts schieben
end if;
end process;
TXD <= txsr(0);
-- Empfangen
process begin
wait until rising_edge(CLK);
prescaler <= prescaler+1;
rxd_sr <= rxd_sr(2 downto 0) & RXD;
if (rxd_sr = "1000" and rxbitcnt=9) then -- fallende Flanke = Startbit
start <= '1'; -- 1 FF nötig wegen evtl. Glitches auf Kombinatorik
end if;
if (rxbitcnt<9) then -- Empfang läuft
if (prescaler="100") then -- In der Bitmitte abtasten
rxdat(rxbitcnt) <= rxd_sr(3); -- Jedes Bit wird sofort nach Empfang ausgegeben --> kein Schieberegister nötig, MUX spart FFs
rxbitcnt <= rxbitcnt+1;
end if;
else -- warten auf Startbit
if (start = '1') then -- fallende Flanke Startbit erkannt
prescaler <= "001"; -- erst mal nur halbe Bitzeit abwarten
rxbitcnt <= 0;
start <= '0';
end if;
end if;
end process;
dout <= rxdat(8 downto 1);
end Behavioral;
Der RXD-Eingang wird zuerst über ein 4 stufiges Schieberegister rxd_sr eingetaktet. dieses Schieberegister wird auch zur Flankenerkennung für das Startbit verwendet. Der Zähler prescaler teilt den achtfachen Takt auf einen Bittakt herunter. Beim Erkennen der fallenden Flanke des Startbits am RXD-Eingang wird der prescaler zurückgesetzt und später immer die Mitte eines Bits abgetastet. Nach 9 übertragenen Bits ist der Empfang beendet, es wird die Erkennung des nächsten Startbits aktiviert.
Achtung: die Bitreihenfolge ist vertauscht, das MSB ist also in rxdat(1) und damit in dout(0). Wenn das stört, kann bei der Zuweisung an dout die Bitreihenfolge getauscht werden. Das könnte z.B. mit bitweiser Zuweisung dout(0) <= rxdat(8) ... dout(7) <= rxdat(1) oder einer for-Schleife oder einem kleinen Quick-And-Dirty-Trick passieren:
dout : out STD_LOGIC_VECTOR (0 to 7) := "00000000");
Auf diese Art passiert das Tauschen der Bits implizit und quasi automatisch...
Das Senden wird über die fallende Flanke eines Startbits am RXD-Eingang ausgelöst. Mit start='1' wird das Sendeschieberegister txsr geladen und anschliessend mit jedem Nulldurchgang des prescaler um eine Stelle weitergeschoben und dabei von links mit Nullen aufgefüllt. Nachdem das Stopbit hinausgeschoben wurde, steht im Schieberegister der Wert "111111111". Damit ist die Übertragung beendet und es wird auf den nächsten Start gewartet.
Im erweiterten Eintrag finden sich die Sourcecodes und die verwendete ucf-Datei.