Message |
|
Hi Dave,
I have updated to the latest version of ioAbstraction and I was super happy that everything worked out of the box - many thanks and congrats to an apparently super well-written and maintained code!!!!
Also, available RAM increased a lot with the new version (84% usage down to 54%) which is great. However, it also uses more flash memory which is at 99% with the current code - even without some of the things I still need to put in. I used to see the memory limitations of my pro micro as a challenge but either I am just wanting too much or I am not skilled enough to code more effienciently. Regardless, I am thinking about upgrading the board. I could not find an arduino board with more flash and RAM that can also work as HID (I am sending keystrokes to the PC with it).
Do you have a recommendation? I was looking at the newer teensy - seems overkill but prices are similar to most arduino boards. Obviously, I would like to continue using your library but I have no chance of estimating whether that would work or not. Can you give me a hint? List of teensys is here: https://www.pjrc.com/teensy/techspecs.html
|
|
|
Hi Dave,
I have finally continued with a little hobby project (we talked back in 2020 I think) and I ran into problems with RAM during runtime, using a pro micro (2560 bytes of RAM, 28672 bytes of Flash) with IoAbstraction version 1.4.11
The Situation: After compiling, I have about 400 bytes of free RAM - not a lot, but everything works fine. But I have to add some more code and as soon as I add that code and initial free RAM is lower than 370 bytes, the program stops at runtime. This seems to happen during encoder initializing (found out by "debugging" via Serial.print).
What I have tried: I am aware of the F-macro and putting stuff into the EPROM via PROGMEM. I have used that whereever possible, I think.
My questions are:
1) Is there any documentation how to deactivate parts / methods of the IoAbstraction library or is this even possible?
2) Is there documentation on the size for every switch or encoder object needed? I think I read somewhere that it is 6 bytes per switch but I am not sure about the encorders. I am using about 40 switches and 8 rotary encoders.
3) Should I upgrade the IoAbstraction library or stay with my version (1.4.11)? Which one is likely to use less RAM?
Thanks in advance,
Tom
|
|
|
Hi Dave,
thanks for your help, I have done it as suggested. I am almost done, many of the things do work - all the buttons and displays work as intended. But I am completely stuck with the rotary encoders. Although I have tested everything before, I cannot get it to work. Here is the relevant part of my code (I have omitted all the display and menu stuff). I know that this is not the greatest way of coding probably, but maybe I have done something wrong that I cannot find.
What I have tested:
1) Whether the interrupt is working and is connected to PIN 7 of the Pro Micro board (I used switches.initialiseInterrupt -> everything works, setting it programmically to PIN 8 while physically to PIN 7 -> nothing works which is the desired behavior)
2) Testing the 23017 expander (I added the encoders as buttons and it worked, the corresponding PIN numbers showed up in the serial monitor)
3) Made sure that all rotary encoders are on the same 23017 and the Max switches and Max encoders numbers are changed in the *.h source file.
4) The encoder callback works, as it gives an output to the serial monitor when the encoder object is created
I really don't know what else could be the reason, but I had two ideas:
A) On all my tests the rotary encoders were located on the first 23017 expander (0x20). Now they are on 0x22. Could that be the reason?
B) Do I have to change something with the "slots" for the encoder objects? I don't really know what they are for.
One more thing about the code down there: I temporary disabled all but the first encoder for testing, but even this is not working.
Any help is greatly appreciated!
Tom
#include <IoAbstraction.h>
#include <IoAbstractionWire.h>
#include <Wire.h>
#include <Keyboard.h>
#include <Arduino.h>
#include <U8g2lib.h>
/////////////////
// DEFINITIONS //
/////////////////
// start value and maximum encoder value before reset
uint16_t EncValList[] = {32000, 32000, 32000, 32000, 32000, 32000, 32000, 32000};
const uint16_t MaxEncValList[] = {65000, 65000, 65000, 65000, 65000, 65000, 65000, 65000};
// Hardcoded hardware addresses
const byte TcaAddress = 0x70; // I2C address of the TCA I2C MUX
const byte ResPinsForArduino = 100; // input value for the IoAbstraction library, reserved pins for Arduino
const byte ArduinoInterruptPin = 7; //physical pin on the arduino to record interrupts
// profile, menu and displays
const byte NoOfButtons = 64;
// instantiate IoAbstraction
MultiIoAbstractionRef MultiIo = multiIoExpander(ResPinsForArduino);
///////////////////////////////
// STANDARD BUTTON CALLBACKS //
///////////////////////////////
void OnPushBtnPress(byte PinNum, bool HeldDown) {
// ignore when buttons are held
if (HeldDown == false) {
ButtonHandler(PinNum, HeldDown);
}
}
void OnToggleBtnPress(byte PinNum, bool HeldDown) {
// ignore when buttons are held
if (HeldDown == false) {
ButtonHandler(PinNum, HeldDown);
}
}
void OnToggleBtnRelease(byte PinNum) {
// Could be the same as OnToggleBtnPress, but separated in case of future changes
ButtonHandler(PinNum, 0);
}
void onEncChange0(uint16_t EncVal) {
Serial.println(F("IN: onEncChange0"));
EncoderToButton(0, EncVal, 36, 44);
}
void onEncChange1(uint16_t EncVal) {
EncoderToButton(1, EncVal, 37, 45);
}
void onEncChange2(uint16_t EncVal) {
EncoderToButton(2, EncVal, 39, 47);
}
void onEncChange3(uint16_t EncVal) {
EncoderToButton(3, EncVal, 38, 46);
}
void onEncChange4(uint16_t EncVal) {
EncoderToButton(4, EncVal, 35, 43);
}
void onEncChange5(uint16_t EncVal) {
EncoderToButton(5, EncVal, 34, 42);
}
void onEncChange6(uint16_t EncVal) {
EncoderToButton(6, EncVal, 33, 41);
}
void onEncChange7(uint16_t EncVal) {
EncoderToButton(7, EncVal, 32, 40);
}
/////////////////////////////
// ROTARY ENCODER WRAPPER //
/////////////////////////////
void EncoderToButton(byte EncAddr, uint16_t EncVal, byte BtnBack, byte BtnFwd) {
Serial.print(F("Encoder Address: "));
Serial.println(EncAddr);
Serial.print(F("Encoder Value: "));
Serial.println(EncVal);
int EncDir = GetEncDirection(EncAddr, EncVal);
if (EncDir == 1) {
ButtonHandler(ResPinsForArduino + BtnBack, false);
}
else if (EncDir == -1) {
ButtonHandler(ResPinsForArduino + BtnFwd, false);
}
}
int GetEncDirection(byte EncAddr, uint16_t NewEncVal) {
//Serial.println(" ");
//Serial.println("-------------------------");
//Serial.println("INSIDE getEncDirection");
int EncDir;
uint16_t OldEndVal = EncValList[EncAddr];
if (NewEncVal > OldEndVal) {
EncDir = 1;
}
else if (NewEncVal < OldEndVal) {
EncDir = -1;
}
else if (NewEncVal == OldEndVal) {
EncDir = 0;
Serial.println(F("ERROR: New EncoderVal = Old EncoderVal!"));
}
if (NewEncVal > 60000 || NewEncVal < 3000) {
NewEncVal = 32000;
}
EncValList[EncAddr] = NewEncVal;
//Serial.print("getEncDirection, Dir: ");
//Serial.println(EncDir);
return EncDir;
}
void ButtonHandler(byte PinNum, bool HeldDown) {
//Serial.println(" ");
//Serial.println("-------------------------");
//Serial.println("INSIDE Task Handler");
// Check whether system is on or off
if (OnOffState == 0) {
return;
}
// wake up the displays
WakeUp();
// get the real ButtonID, identical to array indices
byte BtnId = PinNum - ResPinsForArduino;
// get the number of display to be sent (via DispSelect)
byte DispAddr = RetrieveNumsFromFlash (4, BtnId); // first input is ArrayNumber = 4 for DisplayNumber
//Get the information, whether this button is used as toggle
bool IsToggle = RetrieveNumsFromFlash (6, BtnId); // ArrayNum = 6 is EnableToggleList
Serial.print(F("PinNum: "));
Serial.println(PinNum);
Serial.print(F("BtnId: "));
Serial.println(BtnId);
Serial.print(F("IsToggle: "));
Serial.println(IsToggle);
// set FontInverse
bool IsFontInverse = false;
// if this is a toggle button, change the state in BtnStateList
// and redraw the display
if (IsToggle == true) {
bool OldBtnState = BtnStateList[BtnId];
BtnStateList[BtnId] = !OldBtnState;
Serial.print(F("OldBtnState: "));
Serial.println(OldBtnState);
Serial.print(F("NewBtnState: "));
Serial.println(!OldBtnState);
if (!OldBtnState == true) {
IsFontInverse = true;
}
// draw the text to display
DispDrawFull(DispAddr);
}
//send the keystroke(s)
KeypressHandler(BtnId);
// Start the Sleep Counter
PrepareSleep();
}
///////////
// SETUP //
///////////
void setup() {
Wire.begin();
Serial.begin(9600);
//InitializeDisplays;
for (byte k = 0; k < 8; k++) {
DispSelect(k);
u8g2.begin();
u8g2.firstPage();
}
// adding all 23017 expander
multiIoAddExpander(MultiIo, ioFrom23017(0x20, ACTIVE_LOW_OPEN, ArduinoInterruptPin), 16);
multiIoAddExpander(MultiIo, ioFrom23017(0x21, ACTIVE_LOW_OPEN, ArduinoInterruptPin), 16);
multiIoAddExpander(MultiIo, ioFrom23017(0x22, ACTIVE_LOW_OPEN, ArduinoInterruptPin), 16);
multiIoAddExpander(MultiIo, ioFrom23017(0x23, ACTIVE_LOW_OPEN, ArduinoInterruptPin), 16);
switches.initialiseInterrupt(MultiIo, true); //switches.initialiseInterrupt(MultiIo, true);
// Basic controls
switches.addSwitch(ResPinsForArduino + 7, OnMainSwitchPress); // ON
switches.onRelease(ResPinsForArduino + 7, OnMainSwitchRelease); // OFF
switches.addSwitch(ResPinsForArduino + 6, OnEnableBtnPress); // Enable ON
switches.onRelease(ResPinsForArduino + 6, OnEnableBtnRelease); // Enable OFF
switches.addSwitch(ResPinsForArduino + 3, OnProfileChBtnPress); // Change Profile
switches.addSwitch(ResPinsForArduino + 2, OnSleepTimeBtnPress); // Change SleepTimer
switches.addSwitch(ResPinsForArduino + 5, OnProfileSelBtnPress);// Select Profile
switches.addSwitch(ResPinsForArduino + 4, OnDispOnOffBtnPress); // Displays On/Off
// Toggle switches, initialise in a loop
for (byte k = 16; k < 32; k++) {
switches.addSwitch(ResPinsForArduino + k, OnToggleBtnPress);
switches.onRelease(ResPinsForArduino + k, OnToggleBtnRelease);
}
// Large Toggle switches
switches.addSwitch(ResPinsForArduino + 56, OnToggleBtnPress);
switches.addSwitch(ResPinsForArduino + 57, OnToggleBtnPress);
// Push Buttons
switches.addSwitch(ResPinsForArduino + 52, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 53, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 54, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 55, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 60, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 61, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 62, OnPushBtnPress);
switches.addSwitch(ResPinsForArduino + 63, OnPushBtnPress);
// Color Push Buttons
switches.addSwitch(ResPinsForArduino + 51, OnPushBtnPress); // Green 0
switches.addSwitch(ResPinsForArduino + 50, OnPushBtnPress); // Green 1
switches.addSwitch(ResPinsForArduino + 58, OnPushBtnPress); // Red 0
switches.addSwitch(ResPinsForArduino + 59, OnPushBtnPress); // Red 1
// Encoder Click buttons, initialise in a loop
for (byte m = 8; m < 16; m++) {
switches.addSwitch(ResPinsForArduino + m, OnPushBtnPress);
}
// Only for testing, Encoder buttons as normal switches
//for (byte m = 32; m < 48; m++) {
// switches.addSwitch(ResPinsForArduino + m, OnPushBtnPress);
//}
// setting up first encoder
setupRotaryEncoderWithInterrupt(ResPinsForArduino + 36, ResPinsForArduino + 35, onEncChange0);
switches.changeEncoderPrecision(50000, 25000);
/* setting up extra encoders. Make sure they are all on the same expander
HardwareRotaryEncoder* EncObj1 = new HardwareRotaryEncoder(ResPinsForArduino + 37, ResPinsForArduino + 45, onEncChange1);
switches.setEncoder(1, EncObj1);
switches.changeEncoderPrecision(1, MaxEncValList[1], EncValList[1]);
HardwareRotaryEncoder* EncObj2 = new HardwareRotaryEncoder(ResPinsForArduino + 39, ResPinsForArduino + 47, onEncChange2);
switches.setEncoder(2, EncObj2);
switches.changeEncoderPrecision(2, MaxEncValList[2], EncValList[2]);
HardwareRotaryEncoder* EncObj3 = new HardwareRotaryEncoder(ResPinsForArduino + 38, ResPinsForArduino + 46, onEncChange3);
switches.setEncoder(3, EncObj3);
switches.changeEncoderPrecision(3, MaxEncValList[3], EncValList[3]);
HardwareRotaryEncoder* EncObj4 = new HardwareRotaryEncoder(ResPinsForArduino + 35, ResPinsForArduino + 43, onEncChange4);
switches.setEncoder(4, EncObj4);
switches.changeEncoderPrecision(4, MaxEncValList[4], EncValList[4]);
HardwareRotaryEncoder* EncObj5 = new HardwareRotaryEncoder(ResPinsForArduino + 34, ResPinsForArduino + 42, onEncChange5);
switches.setEncoder(5, EncObj5);
switches.changeEncoderPrecision(5, MaxEncValList[5], EncValList[5]);
HardwareRotaryEncoder* EncObj6 = new HardwareRotaryEncoder(ResPinsForArduino + 33, ResPinsForArduino + 41, onEncChange6);
switches.setEncoder(6, EncObj6);
switches.changeEncoderPrecision(6, MaxEncValList[6], EncValList[6]);
HardwareRotaryEncoder* EncObj7 = new HardwareRotaryEncoder(ResPinsForArduino + 32, ResPinsForArduino + 40, onEncChange7);
switches.setEncoder(7, EncObj7);
switches.changeEncoderPrecision(7, MaxEncValList[7], EncValList[7]);
*/
// Setting the LEDs of the color push buttons (connected to Arduino directly) to Output
ioDevicePinMode(MultiIo, 4, OUTPUT);
ioDevicePinMode(MultiIo, 5, OUTPUT);
// Getting button states at start
if (switches.isSwitchPressed(106)) {
EnableState = 1;
}
if (switches.isSwitchPressed(107)) {
OnOffState = 1;
}
uint8_t taskId = taskManager.scheduleFixedRate(2000, ReportIn);
Serial.print( "task ID: " );
Serial.println(taskId);
}
void loop() {
taskManager.runLoop();
}
|
|
|
Hey Dave,
in case this question should be better asked in a new topic, please feel free to move it, but it is still concerning the same project of mine.
The situation: For the looks and feels of my button box, I have some physical toggle buttons that are connected to simply be on or off (their behavior is like permanent push buttons).
The wanted behavior: Send a virtual keystroke (let's say a "k" character) to the PC whenever the toggle status is CHANGED.
The question: How would you do this using IoAbstraction? Using normal callback and a "onRelease" Callback just initiating the same keypress?
The problem: Using a toggle button would always call the button callback twice, one time for button "pressed", one time for "held". How can I avoid this?
Thanks in advance,
Tom
|
|
|
Thanks Dave for letting me know! I have avoided the problem by storing the old encoder value and comparing it with the new one to get the direction. In the unlikey event of the value getting close to the boundary of 65.536, it will just set the encoder back to starting value (32.76
. That works fine for me, but I am happy that you plan to integrate this functionality.
While working with the encoders I also noticed that you would have to create a callback for each encoder separately. Maybe integrate that the encoders return not only the new value, but also the pin number, like the buttons do. That way you could have one callback function for all encorders.
|
|
|
Hi,
I ran into the problem that the example for the U8g2 library seems to need to much memory for the Sparkfun Pro Micro board (5V version) that I am using. The compiler stops with the message that I would need 109 % of the memory present (31470 bytes needed, 28672 bytes present). I generated the code with the TcMenu UI designer and used most of the example sketch "simpleU8g2".
My question: Is there an easy way to reduce memory needed? As I have a fairly easy menu in mind (just 1 layer with 4-6 selectable choices), is there an alternative that would need less space, maybe 8x8 library or some other way to create a menu?
Thanks,
Tom
#include "TestforTcMenu_menu.h"
#include <IoAbstraction.h>
// the width and height of the attached OLED display.
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// Here we declare the variable using exactly the name that we used in the
// designers code generator panel for the graphics variable. The name and
// type must match exactly
U8G2_SH1106_128X64_NONAME_F_HW_I2C gfx(U8G2_R0, 5, 4);
IoAbstractionRef arduinoPins = ioUsingArduino();
//
// In a tcMenu application, before calling setupMenu it's your responsibility to ensure
// that the display you're going to use is ready for drawing. You also need to start
// wire if you have any I2C devices. Here I start serial for some printing in the callback.
//
void setup() {
// If you use i2c devices, be sure to start wire.
Serial.begin(115200);
// start up the display. Important, the rendering expects this has been done.
gfx.begin();
// This is added by tcMenu Designer automatically during the first setup.
setupMenu();
// Note:
// during setup in a full menu application you'd probably load values
// back from EEPROM and maybe initialise your remote code (see other examples)
}
//
// In any IoAbstraction based application you'll normally use tasks via taskManager
// instead of writing code in loop. You are free to write code here as long as it
// does not delay or block execution. Otherwise task manager will be blocked.
//
void loop() {
taskManager.runLoop();
}
//
// this is the callback function that we declared in the designer for action
// "Start Toasting". This will be called when the action is performed. Notice
// instead of using callbacks for every toaster setting, we just get the value
// associated with the menu item directly.
//
void CALLBACK_FUNCTION onStartToasting(int id) {
Serial.println("Let's start toasting");
Serial.print("Power: "); Serial.println(menuToasterPower.getCurrentValue());
Serial.print("Type: "); Serial.println(menuType.getCurrentValue());
Serial.print("Frozen: "); Serial.println(menuFrozen.getCurrentValue());
}
|
|
|
Hi Dave,
I sent you an email with the error message and file list via the contact form. By the way: I am getting this error even when I am not using the TcMenu UI designer and just double click one any of the exmample files. To make sure I did not break anything, I deleted and reinstalled the library - so this is happening after a complete "vanilla" install.
Thanks for the help!
Tom
|
|
|
I hope it is okay to bump this as I have exactly the same problem with TcMenu: Trying to compile, I get the error "tcMenu.h: No such file or directory".
Here is what I did:
1) Install the TcMenu designer, update all the libraries (clicking the button)
2) Use the U8g2 example, generate code as shown in the tutorial. -> got this error while compiling
3) Tried saving an example under a different name -> got the error while compiling
4) Tried to look for the tcMenu.h manually. It is there, in the folder "C:\Users\MYUSERNAME\Documents\Arduino\libraries\tcMenu\src" where it probably belongs
5) Tried to copy tcMenu.h to different locations along the path and tried to include it manually by the arduino app "Sketch -> add file..." -> got this error while compiling
Help would be much appreaciated!
Thanks, Tom
|
|
|
Thanks for the kind and fast response! I have read through the examples and tutorials and made the firsts tests. I came across a couple of questions:
1) Sometimes, you define arduino pin 2 for an interrupt, in other examples it seems to be unnecessary - I do not understand why. Would you also have to connect the button physically?
2) As I said, I am going to simulate key presses in my project (like sending a "q" character to the pc, when button 3 ist pressed or so) I wanted to function part of the rotatry encoders the same way: turning left sends a "p", turning right sends a "g" (just an example). It's just quicker than hitting "g" 10 times on the keyboard. I know that this is different from using the encorder in a "classical" way. To do this, can you switch off the encoder maximum value completely (unlimited) or will it always have a maximum value? Also, is there a way to find out the direction (L/R) of the encoder change? Of course, I could do this by some simple math (value de- or increased) but I need for some of the encoders would be: one step left -> send "p", one step right "send g".
3) Lastly, a wiring questions, if you don't feel like answering them, it's totally fine though! SDA/SCL seem to need pull up resistors. Do I need one for each I2C device separately or can I just use one for all of them, basically making a "rail" and connect many devices? Same for setting addresses on the 23017: I will need pull up or pull down resistors to A0, A1 or A2, right? For many 23017s, can I group all things to be pulled high (let's say A0 and A1 from expander 1 and A2 from expander 2) or does every input needs a separate resistor?
Thanks again,
Tom
|
|
|
Hello there,
I am quite new to Arduino, but I have started an ambitious (at least for me) project. And now I found your fantastic library that could potentially solve most of the problems I would certainly have.
Short description of what I am planning: Button box for PC gaming, using a “pro micro” with a library that enables the pro micro to be recognized as HID to emulate key presses from the keyboard. This button box has 14 push buttons, 18 toggle buttons and 7 rotary encoders with push. I got 4 x the 23017 IO expander.
My question is: Do you think the IoAbstraction library is capable of doing what I am describing? Are there limitations and do I have to change basic values (I think I already understood that I would have to change the limitation of maximum usable buttons)? My goal is to have complete software based debounce – included in the library if I am not wrong. Also, I want button presses to be handled by interrupts. It seems to me that this is the better way of coding and in addition, I want to update OLEDs from time to time (I got an TCA9548A as I am using multiple displays), which makes polling a bad idea.
I would be very happy if you could answer my questions and tell me potential caveats of my idea. Thanks in advance!
Tom
|
|
|
|
|