TODO

  • Check documentation vs. controller - some offsets seem wrong in the documentation (for example, the $(x_{1},y_{1})$ offset for lines is fixed in the documentation at $127$, however the display seems to accept more than that). The range for lines is $\overline{0,127}$ and the range for boxes is $\overline{0,130}$ for both $x$ and $y$ - the specification seems to halve the interval for lines because by doing that, it makes sure that we never overstep the $\overline{0,127}$ interval. The solution below permits the full range $\overline{0,127}$ for lines, as well as many extras.
  • Implement the rest of the functions.

Features

The code below is a rough perl sketch that implements several of the ByVAC functions and can be used from the command line in order to pass data to the display, as well as toggle some parameters of the display. The perl script reads STDIN for input and then pipes it to the display controller, by interpreting user-supplies input and translating them to the escape codes used by the display.

The code implements, at the time of writing:

  • initializing the controller by using CR (there is no way to know whether the controller has been already initialized).
  • implements the necessary OOB checks to make sure that the device will not be stuck after a command (for example, sending more data than the device can process or drawing graphics over the dimensions of the display).
  • reading from the command line and posting to the display (ASCII art shows up nicely on the display).
  • the following toggles:
    • backlight control, on and off.
    • cursor display, on and off.
  • the following drawing elements:
    • lines
    • boxes
    • circles
  • setting the foreground and background on both strings and numeric values:
    • black
    • white
    • red
    • green
    • blue
    • yellow
    • magenta
    • cyan
    • any numerical value between 0 (black) and 255 (white).
  • clearing the screen.
  • reverses the $y$-axis so that it conforms to mathematical rules instead of engineering.
  • implements –help man page using pod2usage.

Coordinate Conversion

Like most displays, the $y$-axis is inverted, such that $+y$ points downwards instead of upwards:

 O(0,0)
   +---------+x M(x)=127|130
   |
   |
   |
   |
  +y M(y)=127|13
  • The origin point $O(0,0)$ of the screen lies in the top-left corner.
  • The maximal value $x$ may take is 127 for lines and 130 for boxes.
    • If a point is chosen so that $M(x)>127$ for lines or $M(x)>130$, the display gets stuck.
  • The maximal value $y$ may take is 127 for lines and 130 for boxes.
    • If a point is chosen so that $M(y)>127$ for lines or $M(y)>130$, the display gets stuck.
  • Circles may take any values, even if off-screen printing occurs, the display does not get stuck.

Since the coordinates are counter-intutive for linear algebra, bv4141ctl.pl performs swaps on the coordinates in order to make sure that no out of bounds rendering occurs and that $+y$ points upward instead of downward:

       +y M(y)=127|13
       |
       |
       |
       |
       +---------+x M(x)=127|130
 O(0,0)

Lines and Boxes

The BV4141 has some peculiarities, for example, if $x_{0}>x_{1}$ or $y_{0}>y_{1}$:

       +y
       |      x0,y0
       |     /
       |    /
       |  x1,y1
       +---------+x
 O(0,0)

then the display gets stuck because BV4141 expects that $x_{0}<x_{1}$ and $y_{0}<y_{1}$.

bv4141ctl.pl takes care so that it swaps $x_{0}$, $x_{1}$ respectively $y_{0}$ and $y_{1}$ so that the segment $\overline{(x_{1},y_{1}),(x_{0},y_{0})}=\overline{(x_{0},y_{0}),(x_{1},y_{1})}$; which is what one would expect in linear algebra.

The same applies to boxes:

       +y
       |   +--x0,y0
       |   |  |
       |   |__|
       |  x1,y1
       +---------+x
 O(0,0)

If $x_{0}>x_{1}$ or $y_{0}>y_{1}$ then the display gets stuck and bv4141ctl.pl swaps the coordinates so that the box is drawn regardless whether $x_{0}>x_{1}$ or $y_{0}>y_{1}$ or $x_{0}<x_{1}$ and $y_{0}<y_{1}$.

Usage Examples

  • Display text on the device:
echo "hello world" | bv4141ctl.pl

will print "hello world" on the display, respecting the newline terminator from echo.

  • Draw a red filled box at $x_{1}=10$, $y_{1}=10$ and $x_{2}=50$, $y_{2}=50$:
bv4141ctl.pl --box=10,10,50,50 --fg=red

this is equivalent to:

bv4141ctl.pl --box=50,50,10,10 --fg=red
  • Draw a line on the diagonal from bottom left of the screen to the top right:
bv4141ctl.pl --line=0,0,127,127 --fg=blue

this is equivalent to:

bv4141ctl.pl --line=127,127,0,0 --fg=blue
  • Roll a ball accross the screen:

Bash script:

bv4141ctl.pl --cursor=off; for i in 10 20 30 40 50 60 70 80 90 100 120; do
  ./bv4141ctl.pl --circle=$i,64,10 --fg=black && bv4141ctl.pl --circle=$i,64,10 --fg=white
done

Outcome:

Code

bv4141ctl.pl
#!/usr/bin/perl
###########################################################################
##  Copyright (C) Wizardry and Steamworks 2012 - License: GNU GPLv3      ##
##  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  ##
##  rights of fair usage, the disclaimer and warranty conditions.        ##
###########################################################################
 
 
use strict;
 
use constant true => 1;
use constant false => 0;
 
# Packages
use Device::SerialPort qw( :PARAM :STAT 0.07 );
use Term::ReadKey;
use Time::HiRes qw(usleep);
use Getopt::Long;
use Switch 'fallthrough';
use Pod::Usage;
 
# Autoflush
$| = 1;
# Map EOF
$/ = eof;
 
# Colors
my %colors = (
  "black" => 0,
  "white" => 255,
  "red" => 224,
  "green" => 28,
  "blue" => 3,
  "yellow" => 252,
  "magenta" => 227,
  "cyan" => 31
);
 
# Defaults
my $baud = 9600;
my $sleep = 1e9/$baud;
 
my $opt_help = false;
my $opt_clear = false;
my $opt_fg=$colors{'black'};
my $opt_bg=$colors{'white'};
my $opt_line="";
my $opt_box="";
my $opt_circle="";
my $opt_cursor="";
my $opt_light="";
my $opt_port = "/dev/tty.usbserial-A600d25K";
 
my $result;
 
GetOptions(
  "help|?" => \$opt_help,
  "clear" => \$opt_clear,
  "fg=s" => \$opt_fg,
  "bg=s" => \$opt_bg,
  "port=s" => \$opt_port,
  "line=s" => \$opt_line,
  "box=s" => \$opt_box,
  "circle=s" => \$opt_circle,
  "cursor=s" => \$opt_cursor,
  "light=s" => \$opt_light
) or pod2usage(-verbose => 2);
 
pod2usage(-verbose => 2) if $opt_help;
 
# Attempt to open port and initialize VAC
my $serialPort = Device::SerialPort->new($opt_port) 
  or die "Cannot open port: $!.\n";
# Initialise VAC
send_serial("\r");
$serialPort->close();
usleep($sleep);
 
# Reopen as brand new and commit
$serialPort = Device::SerialPort->new($opt_port)
  or die "Cannot open port: $!.\n";
$serialPort->baudrate($baud);
$serialPort->databits(8);
$serialPort->parity("none");
$serialPort->stopbits(1);
$serialPort->handshake("rts");
$serialPort->lookclear;
$serialPort->write_settings;
send_serial("\r");
usleep($sleep);
 
# Sending subroutine
sub send_serial {
  my $data = shift;
  $result = $serialPort->write($data) 
    or die "Unable to write to port: $!.\n";
  usleep($result * $sleep);
}
 
#send_serial("\033{127 0 0 127L");
#goto END;
 
# Validate coordinates
# This may seem like Voodoo but it is not.
## The reasoning is as follows:
### x,y vary 0..130 for a box
### x,y vary 0..127 for a line
sub areValidCoordinates {
  my @param = @_;
  my $type = pop @param;
  switch($type) {
    case "C" {
      if(scalar @param == 3) {
        return true;
      }
    }
    case "B" { }
    case "L" {
      if(scalar @param != 4) {
        return false;
      }
      # Per spec, the coordinates may not be equal.
      if($param[0] == $param[2] && $param[1] == $param[3]) {
        return false;
      }
    }
  }
  for my $idx (0 .. $#param) {
    switch($idx) {
      case 0 { }  # y0
      case 2 {    # y1
        switch($type) {
          case "L" { 
            if($param[$idx] < 0 || $param[$idx] > 127) {
              return false;
            }
            last;
          }
          case "B" { goto CHECK_PIXELS; }
        }
        next;
      }
      case 1 { }  # x0
      case 3 {    # x1
        switch($type) {
          case "L" { 
            if($param[$idx] < 0 || $param[$idx] > 127) {
              return false;
            }
            last;
          }
          case "B" {
CHECK_PIXELS:
            if($param[$idx] < 0 || $param[$idx] > 130) {
              return false;
            }
            last;
          }
        }
        next;
      }
    }
  }
  return true;
}
 
sub swapxy {
  my @coordinate_pairs = @_;
 
  ## This is not needed but we like to work
  ## by convention (x,y) instead of (y,x) and
  ## with the +y axis pointing upward.
 
  # x0r swap,
  # so that y0 <-> x0:
  $coordinate_pairs[0] ^= $coordinate_pairs[1];
  $coordinate_pairs[1] ^= $coordinate_pairs[0];
  $coordinate_pairs[0] ^= $coordinate_pairs[1];
 
  # and y1 <-> x1, only if we have a complete set.
  if(scalar @coordinate_pairs != 4) {
    goto SWAP_RETURN;
  }
 
  $coordinate_pairs[2] ^= $coordinate_pairs[3];
  $coordinate_pairs[3] ^= $coordinate_pairs[2];
  $coordinate_pairs[2] ^= $coordinate_pairs[3];
 
  # Note: this function is a binary relation on
  # pairs of coodinates {(x0,y0); (x1,y1)} that
  # will swap x0 with y0 and x1 with y1. The
  # operations are inversible, such that 
  # we have swapxy(S) = swapxy(swapxy(S)).
 
SWAP_RETURN:
  return @coordinate_pairs;
}
 
# Clear screen
if($opt_clear) {
	send_serial("\033[3J");
	goto END;
}
 
# Set colors
# Set foreground
if($opt_fg ne $colors{'black'}) {
  if(exists $colors{$opt_fg}) {
    send_serial("\033[".$colors{$opt_fg}."f");
    goto OUT_OPT_FG;
  }
  if($opt_fg <= 255 && $opt_fg >= 0) {
    send_serial("\033[".$opt_fg."f");
    goto OUT_OPT_FG;
  }
} OUT_OPT_FG:
 
# Set background
if($opt_bg ne $colors{'white'}) {
  if(exists $colors{$opt_bg}) {
    send_serial("\033[".$colors{$opt_bg}."b");
    goto OUT_OPT_BG;
  }
  if($opt_bg <= 255 && $opt_bg >= 0) {
    send_serial("\033[".$opt_bg."b");
    goto OUT_OPT_BG;
  }  
} OUT_OPT_BG:       
 
# Toggle Cursor
if($opt_cursor) {
  switch($opt_cursor) {
    case "on" {
      send_serial("\033[?25h");
      last;
    }
    case "off" {
      send_serial("\033[?25I");
    }
  }
  goto END;
}
 
# Toggle Backlight
if($opt_light) {
  switch($opt_light) {
    case "on" {
      send_serial("\033[?26h");
      last;
    }
    case "off" {
      send_serial("\033[?26I");
    }
  }
  goto END;
}
 
# Draw line
if($opt_line) {
  my @line_co=split(/,/,$opt_line);
  # we invert the y axis so +y points upward.
  $line_co[1] = abs(127 - $line_co[1]);
  $line_co[3] = abs(127 - $line_co[3]);
  # swap (y0,x0) -> (x0,y0)
  # and (y1,x1) -> (x1,y1)
  @line_co=&swapxy(@line_co);
  if(&areValidCoordinates(@line_co, "L")) {
    send_serial("\033{"
      .$line_co[0]." "
      .$line_co[1]." "
      .$line_co[2]." "
      .$line_co[3]."L");
  }
  goto END;
}
 
# Draw box
if($opt_box) {
  my @box_co=split(/,/, $opt_box);
  # we invert the y axis so +y points upward.
  $box_co[1] = abs(130 - $box_co[1]);
  $box_co[3] = abs(130 - $box_co[3]);
  # swap (y0,x0) -> (x0,y0)
  # and (y1,x1) -> (x1,y1)
  @box_co=&swapxy(@box_co);
  # By spec, x0 < x1 and y0 < y1, 
  # so we swap x0,x1 and y0,y1
  # in case x0 > x1 and y0 > y1.
  if($box_co[0] > $box_co[2]) {
    ($box_co[0],$box_co[2])=&swapxy($box_co[0], $box_co[2]);
  }
  if($box_co[1] > $box_co[3]) {
    ($box_co[1],$box_co[3])=&swapxy($box_co[1], $box_co[3]);
  }
  if(&areValidCoordinates(@box_co)) {
    send_serial("\033{"
      .$box_co[0]." "
      .$box_co[1]." "
      .$box_co[2]." "
      .$box_co[3]."F");
  }
  goto END;
}
 
# Draw circle
if($opt_circle) {
  my @circle_co=&swapxy(split(/,/, $opt_circle));
  if(@_=&areValidCoordinates(@circle_co)) {
    send_serial("\033{ "
      .$circle_co[0]." "
      .$circle_co[1]." "
      .$circle_co[2]."C");
  }
  goto END;
}
 
# Process input
# Grab STDIN
my $input;
while(<>) {
  $input.=$_;
}
 
# Split lines to re-encode for VAC
my @data=split(/\n/,$input);
my $vacLines;
foreach(@data) {
  $vacLines.=$_."\n\r";
}
 
# Split each character for VAC
@data=split(//,$vacLines);
foreach(@data) {
  send_serial($_);
}
 
END:
# Close port.
$serialPort->close();
exit 0;
 
__END__
=head1 NAME
 
bv4141ctl - Control a BV4141 display.
 
=head1 SYNOPSIS
 
echo "Hello world!" | bv4141ctl [options]
 
or
 
bv4141ctl [options]
 
=head1 OPTIONS
 
=over 8
 
=item B<--help>	
 
=item B<--?>			   
 
documentation
 
=item B<--clear>               
clear screen
 
=item B<--fg=<STRING|0..255>>  
 
sets foreground color and may be one of B<black>, B<white>, B<red>, B<green>, B<blue>, B<yellow>, B<magenta>, B<cyan> or an integer where B<0> represents black and B<255> white.
 
=item B<--bg=<STRING|0..255>>  
 
sets background color and may be one of B<black>, B<white>, B<red>, B<green>, B<blue>, B<yellow>, B<magenta>, B<cyan> or an integer where B<0> represents black and B<255> white.
 
=item B<--port=<STRING>>    
 
path to FTDI device. Using VDR, this defaults to B</dev/tty.usbserial-A600d25K> and needs to be changed accordingly.
 
=item B<--line=<x1,y1,x2,y2>>  
 
draws a line starting at point B<(x1,y1)> and ending with the point B<(x2,y2)> where B<x1,x2>=B<0..130> and B<y1,y2>=B<0..130>.
 
=item B<--box=<x1,y1,x2,y2>>   
 
draws a filled box between point B<(x1,y1)> and ending with the point B<(x2,y2)> where B<x1,x2>=B<0..130> and B<y1,y2>=B<0..130>.
 
=item B<--circle=<x1,y1,d>>    
 
draw a circle centered in the point B<(x1,y1)> of diameter B<d> if B<x1>>B<130> and B<y1>>B<130> then off-screen rendering occurs.
 
=item B<--cursor=<on|off>>     
 
hide (B<on>) or show the cursor (B<off>).
 
=item B<--light=<on|off>>   
 
toggle the backlight B<on> or B<off>.
 
=back
 
=head1 DESCRIPTION
 
B<bv4141ctl> reads STDIN for input and then pipes it to the display controller, by interpreting user-supplies input and translating them to the escape codes used by the display.
 
=cut

Man Page

BV4141CTL(1)             User Contributed Perl Documentation            BV4141CTL(1)



NAME
       bv4141ctl − Control a BV4141 display.

SYNOPSIS
       echo "Hello world!" | bv4141ctl [options]

       or

       bv4141ctl [options]

OPTIONS
       −−help
       −−?     documentation

       −−clear clear screen
       −−fg=<STRING|0..255>
               sets foreground color and may be one of black, white, red,
               green, blue, yellow, magenta, cyan or an integer where 0
               represents black and 255 white.

       −−bg=<STRING|0..255>
               sets background color and may be one of black, white, red,
               green, blue, yellow, magenta, cyan or an integer where 0
               represents black and 255 white.

       −−port=<STRING>
               path to FTDI device. Using VDR, this defaults to
               /dev/tty.usbserial−A600d25K and needs to be changed
               accordingly.

       −−line=<x1,y1,x2,y2>
               draws a line starting at point (x1,y1) and ending with the
               point (x2,y2) where x1,x2=0..130 and y1,y2=0..130.

       −−box=<x1,y1,x2,y2>
               draws a filled box between point (x1,y1) and ending with the
               point (x2,y2) where x1,x2=0..130 and y1,y2=0..130.

       −−circle=<x1,y1,d>
               draw a circle centered in the point (x1,y1) of diameter d if
               x1>130 and y1>130 then off‐screen rendering occurs.

       −−cursor=<on|off>
               hide (on) or show the cursor (off).

       −−light=<on|off>
               toggle the backlight on or off.

DESCRIPTION
       bv4141ctl reads STDIN for input and then pipes it to the display
       controller, by interpreting user‐supplies input and translating them to
       the escape codes used by the display.



perl v5.10.0                      2012‐08‐14                         BV4141CTL(1)

hardware/bv4141/display_controller.txt · Last modified: 2022/04/19 08:28 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.