VHDL wrapper for 1-wire core for DS18B20 temperature sensor

3.1k views Asked by At

currently I am trying to write a VHDL wrapper for this Opencore Verilog module (1-wire master) so that I can send/receive from this temperature sensor (DS18B20).

However I am struggling to understand the usage. Namely the read/write enable vs. the cyc bit in the control/status register of the 1-wire master module.

The code I have so far sets the cyc bit to 1 and the read/write enable to one simultaneously but does not cycle them during each bit. Is this correct or am I misunderstanding it? I'm new to VHDL/ reading a datasheet so I have been struggling over this for a few days. Any help would be appreciated.

I found this site that I have been using as a reference but it does not deal with the Verilog module that I am using.

I am also looking for tips on my code style, and VHDL tips in general.

My current code:

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL; --may need to remove if signed not used

ENTITY one_wire_temp_probe_control IS
    GENERIC (
        one_us_divider_g : integer range 0 to 50      := 50      -- clock divider for one micro second
    );    
    PORT (
        i_clk_50mhz      : IN STD_LOGIC;
        i_read_enable    : IN std_logic;
        io_temp_probe    : INOUT STD_LOGIC; --how do i register an inout
        o_temperature    : OUT signed(6 DOWNTO 0); 
        o_temp_ready     : OUT std_logic
     );
END one_wire_temp_probe_control;

ARCHITECTURE rtl of one_wire_temp_probe_control IS
    ----temp commands----
    CONSTANT skip_rom_c          : std_logic_vector(7 DOWNTO 0) := x"CC"; --command to skip ROM identity of temperature sensor
    CONSTANT convert_temp_c      : std_logic_vector(7 DOWNTO 0) := x"44"; --command to start temperature conversion
    CONSTANT read_scratchpad_c   : std_logic_vector(7 DOWNTO 0) := x"BE"; --command to read the scratchpad i.e. get temperature data
    CONSTANT command_bits_c      : integer RANGE 0 TO 8         := 8;     --number of bits in the above commands (note: range used to limit number of bits to minimum needed)
    CONSTANT data_bits_c         : integer RANGE 0 to 12        := 12;    --number of bits in received data
    ----1-wire commands----
    CONSTANT send_reset_pulse          : std_logic_vector(7 DOWNTO 0) := "00001010"; --command to send reset pulse
    CONSTANT write_command_structure_c : std_logic_vector(6 DOWNTO 0) := "0000000";  --structure of the command that must be passed to the 1-wire controller (----EDIT----)
    ----timing constants----
    CONSTANT delay_65us_c        : integer := one_us_divider_g * 65;      --65 micro-second delay
    CONSTANT delay_960us_c       : integer := one_us_divider_g * 960;     --960 micro-second delay 
    CONSTANT delay_750ms         : integer := one_us_divider_g * 1000 * 750; --760 milli-second delay

    ----state machine----
    TYPE state_type              IS (idle, presence_pulse, wait_presence_pulse, skip_rom, temp_conversion, wait_for_conversion,
                                     read_scratchpad, data_read, convert_data, wait_65us);
    SIGNAL state                 : state_type := idle;
    SIGNAL previous_state        : state_type := idle;
    ----1-wire----
    SIGNAL read_enable_s, write_enable_s, reset_s, owr_e_s : std_logic := '0';
    SIGNAL write_data_s, read_data_s                       : std_logic_vector(7 DOWNTO 0):= (OTHERS => '0'); --8 bit mode chosen in sockit_owm 
    SIGNAL address_s                                       : std_logic_vector(1 DOWNTO 0) := "00"; 
    SIGNAL timer_s                                         : integer := 0;
    ----commands---
    SIGNAL bit_counter_command_s : integer RANGE 0 TO command_bits_c := 0;  --counter for bits in commands (note: not -1 due to using 9th bit as state change)
    SIGNAL bit_counter_data_s    : integer RANGE 0 TO data_bits_c    := 0;  --counter for bits in data recieved
    ----temperature----
    SIGNAL temperature_raw_data  : std_logic_vector(11 DOWNTO 0) := (OTHERS => '0');

    ----one wire control----
    COMPONENT sockit_owm IS
    PORT (
        ----control interface----
        clk     : IN std_logic;
        rst     : IN std_logic;
        bus_ren : IN std_logic;
        bus_wen : IN std_logic;
        bus_adr : IN std_logic_vector(7 DOWNTO 0);
        bus_wdt : IN std_logic_vector(7 DOWNTO 0);
        bus_rdt : OUT std_logic_vector(7 DOWNTO 0);
        bus_irq : OUT std_logic;
        ----1-wire interface----
        owr_p   : OUT std_logic; --verilog code is a one bit wide vector
        owr_e   : OUT std_logic;
        owr_i   : IN std_logic
    );
    END COMPONENT;

BEGIN

    address_s <= "00"; --for the temp probe control we're not interested in other address spaces

    PROCESS(i_clk_50mhz) BEGIN --state change
        IF rising_edge(i_clk_50mhz) THEN
            CASE state is
                WHEN idle =>
                    o_temp_ready <= '0';
                    IF (i_read_enable = '1') THEN
                        state <= presence_pulse;
                    ELSE
                        state <= idle;
                    END IF; 

                WHEN presence_pulse =>
                ----send reset/presence pulse----
                    write_enable_s  <= '1';
                    write_data_s    <= send_reset_pulse;
                    timer_s         <= delay_960us_c;
                    state           <= wait_presence_pulse;

                WHEN wait_presence_pulse =>
                ----wait for 960 micro seconds----
                    read_enable_s <= '1';
                    IF (timer_s = 0) THEN
                        IF (read_data_s(0) = '0') THEN
                            state <= skip_rom;
                        ELSIF (read_data_s(0) = '1') THEN
                            --precence not detected
                        ELSE
                            state <= wait_presence_pulse;
                        END IF;
                    ELSE
                        timer_s <= timer_s - 1;
                        state   <= wait_presence_pulse;
                    END IF;

                WHEN skip_rom =>
                ----send skip rom command----
                    previous_state <= skip_rom;
                    write_enable_s <= '1';
                        IF (bit_counter_command_s = command_bits_c) THEN
                            bit_counter_command_s <= 0;
                            state                 <= temp_conversion;
                        ELSE
                            write_data_s <= write_command_structure_c & skip_rom_c(bit_counter_command_s); ---command structure concatonated with 1 bit from command
                            bit_counter_command_s <= bit_counter_command_s + 1;
                            timer_s               <= delay_65us_c;
                            state                 <= wait_65us;
                        END IF;   

                WHEN temp_conversion =>
                ----send temp conversion command to probe----
                    previous_state <= temp_conversion;
                    IF (bit_counter_command_s = bit_counter_command_s) THEN
                        bit_counter_command_s   <= 0;
                        timer_s                 <= delay_750ms;
                        state                   <= wait_for_conversion;
                    ELSE
                        write_data_s <= write_command_structure_c & convert_temp_c(bit_counter_command_s); ---command structure concatonated with 1 bit from command
                        bit_counter_command_s   <= bit_counter_command_s + 1;
                        timer_s                 <= delay_65us_c;
                        state                   <= wait_65us;
                    END IF;

                WHEN wait_for_conversion =>
                ----wait for temperature conversion to finish----
                    IF (timer_s = 0) then
                        state <= read_scratchpad;
                    ELSE
                        timer_s <= timer_s - 1;
                    END IF;

                WHEN read_scratchpad =>
                ----send read scratchpad command----
                    previous_state <= read_scratchpad;
                    IF (bit_counter_command_s = command_bits_c) THEN
                        state                 <= data_read;
                        bit_counter_command_s <= 0;
                    ELSE
                        write_data_s <= write_command_structure_c & read_scratchpad_c(bit_counter_command_s); ---command structure concatonated with 1 bit from command
                        bit_counter_command_s <= bit_counter_command_s + 1;
                        timer_s               <= delay_65us_c;
                        state                 <= wait_65us;
                    END IF;

                WHEN data_read =>
                ----read incoming data----
                    previous_state <= data_read;
                    read_enable_s <= '1';
                    IF (bit_counter_data_s = data_bits_c) THEN
                        bit_counter_data_s <= 0; --may need to invert this
                        state              <= convert_data;
                    ELSE
                        temperature_raw_data(bit_counter_data_s) <= read_data_s(0);
                        bit_counter_data_s                       <= bit_counter_data_s + 1;
                        timer_s                                  <= delay_65us_c;
                        state                                    <= wait_65us;
                    END IF;

                WHEN convert_data =>
                ----convert raw data into temperature----
                    o_temp_ready <= '1';

                WHEN wait_65us =>
                ----wait for read/write cycle to finish----
                    IF (timer_s = 0) THEN
                        state <= previous_state;
                    ELSE 
                        timer_s <= timer_s - 1;   
                        state   <= wait_65us;
                    END IF;
            END CASE;
        END IF;
    END PROCESS;

    ----one wire component instantiation----
    one_wire_control : sockit_owm
    PORT MAP(
        ----control interface----
        clk     => i_clk_50mhz,
        rst     => reset_s,
        bus_ren => read_enable_s,
        bus_wen => write_enable_s,
        bus_adr => address_s,
        bus_wdt => write_data_s,
        bus_rdt => read_data_s,
        bus_irq => OPEN,
        ----1-wire interface----
        owr_p   => OPEN,
        owr_e   => owr_e_s,
        owr_i   => io_temp_probe
    );
    io_temp_probe <= owr_e_s ? '0' : 'Z'; --I also need help converting this line to VHDL

