Maker Pro
Maker Pro

MP3 player - timeout and "interrupts"

BlueObsecurit

Mar 24, 2021
36
Joined
Mar 24, 2021
Messages
36
Hi.
Those of you who answer my threads may remember me.
My mp3 player project is coming along nicely.
I have now got the menu logic working and I'm quite happy with it so far.

My problem is that I am currently struggling to implement some functionality.

My menu logic works like this:
1. Setup code sets the initial menu and position of the cursor.
2. Main loop reads menu value and position value and waits for input
3. Input is received, code is executed and menu changed if applicable.

First, I am trying to implement a timeout. If no input is received for 10 seconds, it should go to a special "menu" where OLED screen power save is turned on and the menu navigation buttons are redefined to change songs instead. This is important. Currently I start a function that waits for 10 secs then sets the menu to that special case, this doesnt work at all because it does not run continously. How could I implement this?

Secondly I am trying to implement volume up and down and play/pause button. The problem is that my menu code is waiting for input, so it does not recognise when these buttons are pressed. I cannot use interrupts because the Seeeduino Xiao stops using pins 5 and 7 which I use for serial communication with the player and I2C communication with the OLED screen. I want these buttons to work at all times because volume up/down is important in case of sudden volume increase due to whatever reason.

Options: I could add these buttons to my code in each and every case of the menu, but this is very excessive. I could abandon my requirement to always have them functional by implementing functionality only in the timeout menu above. However I was hoping there may be easier ways.

Regarding pins 7 and 5 not working when using interrupts, it says to see this: https://github.com/Seeed-Studio/ArduinoCore-samd/blob/master/variants/XIAO_m0/variant.cpp for more info, but I am not too sure what I am looking at.

I'll post my code, since I know you'll ask, but it is very long, messy some places, contains legacy snippets commented out and things that needs to be redone and finally misses some functionality. I don't really expect you to read it entirely.

I welcome any improvement on the code regardless of being out of scope with my questions.
Sorry if I missed something, let me know if I need to provide more info or anything else.

Code:
#include <SoftwareSerial.h>
#include <DFPlayerMini_Fast.h>
#include <Button.h>
#include <U8g2lib.h>
#include <U8x8lib.h>
#include <Wire.h>




DFPlayerMini_Fast player;

Button btnMenu(10);
Button btnLeft(9);
Button btnRight(8);
Button btnPlayPause(3);
Button btnVolUp(1);
Button btnVolDown(2);

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ A5, /* data=*/ A4);

//SoftwareSerial mySerial(1, 2); // RX, TX

int menuState = 1; //0 menu is continue playing music. 1 is main menu, 2 is folder menu, 7 is folder 2, 3 is play mode, 4 is EQ menu, 5 is music menu
int cursorPos = 0; //0 is first position in every menu, but specifics may depend on menu
int folderNr = 0; //number to pass to shuffle function
unsigned long int tracks[999];
unsigned long timeout = 0;

const byte busyPin = 0;


// 'pixil-frame-0(1)', 128x32px
const uint8_t mainMenu[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
   0x00, 0x00, 0x0e, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xff, 0xff, 0x01,
   0x00, 0x00, 0x03, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00, 0x18, 0x0c, 0x00,
   0x80, 0x00, 0x00, 0x02, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x60, 0x10, 0x00,
   0x00, 0x04, 0x10, 0x00, 0x80, 0x08, 0x21, 0x02, 0x00, 0x00, 0x19, 0x00,
   0x00, 0x18, 0x70, 0x00, 0x00, 0x02, 0x20, 0x00, 0x80, 0x08, 0x21, 0x02,
   0x00, 0x00, 0x13, 0x00, 0x00, 0x06, 0x4c, 0x00, 0x00, 0xc2, 0x21, 0x00,
   0x80, 0x08, 0x71, 0x02, 0x00, 0x00, 0x37, 0x00, 0x00, 0x01, 0x43, 0x01,
   0x00, 0x21, 0x42, 0x00, 0x80, 0x1c, 0x51, 0x02, 0x00, 0x00, 0x6f, 0x00,
   0x00, 0xc1, 0x40, 0x03, 0xc0, 0xf1, 0x44, 0x00, 0x80, 0x94, 0x53, 0x02,
   0x00, 0x00, 0xf9, 0x00, 0x00, 0x3a, 0x40, 0x07, 0x40, 0xc0, 0x44, 0x00,
   0x80, 0x94, 0x52, 0x02, 0x00, 0x00, 0x01, 0x00, 0x80, 0x16, 0x40, 0x03,
   0x80, 0x60, 0x44, 0x00, 0x80, 0x94, 0x72, 0x02, 0x00, 0x78, 0x01, 0x00,
   0xc0, 0x1a, 0x40, 0x01, 0x80, 0x60, 0x44, 0x00, 0x80, 0x9c, 0x72, 0x02,
   0x00, 0x84, 0x01, 0x00, 0xe0, 0x14, 0x40, 0x00, 0x00, 0x31, 0x42, 0x00,
   0x80, 0x9c, 0x23, 0x02, 0x00, 0x82, 0x01, 0x00, 0xc0, 0x1c, 0x60, 0x00,
   0x00, 0x31, 0x21, 0x00, 0x80, 0x88, 0x23, 0x02, 0x00, 0xc6, 0x01, 0x00,
   0x80, 0x18, 0x18, 0x00, 0x00, 0x1a, 0x22, 0x00, 0x80, 0x08, 0x21, 0x02,
   0x00, 0xfe, 0x01, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x1a, 0x14, 0x00,
   0x00, 0xff, 0xff, 0x01, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x90, 0x01, 0x00,
   0x00, 0x0c, 0x08, 0x00, 0x00, 0xfe, 0xff, 0x00, 0x00, 0xfc, 0x00, 0x00,
   0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

// 'folder1', 128x32px
const uint8_t folder1[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00,
   0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00,
   0x00, 0x00, 0x13, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00, 0xc0, 0x20, 0x00,
   0x00, 0xc0, 0x20, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x60, 0x10, 0x00,
   0x00, 0x30, 0xe0, 0x00, 0x00, 0x30, 0xe0, 0x00, 0x00, 0x30, 0xe0, 0x00,
   0x00, 0x18, 0x70, 0x00, 0x00, 0x0c, 0x98, 0x00, 0x00, 0x0c, 0x98, 0x00,
   0x00, 0x0c, 0x98, 0x00, 0x00, 0x06, 0x4c, 0x00, 0x00, 0x02, 0x86, 0x00,
   0x00, 0x02, 0x86, 0x00, 0x00, 0x02, 0x86, 0x00, 0x00, 0x01, 0x43, 0x00,
   0x00, 0x82, 0x81, 0x00, 0x00, 0x82, 0x81, 0x00, 0x00, 0x82, 0x81, 0x00,
   0x00, 0xc1, 0x40, 0x00, 0x00, 0x74, 0x80, 0x00, 0x00, 0x74, 0x80, 0x00,
   0x00, 0x74, 0x80, 0x00, 0x00, 0x3a, 0x40, 0x00, 0x00, 0x2c, 0x80, 0x00,
   0x00, 0x2c, 0x80, 0x00, 0x00, 0x2c, 0x80, 0x00, 0x00, 0x16, 0x40, 0x00,
   0x00, 0x34, 0x80, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x34, 0x80, 0x00,
   0x00, 0x1a, 0x40, 0x00, 0x00, 0x28, 0x80, 0x00, 0x00, 0x28, 0x80, 0x00,
   0x00, 0x28, 0x80, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, 0x38, 0xc0, 0x00,
   0x00, 0x38, 0xc0, 0x00, 0x00, 0x38, 0xc0, 0x00, 0x00, 0x1c, 0x60, 0x00,
   0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00,
   0x00, 0x18, 0x18, 0x00, 0x00, 0x30, 0x0c, 0x00, 0x00, 0x30, 0x0c, 0x00,
   0x00, 0x30, 0x0c, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x20, 0x03, 0x00,
   0x00, 0x20, 0x03, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x90, 0x01, 0x00,
   0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
   0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x00,
   0x00, 0x38, 0x1c, 0x00, 0x00, 0x38, 0x1c, 0x00, 0x00, 0x1c, 0x08, 0x00,
   0x00, 0x44, 0x0e, 0x00, 0x00, 0x44, 0x22, 0x00, 0x00, 0x44, 0x22, 0x00,
   0x00, 0x22, 0x0c, 0x00, 0x00, 0x64, 0x08, 0x00, 0x00, 0x64, 0x20, 0x00,
   0x00, 0x64, 0x20, 0x00, 0x00, 0x32, 0x0a, 0x00, 0x00, 0x54, 0x08, 0x00,
   0x00, 0x54, 0x10, 0x00, 0x00, 0x54, 0x18, 0x00, 0x00, 0x2a, 0x09, 0x00,
   0x00, 0x4c, 0x08, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x4c, 0x20, 0x00,
   0x00, 0x26, 0x1f, 0x00, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44, 0x04, 0x00,
   0x00, 0x44, 0x20, 0x00, 0x00, 0x22, 0x08, 0x00, 0x00, 0x44, 0x08, 0x00,
   0x00, 0x44, 0x02, 0x00, 0x00, 0x44, 0x22, 0x00, 0x00, 0x22, 0x08, 0x00,
   0x00, 0x38, 0x3e, 0x00, 0x00, 0x38, 0x3e, 0x00, 0x00, 0x38, 0x1c, 0x00,
   0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

// 'folder2', 128x32px
const uint8_t folder2[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00,
   0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x13, 0x00,
   0x00, 0x00, 0x13, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00, 0xc0, 0x20, 0x00,
   0x00, 0xc0, 0x20, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x60, 0x10, 0x00,
   0x00, 0x30, 0xe0, 0x00, 0x00, 0x30, 0xe0, 0x00, 0x00, 0x30, 0xe0, 0x00,
   0x00, 0x18, 0x70, 0x00, 0x00, 0x0c, 0x98, 0x00, 0x00, 0x0c, 0x98, 0x00,
   0x00, 0x0c, 0x98, 0x00, 0x00, 0x06, 0x4c, 0x00, 0x00, 0x02, 0x86, 0x00,
   0x00, 0x02, 0x86, 0x00, 0x00, 0x02, 0x86, 0x00, 0x00, 0x01, 0x43, 0x00,
   0x00, 0x82, 0x81, 0x00, 0x00, 0x82, 0x81, 0x00, 0x00, 0x82, 0x81, 0x00,
   0x00, 0xc1, 0x40, 0x00, 0x00, 0x74, 0x80, 0x00, 0x00, 0x74, 0x80, 0x00,
   0x00, 0x74, 0x80, 0x00, 0x00, 0x3a, 0x40, 0x00, 0x00, 0x2c, 0x80, 0x00,
   0x00, 0x2c, 0x80, 0x00, 0x00, 0x2c, 0x80, 0x00, 0x00, 0x16, 0x40, 0x00,
   0x00, 0x34, 0x80, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x34, 0x80, 0x00,
   0x00, 0x1a, 0x40, 0x00, 0x00, 0x28, 0x80, 0x00, 0x00, 0x28, 0x80, 0x00,
   0x00, 0x28, 0x80, 0x00, 0x00, 0x14, 0x40, 0x00, 0x00, 0x38, 0xc0, 0x00,
   0x00, 0x38, 0xc0, 0x00, 0x00, 0x38, 0xc0, 0x00, 0x00, 0x1c, 0x60, 0x00,
   0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00,
   0x00, 0x18, 0x18, 0x00, 0x00, 0x30, 0x0c, 0x00, 0x00, 0x30, 0x0c, 0x00,
   0x00, 0x30, 0x0c, 0x00, 0x00, 0x18, 0x06, 0x00, 0x00, 0x20, 0x03, 0x00,
   0x00, 0x20, 0x03, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x90, 0x01, 0x00,
   0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
   0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x3e, 0x00,
   0x00, 0x38, 0x1c, 0x00, 0x00, 0x38, 0x3e, 0x00, 0x00, 0x1c, 0x0e, 0x00,
   0x00, 0x44, 0x02, 0x00, 0x00, 0x44, 0x02, 0x00, 0x00, 0x44, 0x20, 0x00,
   0x00, 0x22, 0x11, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0x64, 0x02, 0x00,
   0x00, 0x64, 0x20, 0x00, 0x00, 0x32, 0x11, 0x00, 0x00, 0x54, 0x1e, 0x00,
   0x00, 0x54, 0x1e, 0x00, 0x00, 0x54, 0x10, 0x00, 0x00, 0x2a, 0x0e, 0x00,
   0x00, 0x4c, 0x20, 0x00, 0x00, 0x4c, 0x22, 0x00, 0x00, 0x4c, 0x08, 0x00,
   0x00, 0x26, 0x11, 0x00, 0x00, 0x44, 0x20, 0x00, 0x00, 0x44, 0x22, 0x00,
   0x00, 0x44, 0x08, 0x00, 0x00, 0x22, 0x11, 0x00, 0x00, 0x44, 0x22, 0x00,
   0x00, 0x44, 0x22, 0x00, 0x00, 0x44, 0x08, 0x00, 0x00, 0x22, 0x11, 0x00,
   0x00, 0x38, 0x1c, 0x00, 0x00, 0x38, 0x1c, 0x00, 0x00, 0x38, 0x08, 0x00,
   0x00, 0x1c, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

void setup() {   
    
    btnMenu.begin();
    btnLeft.begin();
    btnRight.begin();
    btnVolDown.begin();
    btnVolUp.begin();
    btnPlayPause.begin();   

    Wire.begin();   

    Serial.begin(115200);
    Serial1.begin(9600);

    player.begin(Serial1, true);

    u8g2.begin();
    u8g2.setFont(u8g2_font_profont12_mf); //if space is required, use mr not mf
    u8g2.setDrawColor(1);
    u8g2.clearBuffer();
    u8g2.drawStr(0, 15, "starting");
    u8g2.sendBuffer();
    Serial.println("reached end of setup");
    delay(200);
    
    player.startDAC();
    player.volumeAdjustSet(1);
    player.volumeAdjustSet(1);
    player.volume(4);
    player.volume(4);
}

void loop() { //frame Coords: 0,0  ,  32,0   ,   64,0   ,   96,0    32 width 21 length

  //add menuState 0 (music player default)
    if (menuState == 0) {
        u8g2.clearBuffer();
        menuFrameClear();
        u8g2.setPowerSave(1);
        //menuTimeout();

        while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
            //menuTimeout();

            if (btnMenu.released()) {
                u8g2.setPowerSave(0);
                menuState = 1;
                break;
            }

            if (btnLeft.released()) {
                player.playPrevious();
                break;
            }

            if (btnRight.released()) {
                player.playNext();
                break;
            }

            
        }
    }

    //menuState 1, main menu
    if (menuState == 1) { //main menu, does not handle any commands to DFplayer
        u8g2.clearBuffer(); //this clears the buffer entirely, add the main menu bitmap, draws a frame around the first item then pushes it to screen.
        u8g2.drawXBMP(0, 0, 128, 32, mainMenu);
        //u8g2.drawFrame(0, 0, 32, 21);
        u8g2.sendBuffer();
        Serial.println("draw menu"); //debug
        //cursorPos = 0;
        Serial.println(cursorPos); //debug
        //menuTimeout();

        cursorChange:
       switch (cursorPos) { //each case instance handles removal and insertion of applicable frame
        case 0: //cursor is folders
            menuFrameClear(); //makes all frames transparent in buffer
            u8g2.drawFrame(0, 0, 32, 21); //add correct frame, push to screen
            u8g2.sendBuffer();
            Serial.println("frame drawn"); //debug
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) { //required to make code pause until input is received
                //menuTimeout();

                if (btnMenu.released()) { //if menu button is pressed, enter folder menu
                    menuState = 2; //this changes the menu state. Program should then exit the parent if loop and proceed to next if loop (next menu). This rarely happens.
                    cursorPos = 0; //menuState 2 handles removal of previous menu. Cursor remains in 0 position.
                    Serial.println("menu case 0"); //debug
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 1; //move cursor to right
                    Serial.println("right case 0"); //debug
                    break;
                }
                if (btnLeft.released()) {

                    cursorPos = 3; //loop around to the left
                    Serial.println("left case 0"); //debug
                    break;
                }
            }
            break;
        case 1: //cursor is at play mode
            menuFrameClear(); //remove all frames, add new one
            u8g2.drawFrame(32, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {

                    menuState = 3; //set menu to play mode, state 3 menu handles delete of previous screen
                    cursorPos = 0;
                    Serial.println("menu case 1");
                    break;
                }
                if (btnRight.released()) {

                    cursorPos = 2; //send cursor to the right
                    Serial.println("right case 1");
                    break;
                }
                if (btnLeft.released()) {

                    cursorPos = 0; //send cursor to the Left
                    Serial.println("left case 1");
                    break;
                }
            }
            break;
        case 2:
            menuFrameClear(); //remove all frames add new one
            u8g2.drawFrame(64, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    menuState = 4; //set menu to EQ mode, state 4 menu handles delete of previous screen
                    cursorPos = 0;
                    Serial.println("menu case 2");
                    break;
                }
                if (btnRight.released()) {

                    cursorPos = 3; //send cursor to the right
                    Serial.println("right case 2");
                    break;
                }
                if (btnLeft.released()) {

                    cursorPos = 1; //send cursor to the Left
                    Serial.println("left case 2");
                    break;
                }
            }
            break;
        case 3:
            menuFrameClear(); //remove all frames
            u8g2.drawFrame(96, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {

                    menuState = 5; //set menu to music mode, state 5 menu handles delete of previous screen
                    cursorPos = 0;
                    Serial.println("menu case 3");
                    break;
                }
                if (btnRight.released()) {

                    cursorPos = 0; //send cursor to the right
                    Serial.println("right case 3");
                    break;
                }
                if (btnLeft.released()) {

                    cursorPos = 2; //send cursor to the Left
                    Serial.println("left case 3");
                    break;
                }
            }
            break;
        }
      if (menuState != 1){
        
      }
      else {
        goto cursorChange;
      }
    }

    //menuState 2 (folders)
    if (menuState == 2) { //folder menu 1
        u8g2.clearBuffer(); //this clears the buffer entirely, add the folder 1  bitmap, draws a frame around the first item then pushes it to screen.
        u8g2.drawXBM(0, 0, 128, 32, folder1);
        //u8g2.drawFrame(0, 0, 32, 21);
        u8g2.sendBuffer();
        //cursorPos == 0
        //menuTimeout();

        cursorChange2:

        switch (cursorPos) { //each case instance handles removal and insertion of applicable frame
        case 0: //cursor is at folder 01
            menuFrameClear(); //makes all frames transparent in buffer, adds a frame then pushed buffer to screen
            u8g2.drawFrame(0, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) { //if menu button is released, play folder 01
                    player.repeatFolder(1); //player large folder 1
                    //add sending to menu 0
                    folderNr = 1;
                    delay(100); //time for playing to start
                    //nextHandler();
                    menuState = 1;
                    Serial.println(player.currentVolume());
                    Serial.println(player.currentEQ());
                    Serial.println(player.currentMode());
                    Serial.println(player.currentVersion());
                    Serial.println(player.currentSdTrack());
                    
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 1; //move cursor to right
                    break;
                }
                if (btnLeft.released()) {
                    menuState = 7;
                    cursorPos = 3; //loop around to the left
                    
                    break;
                }
            }
            break;
            
        case 1: //cursor is at folder 2
            menuFrameClear(); //remove all frames, add new one
            u8g2.drawFrame(32, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    player.repeatFolder(2); //play large folder 2
                    //add sending to menu 0
                    folderNr = 2;
                    delay(100); //time for playing to start
                    //nextHandler();
                    
                    Serial.println(player.currentSdTrack());
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 2; //send cursor to the right
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 0; //send cursor to the Left
                    break;
                }
            }
            break;

        case 2: //cursor is at folder 3
            menuFrameClear(); //remove all frames add new one
            u8g2.drawFrame(64, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    player.repeatFolder(3); //play large folder 3
                    folderNr = 3;
                    delay(100); //time for playing to start
                    //nextHandler();
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 3; //send cursor to the right
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 1; //send cursor to the Left
                    break;
                }
            }
            break;

        case 3: //cursor is at folder 4
            menuFrameClear(); //remove all frames add new one
            u8g2.drawFrame(96, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    player.repeatFolder(4);
                    folderNr = 4;
                    delay(100); //time for playing to start
                    //nextHandler();
                    break;
                }
                if (btnRight.released()) {
                    menuState = 7;
                    cursorPos = 0; //send cursor to the right
                    
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 2; //send cursor to the Left
                    break;
                }
            }
            break;
        }
        if (menuState == 2) {
            goto cursorChange2;
        }
        else {
            
        }
    }

    //menuState 7 (folders 2)
    if (menuState == 7) { //folder menu 1
        u8g2.clearBuffer(); //this clears the buffer entirely, add the folder 1  bitmap, draws a frame around the first item then pushes it to screen.
        u8g2.drawXBM(0, 0, 128, 32, folder1);
        u8g2.drawFrame(0, 0, 32, 21);
        u8g2.sendBuffer(); //exception: menustate 7 does not override previous cursorpos due to navigation from menustate 2 from cursorpos 0
        //menuTimeout();

        cursorChange7:

        switch (cursorPos) { //each case instance handles removal and insertion of applicable frame
        case 0: //cursor is at folder 05
            menuFrameClear(); //makes all frames transparent in buffer, adds a frame then pushed buffer to screen
            u8g2.drawFrame(0, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) { //if menu button is released, play folder 01
                    player.repeatFolder(5); //player large folder 5
                    folderNr = 5;
                    delay(100); //time for playing to start
                    //nextHandler();
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 1; //move cursor to right
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 3; //loop around to the left
                    menuState = 2;
                    break;
                }
            }
            break;

        case 1: //cursor is at folder 6
            menuFrameClear(); //remove all frames, add new one
            u8g2.drawFrame(32, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    player.repeatFolder(6); //play large folder 6
                    folderNr = 6;
                    delay(100); //time for playing to start
                    //nextHandler();
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 2; //send cursor to the right
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 0; //send cursor to the Left
                    break;
                }
            }
            break;

        case 2: //cursor is at folder 7
            menuFrameClear(); //remove all frames add new one
            u8g2.drawFrame(64, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    player.repeatFolder(7); //play large folder 7
                    folderNr = 7;
                    delay(100); //time for playing to start
                    //nextHandler();
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 3; //send cursor to the right
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 1; //send cursor to the Left
                    break;
                }
            }
            break;

        case 3: //cursor is at folder 8
            menuFrameClear(); //remove all frames add new one
            u8g2.drawFrame(96, 0, 32, 21);
            u8g2.sendBuffer();
            while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
                //menuTimeout();

                if (btnMenu.released()) {
                    player.repeatFolder(8);
                    folderNr = 8;
                    delay(100); //give time for the playing to start
                    //nextHandler();
                    break;
                }
                if (btnRight.released()) {
                    cursorPos = 0; //send cursor to the right
                    menuState = 2;
                    break;
                }
                if (btnLeft.released()) {
                    cursorPos = 2; //send cursor to the Left
                    break;
                }
            }
            break;
        }
     }
     if (menuState != 7) {

     }
     else {
         goto cursorChange7;
     }
    
 }


  
    //add menuState 3 (play mode), 4 modes, make new bitmap, C/P main menu code
    //

    //add menuState 4 (EQ mode) 6 options, make new bitmap need 6 cursorPositions, new frame clear function

    //add mennuState 5 (music mode) requires selecting folder then track number. may need to let DFplayer know to continue after song is played. (this may need a function)

 

