Dx display
From Hamsterworks Wiki!
This FPGA Project was completed in September 2012.
This project lights one segment in each display, and turns all the LEDS on. The board is a TM1638 display board from Deal Extreme (http://dx.com/p/8x-digital-tube-8x-key-8x-double-color-led-module-81873). The only documentation I had available to me was http://code.google.com/p/tm1638-library/ - an Arduino Library in C++.
Tested on a Digilent Nexys2 at both 5V and 3.3V VCC, and the Digilent Basys2 at 3.3V, with transfers slightly under 1MHz.
After this project was seen on Hackaday Paul posted a link to a translated datasheet: https://docs.google.com/file/d/0B84N2SrJaybwZTgxYjM4ZmEtY2EyZi00YjVjLWIzOTctYTlhMjJkM2MxMTBl/edit?pli=1 Strangely enough the link only works for me in Internet Explorer and not Chrome :-/ It looks like the data rate is 1Mb/s - as I divide the clock by 64 for each half of the interface's clock signal I'm running at about 0.390MHz for a 50MHz clock.
Contents |
Connections (as viewed from component side
Important Note The layout of the pins that is silk-screened on the board is as viewed from the back of the board. VCC is the pin soldered in the hole with the square outline.
Strobe1 controls this board, the other strobes pass through to the 'out' connector.
| Gnd | Vcc |
| Clk | Data |
| Strobe1 | Strobe2 |
| Strobe3 | Strobe4 |
| Strobe5 | Strobe6 |
Device summary
Only 4 commands are needed to use the display functions. I haven't worked on reading the buttons:
Commands
| bits | Description |
|---|---|
| 010000000 | Address mode, auto increment |
| 010000100 | Address mode, single address |
| 1100aaaa dddddddd | At address aaaa write dddddddd - multiple bytes of data can be transferred |
| 1000abbb | Display control - a = active, bbb = brightness |
Register map
| Address | Value |
|---|---|
| 0000 | 7seg 1 |
| 0001 | LED 1, bit 0 = red, bit 1 = green |
| 0010 | 7seg 2 |
| 0010 | LED 2, bit 0 = red, bit 1 = green |
| 0100 | 7seg 3 |
| 0011 | LED 3, bit 0 = red, bit 1 = green |
| 0110 | 7seg 4 |
| 0111 | LED 4, bit 0 = red, bit 1 = green |
| 1000 | 7seg 5 |
| 1001 | LED 5, bit 0 = red, bit 1 = green |
| 1010 | 7seg 6 |
| 1011 | LED 6, bit 0 = red, bit 1 = green |
| 1100 | 7seg 7 |
| 1101 | LED 7, bit 0 = red, bit 1 = green |
| 1110 | 7seg 8 |
| 1111 | LED 8, bit 0 = red, bit 1 = green |
design_top.vhd
----------------------------------------------------------------------------------
-- Company:
-- Engineer: Mike Field <hamster@snap.net.nz>
--
-- Module Name: design_top - Behavioral
-- Description: Top level for testing the DX display
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity design_top is
Port ( clk : in STD_LOGIC;
d_clk : out STD_LOGIC;
d_strobe : out STD_LOGIC;
d_data : out STD_LOGIC);
end design_top;
architecture Behavioral of design_top is
COMPONENT dx_display
PORT(
clk : IN std_logic;
reset : IN std_logic;
adv : IN std_logic;
byte : OUT std_logic_vector(7 downto 0);
endCmd : OUT std_logic;
newData : OUT std_logic
);
END COMPONENT;
COMPONENT dx_display_xmit
PORT(
clk : IN std_logic;
reset : IN std_logic;
byte : IN std_logic_vector(7 downto 0);
endCmd : IN std_logic;
newData : IN std_logic;
d_strobe : OUT std_logic;
d_clk : OUT std_logic;
d_data : OUT std_logic;
adv : OUT std_logic
);
END COMPONENT;
signal byte : std_logic_vector(7 downto 0);
signal endCmd : std_logic;
signal newData : std_logic;
signal adv : std_logic;
signal reset : std_logic;
signal resetShift : std_logic_vector(15 downto 0) := (others =>'1');
begin
reset <= resetShift(0);
Inst_dx_display: dx_display PORT MAP(
clk => clk,
reset => reset,
byte => byte,
endCmd => endCmd,
newData => newData,
adv => adv
);
Inst_dx_display_xmit: dx_display_xmit PORT MAP(
clk => clk,
reset => reset,
byte => byte,
endCmd => endCmd,
newData => newData,
adv => adv,
d_strobe => d_strobe,
d_clk => d_clk,
d_data => d_data
);
reset_proc: process(clk)
begin
if rising_edge(clk) then
resetShift <= '0' & resetShift(15 downto 1);
end if;
end process;
end Behavioral;
dx_display.vhd
----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
--
-- Description: Driver for the DealExteme display board,
-- 8 x 7 segs
-- 8 x bi-colour LED
-- 8 x buttons
--
-- Dependencies: None
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity dx_display is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
byte : out STD_LOGIC_VECTOR(7 downto 0);
endCmd : out STD_LOGIC;
newData : out STD_LOGIC;
adv : in STD_LOGIC);
end dx_display;
architecture Behavioral of dx_display is
signal counter : std_logic_vector(4 downto 0) := (others => '0');
signal nextcounter : unsigned(4 downto 0);
begin
nextcounter <= unsigned(counter) + 1;
data_proc: process(counter)
begin
case counter is
when "00000" => byte <= x"40"; endCmd <= '1'; newData <= '1'; -- Set address mode - auto inc
when "00001" => byte <= x"8C"; endCmd <= '1'; newData <= '1'; -- Turn display on, brightness 4 of 7
when "00010" => byte <= x"C0"; endCmd <= '0'; newData <= '1'; -- Write at the left display
when "00011" => byte <= x"01"; endCmd <= '0'; newData <= '1'; -- 7seg1 - Top center Segment
when "00100" => byte <= x"01"; endCmd <= '0'; newData <= '1'; -- LED1 red
when "00101" => byte <= x"02"; endCmd <= '0'; newData <= '1'; -- 7seg2 - Top right segment
when "00110" => byte <= x"02"; endCmd <= '0'; newData <= '1'; -- LED2 green
when "00111" => byte <= x"04"; endCmd <= '0'; newData <= '1'; -- 7seg3 - Bottom right segment
when "01000" => byte <= x"01"; endCmd <= '0'; newData <= '1'; -- LED3 red
when "01001" => byte <= x"08"; endCmd <= '0'; newData <= '1'; -- 7seg4 - Bottom center segment
when "01010" => byte <= x"02"; endCmd <= '0'; newData <= '1'; -- LED4 green
when "01011" => byte <= x"10"; endCmd <= '0'; newData <= '1'; -- 7seg5 - Bottom left segment
when "01100" => byte <= x"01"; endCmd <= '0'; newData <= '1'; -- LED5 red
when "01101" => byte <= x"20"; endCmd <= '0'; newData <= '1'; -- 7seg6 - Top left segment
when "01110" => byte <= x"02"; endCmd <= '0'; newData <= '1'; -- LED6 green
when "01111" => byte <= x"40"; endCmd <= '0'; newData <= '1'; -- 7seg6 - Top right segment
when "10000" => byte <= x"01"; endCmd <= '0'; newData <= '1'; -- LED7 red
when "10001" => byte <= x"80"; endCmd <= '0'; newData <= '1'; -- 7seg6 - Center segment
when "10010" => byte <= x"02"; endCmd <= '1'; newData <= '1'; -- LED8 green
when others => byte <= x"FF"; endCmd <= '1'; newData <= '0'; -- End of data / idle
end case;
end process;
clk_proc: process(clk)
begin
if rising_edge(clk) then
if reset = '1' then
counter <= (others => '0');
elsif adv = '1' and counter /= "11111" then
counter <= std_logic_vector(nextcounter);
end if;
end if;
end process;
end Behavioral;
dx_display_xmit.vhd
This needs a good re-write to make it more compact, but it works for now!
----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
--
-- Module Name: dx_display_xmit - Behavioral
-- Description: Drive the serial bus for the display
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity dx_display_xmit is
Port ( clk : in STD_LOGIC;
reset : in STD_LOGIC;
byte : in STD_LOGIC_VECTOR (7 downto 0);
endCmd : in STD_LOGIC;
newData : in STD_LOGIC;
adv : out STD_LOGIC;
d_strobe : out STD_LOGIC;
d_clk : out STD_LOGIC;
d_data : out STD_LOGIC);
end dx_display_xmit;
architecture Behavioral of dx_display_xmit is
signal thisEndCmd : std_logic;
signal thisByte : std_logic_vector(7 downto 0) := (others => '0');
signal bitsLeftToSend : std_logic_vector(6 downto 0) := (others => '0');
signal state : std_logic_vector(2 downto 0) := (others => '0');
signal divider : std_logic_vector(63 downto 0) := x"1000000000000000";
begin
clk_proc: process(clk)
begin
if rising_edge(clk) then
divider <= divider(0) & divider(63 downto 1);
adv <= '0';
if reset = '1' then
thisByte <= (others => '0');
thisEndCmd <= endCmd;
state <= (others => '0');
d_strobe <= '1';
d_clk <= '1';
d_data <= '1';
divider <= x"1000000000000000";
elsif divider(0) = '1' then
d_strobe <= '1';
d_clk <= '1';
d_data <= '1';
case state is
when "000" => -- Idle, without an open command
if newData = '1' then
state <= std_logic_vector(unsigned(state)+1);
thisByte <= byte;
thisEndCmd <= endCmd;
adv <= '1';
d_strobe <= '0';
d_clk <= '1';
d_data <= '1';
else
d_strobe <= '1';
d_clk <= '1';
d_data <= '1';
end if;
bitsLeftToSend <= (others => '1');
when "001" => -- transfer a bot
state <= std_logic_vector(unsigned(state)+1);
d_strobe <= '0';
d_clk <= '0';
d_data <= thisByte(0);
when "010" =>
if bitsLeftToSend(0) = '1' then -- Still got a bit to send?
state <= std_logic_vector(unsigned(state)-1);
elsif thisEndCmd = '1' then
state <= "011"; -- close off command
else
state <= "100"; -- keep command open
end if;
d_strobe <= '0';
d_clk <= '1';
d_data <= thisByte(0);
thisByte <= '1' & thisByte(7 downto 1);
bitsLeftToSend <= '0' & bitsLeftToSend(6 downto 1);
when "011" => --- ending the command by rasing d_strobe, the going back to idle state
state <= "000";
d_strobe <= '1';
d_clk <= '1';
d_data <= thisByte(0);
when "100" => -- Waiting for data, withan open command
d_strobe <= '0';
d_clk <= '1';
d_data <= '1';
if newData = '1' then
state <= "001"; -- start transfering bits
thisByte <= byte;
thisEndCmd <= endCmd;
adv <= '1';
bitsLeftToSend <= (others => '1');
end if;
when others => -- performa a reset
thisByte <= (others => '0');
state <= (others => '0');
d_strobe <= '1';
d_clk <= '1';
d_data <= '1';
end case;
end if;
end if;
end process;
end Behavioral;
nexys2.ucf
NET "clk" TNM_NET = clk; TIMESPEC TS_clk = PERIOD "clk" 50 MHz HIGH 50%; ############################################################################## # Nexys2 - 12 pin connectors - top row of JA - data, clk, strobe, N/C, GND, 5V ############################################################################## NET "clk" LOC = "B8" | IOSTANDARD=LVTTL; # Bank = 0, Pin name = IP_L13P_0/GCLK8, Type = GCLK, Sch name = GCLK0 NET "d_data" LOC = "L15" | IOSTANDARD=LVTTL; # Bank = 1, Pin name = IO_L09N_1/A11, Type = DUAL, Sch name = JA1 NET "d_clk" LOC = "K12" | IOSTANDARD=LVTTL; # Bank = 1, Pin name = IO_L11N_1/A9/RHCLK1, Type = RHCLK/DUAL, Sch name = JA2 NET "d_strobe" LOC = "L17" | IOSTANDARD=LVTTL; # Bank = 1, Pin name = IO_L10N_1/VREF_1, Type = VREF, Sch name = JA3
basys2.ucf
NET "clk" TNM_NET = clk; TIMESPEC TS_clk = PERIOD "clk" 50 MHz HIGH 50%; ######################################## # Pins for Basys2 Board - Pmod JA ######################################## NET "clk" LOC = "B8"; # Bank = 0, Signal name = MCLK NET "clk" CLOCK_DEDICATED_ROUTE = FALSE; NET "d_data" LOC = "B2" | DRIVE = 2; # | PULLUP ; # Bank = 1, Signal name = JA1 NET "d_clk" LOC = "A3" | DRIVE = 2; # | PULLUP ; # Bank = 1, Signal name = JA2 NET "d_strobe" LOC = "J3" | DRIVE = 2; # | PULLUP ; # Bank = 1, Signal name = JA3
Comparing the software vs hardware design
It is quite interesting to compare the intent of both designs. One is from a software perspective, one is from the hardware perspective. The C++ code inspired by the HDL design is much cleaner, but doesn't show the intent of the code (e.g. what is a command, what is data).
The original C++
void TM16XX::sendData(byte address, byte data)
{
sendCommand(0x44);
digitalWrite(strobePin, LOW);
send(0xC0 | address);
send(data);
digitalWrite(strobePin, HIGH);
}
void TM16XX::sendCommand(byte cmd)
{
digitalWrite(strobePin, LOW);
send(cmd);
digitalWrite(strobePin, HIGH);
}
void TM16XX::send(byte data)
{
for (int i = 0; i < 8; i++) {
digitalWrite(clockPin, LOW);
digitalWrite(dataPin, data & 1 ? HIGH : LOW);
data >>= 1;
digitalWrite(clockPin, HIGH);
}
}
The intent of the HDL version
void TM16XX::sendData(byte address, byte data)
{
send(0x44, 1);
send(0xC0 | address,0);
send(data, 1);
}
void TM16XX::send(byte data, byte endCmd)
{
digitalWrite(strobePin, LOW);
for (int i = 0; i < 8; i++) {
digitalWrite(clockPin, LOW);
digitalWrite(dataPin, data & 1 ? HIGH : LOW);
data >>= 1;
digitalWrite(clockPin, HIGH);
}
digitalWrite(strobePin, endCmd);
}
