General M2TKLIB questions specifically for M2_TEXT, cb functions and M2_TOGGLE

I have been fiddling around with M2TKLIB for some time now and in general I really like it. However, I have accumulated a few things I just cannot seem to figure out and which I have been unable to solve by use of the wiki for m2tklib or with searches on these forums.

1. M2_TEXT.
I am trying to use M2_TEXT so a user can type in a device name that can then be stored. My problem is that I have to use the select key 65 times before I get to the letter "A" which I suspect is due to the ASCII code for "A". Is there any way that I can change this so that only capital letters and numbers for instance will be options for M2_TEXT when I click through it? Otherwise I suspect any user will give up long before they're done typing.

2. Cb functions for M2_SPACECB.
In the next post I have posted the full code of what I have built so far. As you can see I have a main menu where you can choose between 3 devices.

Whether I choose device 1, 2, or 3 the menu that will come up will look exactly the same, of course with the difference that each one will refer to device 1, 2, or 3 respectively. Is there an easier way of doing this than what I have done, ie. build a complete menu for itself for each one?

Also I want to check if there has actually been added a device to the specific slot. The current way I do that is with M2_SPACECB and then I use the CB-function, but since I dont know how to send an integer through the cb-function I have had to make a specific function for each device. Is there a way where I can send eg. an integer 1, 2 or 3 to the cb-function and then just use a switch in the function to handle it so I do not need 3 functions? Also according to the documentation for M2_SPACECB it should load the CB-function when it first appears but I have found that I have to do the following:

void is_device1_empty(m2_el_fnarg_p fnarg){
	if(device1_status){
		m2_SetRoot(&top_el_device1_menu);
	}
	else{
		m2_SetRootExtended(&top_el_device_menu, 0, 0);
	}
	m2.setKey(M2_KEY_SELECT);
	m2.checkKey();
	if ( m2.handleKey() )
		m2.draw();
}

Is this a bug or just me using M2_SPACECB in a wrong way?

M2_TOGGLE
In a different program I have been making I have tried to use M2_TOGGLE together with M2_BUTTON on a 16x2 LCD, which I also have been unable to get to work. The problem can also be seen when I use the Radio example from the arduino IDE modified to a 16x2 LCD. Is there any way I can solve this so I can neatly scroll through it, instead of everything being moshed together? I have enclosed a picture of what appears here:

Disclaimer: I'm not a good coder so if you see anything that seems odd, please do tell me so I can learn and optimize the code :slight_smile:

#include <LiquidCrystal.h>
#include "M2tk.h"
#include "utility/m2ghlc.h"


//=================Input/Output setup=================

uint8_t user_up_pin = 2;
uint8_t user_down_pin = 3;
uint8_t user_select_pin = 8;

//=================LCD setup=================

LiquidCrystal lcd(12, 11, 7, 6, 5, 4);
const int contrastPin = 10;

//=================Parameters and variables=================

//-------Variables to keep track of the timing of recent interrupts-------

unsigned long button_time = 0;
unsigned long last_button_time = 0; 

//-------Device and command names-------
const int buffer_size = 13; //set buffer size 
const int name_size = 12; //set max name size to 11 chars
String menu_sub_dot = String(". "); //used for sub menus 

//Device 1
char device1_name[name_size] = "Empty slot1";
char device1_menu_name_char[buffer_size]; //char array to hold the menu name for device to be shown in the menu
String device1_name_string = device1_name; //The name of Device
String device1_menu_name = String(); //String with the menu name for device
bool device1_status = true;

//Device 2
char device2_menu_name_char[buffer_size]; 
String device2_name = String("Empty slot2");
String device2_menu_name = String(); 
bool device2_status = false;

//Device 3
char device3_menu_name_char[buffer_size];
String device3_name = String("Empty slot3"); 
String device3_menu_name = String();
bool device3_status = false;

//=================Menu setup=================

//-----------------Forward declarations of the toplevel elements-------

M2_EXTERN_HLIST(top_el_device_menu);
M2_EXTERN_HLIST(top_el_device1_menu);
M2_EXTERN_HLIST(top_el_device2_menu);
M2_EXTERN_HLIST(top_el_device3_menu);
extern M2tk m2;

//-----------------Menu elements-------

M2_ROOT(el_splash_screen, NULL, "VeAr Remote", &top_el_device_menu); //Splash screen for startup
M2_ALIGN(el_centered_splash_screen, NULL, &el_splash_screen);

M2_SPACECB(top_el_device1_cb, NULL, is_device1_empty); //Checks if there is any device on device slot device menu
M2_SPACECB(top_el_device2_cb, NULL, is_device2_empty); 
M2_SPACECB(top_el_device3_cb, NULL, is_device3_empty);

char buffer[name_size]; //buffer to hold the new name
M2_TEXT(el_add_device1_name_text,"a0f2", buffer, name_size);
M2_BUTTON(el_ok, "", " OK ", fn_add_device_name_ok);
M2_LIST(add_device1_list) = {&el_add_device1_name_text, &el_ok};
M2_VLIST(el_add_device1, NULL, add_device1_list);

//-------Overall menu structure for devices-------

m2_xmenu_entry xmenu_data_device[] = 
{
	{ "Choose device", NULL, NULL},
		{ device1_menu_name_char, &top_el_device1_cb, NULL},
		{ device2_menu_name_char, &top_el_device2_cb, NULL},
		{ device3_menu_name_char, &top_el_device3_cb, NULL},
	{ "Add device", NULL, NULL },
		{ device1_menu_name_char, &el_add_device1, NULL},
		{ device2_menu_name_char, &top_el_device2_cb, NULL},
		{ device3_menu_name_char, &top_el_device3_cb, NULL},
	{ "Remove device", NULL, NULL },
	{ NULL, NULL, NULL },
};

// This is the main menu dialog box

uint8_t el_device_first = 0;
uint8_t el_device_cnt = 2;

// M2_X2LMENU definition
M2_X2LMENU(el_device_strlist, "l2e1w12", &el_device_first, &el_device_cnt, xmenu_data_device, '+','-','\0');
M2_VSB(el_device_vsb, "l2W2r1", &el_device_first, &el_device_cnt);
M2_LIST(list_device) = { &el_device_strlist, &el_device_vsb };
M2_HLIST(top_el_device_menu, NULL, list_device);

//-------Overall menu structure for device 1-------

m2_xmenu_entry xmenu_data_device1[] = 
{
	{ "Commands left", NULL, NULL},
	{ "Add Command", NULL, NULL },
	{	". Command 1", NULL, NULL},
	{	". Command 2", NULL, NULL},
	{	". Command 3", NULL, NULL},
	{ "Remove command", NULL, NULL },
	{ "Main menu", &top_el_device_menu, NULL},
	{ NULL, NULL, NULL },
};

// This is the main menu dialog box

uint8_t el_device1_first = 0;
uint8_t el_device1_cnt = 2;

// M2_X2LMENU definition
M2_X2LMENU(el_device1_strlist, "l2e1w12", &el_device1_first, &el_device1_cnt, xmenu_data_device1, '+','-','\0');
M2_VSB(el_device1_vsb, "l2W2r1", &el_device1_first, &el_device1_cnt);
M2_LIST(list_device1) = { &el_device1_strlist, &el_device1_vsb };
M2_HLIST(top_el_device1_menu, NULL, list_device1);

//-------Overall menu structure for device 2-------

m2_xmenu_entry xmenu_data_device2[] = 
{
	{ "Commands left", NULL, NULL},
	{ "Add Command", NULL, NULL },
	{	". Command 1", NULL, NULL},
	{	". Command 2", NULL, NULL},
	{	". Command 3", NULL, NULL},
	{ "Remove command", NULL, NULL },
	{ "Main menu", &top_el_device_menu, NULL},
	{ NULL, NULL, NULL },
};

// This is the main menu dialog box

uint8_t el_device2_first = 0;
uint8_t el_device2_cnt = 2;

// M2_X2LMENU definition
M2_X2LMENU(el_device2_strlist, "l2e1w12", &el_device2_first, &el_device2_cnt, xmenu_data_device2, '+','-','\0');
M2_VSB(el_device2_vsb, "l2W2r1", &el_device2_first, &el_device2_cnt);
M2_LIST(list_device2) = { &el_device2_strlist, &el_device2_vsb };
M2_HLIST(top_el_device2_menu, NULL, list_device2);

//-------Overall menu structure for device 3-------

m2_xmenu_entry xmenu_data_device3[] = 
{
	{ "Commands left", NULL, NULL},
	{ "Add Command", NULL, NULL },
	{	". Command 1", NULL, NULL},
	{	". Command 2", NULL, NULL},
	{	". Command 3", NULL, NULL},
	{ "Remove command", NULL, NULL },
	{ "Main menu", &top_el_device_menu, NULL},
	{ NULL, NULL, NULL },
};

// This is the main menu dialog box

uint8_t el_device3_first = 0;
uint8_t el_device3_cnt = 2;

// M2_X2LMENU definition
M2_X2LMENU(el_device3_strlist, "l2e1w12", &el_device3_first, &el_device3_cnt, xmenu_data_device3, '+','-','\0');
M2_VSB(el_device3_vsb, "l2W2r1", &el_device3_first, &el_device3_cnt);
M2_LIST(list_device3) = { &el_device3_strlist, &el_device3_vsb };
M2_HLIST(top_el_device3_menu, NULL, list_device3);

//-------m2 object and constructor-------
M2tk m2(&el_centered_splash_screen, m2_es_arduino, m2_eh_4bs, m2_gh_lc);


void setup(){
	//Menu setup
	m2_SetLiquidCrystal(&lcd, 16, 2);
		
	//LCD setup
	pinMode(contrastPin, OUTPUT);  
	analogWrite(contrastPin, 60);
	lcd.createChar(0, degree);

	//User input setup and attach of interrupts
	PCICR |= (1<<PCIE0);					//Enable interrupt on pin 8
	PCMSK0 |= (1<<PCINT0);					//Tell pin 8 to search for a change in state
	MCUCR = (1<<ISC01) | (1<<ISC00);		//The change is set to FALLING

	attachInterrupt(0, user_input_up, FALLING); //Interrupt on pin 2
	attachInterrupt(1, user_input_down, FALLING); //Interrupt on pin 3

	pinMode(user_up_pin, INPUT_PULLUP); //activating internal pullup resistor
	pinMode(user_down_pin, INPUT_PULLUP); //activating internal pullup resistor
	pinMode(user_select_pin, INPUT_PULLUP); //activating internal pullup resistor

	//Write the menu to LCD
	m2.draw();	
}

void loop(){

//-------Update menu titles-------
	device1_name_string = device1_name;
	device1_menu_name = menu_sub_dot + device1_name_string;
	device1_menu_name.toCharArray(device1_menu_name_char, buffer_size);
	
	device2_menu_name = menu_sub_dot + device2_name;
	device2_menu_name.toCharArray(device2_menu_name_char, buffer_size);
	
	device3_menu_name = menu_sub_dot + device3_name;
	device3_menu_name.toCharArray(device3_menu_name_char, buffer_size);
	
}


//=================Functions=================

void user_input_up(){
	button_time = millis(); 
	
	if (button_time - last_button_time > 250){
		m2.setKey(M2_KEY_PREV);
		m2.checkKey();
		if ( m2.handleKey() )
			m2.draw();
		last_button_time = button_time;
	}
}

void user_input_down(){
	button_time = millis(); 
	
	if (button_time - last_button_time > 250){
		m2.setKey(M2_KEY_NEXT);
		m2.checkKey();
		if ( m2.handleKey() )
			m2.draw();
		last_button_time = button_time;
	}
}


ISR(PCINT0_vect) { //interrupt on pin 8 is set to be a SELECT function
	button_time = millis(); 
	
	if (button_time - last_button_time > 250){
		m2.setKey(M2_KEY_SELECT);
		m2.checkKey();
		if ( m2.handleKey() )
			m2.draw();
		last_button_time = button_time;
	}
} 

void is_device1_empty(m2_el_fnarg_p fnarg){
	if(device1_status){
		m2_SetRoot(&top_el_device1_menu);
	}
	else{
		m2_SetRootExtended(&top_el_device_menu, 0, 0);
	}
	m2.setKey(M2_KEY_SELECT);
	m2.checkKey();
	if ( m2.handleKey() )
		m2.draw();
}

void is_device2_empty(m2_el_fnarg_p fnarg){
	if(device2_status){
		m2_SetRoot(&top_el_device2_menu);
	}
	else{
		m2_SetRootExtended(&top_el_device_menu, 0, 0);
	}
	m2.setKey(M2_KEY_SELECT);
	m2.checkKey();
	if ( m2.handleKey() )
		m2.draw();
}

void is_device3_empty(m2_el_fnarg_p fnarg){
	if(device3_status){
		m2_SetRoot(&top_el_device3_menu);
	}
	else{
		m2_SetRootExtended(&top_el_device_menu, 0, 0);
	}
	m2.setKey(M2_KEY_SELECT);
	m2.checkKey();
	if ( m2.handleKey() )
		m2.draw();
}

void fn_add_device_name_ok(m2_el_fnarg_p fnarg){
	strcpy(device1_name,buffer);
	buffer[name_size];
	device1_status = 1;
	m2_SetRoot(&top_el_device_menu);
}

Hi

ok, i try to answer your questions step by step, maybe in more than one response.

Let me start with M2_TEXT.
The range of valid characters can be updated in the file utility/m2eltext.c, lines 41 and 42:

#define M2_EL_CHAR_MIN ' '
#define M2_EL_CHAR_MAX 'z'

So you could change this to:

#define M2_EL_CHAR_MIN 'A'
#define M2_EL_CHAR_MAX 'Z'

I always wanted to make this more flexible, but did not find time to do so.

Oliver

M2_TOGGLE
You did not post your actual code, so it is difficult to say what is wrong here.
When i do the design of a new menu, i usually start with some blank paper and write down the desired menu:

0123456789012345
Blue:    [?]
[cancel] [ok]

In this example, i personally would use a 2x2 GRIDLIST ("c2") and put each of the elements into one of the corners.
In general scrolling is not supported except for complex elements like STRLIST and 2LMENU. However you can use X2LMENU to implement toggle buttons. It would look like this (not tested):

uint8_t value = 0;
char buf[20];
const char *menu_toggle_cb(uint8_t idx, uint8_t msg)
{  
  /* display some text, depending on current value */
  if ( msg == M2_STRLIST_MSG_GET_STR ) {
    if ( value == 0 )
       strcpy(buf, " blue");
    else
       strcpy(buf, " red");
    return buf;
  }
  /* implement toggle function */
  if ( msg == M2_STRLIST_MSG_SELECT  ) {
     if ( value == 0 )
       value = 1;
     else
       value = 0;
  }
  return "";
}
m2_xmenu_entry xmenu_data[] = 
{
  ...
  { "", NULL, menu_toggle_cb },		
  ...
  { NULL, NULL, NULL },
};

The trick is to use the same callback function to output the menu line and also to change the toggle state.

In general, M2tklib often requires the use of global variables. In this case, "value" is used to store the current status of the "toggle".

Oliver

Hey Oli,

I've tried what you suggested and updated the utility/m2eltext.c to:

#define M2_EL_CHAR_MIN '0'
#define M2_EL_CHAR_MAX 'Z'

However I still have to click my way through all of it (65 times) like before when I upload the sketch after saving the m2eltext.c file again.

Is there something I forgot?

If it isn't scrollable then I guess that's the problem. I will try your example when I can get a hold of the system with the issue in question again (don't have it with me). However I still posted the modified code from the RADIO example here:

/*

  Radio.pde

  LiquidCrystal (16x4) example

  m2tklib = Mini Interative Interface Toolkit Library
  
  Copyright (C) 2011  olikraus@gmail.com

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
*/

#include <LiquidCrystal.h>
#include "M2tk.h"
#include "utility/m2ghlc.h"

LiquidCrystal lcd(12, 11, 7, 6, 5, 4);
const int contrastPin = 10;

uint8_t uiKeySelectPin = 2;
uint8_t uiKeyNextPin = 3;

uint8_t select_color = 0;

void fn_ok(m2_el_fnarg_p fnarg) {
  /* accept selection */
}

void fn_cancel(m2_el_fnarg_p fnarg) {
  /* discard selection */
}

M2_LABEL(el_label1, NULL, "red");
M2_RADIO(el_radio1, "v0", &select_color);

M2_LABEL(el_label2, NULL, "green");
M2_RADIO(el_radio2, "v1", &select_color);

M2_LABEL(el_label3, NULL, "blue");
M2_RADIO(el_radio3, "v2", &select_color);

M2_BUTTON(el_cancel, NULL, "cancel", fn_cancel);
M2_BUTTON(el_ok, NULL, "ok", fn_ok);

M2_LIST(list) = { 
    &el_label1, &el_radio1, 
    &el_label2, &el_radio2,  
    &el_label3, &el_radio3, 
    &el_cancel, &el_ok 
};
M2_GRIDLIST(list_element, "c2",list);
M2tk m2(&list_element, m2_es_arduino, m2_eh_2bs, m2_gh_lc);

void setup() {
  //Menu setup
  m2_SetLiquidCrystal(&lcd, 16, 2);
		
  //LCD setup
  pinMode(contrastPin, OUTPUT);  
  analogWrite(contrastPin, 60);
  m2.setPin(M2_KEY_SELECT, uiKeySelectPin);
  m2.setPin(M2_KEY_NEXT, uiKeyNextPin);
}

void loop() {
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey() )
    m2.draw();
  m2.checkKey();
}

Cb functions for M2_SPACECB.

I would like to redirect you to one of the tutorials: I think it describes a similar situation: Google Code Archive - Long-term storage for Google Code Project Hosting.

Two suggestions:

  • Use global variables to pass information to submenues.
  • Use a global index which tells the submenu the active "device".
  • Avoid SPACECB, instead use the calling menu (that means the button callback of the calling menu) to set the desired root window (as shown in tutorial 8)

The radio button example has been written for a 4-line display, so it will not work for 2-line displays.

Oliver

Muppo:
Hey Oli,

I've tried what you suggested and updated the utility/m2eltext.c to:

#define M2_EL_CHAR_MIN '0'

#define M2_EL_CHAR_MAX 'Z'




However I still have to click my way through all of it (65 times) like before when I upload the sketch after saving the m2eltext.c file again.

Is there something I forgot?

I think you need to fill the string buffer with 'A' first. Will this help?

Oliver

I'll try what you suggested with the tutorial and see if I can get that to work :slight_smile:

I tried changing it to A first and then Z but still have to click my way though everything.

Muppo:
I tried changing it to A first and then Z but still have to click my way though everything.

Then i might need to do a deeper investigation.

Regarding your code (second post):
Is there any reason you manually catch the button inputs? Debounce, pin configuration and check is already build into M2tklib.

Oliver

I would greatly appreciate it if you would check out how to solve that issue :slight_smile:

Regarding your code (second post):
Is there any reason you manually catch the button inputs? Debounce, pin configuration and check is already build into M2tklib.

No particular reason other than that is how I got it to work xD

With m2tklib will it also work as interrupts? I enable interrupt on pin 8 as I use an Arduino DUO R3 which has only 2 external interrupts enabled (pin 2 and 3). As far as I have understood you usually use the loop to catch the button inputs with m2.checkKey(), however, I want to handle it in interrupts and I thought the only way to do that with m2tklib was the way I implemented it (except for the debounce which I never tried without :P)

So I could do it like this then and just use m2_SetPin(M2_KEY_NEXT, 8 ) in order to enable and attach interrupts on pin 8?
wouldn't I have to call m2.checkKey() in the loop then?

Muppo:
No particular reason other than that is how I got it to work xD

With m2tklib will it also work as interrupts? I enable interrupt on pin 8 as I use an Arduino DUO R3 which has only 2 external interrupts enabled (pin 2 and 3). As far as I have understood you usually use the loop to catch the button inputs with m2.checkKey(), however, I want to handle it in interrupts and I thought the only way to do that with m2tklib was the way I implemented it (except for the debounce which I never tried without :P)

So I could do it like this then and just use m2_SetPin(M2_KEY_NEXT, 8 ) in order to enable and attach interrupts on pin 8?
wouldn't I have to call m2.checkKey() in the loop then?

Well, my concern actually is, that you only call

		if ( m2.handleKey() )
			m2.draw();

once in your interrupt.

It is like windows executable: All events are handled and the dialog box gets updated. Internally there is a queue with events and m2tklib might generate its own events. This might also be the reason, why you need to call m2.draw() in "setup()".

Also note that the "loop()" gets executed by the Arduino environment all the time. So the (none m2tklib) code gets executed all the time.

I think it makes more sense to call

		if ( m2.handleKey() )
			m2.draw();

and restrict your interrupts to set the key itself. If you want to use interrupts, then setPin does not make sense, but you sould also replace "m2_es_arduino" by an empty event source:

uint8_t my_m2_es_empty(m2_p ep, uint8_t msg) 
{
  return 0;
}

This will reduce code size a little bit, because the m2tklib key and debounce handling is not included.

Oliver

Regarding M2_TEXT:

It works nicely for me.
Exmple: Set the following in m2eltext.c:

#define M2_EL_CHAR_MIN 'a'
#define M2_EL_CHAR_MAX 'z'

and for the text itself, choose:

char text[10] = "aaaaaaaaa";

or do a strcpy operation.

Oliver

olikraus:
Regarding M2_TEXT:

It works nicely for me.
Exmple: Set the following in m2eltext.c:

#define M2_EL_CHAR_MIN 'a'

#define M2_EL_CHAR_MAX 'z'



and for the text itself, choose:


char text[10] = "aaaaaaaaa";



or do a strcpy operation.

I have implemented most of the things you have said and have been able to reduce the code quite a lot (got rid of all the strings), however, I still cannot get the M2_TEXT to work like I want it to.

I have updated the m2eltext.c file to:

#define M2_EL_CHAR_MIN 'A'
#define M2_EL_CHAR_MAX 'Z'

And if I set the text field to hold "AAAAAAAA" then I can browse through it like you said, except it does not start over after 'Z',
but when the user starts typing I do not want the text field to say "AAAAAAAA" I want an empty field otherwise the user would have to delete all the chars he does not need, is this possible?

I am still working on the code but I got a lot of inspiration from you so far that has really helped me, thank you very much, hope we can figure out the last parts as well :slight_smile:

For M2_TEXT currently only a certain ASCII range is supported. I think more changes are required, if you want ' ' (32) and a range 'A' to 'Z'. The start over was working in my example.

A workaround could be

#define M2_EL_CHAR_MIN ' '
#define M2_EL_CHAR_MAX 'Z'

But you also must do a proper setup of the string with blanks:

char text[10] = "         ";

M2_TEXT is not really a string entry procedure. It is more a character editor for a specific number of single chars.

Oliver

I have created issue 135 for the M2_TEXT topic.

Oliver

I did some implementation for this. Inside m2eltext.c you will find this:

uint8_t m2_is_valid_text_char(uint8_t c)
{
  if ( c >= M2_EL_CHAR_MIN  && c <= M2_EL_CHAR_MAX )
    return 1;
  return 0;
}

/* only space and upper case */
/*
uint8_t m2_is_valid_text_char(uint8_t c)
{
  if ( c == ' ' || ( c >= 'A'  && c <= 'Z' ) )
    return 1;
  return 0;
}
*/

Remove the first procedure and uncomment the second (with the same function name). This should solve your problem.

Attached m2tklib for LiquidCrystal.

Oliver

m2tklib_arduino_lc_1.12pre1.zip (208 KB)

Thank you very much, I will try to implement that tomorrow then.

Really appreciate your extensive help :slight_smile:

I have tried implementing it as you said (deleted the old M2TKLIB folder, downloaded the attached file, and unzipped it and changed around the functions in m2eltext.c as you described.)

It still does not work. I still browse through all the chars and when I then tried to set the char array I write to with M2_TEXT to: " " it starts with '!' and when I reach 'Z' then it does not start over with ' ' but with '['.

Strange. It is working for me. Maybe M2tklib has been installed twice in different locations. Libraries can be located inside the Arduino IDE folder tree and also in your sketch folder.

Oliver