Projects‎ > ‎FPGA Projects‎ > ‎

SMS to LED/LCD Ticker

Context: Study at Beuth Hochschule, Embedded Systems with Prof. Dr.-Ing. Detlef Heinemann
 

GSM module


Hardware

Figure: GSM module
 

AT-commands


The GSM module can be operated with AT-commands (see Wikipedia [1] ). A complete tutorial can be found here
The module can be configured, controlled and read out over the COM-port with the help of the AT-commands. A terminal program (e.g. Microsoft HyperTerminal, MTTTY) is used to send the commands. Due to the asynchronous communication via COM-port, both devices, PC and GSM module, have to be equally configured:

Baud rate: 9600, 8-bit data word, no parity, one stop bit (Short: 9600,8,n,1)

See below some command examples:

AT
The response "Ok" symbolizes a working connection between PC and module.

AT+CPIN?
Request, if a PIN is needed to access the device. Answer: +CPIN: <code> OK. <code> descript the state and can have the following meanings:
READY: PIN was already entered or is not needed
SIM PIN/PUK: PIN expected

AT+CPIN=”1234?
Inserts PIN 1234. If the response is "Ok" was the PIN correct and the device can be configured.

AT+CMGF=1
This important command sets the input and output of the GSM module to human readable text mode. Further operations are now easier. Response should be "Ok".

AT+CMGL="ALL"
Lists all SMS in the memory.

AT+CMGL="REC UNREAD"
Lists all unread SMS in the memory. Now all unread SMS are set to "REC READ".

AT+CMGD=<index>
Deletes the SMS with index <index> from the memory.

ATD<number>
Calls the number <number> and tries to establish a connection with the target (not used in this project).
 

Project SMS

 
Concept
 
The idea is to display an SMS on a LCD or LED ticker.
 

Realization

 
The SMS is send via mobile net to the GSM module, is read out with a FPGA board and is displayed finally on a LCD/LED ticker.
 

Video

 
 

Hardware


The FPGA board is connected via the RS232 interface to the GSM module. Both boards have an integrated voltage level converter, which is adapting to the corresponding level. That's why both modules can be easily connected to the PC or to each other.
The Nexys2 board communicates via AT-commands with the GSM module and is polling every second the current SMS in the memory. The GSM should send the SMS as human readable text to the board, which saves the SMS to a RAM.

Figure: GSM module, Nexys2 FPGA board, LCD, MAX232 bread board

The LCD is operated with 8-bit data words and is displaying the SMS from the RAM as a ticker. This LCD module needs a negative voltage for the contrast. I use a MAX232 IC to generate the negative voltage level. This IC is normally used as a voltage level converter, but fortunately it outputs the supply voltage VCC as a negative voltage -V.

Figure: Nexys2 FPGA board, LCD

The MAX232 is placed in the middle of the bread board. The negative supply voltage -V can be found at pin 6. This is a easy way to generate negative voltages without having two power supplies. In this case I use 5V from the Nexys2 board as power supply for the MAX232. The negative voltage of -5V is regulated with a poti (LCD contrast needs 0V down to -5V) and supplied to the contrast pin of the LCD. MAX232 is not able to deliver high current, but it's enough as reference voltage.
You can find the application note of the MAX232 datasheet below (ELKOs can be replaced with ceramic capacitors of 100nF).

Figure: Schematic MAX232

Bild: MAX 232

Schematic

 
Figure: Schematic
 

I/Os 

# CLOCK
NET "CLK" CLOCK_DEDICATED_ROUTE = FALSE; 
NET "CLK" LOC="B8";

# RS232
NET "RXD" LOC = "U6";
NET "TXD" LOC = "P9";
#NET "test_txd" LOC = "M18";

# Output
NET LED<0> LOC = J14;
NET LED<1> LOC = J15;
NET LED<2> LOC = K15;
NET LED<3> LOC = K14;
NET LED<4> LOC = E16;
NET LED<5> LOC = P16;
NET LED<6> LOC = E4;
NET LED<7> LOC = P4;

#RESET
NET "RESET" LOC = "G18";

#FLAG
NET "FLAG_R" LOC = "C17";
NET "FLAG_T" LOC = "J13";
NET "bit_clk" LOC = "G15";

#NET LED_OUT LOC = J14;

##FLAG
#NET "LED_OUT" LOC = "C17";

#LCD
NET DATA<0> LOC = L15;
NET DATA<1> LOC = K12;
NET DATA<2> LOC = L17;
NET DATA<3> LOC = M15;
NET DATA<4> LOC = K13;
NET DATA<5> LOC = L16;
NET DATA<6> LOC = M14;
NET DATA<7> LOC = M16;

NET ENA LOC = M13;
NET RS LOC = R18;
NET CLK_1000Hz LOC = R15;
Modules
 
The following modules are realized in VHDL:

TX
 
The transmitter:
SMS to LED/LCD Ticker
Figure: Tx module
 
The transmitter communicates via RS232 protocol. In detail, it is the driver to send 8-bit data words over the RS232 interface of the FPGA board. The Nexys2 board already has a voltage level converter. The corresponding pins of the module are directly connected to this IC, therefore it's not necessary to consider neither voltage levels nor inverting of the data bits (Serial interface: voltage levels from -12V up to 12V; '1' equals -12V and '0' equals 12V).
The TX module is sending with 9600 baud and the data format 8,n,1 (8 data bits, no parity, 1 stop bit). The baud rate is generated by the module using the 50MHz board clock. The data word is read parallel and sent serially. Starting the transmission process sets the transmission flag (Flag_T). The flag is reset after the correct transmission of the stop bit.
The baud rate of 9600Hz can be used externally via pin "bit_clk".
An asynchronous reset is resetting all states to their default values.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity transmitter is
    Port ( CLK : in  STD_LOGIC;
           TXD : out STD_LOGIC;
              bit_clk : out std_logic;
           RESET : in  STD_LOGIC;
           DATA_IN : in  STD_LOGIC_VECTOR (7 downto 0);
              FLAG_T : inout  STD_LOGIC);
end transmitter;