void menuFrameClear() {
    u8g2.setDrawColor(0);
    u8g2.drawFrame(0, 0, 32, 21);
    u8g2.drawFrame(32, 0, 32, 21);
    u8g2.drawFrame(64, 0, 32, 21);
    u8g2.drawFrame(96, 0, 32, 21);
    u8g2.setDrawColor(1);
    return;
}

/*void nextHandler() {
    attachInterrupt(digitalPinToInterrupt(busyPin), nextSong, FALLING); //interrupt plays next song when playing song is finished.
}

void nextSong() { //this is function is required to use interrupts when handling next song functionality.
    player.playNext();
    nextHandler();
}*/


 
void volDown() {
  while (!btnVolDown.pressed()){}
    player.decVolume();
    volDown();
}

void volUp() {
  while (!btnVolUp.pressed()){}
    player.incVolume();
    volUp();
}

void playPause() {
  while (!btnPlayPause.pressed()){}
    if (player.isPlaying() == true) {
        player.pause();
    }
    else {
        player.resume();
    }
    playPause();
}
    

void shuffleFolder(int folderNr) {
    //n is array position
    int n = 0;
    int i = 1;


        for (int i = 1; i = player.numTracksInFolder(folderNr); i++) {
            //write i to n pos
            tracks[n] == i;
            n++;
        }

     shuffleArray(); //no idea if this is what I want it to do
     playShuffle();
    
}

