Introduction
I have a daughter at primary school. As a father, I have some responsibilities, of course. I like being a father, I have no problem with that. What bothers me is the endless math study sessions in that business. It is really killing me sometimes. Again and again, 2+2, 5+6, 4+3. That was start, now it is time for subtraction: 5-4, 10-4 .. and everybody knows that it has no end. I said enough and decided to leverage the technology. You know, technology for people, and now for me. Come here my dear Arduino, I need you.
I was hoping that this project would be easy to implement at the very beginning. All I would need to do is to write some functions to show some numbers and maybe a buzzer and some LED to make it interesting. However, things were changed after I started to think about it elaborately, Some hardware management issues came out, then UI management, then content management. Our small Arduino application changed into something that had to be handled more seriously which leads me to write this article. Let's start with the requirements.
Project Requirements
- System shall show a menu on a display for the basic operations: addition, subtraction, multiplication, division
- User (my daughter) can select an arithmetic operation to study from the menu with a keypad.
- There shall be some difficulty levels. After operation selection, difficulty level selection will appear on the display.
- According to difficulty level, random questions shall be shown on the display and user can answer the question with the keypad.
- User can correct her answer before entering it.
- A message shall be displayed according to the correctness after entering an answer.
- The correct answer shall be displayed after 3 wrong answers.
- User can browse menu (menu-up or selecting menu items)
- The system shall have audio and visual warning infrastructure. Messages will be accompanied by that infrastructure.
- Each operation will have a time-limited quiz section.
- Quiz section shall start to randomize questions beginning with the easiest level and goes up to higher difficulty levels.
- Statistics will be displayed at the end of the quiz (how many questions asked, how many of them answered correctly)
- System can take attention when she comes around.
- There shall be some fun part out of math which directs user to do something like 'sing a song', 'kiss your daddy' etc. Else the user acceptance test is absolutely going to fail!
- Warning infrastructure can be used as fun material in the fun part.
Hardware
What we need in this project:
- Arduino Mega
- Serial LCD
- Matrix keypad
- Analog keypad
- PIR motion sensor
- LED & 400-ohm resistor
- Digital buzzer module
- Hook-up wires
and see the following for the hardware design.
Notes for mismatching parts:
- LCD is a serial LCD.
- Arduino UNO should be replaced with Mega.
(I started this project with an Arduino UNO, but decided to continue with Arduino Mega because of more memory need. At the beginning, everything was good with the Arduino UNO. However, when the code started to get bigger, after a point, I couldn't manage to hold the RAM usage below the capacity of the Arduino UNO and as you expect, it ended up with Arduino Mega, which has 8K of SRAM.)
Software Design
Image1: Overview of the design.
The system can be divided into 2 main parts. As you see in the Image1, the first part is responsible for the hardware management.
- Input System: We have 2 different keypads and they are merged into one as seen by other components. Unified keyboard informs registered client when any of keys is pressed from any keyboard (matrix keypad or adkeyboard).
- Output system: Serial LCD with added functionality.
- Signalling: Unified signalling sub-system. It is composed of a LED and a digital buzzer and it provides the capability of defining different signal patterns to the client code. Client code can start any pattern according to its need.
- Motion detection: Motion detection capability with PIR sensor. When somebody is detected, it triggers a signal to take attention.
The presentation layer is responsible for the user interaction. It contains infrastructure for visual items (menus and pages) and content management sub-system.
- UI management: In this sub-system, visual items are defined. A menu lists items on the display for user selection. A menu item may represent a sub-menu or a page. User can select a menu item with its displayed index by pressing corresponding key on the keyboard. One can go up in the menu by pressing 'Escape' key. If the selected menu item is a page, it is displayed, then. A page is responsible to show the content attached to it. Page waits for user input to change its content, or owner menu is displayed if 'Escape' is pressed. While in a page, pressing 'Enter' key results in evaluation of the answer. If user makes a mistake, she can delete her answer by pressing 'Backspace' key. According to the correctness of the answer, appropriate signal is triggered.
- Content management: This sub-system provides content to be displayed on the screen. Algorithms for different levels of arithmetic operations are provided. Client code (a page) requests content from this sub-system.
Simplified class diagrams are the following. These images show the basic structure to make easy to understand the actual class implementations.
Hardware Management
MFK_InputDevice
merges Keypad2
and AdKeyboard
into one device. It handles their events and produces a new event for its clients with a new set of keys which are given below.
Image2: Input sub-system.
Key mappings:
Keypad | Button | Key | Value (hex) |
Matrix | 0 | '0' | 0x30 |
Matrix | 1 | '1' | 0x31 |
Matrix | 2 | '2' | 0x32 |
Matrix | 3 | '3' | 0x33 |
Matrix | 4 | '4' | 0x34 |
Matrix | 5 | '5' | 0x35 |
Matrix | 6 | '6' | 0x36 |
Matrix | 7 | '7' | 0x37 |
Matrix | 8 | '8' | 0x38 |
Matrix | 9 | '9' | 0x39 |
Matrix | * | Escape | 0x1B |
Matrix | # | Enter | 0x0D |
AD | S1 | Backspace | 0x08 |
AD | S2 | F1 | 0x80 |
AD | S3 | F2 | 0x81 |
AD | S4 | F3 | 0x82 |
AD | S5 | F4 | 0x83 |
MFK_OutputDevice
is derived from SerialLCD
class. It enhances SerialLCD
for that project by combining functions of SerialLCD
.
Image3: Output sub-system.
A signal pattern is built by using signal sources. A pattern should be stored in the signal controller with an index. The only thing to start a signal pattern is to call it from the signal controller with its index.
Image4: Signalling sub-system.
At the top of the hardware management layer, MFK_Hardware
class is placed. It orchestrates all other hardware devices and hides redundant complexity from clients. For example, PIRMotion
and SignalController
is not exposed to clients. However, input & output devices are mandatory to expose to the outer world since the UI system needs direct access to their functions. Signal patterns are also constructed in that class. They are ready to use by their named indices.
Image5: Hardware management
Presentation
This layer is responsible for user interaction. It provides visual elements and content.
ContentFactory
creates ContentProvider
s according to ContentTypeEnum
and ContentLevelEnum
. Client gets the instance of the content factory, then it can request a content provider.
Image6: Content management
VisualItem
is the base class for visual elements (menu & page). It also binds hardware management and presentation. 'show
' and 'msgbox
' functions use the output device (MFK_OutputDevice
) and the input device (MFK_InputDevice
) calls callback function provided by VisualItem
. 'msgbox
' function has also the ability of starting a signal pattern by calling 'signal
' function of the hardware (MFK_Hardware
).
Menu
, as the name implies, provides a list of menu items to select. User can select an item with its index, and menu 'show's that VisualItem
.
Page
is the visual item for displaying content. Except for fun content, it expects an input from user. User 'Enter's her answer, then a message is displayed to inform user. After the message, new content is required from the content management.
Chapter
is the intermediate class between ContentProvider
and Page
. When a page is shown for the first time, its chapter and related ContentProvider
are created. User answers are directed to the chapter and correctness of an answer is evaluated in the chapter. Chapter
also holds statistics for a study session in that page. FunChapter
is a kind of chapter in which an answer is not expected from user. QuizChapter
is the time-limited chapter. In a quiz chapter, questions are asked until the end of the time frame.
Image7: UI management
Implementation
I hope the general structure of the system is clear. Now, it is time to dive into the code where the caption of this article means something.
I'd like to start with MathForKid.ino. It is the main code which is uploaded to Arduino Mega.
MFK_Hardware* hw;
Menu* mainMenu;
void setup() {
Serial.begin(9600);
hw = MFK_Hardware::getInstance();
hw->begin();
CreateUI();
mainMenu->show();
}
void loop() {
hw->update();
VisualItem *v = VisualItem::getActiveItem();
if(v!=NULL)
v->update();
}
That is all. Run your application on the Arduino. OK, maybe a little explanation would be better
As I said at the very beginning of the 'Software Design' section, we have two main components: one for hardware management and one for presentation. They are defined globally at the top of the code and we initialize both of them in the 'setup
' function. 'loop
' function invokes the code to update them.
Actually, CreateUI
function is also implemented in that file. It creates the user interface where user interaction occurs. mainMenu
, all sub-menus and all pages are created and chapter properties are specified for pages in this function.
void CreateUI() {
mainMenu = new Menu("main");
Menu* m = new Menu("+");
mainMenu->addMenuItem(m);
Page* p = new Page("L1");
p->setChapterProperties(Chapter::NormalChapter, \
ContentFactory::Addition, ContentFactory::Level1Content);
m->addMenuItem(p);
p = new Page("L2");
p->setChapterProperties(Chapter::NormalChapter, \
ContentFactory::Addition, ContentFactory::Level2Content);
m->addMenuItem(p);
...
Let's continue with the specific design patterns in this application.
As you may expect, MFK_Hardware
is an example of facade pattern. It hides complexity of underlying hardware management issues and provides a clean interface to its clients. It is also a singleton since there exists only one instance of it all over the system. To achieve that, its constructor, copy and assignment operator are declared in the private section of the class declaration:
MFK_Hardware();
MFK_Hardware(MFK_Hardware const&); void operator=(MFK_Hardware const&);
and you can only use getInstance
static function to access it, which is in the public section:
static MFK_Hardware* getInstance() {
static MFK_Hardware hw;
return &hw;
};
From http://www.dofactory.com/Patterns/Patterns.aspx:
- Facade: A single class that represents an entire subsystem (structural pattern)
- Singleton: A class of which only a single instance can exist (creational pattern)
MFK_InputDevice
is also a good example of facade pattern. It merges two different input devices (matrix keypad and adkeyboard) into one. Moreover, it has the subject role of observer pattern. It is a ClientOwner
of type MFK_InputDeviceClient
where clients can register/unregister to MFK_InputDevice
. Therefore, when a change occurs in a state (keypress in that case), all clients are informed. I have used that pattern in many places in that project.
template<>
class MFK_InputDevice<MFK_InputDeviceClient>:
public ClientOwner<MFK_InputDeviceClient> {
private:
...
void informClients(char);
...
void MFK_InputDevice<MFK_InputDeviceClient>::informClients(char c) {
for(int i=0; i<5; i++) {
if(this->client[i]!=NULL)
this->client[i]->invokeMFKInputCallback(c);
}
}
ClientOwner
implements register/unregister functions. A MFK_InputDeviceClient
overrides invokeMFKInputCallback
function and registers itself to the ClientOwner
. Then MFK_InputDevice
can inform them with the callback provided by them. VisualItem
inherits from MFK_InputDeviceClient
, therefore a visual item can register itself to MFK_InputDevice
to observe it.
From http://www.dofactory.com/Patterns/Patterns.aspx:
- Observer: A way of notifying change to a number of classes (behavioral pattern)
ContentFactory
and ContentProvider
classes have also some interesting properties. ContentFactory
is a singleton and can be thought as some form of factory pattern. It constructs content providers for clients, but content providers vary according to client's need.
ContentProvider* ContentFactory::getContentProvider(ContentTypeEnum op,\
ContentLevelEnum level) {
if(this->contentProvider[op][level] != NULL)
return this->contentProvider[op][level];
ContentProvider *p=NULL;
switch(op) {
case Addition:
switch(level) {
case Level1Content:
p = new ContentProvider(ContentP_0_0);
break;
...
return p;
}
From http://www.dofactory.com/Patterns/Patterns.aspx:
- Factory: Creates an instance of several derived classes (creational pattern)
ContentProvider
can be thought as an example of strategy pattern along with Chapter
. A chapter is the context where a content provider resides. ContentProvider
provides a result (in that case, a question and an answer) with a different algorithm for each instance.
char* Chapter::next() {
char *retval=NULL;
...
retval = this->contentP[0]->getContent(Chapter::CONTENT, \
Chapter::ANSWER);
...
return retval;
}
From http://www.dofactory.com/Patterns/Patterns.aspx:
- Strategy: Encapsulates an algorithm inside a class (behavioral pattern)
Conclusion
You know, developing an application is not just coding. Better requirement gathering leads to better design. Better design leads to a better software, and 'better' is defined in many software engineering books (which is not the main subject of this article). To develop better software, it is better to know about design patterns. (how many 'better's do we have in this paragraph, by the way?)
A design pattern, as the name implies, is the generalization of a solution for a kind of problem. Therefore, not to invent the wheel again, a basic knowledge about design patterns makes our job easier.
However, applying a design pattern to a problem is different than knowing it. You cannot force your problem to match the design pattern. Things get worse when you try to change your actual problem to something else. What I do is to tailor the pattern according to my need. If I don't need a structure within the current problem domain, I don't do anything with the expectation of a future release. You may think that statement is very obvious, but I have seen many projects which became overdue just because of that reason. In software development, time is not just money, but equals to money, literally. Whatever you do in excess results in a money loss, both for you and for your client.
There are very good articles about design patterns in our codeproject. I especially recommend you to read design pattern series by ian mariano. Applause Ian.
I will continue to study on this project. I know there are parts which could be better and any advice will be appreciated. (Fun part is still missing, I had to comment out them because there is something wrong with either the flash library or calls in my code.)
References
- http://www.dofactory.com/Patterns/Patterns.aspx
- http://www.cplusplus.com/doc/tutorial/
- http://arduino.cc/
- http://arduiniana.org/libraries/flash/
History
This is an ongoing project. Source code may change after the publish of this article. You can download the latest version from https://github.com/ozanoner/arduino.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.