LowPower library: Powerdown (sleep) an ATMega32u4 works but wakeup doesn't happen - c++

Situation:
I made/developing this controller (with many functions) with use of a Pro Micro (ATMega32u4) and want to extend it with a wake-up functionality when touching it (with use of a touch sensor). This all to save some energy when not used. So when the device is picked up/touched, the device woke up. When there is no contact, it will go to sleep after a while. The reach distance of the touch sensor will be extended with a small strip of copper around the case.
Picture of the device:
I use pin 9 of the touch sensor and the pin 16 of the IR-sensor to wake up but it doesn't work. Both device functions works OK, I can read the touch state (low=untouched or high=touched) and I can receive IR-commands because it is already fully implemented. When receiving a signal, those pins will be high for some period.
Software question:
Is there something wrong with my code (wrong parameters?)? The device goes to sleep when executing this code (but never woke up):
#include <avr/sleep.h> // To enter sleep mode, save power
#include <LowPower.h> // To enter sleep mode, save power
......
#define TEP_IR_RECV_PIN 16
#define TEP_PIN_TOUCHSENSOR 9
......
......
void sleep()
{
attachInterrupt( TEP_PIN_TOUCHSENSOR, wakeup, HIGH );
attachInterrupt( TEP_IR_RECV_PIN, wakeup, HIGH );
// Enter power down state with ADC and BOD module disabled.
// Wake up when wake up pin is high.
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
// Disable external pin interrupt on wake up pin.
detachInterrupt( TEP_PIN_TOUCHSENSOR );
detachInterrupt( TEP_IR_RECV_PIN );
}
Sleep function of my EnjoyPad class (will be triggered when pushing power button on remote control or when reach timeout when not touched or doing anything):
void TEnjoyPad::setSleepMode()
{
// Notify user device entering sleep mode, beep twice
setBeep( 200 );
setBeep( 200, 100, false );
// To be sure: Trigger end / unhandled down events and reset states
reset(init);
// Handle sleep event
eventSleep();
// Stop all peripherals
end();
// Finally go to sleep, code stops here
TEP_LIB_FUNCS::sleep();
// Device woke up
setBeep( 500, 100, false );
// Restart all peripherals
begin();
// Handle wake up event
eventAwake();
}
Any ideas what could be wrong? If I need to change a pin for the touch sensor, it is possible but I want to be sure it is the correct pin because I need to solder it (not on a breadboard). Pins left: 10, 14 and A3
NOTICE: I have tested, with just a delay replacement instead of going to sleep, the functions after the sleep are working just fine. So I can hear a beep and device comes on again. There is no problem with the code after sleep(), it just doesn't want to wake-up.

