Marco Zonca
Published © GPL3+

Enhanced & portable TTL Serial Monitor

Usable both on-the-field and on-the-desk, with special characteristics. Its scrolling display can be paused and cleared, selectable speed.

IntermediateFull instructions provided821
Enhanced & portable TTL Serial Monitor

Things used in this project

Hardware components

Arduino Pro Mini 328 - 3.3V/8MHz
SparkFun Arduino Pro Mini 328 - 3.3V/8MHz
×1
SparkFun FTDI Basic Breakout - 3.3V
SparkFun FTDI Basic Breakout - 3.3V
×1

Software apps and online services

Arduino IDE
Arduino IDE
PCBWay online Services for PCB and 3D Printing (optional)

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

3D printing Box

Sketchfab still processing.

3D printing Box, front panel

Sketchfab still processing.

Schematics

Schematic diagram (Fritzing)

PCB Gerber file

PCB top layer

PCB bottom layer

PCB top silk

PCB bottom silk

Code

Arduino code (sketch)

Arduino
/*
 * ard-serialmonitor - by Marco Zonca 03/2025
 * 
 * It works as external serial monitor at adjustable speed (baud rate). Components are:
 * Arduino Pro Mini 8Mhz 3.3V, button to change speed, button to pause, button to clear screen, 
 * ILI9341 2.4" TFT_LCD 240x320 RGB display in portrait, passive buzzer, switch, TP5400 module,
 * transistor BC337, pause red LED, CT3650 Li-Ion 1450mA/h rechargeable battery, some other components;
 * You should use two selected 1% 10k ohm resistors to compose the voltage divider checking the battery,
 * just to have an accurate value out from it;
 * 
 * Power supply is given by a rechargeable battery with a TP5400 module that both recharge
 * the battery and step-up to 5.0Vcc to power the MPU at RAW pin, then the Arduino regulator provides
 * the 3.3Vcc necessary to the circuit; the regulator limit is 150mA of current, but actually
 * the whole circuit needs just around 65mA; to recharge let connect a micro-USB cable; the
 * USB-A female is for powering an ipotethic additional equipment with 5Vcc needs, i.e. the circuit 
 * to monitor;
 * 
 * the display uses a modified library for scrolling text; you may connect the serial data cable of the
 * equipment to monitor in the range of 3-5Vcc TTL;
 * 
 * Additional service: on the equipment to monitor you may add some control characters in your debug
 * code messages to enrich/simplify the debugging: so you may change text colour on the display and
 * play a bip when you like just adding one of these control-codes when necessary:
 * 
 * CTRL-A 01 (start YELLOW)
 * CTRL-B 02 (start GREEN)
 * CTRL-C 03 (start RED)
 * CTRL-D 04 (start CYAN)
 * CTRL-E 05 (start WHITE, recover the normal colour)
 * CTRL-G 07 (play a bip)
 * 
 * for example: 
 * --------------
 * char ccBEL=07;
 * Serial.print(ccBEL);  // you will have a "bip" sound on the serial monitor
 * 
 * char ccRED=03;
 * char ccWHITE=05;
 * Serial.print(ccRED);
 * Serial.println("Warning: out of range!");  // you will have a red colour message on the external serial monitor
 * Serial.print(ccWHITE);  // recover the normal white colour
 * 
 * default speed is 9600 with 8 bit, NO parity, 1 stop (SERIAL_8N1)
 * with speed button you may change to 300, 1200, 2400, 4800, 9600, 19200, 38400 baud;
 * I tested a continuous RX works up to 4800; higher speed could make the buffer full during
 * continuous receiving because display is slower then receiving data; occasional RX data as per 
 * usual serial monitor purposes of debugging works fine even with higher speeds;
 * 
 * To avoid interferences during uploading the sketch, I used 2 new pins for serial RX/TX by the way of
 * SoftwareSerial library, so the communication pins are 4 and 5;
 * 
 * The SoftwareSerial library buffer size is 64 bytes; I modified SoftwareSerial.h 
 * and SoftwareSerial.cpp to 512 bytes, so now is more difficult to make the buffer full;
 * For a more confortable use you should modify the library too as per instructions and pictures;
 * this sketch use around 1450bytes of memory plus the 512bytes of the enlarged buffer it should stay
 * below the 2kbytes limit of Arduino Mini-Pro.
 * 
 * Are displayed only printable characters in the ASCII range 32-126, so no control codes
 * and no extended ASCII are show (all ignored);
 * 
 * In Pause mode the display stops to show more data, so the receiving data is ignored (lost);
 * 
 * Looking at TP5400 module datasheet they say it will charge at 900mA current because the programming
 * resistor installed on pin 3 of the chip is a 1.2k Ohm; the Li-Ion battery I used for this project has
 * 1450mA/h so in around 1.5h is charged; I think the charging current in this case is adeguate
 * even if I usually like a less charging current comparing to the battery capacity; just to warn you
 * to avoid to install a smaller capacity battery, or you should change the resistor to a bigger
 * value (10k -> 130mA, 5.1k -> 245mA, 2k 560mA, 1.5k -> 740mA, 1.1k -> 1000mA); i.e. for small 350mA/h  
 * battery I suggest to change pin 3 programming resistor to a value between 5.1k and 10k Ohm.
 * 
 * Every 30" the software checks battery voltage: if less than 3.4v it generates a sound as warning to provide
 * to recharge the battery soon; full battery should last around 15h.
 * 
 * It follows notes by the author of the Adafruit modified libraries in this sketch;
 * 
 */


/*************************************************************
  This sketch implements a simple serial receive terminal
  program for monitoring serial debug messages from another
  board.
  
  Connect GND to target board GND
  Connect RX line to TX line of target board
  Make sure the target and terminal have the same baud rate
  and serial stettings!

  The sketch works with the ILI9341 TFT 240x320 display and
  the called up libraries.
  
  The sketch uses the hardware scrolling feature of the
  display. Modification of this sketch may lead to problems
  unless the ILI9341 data sheet has been understood!

  Written by Alan Senior 15th February 2015
  
  BSD license applies, all text above must be included in any
  redistribution
 *************************************************************/
 

#include <Adafruit_GFX_AS.h>     // Core graphics library customized by Alan Senior
#include <Adafruit_ILI9341_AS.h> // Hardware-specific library customized by Alan Senior
#include <SPI.h>
#include <NewTone.h>
#include <SoftwareSerial.h>

#define _sclk 13  // These are the pins used for the UNO, we must use hardware SPI
#define _miso 12 // Not used
#define _mosi 11
#define _cs 10 // Not connected, Not used
#define _dc  9
#define _rst 8

#define myTX 5  // TX output pin of mySerial (to RX pin of external equipment) - not used at the moment
#define myRX 4  // RX input pin of mySerial (to TX pin of external equipment)

const int vBatPin = 15;
const int ledPausePin = 14;
const int buzzerPin = 7;
const int butSpeedPin = 6;
const int butClearPin = 3;
const int butPausePin = 2;
char speedChoice=0;
char banner[32]="";
boolean isPause=false;
int n1 = 0;
float n2 = 0;
float v = 0;
unsigned long prevMillisService = 0;

SoftwareSerial mySerial(myRX, myTX);
Adafruit_ILI9341_AS tft = Adafruit_ILI9341_AS(_cs, _dc, _rst);

// The scrolling area must be a integral multiple of TEXT_HEIGHT
#define TEXT_HEIGHT 16 // Height of text to be printed and scrolled
#define BOT_FIXED_AREA 0 // Number of lines in bottom fixed area (lines counted from bottom of screen)
#define TOP_FIXED_AREA 16 // Number of lines in top fixed area (lines counted from top of screen)

// The initial y coordinate of the top of the scrolling area
uint16_t yStart = TOP_FIXED_AREA;
// yArea must be a integral multiple of TEXT_HEIGHT
uint16_t yArea = 320-TOP_FIXED_AREA-BOT_FIXED_AREA;
// The initial y coordinate of the top of the bottom text line
uint16_t yDraw = 320 - BOT_FIXED_AREA - TEXT_HEIGHT;

// Keep track of the drawing x coordinate
uint16_t xPos = 0;
// For the byte we read from the serial port
byte data = 0;

// We have to blank the top line each time the display is scrolled, but this takes up to 13 milliseconds
// for a full width line, meanwhile the serial buffer may be filling... and overflowing
// We can speed up scrolling of short text lines by just blanking the character we drew
int blank[19]; // We keep all the strings pixel lengths to optimise the speed of the top line blanking

void setup() {
  pinMode(ledPausePin, OUTPUT);
  pinMode(buzzerPin, OUTPUT);
  pinMode(butSpeedPin, INPUT_PULLUP);
  pinMode(butClearPin, INPUT_PULLUP);
  pinMode(butPausePin, INPUT_PULLUP);
  pinMode(vBatPin, INPUT);

  // Setup the TFT display
  tft.init();
  tft.setRotation(0);
  tft.fillScreen(ILI9341_BLACK);
  // Setup scroll area
  setupScrollArea(TOP_FIXED_AREA, BOT_FIXED_AREA);
  
  // Setup baud rate and draw top banner
  mySerial.begin(9600);
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLUE);
  tft.fillRect(0,0,240,16, ILI9341_BLUE);
  strcpy(banner," Serial Monitor - 9600 baud ");
  tft.drawCentreString(banner,120,0,2);

  // Change colour for scrolling zone
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);

  // Zero the array
  for (byte i = 0; i<18; i++) blank[i]=0;

  // initial show
  digitalWrite(ledPausePin,HIGH);
  NewTone(buzzerPin,1000,500);
  delay(500);
  digitalWrite(ledPausePin,LOW);
  prevMillisService=millis();
}//setup()


void loop(void) {
  if (digitalRead(butSpeedPin)==LOW) { changeSpeed(); }
  if (digitalRead(butClearPin)==LOW) { clearScreen(); }
  if (digitalRead(butPausePin)==LOW) { playPause(); }
  if (millis() > (prevMillisService+30000)) { checkVIn(); }
  while (mySerial.available()) {
    data = mySerial.read();
    if (isPause==false) {
      // Change colour when receiving CTRL characters as per this software characteristic
      if (data == 01) {tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);}  // CTRL-A == 01 == start YELLOW colour
      if (data == 02) {tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);}  //  CTRL-B == 02 == start GREEN  colour
      if (data == 03) {tft.setTextColor(ILI9341_RED, ILI9341_BLACK);}  //    CTRL-C == 03 == start RED    colour
      if (data == 04) {tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);}  //   CTRL-D == 04 == start CYAN   colour
      if (data == 05) {tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);}  //  CTRL-E == 05 == start WHITE  colour
      if (data == 07) {NewTone(buzzerPin,3000,100);}  //                     CTRL-G == 07 == play a bip
      if (data == 13 || xPos>231) {  // 13 == "\r" == CR == 0d == CTRL-M
        xPos = 0;
        yDraw = scroll_line(); // It takes about 13ms to scroll 16 pixel lines
      }
      if (data >= 32 && data <= 126) {  // only printable characters, no control codes, no extended ASCII, (all ignored)
        xPos += tft.drawChar(data,xPos,yDraw,2);
        blank[(18+(yStart-TOP_FIXED_AREA)/TEXT_HEIGHT)%19]=xPos; // Keep a record of line lengths
      }
    }//isPause
  }//while
}//loop()


void clearScreen() {
  NewTone(buzzerPin,200,500);
  tft.fillRect(0,17,240,320, ILI9341_BLACK);
  xPos=0;
}//clearScreen()


void playPause() {
  if (isPause == false) {
    isPause=true;
    digitalWrite(ledPausePin,HIGH);
    NewTone(buzzerPin,500,250);
  }else{
    isPause=false;
    digitalWrite(ledPausePin,LOW);
    NewTone(buzzerPin,1000,500);  
  };
  delay(500);  
}//playPause()


void changeSpeed() {
  NewTone(buzzerPin,1000,250);
  delay(500);  
  mySerial.end();
  switch (speedChoice) {
    case 0:
      mySerial.begin(19200);
      speedChoice=1;
      tft.setTextColor(ILI9341_WHITE, ILI9341_MAGENTA);
      tft.fillRect(0,0,240,16, ILI9341_MAGENTA);
      strcpy(banner," Serial Monitor - 19200 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
    case 1:
      mySerial.begin(38400);
      speedChoice=2;
      tft.setTextColor(ILI9341_BLACK, ILI9341_CYAN);
      tft.fillRect(0,0,240,16, ILI9341_CYAN);
      strcpy(banner," Serial Monitor - 38400 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
    case 2:
      mySerial.begin(300);
      speedChoice=3;
      tft.setTextColor(ILI9341_BLACK, ILI9341_YELLOW);
      tft.fillRect(0,0,240,16, ILI9341_YELLOW);
      strcpy(banner," Serial Monitor - 300 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
    case 3:
      mySerial.begin(1200);
      speedChoice=4;
      tft.setTextColor(ILI9341_BLACK, ILI9341_WHITE);
      tft.fillRect(0,0,240,16, ILI9341_WHITE);
      strcpy(banner," Serial Monitor - 1200 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
    case 4:
      mySerial.begin(2400);
      speedChoice=5;
      tft.setTextColor(ILI9341_WHITE, ILI9341_RED);
      tft.fillRect(0,0,240,16, ILI9341_RED);
      strcpy(banner," Serial Monitor - 2400 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
    case 5:
      mySerial.begin(4800);
      speedChoice=6;
      tft.setTextColor(ILI9341_BLACK, ILI9341_GREEN);
      tft.fillRect(0,0,240,16, ILI9341_GREEN);
      strcpy(banner," Serial Monitor - 4800 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
    case 6:
      mySerial.begin(9600);
      speedChoice=0;
      tft.setTextColor(ILI9341_WHITE, ILI9341_BLUE);
      tft.fillRect(0,0,240,16, ILI9341_BLUE);
      strcpy(banner," Serial Monitor - 9600 baud ");
      tft.drawCentreString(banner,120,0,2);
      break;
  }
}//changeSpeed()


void checkVIn() {
  n1 = analogRead(vBatPin);
  n2=(((3.30 * n1 * 2) / 1023.00));  // (3.3 / voltage divider) * 2 = 3.3v ref. voltage
  v=(n2 + ((n2 * 0.0) /100));  // arbitrary correction (not active if 0.0%)
  if (v < 3.4) {
    NewTone (buzzerPin,1000,45);
    delay(50);
    NewTone (buzzerPin,2900,45);
    delay(50);
    NewTone (buzzerPin,1000,45);
    delay(50);
    NewTone (buzzerPin,2900,45);
    delay(50);
    NewTone (buzzerPin,1000,45);
    delay(50);
    NewTone (buzzerPin,2900,45);
    delay(100);
  }
  prevMillisService=millis();
}//checkVIn()


// ##############################################################################################
// Call this function to scroll the display one text line
// ##############################################################################################
int scroll_line() {
  int yTemp = yStart; // Store the old yStart, this is where we draw the next line
  // Use the record of line lengths to optimise the rectangle size we need to erase the top line
  tft.fillRect(0,yStart,blank[(yStart-TOP_FIXED_AREA)/TEXT_HEIGHT],TEXT_HEIGHT, ILI9341_BLACK);

  // Change the top of the scroll area
  yStart+=TEXT_HEIGHT;
  // The value must wrap around as the screen memory is a circular buffer
  if (yStart >= 320 - BOT_FIXED_AREA) yStart = TOP_FIXED_AREA + (yStart - 320 + BOT_FIXED_AREA);
  // Now we can scroll the display
  scrollAddress(yStart);
  return  yTemp;
}

// ##############################################################################################
// Setup a portion of the screen for vertical scrolling
// ##############################################################################################
// We are using a hardware feature of the display, so we can only scroll in portrait orientation
void setupScrollArea(uint16_t TFA, uint16_t BFA) {
  tft.writecommand(ILI9341_VSCRDEF); // Vertical scroll definition
  tft.writedata(TFA >> 8);
  tft.writedata(TFA);
  tft.writedata((320-TFA-BFA)>>8);
  tft.writedata(320-TFA-BFA);
  tft.writedata(BFA >> 8);
  tft.writedata(BFA);
}

// ##############################################################################################
// Setup the vertical scrolling start address
// ##############################################################################################
void scrollAddress(uint16_t VSP) {
  tft.writecommand(ILI9341_VSCRSADD); // Vertical scrolling start address
  tft.writedata(VSP>>8);
  tft.writedata(VSP);
}

Credits

Marco Zonca
19 projects • 52 followers
"From an early age I learned to not use pointers"

Comments