3D Printer Text Based Menu

Life is what happens while you are making other plans. (This quote is a secular rephrasing of Proverbs 16:9 circa 350 B.C. See https://quoteinvestigator.com/2012/05/06/other-plans/)

Well, I have been hit squarely in the head by my proposing and God’s disposing. That makes this post *much* later than it should be. Onward!

PSOC Creator, Debugging, and VMWare: An Aside

With Creator 4.4 (and maybe 4.2), compatibility with VMWare has undergone some changes. It still works, but it has much personality. (That is a code word for a mental condition.) This personality provides some quirky results for debug.

If you are using the CY8CKit-059 break away board (KitProg 2.21) for debugging, when you click on the “bug” icon, many times nothing appears to happen (at least under windows 7). The selection window for what debug device to connect to does not appear. No amount of clicking on the Creator window appears to work. You are stuck.

Something did happen. The debugging menu did show, behind the Creator Main window. The problem with that is the debugging window is a modal dialog. Modal dialogs steal all the keystrokes and mouse clicks, but only if they are on top. The Creator main window is waiting until the modal dialog closes before it accepts any input from the user, which can’t happen because the modal dialog is hiding behind the main window. Either Microsoft broke the “window on top flag” implementation, or Infineon engineers broke displaying the modal dialog. I suspect different rules for Windows 10 versus Windows 7 are at the root of the problem.

Solution

The solution is very simple, but took a while to find out. Go to the VMWare “USB & Bluetooth” menu, and select “Disconnect KitProg” if it is connected. Then go back immediately and select “Connect KitProg.” This will force the modal window into the foreground and allow you to select, connect, and start your debug session. It may take a few seconds for the KitProg to show back up in the Modal Dialog.

MENUS

I have written some MENU code for FreeRTOS which allows the RepRap display’s A/B phased rotary input to work. It uses a button press to select the line the menu indicator ‘>’ is on, and perform some action. In my previous experiences with user menu selection (all the way back to 1976), I have found that (my) menu code always follows the following pattern(s):

  1. Display Line after Line Of Text (LOT) (or icons)
  2. Allow the user to select the LOT (or icon) by some indication. (Either a number on a keyboard or a button press)
  3. Perform immediate action
  4. Give the User some Feedback that indicates the action was performed. (Usually a click or a beep)

Ideally, the menu text can be modified without having to write new lines of code. In most cases, as your menu grows, something will be forgotten, and the menu code needs updates. After a certain amount of time, the menu code can be frozen and used as is, with only menu text/icons changing.

The following code *should* provide a fairly robust menu. No guarantees.

The Main Structure

The menu task needs a C structure to allow keeping together the items needed for easy access by the menu interpreter/handler. This structure consists of several enum variables, a pointer to a function, and a pointer to text to display.

The use of enum variables are used to allow the compiler to flag you if you enter the wrong kind of value while setting up the array. I try to allow the compiler to tell me when I make mistakes. This makes it more likely that my first attempt at running new code does not result in a fire, a real possibility with a 3D printer. (An Aside: The old Motorola 6800 processor had an unlisted instruction that experimenters called HCF. If that instruction was executed, the processor would Halt and heat up. If left alone, it would burn up the processor, and could cause paper to burn. So, Halt and Catch Fire (HCF) became the instruction moniker. You could literally destroy a computer with that instruction if it were left powered in that state long enough.)

Each Menu can cause another menu to be shown. (i.e. more menu items) Each menu can also execute code (i.e. call a function). The menu interpreter uses the enums to tell it to either exit to a top level menu (or leave the menu system), show a sub-menu, or execute a function when the menu is selected.

Header File

Following is the header file for the submenu system. This is rather simple. The #ifndef _MENU_H_ / #define _MENU_H_ allows you to include this file in other headers and files without accidentally re-running any #defines, causing compiler complaints or errors:

   #ifndef _MENU_H_
    #define _MENU_H_
/* SOCino 3d printer firmware, FreeRTOS version
 *
 * Copyright 2021, Wade Maxfield
 * Written by Wade Maxfield
 *
 * Commercial license Available.
 *
 * 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/>.
 *
   This license does not override previous licenses
 */
#include "main.h"
#include "Configuration.h"
       void vAltStartMenuTask(int16_t priority);// MenuTask.c

    extern QueueHandle_t       MenuTaskQueue;          // Queue for messages


enum SubMenuSelectionEnum {
     noSubMenu=0
    ,subMenuExitToPreviousMenu  // keep a menu stack and move back
    ,subMenuExitMenuSystem  // shut down menus, exit to normal display
    ,subMenuMainMenu
    ,subMenuExecuteFunction
    ,subMenuPrinterInfo
    ,numberOfItemsInSubMenuSelectionEnum
};

enum MenuSelectionEnum {
     noMenuSelection=0
    ,menuSelectionExitLevel     // exit this menu level
    ,menuShowPrintVolume        // show printer xyz information
    ,numberOfItemsInMenuSelectionEnum
};
    
typedef struct menu_map_struct {
    enum SubMenuSelectionEnum   SubmenuID;          // This sub menu id
    enum MenuSelectionEnum      MenuItemID;         // menu item selection ID
    enum SubMenuSelectionEnum   SubmenuIDToMoveTo;  // sub menu to show if this is pressed (0 for no item) 
    uint16 (*MenuFunction)(const struct menu_map_struct *me,TaskMonitor_t *TaskMonitorPtr ); 
    char                       *MenuItemText;    
} MENU_MAP_t;


#define BUTTON_MENU_SELECT_PIN D35_D7_pin

#endif
/* [] END OF FILE */

Menu Strings

For the currently selected display, we have a 16 character wide display. The ‘>’ character is used to show the currently selected menu item. Pressing the rotary switch in selects that line of text. Therefore, each menu line must only use 15 characters. The first character is always a space, and the menu engine replaces that character with the ‘>’ character.

If you are showing information, you can use all of the 16 characters across the screen. I use sprintf() to format lines easily, but that can easily lead to too many characters in the string, so be careful when you craft dynamic menus.

#ifndef _MENUSTRINGS_H_
    #define _MENUSTRINGS_H_
/* SOCino 3d printer firmware, FreeRTOS version
 *
 * Copyright 2021, Wade Maxfield
 * Written by Wade Maxfield
 *
 * Commercial license Available.
 *
 * 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/>.
 *
    This license does not override previous licenses
 */
#include "includes.h"
#include "Configuration.h"

    /*
       Top Level Menu:
    
        Exit Menus
        Printer Information
        
    */

#if USE_LANGUAGE == ENGLISH_LANGUAGE
    //-------------------------------------------------------------------------
    // TOP LEVEL MENU
    //      only 15 chars allowed per menu (the '>' selection char is char 0)
    //                                      0123456789012345 <--no chars past 5
    //-------------------------------------------------------------------------
    #define MENU_EXIT_MENU_STRING          " Exit Menu      "
    #define MENU_PRINTER_INFO_STRING       " Printer Info   "
    //-------------------------------------------------------------------------
    // PRINTER INFO MENU
    //      only 15 chars allowed per menu (the '>' is char 0)
    //                                           0123456789012345 <--no chars past 5
    //-------------------------------------------------------------------------
    #define MENU_PRINT_VOLUME_LIMITS_STRING     " Print Vol. Info"


    //-------------------------------------------------------------------------

    
    
    // Show Print Volume info
    //      only 16 chars allowed.  sprintf() format, be careful to not exceed width
    //                              1234567890123456 <--no chars past 6
    #define MENU_PRINT_VOLUME_1    "Printer Capacity"
    #define MENU_PRINT_VOLUME_2    "X Axis: %d mm"
    #define MENU_PRINT_VOLUME_3    "Y Axis: %d mm"
    #define MENU_PRINT_VOLUME_4    "Z Axis: %d mm"
    #define MENU_PRINT_VOLUME_5    "                "
    #define MENU_PRINT_VOLUME_6    "----------------"
    #define MENU_PRINT_VOLUME_7    "Push Button To  "
    #define MENU_PRINT_VOLUME_8    "Return To Menu  "
    
    
#endif



#endif


/* [] END OF FILE */

The Code

In the following code, the MenuMap is the controlling array for the menu, providing the instructions for the menu interpreter/handler. Examine the code, read the comments, debug it in Creator to understand how it is working.

ISR’s

The hardware used is shown in the previous post. It is deceptively simple.

The CY_ISR(EncPressISR){} is an interrupt service routine that posts to the menu task queue the fact that a button press occurred. The menu task runs the menu interpreter, which handles the menu system. (See the previous post for the PSOC internal hardware behind the menu system.)

The CY_ISR(EncoderChangeOccurred){} is an interrupt service routine that also posts to the menu task queue the fact the user twisted the rotary switch. I found that the ISR can execute while the rotary switch is still bouncing, causing nothing to be read. Through experimentation, a maximum of 10 consecutive reads allows the code to see if A is high (with B being low), or the reverse. Once a bit is read high, the encoder has been successfully read, and the routine can continue. Since this ISR triggers due to user interaction, the system is not fully in real time mode (more or less), so spending a few extra microseconds in the encoder ISR does not hurt.

/* SOCino 3d printer firmware, FreeRTOS version
 *
 * Copyright 2021, Wade Maxfield
 * Written by Wade Maxfield
 *
 * Commercial license Available.
 *
 * 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/>.
 *
    This license does not override previous licenses
 */
#include "Menus/MenuTask.h"
#include "Menus/MenuStrings.h" // all menu text is in the MenuStrings.h file
#include "Beep/BeepTask.h"
#include "Display/Display.h"

#if ENABLE_DISPLAY

/*
    Basically, use the menu of the following form:
    >Menu Option 1
     Menu Option 2
     Menu Option 3
     .
     Menu Option 8 // 8 lines 16 chars

    1) Rotating the knob will move the '>' up or down
    2) Clicking the knob (pressing it down) will select that menu item.
    


*/
    
#define MenuTaskSTACK_SIZE  (configMINIMAL_STACK_SIZE*3) // sprintf used
#define MenuTask_QueueSize   (4)// random number


// variables
QueueHandle_t       MenuTaskQueue;          // Queue for messages


    // external menu functions
#include "Menus/MenuHandlerFunctions.h"
    
// static const puts the following array into flash, consuming no ram.
static const MENU_MAP_t MenuMap[] = {
//      SubMenuId           MenuItemId              SubmenuIDToMoveTo       MenuFunction        MenuText (MenuStrings.h)
     {  subMenuMainMenu,    menuSelectionExitLevel, subMenuExitMenuSystem,  0,                  MENU_EXIT_MENU_STRING }
//                          this selection only moves to sub menu "PrinterInfo"   
    ,{  subMenuMainMenu,    noMenuSelection,        subMenuPrinterInfo,     0,                  MENU_PRINTER_INFO_STRING }

    // each submenu is listed in order of strings shown on display
    ,{  subMenuPrinterInfo, menuSelectionExitLevel, subMenuMainMenu,        0,                  MENU_EXIT_MENU_STRING }
    // the following submenu line calls the function mhfShowPrintVolume(menuMapPtr,TaskInfoPtr)
    // to show text calculated inside the function on the screen 
    // mhfShowPrintVolume() returns pdTRUE so a button press will magically exit the submenu.
    ,{  subMenuPrinterInfo, menuShowPrintVolume,    subMenuExecuteFunction,mhfShowPrintVolume,  "" }
         
};

#define MENU_STACK_DEPTH 32

static int16 gMenuStack[MENU_STACK_DEPTH+1];     // id's of the menus being traveled
       int16 gMenuItemSelected;                 // current menu item selected in the MenuMap[]
static int16 gMenuStackPointer;                  // pointer to current menu id
static int16 gNumberOfMenuItemsOnCurrentSubmenu;
static int16 gMenuIndicator;
static int16 ReturnFromMenuHandlerFunctionWhenButtonPressedFlag;

uint16               gEncoderStatus;            // updated by the IRQ
static uint16       _encoderChangeCount;        // incremented by one when we detect encoder changes
static uint16       _buttonPressCount;          // incremented by one when we detect button press
static int16        gMenuIsControllingDisplay;  // set when menu is controlling display



//----------------------------------------------------------------------------------------
// display the text for the submenu on the display.
//----------------------------------------------------------------------------------------
void showSubMenu(enum SubMenuSelectionEnum submenuID,TaskMonitor_t *TaskMonitorPtr,int16 _ClearDisplay){
    int16 i,i1;
    int16 max =sizeof(MenuMap)/sizeof(MENU_MAP_t);
    int16 found=0;
    const MENU_MAP_t *menuMapPtr;
    
    if (_ClearDisplay)
        clearDisplay(TaskMonitorPtr);
    
    // find the first matching submenu
    for (i1 =0 ; i1 < max; i1++){
        if (MenuMap[i1].SubmenuID==submenuID){
            found=1;
            break;
        }
    }
    if (!found)
        return; // error, bail silently
   
    // i1 now points to the first submenu.  now print all the one up to 16
    // *NO* formatting (sprintf) on menu items for now
    for (i=0; i < gNumberOfMenuItemsOnCurrentSubmenu && i1 < max; i1++){
        menuMapPtr=&MenuMap[i1];
        if (menuMapPtr->SubmenuID==submenuID){
            if (menuMapPtr->MenuFunction){
                // if the function pointer is corrupted, we are toast here.
                // if function returns non-zero, button push will bump up a menu level
                ReturnFromMenuHandlerFunctionWhenButtonPressedFlag =
                    (menuMapPtr->MenuFunction)(menuMapPtr,TaskMonitorPtr ); 
            } else {
                showTextOnTheDisplay(i,0,(uint8*)(menuMapPtr->MenuItemText),1, 50,TaskMonitorPtr);
                if (i==gMenuIndicator){
                   char c[3]={ '>',' ',0 };
                   c[1]= menuMapPtr->MenuItemText[1];// pick up the character the > is next to
                    showTextOnTheDisplay(i,0,(uint8*)c,1, 50,TaskMonitorPtr);                
                }       
            }

           i++; //increment the item number index, we printed it on display
        }                   
    }    
    
    //showTextOnTheDisplay(gMenuIndicator,0,">",1,50,TaskMonitorPtr);// show the indicator

}
//----------------------------------------------------------------------------------------
// return the number of items in the submenu selected. Linear search.
// We are in the menu system at the moment.  time is not an issue
//----------------------------------------------------------------------------------------
static int16 getCountOfItemsInCurrentSubmenu(enum SubMenuSelectionEnum subMenu) {
    int16 i=0;
    int16 numItems=0;
    int16 max =sizeof(MenuMap)/sizeof(MENU_MAP_t);
    
    for (i=0; i < max; i++){
        if (MenuMap[i].SubmenuID==subMenu){
            numItems++;
        }
    }
    
    return numItems;
}

//----------------------------------------------------------------------------------------
// return array index of first item in the subMenu
// We are in the menu system at the moment.  time is not an issue
//----------------------------------------------------------------------------------------
static int16 getFirstItemIndexOnSubMenu(enum SubMenuSelectionEnum subMenu){
    int16 i=0;
    int16 max =sizeof(MenuMap)/sizeof(MENU_MAP_t);
    
    for (i=0; i < max; i++){
        if (MenuMap[i].SubmenuID==subMenu){
            return i;
        }
    }
    
    return 0;
}
//----------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------
void handleMenuSetup(enum SubMenuSelectionEnum subMenu,TaskMonitor_t *TaskMonitorPtr){

    gMenuIndicator=0;
    gMenuItemSelected=getFirstItemIndexOnSubMenu(subMenu); // item 0 is the "return to previous menu" item
    gNumberOfMenuItemsOnCurrentSubmenu = getCountOfItemsInCurrentSubmenu(subMenu);
    showSubMenu(subMenu,TaskMonitorPtr,pdTRUE);

}
//----------------------------------------------------------------------------------------
// back up a menu item level and display it
//----------------------------------------------------------------------------------------
static void popMenuStackAndShowMenu(TaskMonitor_t *TaskMonitorPtr){
    enum SubMenuSelectionEnum subMenu=subMenuMainMenu;
    
    if (gMenuStackPointer >0)
        gMenuStackPointer--;
    else
        gMenuStackPointer=0;
    
    subMenu = gMenuStack[gMenuStackPointer];
    handleMenuSetup(subMenu,TaskMonitorPtr);    
}

//----------------------------------------------------------------------------------------
// The button is pressed. Select the menu item
//----------------------------------------------------------------------------------------
void handleButtonPress(TaskMonitor_t *TaskMonitorPtr,int16 _PhysicalButtonWasPressed){
    enum SubMenuSelectionEnum subMenu=subMenuMainMenu;
    const MENU_MAP_t *MenuMapPtr=0 ;
    
        if (!gMenuIsControllingDisplay){
            gMenuIsControllingDisplay=pdTRUE;
            gMenuStackPointer=1;// set up to be first menu item  
            //                command         from task  to task
            postTaskMessage(cmdMenuTaskActive,MENU_TASK, DISPLAY_TASK);
            postTaskMessage(displayClearCommand,MENU_TASK, DISPLAY_TASK);
            gMenuStack[gMenuStackPointer]=subMenu;
            handleMenuSetup(subMenu,TaskMonitorPtr);
            return;
        }
    
       if (_PhysicalButtonWasPressed){
            // the rotary switch button was pressed.
            
            // if ReturnFromMenuHandlerFunctionWhenButtonPressedFlag is non-zero, then
            // back up a menu level
            if (ReturnFromMenuHandlerFunctionWhenButtonPressedFlag){
                ReturnFromMenuHandlerFunctionWhenButtonPressedFlag=0;//clear this flag
                popMenuStackAndShowMenu(TaskMonitorPtr);
                return;
            }
            
            //handle the menu item
            if (!gMenuStackPointer){
                // we are not in the menu system.  set up for first menu
                gMenuStackPointer++;
                gMenuStack[gMenuStackPointer]=subMenu;
                handleMenuSetup(subMenu,TaskMonitorPtr);
                return;
            } else{
                MenuMapPtr = &MenuMap[gMenuItemSelected];
                
                switch(MenuMapPtr->SubmenuIDToMoveTo){
                    case   noSubMenu://=0
                    break;
                                            
                    case subMenuExitToPreviousMenu:  // keep a menu stack and move back
                        gMenuStackPointer--;
                        if (gMenuStackPointer ==0){
                            gMenuIsControllingDisplay=pdFALSE;
                            postTaskMessage(displayClearCommand,MENU_TASK, DISPLAY_TASK);
                            postTaskMessage(cmdMenuTaskFinished,MENU_TASK, DISPLAY_TASK);
                            return;
                        }
                    break;

                    case subMenuExitMenuSystem:  // shut down menus, exit to normal display
                        gMenuIsControllingDisplay=pdFALSE;
                        gMenuStackPointer=0;
                        postTaskMessage(displayClearCommand,MENU_TASK, DISPLAY_TASK);
                        postTaskMessage(cmdMenuTaskFinished,MENU_TASK, DISPLAY_TASK);
                    break;
                        
                    case subMenuMainMenu:
                    case subMenuPrinterInfo:
                        // the user has selected a submenu.  display that menu
                        gMenuStackPointer++;
                        gMenuStack[gMenuStackPointer]=subMenu=MenuMap[gMenuItemSelected].SubmenuIDToMoveTo;

                        handleMenuSetup(subMenu,TaskMonitorPtr);
                    break;
                        
                    default:
                        break;
                }
            }                   
        }    
}
//----------------------------------------------------------------------------------------
// move the indicator down, along with the item selected
// return 1 if change occurred, 0 otherwise
//----------------------------------------------------------------------------------------
int16 moveIndicatorDown(){
    
    
    // point to the next menu item
    gMenuIndicator++,   gMenuItemSelected++;
    
    // if we went past the end of the menus for this submenu, 
    // then back up and exit
    if (gMenuIndicator >= gNumberOfMenuItemsOnCurrentSubmenu){
        gMenuIndicator --,gMenuItemSelected--;
        return 0;
    }
    // if we are at a no-subMenu selection, then the pointer can't be located here
    // because there are no submenus.  Back up and exit
    if ( MenuMap[gMenuItemSelected].SubmenuIDToMoveTo == noSubMenu){
        gMenuIndicator --,gMenuItemSelected--;
        return 0;       
    }

    
    return 1;
}
//----------------------------------------------------------------------------------------
// move the indicator '>' up the display
// return 1 if change occurred, 0 otherwise
//----------------------------------------------------------------------------------------
int16 moveIndicatorUp(){
        gMenuIndicator --,gMenuItemSelected--;

    if (gMenuIndicator < 0){
        gMenuIndicator ++,gMenuItemSelected++;
        return 0;
    }
    
    return 1;
    
}
//----------------------------------------------------------------------------------------
// if encoder status changed, return 1, else return 0
//----------------------------------------------------------------------------------------
int16 handleEncoderChange(uint16 *encoderChangeCount,uint16 *encoderStatus,TaskMonitor_t *TaskMonitorPtr){
    (void)encoderChangeCount;
    uint16 es = *encoderStatus;
    
    if (!es || es==3)
        return 0; // ignore
    
    if (es == 1 ){
       if ( !moveIndicatorUp())
            return 0;
    } else {
        
        // es == 2
       if (! moveIndicatorDown())
            return 0;
    }
        
      
    showSubMenu(gMenuStack[gMenuStackPointer],TaskMonitorPtr,pdFALSE);
    
    return 1;
}

//-----------------------------------------------------------------------------
// This button handler code relies on software debounce.
//-----------------------------------------------------------------------------
static TASK_MESSAGE_t _bTaskMessage = { cmdButtonPress, MENU_TASK, MENU_TASK, 0, {0}};
static TASK_MESSAGE_t *bMsgPtr = &_bTaskMessage;
static uint32         _buttonTime;
//-----------------------------------------------------------------------------
CY_ISR(EncPressISR){
    BaseType_t xHigherPriorityTaskWoken=pdFALSE;

// if this is a switch bounce, ignore.
    if ( getElapsedTimeInMilliseconds(_buttonTime)<MINIMUM_TIME_IN_MS_BETWEEN_BUTTON_PRESSES)
        return;
    _buttonTime = getSystemTimeInMs();
    
    ++_buttonPressCount;// visual indicator for debugging
    xQueueSendFromISR(MenuTaskQueue,&bMsgPtr  , &xHigherPriorityTaskWoken);
}
//-----------------------------------------------------------------------------
// This IRQ is for encoder button changes. 
//-----------------------------------------------------------------------------
static TASK_MESSAGE_t _eTaskMessage = { cmdEncoderChange, MENU_TASK, MENU_TASK, 0, {0}};
static TASK_MESSAGE_t *eMsgPtr = &_eTaskMessage;
CY_ISR(EncoderChangeOccurred){
    int16 es;
    int16 count=0;
    
    es = srEncoder_Read() & 0x3;// only 2 bits are exposed 
    // sometimes we are so fast we get there and the transition has not finished
    while (!es && count < 10){
        es = srEncoder_Read() & 0x3;
    }
    
    // if es == 3, we were too slow to capture the signal.
    // nothing to do but return in that case
    // if es is still 0, then a glitch happened while both lines were
    // going negative or are both negative.  Leave in that case also.
    if (!es || es==3)
        return;
    
    BaseType_t xHigherPriorityTaskWoken=pdFALSE;

    ++_encoderChangeCount;// visual indicator for debugging
    // read again for those occasional moments we are too fast
    _eTaskMessage.data[0]=es;
    xQueueSendFromISR(MenuTaskQueue,&eMsgPtr  , &xHigherPriorityTaskWoken);
    
}


//-----------------------------------------------------------------------------
// Task to Handle Menus
//-----------------------------------------------------------------------------
static portTASK_FUNCTION( vMenuTask, pvParameters )
{
    static TaskMonitor_t *TaskMonitorPtr = &TaskMonitorArray[MENU_TASK];
    static TASK_MESSAGE_t *mdPtr;
    (void) pvParameters;
    TickType_t xTicksToWait = portMAX_DELAY;
    //static int16 v;
    
    // be sure to add in some padding to queue size for race conditions
    MenuTaskQueue = xQueueCreate( MenuTask_QueueSize,sizeof( TASK_MESSAGE_t * ) );
       
    while( !MenuTaskQueue  )   	{
    		// Queue was not created and must not be used.
            // panic here.
            DebugPrintString(MENU_TASK_Q_NOT_CREATED_STRING);
            vTaskDelay(pdMS_TO_TICKS(1000));// warn user the system is dead
            MenuTaskQueue = xQueueCreate( MenuTask_QueueSize, 
                                            sizeof( TASK_MESSAGE_t * ) );
	}     

  extern void initializeMenuHardware();// hardware init.c
  initializeMenuHardware();
    
    GCodePrintString(MENU_TASK_STARTED_STRING CRLF);
    

    for(;;){
        TaskMonitorPtr->runCounter++;// crude profile indicator.
        
        //----------------------------------------------------------------------
        // Various tasks post here, and display the strings or graphics (future) posted
        //----------------------------------------------------------------------
        if ( QReceive( MenuTaskQueue, &mdPtr, xTicksToWait ,TaskMonitorPtr) == pdPASS ){
            if (mdPtr){
                switch(mdPtr->command){
                    case cmdButtonPress:{//0x801
                        beep(100,TaskMonitorPtr);// beep for button pressRotarySwitchQuadratureDecoderCount,(uint16)mdPtr->data[0],TaskMonitorPtr);                                              
                        handleButtonPress(TaskMonitorPtr,pdTRUE);
                    }break;
                    
                    case cmdEncoderChange://0x802
                        gEncoderStatus = mdPtr->data[0];
                        mdPtr->data[1]=0;
                        handleEncoderChange(&_encoderChangeCount,&gEncoderStatus,TaskMonitorPtr);
                                            
                         //extern void showTextOnTheDisplay(int16 lineNumber,int16 offset,uint8 *text,int16 numberOfTriesToMake, 
                         //       int16 delayMsBetweenTries,TaskMonitor_t *TaskMonitorPtr);
                    
                    break;
                        
                    case cmdMenuTaskActive: // 0x803 <-- menuTask takes over display
                        
                    break;
                        
                    case cmdMenuTaskFinished:// 0x804 <-- menuTask is finished
                        
                    break;
                        
                    default: //EndOfCommands
                    break;
                                
                } //switch
            }// if(mdPtr)
        }  // if QReceive
          
    }// for(;;)
}//portTASK_FUNCTION

//----------------------------------------------------------------------------------------
// start the menu subsystem
//----------------------------------------------------------------------------------------
void vAltStartMenuTask(int16_t priority){
    
        	xTaskCreate( vMenuTask,
                    MENU_TASK_STRING , //task name
                    MenuTaskSTACK_SIZE, 
                    (void *)NULL, 
                    priority, 
                    ( TaskHandle_t * ) &TaskMonitorArray[MENU_TASK].taskHandle ); 
}


#endif

/* [] END OF FILE */

User Provide Menu Handler Functions

Often, a menu selection requires specific code to be executed. To allow the menu interpreter to be as stand alone as possible, pointers to functions are included in the structure. If a menu function is to be called, the user references it in a header file, puts it into the MenuMap array initializer, and indicates through the enum that the function will be called when the user selects that menu.

The “static const ” in front of the array initializer forces the initializer contents into flash rather than ram, so it can’t be re-written due to typical rogue code. (Unless the flash itself is being modified, which can happen!) The following Header File references the menu function placed in the array through this line of code. Hopefully the comment lines explain the process:

    // the following submenu line calls the function mhfShowPrintVolume(menuMapPtr,TaskInfoPtr)
    // to show text calculated inside the function on the screen 
    // mhfShowPrintVolume() returns pdTRUE so a button press will magically exit the submenu.
    ,{  subMenuPrinterInfo, menuShowPrintVolume,    subMenuExecuteFunction,mhfShowPrintVolume,  "" }
#ifndef _MENU_HANDLER_H_
    #define _MENU_HANDLER_H_
/* SOCino 3d printer firmware, FreeRTOS version
 *
 * Copyright 2021, Wade Maxfield
 * Written by Wade Maxfield
 *
 * Commercial license Available.
 *
 * 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/>.
 *
   This license does not override previous licenses
 */
#include "Configuration.h"
#include "Menus/MenuTask.h"
/*
    Return 1 from MenuHandlerFunction to cause MenuTask to exit this submenu when
    the Rotary Switch Button is Pressed.
*/
extern uint16 mhfShowPrintVolume(const struct menu_map_struct *me,TaskMonitor_t *TaskMonitorPtr );


#endif

/* [] END OF FILE */

The User Menu Handler Function

A basic menu function showing the current printer configuration follows. The sprintf() function is used extensively. Note that when the printer menu task is created, enough stack space must be allocated so the sprintf() function does not overflow and cause strange operation of the printer:

/* SOCino 3d printer firmware, FreeRTOS version
 *
 * Copyright 2021, Wade Maxfield
 * Written by Wade Maxfield
 *
 * Commercial license Available.
 *
 * 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/>.
 *
    This license does not override previous licenses
 */
#include "includes.h"
#include "Menus/MenuTask.h"
#include "Menus/MenuStrings.h" // all menu text is in the MenuStrings.h file
#include "Menus/MenuHandlerFunctions.h"

#if ENABLE_DISPLAY
static uint8 outMsg[32];
    

//----------------------------------------------------------------------------------------
// print out information regarding the current printer characteristics
// This function requires sprintf and access to the config structure
//    Return 1 from MenuHandlerFunction to cause MenuTask to exit this submenu when
//    the Rotary Switch Button is Pressed.
//----------------------------------------------------------------------------------------
uint16 mhfShowPrintVolume(const struct menu_map_struct *me,TaskMonitor_t *TaskMonitorPtr ){

/*   MENU_PRINT_VOLUME_1    "Printer Capacity"
     MENU_PRINT_VOLUME_2    "X Axis: %d mm"
     MENU_PRINT_VOLUME_3    "Y Axis: %d mm"
     MENU_PRINT_VOLUME_4    "Z Axis: %d mm"
     MENU_PRINT_VOLUME_5    "                "
     MENU_PRINT_VOLUME_6    "                "
     MENU_PRINT_VOLUME_7    "Push Button     "
     MENU_PRINT_VOLUME_8    "To Exit         "
*/
    showTextOnTheDisplay(1,0,(uint8*)MENU_PRINT_VOLUME_1,1, 50,TaskMonitorPtr);
    sprintf((char*)outMsg,MENU_PRINT_VOLUME_2,config.X_Maximum-config.X_Minimum);
    showTextOnTheDisplay(2,0,(uint8*)outMsg             ,1, 50,TaskMonitorPtr);
    sprintf((char*)outMsg,MENU_PRINT_VOLUME_3,config.Y_Maximum-config.Y_Minimum);
    showTextOnTheDisplay(3,0,(uint8*)outMsg             ,1, 50,TaskMonitorPtr);
    sprintf((char*)outMsg,MENU_PRINT_VOLUME_4,config.Z_Maximum-config.Z_Minimum);
    showTextOnTheDisplay(4,0,(uint8*)outMsg             ,1, 50,TaskMonitorPtr);
    showTextOnTheDisplay(5,0,(uint8*)MENU_PRINT_VOLUME_5,1, 50,TaskMonitorPtr);
// following not shown 
   //showTextOnTheDisplay(6,0,(uint8*)MENU_PRINT_VOLUME_6,1, 50,TaskMonitorPtr);
    //showTextOnTheDisplay(6,0,(uint8*)MENU_PRINT_VOLUME_7,1, 50,TaskMonitorPtr);
    //showTextOnTheDisplay(7,0,(uint8*)MENU_PRINT_VOLUME_8,1, 50,TaskMonitorPtr);

    return 1;// returning 1 causes menu to bump up a level when button pressed
}
    
#endif

/* [] END OF FILE */

Next Time

The next time will be delayed again due to Trade Shows and Other Things Of Importance (OTOI). At this point, none of the M20 through M29 functions are implemented. Implemented are G0, G1, G4, G20, G21, G28, G90, G91, and G92. Also, M0, M17, M18, M82, M83, M84, M104, M105, M106, M107, M109, M111, M113, M114, M115 are implemented. Some testing will resume in about 30 days or so (hopefully). The third generation of the adapter card is completed and delivered. I have to build it in order to test it. Once done, I expect to upload the Eagle file(s) at some point. I need to know I can control a printer with no cuts/jumpers on the board before uploading.

I don’t know what the next post will be about. I think it is about time to do a “dry run” on a print, to make sure I can print a small cube (conceptually), without any filament in the system (and possibly with the heater at a very low level to not carbonize any left over plastic in the head.)

If I come across any interesting PSOC5LP gotcha’s, I will do a brief post about them when the RT (round tuit) comes into my posession.

Enjoy!

Add a Comment

Your email address will not be published.