element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Embedded and Microcontrollers
  • Technologies
  • More
Embedded and Microcontrollers
Embedded Forum Firmware Modular Design for User Input: OK?
  • Blog
  • Forum
  • Documents
  • Quiz
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Embedded and Microcontrollers to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • State Suggested Answer
  • Replies 12 replies
  • Answers 2 answers
  • Subscribers 460 subscribers
  • Views 980 views
  • Users 0 members are here
  • firmware
  • modular_design
  • best_practise
  • module
Related

Firmware Modular Design for User Input: OK?

Jan Cumps
Jan Cumps over 9 years ago

I'm working on a power regulator that takes user input from two different devices, and controls one output: a power driver.

I'd like to validate the modular design of my firmware with you.

 

image

The two input methods tell my device to output more or less power:

  • a pair of buttons: one for less power, one for more
  • a scroll wheel: turn left for less power, right for more.

The output is a PWM signal where the duty cycle defines the power going to the ballast.

 

My initial design was with a scroll wheel only, and I didn't pay too much attention on where to keep the state. I just kept it with the scroll wheel logic. Turning the wheel up or down changed that state.

My control loop would read the scroll wheel's value at regular times. If it had changed, it would change the duty cycle of the output.

 

My new design uses both the scroll wheel and up/down buttons.

It shouldn't matter for the output if the user scrolls the wheel, or presses the buttons. It should react just the same.

I could keep the state of the output in the main loop, or in the output module. I choose the latter.

 

I changed the scroll wheel module to keep a delta change state in stead of an absolute value. If you turn it, that will result in a positive or negative value depending on how you turned it. It has a value of 0 if you didn't touch the scroll wheel.

I did the same logic for the button module, but the delta is -1 for the down button, +1 when the up button is pushed. 0 when none is pushed.

 

My main module is rather stupid. It just polls the delta of the modules, and sends it to the output module.

 

The output module will apply that delta to it's current value and regulate the duty cycle of its signal accordingly.

It also guards the border conditions like min and max.

 

In total I have 3 layers

  • peripheral driver
  • my module
  • the control loop

 

There's the peripheral driver from the supplier (the GIO, QUADRATURE DECODE and PWM drivers). They talk to the physical inputs and outputs. My main module doesn't know about them.

 

Above each driver, there's my own module. These translates the input events into a usable value and handles things like debounce for buttons and wrap-around for the scroll wheel.

The output module translates output values into PWM duty cycle change commands.

 

The control loop is the glue. It polls the inputs, without going to the driver level. It asks my own module that's on top of that. And it calls my own output module if anything interesting happened at the input side.

 

It all works. I can fairly easily add new input methods (there's ethernet hardware on my module, so I could take commands from there too, or a capacitive sense control?).

 

But how would you approach this? Feel free to bash my design, give advise on better approaches. Or share your typical way of working.

Thanks!

  • Sign in to reply
  • Cancel

Top Replies

  • shabaz
    shabaz over 9 years ago +2 suggested
    Hi Jan! You won't initially like this idea maybe since it is more work, but the general scalable solution would be to have some small OS, to give you the ability to have several tasks running. Then the…
  • clem57
    clem57 over 9 years ago in reply to Jan Cumps +2
    This looks better with class! OOP to be sure. class scroll wheel class buttons class power output That way the value for output is contained in the class. When a change in output feedback can be given…
  • shabaz
    shabaz over 9 years ago in reply to Jan Cumps +2
    Hi Jan! Ah. I remember now, yes easy for you to move to an OS if you want to, since you have experience with that. Always difficult knowing from the outset if an OS was needed/useful or not for the very…
  • shabaz
    0 shabaz over 9 years ago

    Hi Jan!

     

    You won't initially like this idea maybe since it is more work, but the general scalable solution would be to have some small OS, to give you the ability to

    have several tasks running.

    Then the solution becomes easier - rely on external interrupts to update some shared memory (or other form of comms) on rotation direction (and possibly

    time delta between interrupts if you wish to implement dial acceleration) and set a flag to indicate data is valid. Direction is identified using a state machine that

    stores the state (i.e. on the previous interrupt).

    A totally separate task checks the flag, and whenever data is valid it updates the count (and computes speed too if you like - can be nice to do with rotary encoders).

    For the push-buttons, read them when an interrupt occurs and record the time and set a flag. Exit the interrupt routine on further interrupts while your time delta is short,

    to eliminate false counts due to bounce. Different strategies are possible.

    Finally you have another task that handles your driver, and reads the count (or PWM or translates the count into PWM value).

    For all the above if there is a risk of error due to multiple variables being written/read then a semaphore can be used to update all your variables without any other

    task pre-empting it half-way.

    For general UI stuff the OS can be quite small, since all you really need is the task handling and some simple semaphores.

     

    Also, for even more advanced scenarios, there are whole massive frameworks written that are designed for real-time stuff, usually also relying on an underlying OS.

     

    But, having said all the above, I've seen some very sophisticated products with a processor handling multiple features and the user interface (buttons/switches/rotary encoder)

    displays etc that were totally implemented with interrupts only and a control loop which cycles around executing what it needs to, and no OS at all. They obviously need a lot

    of planning and care, but are totally feasible (just not flexible without a lot more care each time you want to add features).

    My approach to such scenarios where I don't have access to any OS would be to create some 'ticks' and use it to fire off functions that can execute in a short period.

    I would arrange it so that (say) display functions execute 30 times a second for example. And basically enhance it with external interrupts since that means you can handle

    buttons/encoders responsively, all ready for when the tick calls the function that wants to make use of it.

    Since there is no OS, global variables are used as best as possible.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • Verify Answer
    • Reject Answer
    • Cancel
  • Jan Cumps
    0 Jan Cumps over 9 years ago in reply to shabaz

    Enabling an OS for my design wouldn't be that hard - I've developed some freeRTOS designs on it before.

    I usually go that direction when things become a little more complex around timing or control.

    I think that I'm going to give your suggestion a go.

     

    Lucky for me, I don't have to do all the state handling, speed and position logic for the scroll wheel. That's all handled with the quadrature decoder hardware on board of my microcontroller.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • Jan Cumps
    0 Jan Cumps over 9 years ago in reply to shabaz

    ... maybe I could even shoot a message with the user input delta - from the button task or the scroll wheel task - and have the PWM output task poll and consume them.

    That would be really loose coupling, and would allow to add additional input controls.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • clem57
    0 clem57 over 9 years ago in reply to Jan Cumps

    This looks better with class! OOP to be sure.

    1. class scroll wheel
    2. class buttons
    3. class power output

     

    That way the value for output is contained in the class. When a change in output feedback can be given to scroll to known value through a method.

    Clem

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • shabaz
    0 shabaz over 9 years ago in reply to Jan Cumps

    Hi Jan!

     

    Ah. I remember now, yes easy for you to move to an OS if you want to, since you have experience with that.

    Always difficult knowing from the outset if an OS was needed/useful or not for the very small projects.

    It brings some simplicity but also some complexity! Nice that modern microcontrollers have lots of power and memory

    to reduce resource restrictions from such a choice!

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • volly
    0 volly over 8 years ago

    clem57 and shabaz you're both right on the money....excuse the pun....

    Jan Cumps how far did you're really get with all of this....please share with us the final outcomes....

    YV.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • Jan Cumps
    0 Jan Cumps over 8 years ago in reply to shabaz

    I just made a program that uses the ticks control as you suggested, shabaz.

    I let the ticker fire every ms. In the handler, I've got routines that are called each tick, each 10 ticks, each 100 ticks and each second.

     

    all code in these functions are very short, they mostly just set some flags or variables.

    it's my main loop that then checks those flags and only calls functions if their run-flag is set.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • Jan Cumps
    0 Jan Cumps over 8 years ago in reply to volly

    Yunus Vollenhoven wrote:

    ...

    Jan Cumps how far did you're really get with all of this....please share with us the final outcomes....

    YV.

    The project is finished.

    Hercules LaunchPad and GaN FETs - Part 1: Control Big Power with a Flimsy Mouse Scroll Wheel

     

    This video is unavailable.
    You don't have permission to edit metadata of this video.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
  • Jan Cumps
    0 Jan Cumps over 8 years ago in reply to shabaz

    Here's the example I did to work with clock ticks and tasks that run at different times.

    It's only useful if you have a non-complex time budget, and if things don't break if they can skip a beat.

    In general, it works though.

     

    Here's the interupt service routine. It just sets a 1ms, 10ms, 100ms and 1s flag when appropriate. Then exits

     

    /* USER CODE BEGIN (8) */
    // these are defined in sys_main.c:
    extern volatile bool bRun1msTasks;
    extern volatile bool bRun10msTasks;
    extern volatile bool bRun100msTasks;
    extern volatile bool bRun1sTasks;
    /* USER CODE END */
    #pragma WEAK(rtiNotification)
    void rtiNotification(uint32 notification)
    {
    /*  enter user code between the USER CODE BEGIN and USER CODE END. */
    /* USER CODE BEGIN (9) */
      static uint32 uTicker = 0U;
      if (notification == rtiNOTIFICATION_COMPARE0) {
    
    
      uTicker++;
      // todo: create a 1ms, 10 ms, 100ms callback
      bRun1msTasks = true; // so yes, flag that the ms handler should be called
      bRun10msTasks = ! (uTicker % 10); // each 10th ms, flag that the 10ms handler should be called
      bRun100msTasks = ! (uTicker % 100); // each 100th ms, flag that the 100ms handler should be called
      if (uTicker > 999) { // second
        bRun1sTasks = true; // flag that the seconds handler should be called
        uTicker = 0; // and start over
      }
    
    
      }
    /* USER CODE END */
    }

     

    Then in my main() function, a loop that dispatches based on those flags

     

    // the activities in the program are driven by the RTI
    // the volatile variables are set by the rti interrupt handler and read in the main thread.
    // the non-volatile are only used in the main thread.
    volatile bool bRun1msTasks = false;
    bool isRunning1msTask = false;
    volatile bool bRun10msTasks = false;
    bool isRunning10msTask = false;
    volatile bool bRun100msTasks = false;
    bool isRunning100msTask = false;
    volatile bool bRun1sTasks = false;
    bool isRunning1sTask = false;
    
    
    // prototypes for the task handlers
    void run1msTasks();
    void run10msTasks();
    void run100msTasks();
    void run1sTasks();
    
    // ...
    
        while(1) {
          if (bRun1msTasks && ! isRunning1msTask) {
            run1msTasks();
          }
          if (bRun10msTasks && ! isRunning10msTask) {
            run10msTasks();
          }
          if (bRun100msTasks && ! isRunning100msTask) {
            run100msTasks();
          }
          if (bRun1sTasks && ! isRunning1sTask){
            run1sTasks();
          }
        }

     

    And then a task for each of these four timings, where the actual things happen

     

    void run1msTasks() {
      isRunning1msTask = true;
     //todo tasks added here
    
    
      bRun1msTasks = false;
      isRunning1msTask = false;
    }
    
    
    void run10msTasks() {
      isRunning10msTask = true;
      taskJoystick();
    
    
      bRun10msTasks = false;
      isRunning10msTask = false;
    }
    
    
    void run100msTasks() {
      isRunning100msTask = true;
     //todo tasks added here
    
    
      bRun100msTasks = false;
      isRunning100msTask = false;
    }
    
    
    void run1sTasks() {
      static bool bRunErrateESM = true;
      isRunning1sTask = true;
     //todo tasks added here
    
    
    
      bRun1sTasks = false;
      isRunning1sTask = false;
    }

     

    You can see that at the moment, I have no actions in most of the task in my program. Nothing happens each 1 ms. There's a user input (joystick) handler in the 10 ms job.

    The 100 ms and 1s tasks are empty.

     

    There's a number of weak points. No guarantee at all that tasks are exactly called at the exact right time. If the minute task takes long, the 1 ms task will not be called for a while, etc...
    It does give a rough "expect that the tasks will be called maybe close to the time you request", and also a guarantee that no task will be called earlier.

    It's a shorthand to separate between tasks that need to run often and others that need to run less often, with a single ticker.

     

    (and note to self: the isRunningXXXXsTask checks aren't needed, because all of the jobs are running in a single tread. No risk for re-entry into the same method.)

     

    Open for critique image

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • Verify Answer
    • Reject Answer
    • Cancel
  • shabaz
    0 shabaz over 8 years ago in reply to Jan Cumps

    Hi Jan,

     

    It looks fine to me! It's a basic real-time task manager, and does the job, if more advanced OS features are not needed (like process syncronization etc, although you can still use

    global variables for now). It could all fall down if care is not taken (e.g. with length of time the tasks occupy) but generally you'll know such timings in advance for the types of applications that you'd subject

    this type of code to.

    It is quite effective when some simple button-press/encoder/display type stuff is needed.

    The end result of some basic OS's is not much more than this, they might allow you to add tasks to a list in memory, and provide some functions for passing parameters or for waiting for a task to

    complete, etc., so that tasks can do things co-ordinated with each other.

    One example book that makes it easy to see how OS's work is the uC/OS-II book (there is now a uC/OS-III but I don't know how different it is) by Jean Labrosse.

    I'm not a fan of that OS, and the book is heavily geared toward only explaining that OS, but nevertheless does explain some basics in an acceptable way, so is easy-to-read.

    I probably wouldn't buy that book now, the original copy was better (way more concise) than the later versions but if a library has it then it is worth a read. I can probably dig up better book titles

    if you need them.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • Verify Answer
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube