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 serial terminal program that runs on a uLCD-28PT-GFX2 display displaying text sent to it. As text fills the display the serial terminal scrolls old text 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 serial terminal to run on the 4D system 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 below.
Arduino Serial Terminal 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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 Serial Terminal Program
This program runs on the uLCD-28PT_GFX2 display, though it should be compatible with any Picaso display.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
#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 |
The 4dgl code does not compile
Looks like all the ” are in html tags like ‘ <‘ OK, i think i fixed them all ,
Thanks for pointing that out. WordPress likes to convert them to html. Annoying!