Okay, I figured it out myself. It seems that pin 9 and pin 16 are not usable with attachInterrupt() so it will never work. Also the usage was not correct, it must be something like this:
attachInterrupt( digitalPinToInterrupt( TEP_PIN_TOUCHSENSOR ), wakeup, HIGH );
Came up with the idea to use a pin change interrupt to wake up the device and this seems to work. However, recovering from sleep mode cannot restore all peripherals without malfunctioning so it needs a hardware reset, it must start from scratch. Also because of changes when sleeping, for example cable disconnected from computer (it also has a battery) and USB connection is lost. Not all of the classes I use are mine and not optimized for such powerdown things.
Anyway, I change the code to this, just to show you the changes, maybe it can help others too:
#include <LowPower.h> // To enter sleep mode, save power
#include <avr/wdt.h> // For watchDog device reset
......
#define TEP_IR_RECV_PIN 16
#define TEP_PIN_TOUCHSENSOR 9
......
......
namespace TEP_LIB_FUNCS
{
void pciSetup(uint8_t iPin)
{
*digitalPinToPCMSK( iPin ) |= bit( digitalPinToPCMSKbit( iPin ) ); // enable pin
PCIFR|= bit( digitalPinToPCICRbit( iPin )); // clear any outstanding interrupt
PCICR|= bit( digitalPinToPCICRbit( iPin )); // enable interrupt for the group
}
#if defined( TEP_PIN_TOUCHSENSOR )
#if TEP_PIN_TOUCHSENSOR >= 8 && TEP_PIN_TOUCHSENSOR <= 13
ISR(PCINT0_vect)
{
#define TEP_SLEEP_WAKEUP_PIN_ENABLED 0
// Event handler for pins: D8 to D13
// Pin change wakeup event handler, does nothing but is required.
// Do not remove this.
}
#elif defined(A0) && defined(A5) && TEP_PIN_TOUCHSENSOR >= A0 && TEP_PIN_TOUCHSENSOR <= A5
ISR(PCINT1_vect)
{
#define TEP_SLEEP_WAKEUP_PIN_ENABLED 1
// Event handler for pins: A0 to A5
// Pin change wakeup event handler, does nothing but is required.
// Do not remove this.
}
#elif TEP_PIN_TOUCHSENSOR >= 0 && TEP_PIN_TOUCHSENSOR <= 7
ISR(PCINT2_vect)
{
#define TEP_SLEEP_WAKEUP_PIN_ENABLED 0
// Event handler for pins: A0 to A5
// Pin change wakeup event handler, does nothing but is required.
// Do not remove this.
}
#endif
#endif
void sleep()
{
// Possible pins Micro, Leonardo, other 32u4-based: 0, 1, 2, 3, 7.
// Because we use all pins already by other functions, we cannot
// use attachInterrupt(), it doesn't work.
// see also: https://www.arduino.cc/en/Reference/AttachInterrupt
// Instead of this we use a change event interrupt to wake up
// the device from sleep state.
//
// The device can be woke up by using:
// - The reset button
// - The touch sensor
// Okay, lets go
// We enable interrupts here to be sure it is going to work
interrupts();
#ifdef TEP_SLEEP_WAKEUP_PIN_ENABLED
// Set pin change interrupt enabled for sensor pin
// (See also https://playground.arduino.cc/Main/PinChangeInterrupt)
// old code: attachInterrupt( digitalPinToInterrupt( TEP_PIN_TOUCHSENSOR ), wakeup, HIGH );
// old code: attachInterrupt( digitalPinToInterrupt( TEP_IR_RECV_PIN ), wakeup, HIGH );
pciSetup( TEP_PIN_TOUCHSENSOR );
#endif
// Enter power down state with ADC and BOD module disabled.
// Wake up when wake up pin has changed.
LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF );
// Disable external pin interrupt on wake up pin.
// old code: detachInterrupt( TEP_PIN_TOUCHSENSOR );
// old code: detachInterrupt( TEP_IR_RECV_PIN );
}
void softReset()
{
wdt_enable(WDTO_15MS);
while(true) {}
}
// Function Implementation
void init(void)
{
MCUSR = 0;
wdt_disable();
}
} // end namespace TEP_LIB_FUNCS
And also the sleep function of my EnjoyPad class (will be triggered when pushing power button on remote control or when reach timeout when not touched or doing anything):
void TEnjoyPad::setSleepMode()
{
// Notify user device entering sleep mode, beep twice
setBeep( 200 );
setBeep( 200, 100, false );
// To be sure: Trigger end / unhandled down events and reset states
reset(init);
// Shut down any
broadcastSleepMode();
// Handle sleep event
eventSleep();
// Stop all peripherals
end();
// Turn off onboard led
digitalWrite( 13, LOW );
pinMode( 13, INPUT );
// Finally go to sleep, code stops here
TEP_LIB_FUNCS::sleep();
// Device woke up
setBeep( 500, 100, false );
// Handle wake up event
eventAwake();
// Restart whole device, start from scratch.
// This seems to be the best way to guarantee all
// peripherals will be initialized properly
// and without errors.
TEP_LIB_FUNCS::softReset();
}
NOTICE: Restarting this type of MCU is fast, there is no remarkable difference in speed when you wake up and continue or wake up and reset.
That's it for the moment, tanks for watching ;-)
----------
EDIT: When adding pciSetup( TEP_IR_RECV_PIN ); it also seems to work perfectly (because there is already an interrupt handler defined for it). So the device can also woke up when receiving an IR-command. Didn't expect this was possible but tried it, neat feature.

Related

SAMD21 doesn't wake up after deep sleep