void playShuffle() {
    int p = 1; //needs to be fixed, cannot use local variable.
    for (int p = 1; p = player.numTracksInFolder(folderNr); p++) {
        player.playFolder(folderNr, tracks[p]);
        attachInterrupt(digitalPinToInterrupt(busyPin), playShuffle, RISING);   //need to redo this     
    }
}

/*void shuffleArray(int * tracks, int size) //random randomizer code found online
{
    int last = 0;
    int temp = tracks[last];
    for (int i = 0; i < size; i++)
    {
        int index = random(size);
        tracks[last] = tracks[index];
        last = index;
    }
    tracks[last] = temp; */

void shuffleArray() {
    const size_t n = sizeof(tracks) / sizeof(tracks[0]);
    randomSeed(A1);
    for (size_t i = 0; i < n - 1; i++) {
        size_t j = random(0, n - i);

        int t = tracks[i];
        tracks[i] = tracks[j];
        tracks[j] = t;
    }
}

void menuTimeout() {


    while (!btnMenu.pressed() && !btnLeft.pressed() && !btnRight.pressed()) {
        timeout = millis();
        if (millis() - timeout >= 10000) {
            menuState = 0;
            }

    }
}
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,700
Joined
Nov 17, 2011
Messages
13,700
Currently I start a function that waits for 10 secs then sets the menu to that special case, this doesnt work at all because it does not run continously. How could I implement this?
Use e.g. the millis() fiunction in the main loop. When you enter the menu loop the first time, read the current time and store it in a variable e.g.
Code:
current_time = millis();
Within the loop evaluate the elapsed time and check whether 10 seconds have passed:
Code:
elapsed_time = millis()-current_time;
if (elapsed_time > 10000L) {
   /* code to turn the display off */
   }
else {
   /* code to evaluate the menu buttons */
   }
Of course you have to update current_time every time a button is pressed and the timeout interval starts again at 0.
The problem is that my menu code is waiting for input, so it does not recognise when these buttons are pressed.
I can't follow here (sorry, no time to dive deep into your code). The purpose of the menu loop is to check buttons and perform the matching actions. If your code hangs in the loop and waits for specific buttons while ignoring other buttons I think your handling of the menu structure is incorrect.
See e.g. here for an example or try one of the available libraries for coding menus, e.g. here.

Options: I could add these buttons to my code in each and every case of the menu, but this is very excessive. I could abandon my requirement to always have them functional by implementing functionality only in the timeout menu above. However I was hoping there may be easier ways.
Rejecting your requirement is the last resort. You want that functionality for a reason.
Adding the code for testing the same button in many places is awkward, hard to maintain later and prone to errors. In case you want to do it the hard way and code the menu by yourself (no doubt a laudable enterprise) instead of using a library, this is how I would do this (psedudo code only):
Code:
current_time = milis();
loop {
   if((millis() - current_time) > 10000L) {
      /* turn display off */
   }
   else {
      read buttons(); // read all button states
      evaluate buttons(); // perform actions depending on which buttons were pressed
   }
} /* end of loop */
 

