Arnoldo B. Canales

Scrolling Characters Emulation System.

  1. The Scrolling Characters System.

  2. What are we learning with this system

    1. Delegates

    2. DataTables and DataGridViews

    3. Arrays

    4. Timers

    5. Controls Collection and Instantiation

  3. Final Comments.

Figure 1: Scrolling Characters Emulation System. (Click image to zoom in).

1. The Scrolling Characters System.

The idea behind this system was to develop the logic and algorithms to be use in a system, that eventually became a reality, using the MAX7219 4-Dot-Matrix Module shown in Figure 2 below.

Figure 2: The MAX7219 Module.

I started the MAX7219 system using an Arduino UNO R3 but quickly run out of memory which forced to switch to an Arduino Mega 2560 R3. Languages C# and C++ are pretty similar so writing this program in C# to then extrapolate the routines to C++ is a rather simple and straightforward task. C++ is the Arduino language but writing code using its Programming interface (IDE - Integreated Development Environment) could be frustrating and cumbersome specially as the program grows in size and complexity, but don't get me wrong, most of the time I use the Arudino Programming interface (IDE) and I enjoy it, but its a love that has grown out of necessity more than anyting else. There is code that is simpler to be written or implemented in C++ than C# and vice versa and, sometimes, the way of doing things in one language will not work on the other language so the extrapolation just won’t work all the time.

An important fact to keep in mind when emulating code in C# for the Arduino Mega, is that the Arduino Mega has a Main Loop and everything has to happen inside that loop which means you cannot call a routine and forget to come back to the Main Loop, or go to a routine for minutes or even seconds and not go back to the Main Loop to check other stuff. That is an important design feature to remember when writing code for Arduino or emulating code for it regardless of the language. For this example I am using a Timer to simulate the Main Loop.

2. What are we learning with this system

As you can see in the main index above, I can’t let go of the opportunity to show off some of the coolest features provided by C# so we will be learning about Delegates and Recursion among many other features. Please also observe that sometimes, I go out of my way to implement code that it may look out of place or unnecessary, but the idea is to provide an example to you the reader.

a. Delegates.

Delegates are rich in features and options but in its more simple definition, they allow to replace the default routine with an alternate one, for example, each Windows Forms Button has a Click Event, so every time a button is clicked the default click event is called but, by using delegate, we can 'delegate' the task to a second event all together or we can 'add' this second event to the sequence of calls executed when the click event is called upon.

I have two buttons to Increase/Decrease (Figure 3) the Timer's delay. Both buttons do the same task but with opposite arithmetic sign. The Increase button increases the time by 50 milliseconds each time is clicked while the Decrease button does the opposite.

Figure 3: Increase / Decrease Delay Time buttons.

So I declared a delegate in the Form’s Load Method and attached its event to the default click event by issuing the statements shown below, so every time the user clicks one of these buttons, the OnIncDecTimerClick routine is called instead of the regular Click event (actually they both are called in sequence, click first followed by the delegate).

Delegate declaration code:


//------------------------------------------------------------------------
// Routine: Form1_Load - Executed when the forms loads.
//------------------------------------------------------------------------
private void Form1_Load(object sender, EventArgs e)
{
  this.btnIncreaseDelay.Click += new EventHandler(OnIncDecTimerClick);
  this.btnDecreaseDelay.Click += new EventHandler(OnIncDecTimerClick);
}

  					

The Routine below shows the delegate function for the Click event:


//------------------------------------------------------------------------
// Routine: OnIncDecTimerClick
//------------------------------------------------------------------------
void OnIncDecTimerClick(object sender, EventArgs e)
{
   Button xButton = sender as Button;
   if        (xButton.Text == "Increase") { IncreaseDecreaseTimerDelay(+50); }
   else { if (xButton.Text == "Decrease") { IncreaseDecreaseTimerDelay(-50); } }
}

  					

Once I get to the delegate routine, as you can see in the code above, the need arises to know who called the delegate routine-- was it the Increase or the Decrease button? so by using the sender parameter as a reference, I instantiate it as button, thus allowing me to render all its properties which, in turn, will allows me to check the ‘senders’ caption to find out what button made the call, taking the proper action accordingly.

Routine to Increase / Decrease the Timer's delay time:


//------------------------------------------------------------------------
// Routine: 
//------------------------------------------------------------------------
private void IncreaseDecreaseTimerDelay(int pTime)
{
    int xTimerDelay = Convert.ToInt32(txtTimerDelay.Text) + pTime;
    //
    //if (xTimerDelay < 50) { xTimerDelay = 50; } else { if (xTimerDelay > 500) { xTimerDelay = 500; } }
    // Using a "?: Ternary Conditional Operator" to replace an if-else statement shown on line above.
    xTimerDelay = xTimerDelay < 50 ? 50 : xTimerDelay > 500 ? 500 : xTimerDelay; 
    //
    tmrTimer1.Interval = xTimerDelay; txtTimerDelay.Text = xTimerDelay.ToString();
}

  					

The same issue can be easily solved by using two routines, or just use each button’s click event calling the Increase/Decrease function with the proper parameter value, but then, we wouldn’t be able to learn delegates, and we want to learn delegates under simple conditions.

b. DataTables and DataGridViews.

I will be using ADO.NET (or ActiveX Data Object for the .NET Framework) to handle data, as in read/write files either in memory or the Hard Drive. ADO.NET has, among many others, a command called DataTable which allows to create memory representation of data in the form of rows and columns, similar to Excel. For this project, I need to have a way to store, and preserve for further reference, what the user types in the data textbox so ADO.NET comes in handy. I use the command below to create a Table in memory:

 

dtDataStream2Transmit = CreateTable();

 

The command above, just like the Delegate declarations, is processed in the Form’s Load Event. CreateTable() is a routine that its only task is to create a DataTable structure (see code below) and assign it to an already declared memory variable of type DataTable.

CreateTable() routine:


//------------------------------------------------------------------------
// Routine: CreateTable
//------------------------------------------------------------------------
public DataTable CreateTable()
{
   DataTable mTable = new DataTable();
   mTable.Columns.Add("asciiData", typeof(byte));
   mTable.Columns.Add("Char", typeof(char));
   return mTable;
}

  					

To change the scrolling message, I type the desired message in the Data TextBox, and click the Append to Datastream button (See Figure 4 below). At that time, the DataTable is cleared and the new message is appended to it and shown in the screen via the DataGridview control.

Figure 4: DataGridView and DataTable controls interacting.

Observe the three routines below. The first one is the Click event for the Append to Datastream button. This routine calls two more routines, the first one, Convert_String2ASCII(), performs the conversion from Text to ASCII representation saving the resulting characters to the dtDataStream2Transmit DataTable, whereas the second routine, takes the DataTable information and makes a copy to a memory array called TransferStream. Remember that these routines will be running on an Arduino Mega so what appears irrelevant here may be useful under a less sophisticated system. This sounds a little cryptic so I’ll explain, Visual Studio provides an elaborate developing environment that Arduino lacks, especially when it comes to troubleshoot and debug code, so sometimes this code has to be over-simplified, compartmentalized further more, to allow easy debug and troubleshoot.

The code below shows the routines to handle user input.


//------------------------------------------------------------------------
// Routine: 
//------------------------------------------------------------------------
private void cmdAppend_Click(object sender, EventArgs e)
{
     //Populate DataTable and DataGridView by converting the typed text 
	 //in the TexBox to ASCII Chars.
     Convert_String2ASCII(txtData.Text);
     //
     //Pre-fix a Command to the typed text and post-fix the text with
	 //Termination Characters.
     GetDataReadyToBeTransmitted();
}

private int Convert_String2ASCII(string xText)
{
    try
    {
       dtDataStream2Transmit.Clear();
       //
       byte[] ASCIIValues = Encoding.ASCII.GetBytes(xText);
       //
       foreach (byte b in ASCIIValues)
       { dtDataStream2Transmit.Rows.Add(b, Convert.ToChar(b)); }
	   //
       return ASCIIValues.Count();
    }
    catch (Exception e) { Console.WriteLine(e); return -1; }
}

private void GetDataReadyToBeTransmitted()
{
     TransferStream = new byte[64];
     //
     int i = 0;
     foreach (DataRow r in dtDataStream2Transmit.Rows)
     { TransferStream[i] = Convert.ToByte(r["asciiData"]); i++; }
	 //
     mTotNumOfChars = i; //This variable holds the Total Number of characters typed by the user.
}

  					

In the actual Scrolling System using the MAX7219 module, the Arduino Mega 2560 handles four (4) MAX7219 modules. At the same time, the Arduino Mega 2560 gets commands plus the scrolling text from a C# program creating a constant back and forth communication between the Mega 2560 and a computer running the C# program. This communication is done via a 64-character stream where the first four characters are a command, followed by the message (up to 59 characters) with a termination character at the end of the message. I am using the Caret (^) as termination character. I don’t have to take all these steps in this emulation system but I have prepared the routines to handle the stream structure as described once I extrapolate the routines to Arduino’s C++.

