Best practices for object and miscelanious

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There's a point when learning things the hard way is just stupid. I think I've hit one and before I needlessly poke myself in the eye I'd like some guidance. Once caveat, this is my "for fun because I can" project. I know much of this has been done elsewhere but I'm learning.

Question 1: How much should/can one put into an object (Ardunio "module")?

Background: I'm creating a button object that will provide debounce, pin management, action, etc. My current code uses timer overflow interrupt. The app guy in me says that the timer and interrupt code goes in the class. The system guy in me says that will hide critical information.

Question 2: How evil is it to rely on interrupts to allow "background" processing?

Background: My button timer is set for a long cycle (5ms) to reduce the impact to processing. the nice thing about this is that I can use the 5ms to do quite a bit of work. In an effort to reduce the impact of the ISR I'm toggling some volatiles that are accessed in main to actually execute the button action. App guy wants to use a second ISR to manage button action. System guy is all "WTF are you trying to do, spend all your time processing ISR?"

Question 3: If a project is heavy on interrupts is it best to separate the interrupt driven components to a dedicated sub-module and use i2c/spi to communicate user input to the main logic?

Background: 20+ buttons with possible multiple user inputs while managing serial (rs232/usb) comm, i2c, calculations and some IO storage. It might make sense to use a attiny23xx or another atmega328 just for user interface leaving the core processing to the main atmega328.

I'm posting this in the avr gcc forum as the answer to the first question would seem to be related to gcc capabilities (I assume my Linux version may be different than the official WinAVR version. If gcc impacts the first answer then I expect it would impact the latter questions as well. If I posted in the wrong location, my apologies. Please relocate as necessary.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For item 2, not evil at all PROVIDED you do very little inside the interrupts. Change a variable, set a flag, write to UDR, or something simple, then get out. Background processing is exactly what interrupts are for.

You really don't need a separate module if the interrupts are managed carefully, because the communication between the two modules takes time, and if your sub module can tolerate that communication time, then there is little need for the sub module.

20 user driven buttons is actually a rather small task load. Most users can't activate more than a few buttons at the same time, even if they want to.

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Jim,

The 20 buttons will actually be 4 separate interfaces that could be accessed simultaneously. My testing with just 10 buttons lead to some "oh crap" discovery when both were accessing the global as I fell through a ridiculous set of conditionals (refer to my first post on freaks).

My modular approach is less about efficiency and more focused on keeping me sane. That may be a lost cause though.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is the timer used as a global resource?
If so, you probably do not want to lump it with one of its users.
Have the ISR bump a counter.
Each user can note the new value, do its thing and bump its own counter.

If you can get away with just setting a flag,
you can probably get away with using the interrupt flag.
No ISR needed.

Do not understand "manage button action">

Do you want 20+ button objects or 1 20+-button object?
Even if you have 20+ button objects as sub-objects,
the main object is probably the only one that needs an overflow counter.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I agree with Jim on the first count, keep ISRs slim and there is no need to worry about them. The other important piece to keep in mind is to handle things atomically where necessary. Multiple interfaces should not be able to bugger each other. Either handle them all in one place or atomically protect the data. Regardless, the system must be designed to handle it properly.

I rarely make the attempt to put the ISR in the class. Really, it has more to do with the fact that I know I might want to piggyback functionality in each ISR and I don't want the expense of a register/call ISR handler using function pointers. But, either way can work. What can be done pretty effectively, however, is to simply place the ISR in the same implementation file and allow it direct access to the class. From a software engineering standpoint, this is a bad idea because it breaks encapsulation. Nevertheless, I've found it productive and substantially more efficient than the "correct" way for embedded designs.

When a module interfaces to hardware, I usually try to abstract that interface out in such a way that the module itself is using a proxy and not the hardware registers directly. That way, if ( or when ) I decide to change how it works at a hardware level, I don't have to muck around with the logic of the functional class. Something like the button interface seems to allow this quite nicely. If it were any lower level, however, it might get difficult. The point is to make maintenance easy without adding so much abstraction that the code starts to bloat. When using C++ ( such as when creating classes for Arduino ) templates can be of great help to build the abstraction without runtime cost. However, there is a tipping point where the templates get too complicated to be easy to work with and, therefore, maintenance is inhibited... so, compromises.

Also, I am quite a fan of using multiple controllers in a design as it is a way to create finalized hardware modules ( sort of like reusable software objects ) that you can simply know work. I would not think, however, that even with 20+ buttons, it would be necessary from a computation standpoint. It may be useful, on the other hand, for the purposes of pin availability.

As for the tradeoff between doing processing in ISRs or in mainline code, it really comes down to two things. First, processing time is just processing time. To a first approximation, if the processing is done in mainline or in the ISR, it doesn't really matter, because they both work at the same rate. On the other hand, doing processing in the ISR will block other ISRs ( unless nested ISR handling is enabled... not a good idea either ) and so increase the latency with which they are handled. Thus, it is best to only do what absolutely must be done immediately in an ISR. For button handling, that might be setting a flag or it might be more extensive, like reading the buttons and storing their states. Still, processing that data, for things like press/release events, is better done in the mainline code where it won't effect the ISR latency.

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

