-1

I'm trying to design a rs232 receiver in VHDL : I send numbers with a python script that it have to catch and show to some leds. I hope to have understood how RS232 works and went to work to make that design. The design doesn't behave the way I hope it would and I wonder if someone could help me identify my mistakes.

Of course I went to look how serial receiver works. But as a begineer, I often feel overwhelmed with the solution proposed (exemple : VHDL RS-232 Receiver" or even worse : https://www.nandland.com/vhdl/modules/module-uart-serial-port-rs232.html), i lack the vocabulary and experience to understand what is going on in there.

I made this design on quartus II web edition: enter image description here

the entity at the bottom just latches to any data coming through the rs232 input to insure the python script is actually doing something.

with these two files : rs232_test

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity rs232_receiver is 
port (
    clock : in std_logic;
    r     : in std_logic;
    reset : in std_logic;
    data  : out std_logic_vector (7 downto 0)
);
end rs232_receiver;

architecture rs232_receiver_arch of rs232_receiver is
    signal data_buffer : std_logic_vector (12 downto 0); 
    signal state : integer; -- 1000 => idle;
begin

    process
    begin
    wait until rising_edge (clock);
        if (reset = '0') then
            if (state = 1000) then
                if (r = '0') then
                    state <= 0; -- startbit as been detected => time to READ !
                end if;
            elsif (state < 13 and state > -1) then
                data_buffer(state) <= r;
                state <= state + 1;
            else
                state <= 1000; -- go back to idle
                data <= data_buffer(7 downto 0);
                data_buffer <= (others => '0');
            end if;
        else
            data_buffer <= (others => '0');
            data <= (others => '0');
            state <= 1000;
        end if;
    end process;
end rs232_receiver_arch;

clock ajustement (raw clock is 24 MHz, I hope that the way i made this divider, output is 1200 Hz which is i believe a baud rate of 1200).

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity div1000 is 
port (
    clock : in std_logic;
    clockOut : out std_logic
);
end div1000;

architecture div1000_arch of div1000 is
    signal count : integer;
begin

    clockOut <= '1' when count < 10000 else '0';

    process begin
    wait until rising_edge (clock);
        if (count > 19999 or count < 0) then
            count <= 1;
        else
            count <= count + 1;
        end if;
    end process;
end div1000_arch;

I load number with this python script :

# -*- coding: utf-8 -*-
"""
Created on Thu May  2 14:52:12 2019

@author: nathan hj ragot
"""

import time
import serial
import serial.tools.list_ports

print ("hello world !")

#  initialisation des variables
port = ""                                                                      # port choisit
ports = []                                                                     # ports listes

#  recherche et choix du port
print ("merci de rentrer le nom d'un port :")
print ("ps : automatiquement, le script a detecte ces ports :")
print ("**************")

ports = serial.tools.list_ports.comports()
for i in range (len(ports)):
    print ("-> [" + str(i) + "] : " + str (ports[i]))

print ("**************")

port = input("? votre choix :")
print ("ok cool")

# open serial port
ser = serial.Serial(
    port=port,
    baudrate=1200,
    parity=serial.PARITY_NONE,
    stopbits=serial.STOPBITS_TWO,
    bytesize=serial.EIGHTBITS
)
print(ser.isOpen())

#start writing
ser.write(bytearray([255]))
for i in range(15):
    input("press enter to print " + str(i))
    ser.write (bytearray([i]))

#stop writing
ser.close()

print(ser.isOpen())
print ("bye")

I was expecting that every time I press enter on my keyboard, the python script would send a byte to my fpga board and the design would latch on the number and show it to the leds. Instead, the leds flash to some random places which do not correspond to the number sent then immediatly turn off.

NRagot
  • 113
  • 2
  • 12

1 Answers1

0

I've asked one my teacher what could be wrong in my design. He told me that: even when two clocks have the same rate, they might not be synchronized as the receiver might try to read the signal when the transmitter is too close from its rising or falling edge. Therefore, he recommended me to add a separate counter that start when the start bit is launch. This would act as a clock divider that starts when the falling edge and r = '0' is recognized.

I've taken upon myself to rewrite the vhdl code of the receiver to obtain this:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity serial_receiver is 
port (
    -- clock is 24 MHz
    clock : in std_logic;
    -- data in at 1200 baud rate
    -- two stop bit
    -- no parity check
    -- byte size at 8
    r     : in std_logic;
    -- packet received
    dataOut : out std_logic_vector (7 downto 0)
);
end serial_receiver;

architecture serial_receiver_arch of serial_receiver is
    -- state of the process :
    --  idle => waiting for start bit
    --      start => waiting THROUGH the start bit
    --      reading => index and storing bits in data buffer
    --      stop => update up data and go back to idle
    type states is (idle, start, reading, stop);
    signal state : states := idle;
    
    -- clock divider for bit reading, one bit is 16 cycle after first division
    signal counter: integer := 0;
    -- clock divider for process, 1250 divider => clock is now 16*1200 Hz
    signal waiter : integer := 0;
    -- index for incoming data to be stored in buffer
    signal index  : integer := 0;
    -- data buffer
    signal data   : std_logic_vector (7 downto 0);
begin
    
    process begin
        wait until rising_edge(clock);
        
        --first divider
        if waiter < 1250 then
            waiter <= waiter + 1;
        else
            -- time to run process
            -- reset waiter to restart clock divider
            waiter <= 0;
            
            -- switch case for state
            case state is
                -- receiver is idle
                when idle =>
                    -- will become ready for start when bit start is received
                    if r = '0' then
                        state <= start;
                        counter <= 0;
                    end if;
                -- receiver is waiting through start bit, doing nothing
                when start =>
                    -- not done yet, counter max must be bigger than 16 to insure not too close from rising_edge
                    if counter < 18 then
                        counter <= counter + 1;
                    else
                    -- done, is getting ready for reading
                        state <= reading;
                        -- counter is set to 16 to immediatly read first bit
                        counter <= 16;
                        index <= 0;
                    end if;
                -- receiver store data inside buffer
                when reading =>
                    -- clock divider to wait until next bit.
                    if counter < 16 then
                        counter <= counter + 1;
                    else
                        -- store bit in buffer if we can
                        if index < 8 then
                            counter <= 0;
                            data(index) <= r;
                            index <= index + 1;
                        else
                        -- if we can't, time to stop
                            counter <= 0;
                            state <= stop;
                        end if;
                    end if;
                -- reading is finnished
                when stop =>
                    -- go back to idle.
                    dataOut <= data;
                    state <= idle;
                    counter <= 0;
            end case;
        end if;
    end process;
end serial_receiver_arch;

the VHDL is alone in the design, all other entities are no longer present.

This seems to work for now (tested some numbers between 0 and 255 included) using the python script. I've tried to make something that looks a bit more like the answer of @QuantumRipple : VHDL RS-232 Receiver. As my code is slightly different, I'm still unsure of what exactly was not working in the old one. I'll just assume it is the syncronization thing. If someone, still have an idea, I'm open to more knowledge.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
NRagot
  • 113
  • 2
  • 12