c. Arrays.

An array is a collection of contiguous memory cells containing a series of elements of the same type. An important feature about arrays is that they have a zero-based index, meaning that the first element in the array has an index of zero, the next cell has an index of 1 and so on and so forth. See figure 5 below for an illustration of an array.

Figure 5: Array declaration and memory representation.

I use two arrays to handle the character scrolling. The TransferStream array, with a length of 64 characters, has the user’s message, the second array, DisplayBuffer, has a length of 16 characters. I am using four MAX7219 modules, each with four cells, for a total of 16 cells, hence the size of the DisplayBuffer array.

Each memory cell in the DisplayBuffer array corresponds to a physical cell in the MAX7219 module, so to scroll one character to the left, I shift the content of the DisplayBuffer array one character to the left and, as the rightmost cell empties, I borrow the next character from array TransferStream, then, I call a routine whose only purpose is to get the DisplayBuffer content and splash it to the MAX7219 modules. The actual scrolling is happening in memory, but with every splash, it creates the scrolling visual effect as happening in the MAX7219 modules. See figure 6 below.

Figure 6: Scrolling characters to the left.

d. Timers.

When I started writing the scrolling code for the Arduino Mega, I made the mistake of doing uninterrupted text scrolling causing the system to hang until the whole text message was scrolled out. As I previously mentioned, Arduino has a main program where different tasks have to loop thru checking for events, user input, or other electronic device’s input. After the issue became obvious, I changed the scrolling routine to scroll one-character at a time, then go back up to the main loop to check for other stuff, including checking the Arduino communications buffer for newly arrived commands, or text messages, then going back to scroll another character and so on and so forth until the whole message was displayed to, then, start all over again.

To implement this main loop behavior, I am using a Timer control. A Timer is a control that process an event, called Tick, every time the Timer interval runs out. The Timer interval is a number set by the programmer and expressed in milliseconds where a value of 1,000 equates to 1 second.

The code below shows how the Timer control is initialized to 250, and I wonder if, from the point of view of the user, 250 milliseconds means absolutely nothing and it would be better to just call it Timer Scroll Speed with values from 0.5 to 5.0, where 0.5 and 5.0 equates to 50 and 500 milliseconds respectively.

Code in the Form’s Load( ) event to initialize the Timer control.


txtTimerDelay.Text = “250”;
tmrTimer1.Interval = 250;
tmrTimer1.Enabled = false;

  					

The code below shows the Tick() event being called after the Timer's interval runs out.


//------------------------------------------------------------------------
// Routine: 
//------------------------------------------------------------------------
private void tmrTimer1_Tick(object sender, EventArgs e)
{
   tmrTimer1.Enabled = false; //Disable Timer
   //
   if (mScrollType == 1) { Load_Message_Scroll_1(); } //Scrolls to the left starting with the RMSC
   if (mScrollType == 2) { Load_Message_Scroll_2(); } //Scrolls 1 char until it fills the display
   if (mScrollType == 3) { Load_Message_Scroll_3(); } //Drop one character starting with the LMSC
   if (mScrollType == 4) { Load_Message_Scroll_4(); } //Fills out the whole display then do left scroll
   if (mScrollType == 5) { Keep_Scrolling(); }
   //
   txtChar_Index_Start.Text = mYIndexStart.ToString();
   txtChar_Index_End.Text = mYIndexEnd.ToString();
   //
   DisplayChars(ref DisplayBuffer);
   //
   tmrTimer1.Enabled = true; //Enable Timer
}

  					

As you can see in the code above, as I get into the Tick event, I disabled the Timer avoiding ticks to happen no more, doing the opposite before leaving the event, then I proceed to call the selected scrolling routine based in the value stored in variable mScrollType. The scrolling routine will scroll one character and return back to the calling program to proceed with a character splash to the MAX7219 modules via the DisplayChars routine.

By default, the timer if off or disabled, but becomes enabled when the user clicks any of the Start Auto Scroll buttons (see Figure 7 below). At that time, the memory variable mScrollType is loaded with a value between 1 and 4. In the same way, to disable the timer and stop the scrolling, the user clicks on the same button.

When I click on any of the Start Auto Scroll buttons, I call the On_btnAutoScrollX_Click delegate event shown in the code below.

Figure 7: Auto-Scroll buttons and their relation to the mScrollType memory variable.

The code below shows the On_btnAutoScrollX_Click delegate event.


//------------------------------------------------------------------------
// Routine: 
//------------------------------------------------------------------------
void On_btnAutoScrollX_Click(object sender, EventArgs e)
{
  Button xButton = sender as Button;
  //
  if (xButton.Text == "Start Auto Scroll")
  {
     int xbtnIndex = Convert.ToInt32(xButton.Name.Substring(xButton.Name.Length - 1, 1));
     //
     mScrollType = xbtnIndex;
     DisEnableSets(mScrollType);
     xButton.Text = "Stop Auto Scroll";
     //
     if (mScrollType==1){mYIndexStart = 0; mYIndexEnd = 0; mCntrl4_LMS_RMSC = 15;}
     if (mScrollType==2){mYIndexStart = 0; mYIndexEnd = 0; mCntrl4_LMS_RMSC = 15; mCntrl4_LMS_Top = 0;}
     if (mScrollType==3){mYIndexStart = -1; mYIndexEnd = -1;}
     if (mScrollType==4){mYIndexStart = 0; mYIndexEnd = 15;}
     //
     tmrTimer1.Enabled = true;
  }
  else
  {
     tmrTimer1.Enabled = false;
     xButton.Text = "Start Auto Scroll";
     DisEnableAllSets(true);
     mScrollType = 0;
  }
}

  					

Remember that the same button is used to start and stop the scrolling process, so the code above shows how I check if the scroll is on or off by checking the calling button caption. If the caption is equal to Start Auto Scroll then it means that the auto-scroll process must be started by enabling the Timer. Next, by using the Substring function, I extract the rightmost significant character out of the calling button’s name and assign it to the mScrollType variable, then I proceed to initialize the index variables. Since I now have everything in place for the Tick() event to happen, I finally proceed to enable the Timer.

Observe the GIF file below for a graphical representation of the code above.

Figure 8: Graphical representation of the Auto-Scroll routines.

e. Controls Collection and Instantiation.

Controls Collection provides an easy way to access large amounts of controls with a few lines of code. Controls Collection is based on the Windows Form Object-Oriented hierarchy structure where the Form as the parent, or main control, is able to hold child controls or sub-controls. C# provides a set of statements that allow to sweep through, identify, and access those controls on a simple easy way. The code below demonstrates this feature.

As you can see on figure 8 below, the Scrolling system has sixteen (16) cells where each cell is named txtCell1, txtCell2, all the way to txtCell16.

Figure 8: Scrolling Cells names.

Observe the routine below, and notice the commented-out code in the top of the routine. The commented-out code shows how I would have written the code without using Collections, but then, right below it you can see the few lines of code needed to replace the code above when using Controls Collection.

The code below show the use regular code versus using Controls Collection.


//------------------------------------------------------------------------
// Routine: 
//------------------------------------------------------------------------
public void Clear_Cells()
{
  /* Commented-out code
  txtCell16.Text = " ";
  txtCell15.Text = " ";
  txtCell14.Text = " ";
  txtCell13.Text = " ";
  txtCell12.Text = " ";
  txtCell11.Text = " ";
  ...
  txtCell6.Text = " ";
  txtCell5.Text = " ";
  txtCell4.Text = " ";
  txtCell3.Text = " ";
  txtCell2.Text = " ";
  txtCell1.Text = " ";*/
  //
  //Accessing the TextBox Controls using Controls Collections.
  foreach (Control txtBox in this.Controls)
  {
    if (txtBox is TextBox)
    {
       TextBox x = txtBox as TextBox; //instantiate x as a textbox.
       if (x.Name.Substring(0, 6) == "txtCell") { x.Text = ""; } //then check the Name property.
    }
  }
}

  					

3. Final Comments.

The purpose of this project, as most of my projects, is to get people interested in the field of Computers in general, and Programming Languages in particular, and not necessarily to get an end product. The different routines I am creating are not merely examples but templates that can be used in a completely different project or application and it is there, I think, where the visitor could find value when reading through this webpage, as well as my other webpages. Please don’t hesitate to leave your comments below, and thanks for visiting.

VIDEOS

 

Scrolling characters emulation demo video

Click here to go to a popular C# website.

 

Click here to go to the official Microsoft C# website.

 

Download Microsoft Visual Studio for FREE!.