I'am working on a personnal board build with an atsamd21e18a. I'am actually working on sleep mode. I make a function to put samd21 in sleep mode like that. I use RTCZero library.
So in my setup function I've something like this
RTCZero rtc;
void setup(){
// Set the XOSC32K to run in standby
SYSCTRL->XOSC32K.bit.RUNSTDBY = 1;
rtc.begin();
.... other line code ....
attachInterrupt(digitalPinToInterrupt(PIN_USER_BUTTON), wakeUpButtonUser, CHANGE);
...other line....
}
So in my setup I intialise rtc and I attach an interrupt in my user button. This button is used to power on or power off my board.
My function goTosleep() :
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__DSB();
__WFI();
In my function wakeUpButtonUser I have a simple if/else statement. If the user pressed the button more than 3 seconds samd21 go to sleep with goToSleep() function, else if it's less than 3 seconds I want wake up the board and light up my led but it's doesn't work.
Here my wakeUpButtonUser function, user_button is object create from C++ for my button.
void wakeUpButtonUser(){
if (userButton.isPressed())
{
userButton.setTimeStartPressed(millis());
PRINT_DEBUG_LN("Pressed");
}
else
{
userButton.setTimeEndPressed(millis());
PRINT_DEBUG_LN("Released");
uint32_t time_pressed = userButton.getTimePressed();
if (time_pressed >= temps_on && time_pressed < temps_off)
{
PRINT_DEBUG_LN("ON");
//here I have code to light up a led
}
else
{
PRINT_DEBUG_LN("STOP");
goSleepMode();
}
}
}
This fuction works because if I comment the line goToSleep(), I can read on my serial ON or OFF depending the time I pressed my button and the led works also because I can light up led before going to sleep. But when my board go to sleep, she's never wake up I don't understand why, I missed something?

Hard Fault When Disabling Timer?

I am trying to get a pulse of 100us to occur 4 times a second through GPIO. The way I am doing this is by having two timer-based interrupts; one that triggers 4 times every second, and another that gets triggered 100us after the first.
Within the interrupt handler of the first timer, the target pin is set high, the second timer is reset, and interrupts on the second timer are enabled. Within the second interrupt handler, the target pin is set low and interrupts are disabled. Here is what my code looks like:
First timer's ISR:
void TIM4_IRQHandler(void)
{
{
TIM4 -> SR = ~(TIM_SR_UIF); // clear UIF flag
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_HIGH); // target pin
endTrigger->restartTimer();
endTrigger->enableInterrupts();
}
}
Second Timer's ISR:
void TIM5_IRQHandler(void)
{
{
TIM5 -> SR = ~(TIM_SR_UIF); // clear UIF flag
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_LOW); // target pin
endTrigger->disableInterrupts();
}
}
retart timer function:
void Timer::restartTimer() {
myhTim->CR1 &= ~TIM_CR1_CEN; // disable the timer
myhTim->CNT = 0; // reset count
myhTim->SR = 0; // clear any interrupt flags
myhTim->CR1 = TIM_CR1_CEN; // re-engage timer
}
For whatever reason, the second I write to CR1 I get a hard fault... Any idea why? I am aware that there are other approaches to getting a 100us pulse but this seemed to be the simplest way for what our needs are... We aren't going to need the additional timer and we will need to be semi-frequently syncing the pulse to an external piece of hardware.
The timer interrupt occurred immediately after initializing the first timer. I had to add a line of code to my second IRQ such that it would only attempt to monkey with the second timer in the case of it not being a nullptr.

ESP8266-01 does not react to AT Commands over UART with TM4C123GH6PM