timer is NOT global at this time. Expectation is that it will be used only for button press detection. The plan is to move the ISR to only update "current read" bitmap (small note is that the ISR will only set current post debounce). I think I'm thinking along the lines you are recommending.

The reason for the interrupt is that I intended to use main for polling devices and calculating display information. Initially I was going to have all button logic in the ISR but I quickly saw the stupid in that thought (my background is in higher level programming so I am ok saying I didn't even think about that stupid until now). My intent not is that handling button action will be part of main. I think I still need the ISR. I'm willing to try it with no ISR...

Manage button action: Each button will have one of several possible actions. Move servo, activate relay, activate solenoid, read a sensor, etc. I looking to the future I can see changing a button's function based on changing the device connected to the avr. The object will provide the map of button to action allowing the main loop to simply call the button.action() method.

There are actually several objects. What I'm calling "button object" is really one object with 20 buttons. Sorry that wasn't clear. As I was building my basic structure I quickly saw that my ISR driven method going to go from executing a dozen lines of logic to several hundred. I would like the ISR routine to simply call button.check() which will then debounce and update the bitmap. Once that returns main will call button.action() which will compare the current read to current settings and fire off the appropriate handlers.

That's the idea anyway.

For the record this is to allow marginal automation/remote actuation of devices in a pigeon loft. Kids are all grown up and the old man is lazy.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The current plan sounds good so far. I think it will work out well.

The one thing to watch out for is that method calls in an ISR increase the size of the ISR because it must push all the registers. Still, I've allowed such method calls in a number of my projects just because it is convenient.

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mckeemj wrote:
The one thing to watch out for is that method calls in an ISR increase the size of the ISR because it must push all the registers.
Not quite, but the reality is bad enough.
Calling an external C function implies that the ISR must save all the call-used registers.
avr-gcc would almost certainly lump them with the other registers saved at the beginning of the ISR.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, yes, that is true. It's not everything, but it is enough that it should be avoided when possible. The register saves are, indeed, lumped with other stack work at the beginning and end of the ISR.

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The arduino already sets up a timer to 1ms. One would think you would leverage off that. If you're worried about the amount of processing in the isr then split it up - do 4 inputs each tick so 5 ticks equals 20 inputs.

As for the architecture dilemma- get some code working first. If you're worried about efficiency, then do some measurements. You can then decide if spending X microseconds in the isr is acceptable or not.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If it is SOP to have a 1 ms timer as a global resource,
use it and document the fact for each module that relies on it.
Have the timer ISR increment a 1-byte counter.
The object's method can look something like this:

void my_class::tick_maybe(void)
{
    // assumes ::timer_counter incremented each millisecond
    // assumes this method called at least once every 5 milliseconds
    // tick occurs at an average rate of 1/(5 ms)
    // test allows for the possibility that method might be called late
    // the chosen my_counter update mechanism allows late calls to cause
    // jitter, but not to change the tick rate
    // the other will be checked for syntax
    if((uint8_t)(timer_counter-my_counter)< 0x80) {
        // tick yes
        if(0) my_counter=timer_counter+5;  // magic number
        else  my_counter+=5; // magic number
        // do stuff
    }
}

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

The arduino already sets up a timer to 1ms. One would think you would leverage off that. If you're worried about the amount of processing in the isr then split it up - do 4 inputs each tick so 5 ticks equals 20 inputs.

Well, the arduino does have a 1ms timer, but I've moved away from the arduino a bit and have moved toward Eclipse with AVR plugin using avr-gcc on linux. Using a dapa cable via parallel port for programming. While I can integrate the arduino lib/core/bootloader I'm working on learning how to "do it the hard way". Hence my method.

I've decided on the following (open to more suggestions)...

1. ISR will simply update 3 volatiles to provide limited debounce and pin state. This limits ISR size and complexity.

2. All dispatch of pin change will be managed via main loop. button.check() will take the needed actions based on the volatile bit maps. To ensure no possible change in button state mid process (ISR fires) I will use local copies of volatiles (u_int8 button = v_button;)

3. class Button will be written in such a way that it can be run on a dedicated uC providing button press status via i2c/TWI/SPI/etc.

Thanks for the ideas.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The Arduino timer is quite nice as it handles the atomic issues correctly, but some sort of equivelent timer functionality is very useful. It is a rare project that I do not have an equivalent free-running timer to do general timing tasks with. Typically, actually, I do nothing but increment a counter in a 1ms interrupt and use the counter value as ground truth in the main logic.

Martin Jay McKee

As with most things in engineering, the answer is an unabashed, "It depends."