Merging codes - feeling like creating a Frankestein

I´ve been working on a demonstrative multi-purpose robot project and begun by developing code separately. I already have:

  • a code to read joystick position incoming from mobile (bluetooth) and move the robot according to it;
  • a code to move a servo (SG-90) with an ultrassonic sensor HC-SR04 and send data to mobile;
  • a code to read two phototransistors (TEMT6000) and store the average value of 5 readings;
  • a working code to get the raw read of the battery status and send it to the mobile;

Now it´s the time to put alltogether and I got stuck.

My idea was to call each of these codes as functions in the void loop(), but...

The code for the light reading takes 50ms between each measure, wich means that, to accomplish the 5 readings it will take at least 250ms. The sweeping with the ultrassonic is much worse, because it requires 50ms to move the servo and get each reading, requiring the trifle of 9000 ms to accomplish 180° (without considering average readings of the ultrassonic sensor).

Meanwhile, the robot should still get directions and moving around. So, I cannot simply call each code as a function inside the main loop, right? Because the main loop will be stuck inside each function even if I use millis() as the trigger for the readings inside them. Am I correct?

What do you suggest to adress this problem?

The simplest answer is to do one (millis triggered) sensor read per loop and store data for processing after readings are finished, but this looks hard to do... is there an easier approach?

yeah, you need finer granularity for your tasks.... an option is to rewrite them so that a call to the function would perform one "step" instead of the whole shebang (as long as the functions are independent from each other).

Best would be to use what you have learnt and start from scratch with a well designed architecture (states machine for example)

the basic idea is good. Use millis.
Split your tasks.
For example you mentioned the timing problem with the ultrasonic on the servo.
So call the command to move the servo to 0 grad. Let the servo move.
do other tasks,
after around 500ms (or what ever will be enough for a 0-180 movement) start your ultrasonic measurement.

Ok! Not sure I have the required programming skills, but I´ll study state machines. Thanks!

Ok, this will avoid doing the first measure while the servo is still positioning. Got it. :wink:

not only for the "first" measurement.
do it between all measurements.

How many measurements are you doing in 180 degrees?

Hi
Have you already evaluated the feasibility of using "treating"?

That´s interesting! Thanks for sharing. But in this project I´m not working with an AVR8 board. It´s an ESP32 based board.

I know that ESP32 has 2 cores and can handle multi-tasking, but I´ve failed to understand how to implement it. It would be cool if one core handle the sensor readings while the other deals with moving and comms.

So, I´m getting to the conclusion that I need some more study to achieve "state machines" and "multi-tasking". They might be the key to achieve what I´m trying to do.

How often do you need the things to happen?

Update to/from the mobile every xx/yy milliseconds?
Update the servo motion every zz milliseconds?
Update robot motion plan every rr milliseconds?
Update WW every ww?

And how much do the elements need to communicate with each other? e.g What uses the phototransistor data? Just the messaging to the mobile? The robot motion planning?

I'd work on making the code into non-blocking functions that share data with other elements. Here's a hack at couple state machine functions to maintain your lights in less than 250ms and report asynchronously. One trick is to let the functions keep track of their own scheduling.

//globals
const byte light1pin = A0;
const byte light2pin = A1;
int light1_ave =0;
int light2_ave =0;
//...

void setupLightReading(void){
  pinMode(light1_pin,INPUT);
  pinMode(light2_pin,INPUT);
}
void doLightReading(void){ // Maintain phototransistor readings
  const int  myDelay=50; //ms 
  static int count = 0;
  static int sum1 = 0;
  static int sum2 = 0; 
  unsigned long lastReading;
  if (millis() -lastReading < myDelay) return; 
  lastReading += myDelay;
  sum1 += analogRead(light1pin);
  sum2 += analogRead(light2pin);
  if (++count == 5){
    // store results in globals and reset
    light1_ave = sum1/5;
    light2_ave = sum2/5;
    sum1 = sum2 = count = 0;
  }
}

void doSerialReport(void){
  const int reportIntervalMS = 500;
  static unsigned long lastReport = 0;
  if(millis()-lastReport < reportIntervalMS) return;
  lastReport += reportIntervalMS;
  Serial.print("Lights: ");
  Serial.print(light1_ave);
  Serial.print(' ');
  Serial.println(light2_ave);
}


void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  setupLightReading();
}

void loop() {
  // put your main code here, to run repeatedly:

  doLightReading();
  doSerialReport();
}

Hi DaveX!

Thanks for answering and for your time writing this code. I´ll try to evaluate it together with what I have. But, let me answer your questions:

The ultrassonic sensor is quick, but it depends on the "slow" servo to reach a desired position. During sweep (1 degree movement each time) I´ve been working with 40ms to move the servo and then get the distance measure.

So, the robot cand send 1 distance each 40~50 ms and one light read each 250ms.

For now, the mobile is only sending x-y positions and the event of sending them is triggered by the joystick itself (no timers associated). I believe whatever other controls I might like to add in the future will also be event driven, not timer based. Thus, information from mobile to the robot is kind of "discrete".

In the other hand, sensors data will be sending from the robot to the mobile "continuously".

In the future I will want the robot to have some autonomous behaviour (one of my codes can already turn it towards the side that has more light), but for now (manual mode) it´s enough to have the readings of the sensors on the mobile.

Yeah, this is much more what I had in mind than knowledge enough to accomplish the mission :sunglasses: but your code is good start point, thanks again!

Play with @DaveX's code here.

I fixed a few typos and added a little whitespace, otherwise, his code. I didn't even look to see what it is supposed to do. It looks like it is working well beyond just comlpiing.

I used slide faders instead of a photoresistor, even though there is one in the simluator. It's just easy to bang on the sliders to simulate light.

Note: the more light, the lower the number I saw from the simulated photocell object. Just sayin', didn't expect it to be one way (higher voltage == light) or the other.

You can make your own copy of the wokwi project and use it to test without leaving the keyboard and mouse, or burning down the house or scaring the cat. Too much fun.

You can add stuff to that as @DaveX releases new version. :wink:

  if (millis()-lastReport < reportIntervalMS) return;

I like this "backwards" test, as I had recent occasion to say, because it doesn't make the real working statements want to live one tab stop indented. :expressionless:

a7

Thanks for that!
Learning some Wokwi added on my to-do list!

I like this return early form because it helps focus on returning control back to the rest of the program as soon as possible. In this scheme, the more common path should be passing control back to loop() unless there's something interesting that needs doing.

The Wokiwi thing is cool. I added a loop counting variable to loop() and the the reporting and watched it tick up about 2k loops per print, which is about what my Uno does.

So the current code/codes delivers all the data to the app as fast as it measures the data, and the app delivers x/y joystick info to the robot drives?

I'd suggest making choices about how often you want to update the app's information-maybe updates only 100-500ms would be adequate for most of it, and planning that update explicitly. It probably takes time to send the data, and maybe communicating 20Hz distance isn't as helpful as 10hz distance+light+speed+heading+.....

You could probably write a sweep-scan function that takes a measurement, updates the servo target and schedules the next scan for 40ms later, much like the lightReading, and get most of the 40ms of processing time back for autonomy, etc.

The HC-SR04 is in the parts there, and a generic mini-servo too.

a7

That´s it! This is how each of the separate codes are working now.

Ok! I´m gonna try this approach.

Do you mean bulding a unique message that gathers all the info or just synchronize the sending of all the data in small pieces? Actually I like rather the second option. Sounds like providing more freedom to adding other pieces of info later. Moreover, if one piece of information is missed during the transmission, it will not screw up with the entire message.

Hi
At the moment I am out of my residence and I only have access to a cell phone.
I believe you are in a hurry, but when I return if you still need it, I'll see if I can help you with the use of the ESP32 cores.

Hello!

I´m doing this project for learning and fun. So, actually I´m not in a hurry. :slightly_smiling_face:

With @DaveX start point and other contributions I already have some tasks to spend my time on. But I´ll be happy to have your suggestions on how to apply the multi-core coding.

Thanks in advance!

This depends on how you want to use the particular information. Without seeing your current code, it's pure speculation how you'd want to use, for example, an 180° arc of distance scans. Pick out the closest/farthest distance and aim towards it? Display the arc on your app like a radar profile?

I was speculating that maybe the UI app might be good enough being updated at 10Hz for the user's eyes, or maybe the motion planner might decide how to change it's target and speed on a 10Hz cycle. What would you use the 20Hz distance data to do?

From the perspective of the folks answering, it is pure speculation without your design/code/intentions.

My general advice for the question as written is to make all the Frankenstein bits as independent and non-blocking as possible, and then figure out what needs to talk to what. My code was demoing making measurement independent of communication by making them both non-blocking.

For now (that I´m working on the "manual mode"), the distance scans are only to display on the app like a radar profile. In the future, for the auto-mode, I will want the robot to get closer/farther from objects.

I can post the codes that I wrote if you want to, but they´re commented in portuguese. :grimacing: . Despite it, you would be able to understand them anyway because they´re quite simple. My programming skills are little more than basic. Should I post them?

There´s no need to use 20Hz if the robot keeps steady. I was just trying to do the sweep quick enough for not losing obstacles while moving around. Note that at the actual speed, the sensor takes 3.6s to do a 90° sweep. In 3.6s the robot can reach 1m...

Yes. I already agreed that this is definitely the idea.

For now, seeing the sensors data at the app is enough.

I´ll try to implement the concept of non-blocking bits with the lights, battery and servo fixed (providing distance at 90°) / applying joystick position to the motors. Then I´ll let you know how it goes, ok? By that time, I hope I´ll have converted my group of silly codes to a not-so-stupid all-in-one :joy: