4D Systems Terminal

From EmbeddedSand

Jump to: navigation, search
4D Systems display running test program

4D Systems are an Australian company that make a range of intelligent displays. These displays include microcontrollers which can be programmed using a language called 4DGL to create graphical displays complete with touch sensitive buttons, images and animations. The display is attached to the Arduino using a serial port, but because it can run sophisticated programs the bandwidth of the serial port need not be a limitation when creating elegant user interfaces.

This project is a small terminal program that runs on a uLCD-28PT-GFX2 display displaying text sent to it. As text fills the display it scrolls off the top. The display seemed to have trouble buffering serial data while rendering text to implement scrolling so two modes are supported: line mode and character mode.

In character mode, characters are written to the display as they are received. If data arrives too quickly characters can be lost. Line mode implements handshaking. Characters are received and buffered until a carriage return is received. The buffered characters are drawn and when the display is ready to receive more text it sends a '>' character back to the Arduino.

To switch to character mode, send the character 0x11. Switch to line mode by sending 0x12 and clear the screen by sending 0x0c. The character and line modes are implemented in two separate functions: you can strip out the bit you don't need to save memory.

The source code for the display and a test program that runs on the Arduino is shown below. The test program assumes the display is connected to serial port 3. You can also download the complete source code for the Arduino terminal display here.

Download MegunoLink

Version: 1.0.6
Updated: 3 October 2012
Size: 1.25 KB
Update History

If you find MegunoLink or this tutorial useful, please consider a donation to help support future development

Arduino Test Program

This program runs on the Arduino and sends a simple text string to the display. It switches between character mode and line mode and clears the display every 50 messages.

void setup()
{
  Serial3.begin(9600);
}
 
int g_nMessageCounter = 0;
bool g_bLineMode = false;
 
void loop()
{
  // Switch between line mode and character mode every 100 messages
  if (g_nMessageCounter == 0)
  {
    g_bLineMode = !g_bLineMode;
    if (g_bLineMode)
      Serial3.write(0x12); // switch to line mode.
    else
      Serial3.write(0x11); // Switch to character mode. 
 
    delay(1000); // give the display a chance to adapt. 
  }
 
  // Every 50 messages, clear the screen.
  if (g_nMessageCounter == 50)
  {
    Serial3.write(0x0c);
    delay(500); // give the display a chance to clear.
  }
 
  // Every 100 messages, start again.
  g_nMessageCounter = (g_nMessageCounter + 1) % 100;
 
  Serial3.write("Hello World!\n");
  if (g_bLineMode)
  {
    // Continue when we get the handshake character.
    while (!Serial3.available())
      ; // wait for handshake.
 
  } else
    delay(200);
}

4D Systems Display Program

This program runs on the uLCD-28PT_GFX2 display, though it should be compatible with any Picaso display.

#platform "uLCD-28PT_GFX2"
 
/* Simple serial terminal.
 * Sends data received over the COM port
 * to the display. */
 
#inherit "4DGL_16bitColours.fnc"
 
/* Setup a serial buffer for characters received. */
#constant cBufferSize 60 // Size of buffer in words.
var SerialBuffer[cBufferSize];
 
/*  Two modes are supported: line mode and character mode. In
    character mode, each character is written to the screen as
    it arrives. In line mode, we wait until a complete line is
    received until writing to the screen. In line mode we send
    a character back to the transmitter when we are ready to
    receive the next line. In either mode a chReady character
    is sent when we are ready to receive text.
 
    Text can be drawn in a different colour for each mode.
    Send 0x11 to switch to character mode. Send 0x12 to switch
    to line mode. Switching to a new mode also clears the display. */
var bLineMode; // 1=>line mode; 0=>character mode.
#constant clrLineMode BLUE
#constant clrCharMode GREEN
#constant chActivateLineMode 0x12
#constant chActivateCharacterMode 0x11
#constant chClearScreen 0x0c
#constant chEOL 0x0a
#constant chReady '>'
 
/*  Setup a screen buffer for text received. This lets us
    redraw the screen to implement scrolling. */
var pTextBuffer;   // Word pointer to text buffer.
var pchTextBuffer; // Byte pointer to text buffer.
var CursorX, CursorY;  // Location for next character on screen (and in buffer)
 
/* Display properties. */
var nCharHeight, nCharWidth; // [pixels]
var nXPixels, nYPixels; // [pixels]
var nRows, nColumns; // [chars]
 
 
func main()
 
    bLineMode := 1;
 
    repeat
 
        CursorY := InitializeDisplay();
 
        // Keep track of where the next character will be printed.
        CursorX := 0;
        CursorY += 1;
 
        UpdateDisplayProperties();
 
        // Allocate some memory to store the text we receive.
        // This lets us implement a reasonably quick scroll when
        // writting to the end of the screen.
        pTextBuffer := mem_Alloc(nRows * nColumns);
        mem_Set(pTextBuffer, 0, nRows * nColumns);
        pchTextBuffer := str_Ptr(pTextBuffer);
 
        // Setup COM port. Circular serial buffer, with no sync character
        setbaud(BAUD_9600);
        com_Init(SerialBuffer, cBufferSize * 2, 0);
        serout(chReady); // Send a character to inidicate we are ready to go.
 
        if (bLineMode == 1)
            LineModeTerminal();
        else
            CharacterModeTerminal();
        endif
 
    forever
endfunc
 
func CharacterModeTerminal()
/*  Write characters to the screen until an error occurs or we
    receive a signal to switch to line mode. */
    var chReceived;
    var iRow, iColumn;
    var pchSource;
 
    txt_Set(TEXT_COLOUR, clrCharMode);
 
    repeat
        chReceived := serin();
        if (chReceived > 0)
            // Display character; update cursor position.
            if (chReceived == chEOL)
                CursorX := 0;
                ++CursorY;
            else if (chReceived == chClearScreen)
                gfx_Cls();
                CursorX := 0;
                CursorY := 0;
                mem_Set(pTextBuffer, 0, nRows * nColumns);
            else if (chReceived == chActivateLineMode)
                bLineMode := 1;
            else if (chReceived != 0x0d)
                txt_MoveCursor(CursorY, CursorX);
                putch(chReceived);
 
                // Store character in the buffer so we can implement scrolling later.
                str_PutByte(pchTextBuffer + CursorY * nColumns + CursorX, chReceived);
 
                ++CursorX;
            endif
 
            // Implement column wrapping
            if (CursorX >= nColumns)
                CursorX := 0;
                ++CursorY;
            endif
 
            // Implement end of screen wrap. The screen is
            // scrolled up one line. ScreenCopyPaste should
            // do this, but doesn't appear to work. Pixel copy
            // is very slow. So we re-write from the character buffer.
 
            if (CursorY >= nRows)
                CursorY := nRows - 1;
                // Scroll up & make the last row an empty string.
                str_ByteMove(pchTextBuffer + nColumns, pchTextBuffer, (nRows - 1) * nColumns);
                for (iColumn:= 0; iColumn < nColumns; ++iColumn)
                    pchSource := pchTextBuffer + (nRows - 1) * nColumns + iColumn;
                    str_PutByte(pchSource, 0);
                next
 
                // Redraw.
                for (iRow := 0; iRow < nRows; ++iRow)
                    gfx_RectangleFilled(0, iRow * nCharHeight, nXPixels, (iRow + 1) * nCharHeight, 0);
                    txt_MoveCursor(iRow, 0);
                    pchSource := pchTextBuffer + iRow * nColumns;
                    for(iColumn := 0; iColumn < nColumns; ++iColumn, ++pchSource)
                        chReceived := str_GetByte(pchSource);
                        if (chReceived == 0) break;
                        putch(chReceived);
                    next
                next
            endif
        endif
    until (com_Error() || bLineMode == 1);
endfunc
 
func LineModeTerminal()
/*  Write lines to the screen until an error occurs, or we switch
    to character mode. */
    var chReceived;
    var iRow, iColumn;
    var pchSource;
    var bDrawLine := 0;
    var nDrawLine_Start;
 
    nDrawLine_Start := CursorY;
 
 
    txt_Set(TEXT_COLOUR, clrLineMode);
    repeat
        chReceived := serin();
        if (chReceived > 0)
            if (chReceived == chActivateCharacterMode)
                bLineMode := 0;
            else if (chReceived == chEOL)
                CursorX := 0;
                ++CursorY;
                bDrawLine := 1;
            else if (chReceived == chClearScreen)
                gfx_Cls();
                CursorX := 0;
                CursorY := 0;
                nDrawLine_Start := 0;
                mem_Set(pTextBuffer, 0, nRows * nColumns);
            else if (chReceived != 0x0d && chReceived != 0x0a)
                // Store character in the buffer so we can draw it when an eol arrives.
                str_PutByte(pchTextBuffer + CursorY * nColumns + CursorX, chReceived);
                ++CursorX;
            endif
 
            // Implement column wrapping
            if (CursorX >= nColumns)
                CursorX := 0;
                ++CursorY;
            endif
 
            // Implement end of screen wrap. The screen is
            // scrolled up one line. ScreenCopyPaste should
            // do this, but doesn't appear to work. Pixel copy
            // is very slow. So we re-write from the character buffer.
            if (CursorY >= nRows)
                CursorY := nRows - 1;
                // Scroll up & make the last row an empty string.
                str_ByteMove(pchTextBuffer + nColumns, pchTextBuffer, (nRows - 1) * nColumns);
                pchSource := pchTextBuffer + (nRows - 1) * nColumns;
                for (iColumn:= 0; iColumn < nColumns; ++iColumn)
                    str_PutByte(pchSource++, 0);
                next
                nDrawLine_Start := 0;
            endif
 
            if (bDrawLine == 1)
                // Redraw.
                for (iRow := nDrawLine_Start; iRow <= CursorY; ++iRow)
                    gfx_RectangleFilled(0, iRow * nCharHeight, nXPixels, (iRow + 1) * nCharHeight, 0);
                    txt_MoveCursor(iRow, 0);
                    pchSource := pchTextBuffer + iRow * nColumns;
                    for(iColumn := 0; iColumn < nColumns; ++iColumn, ++pchSource)
                        chReceived := str_GetByte(pchSource);
                        if (chReceived == 0) break;
                        putch(chReceived);
                    next
                next
                nDrawLine_Start := CursorY;
                bDrawLine := 0;
 
                serout(chReady); // Ready for next line.
            endif
 
        endif
 
    until (com_Error() || bLineMode == 0);
 
endfunc
 
func UpdateDisplayProperties()
/* Gather the vital statistics about the display. */
    nXPixels := gfx_Get(0);
    nYPixels := gfx_Get(1);
    nCharWidth := charwidth(' ');
    nCharHeight := charheight(' ');
    nRows := nYPixels / nCharHeight;
    nColumns := nXPixels / nCharWidth;
endfunc
 
func InitializeDisplay()
/*  Setup display modes and print a welcome message. Returns
    the number of lines used for the welcome message. */
 
    gfx_Set(OUTLINE_COLOUR, 0);
    gfx_Cls();
    txt_Set(FONT_ID, 0);
    txt_Wrap(0); // Turn off wrapping. We do it ourselves.
 
    txt_MoveCursor(0,0);
    putstr("Serial Terminal (9600 baud).\n");
    putstr("x0C=>clear; x12->line; x11 char\n");
    putstr("Current mode: ");
    putstr((bLineMode == 1) ? "Line" : "Character");
 
    return 3;
endfunc





Dinaamelia said ...

21:29, 14 January 2013 (MST)

Lot of smarts in that ptosing!

Personal tools