shumifan50

Jan 16, 2014
579
Joined
Jan 16, 2014
Messages
579
This is the perfect case for a state machine. Break the various functions you want to implement into different states e.g.
state 1 - wait a certain time
state 2 - wait for keypress
state 3 - .........
Each state sets the next state when it has done its work. Use a "switch" statement to execute the different states.
Under VERY few conditions should the code on a micro controller go into a closed loop:
e.g.
while (10 {
.........
}
as this does not give control back to the underlying code. Similarly, as Harold pointed out, do NOT ever use delay(), rather use a state and use millis() to time the delay. Your loop() function should be coded to run for the minimum of time before exiting.
Simple example(not tested and open for comment):

Code:
unsigned long prevmils = 0;
int nextState = 0;
int delayTime = 0;
int delaymillis(int mils) {
     if (prevmils == 0) {
         prevmils = millis();
         delayTime = mils;
     }
     if (millis() - prevmils >= delayTime) {
          prevmils = 0;
          delayTime = 0;
          return(1);  //time has expired
     }
}

void loop() {
      switch(state) {
          case 0:
                if (delaymillis(50)) {
                    nextState = 1;
                }
                break;
           case 1:
                 .... do whatever after the delay
                 nextState = 3;
                 break;
            default:
                 ..... most likely an error
      }
}

Moderators note : used code tags for piece of code.
 
Last edited by a moderator:

shumifan50

Jan 16, 2014
579
Joined
Jan 16, 2014
Messages
579
Also: remember to declare variables shared between interrupts and main loop as "volatile" to ensure synchronization. ISR functions MUST be as short as possible. If an interrupt needs more than an absolute minimum amount of processing, set a flag in the ISR and do the processing in the main loop state machine. Mutually exclusive code should be locked using semaphores or critical sections.
 

BlueObsecurit

Mar 24, 2021
36
Joined
Mar 24, 2021
Messages
36
Sorry that I took some time to get back to this, outside commitments unexpectedly took my time.

I think your handling of the menu structure is incorrect.

This is of course correct. I've rewritten the code partially, but still need to fix some stuff such as a special menu case.
Code is now significantly shorter.

This is the perfect case for a state machine.

This could work, however with my rewrite all I want to have the main loop do is listen for keypresses and possible timeout, and I can handle this with the method Harald psudeo coded I think. If not, I'll look more into this. Thank you nevertheless, it gives me insight into how to think when coding and who knows, I might need to use this at some point.

Also: remember to declare variables shared between interrupts and main loop as "volatile" to ensure synchronization. ISR functions MUST be as short as possible. If an interrupt needs more than an absolute minimum amount of processing, set a flag in the ISR and do the processing in the main loop state machine. Mutually exclusive code should be locked using semaphores or critical sections.

I'll admit, some of this goes over my head. I've gotten the part that the code run by interrupts must be as short as possible and they were. But unfortunatly I cannot use interrupts at all with this microcontroller. You're saying that if I went with a state machine and interrupts and I needed to execute a fair bit of code, I must simply send the interrupt to the piece of code in order to end the interrupt as fast as possible?

do NOT ever use delay()

Can I ask why?
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,700
Joined
Nov 17, 2011
Messages
13,700
delay() is perfect for short delays during which the mcu has nothing else to do.
However, delay() blocks the mcu from doing other stuff. Therefore it is inappropriate for situations where the mcu has to perform other jobs during the delay time.
Within a menu where you wait for a keypress delay() can be used - if used correctly. You'll have to be careful that the different calls to delay() don't add up a long unexpected timespan.
Example: debouncing a number of keys/buttons:

Naive approach:
Code:
for (i=0; i<NUMKEYS;i++) {
   tmp = read_key(i);
   delay(10); //wait for debouncing the key
   if (read_key(i) == tmp) //assume key is stable if still in the same position
      key[i] = 1;
   else
      key[i] = 0;
} // end for loop
Since within the loop delay() is called for each key, the total delay for the loop will be NUMNKEYS × 10 ms. With more than a few keys this will take a noticeable time and irritate the user who will think the keypress was not registered,
If one were to code it like this:
Code:
for (i=0; i<NUMKEYS;i++) {
   tmp[i] = read_key(i);
} // end for 1st loop

delay(10); //wait for debouncing the keys

for (i=0; i<NUMKEYS;i++) {
   if (read_key(i) == tmp[i]) //assume key is stable if still in the same position
      key[i] = 1;
   else
      key[i] = 0;
} // end for 2nd loop
The delay() is called only once between the two loops and the 10 ms will be unnoticeable to the user.

So imho it is not a matter of "don't use delay()" but "know what you do when you use delay()"..
 

BlueObsecurit

Mar 24, 2021
36
Joined
Mar 24, 2021
Messages
36
You'll have to be careful that the different calls to delay() don't add up a long unexpected timespan

Thanks!

Been trying to hack away at this code, I'm certain I've made some schoolboy error.

Psudeo code:
Code:
void loop() {

  listenForLeftButton {

    leftButtonHandler(cursorPos, menuState);
    
  }
}

void lefButtonHandler(int cursorPos, int menuState){

  specialCase(); //currently not testable

  switch (cursorPos){
    case 0:
      cursorPos = 3;
      cursorDraw(cursorPos);
      break;
  }
}

So what happens when I press left button on Pos 0 is that the cursor goes to pos 3 as expected.
However thats where it stays. I used serial probes to see what happens with cursorPos before and after the logic is executed, and it stays at 0. So this explains the behaviour I am seeing.

What I want: The logic should change the integer cursorPos globally for use in the next set of logic execution.

What I think I have done: The integer cursorPos is only changed within the function that handles it.

How can I fix this? I've been reading about ByVal and ByRef with Visual Basic so I may get my wires crossed here, but it seems like a plausible explaination.
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,700
Joined
Nov 17, 2011
Messages
13,700
I've been reading about ByVal and ByRef with Visual Basic
No such thing in C. You could use pointers but that's a mess unless you really know what you do.

The logic should change the integer cursorPos globally
You have two possibilities:
  1. define cursorPos as a global variable
  2. define cursor Pos as a static variable.
The difference being that you cannot access a static variable outside the function where it is defined.
 

BlueObsecurit

Mar 24, 2021
36
Joined
Mar 24, 2021
Messages
36
No such thing in C.

Had a bit of a google and found this out too. I can't figure out why cursorPos remains 0 even though it is changed in the function. The obvious answer would be that something sets it back to 0, but I can't find it. On top of that, everything now acts up. The player who never had such issue, suddenly alternates between playing as expected, playing then pausing right away or not playing at all. Button is registered by the microcontrolled and command sent, but something happens.

Guess I'll rebuilt it looking for hardware faults. Not too sure what to do with the cursorPos situation. I guess I could redo the function so that it returns the value of cursorPos. Is there an issue/limit with calling functions from functions? I'm aware that's a slightly stupid question, but I end up calling a function from a function about 3-4 times. Could this cause something to not save the value? I'm not sure and I guess I am grasping at straws here.
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,700
Joined
Nov 17, 2011
Messages
13,700
The obvious answer would be that something sets it back to 0, but I can't find it.
That's because the call to
Code:
lefButtonHandler(int cursorPos, int menuState)
supplies the function with a copy of cursorPos on the stack. You can change cursorPos within the function as much as you like, but the original variable will not be changed. So there's no 'reset' but simply no change to cursorPos outside the function.
This requires to declare cursorPos as a global variable. In that case you have to remove cursorPos from the call:
Code:
lefButtonHandler(int menuState)
Read this information on how to use global variables in Arduino code.

Guess I'll rebuilt it looking for hardware faults.
I doubt you'll find one that causes this behavior.
 

BlueObsecurit

Mar 24, 2021
36
Joined
Mar 24, 2021
Messages
36
In that case you have to remove cursorPos from the call

I understood the scope, but just not that I didn't have to explicitly pass the variables to the function. (And what would happen if I "did")

Removed all calls to global variables did the trick and I was able to debug the remaining code. It now works as expected, and I can start adding the remaining features! Thank you so much!

I doubt you'll find one that causes this behavior.

Quite right, I had forgotten to add break; a few places, which, to my defense I did explicitly look for several times before finding! Also issues with a dying battery didn't help...

It still has a few instances where a button press does not register and you're left wondering if something happened, but I'm not too sure if that isn't simply something that happens due to limitations of the technology I'm using. Should I run into any more problems, would it be alright if I quoted something of yours to reply? As to not create too many threads for what is essentially coding assistance? Once again thank you, I'm really grateful.
 

shumifan50

Jan 16, 2014
579
Joined
Jan 16, 2014
Messages
579
It is not true that byval and byref does not exist in C, unless I misunderstand what the terms mean
Example:
Code:
int i = 0;

void foo(int j) {  //by value -value of i is passed on stack. Changing the value will change the value on the stack, but not the value of the global i.
    j = 10;
}

void foo2(int *j) { //by reference - pointer to i is passed on stack. *j being set changes the value of the data pointed at by the pointer j and therefore the globally declared i.
     *j = 11;
}
void foo3() {
     i=12; //change global variable directly 
}

void main() {
      foo(i);   //by value, on return i is still 0
     foo2(&i); //by reference, on return i is 11
      foo3();  // i=12 on return as foo3 changes the global variable
}
 
Last edited:

shumifan50

Jan 16, 2014
579
Joined
Jan 16, 2014
Messages
579
I can't figure out why cursorPos remains 0 even though it is changed in the function. The obvious answer would be that something sets it back to 0
Look at my immediately preceding post and it will explain your problem.
 

shumifan50

Jan 16, 2014
579
Joined
Jan 16, 2014
Messages
579
A further thing to note:
Code:
int i=0;

void foo() {
    int i;  //local to foo()
    i=11;
}
int foo2(int i){
    i++;
    return(i);
}
void main{
     int j=2;
     foo();
      i=foo2(j);  //assign returned value from foo2() to global i. i=3, j=2
}

Although the same variable names can be used globally and in functions because of scope, it is highly recommended not to do so.
Using global variables makes initial coding (maybe) easier, but using passed variables is safer.
HOWEVER, remember that when you pass variables to a function, they are pushed onto the stack, so passing large amounts of data by value is a bad idea, rather pass big blocks of data using a pointer.[/CODE]
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,700
Joined
Nov 17, 2011
Messages
13,700
It is not true that byval and byref does not exist in C
I wrote:
You could use pointers but that's a mess unless you really know what you do.
The difference being that with pointers you can do pointer arithmetic and this can create havoc if not used correctly. Using BASIC's ByVal and Byref qualifiers doesn't allow that as far as I know.
 

Harald Kapp

Moderator
Moderator
Nov 17, 2011
13,700
Joined
Nov 17, 2011
Messages
13,700
Using global variables makes initial coding (maybe) easier, but using passed variables is safer.
I agree. One way out of this dilemma is by having the function return the value explicitly:
Code:
/* cursorPos() is called with an integer cursor position as parameter and returns a new integer cursor position*/
int cursorPos(int position) {
/* my preferred way using an intermediate variable: */
    int newCursorPos;
    
    newCursorPos = some_function(position); // compute new position from old position and some function, e.g. read cursor keys,
    return(newCursorPos); // return the new cursor position
    
/* Less preferred by me, but in this simple case everything could be combined into a single line without the need for an intermediate variable:
int cursorPos(int position) {   
   return(some_function(position);
}
Note in this siple case you could call some_function(position) directly without the overhead of the call to cursorPos(). This is for demonstration only. Instead of the call to some_function() you would place directly your code to look for button presses etc.
*/

}

}
void main{
     int cursorPos =0;

/* some code
...

Here you call cursorPosition() with the old cursor position as parameter and assign the returned new cursor position to your local variable cursorPos. No global variables or pointers needed.
Clear structure as one can clearly see what happens without knowing what happens inside cursorPosition().
*/
   cursorPos = cursorPosition(cursorPos);
  
/* some more code
...
*/
 
}
 

shumifan50

Jan 16, 2014
579
Joined
Jan 16, 2014
Messages
579
Sorry, did not mean to tread on toes, just wanted to help the understanding of variable scope and functions. Note that the last call in my last example shows the use of a function returning a value and assigning it to a variable.
The one thing I did not mention is that a structure behaves like a simple variable, so if you wish to change values in the original structure, it must be passed as a pointer. If the structure is small then it will be OK to return the structure from a function, but if it is a big structure it is best to work with pointers to avoid excessive stack usage so updates are done in place.
Further, when a pointer is passed to a function, the function can do any arithmetic on the pointer and it will have no effect on the original pointer passed as on return the passed pointer is popped back off the stack.
 
Top