I am trying to connect my TM4C123GH6PM Microcontroller from Texas Instruments with my Smartphone and use it to control an alarm clock and LED Lights. (the LEDs are controlled over a Transistor, which is controlled over an GPIO Pin).
I have some experience with coding in C++ and the TM4C123GH6PM, but I am still learning a lot. So please excuse some foolish mistakes I might have made.
I want to connect the ESP8266 with the Microcontroller using UART and the TivaWare Framework.
I have written some code and my UART works correctly (I tested it by sending chars from UART 4 to 3).
According to the AT commands of ESP8266 It should respond to "AT" with "OK". But whenever I send something to the ESP it responds with exactly what I sent to it. I checked the wiring, and that's not The Issue. Or at least I think so. Please correct me, if the wiring is wrong.
ESP -> TM4C123GH6PM:
GND -> GND
VCC -> 3.3V
Tx -> Rx (UART3 / PC6)
Rx -> Tx (UART4 / PC5)
CH_PD -> 3.3V
I also checked for the power consumption of the ESP. Everything is powered by the USB-port of my laptop, since that helps keep the cable mess down. I monitor the power consumption with (https://www.amazon.de/gp/product/B07C8CM5TG/ref=ppx_yo_dt_b_asin_title_o08_s00?ie=UTF8&psc=1). The ESP is drawing about 150mA from the computer, but the port can provide a lot more. I checked with some LEDs and 400mA is not a problem.
Can anyone help me? I am working on this now for over two days and can't find a Solution. What is the Problem with the ESP not responding correctly to the AT command? The blue light is one, when the code is running.
PS: The attached code contains also code for the alarm clock control and LEDs. I attached it, since it could be part of the problem, but some of it is commented out and most of it is not used.
#include<stdint.h>
#include<stdbool.h>
#include"inc/hw_ints.h"
#include"inc/hw_memmap.h"
#include"inc/hw_types.h"
#include"driverlib/gpio.h"
#include"driverlib/sysctl.h"
#include"driverlib/timer.h"
#include"driverlib/interrupt.h"
#include"driverlib/uart.h"
#include"driverlib/pin_map.h"
#include "driverlib/rom.h"
// stores the time since system start in ms
uint32_t systemTime_ms;
//bools or controling the alarm clock and LEDS
bool an_aus = false;
bool alarm_clock = false;
void InterruptHandlerTimer0A (void)
{
// Clear the timer interrupt flag to avoid calling it up again directly
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
// increase the ms counter by 1 ms
systemTime_ms++;
}
void clockSetup(void)
{
uint32_t timerPeriod;
//configure clock
SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ| SYSCTL_OSC_MAIN);
//activate peripherals for the timer
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
// configure timers as 32 bit timers in periodic mode
TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
// set the variable timerPeriod to the number of periods to generate a timeout every ms
timerPeriod = (SysCtlClockGet()/1000);
// pass the variable timerPeriod to the TIMER-0-A
TimerLoadSet(TIMER0_BASE, TIMER_A, timerPeriod-1);
// register the InterruptHandlerTimer0A function as an interrupt service routine
TimerIntRegister(TIMER0_BASE, TIMER_A, &(InterruptHandlerTimer0A));
// activate the interrupt on TIMER-0-A
IntEnable(INT_TIMER0A);
// generate an interrupt when TIMER-0-A generates a timeout
TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
// all interrupts are activated
IntMasterEnable();
// start the timer
TimerEnable(TIMER0_BASE, TIMER_A);
}
void UART (void)
{
//configure UART 4:
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART4);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UART4));
//GPIO pins for transmitting and receiving
GPIOPinConfigure(GPIO_PC4_U4RX);
GPIOPinConfigure(GPIO_PC5_U4TX);
GPIOPinTypeUART(GPIO_PORTC_BASE, GPIO_PIN_4 | GPIO_PIN_5);
//configure UART 8Bit, no parity, baudrat 38400
UARTConfigSetExpClk(UART4_BASE, SysCtlClockGet(), 38400, (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
//configure UART 3:
SysCtlPeripheralEnable(SYSCTL_PERIPH_UART3);
while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UART3));
GPIOPinConfigure(GPIO_PC6_U3RX);
GPIOPinConfigure(GPIO_PC7_U3TX);
GPIOPinTypeUART(GPIO_PORTC_BASE, GPIO_PIN_6 | GPIO_PIN_7);
UARTConfigSetExpClk(UART3_BASE, SysCtlClockGet(), 38400, (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
}
void delay_ms(uint32_t waitTime)
{
// Saves the current system time in ms
uint32_t aktuell = systemTime_ms;
// Wait until the current system time corresponds to the sum of the time at the start of the delay and the waiting time
while(aktuell + waitTime > systemTime_ms);
}
void ex_int_handler(void)
{
// press the button to start timer for alarm clock
alarm_clock = true;
GPIOIntClear(GPIO_PORTF_BASE,GPIO_PIN_4);
}
int main(void)
{
//Peripherals for LED and GPIO
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
//UART
UART();
//Timer
clockSetup();
// button
GPIOPinTypeGPIOInput(GPIO_PORTF_BASE,GPIO_PIN_4);
GPIOPadConfigSet(GPIO_PORTF_BASE,GPIO_PIN_4,GPIO_STRENGTH_2MA,GPIO_PIN_TYPE_STD_WPU);
//OnboardLED
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,GPIO_PIN_1);
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE,GPIO_PIN_3);
//Interrupt Timer
GPIOIntDisable(GPIO_PORTF_BASE,GPIO_PIN_4);
GPIOIntClear(GPIO_PORTF_BASE,GPIO_PIN_4);
GPIOIntTypeSet(GPIO_PORTF_BASE,GPIO_PIN_4,GPIO_FALLING_EDGE);
GPIOIntRegister(GPIO_PORTF_BASE,ex_int_handler);
GPIOIntEnable(GPIO_PORTF_BASE,GPIO_PIN_4);
//Transistor Gate
GPIOPinTypeGPIOOutput(GPIO_PORTB_BASE,GPIO_PIN_0);
//GPIOPadConfigSet(GPIO_PORTB_BASE,GPIO_PIN_0,GPIO_STRENGTH_6MA,GPIO_PIN_TYPE_STD_WPU);
//debugging only: save all the received data from the ESP in an array to look at while debugging
int32_t data[20] = {0};
int32_t j = 0;
//Code for debugging the UART and ESP8266
while(1){
//Checks for Data in the FIFO
while(!UARTCharsAvail(UART4_BASE));
//send AT-command to ESP8266
UARTCharPut(UART4_BASE, 'A');
while(UARTBusy(UART4_BASE));
UARTCharPut(UART4_BASE, 'T');
while(UARTBusy(UART4_BASE));
if(UARTCharsAvail(UART3_BASE))
{
while(UARTCharsAvail(UART3_BASE))
{
//Read data from the FIFO in UART3 -> received from ESP8266
data[j] = UARTCharGet(UART3_BASE);
j++;
}
}
//clear array when its full
if (j >= 20)
{
j = 0;
for(int32_t a = 0; a <21; a++)
{
data[a] = 0;
}
}
}
//code to run the alarm clock and leds
/*
while(1)
{
if (alarm_clock)
{
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_3,GPIO_PIN_3);
//Wait
delay_ms(30600000);
GPIOPinWrite(GPIO_PORTB_BASE,GPIO_PIN_0,GPIO_PIN_0);
alarm_clock = false;
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_3,0x00);
//Start Red LED blinking when it is finished
while(1)
{
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1,GPIO_PIN_1);
delay_ms(1000);
GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1,0x00);
delay_ms(1000);
}
}
}
*/
}
According to the AT commands of ESP8266 It should respond to "AT" with
"OK". But whenever I send something to the ESP it responds with
exactly what I sent to it
Modems with AT Commands commonly ship with the echo mode turned on, so that when you are interacting with it manually through serial port, it will echo the characters you sent first, and then send the reply.
So, when you are automating the process, you first send the characters, then wait for the reply until you reach a '\r'. Well, you are reaching a '\r', but its the one from the echo. You might have some other characters next. You send AT, you should receive AT first, then you have the OK.
To solve this problem, you should turn echo mode off.
The command to turn off echo is ATE0.

Is there a general I2C command to see if a device is still present on the bus?

Is there a general I2C command to see if a device is still present on the bus after it is initialized once? For example an OLED display. The reason I ask this is to avoid the main program will freeze (when a device is disconnected) because of infinite loops present in the library code, in for example, the Wire library.
At startup of the MCU I want to check if a device is available or not, and initialize it when it is available. I do this with this function and works fine .....
bool MyClass::isPnpDeviceAvailable( uint8_t iAddress, bool bIsInitOnce = false )
{
// Try to start connection
Wire.beginTransmission( iAddress );
// End connection without STOP command if already is initialized
return ( Wire.endTransmission( !bIsInitOnce ) == 0x00 ); // No Error?, return true
}
.... however, when I want to check if a device is still there, before I perform an update, when I do this:
// 1.
if( isPnpDeviceAvailable( 0x3C, true ))
{ /* Cause program hang */ }
// 2.
if( isPnpDeviceAvailable( 0x3C ))
{ /* Cause display to turn off */ }
Is there a general command available, to say/send just a "Hello, are you there" and wait for a reply without sending START and STOP commands and without interrupting device/bus status?
Here is the proto-type device I made with attached (optional PNP I2C) display.
Allright, it takes a longer journey to figure it out and test it. Also made a video of it, see link at the bottom of this answer. All credits go to #user0042 who points me into the right direction. The default Wire library is actually of no use when it comes to stability, reliability, so it is required to 'replace' it with this:
The I2C Master library -
http://dsscircuits.com/articles/arduino-i2c-master-library
There are more benefits to use this library, it is smaller in compile size, read the article above for more information.
I changed my software, the 'key' to detect a device on the bus could be simplified to this:
bool TEnjoyPad::isPnpDeviceAvailable( uint8_t iAddress )
{
return ( I2c.write( (int)iAddress, (int)0x00 ) == 0x00 );
}
Notice: The (int) typecasting is required to avoid a compiler warning but it does work fine without.
I send a **0x00 command** which do nothing, however, the device seems to answer. The function I made returns true when plugged in and false if not.
I doesn't test it with other i2c devices yet, however, will try later and update this question. For now it seems to working fine.
NOTICE: SEE UPDATE BELOW:
The PNP approach
Step #1
In the first version I didn't use any resistors (lazyness) but it is a good idea to stabilize the readouts of the bus. Add two resistors (4.7K) on the +5V output to the data lines. It is very important to do this to avoid false detections and to avoid your Arduino can still freeze because of that.
Step #2
You need to keep track on changes/device state of each I2C device. I use three states:
Connected
Reconnected (aka was connected)
Disconnected (or never connected before)
Step #3
If you use a class to 'speak' to a device, it must be dynamically created when device comes available. In my example it is something like this:
TOLEDdisplay* display; // declaration
......
......
display = new TOLEDdisplay( SDA, SCL ); // To create it
display->begin(); // it's a pointer to an object so you need to use -> instead of . (simple explanation ;-) )
......
// etc
Step #4
Before each update, you need to check the availability and the state of initialization (the three states mentioned in step #3). This is very important to avoid unnecessary delays/code execution (stress).
If it wasn't connected before, you need the create the class
If it was connected before (a reconnect), you have to reinitialize (the class and thus the device)
Step #5
You need to check for changes, in a loop or an interrupt. Better do it in a loop instead of an interrupt.
Step #6
Perform updates when changes are detected. Use a little delay of about 200ms seconds before the real update.
Some example code
You cannot use this code, however, it can give you some idea how to design your code. I use many macro's to simplify my actual code, so it is easier to read:
void TEnjoyPad::showAbout() // only showed at initialization
{
__tepClearDisplay();
__tepSetDisplayText( "ENJOYPAD v1.0" , TOD_TEXT_ALIGN_CENTER, TEP_DISPLAY_LINE1 );
__tepSetDisplayText( "(c) 2017 codebeat" , TOD_TEXT_ALIGN_CENTER, TEP_DISPLAY_LINE2 );
__tepRefreshDisplay();
setDelay( 2000 );
updateDisplay();
}
void TEnjoyPad::updateDisplay()
{
if( !__tepDisplayIsInit() )
{ return; }
__tepDrawDisplayBitmap( TEP_DISPLAY, // bitmap
0, TEP_DISPLAY_LINE0, // x,y
TEP_DISPLAY_WIDTH,
TEP_DISPLAY_HEIGHT
);
uint8_t i = TEP_MIN_MODE - 1;
__tepDrawDisplayClearRect( 0, 10, 128, 35 );
while( ++i <= TEP_MAX_MODE )
{
if ( emuMode != i )
{
// erase area, delete what's NOT selected
__tepDrawDisplayClearRect( TEP_DISPLAY_MODE_ICON_X + ((i - 1) * (TEP_DISPLAY_MODE_ICON_WIDTH + TEP_DISPLAY_MODE_ICON_SPACING)),
TEP_DISPLAY_MODE_ICON_Y,
TEP_DISPLAY_MODE_ICON_WIDTH,
TEP_DISPLAY_MODE_ICON_HEIGHT
);
}
else {
__tepSetDisplayText( TEP_MODE_GET_NAME(i), TOD_TEXT_ALIGN_CENTER, TEP_DISPLAY_LINE1 );
}
}
__tepRefreshDisplay();
}
void TEnjoyPad::beginDisplay( bool bIsFound = false )
{
static bool bWasConnected = false;
bIsFound = bIsFound?true:isPnpDeviceAvailable( TEP_PNP_ADDR_DISPLAY );
if( bIsFound )
{
if( !bWasConnected )
{
if( pnpStates[ TEP_PNP_IDX_DISPLAY ] )
{
// Reset
setDelay( 200 );
// Reset display
bIsFound = isPnpDeviceAvailable( TEP_PNP_ADDR_DISPLAY );
if( bIsFound )
{
__tepDisplay->begin();
updateDisplay();
}
}
else {
// (re-)connected" );
__tepCreateDisplay(); // This macro checks also if class is created
__tepInitDisplay();
showAbout();
// Set class is created
pnpStates[ TEP_PNP_IDX_DISPLAY ] = TEP_PNP_ADDR_DISPLAY;
}
}
bWasConnected = bIsFound;
}
else {
// Disconnected
bWasConnected = false;
}
}
// In a loop I call this function:
uint8_t TEnjoyPad::i2CPnpScan()
{
uint8_t iAddress = 0x7F; // 127
bool bFound = false;
uint8_t iFound = 0;
//Serial.println( "Scanning PNP devices...." );
while ( --iAddress )
{
//Serial.print( "Scanning address: 0x" );
//Serial.println( iAddress, HEX );
if( iAddress == TEP_PNP_ADDR_DISPLAY )
{ beginDisplay( bFound = isPnpDeviceAvailable( iAddress ) );
iFound+=bFound;
}
}
return iFound;
}
Demo video
I also create a demo video, proof of concept, to show you this method is working fine. You can watch the video on YouTube:
https://www.youtube.com/watch?v=ODWqPQJk8Xo
Thank you all for the help and hopefully this info can help others too.
UPDATE:
My method seems to work fine with several I2C devices. I wrote this renewed I2CScanner:
I2CScanner code that you can use:
/*
----------------------------------------
i2c_scanner - I2C Master Library Version
Version 1 (Wire library version)
This program (or code that looks like it)
can be found in many places.
For example on the Arduino.cc forum.
The original author is not know.
Version 2, Juni 2012, Using Arduino 1.0.1
Adapted to be as simple as possible by Arduino.cc user Krodal
Version 3, Feb 26 2013
V3 by louarnold
Version 4, March 3, 2013, Using Arduino 1.0.3
by Arduino.cc user Krodal.
Changes by louarnold removed.
Scanning addresses changed from 0...127 to 1...119,
according to the i2c scanner by Nick Gammon
http:www.gammon.com.au/forum/?id=10896
Version 5, March 28, 2013
As version 4, but address scans now to 127.
A sensor seems to use address 120.
Version 6, November 27, 2015.
Added waiting for the Leonardo serial communication.
Version 7, September 11, 2017 (I2C Master Library version)
- By codebeat
- Changed/Optimize code and variable names
- Add configuration defines
- Add fallback define to standard Wire library
- Split functionality into functions so it is easier to integrate
- Table like output
This sketch tests the standard 7-bit addresses between
range 1 to 126 (0x01 to 0x7E)
Devices with higher addresses cannot be seen.
---------------------
WHY THIS NEW VERSION?
The Wire library is not that great when it comes to stability,
reliability, it can cause the hardware to freeze because of
infinite loops inside the library when connection is lost or
the connection is unstable for some reason. Because of that
the Wire library is also not suitable for plug and play
functionality, unplugging an I2C device will immediately
lock the hardware (if you want to talk to it) and you
need to reset the hardware. I will not recover on itselfs.
Another reason is the way to check if a device is plugged-in
or not. The methods of the Wire library doesn't allow to
do this because it resets/stop the I2C device when it is
already started/available.
Benefits of the I2C Master Library:
- More flexible;
- Faster;
- Smaller compile size;
- Idiot proof;
- Self recovering (no hardware freeze);
- Able to check for availability of devices without
interrupt bus status and/or device (see the
example function isDeviceAvailable() how to achieve
this)
.
More info at:
http://dsscircuits.com/articles/arduino-i2c-master-library
You can also download the library there.
PRECAUTIONS:
It is a good idea to stabilize the readouts of the bus.
Add two resistors (4.7K) on the +5V output to the data lines.
Only one pair is required, don't use more or different resistors.
It is very important to do this to avoid false detections and to
avoid your Arduino can still freeze because of that.
NOTICE:
When selecting the default Wire library, this scanner will probably
not show the side effects I am talking about because the code
don't talk to the device and the connection to a device is extremely
short period of time.
*/
// *** Uncomment this if you want to use the default Wire library.
//#define I2C_LIB_WIRE
// Some settings you can change if you want but be careful
#define I2C_MIN_ADDRESS 0x01
#define I2C_MAX_ADDRESS 0x7F
#define I2C_UPDATE_TIMEOUT 3000
#define I2C_I2CLIB_TIMEOUT 1000
#define I2C_I2CLIB_FASTBUS true
// Errorcodes that are normal errors when I2C device does
// not exists.
#define I2C_I2CLIB_ERROR_NOT_AVAIL 32
#define I2C_WIRELIB_ERROR_NOT_AVAIL 2
// -------------------------------------------------------------
#ifdef I2C_LIB_WIRE
#define I2C_ERROR_NOT_AVAIL I2C_WIRELIB_ERROR_NOT_AVAIL
// Compile size with Wire library: 6014 bytes
#include <Wire.h>
#pragma message "Compiled with Wire library"
#else
#define I2C_ERROR_NOT_AVAIL I2C_I2CLIB_ERROR_NOT_AVAIL
// Compile size with I2C Master library: 5098 bytes
#include <I2C.h>
#define Wire I2c
#pragma message "Compiled with I2C Master library"
#endif
// -------------------------------------------------------------
int iLastError = 0;
bool isDeviceAvailable( uint8_t iAddress )
{
#ifdef I2C_LIB_WIRE
// Wire:
// The i2c_scanner uses the return value of the Write.endTransmisstion
// to see if a device did acknowledge to the address.
Wire.beginTransmission( iAddress );
iLastError = Wire.endTransmission();
#else
// I2C Master Library:
// Just send/write a meaningless 0x00 command to the address
// to figure out the device is there and the device answers.
iLastError = Wire.write( (int)iAddress, (int)0x00 );
// Notice: The (int) typecasting is required to avoid compiler
// function candidate notice.
#endif
return ( iLastError == 0x00 );
}
byte findI2Cdevices( bool bVerbose = true )
{
byte nDevices = 0;
if( bVerbose )
{ Serial.println("Scanning..."); }
for(byte iAddress = I2C_MIN_ADDRESS; iAddress < I2C_MAX_ADDRESS; iAddress++ )
{
if( bVerbose )
{
Serial.print("Address 0x");
if( iAddress < 16 )
{ Serial.print("0"); }
Serial.print( iAddress, HEX );
Serial.print(": ");
}
if( isDeviceAvailable( iAddress ) )
{
if( bVerbose )
{ Serial.println("FOUND !"); }
nDevices++;
}
else {
if( bVerbose )
{
Serial.print( "<NO DEVICE FOUND" );
if( iLastError != I2C_ERROR_NOT_AVAIL )
{
Serial.print( " - ERRCODE: " );
Serial.print( iLastError );
}
Serial.println( ">" );
}
}
}
if( bVerbose )
{
if( nDevices > 0 )
{
Serial.print( nDevices );
Serial.println( " device(s) found\n" );
}
else { Serial.println( "No I2C devices found\n"); }
Serial.print( "Press CTRL+A, CRTL+C to copy data.\n" );
}
return nDevices;
}
void setupI2C()
{
Wire.begin();
#ifndef I2C_LIB_WIRE
// This is important, don't set too low, never set it zero.
Wire.timeOut( I2C_I2CLIB_TIMEOUT );
#ifdef I2C_I2CLIB_FASTBUS
if( I2C_I2CLIB_FASTBUS )
{ Wire.setSpeed(1); }
#endif
#endif
}
void setupSerial()
{
Serial.begin(9600);
while (!Serial); // Leonardo: wait for serial monitor
Serial.println("\nI2C Scanner");
}
// -------------------------------------------------------------
void setup()
{
setupI2C();
setupSerial();
}
void loop()
{
// Skip the Arduino slow down housekeeping after the loop()
// function, we stay here forever ;-)
while(1)
{
findI2Cdevices();
delay( I2C_UPDATE_TIMEOUT ); // wait n seconds for next scan
}
}
#immibis made a very good point.
The probably better solution is to harness your update command with a certain timeout, that breaks blocking at that point.
Here seems to be some more information how to realize that properly.
Here's another Q&A from the SE Arduino site, matching the topic.

Performance when use a timer vs a lot of thread to listen event changed?

I need listen a lot of (<10) external event send to app and process them. Event can happen every 50ms or can is a hour, can't predetermine time or frequency, depend user. System use pthread on linux 3.6.5, single core cpu, ram 512. Listener must start at begin system and stop at end system, don't delay recive event (in 50ms). Moreover, also have a thread run update UI, 2 thread in threadpool to download.
External event here can is mouse click, keyboard key down/up, on/off LCD, button power, press key ir remote, a file modified, a alarm push by server,..
I consider 2 option, a listener on thread or a timer thread.
can any one please explain efficient of performance with both above away when use on descripted system ? and which one would be a better choise or a new option ?
a listener on a thread
//==================================== option 1: use 1 listener/thread
// i = [0, n]
bool waitForEvent_i(){
// can is poll change on a file description, read on socket
// or pthread_cond_wait a other thread
// ...
}
void thread_i(){
while(waitForEvent_i()){
excute_i();
}
}
void startListener(){
startThread_0();
startThread_1();
....
startThread_n(); // n < 10;
}
a pthread such as timer proactive check all event can
//================================== option2: use timer to interval check
class Task{
public:
void run(){
// check if have change, call excute_i()
}
};
void timer_thread(timeout){
while(1){
while(!queue.empty()}
{
task = queue->getTask();
task->run();
usleep(timeout - task->timeExcuted()); // suppose, timeExcuted < timeout
}
usleep(timeout);
}
}
void startTimer(){
start_Timer_thread();
}