architecture Behavioral of transmitter is
constant baudDivide : std_logic_vector(9 downto 0) := "1010001011"; 
--baude rate divider for 9600 by 4*baude rate = sample rate
-- by dividing 50Mhz by 2 and 38400
signal clkDiv : std_logic_vector(9 downto 0) := "0000000000";
signal tClk : std_logic := '0';    -- sample clock (38400)
signal sendBuffer : std_logic_vector(7 downto 0);
begin
    process (CLK)                         --Define rClk
    begin
        if CLK = '1' and CLK'Event then
            if clkDiv = baudDivide then
                tClk <= not tClk;
                clkDiv <= "0000000000";
            else
                clkDiv <= clkDiv +1;
            end if;
        end if;
    end process;

    process(tClk,RESET)
        variable count : integer;
    begin

        if(RESET='1') then          -- asynchrous reset
            FLAG_T   <=  '0';
            count := 0;
            sendBuffer <=  "00000000";
        elsif(tClk'event and tClk='1' ) then
            if(FLAG_T='0')then
                if not(DATA_IN = "00000000") then
                    FLAG_T  <= '1' ;
                end if;
                count := 0;
            elsif(FLAG_T='1') then
                sendBuffer <= DATA_IN;
                case count is
                    when 0 => TXD <= '0' ; bit_clk <= '1'; -- Start-Bit
                    when 4 => TXD <= sendBuffer(0);bit_clk <= '1'; --  6. Takt LSB
                    when 8 => TXD <= sendBuffer(1);bit_clk <= '1'; -- 10. Takt
                    when 12 => TXD <= sendBuffer(2);bit_clk <= '1'; -- 14. Takt
                    when 16 => TXD <= sendBuffer(3);bit_clk <= '1'; -- 18. Takt
                    when 20 => TXD <= sendBuffer(4);bit_clk <= '1'; -- 22. Takt
                    when 24 => TXD <= sendBuffer(5);bit_clk <= '1'; -- 26. Takt
                    when 28 => TXD <= sendBuffer(6);bit_clk <= '1'; -- 30. Takt
                    when 32 => TXD <= sendBuffer(7);bit_clk <= '1'; -- 34. Takt MSB
                    when 36 => TXD <= '1' ;bit_clk <= '1'; --stop-Bit
                    when 60 => FLAG_T<='0' ;
                    when others   => bit_clk <= '0';       -- 38. Takt Stop-Bit
                end case ;
                count := count + 1;
            end if ;
        end if ;
    end process ;
end Behavioral;
RX 

The receiver:
Figure: Rx module

The receiver module operates with the same specifications as the transmitter. The RX signal of the RS232 interface is observed by the module. If the signal is put to ground (start bit) and the RX module is ready (Receiver flag FLAG_R is reset), the incoming data bits are sensed and saved in a buffer (sensing rate equals four times the baud rate). It's important that the data bit is sensed in the middle to verify a stable state on the RS232 line. FLAG_R is set during the receiving process and reset after the stop bit was received correctly. The received data is output on a 8bit data bus and is buffered until the next receiving process. An asynchronous reset is resetting all states and the data buffer.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity receiver is
    Port ( CLK : in  STD_LOGIC;
           RXD : in  STD_LOGIC;
           FLAG_R : inout  STD_LOGIC;
           DATA_OUT : out  STD_LOGIC_VECTOR (7 downto 0);
           RESET : in  STD_LOGIC);
end receiver;

architecture Behavioral of receiver is
constant baudDivide : std_logic_vector(9 downto 0) := "1010001011"; 
--baude rate divider for 9600 by 4*baude rate = sample rate
-- by dividing 50Mhz by 2 and 38400
signal clkDiv : std_logic_vector(9 downto 0) := "0000000000";
signal rClk : std_logic := '0';    -- sample clock (38400)
signal readBuffer : std_logic_vector(7 downto 0);
begin
    process (CLK)                         --Define rClk
    begin
        if CLK = '1' and CLK'Event then
            if clkDiv = baudDivide then
                rClk <= not rClk;
                clkDiv <= "0000000000";
            else
                clkDiv <= clkDiv +1;
            end if;
        end if;
    end process;

    process(rClk,RESET)
        variable count : integer;
    begin
        if(RESET='1') then          -- asynchrous reset
            FLAG_R   <=  '0' ;
            count := 0;
            DATA_OUT <=  "00000000" ;
        elsif(rClk'event and rClk='1' ) then
            if(RXD='0' and FLAG_R='0')then   -- Start-Bit
                FLAG_R  <= '1' ;
                count := 0;
            elsif(FLAG_R='1') then
                case count is
                    when 5 => readBuffer(0) <= RXD ; --  6. Takt LSB
                    when 9 => readBuffer(1) <= RXD ; -- 10. Takt
                    when 13 => readBuffer(2) <= RXD ; -- 14. Takt
                    when 17 => readBuffer(3) <= RXD ; -- 18. Takt
                    when 21 => readBuffer(4) <= RXD ; -- 22. Takt
                    when 25 => readBuffer(5) <= RXD ; -- 26. Takt
                    when 29 => readBuffer(6) <= RXD ; -- 30. Takt
                    when 33 => readBuffer(7) <= RXD ; -- 34. Takt MSB
                    when 37 => FLAG_R <= '0' ;
                    when others => null;       
                end case ;
                count := count + 1;
                DATA_OUT <= readBuffer;
            end if ;
        end if ;
    end process ;
end Behavioral;
RAM 
Figure: RAM module (Dual-Port-RAM)

The RAM is internal data storage for the SMS. Basically it's a 8bit wide RAM with 256 possible addresses. You can compare it to a char array of 256 chars. This RAM features parallel writing and reading of different storage addresses (separate address and data busses for read and write access: So called Dual-Port-RAM). Read and write processes are enabled with separated control signals. Access is synchronized with the board clock. An asynchronous reset clears all data cells with the value 0x01h.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity RAM is
    Port ( DATA_OUT : out  STD_LOGIC_VECTOR (7 downto 0);
           DATA_IN : in  STD_LOGIC_VECTOR (7 downto 0);
           ADDRESS : in  STD_LOGIC_VECTOR (7 downto 0);
              ADDRESS_OUT : in  STD_LOGIC_VECTOR (7 downto 0);
           RD : in  STD_LOGIC;
              CLK : in  STD_LOGIC;
              RESET : in STD_LOGIC;
           WRT : in  STD_LOGIC);
end RAM;

architecture Behavioral of RAM is
    type arr is array(0 to 255) of std_logic_vector(7 downto 0);
    
    function vec_to_int (vec : in STD_LOGIC_VECTOR (7 downto 0)) return integer is
        variable result : integer := 0;
    begin
        result := 0;
        if (vec(7) = '1') then
            result := result + 128;
        end if;
        if (vec(6) = '1') then
            result := result + 64;
        end if;
        if (vec(5) = '1') then
            result := result + 32;
        end if;
        if (vec(4) = '1') then
            result := result + 16;
        end if;
        if (vec(3) = '1') then
            result := result + 8;
        end if;
        if (vec(2) = '1') then
            result := result + 4;
        end if;
        if (vec(1) = '1') then
            result := result + 2;
        end if;
        if (vec(0) = '1') then
            result := result + 1;
        end if;
        return result;
    end;
begin
    process(CLK, RESET, ADDRESS)
        variable i : integer;
        variable data : arr;
    begin
        if (RESET = '1') then
            for i in 0 to 255 loop
                data(i) := "00000001";
            end loop;
            DATA_OUT <= "00000001";
        elsif (CLK'event and CLK = '1') then
        --else
            if (RD = '1') then    --Read data
                DATA_OUT <= data(vec_to_int(ADDRESS_OUT));
            end if;
            if (WRT = '1') then    --Write data
                data(vec_to_int(ADDRESS)) := DATA_IN;
            end if;
        end if;
    end process;
end Behavioral;
LCD controller

Figure: LCD controller module

The LCD controller is used to control the LCD and to display the SMS on the LCD like a ticker. First step is to initialize the display (8bit data, 1 line, cursor off, cursor moves, cursor increments). Now the data is read from the RAM and is displayed on the LCD.
The controller is responsible for the right offset of the text to invoke the effect of scrolling letters. The LCD is filled multiple times a second and the starting address is incremented after each of these cycles. That's why the text seems to scroll through the display.
This module is working independently of the RAM content. The module tries immediately after reboot to read data from the RAM although there is no data stored. Maybe I'll add a control signal from the system controller to fix this problem.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity LCD_Controller is
    Port ( CLK : in  STD_LOGIC;
              CLK_1000Hz : inout STD_LOGIC;
           RESET : in  STD_LOGIC;
           DATA_OUT : out  STD_LOGIC_VECTOR (7 downto 0);
              RAM_ADDRESS : out  STD_LOGIC_VECTOR (7 downto 0);
           ENABLE : out  STD_LOGIC;
              RD_RAM : out  STD_LOGIC;
           RS : out  STD_LOGIC;
           DATA_IN : in  STD_LOGIC_VECTOR (7 downto 0));
end LCD_Controller;

architecture Behavioral of LCD_Controller is
    type arr is array(0 to 36) of std_logic_vector(7 downto 0);
    signal CLK_1ms : std_logic;
    
begin
    process(CLK)
        variable cnt : integer range 0 to 50000 := 0;
    begin
        if (CLK'Event) and (CLK = '1') then
            if (cnt >= 25000) then
                CLK_1ms <= not(CLK_1ms);
                CLK_1000Hz <= not(CLK_1000Hz);
                cnt := 0;
            end if;
            cnt := cnt + 1;
        end if;
    end process;
    
    process(CLK_1ms, RESET)
        variable token : arr;
        variable isInit : boolean := false;
        variable initStep : integer := 0;
        variable writeStep : integer := 0;
        variable cmd : std_logic_vector (7 downto 0);
        variable ram_add : std_logic_vector (7 downto 0);
        variable cntTime : integer := 0;
        variable cntLimit : integer := 0;
        variable letterOffset : integer := 0;
    begin
        if (RESET = '1') then
            isInit := false;            --controll var for initilize display
            initStep := 0;                --controll var for different commands
            cntTime := 0;                --timer for waiting times
            cntLimit := 0;                --timer limit
            writeStep := 0;            --controll var for writing on display (which char)
            letterOffset := 0;        --offset for shifting effect on display
            ram_add := "00000000";    --RAM address lines resetz
            RD_RAM <= '1';                --Read RAM allways 1
        elsif (CLK_1ms'Event) and (CLK_1ms = '1') then
            RAM_ADDRESS <= ram_add;    --setting RAM address
            if (isInit = false) then     --initilize LCD
                if (cntTime = 0) then
                    case initStep is        -- define commands and waiting times
                        when 0 => cmd := "00111000"; cntLimit := 100;    --0x38
                        when 1 => cmd := "00111000"; cntLimit := 50;        --0x38
                        when 2 => cmd := "00111000"; cntLimit := 10;        --0x38
                        when 3 => cmd := "00111000"; cntLimit := 10;        --0x38
                        when 4 => cmd := "00111000"; cntLimit := 10;        --0x38
                        when 5 => cmd := "00000110"; cntLimit := 10;        --0x06
                        when 6 => cmd := "00001100"; cntLimit := 10;        --0x0C
                        when 7 => cmd := "00010100"; cntLimit := 10;        --0x14
                        when 8 => cmd := "00000001"; cntLimit := 50;        --0x01
                        when 9 => cmd := "10000000"; cntLimit := 100;    --0x10
                        when others => initStep := 0; isInit := true;
                    end case;
                    DATA_OUT <= cmd;    --command ist outputted
                    RS <= '0';            --writing command
                    ENABLE <= '1';     --high stroke
                end if;
                cntTime := cntTime + 1;
                if (cntTime = 5) then
                    ENABLE <= '0';     --end of stroke (4ms length)
                end if;
                if (cntTime >= cntLimit) then    --waiting times
                    cntTime := 0;    --reset waiting time counter
                    initStep := initStep + 1;    --change between different commands
                end if;
            else    --LCD is init
                if (cntTime = 0) then
                    if (writeStep = 0) then    --Reset cursor position
                        cntLimit := 100;        --prev 10, now for waiting for shift
                        cmd := "10000000";    --Reset cursor position to 0x00
                        DATA_OUT <= cmd;        --command ist outputted
                        RS <= '0';                --writing command
                    else
                        if (DATA_IN = "00000001") then    --empty data from RAM changed to <space>
                            DATA_OUT <= "00100000";
                        else
                            DATA_OUT <= DATA_IN;    --Output data from RAM
                        end if;
                        RS <= '1';            --writing data
                        cntLimit := 10;    --define waiting time
                    end if;
                    ENABLE <= '1';     --high stroke
                end if;
                
                cntTime := cntTime + 1;
                if (cntTime = 5) then
                    ENABLE <= '0';     --end of stroke (4ms length)
                end if;
                if (cntTime >= cntLimit) then --waiting time for LCD
                    cntTime := 0;    --reset counter for waiting
                    writeStep := writeStep + 1;    --writing next char
                    ram_add := ram_add + 1;    --next databyte from RAM
                    if (writeStep > 20) then    --end of display line
                        writeStep := 0;    --start from beginning
                        letterOffset := letterOffset + 1;    --Offset for shifting the LCD
                        if (letterOffset >= 180) then
                            letterOffset := 0;
                        end if;
                        ram_add := "00000000";    --reset RAM address
                        ram_add := ram_add + letterOffset;    
--starting with offset for shifting effect on display
                    end if;
                end if;                
            end if;
        end if;
    end process;
end Behavioral;
System controller 

Figure: System controller module

The system controller is the central controlling device. It uses the RX and the TX component to communicate with the GSM module. The controller is sending the AT-command AT+CMGL="ALL" every second to poll the stored SMS from the GSM module. The received data is saved in the RAM.
At the moment the GSM module has to be configured via PC (e.g. PIN, text mode, clear the memory), because the system controller is only capable of sending one command. It's planed to make the system work autonomously. Therefore it has to deal with all necessary commands.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Test_Data_out is
    Port ( DATA_OUT : out  STD_LOGIC_VECTOR (7 downto 0);
                FLAG_T : in std_logic;
                FLAG_R : in std_logic;
                RESET : in  STD_LOGIC;
                DATA_IN : in  STD_LOGIC_VECTOR (7 downto 0);
                RAM_OUT : out  STD_LOGIC_VECTOR (7 downto 0);
                RAM_ADDRESS : out  STD_LOGIC_VECTOR (7 downto 0);
                --RD_RAM : out std_logic;
                WRT_RAM : out std_logic;
                clk : in std_logic);
     
end Test_Data_out;

architecture Behavioral of Test_Data_out is
    type arr is array(0 to 13) of std_logic_vector(7 downto 0);
    --shared variable token : arr;
    signal at_cmgl_all : arr;    
    
begin

    process(clk, RESET)
        variable cnt : integer range 0 to 13 := 0;
        variable cnt_clk : integer range 0 to 100000000 := 90000000;
        variable send : boolean;
        variable ram_wrt : boolean;
        variable sending : boolean;
        variable ram_add : std_logic_vector (7 downto 0);
        procedure init_tokens is
        begin
            at_cmgl_all(0) <= "01100001";        --a
            at_cmgl_all(1) <= "01110100";        --t
            at_cmgl_all(2) <= "00101011";        --+
            at_cmgl_all(3) <= "01100011";        --c
            at_cmgl_all(4) <= "01101101";        --m
            at_cmgl_all(5) <= "01100111";        --g
            at_cmgl_all(6) <= "01101100";        --l
            at_cmgl_all(7) <= "00111101";        --=
            at_cmgl_all(8) <= "00100010";        --"
            at_cmgl_all(9) <= "01100001";        --a
            at_cmgl_all(10) <= "01101100";    --l
            at_cmgl_all(11) <= "01101100";    --l
            at_cmgl_all(12) <= "00100010";    --"
            at_cmgl_all(13) <= "00001101";    --<cr>
        end init_tokens;
    begin
        init_tokens;
        if (RESET = '1') then
            ram_add := "00010100"; --20 for text start position
            cnt_clk := 0;
        elsif (clk = '1') and (clk'event) then
            if (cnt_clk = 100000000) then
                cnt_clk := 0;
                send := true;
                ram_add := "00010100"; --20 for text start position
            end if;
            if (send = true) then
                if (FLAG_T = '0') then
                    DATA_OUT <= at_cmgl_all(cnt);
                end if;
                if (FLAG_T = '1') then
                    sending := true;
                end if;
                if (FLAG_T = '0') and (sending = true) then
                    sending := false;
                    cnt := cnt + 1;
                end if;
                if (cnt = 14) then
                    cnt := 0;
                    send := false;
                    DATA_OUT <= "00000000";
                end if;
            else
                if (FLAG_R = '0') then
                    ram_wrt := true;
                    RAM_ADDRESS <= ram_add;
                    RAM_OUT <= DATA_IN;
                    WRT_RAM <= '1';
                else
                    if (ram_wrt = true) then
                        ram_add := ram_add + 1;
                        ram_wrt := false;
                    end if;
                end if;
            end if;
            cnt_clk := cnt_clk + 1;
        end if;
    end process;
end Behavioral;
Comments


Sources
ċ
SMS_to_LCD.zip
(4196k)
Alexander Wegner,
26 Jul 2011, 02:08