A library/class that builds on the page ROM_Data to use the LCD bit patterns stored in ROM (Program Memory)
The full source code is provided in the directory LCD_ROM_Class_code.
This project will develop a class/library LCD_ROM_Class that will include a public method that will take as its inputs (arguments) the character to be displayed and the location where the pattern is to be stored.
Later projects LCD_Init_Class will interface an LCD display. (Part number XC4616 from Jaycar**) and inherit LCD_ROM_Class to display the results of a write(char) method to the LCD display. A third project will inherit both classes and expand the write( ) methods and inherit the stand print functions to display characters, strings and numbers in a range of formats. All projects could be combined into one big project. For educational purposes I have chosen to break the project into three. Any uses of the final class LCD_Print_Class will not know the difference of the implementation.
**I have no affiation with Jaycar.
Include File: LCD_ROM_Class.h
Constructor: LCD_ROM_Class No arguments
Public Method: char_pat(ptr,pattern);
ptr: character
pattern: 6 byte array for pattern
---------------------------------------------------
Project Summary: This HTML page reviews generating the bit patterns to generate the ASCII characters for a LCD. The data will be placed in ROM. The page gives sample code for reading data stored in ROM as opposed to RAM.
Keywords: ROM, RAM, Harvard Architecture, PROGMEM, pgm_read_byte( ),Arduino IDE,Arduino Library,Cpp Class,Cpp Public,Cpp Private,scope resolution operator
Possible Audience: Enthusiasts interested in Arduino programming. The project will be of interest to programmers developing their first Arduino Class/Library.
Required Components: Arduino UNO or NANO
The background to this project was the need for a LCD display where the bit patterns for each character were stored in memory. In my example there were 96 different characters each requiring 5 bytes making a total of 480 bytes. Since the Arduino has only 2K bytes of RAM storing the bit patterns in RAM uses 25% of the RAM resources. A better solution is to use the patterns already in Flash (ROM) memory.
Briefly the Arduino IDE compiler will generate a memory image that will contain an image of the program and an image of the data (variables). As part of the program image there will be code that at start up transfers the data image from Flash (Read Only) Memory into RAM (Random Access Memory or write-read memory). This is illustrated below:
In the example for this project/ page the data image includes the bit patterns ASCII[][] for all the characters. For fixed or constant data this implies there are two identical copies, one in the microcontroller flash and the other in RAM. Since the Arduino has limited RAM the ideal solution is have one copy only, that is in Flash (or Program Memory) and use only that copy.
The C Language is designed for Von Neumann architectures where the code and data exist in the same address space. With Von Neumann architectures there is no fundamental difference to reading code or data memory. However, the Arduino uses an ATmega 328P microcontroller that has a Harvard architecture processor with a separate address bus for program memory and RAM. This implies changes/modifications/enhancements are necessary with the compiler to use program memory for constant data storage. (Some compilers use non-standard C language keywords, or they extend the standard syntax in non-standard ways.)
With the Arduino to use the copy of the character array in ROM memory is a two step process
1 Add the macro PROGMEM to the data. At run time a copy of the data will no longer be created in RAM.
2.Use the macro "pgm_read_byte( )" to read the data from the Program Memory (ROM/Flash)
Using this project as an example the structure of a typical Cpp program is shown below:
If the target for the program is a mainstream computer there will be one file. In the micro-controller environment this code is broken into three files:
1. The header file that contains the definition of the class
2. The implementation code that provides the code for all the methods/routines/functions.
3. The user code.
In the first instance the developer must write all three files and that can be hard work. However the big savings come in future projects where the header and implementation files that have been combined into a library may be reused. That is the situation in this project - developing the class/library LCD_ROM_Class will be hard work However that same code will be used in the UNO_LCD_Class - the hard work has been done and all that is needed is the new client code.
The project will use the Arduino IDE.
When a new file is created it will
1. Be given an extension .ino by the IDE
2. Be located in a sub-directory of the same name as the file.
3. The sub-directory will be located in the work/sketchbook directory. (To view or change use file-->preferences)
This project will consist of three files:
1. The application or test code that I have chosen to give the label "LCD_ROM_Class". This will be created in the Arduino IDE (file--> new). This file has the extension *.ino by default.
2. The implementation file. This should be created using the inverted triangle near the top right of the Arduino IDE. Select "new tag" and give it the label "LCD_ROM_Class.cpp". Note the extension is required.
3. The header file. Create as for implementation file but use extension *.h. ie LCD_ROM_Class.h. Note the header and implementation files must have the same label (but different extensions). The test code can have any name.
Add the following starting code to all three files:
From the Tools menu ensure the UNO is selected and the correct PORT is set.
The above code should now compile and run (but do nothing)
The three files will be discussed and populated in the following sections.
To avoid problems should the same header file be included multiple times by other projects it includes the wrapper:
//Header file LCD_ROM_Class.h
#ifndef LCD_ROM_Class_H
#define LCD_ROM_Class_H
.........
#endif
The header file also includes the Arduino header file. "Arduino.h" is included by default with normal Arduino programs ( ie files with extension *ino) but needs to be specifically included with library files. In this project it was necessary for the definition of the type "byte"
At this point the LCD_ROM_Class class is declared. It has a public constructor LCD_ROM_Class( ) that has no arguments. Arguments could be added if necessary.
The implementation of the class is performed in the file LCD_ROM_Class.cpp
The *.cpp file includes all the implementaion details of the LCD_ROM_Class class.
At this point there are only two statements:
//Implementation file1. include the header file. ie #include "LCD_ROM_Class.h"
2. the code for the constructor. In this example it is just a null routine: Note the format: "LCD_ROM_Class::LCD_ROM_Class ( ) { }". The first "LCD_ROM_Class" is the class and the second "LCD_ROM_Class( )" a method in that class (the constructor). Note the "::" - the scope resolution operator that links the class name with the member/method's name.
For this page this code will simply test the class "LCD_ROM_Class"
//Application or test code LCD_ROM_Class.ino
#include "LCD_ROM_Class.h"LCD_ROM_Class LCD; //No ( ) if no parametersvoid setup(void){Serial.begin(115200);}void loop(void) { }Currently the application code :
1. Includes the header file. ie #include "LCD_ROM_Class.h"
2. Creates an instance of the class. ie LCD_ROM_Class LCD; . Note for no arguments there are no parenthesis.***
3. Initialises the serial communication to be used as part of the testing.
*** I found that including parenthesis everything appeared to work but I then obtained subtle errors. From my reading it appears that with parenthesis the compiler treats the new instance/object as a function!
The next stage is to create the bit map of the required character patterns.
In this example a character is defined on a 7 bit high and 5 bit wide matrix plus a blank left hand column and a blank bottom row (its now 8x6 overall ). This is illustrated below for the character "A". (Additional elements are shown to allow readers to define their own characters.)
At the implementation phase the information is written to the LCD one column at a time. For the character "A" the data, reading bottom to top will be 0x00, 0x7E, 0x7F, 0x11, 0x7F and 0x7E which in decimal becomes, 0,126,127,17,127 and 126.
To handle the alpha characters a two dimensional array ASCII [ ][5] is declared as illustrated below.
static const byte ASCII[96][5] ={ {0,0,0,0,0}, // space {0,95,95,0,0}, //! {3,3,0,3,3}, //" {60,60,36,60,60}, //# ....etc.... {126,127,17,127,126}, //A ....more......ASCII is declared as a "static const byte". Byte is the size of each element of the variable. This array will be added to the private area of the LCD_ROM_Class class:
class LCD_ROM_Class { public: LCD_ROM_Class( ); private: static const byte ASCII[96][5]; };In the above declaration the exact size of the table is required. If desired #defines could be used where the size of the table will be the (integer value of the last character (127) less the integer value of the first character (a space = 32) plus 1) giving 96 total.
The actual ASCII array will be added to the implementation code***:
Note the use of the directive PROGMEM. This tells th ecompiler that the program will use the copy of the table that is in ROM or flash memory. Most importantly the table will not be copied into RAM at run-time saving in this example 480 bytes of RAM. However to read the ROM a special routine (pgm_read_byte( ROM-location)) is required. See the Introduction/Background section and also the next section.
*** At one stage I thought it was a good idea to draw up some characters for the values 0 though 31 (Hex 1F). These characters turned out to be very cluttered so I gave up the idea. These extra 32 values have been commented out in the provided code. To use the array will now become ASCII[128][5].
To read or extract the ASCII values a public method void char_pat(char c, byte pat[6]); is used.
This method will take two arguments:
1. char c the character for which the bit pattern is required
2. byte pat[6] the 6 element array where the bit pattern will be returned.
Possible code will be:
Note the operation includes the function (pgm_read_byte(&ASCII[c - 0x20][index])). This special function is required to read the Arduino Flash or ROM memory. With the Arduino, or more specifically its underlying micro-controller the ATMEL328P which has sparate RAM and ROM memory the read operations will only read values in RAM or dynamic memory. A special function (pgm_read_byte( )) is required to read the ROM see the Introduction/Backround section..
The implementation code first checks if the character is in the allowable range.***
Since the ASCII table assumes the first column is a blank pat[0] is assigned to the pattern 0x00.
The "for loop" transfers the table contents to the array pat[index+1]. It uses as a pointer the value of the character less the value of a space (the first entry in the table.)
*** As given character 127 displays three dots. For a later project capturing a long string of data that contained multiple carriage-return line-feeds, both out of range characters I found multiple dots difficult to read so I changed out of range values from 127 to display '^'.
To use char_pat( ) the critical client code becomes:
Note as given (i) an instance/object LCD of the class LCD_ROM_Class is created.
(ii) That instance/object uses the method char_pat( ) to transfer the pattern from the ASCII array to the byte array pattern[6]
Legally that completes the project. A class has been developed that for a given character reads the required bit pattern from ROM and places the pattern into the variable pat[6].
The remainder of this project will be to verify that this is what really happens and to evaluate the performance.
The testing will basically consist of reading every character in the ASCII array and printing the results to the serial monitor.
1. The test program will print out the bit pattern for each character (characters with hex values in range 0x20 through 0x7f) in the following format
The character then the 6 bytes of the bit pattern via the routine DumpCharPat( ).
2. DumpCharPat( ) will invoke DumpByte() 6 times to print each byte in turn separated by a space.
3. DumpByte( ) will break the hex byte into the high and low portions and print each using the routine DumpHex ( ).
4. DumpHex ( ) will convert the hex value (or binary pattern) to an ASCII character for printing. That is binary patterns 0000 through 1001 becoming the characters "0" through '9' and patterns 1010 through 1111 the characters 'A' through 'F'.
Note: for (ptr=0x20; ptr < 0x80; ptr++) LCD.char_pat(ptr,pattern); is the critical code. The remaining code is just neatly displaying the results.
A partial screen capture of the print out about the character 'A' is shown. Close inspection will verify the program operation.
Placing the ASCII array in RAM (ie removing both the PROGMEM directive and pgm_read_byte operative) the results were 676 bytes dynamic memory and 2274 bytes of program memory. The program as given required 186 bytes of dynamic memory and 2256 bytes of program memory - a significant saving in dynamic memory. In addition an output pin was toggled about the LCD.char_pat( )and the pulse observed on a mixed signal oscilloscope.. Using the array stored in dynamic memory required 5.52uS to execute where as operating with the array in ROM increased the execution time to 8.72uS. Note both these times will be insignificant when interfaced with an LCD
void setup(void)
{.......pinMode(2,OUTPUT);} void loop(void){......... digitalWrite(2,HIGH);LCD.char_pat(ptr,pattern);digitalWrite(2,LOW);........}With the project complete it is sensible to made a library class so the results can be used by other projects.
The LCD_ROM_Class can be added to the Arduino as a library.
Currently the *.h and *.cpp files (and *.ino) will be under the user work directory in a sub folder LCD_ROM_Class. (Shown as "This Project" in the above diagram)
To generate a library:
1. In the sub folder LCD_ROM_Class use a program such as Windows Explorer to create a *.zip file of the *.h and *.cpp files.
2. In the Arduino IDE under sketch --> Include Library --> Add .zip Library
3. Browse your files and select LCD_ROM_Class.zip
The operation will, if necessary create another sub folder "libraries". Under the libraries folder will be a folder LCD_ROM_Class where there will be the LCD_ROM_Class.cpp and LCD_ROM_Class.h files. Further libraries will be created under the same libraries folder.**
Notes (i) .if the two files are copied across the Arduino would need to be restarted for the updates to take affect.
ii. The original client code (*.ino file) included the library using #include "LCD_ROM_Class.h" with quotes. To use the files in the libraries folder the #include uses < ..>.
4. For other project to use the library (i) in the Arduino IDE under sketch-->Include Library and (ii) in the list of contributed libraries select LCD_ROM_Class.
For other users if the *.zip file is available start at step 2.
** This is a different location to libraries included as part of the Arduino IDE. In my case they were at C:\\Program Files(x86)\Arduino\Arduino\libraries. With the "< >" directive the compiler will search in both locations for the given file.
This project developed a class LCD_ROM_Class with a single public method char_pat(ptr,pattern); that read the bit parrern for a character and placed it in an array pattern[6]. The bit paterns for the ASCII characters were stored in ROM which resulted in a significant saving in dynamic (RAM) memory.
A future project UNO_LCD_Class will use the library to display information to a 6 line 14 character display. This project will in turn be used to display information to assist developing a HTML project.