CheapScope

From Hamsterworks Wiki!

Jump to: navigation, search

None of the features, none of the price, all on one web page

CheapScope is my functional but limited Virtual Logic Analyser, designed for the Papilio One, but usable on any Xilinx FPGA where you can gain access to one spare I/O pin.

It is feature poor, but it works.

Contents

Example

Here CheapScope watching the LFSR created by the VHDL code below

Cheapscope.png

The "><" above the samples indicates where the trigger fired, and the number in the top right corner is the position of the blue cursor relative to the trigger point. I haven't set up two-way communication yet, so triggers are set by editing "capture.vhdl" and then rebuilding the project.

----------------------------------------------------------------------------------
-- Engineer:  Mike Field <hamster@snap.net.nz>
-- Description: lfsr_test : a test for CheapScope.
--
-- I've put this together for my personal use. You are welcome to use it however
-- your like. Just because it fits my needs it may not fit yours, if so, bad luck.
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity lfsr_test is
    Port ( clk : in  STD_LOGIC;
           cheapscope_tx : out  STD_LOGIC);
end lfsr_test;

architecture Behavioral of lfsr_test is
   -------------------------------------
   -- Cheapscope added for testing
   -------------------------------------
   COMPONENT cheapscope
   GENERIC( tx_freq :natural);
   PORT( 
      capture_clk : IN std_logic;
      tx_clk : IN std_logic;
      probes : IN std_logic_vector(15 downto 0);          
      serial_tx : OUT std_logic
   );
   END COMPONENT;

   signal state   : std_logic_vector(15 downto 0) := (others => '1');
   signal feedback : std_logic;
begin
   feedback <= state(15) xor state(2);
process (clk)
   begin
      if rising_edge(clk) then
         state <= state(14 downto 0) & feedback;
      end if;
   end process;

   -------------------------------------
   -- Cheapscope added for testing
   -------------------------------------
   Inst_cheapscope: cheapscope GENERIC MAP (
      tx_freq => 32000000
   ) PORT MAP(
      capture_clk => clk,
      probes => state,
      tx_clk => clk,
      serial_tx => cheapscope_tx
   );
end Behavioral;

What is a Virtual Logic Analyzer?

All FPGA vendors have an Virtual Logic Analyser (VLA) that can be used to inspect a design as it runs. The downside is that most require an expensive paid license to use, and then either the board or JTAG adapter must be supported by the vendor's tools.

The good feature of VLAs these are:

  • No physical probing is needed - you attach your VLA to signals within the FPGA fabric
  • Host communication is usually provided over the programming interface (most often JTAG)
  • With commercial VLAs you do not have to change your design and then 're-implement' it - you just merge the VLA core into your fully 'place and routed' design.
  • The VLA captures signals in the same clocking domain as the design itself - you don't have to worry about sampling artifacts or timing issues

The bad features of these are:

  • Your FPGA board must have an interface compatible with the vendor tools (i.e. JTAG)
  • VLAs usually require a commercial license, making them too expensive for hobbyists to access.
  • The vendor's front-end software is fixed - you can't customise it, and usually you can't run it independently of the rest of the design tools.
  • You don't see what is really occurring on FPGA's external interfaces, like you can with a real logic analyser, so you really will need both to solve really tricky problems.

My crufty solution

My solution is a minimal implementation of an eight bit VLA. It works as follows:

  • Continuously capture 16 bits of probe data in a 16x1024 Block RAM
  • Wait for the Block RAM is half full before arming the trigger logic
  • For the trigger to fire
  • Wait for 512 smaples to be captured then stop capturing
  • Send the captured data out over the RS232 interface, starting from 512 samples before the trigger
  • Start over again

I don't have the ability to attach CheapScope to a netlist - instead you have to design it into your project and re-implement it. TO keep things small, rather than having an interactive way to set triggers, CheapScope requires you to edit the project and re-implement it.

The data is stored in dual-port block RAM, and the capturing module can be in a different clock domain to the RS232 interface, which can be useful when designs have multiple domains.

Although it isn't anywhere as good as a commercial VLA, it is useful for watching your designs in action. You can also use it on FPGA boards that do not have the vendor's VLA interface available - all that is needed is one I/O pin to connect to a RS232 to USB breakout board.

Because of the protocol's simplicity CheapScope clients can be written for any OS that supports serial ports.

RS232 format

The Binary data is converted to ASCII hex characters, and then sent up the line at 19200 baud, followed with a CR+NL pair, for a grand total of 4098 characters per capture. The client code below sorts this all out for you.

You can check if your device is functioning using the following two command under Linux:

stty 19200 < /dev/ttyUSB1
cat < /dev/ttyUSB1

You should then see the raw ASCII hex values streaming past.

On Windows you can open the serial port in Putty - just remember to set the speed and flow control.

Due to lack of flow control on the serial port sometimes data can get lost. To allow for this I only display data when all 4096 characters are received between the newline characters.

Possible Enhancements

  • Interactive triggers (but would add a lot of resource usage)
  • Larger capture buffer
  • A more robust RS232 TX
  • A 'tighter' transmitter component. My one works in 40 bit chunks.

FPGA Source

This component requires about 100 slices and one BRAM block.

cheapscope.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Description: Cheapscope - captures signals and then sends them as ASCII 
--                           to the serial port
--
-- I've put this together for my personal use. You are welcome to use it however
-- your like. Just because it fits my needs it may not fit yours, if so, bad luck.
--
-- v2.0 Changed BRAM to 4bit/16bit to reduce logic count
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
Library UNISIM;
use UNISIM.vcomponents.all;

entity cheapscope is
   generic (tx_freq : natural);
    Port ( capture_clk : in  STD_LOGIC;
           tx_clk : in  STD_LOGIC;
           probes : in  STD_LOGIC_VECTOR (15 downto 0);
           serial_tx : out  STD_LOGIC);
end cheapscope;

architecture Behavioral of cheapscope is

   COMPONENT Transmitter
   generic (FREQUENCY : natural );
   PORT(
      clk             : IN  std_logic;
      ram_data        : IN  std_logic_vector(3 downto 0);
      capture_done    : IN  std_logic;
      trigger_address : IN  std_logic_vector(9 downto 0);          
      serial_tx       : OUT std_logic;
      ram_address     : OUT std_logic_vector(11 downto 0);
      arm_again       : OUT std_logic
      );
   END COMPONENT;

   COMPONENT Capture
   PORT(
      clk             : IN std_logic;
      probes          : IN  std_logic_vector(15 downto 0);
      write_address   : OUT std_logic_vector( 9 downto 0);
      write_data      : OUT std_logic_vector(15 downto 0);
      write_enable    : OUT std_logic;
      capture_done    : OUT std_logic;
      trigger_address : OUT std_logic_vector(9 downto 0);
      arm_again       : IN std_logic
      );
   END COMPONENT;

   signal write_address: STD_LOGIC_VECTOR(9 downto 0) := (others => '0');
   signal read_address:  STD_LOGIC_VECTOR(11 downto 0) := (others => '0');

   signal write_data      : STD_LOGIC_VECTOR(15 downto 0);
   signal read_data       : STD_LOGIC_VECTOR(3 downto 0);
   signal capture_done    : STD_LOGIC;
   signal trigger_address : STD_LOGIC_VECTOR(9 downto 0);
   signal arm_again       : STD_LOGIC;
   
   signal write_enable    : STD_LOGIC;
   
begin

   RAMB16_S18_S18_inst : RAMB16_S4_S18
   generic map (
      INIT_A => X"000", --  Value of output RAM registers on Port A at startup
      INIT_B => X"000", --  Value of output RAM registers on Port B at startup
      SRVAL_A => X"000", --  Port A ouput value upon SSR assertion
      SRVAL_B => X"000", --  Port B ouput value upon SSR assertion
      WRITE_MODE_A => "WRITE_FIRST", --  WRITE_FIRST, READ_FIRST or NO_CHANGE
      WRITE_MODE_B => "WRITE_FIRST", --  WRITE_FIRST, READ_FIRST or NO_CHANGE
      SIM_COLLISION_CHECK => "ALL")  -- "NONE", "WARNING", "GENERATE_X_ONLY", "ALL" 
   port map (
      DOA    => read_data,    -- Port A 4-bit Data Output
      ADDRA => read_address,  -- Port A 11-bit Address Input
      CLKA  => tx_clk,        -- Port A Clock
      DIA   => (others=>'0'), -- Port A 4-bit Data Input
      ENA   => '1',           -- Port A RAM Enable Input
      SSRA  => '0',           -- Port A Synchronous Set/Reset Input
      WEA   => '0',           -- Port A Write Enable Input
      
      DOB    => open,         -- Port B 16-bit Data Output
      DOPB  => open,          -- Port B 2-bit Parity Output
      ADDRB => write_address, -- Port B 11-bit Address Input
      CLKB  => capture_clk,   -- Port B Clock
      DIB   => write_data,    -- Port B 16-bit Data Input
      DIPB  => "00",          -- Port B 2-bit parity Input
      ENB   => '1',           -- Port B RAM Enable Input
      SSRB  => '0',           -- Port B Synchronous Set/Reset Input
      WEB   => write_enable   -- Port B Write Enable Input
   );

   Inst_Transmitter: Transmitter GENERIC MAP (
      frequency => tx_freq
   ) PORT MAP(
      clk             => tx_clk,
      serial_tx       => serial_tx,
      ram_data        => read_data,
      ram_address     => read_address,
      capture_done    => capture_done,
      trigger_address => trigger_address,
      arm_again       => arm_again
   );

   Inst_Capture: Capture PORT MAP(
      clk             => capture_clk,
      probes          => probes,
      write_address   => write_address,
      write_data      => write_data,
      capture_done    => capture_done,
      arm_again       => arm_again,
      trigger_address => trigger_address,
      write_enable    => write_enable
   );
end Behavioral;

capture.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- I've put this together for my personal use. You are welcome to use it however
-- your like. Just because it fits my needs it may not fit yours, if so, bad luck.
--
-- Description: 
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_unsigned.ALL;

entity Capture is
    Port ( clk             : in  STD_LOGIC;
           probes          : in  STD_LOGIC_VECTOR (15 downto 0);
           
           write_address   : out STD_LOGIC_VECTOR (9 downto 0);
           write_data      : out STD_LOGIC_VECTOR (15 downto 0);
           write_enable    : out STD_LOGIC;
           
           capture_done    : out STD_LOGIC;
           arm_again       : in  STD_LOGIC;
           trigger_address : out STD_LOGIC_VECTOR (9 downto 0)
         );
end Capture;

architecture Behavioral of Capture is
   constant STATE_ARMING    : std_logic_vector(1 downto 0) := "00"; 
   constant STATE_ARMED     : std_logic_vector(1 downto 0) := "01"; 
   constant STATE_TRIGGERED : std_logic_vector(1 downto 0) := "10"; 
   constant STATE_DONE      : std_logic_vector(1 downto 0) := "11"; 
   signal state      : std_logic_vector(1 downto 0) := STATE_ARMING;

   signal address  : std_logic_vector(9 downto 0) := (others => '0');
   signal captured : std_logic_vector(9 downto 0) := (others => '0');
   signal last     : std_logic_vector(15 downto 0)  := (others => '0');
begin
   process(clk)
   begin
      if rising_edge(clk) then
         write_data    <= probes;
         write_address <= address;
         address       <= address+1;
         case state is
            when STATE_ARMING    =>
               capture_done <= '0';
               write_enable  <= '1';
               if captured = "0111111111" then 
                  state <= STATE_ARMED;
               end if;
               captured      <= captured+1;
            when STATE_ARMED     =>
               write_enable    <= '1';
               trigger_address <= address;
               
               if last(0) = '0' and probes(0) = '1' then
                  state           <= STATE_TRIGGERED;
               end if;
            when STATE_TRIGGERED =>
               write_enable  <= '1';
               if captured = "1111111111" then 
                  capture_done <= '1';
                  state <= STATE_DONE;
               end if;
               captured      <= captured+1;
            when STATE_DONE      =>
               write_enable <= '0';
               if arm_again = '1' then 
                  state <= STATE_ARMING;
               end if;
            when others  =>
         end case;
         last <= probes;
      end if;
   end process;
end Behavioral;

transmitter.vhd

----------------------------------------------------------------------------------
-- Engineer: Mike Field <hamster@snap.net.nz>
-- 
-- Description:  Transmitter - the RS232 TX component for cheapscope
--
-- I've put this together for my personal use. You are welcome to use it however
-- your like. Just because it fits my needs it may not fit yours, if so, bad luck.
--
-- v2.0 Changed BRAM to 4bit/16bit to reduce logic
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity Transmitter is
    generic (FREQUENCY : natural );
    Port ( clk             : in  STD_LOGIC;
           serial_tx       : out STD_LOGIC;
           ram_data        : in  STD_LOGIC_VECTOR(3 downto 0);
           ram_address     : out STD_LOGIC_VECTOR(11 downto 0);
           capture_done    : in  STD_LOGIC;
           trigger_address : in  STD_LOGIC_VECTOR(9 downto 0);
           arm_again       : out STD_LOGIC);
end Transmitter;

architecture Behavioral of Transmitter is

   COMPONENT bin2ascii
   PORT(
      value : IN std_logic_vector(3 downto 0);          
      char : OUT std_logic_vector(7 downto 0)
      );
   END COMPONENT;

   -- For the binary to ASCII Hex conversion
   signal ascii_data : std_logic_vector(9 downto 0);
   
   -- Where we are in RAM readback 
   signal xmit_address : std_logic_vector(11 downto 0) := (others => '0');

   -- The FSM to control sending data 
   constant STATE_WAITING : std_logic_vector(2 downto 0) := "000"; 
   constant STATE_XMIT    : std_logic_vector(2 downto 0) := "001"; 
   constant STATE_NL      : std_logic_vector(2 downto 0) := "010"; 
   constant STATE_CR      : std_logic_vector(2 downto 0) := "011"; 
   constant STATE_REARM   : std_logic_vector(2 downto 0) := "100"; 
   signal state           : std_logic_vector(2 downto 0) := STATE_WAITING;
   
   -- The RS232 interface
   constant TERMINAL_COUNT : std_logic_vector(11 downto 0) := "000000000000"+FREQUENCY/19200;
   signal   xmit_cnt       : std_logic_vector(11 downto 0) := "000000000000";
   signal   shift_reg      : std_logic_vector( 9 downto 0) := (others => '1');
   signal   shift_busy     : std_logic_vector(10 downto 0) := (others => '0');
begin
   -- Assign the outgoing RS232 signal
   serial_tx <= shift_reg(0);
   
   -- the '11' on the end of the xor reverses the order of the nibbles being sent up the line.
   ram_address <= (trigger_address & "00") + (xmit_address XOR "100000000011");   
process(ram_data)
   begin
      case ram_data is 
         when x"0"   => ascii_data <= "1" & x"30" & "0";
         when x"1"   => ascii_data <= "1" & x"31" & "0";
         when x"2"   => ascii_data <= "1" & x"32" & "0";
         when x"3"   => ascii_data <= "1" & x"33" & "0";
         when x"4"   => ascii_data <= "1" & x"34" & "0";
         when x"5"   => ascii_data <= "1" & x"35" & "0";
         when x"6"   => ascii_data <= "1" & x"36" & "0";
         when x"7"   => ascii_data <= "1" & x"37" & "0";
         when x"8"   => ascii_data <= "1" & x"38" & "0";
         when x"9"   => ascii_data <= "1" & x"39" & "0";
         when x"A"   => ascii_data <= "1" & x"41" & "0";
         when x"B"   => ascii_data <= "1" & x"42" & "0";
         when x"C"   => ascii_data <= "1" & x"43" & "0";
         when x"D"   => ascii_data <= "1" & x"44" & "0";
         when x"E"   => ascii_data <= "1" & x"45" & "0";
         when others => ascii_data <= "1" & x"46" & "0";
      end case;
   end process;

   process (clk)
   begin
      if rising_edge(clk) then
         case state is
            when STATE_WAITING =>
               -- do nothing while we wait for the capture to finish
               arm_again <= '0';
               if capture_done = '1' and xmit_cnt = 0 then
                  xmit_cnt        <= (others => '0');
                  shift_reg       <= ascii_data; 
                  shift_busy      <= (others => '1');
                  xmit_address    <= (others => '0');
                  xmit_address(0) <= '1';
                  state           <= STATE_XMIT;   
               else
                  state <= STATE_WAITING;   
               end if;
            when STATE_XMIT =>
               if shift_busy(0) = '0' then
                  if xmit_address = 0 then
                     state <= STATE_NL;
                     shift_reg  <= "1000010100";  -- NL.
                     shift_busy   <= (others => '1');
                  else
                     xmit_address <= xmit_address+1;
                     shift_reg    <= ascii_data;
                     shift_busy   <= (others => '1');
                  end if;
               end if;
            when STATE_NL =>
               state <= STATE_NL;
               if shift_busy(0) = '0' then
                   shift_reg   <= "1000011010";  -- CR.
                   shift_busy  <= (others => '1');
                  state <= STATE_CR;
               end if;
            when STATE_CR =>
               state <= STATE_CR;
               if shift_busy(0) = '0' then
                  state <= STATE_REARM;
               end if;
            when STATE_REARM =>
               if capture_done = '1' then
                  arm_again <= '1';
                  state <= STATE_REARM;
               else
                  state <= STATE_WAITING;
               end if;
            when others =>
               state <= STATE_WAITING;
         end case;
         
         -- Sending out the RS232 bits
         if xmit_cnt = TERMINAL_COUNT then
            shift_reg    <= '1' & shift_reg(9 downto 1); 
            shift_busy   <= '0' & shift_busy(10 downto 1);
            xmit_cnt     <= (others => '0');
         else
            xmit_cnt <= xmit_cnt+1;
         end if;
      end if;
   end process;
end Behavioral;

Client Program

Here is the source for a very simple ncurses based client for Linux. Just compile this source with:

gcc -o cheapscope cheapscope.c -Wall -pedantic -O2 -lncurses

When you run it you will need to supply the name of the serial port that is receiving the data as the only command line argument. For my Papilio One I use:

 ./cheapscope /dev/ttyUSB1

For me it works fine in Virtual Box if I pass through USB serial device to the VM running Linux.

The only source file for the whole project is cheapscope.c

cheapscope.c

/*******************************************
* cheapscope.c
*
* A simple character mode interface for my
* CheapScope Virtual Logic Analyser
*
* I've put this together for my personal use. You are welcome to use it however
* your like. Just because it fits my needs it may not fit yours, if so, bad luck.
*
*******************************************/
#include <stdio.h>
#include <unistd.h>
#include <ncurses.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <memory.h>
#include <termios.h>

#define CHANNELS 16
#define MAXVALUES 2048
struct Samples {
    int pos;
    int cursor;
    int nSamples;
    int trigger;
    unsigned int values[MAXVALUES];
};
int max_width, max_height;
struct Samples *samplesNext, *samples = NULL;
char lineBuffer[8192];          /* 4 bits per ASCII character, total of 4Kb */
int lineBufferUsed = 0;
int serialPort;

/********************************************************************/
static void displayChannel(int channel, struct Samples *s)
{
    unsigned int mask = (1 << channel);
    int i;
    move(2 * (CHANNELS - channel) - 1, 4);
    attron(COLOR_PAIR(2));
    for (i = 0; i < max_width - 4; i++)
    {
        if (s != NULL && s->pos + i == s->cursor)
            attron(COLOR_PAIR(5));

        if (s != NULL && s->pos + i == s->trigger - 1)
            addch('>');
        else if (s != NULL && s->pos + i == s->trigger)
            addch('<');
        else
            addch(' ');

        if (s != NULL && s->pos + i == s->cursor)
            attron(COLOR_PAIR(2));
    }
    move(2 * (CHANNELS - channel), 4);
    for (i = 0; i < max_width - 4; i++)
    {
        if (s == NULL || s->pos + i >= s->nSamples)
        {
            attron(COLOR_PAIR(2));
            addch(' ');
        }
        else
        {
            if (s->values[s->pos + i] & mask)
            {
                attron(COLOR_PAIR(3));
                addch(' ');
            }
            else
            {
                if (s->pos + i == s->cursor)
                    attron(COLOR_PAIR(5));
                else
                    attron(COLOR_PAIR(2));
                addch('_');
            }
        }
    }
}


/********************************************************************/
static void displayScreenStatic(void)
{
    int channel;
    clear();
    attron(COLOR_PAIR(4));
    move(0, 0);
    printw("%*.*s", -max_width, max_width,
           "Cheapscope virtual logic analyser");
    for (channel = 0; channel < CHANNELS; channel++)
    {
        move(2 * (CHANNELS - channel), 0);
        attron(COLOR_PAIR(1));
        printw("%2i:", channel);
    }
}


/********************************************************************/
static void updateScreen(struct Samples *s)
{
    int channel;
    if (s != NULL)
    {
        attron(COLOR_PAIR(4));
        move(0, max_width - 6);
        printw("%6i", s->cursor - s->trigger);
        if (s->cursor < s->pos)
            s->pos = s->cursor - 5;
        if (s->cursor >= s->pos + max_width - 4)
            s->pos = s->cursor - max_width + 10;

        /* Clap the display position */
        if (s->pos < 0)
            s->pos = 0;
        if (s->pos > s->nSamples - (max_width - 4))
            s->pos = s->nSamples - (max_width - 4);
    }
    for (channel = 0; channel < CHANNELS; channel++)
        displayChannel(channel, s);
    refresh();
    attron(COLOR_PAIR(4));
    move(max_height - 1, 0);
    if (samplesNext == NULL)
        printw("%*.*s", -max_width, max_width,
               "Use arrows to scroll, 'Q' to quit");

    else
        printw("%*.*s", -max_width, max_width,
               "Use arrows to scroll, 'Q' to quit, 'N' to get move to next capture");
}


/********************************************************************/
static int openSerialPort(char *fname)
{
    int f, flags;
    struct termios cf;
    f = open(fname, O_RDWR);
    if (f == -1)
    {
        fprintf(stderr, "Unable to open '%s'\n", fname);
        return -1;
    }
    if (tcgetattr(f, &cf) == -1)
    {
        fprintf(stderr, "Unable to get termios details\n");
        close(f);
        return -1;
    }
    if (cfsetispeed(&cf, B19200) == -1 || cfsetospeed(&cf, B19200) == -1)
    {
        fprintf(stderr, "Unable to set speed\n");
        close(f);
        return -1;
    }

    /* Make it a raw stream and turn off software flow control */
    cfmakeraw(&cf);
    cf.c_iflag &= ~(IXON | IXOFF | IXANY);
    if (tcsetattr(f, TCSANOW, &cf) == -1)
    {
        fprintf(stderr, "Unable to set termios details\n");
        close(f);
        return -1;
    }
    if (-1 == (flags = fcntl(f, F_GETFL, 0)))
        flags = 0;
    if (-1 == fcntl(f, F_SETFL, flags | O_NONBLOCK))
    {
        fprintf(stderr, "Unable to set non-blocking\n");
        close(f);
        return -1;
    }
    return f;
}


/********************************************************************/
static int hexchar2bin(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    return 0;
}


/********************************************************************/
static int processSerialData(int serialPort)
{
    char bytes[128];
    int bytesRead;
    int i, redraw = 0;
    for (;;)
    {
        bytesRead = read(serialPort, bytes, sizeof(bytes));
        if (bytesRead == 0 || bytesRead == -1)
            break;
        for (i = 0; i < bytesRead; i++)
        {
            if ((bytes[i] >= '0' && bytes[i] <= '9') || (bytes[i] >= 'A' && bytes[i] <= 'F'))
            {
                if (lineBufferUsed < sizeof(lineBuffer))
                    lineBuffer[lineBufferUsed++] = bytes[i];
            }
            else if (bytes[i] == '\n')
            {
              if(lineBufferUsed == 4096)
              {
                if (samplesNext == NULL)
                    samplesNext = malloc(sizeof(struct Samples));
                if (samplesNext != NULL)
                {
                    memset(samplesNext, 0, sizeof(struct Samples));
                    for (i = 0; i < MAXVALUES && i < lineBufferUsed / 4; i++)
                    {
                        int ll = 0, lh = 0, hl = 0, hh = 0;
                        hh = hexchar2bin(lineBuffer[i * 4 + 0]);
                        hl = hexchar2bin(lineBuffer[i * 4 + 1]);
                        lh = hexchar2bin(lineBuffer[i * 4 + 2]);
                        ll = hexchar2bin(lineBuffer[i * 4 + 3]);
                        samplesNext->values[i] = (hh << 12) + (hl << 8) + (lh << 4) + ll;
                    } samplesNext->nSamples = i;
                    samplesNext->trigger = 512;
                    samplesNext->cursor = 512;
                    samplesNext->pos = 512 - (max_width - 4) / 2;
                }
                redraw = 1;
              }
              lineBufferUsed = 0;
            }
        }
    }
    return redraw;
}


/********************************************************************/
int main(int argc, char *argv[])
{
    int redraw;
    int loop;
    int serialPort;
    WINDOW *win;
    if (argc != 2)
    {
        fprintf(stderr,
                "CheapScope Virtual Logic Analyser\n\nusage: %s serial_port\n",
                argv[0]);
        return 0;
    }
    serialPort = openSerialPort(argv[1]);
    if (serialPort == -1)
        return 1;
    win = initscr();
    cbreak();
    noecho();
    nonl();
    intrflush(stdscr, FALSE);
    keypad(stdscr, TRUE);
    if (has_colors())
    {
        start_color();
        init_pair(1, COLOR_WHITE, COLOR_BLACK);
        init_pair(2, COLOR_GREEN, COLOR_BLACK);
        init_pair(3, COLOR_BLACK, COLOR_GREEN);
        init_pair(4, COLOR_WHITE, COLOR_RED);
        init_pair(5, COLOR_GREEN, COLOR_BLUE);
    }
    getmaxyx(win, max_height, max_width);
    if (max_height < 34)
    {
        clear();
        printw("Resize your window to 34 lines to see all channels\n");
        refresh();
        sleep(5);
    }
    timeout(1);
    displayScreenStatic();
    redraw = 1;
    loop = 1;
    while (loop)
    {
        int k;
        if (samples == NULL && samplesNext != NULL)
        {
            samples = samplesNext;
            samplesNext = NULL;
            redraw = 1;
        }
        if (processSerialData(serialPort))
            redraw = 1;
        if (redraw)
        {
            updateScreen(samples);
            redraw = 0;
        }
        k = getch();
        switch (k)
        {
        case KEY_LEFT:
            if (samples && samples->cursor > 0)
            {
                samples->cursor--;
                redraw = 1;
            }
            break;
        case KEY_RIGHT:
            if (samples && samples->cursor < samples->nSamples - 1)
            {
                samples->cursor++;
                redraw = 1;
            }
            break;
        case KEY_NPAGE:
            if (samples && samples->cursor > 0)
            {
                samples->cursor -= (max_width - 4) / 2;
                if(samples->cursor < 0) samples->cursor = 0;
                redraw = 1;
            }
            break;
        case KEY_PPAGE:
            if (samples && samples->cursor < samples->nSamples - 1)
            {
                samples->cursor += (max_width - 4) / 2;
                if(samples->cursor >=  samples->nSamples - 1)
                  samples->cursor =  samples->nSamples - 1;
                redraw = 1;
            }
            break;
        case 'N':
        case 'n':
            if (samples != NULL && samplesNext != NULL)
            {
                free(samples);
                samples = samplesNext;
                samplesNext = NULL;
                redraw = 1;
            }
            break;
        case 'Q':
        case 'q':
        case '\027':
            loop = 0;
            break;
        case KEY_RESIZE:
            getmaxyx(win, max_height, max_width);
            displayScreenStatic();
            redraw = 1;
        }
    }
    endwin();
    return 0;
}

Personal tools