END rtl;

Thank you in advance. Best Tom

2

There are 2 answers

2
JHBonarius On BEST ANSWER

I am also looking for tips on my code style, and VHDL tips in general.

OK.

First thing: don't make the lines so long. So don't put comments at the end of a line. Put them a line before.


use IEEE.NUMERIC_STD.ALL; --may need to remove if signed not used

then remove, as I don't see any signed


one_us_divider_g : integer range 0 to 50      := 50      -- clock divider for one micro second

So... what happens is one_us_divider_g is set to 0? Seems an illegal value. Using it for simulation?


io_temp_probe    : INOUT STD_LOGIC; --how do i register an inout

One option is to use a tristate IOBUFFER. This is a special FPGA edge element which splits the input and output to separate signals. You can tristate the ouput by setting a control port.

Alternatively you could just do it the way you do in your code (this is also explained in for instance the Xilinx synthesis user guide). Which leads me to another question in your code.

io_temp_probe <= owr_e_s ? '0' : 'Z'; --I also need help converting this line to VHDL

io_temp_probe <= '0' when owr_e_s = '1' else 'Z';

CONSTANT command_bits_c      : integer RANGE 0 TO 8         := 8;

No need for an integer range if it is a constant.


CONSTANT send_reset_pulse : ...
CONSTANT delay_750ms : ...

Missing the "_c" you put behind all your constants. But I would not add this "s", "_c" or "_g" anyhow. A lot of work for little gain.


COMPONENT sockit_owm IS
PORT (
    [...]
);
END COMPONENT;

Component declarations are not required anymore since some time now. You can remove it and change your instantiation:

one_wire_control : entity work.sockit_owm
PORT MAP(
    [...]

WHEN idle =>
    [...]
    ELSE
        state <= idle;
    END IF; 

not required. If you don't change state, it stays at idle.


WHEN wait_presence_pulse =>
    IF (timer_s = 0) THEN
        IF (read_data_s(0) = '0') THEN
            [...]
        ELSIF (read_data_s(0) = '1') THEN
            [...]
        ELSE
            state <= wait_presence_pulse;
        END IF;

read_data_s(0) '0' and '1' are covered. Do you expect any other value? That can only happen in simulation, not in implementation. So the code in the last else-statement is unreachable then.


[...]
    timer_s               <= delay_65us_c;
    state                 <= wait_65us;
[...]
WHEN wait_65us =>
    IF (timer_s = 0) THEN
        [...]
    ELSE 
        timer_s <= timer_s - 1;   
    END IF;

Let's say a delay is 65 us lasts 10 clock cycles. Setting the divider to 1, delay_65us_c=10. So at t=0, timer_s is set to 10. at t=1 -state is wait_65us now- timer_s is set to 9. And so on: at t=10, timer_s is set to 0... but state is still wait_65us. So at t=11, timer_s is detected 0, and state is changed to the previous one. Which it will enter at t=12. So, instead of a 10 clock cycle delay, you get a 12 clock cycle delay.

Is this a problem? If yes, you should reconsider your code.


SIGNAL read_enable_s, write_enable_s, reset_s, owr_e_s : std_logic := '0';
[... not used anywhere else... ]
one_wire_control : sockit_owm
PORT MAP(
    [...]
    rst     => reset_s,

Are you sure this is correct? A lot of components need to be properly reset before they operate correctly.

1
AudioBubble On

If you're working with Quartus, you can mix VHDL code with Verilog and even schematic elements. In the link below, I use a verilog driver for the same chip (DS18B20).

See here for details: https://physnoct.wordpress.com/2016/12/14/altera-quartus-combining-verilog-and-vhdl/