Hi,
I'm about to piece together my first big ESP32 program, and need some tips as to how I should architect the program.
During the initial phase where I've built my own ESP32 PCB, I've made standalone ESP32 applications for testing the individual parts of my board. (https://github.com/Scalpel78/Ghost/tree/master/ESP32). Now, most of the parts are working, and it's time to start piecing together this into one large application.
I'm wondering how I should structure the pieces so that they communicate in a stable fashion. Things like, when should I use Tasks, and how much work can I do in an ISR, and how should I store state. Let me explain my project, and perhaps someone has some suggestions as to how I should implement this.
The ESP32 is going to be the controller of an autonomous RC-car. The car has IR sensor (up to 16 of them), it has a Startmodule (another kind of IR sensor that triggers a Start/Stop signal), it has a Servo for steering, and a PWM-controllable motorcontroller. In addition it has an accelerometer, and a compass.
I've built a few of these cars before, but then using Arduino. The main flow of the application goes like this;
- Has the Start signal been detected? If yes, set the Drive-state, and continue.
- Query the IR sensors, and calculate which way to turn and what motor speed to use.
- Turn the wheels.
- Adjust motor speed
And so it loops over and over again.
What would be a good way to accomplish the same with ESP32? Should the IR sensors just spin up a separate task? If so, how should that task send the IR sensor readings to the part that calculates steering/motor speed? Should the calculations also be in a separate Task? And then there is the steering and motor-control. Should those be controlled by two additional tasks that get their values from somewhere?
Any tips are welcomed!
How to structure semi-complex application?
Re: How to structure semi-complex application?
Welcome to the world of solution design. Getting individual components working is GREAT fun ... but for a project of even modest size, putting it all together is a core discipline. One can literally "hack" a solution together and it will all work. No question that can be done. For larger projects, the story starts to change. As components start to interact with each other, we can get into a spaghetti entanglement where the time you saved not "architecting" the solution slowly and carefully can start to come back and bite you. Especially when bugs are found and always when a re-design is required.
The most valuable advice I can offer is that of modularization and compartmentalization. Take a piece of function and write that function so that it depends on the minimum and has a well defined (and documented) interface. This function then becomes a much coarser component ... a lego block if you like ... that can be used in the project, modified without changing its interfaces and plugged in where needed.
This is part of the reason that I'm on such a big C++ kick at the moment. For me, encapsulating functions as C++ classes enforces containment and re-use. If I write a C++ class (such as a motor controller) then it "owns" state and exposes methods that make it work. While I am focused on the implementation of the motor controller, I am in its gory depth ... but once done ... I can now "contemplate it" as a unit.
This compartmentalization allows me to later choose how the bits are bolted together.
I'd also look at an event driven design for your specific puzzle. In most designs like this, I tempt to aim for a "Model/View/Controller" design. This is commonly associated with User Interface programming where the user interacts with the screen (the view) that sends an event to the Controller which updates the Model that is reflected back to the View. In our IoT world, sensor input (or wireless events) is the equivalent of the View input, the Controller is what processes the events, the Model is updated by the controller (current speed, direction etc) and the IoT actuation (change speed in wheels for example) is again the View.
I realize that this is all computer science "like" theory ... but its where we have to go for larger projects. In my world, large projects are at the systems level where we aren't talking about 500-2000 lines of code but are instead talking 100's of thousands of lines of code and up. For the story you are describing ... even if you just "hacked it together" (and I don't mean that to be derogatory) its still manageable.
For your specific questions .... yes ... use Tasks ... they are useful ... however when you open up the world of tasks, you have stepped into a world of new considerations (locking, exclusivity of data, timing issues) and testing cost goes up substantially. What you gain in tasks is potential "efficiency" ... when a task is blocked, it is doing no un-needed work ... if you don't use tasks, then you are likely to be polling which is busy work ... but it does simplify the design for those who haven't spent a long time studying "threading" techniques. For ISR, ALWAYS do the very minimum work necessary. Post that the ISR has occurred and flip a couple of bits as needed ... but no algorithms or other time consuming work. I don't have a metric, but if I executed 100 simple statements in an ISR I would call that too much.
The most valuable advice I can offer is that of modularization and compartmentalization. Take a piece of function and write that function so that it depends on the minimum and has a well defined (and documented) interface. This function then becomes a much coarser component ... a lego block if you like ... that can be used in the project, modified without changing its interfaces and plugged in where needed.
This is part of the reason that I'm on such a big C++ kick at the moment. For me, encapsulating functions as C++ classes enforces containment and re-use. If I write a C++ class (such as a motor controller) then it "owns" state and exposes methods that make it work. While I am focused on the implementation of the motor controller, I am in its gory depth ... but once done ... I can now "contemplate it" as a unit.
This compartmentalization allows me to later choose how the bits are bolted together.
I'd also look at an event driven design for your specific puzzle. In most designs like this, I tempt to aim for a "Model/View/Controller" design. This is commonly associated with User Interface programming where the user interacts with the screen (the view) that sends an event to the Controller which updates the Model that is reflected back to the View. In our IoT world, sensor input (or wireless events) is the equivalent of the View input, the Controller is what processes the events, the Model is updated by the controller (current speed, direction etc) and the IoT actuation (change speed in wheels for example) is again the View.
I realize that this is all computer science "like" theory ... but its where we have to go for larger projects. In my world, large projects are at the systems level where we aren't talking about 500-2000 lines of code but are instead talking 100's of thousands of lines of code and up. For the story you are describing ... even if you just "hacked it together" (and I don't mean that to be derogatory) its still manageable.
For your specific questions .... yes ... use Tasks ... they are useful ... however when you open up the world of tasks, you have stepped into a world of new considerations (locking, exclusivity of data, timing issues) and testing cost goes up substantially. What you gain in tasks is potential "efficiency" ... when a task is blocked, it is doing no un-needed work ... if you don't use tasks, then you are likely to be polling which is busy work ... but it does simplify the design for those who haven't spent a long time studying "threading" techniques. For ISR, ALWAYS do the very minimum work necessary. Post that the ISR has occurred and flip a couple of bits as needed ... but no algorithms or other time consuming work. I don't have a metric, but if I executed 100 simple statements in an ISR I would call that too much.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32
Re: How to structure semi-complex application?
Hi, and thanks for your proper reply.
Those are all very good comments, and you are preaching to the choir here. I'm also a fan of modularization and compartmentalization. As a side note I've been a software developer for the better part of 13 years now, but for the most part with C# and web related software.
I'm thinking that the IR sensors will be queried, from inside a task, every so often. The result of the distance measurements can be put in a global state somewhere, probably some tens of times every second.
Then another task will be responsible for transforming the sensory data (from IR , accelerometer, compass and wheelencoder) into control actions to manouvre the car. Those control actions (direction to turn, and motor speed to set) can also be written to the global state, and from there picked up by the tasks which controls the actuators.
Is there any way in the ESP world to raise some sort of internal event when the model is updated? So that when the sensors have updated the model, then that can trigger the task which should do the calculations.
Also, using C++ might be a good choice for me as well. Is there any specific setup which has to be done in order to use C++?
Those are all very good comments, and you are preaching to the choir here. I'm also a fan of modularization and compartmentalization. As a side note I've been a software developer for the better part of 13 years now, but for the most part with C# and web related software.
I'm thinking that the IR sensors will be queried, from inside a task, every so often. The result of the distance measurements can be put in a global state somewhere, probably some tens of times every second.
Then another task will be responsible for transforming the sensory data (from IR , accelerometer, compass and wheelencoder) into control actions to manouvre the car. Those control actions (direction to turn, and motor speed to set) can also be written to the global state, and from there picked up by the tasks which controls the actuators.
Is there any way in the ESP world to raise some sort of internal event when the model is updated? So that when the sensors have updated the model, then that can trigger the task which should do the calculations.
Also, using C++ might be a good choice for me as well. Is there any specific setup which has to be done in order to use C++?
Re: How to structure semi-complex application?
Here is a video on using C++ in ESP32 ... https://www.youtube.com/watch?v=-ttiPfmrehU
All works deliciously well and I for one am delighted with the level of support.
Back to your core question ... the ESP-IDF is built on top of FreeRTOS - http://www.freertos.org/
What that means is that you have all the functions of FreeRTOS available to you. This includes queues, semaphoes, mutices, flags and more. You can easily have a task block on a signal variable and unblock when the signal is signaled in another task. For me, Ive been a big fan of the FreeRTOS queues. In some designs I have one task block on a queue and then another task push a new message to the queue containing the details of what I want done. This unblocks the blocked task and also gives it the data it needs to process. This is a quick and easy communication vehicle and also works great in ISR functions. It also handles those race conditions where you need to process EVERY request and if they arrive too closely together you may miss the second signal because you are still processing the first. The queues bypass that problem as if you unblock when a new message is on the queue, you will again unblock immediately when you next block waiting for the next event.
All works deliciously well and I for one am delighted with the level of support.
Back to your core question ... the ESP-IDF is built on top of FreeRTOS - http://www.freertos.org/
What that means is that you have all the functions of FreeRTOS available to you. This includes queues, semaphoes, mutices, flags and more. You can easily have a task block on a signal variable and unblock when the signal is signaled in another task. For me, Ive been a big fan of the FreeRTOS queues. In some designs I have one task block on a queue and then another task push a new message to the queue containing the details of what I want done. This unblocks the blocked task and also gives it the data it needs to process. This is a quick and easy communication vehicle and also works great in ISR functions. It also handles those race conditions where you need to process EVERY request and if they arrive too closely together you may miss the second signal because you are still processing the first. The queues bypass that problem as if you unblock when a new message is on the queue, you will again unblock immediately when you next block waiting for the next event.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32
Re: How to structure semi-complex application?
Hi,
I am fiddling myself through this documentation:
http://www.freertos.org/Documentation/RTOS_book.html
If you are in controlling a motor with sensors or commands you may find the presentation given in the following url useful:
Source:
http://www.renesasinteractive.com/file. ... ations.pdf
For me it was p.25 to get the idea:
Tasks: slow (periodic, ms resolution) - high priority
Interrupts: fast (µs resolution) - medium priority
Peripheral (e.g. pwm, direction): continuous - low priority
So, if one wants continuous processing then one has to do it in the lowest priority task,
otherwise nothing else will run.
To start in c or cpp using freertos you may find these examples useful (thanks to Neil and esp_Angus):
viewtopic.php?f=18&t=1659
Tasks can communicate with each other, and with interrupts, using FreeRTOS queues.
There is a nice example using events triggered by an uart:
https://github.com/espressif/esp-idf/tr ... art_events
Organizing a bigger c or cpp program e.g. in eclipse I would use some structure like this:
yourprogram
+components
++actuators
++sensors
++driver
++protocols
++webservices
++utils
+main
++mainapp.cpp
+Makefile
Let the compiler find the files:
Each folder (e.g. "action") should contain a 'component.mk' file
including the following lines:
COMPONENT_ADD_INCLUDEDIRS := .
COMPONENT_SRCDIRS := $(COMPONENT_ADD_INCLUDEDIRS)
To find the components folder
the 'Makefile' should include some information about the directories:
PROJECT_NAME := yourprogram
COMPONENT_ADD_INCLUDEDIRS := . components/include // depending on your configuration try '.' or '/'
include $(IDF_PATH)/make/project.mk
Let your program find the source and header files:
In C/C++, in general square brackets are used to include system headers and external headers, while double quotes are used to include local files. One has to take into consideration that angle brackets in #include directives mean the search path is limited to the "system" include directories. Double quotes mean the search path includes the current directory, followed by the system include directories.
mainapp:
...
#include "/actuators/motor.h" // to find motor header file ...
best regards
DL88AI88
I am fiddling myself through this documentation:
http://www.freertos.org/Documentation/RTOS_book.html
If you are in controlling a motor with sensors or commands you may find the presentation given in the following url useful:
Source:
http://www.renesasinteractive.com/file. ... ations.pdf
For me it was p.25 to get the idea:
Tasks: slow (periodic, ms resolution) - high priority
Interrupts: fast (µs resolution) - medium priority
Peripheral (e.g. pwm, direction): continuous - low priority
So, if one wants continuous processing then one has to do it in the lowest priority task,
otherwise nothing else will run.
To start in c or cpp using freertos you may find these examples useful (thanks to Neil and esp_Angus):
viewtopic.php?f=18&t=1659
Tasks can communicate with each other, and with interrupts, using FreeRTOS queues.
There is a nice example using events triggered by an uart:
https://github.com/espressif/esp-idf/tr ... art_events
Organizing a bigger c or cpp program e.g. in eclipse I would use some structure like this:
yourprogram
+components
++actuators
++sensors
++driver
++protocols
++webservices
++utils
+main
++mainapp.cpp
+Makefile
Let the compiler find the files:
Each folder (e.g. "action") should contain a 'component.mk' file
including the following lines:
COMPONENT_ADD_INCLUDEDIRS := .
COMPONENT_SRCDIRS := $(COMPONENT_ADD_INCLUDEDIRS)
To find the components folder
the 'Makefile' should include some information about the directories:
PROJECT_NAME := yourprogram
COMPONENT_ADD_INCLUDEDIRS := . components/include // depending on your configuration try '.' or '/'
include $(IDF_PATH)/make/project.mk
Let your program find the source and header files:
In C/C++, in general square brackets are used to include system headers and external headers, while double quotes are used to include local files. One has to take into consideration that angle brackets in #include directives mean the search path is limited to the "system" include directories. Double quotes mean the search path includes the current directory, followed by the system include directories.
mainapp:
...
#include "/actuators/motor.h" // to find motor header file ...
best regards
DL88AI88
Re: How to structure semi-complex application?
Hi guys,
I'm working my way through the FreeRTOS documentation, which seems very nicely written.
DL88AI88, the pdf in your link doesn't seem to exist anymore.
So far this is what my high-level architecture looks like:
So, basically, I'm thinking of using separate Tasks to gather the information from the sensors that needs to be queried. For the two sensors that can, I'm thinking of using interrupts to detect and notify of events. A central Task will be responsible for using all the gathered data and to tell the actuators what to do. In the first iteration this is going to be a simple implementation which mainly just uses the IR sensors to figure out where to turn. In later revisions I can play around with more and more sophisticated sensor fusion theories to make the controls more intelligent.
I have a question about the section that handles the Car State. How should I (like concretely in C++) store and pass the data from the sensors to the drive-controller? Should I have some sort of global variable object? Perhaps a singleton class? Or should the data just be passed directly between the Tasks like I see (but haven't had a chance to read the documentation for yet) that FreeRTOS supports.
Any inputs on state-handling would be much appreciated.
I'm working my way through the FreeRTOS documentation, which seems very nicely written.
DL88AI88, the pdf in your link doesn't seem to exist anymore.
So far this is what my high-level architecture looks like:
So, basically, I'm thinking of using separate Tasks to gather the information from the sensors that needs to be queried. For the two sensors that can, I'm thinking of using interrupts to detect and notify of events. A central Task will be responsible for using all the gathered data and to tell the actuators what to do. In the first iteration this is going to be a simple implementation which mainly just uses the IR sensors to figure out where to turn. In later revisions I can play around with more and more sophisticated sensor fusion theories to make the controls more intelligent.
I have a question about the section that handles the Car State. How should I (like concretely in C++) store and pass the data from the sensors to the drive-controller? Should I have some sort of global variable object? Perhaps a singleton class? Or should the data just be passed directly between the Tasks like I see (but haven't had a chance to read the documentation for yet) that FreeRTOS supports.
Any inputs on state-handling would be much appreciated.
Re: How to structure semi-complex application?
For me personally, the FreeRTOS queue technology is a life saver. See:
http://www.freertos.org/Embedded-RTOS-Queues.html
One task places an event on the queue and the "controller" task pulls the events from the queue when there is an event ready or blocks if there isn't. The controller then can examine each event and determine what to do when that event arrives.
http://www.freertos.org/Embedded-RTOS-Queues.html
One task places an event on the queue and the "controller" task pulls the events from the queue when there is an event ready or blocks if there isn't. The controller then can examine each event and determine what to do when that event arrives.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32
Who is online
Users browsing this forum: Google [Bot] and 135 guests