Ghost-Free Seven-Segment Display Controller on FPGA

A multiplexed seven-segment display looks simple until dim neighboring segments glow, digits smear during updates, or brightness changes with the number being shown. The fix is a disciplined scan sequence: blank, change the segment pattern, select the next digit, then enable light. This tutorial implements that sequence and adds per-display PWM brightness.

Understand the electrical polarity

VHDL-2008 reference implementation

This article uses synthesizable VHDL-2008. The complete tested VHDL bundle is linked below; adapt clock constraints, I/O standards, and timing parameters to the target board and panel.

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

entity hub75_row_scanner is
  generic (COLUMNS : positive := 64; ROW_GROUPS : positive := 16);
  port (
    clk       : in  std_logic;
    rst       : in  std_logic;
    dwell_done: in  std_logic;
    pixel_bits: in  std_logic_vector(5 downto 0);
    hub_clk   : out std_logic;
    lat       : out std_logic;
    oe_n      : out std_logic;
    row_addr  : out natural range 0 to ROW_GROUPS - 1;
    rgb       : out std_logic_vector(5 downto 0)
  );
end entity;

architecture rtl of hub75_row_scanner is
  type state_t is (blank, shift_low, shift_high, latch, set_row, display);
  signal state : state_t := blank;
  signal col   : natural range 0 to COLUMNS - 1 := 0;
  signal row   : natural range 0 to ROW_GROUPS - 1 := 0;
begin
  process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        state <= blank; col <= 0; row <= 0;
      else
        case state is
          when blank      => col <= 0; state <= shift_low;
          when shift_low  => state <= shift_high;
          when shift_high => if col = COLUMNS - 1 then state <= latch; else col <= col + 1; state <= shift_low; end if;
          when latch      => state <= set_row;
          when set_row    => state <= display;
          when display    => if dwell_done = '1' then if row = ROW_GROUPS - 1 then row <= 0; else row <= row + 1; end if; state <= blank; end if;
        end case;
      end if;
    end if;
  end process;

  hub_clk  <= '1' when state = shift_high else '0';
  lat      <= '1' when state = latch else '0';
  oe_n     <= '0' when state = display else '1';
  row_addr <= row;
  rgb      <= pixel_bits when state = shift_low else (others => '0');
end architecture;

Download the VHDL-2008 FPGA LED controller examples

The Basys 3 uses a four-digit common-anode display. Its digit enables and segment cathodes are active-low: drive one anode low to select a digit, and drive a segment line low to illuminate that segment. Other boards may be common-cathode, so put polarity conversion at the top level instead of burying it in the scan engine.

Choose a scan rate

If the controller visits four digits at a 4 kHz scan-event rate, each digit refreshes at 1 kHz. That leaves comfortable margin for human vision and many cameras. The segment duty cycle is about one quarter before global brightness PWM is applied.

Decode hexadecimal digits

Blank before switching

The explicit blank interval prevents the old segment pattern from appearing on the new digit. Because nonblocking assignments use old right-hand-side values, calculate the next digit in a temporary signal or update the index and pattern in separate states in production RTL.

Add brightness without breaking the scan

Keep scanning at a fixed rate and gate the selected anode with a higher-frequency PWM enable. Do not slow the scan to dim the display; that creates flicker. Latch brightness at a PWM boundary so one pulse cannot be truncated by a mid-period write.

Hardware checklist

  • Confirm common-anode versus common-cathode polarity.
  • Verify the A-through-G bit order against the schematic.
  • Blank all digits during reset.
  • Probe an enable and one segment together to confirm non-overlap.
  • Use the board manufacturer’s pin constraints and I/O voltage.

Official reference

Digilent Basys 3 FPGA Board Reference Manual documents the common-anode display, active polarities, and package pins.


FPGA LED controller tutorial series

This article is part of the FPGA LED Controller Tutorials learning path. Continue with PWM vs. BCM, the HUB75 timing calculator, or VHDL-2008 verification.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *