XRAD'S LIDAR Scanner Teensy 3.2 TFMini TFT Millis() question

Hello All,

I have used some basic scanning code from the internet and modified it significantly to work with the TFMini, a ‘LIDAR’ distance device (really IR). Many programs/websites out there use the same basic scanning program for the ultrasound distance device. My IR range is 14m but useful to about 10m (up to 100 cycles/ sec). I have modified the display to correct for the extra distance. I have added sounds and color changes to the program to enhance the fun of the device when detecting different range objects. Also plan to add a ‘distance ZOOM’ option.

But, again, the issue is similar to my sbus PWM problem. I have tried many variations but I do not know how to time a pinout HIGH for 10ms without using millis() (and not using a delay() ). In the loop, I have 4 millis() timers going , each for a short time. This works fine (but not optimal). What is my next step to improve this?

The purpose of the 10ms pinout HIGH is to trigger a sound function from a sound board:

  1. Only needs to be 10ms
  2. has to be momentary and then turn pinout LOW for remainder of L or R scan cycle
  3. due to loop construction, the ‘left’ and ‘right’ scans each initiate the pinout HIGH command when they begin(if criteria met) and the pinout HIGH also occurs when distance changes (even in the middle of a sweep).

Although it works, my code looks messy . What is the better way (array? I still have not figured this out)

Thanks for any input!

PS( I had to omit the RIGHT sweep code block due to exceeding max character length)

//Need these libraries

#include <TFT.h>
#include <SPI.h>
#include <TFMini.h>
#include <Servo.h>

//Teensy 3.2 pin definitions:

#define SERVO_PIN    6   // servo control signal

#define RST          8  
#define DC           9
#define CS          10

 #define MOSI        11    // defined in lib too, needs to be wired up
                                     // can be commented out
//#define MISO        12  //  not necessary to be defined or wired up
                                     // for this program
 #define  SCK        13    //  defined in lib too, needs to be wired up
                                    //  can be commented out

#define PINAUDIO1  16 // scanner start sound
#define PINAUDIO2  17 // scanner cycle
#define PINAUDIO3  18 // scanner object <6m
#define PINAUDIO4  19 // scanner object <3m
#define PINAUDIO5  20 // scanner object >6m

#define BLACK 0x0000  // 16 bit color choices
#define RED 0x001F
#define CYAN 0x07FF
#define GREEN 0x0400
#define MAGENTA 0xF81F
#define BLUE 0xF800
#define WHITE 0xFFFF
#define YELLOW 0xFFE0

TFT tft = TFT( CS, DC, RST );
Servo servo;
TFMini tfmini;

// distance char array to print to the screen
char rc_Printout[ 4 ];

int interval = 0;
double distance = 0;


int buttonState = 0;// for future button

unsigned long scanTime1 = 20;

unsigned long startMillis1;
unsigned long startMillis2;
unsigned long startMillis3;
unsigned long startMillis4;

unsigned long currentMillis1;
unsigned long currentMillis2;
unsigned long currentMillis3;
unsigned long currentMillis4;

void setup( )
{
  servo.attach( SERVO_PIN );
  Serial.begin(115200);

  // setup sudio
  pinMode(PINAUDIO1, OUTPUT);
  pinMode(PINAUDIO2, OUTPUT);
  pinMode(PINAUDIO3, OUTPUT);
  pinMode(PINAUDIO4, OUTPUT);
  pinMode(PINAUDIO5, OUTPUT);


  digitalWrite(PINAUDIO1, HIGH);
  delay(10);
  digitalWrite(PINAUDIO1, LOW);


  //start the TFMini
  Serial1.begin(TFMINI_BAUDRATE);
  tfmini.begin(&Serial1);
  delay(100);
  tfmini.setSingleScanMode();

  startMillis1 = millis();
  startMillis2 = millis();
  startMillis3 = millis();
  startMillis4 = millis();


  //clear the TFT
  tft.begin( );
  tft.background( BLACK );
  delay(200);


  //cool start up
  for ( int i = 0; i < 5; i ++ )
  {
    tft.setTextSize( 2 );
    tft.stroke( WHITE );
    tft.text( "WARNING", 40, 30 );
    tft.text( "INITIALIZING", 10, 50 );
    tft.text( "LIDAR", 50, 70 );
    delay(500);
    tft.background( BLACK );

  }
  digitalWrite(PINAUDIO2, HIGH);
  delay(10);
  digitalWrite(PINAUDIO2, LOW);
}

void loop( )
{

  currentMillis1 = millis();
  currentMillis2 = millis();
  currentMillis3 = millis();
  currentMillis4 = millis();


  int r_beam = 100; // pixel length of green scan radius
  int r2 = r_beam * .33;
  int r3 = r_beam * .66;
  int r4 = r_beam ;// used for distance sound (maybe)

  tft.stroke( BLUE );
  tft.circle( 80, 128, r_beam );
  tft.circle ( 80, 128, r_beam - r2 );
  tft.circle ( 80, 128, r_beam - r3);
  tft.setTextSize( 2 );

  tft.stroke( RED );
  tft.text( "RANGE(m)", 10, 1 );
  tft.setTextSize( 1 );
  tft.stroke( RED );
  tft.text( "10m", 3, 38 );
  tft.setTextSize( 1 );
  tft.text( "6.5m", 15, 78 );
  tft.setTextSize( 1 );
  tft.text( "3m", 30, 120 );


  //Left display/servo rotation
  for ( int i = 60; i < 120; i = i + 1 )
  {

    tfmini.externalTrigger();
    uint16_t distance = tfmini.getDistance();
    Serial.println(distance);
    servo.write( i );
    delay( 30 );

    //Set the distance for LIDAR scan display and adjust to
    //TFT meter ranges (green semi-circles). Currently active display
    //range is the longest setting , 10m.
    //TFMini not active below 30cm, max distance 14m

    //This is for 50cm display range:
    //int r = distance * 2;
    //String r_Printout = String( r +2 );

    //This is for 10m display range
    int r = distance * .1;
    String r_Printout = String( r * 0.1 );

    if ( r < r2 ) {
      if (currentMillis1 - startMillis1 < scanTime1) {
        digitalWrite(PINAUDIO4, LOW);
        delay(10);
      }
      else if ( r < r2 ) {
        startMillis1 = currentMillis1;
        digitalWrite(PINAUDIO4, HIGH);
        delay(10);
      }
    }

    if ( r < r3 && r > r2) {
      if (currentMillis2 - startMillis2 < scanTime1) {
        digitalWrite(PINAUDIO3, LOW);
        delay(10);

      }
      else if ( r < r3 && r > r2) {
        startMillis2 = currentMillis2;
        digitalWrite(PINAUDIO3, HIGH);
        delay(10);
      }
    }

    tft.stroke( 0, 0, 0 );
    tft.setTextSize( 2 );
    tft.text( rc_Printout, 115, 0 );
    r_Printout.toCharArray( rc_Printout, 4 );
    tft.stroke( WHITE );
    tft.setTextSize( 2 );
    tft.text( rc_Printout, 115, 0 );

    tft.stroke( GREEN );
    tft.line( 80, 128, 80 + r_beam * cos( ( 360 - i ) * 3.14 / 180 ), 128 + r_beam * sin( ( 360 - i ) * 3.14 / 180 ) );
    if ( r > 0 && r <= r_beam + 2 )
    {

      tft.circle( 80 + r * cos( ( 360 - i ) * 3.14 / 180 ), 128 + r * sin( ( 360 - i ) * 3.14 / 180 ), 2 );
      tft.fillCircle( 80 + r * cos( ( 360 - i ) * 3.14 / 180 ), 128 + r * sin( ( 360 - i ) * 3.14 / 180 ), 2 , 0x001F   );
      if (r < r2) {
        tft.fillCircle( 80 + r * cos( ( 360 - i ) * 3.14 / 180 ), 128 + r * sin( ( 360 - i ) * 3.14 / 180 ), 2 , 0xFFFF   );
      }
    }
  }

  tft.background( BLACK );

  tft.stroke( BLUE );
  tft.circle( 80, 128, r_beam );
  tft.circle ( 80, 128, r_beam - r2 );
  tft.circle ( 80, 128, r_beam - r3);
  tft.setTextSize( 2 );

  tft.stroke( RED );
  tft.text( "RANGE(m)", 10, 1 );
  tft.setTextSize( 1 );
  tft.stroke( RED );
  tft.text( "10m", 3, 38 );
  tft.setTextSize( 1 );
  tft.text( "6.5m", 15, 78 );
  tft.setTextSize( 1 );
  tft.text( "3m", 30, 120 );


  

  tft.background( BLACK );
}
uint16_t color565( uint8_t r, uint8_t g, uint8_t b )
{
  return ( ( r & 0xF8 ) << 8 ) | ( ( g & 0xFC ) << 3 ) | ( b >> 3 );
}
unsigned long startMillis1;
unsigned long startMillis2;
unsigned long startMillis3;
unsigned long startMillis4;

There is nothing magic (or even remotely useful) about millis or Millis in the name of the variables. So, these variables represent the start time of event 1, event 2, event 3, and event 4. It would be FAR more useful to use names that reflect what event 1, event 2, event 3, and event 4 are.

  pinMode(PINAUDIO1, OUTPUT);
  pinMode(PINAUDIO2, OUTPUT);
  pinMode(PINAUDIO3, OUTPUT);
  pinMode(PINAUDIO4, OUTPUT);
  pinMode(PINAUDIO5, OUTPUT);

When you find yourself adding numeric suffixes to variables, it’s time to grab the array clue-by-four and give yourself a few whacks.

  currentMillis1 = millis();
  currentMillis2 = millis();
  currentMillis3 = millis();
  currentMillis4 = millis();

Why do you need 4 nearly identical, but NOT identical, copies of now?

  int r_beam = 100; // pixel length of green scan radius
  int r2 = r_beam * .33;
  int r3 = r_beam * .66;
  int r4 = r_beam ;// used for distance sound (maybe)

These values never vary. They should be global and const.

  for ( int i = 60; i < 120; i = i + 1 )
  {

    tfmini.externalTrigger();
    uint16_t distance = tfmini.getDistance();
    Serial.println(distance);
    servo.write( i );

Personally, I’d want to position the servo (assuming that the range sensor is attached to the servo) first (to a known position) and then read the distance.

    if ( r < r2 )
    {
        if (currentMillis1 - startMillis1 < scanTime1)
        {
            digitalWrite(PINAUDIO4, LOW);
            delay(10);
        }
        else if ( r < r2 )
        {

The else if clause condition bears no relationship to the if clause condition. Is that correct?

How could the else if clause ever be false?

Putting every { on a line by itself, and using Tools + Auto Format makes mistakes such as this much more evident.

Thank you!

The millis names 1,2,3,4 are for different event timers which when named under one timer, did not work. Basically, I tried one running timer to time all events, but did not work. So I had to name each separately. Maybe the way my loop is set up?

I will const the r2, r3, etc

The servo position and the display radius are linked. It starts at the appropriate 60 degree mark.

Relationship: the ‘if’ clauses denote the distance change and relates to the display (meters = radius of object). If an object is detected at less than a certain distance, something happens (the sound plays and the display changes to red dot). I had to create the r2,r3,r4 to identify when there is a distance change. This was the only thing I could figure to relate this to the ‘radius distance’ for the object WHEN less than the normal radius(ie: no echo) without messing up the code.

I am VERY new to programming(this is my <= 10th program), so I had to use ‘if’ and within this another ‘if’…not pretty…

scheme following initialization:

  1. servo position matches the screen image
  2. scan initiates, display initiates
  3. distance is calculated
  4. if distance is LESS than a certain range, a red dot shows up
  5. if red dot is < r3 something happens (sound and display change)
  6. if red dot is < r2 something happens (sound and display change)
  7. refresh of screen and scan again

By the way, the Teensy 3.2 has been rock solid! absolutely no issues.

Relationship: the 'if' clauses denote the distance change and relates to the display (meters = radius of object). If an object is detected at less than a certain distance, something happens (the sound plays and the display changes to red dot). I had to create the r2,r3,r4 to identify when there is a distance change. This was the only thing I could figure to relate this to the 'radius distance' for the object WHEN less than the normal radius(ie: no echo) without messing up the code.

I'm sorry. That makes no sense to me. You are saying that "if the distance measured is less than the threshold, then there is something more to do".

That something more depends on the current time versus the last time something happened OR the distance being less than the threshold. THAT is what doesn't make sense.

You already KNOW, when deciding what to do, that the distance is less than the threshold, so that test is unnecessary.

You need to rethink the inner if/else if part. The outer if, with no else, is fine.

Using functions more would greatly improve your code.

Create a function that returns 0, 1, 2, 3, or 4, based on the measured distance and the threshold values (0 means invalid distance; 1 means the distance is between 0 and the 1st threshold; 2 means that the distance is between the 1st and 2nd thresholds; 3 means that it is between the 2nd and 3rd thresholds; 4 means that it is above the 3rd threshold.

Then, dealing with the actual distance measured is a matter of a switch/case statement, switching on the value returned by the function.

It is easy to test the function, and observe that it returns, or does not, valid values. If not, it becomes easy (or easier) to fix the code once than to fix it several times.

The distance is calculated/measured and it is then displayed as a WHITE character top right corner of TFT .

So the display shows in a number character the following as 1.2m , 3.4m etc,… utilizing

//This is for 10m display range
    int r = distance * .1;
    String r_Printout = String( r * 0.1 );

And then I created this to define a threshhold :

const int r2 = r_beam * .33;
 const int r3 = r_beam * .66;

So if < r2, or <r3, or maybe even < r4, something happens (the pinout HIGH trigger a sound .wav, or an LED)

However, because the program runs so fast, the <r2, <r3 etc occurs many times…So I had to add a limiting step, the millis() timer so that pinout HIGH only happens ONCE per sweep.

My issues is that the millis timer set-up is not optimal. It works, but any scanTime >1 will make it function. So I do not think it is the best method.

I want to be able to control the pinout HIGH to cycle 2 times in a left or right scan WHEN the <r2 or <r3 etc criteria are met.

And I will try to create a function for distance T/F well but this puts me back in same situation where the function is T or F thousands of times a second, how do I limit control of the pin out HIGH or LOW without a delay or without millis?

use a for(i,i,i) within the function? this will still run the pinout HIGH or LOW too fast without a delay()…

Thank you!

And I will try to create a function for distance T/F well but this puts me back in same situation where the function is T or F thousands of times a second, how do I limit control of the pin out HIGH or LOW without a delay or without millis?

Look at the state change detection example. Do something when the state changes from T to F or from F to T, rather than doing something when the state IS T or F.

OK, state change. something like T or F = to something etc. do something Then I could just add one delay(10) after pin HIGH then pin LOW to trigger the wave. I could still use the <r2 , <r3 etc and when defined state changes , beep will happen.

I got lost in everything else…especially since my display crashed for an unknown reason. (display is adafruit 1.8" TFT w/ST7735R driver ). Was working great and then something happened…now I have the GSOD (gray screen of death)

So if I do something like this?

int  currentState = 0

currentState = >r3

switch(currentState) {

   case 0:
         if ( r < currentState && r > r2 ){
          digitalWrite(PINAUDIO2, HIGH);  //the mid level beep
        delay(10);
         digitalWrite(PINAUDIO2, LOW); 
        }
        break;

    case 1:
        if (  r < r2){
          digitalWrite(PINAUDIO4, HIGH); // the high level beep
        delay(10);
         digitalWrite(PINAUDIO4, LOW); 
       
 
        }
        break;
        

     default: // or case2
If (currentState){
         digitalWrite(PINAUDIO3,LOW)
         digitalWrite(PINAUDIO4,LOW)
      }
     break;

so the question remains, how do I keep this from repeating every time through the loop, because it will be for example TRUE thousands of times a second, and will want to trigger the pinout HIGH thousands of times as well. Or am I way off???

How do I isolate the state change from the loop? (except for evaluating whether T/F for the r value)

Do I need a side branch to the loop that times the digitalWrite to only 2 times per left or right scan (about 2.5 sec) AND does not interfere with loop speed…

Does switch/case remain in my loop but outside the LEFT or RIGHT scan blocks?? I think it needs to be the LEFT RIGHT scan blocks so that if within a scan, distance changes, the alert beep will sound (as it currently does)

Or should I use something like:

bool currentState = 0  //  false


switch (currentState) {

  case 0:
     
    if (r < r2) {

       if (currentState == 0) {
     digitalWrite (PINAUDIO4, HIGH);
     delay(10);
     digitalWrite(PINAUDIO4,LOW);

     currentState = 1;  // remains TRUE until made false again
     }
     }
     break;

 //etc....

Thank you

currentState is true or false. r2 is a distance. It does NOT make sense to compare currentState to a distance.

I don't know where you got the current state idea from.

You should create a function that returns 0, 1, 2, 3, or 4, as I suggested, based on the distance reading and the 4 thresholds. Then, compare the value based on the distance reading at angle n to that of the value based on the distance reading at angle n-1. THAT change is what seems important. Unless I'm completely missing what you are trying to do.

well, got my display functioning again. Not sure if it is necessary that TFT lib has to be followed by the SPI lib? but it works this way(corrected above in original post). And pin 11 MOSI, 13 SCK are inherent in the SPI lib but don't need to be wired, and not called for in TFT tft = TFT(CS, DC, RST ) ?? Still learning..

now to work on the code some more.

Yes PaulS, I will try create "create a function that returns 0, 1, 2, 3, or 4" but I do not know how to yet.

maybe a bool flag true false would be easier for me.... don't know...

maybe a bool flag true false would be easier for me....

The distance is below the minimum interesting value, is between the minimum and the first threshold, is between the first and second threshold, is between the second and third threshold, is between the third and fourth threshold, or is above the fourth threshold. Good luck determining which of those is the case with a function that only returns true or false.

Yes , right. It would be a long winded T F statement to meet all the criteria.

I am reading about Functions and arrays.

If I can get the function constructed, I hope to divided it into further sections based on 0-10 meters so that I can have 11 different distance warning .wavs playing respectively(when a change in distance state), so that I do not have to look at screen to determine distance.

So I get to come home from work and relax with this mental challenge every day until solved..... :slight_smile:

solved the pin-out sounds for <= 6m at 1 m intervals with:

const int rMin = 0
const int rMax = 600

 int range = map(distance, rMin, rMax, 0, 5);
    switch (range) {

      case 0: //etc for 5 more cases
      default: //do soemthing

I want to simplify this probably easily simplified issue. How do I add 10 (pixels) to the circle radius to get a “pulse out effect” without having to write all these code lines (see below). I suspect that if I create a minimum ‘r’ (const int r_beam1 = 10) and add 10 to it each time through it will work, and ‘BLACK’ also has to follow so that the ‘pulse effect’ works…

ideas?

//this code works fine, but cumbersome to look at...and I only need 2 or 3 pulses (it's in the setup)....
 
for ( int i = 0; i < 2; i ++ )
  {
    tft.fill(  GREEN );
    tft.circle(80, 124, 10);
    tft.circle(80, 124, 20);
    tft.circle(80, 124, 30);
    tft.circle(80, 124, 40);
    tft.circle(80, 124, 50);
    tft.circle(80, 124, 60);
    tft.circle(80, 124, 70);
    tft.circle(80, 124, 80);
    tft.circle(80, 124, 90);
    tft.circle(80, 124, 100);

    tft.fill(  BLACK );
    tft.circle(80, 124, 10);
    tft.circle(80, 124, 20);
    tft.circle(80, 124, 30);
    tft.circle(80, 124, 40);
    tft.circle(80, 124, 50);
    tft.circle(80, 124, 60);
    tft.circle(80, 124, 70);
    tft.circle(80, 124, 80);
    tft.circle(80, 124, 90);
    tft.circle(80, 124, 100);

    tft.background( BLACK );

  }

I tried several variations of something like this, makes a circle with beginning r of 10, and growws, but way too slow and small…

const int r_beam1 = 10  // because I already have an r_beam....


for ( int i = 0; i <9; i ++);
  { 
    
    for ( int i = 0; i < 9; (i++) )
    {
    tft.fill(  GREEN );
    tft.circle(80, 124,(r_beam1 * i));
    delay(500);
}    
   tft.background( BLACK );

  }

I suspect that if I create a minimum 'r' (const int r_beam1 = 10) and add 10 to it each time through it will work

You could try that. Of course, it won't work, because you don't seem to understand what const(ant) means.

You could drop the const, and it would work.

well, I thought const meant 'read-only' allocation which is what I thought I wanted as the initial radius for the circle, but I think I see your point, the radius changes each iteration, so it has to be just 'int'.

If you mean how to use the ( ), I have no idea.

Also, I tried and int r_beam = 10 and I do not remember it 'growing' any more than with const r_beam.....

I am not a programmer, still learning......

Also, I tried and int r_beam = 10 and I do not remember it 'growing' any more than with const r_beam....

Without seeing your code, you can't expect us to explain why the code didn't do what you expected.

Paul, what would I do without your kind ‘kick in the pants’??

solved the LIDAR ping for the start-up. Three green arcs pinging out. The only thing I could not figure out was how to make the ring thicker. tft.setTextSize(2 or whatever) has no noticeable effect in the code, even if I delay it more:

  int r_beam1 = 10;

  for ( int i = 0; i < 3; i ++)
  {
    for ( int i = 0 ; i < 9 ; i++) {

      tft.stroke (GREEN);    
      tft.circle(80, 124, (r_beam1 * i));
      tft.setTextSize( 2 );
      delay(30);
      tft.stroke (BLACK);
      tft.circle(80, 124, (r_beam1 * i));
      tft.setTextSize( 2 );
      delay(30);
      
    }
    tft.background( BLACK );
  }

The only thing I could not figure out was how to make the ring thicker.

I'm not sure that I understand what you want to do. Do you want to draw the ring using a wider line?

tft.setTextSize(2 or whatever) has no noticeable effect in the code

I can sure understand that. You aren't displaying any text, so of course changing the text parameters is going to have no effect.

even if I delay it more

What you are doing makes no sense. It's like cooking a souffle. The souffle doesn't set up no matter what channel you tune the television to. Putting the f**king souffle in the oven, and turn the oven on. Throw the stupid TV away.

Delaying will have NO affect on the line width or the text size. The text size will have no effect on the line width, the line length, or the line color.

yeah, can’t say it enough: This is my first real attempt at coding. No training, No classes , and just the internet and a few C++ manuals. I thought this was the programming question section…

YES: I want to Draw a ring with a wider line.

The delay is only to allow me to see the first ARC before the second ARC is drawn over it. Any faster (< 10ms) and too fast to see, any slower not necessary. Obviously it has nothing to do with the actual thickness of the stroke. I thought that the SECOND stroke in black was hiding the first one. Therefore a delay in RED was to show the whole RED stroke thickness.

Now I understand that setTextSize does not affect stroke size! (the learning process)

So how do I make ‘stroke’ thicker? Is there a ‘size’ argument somewhere or tft.setStrokeSize ()?? I can’t find it anywhere…

THX!

So how do I make 'stroke' thicker?

The library authors appear to not have thought of that. Drawing a parallel line, or parallel circle, a small distance away (one pixel?), appears to be the only solution.

For circles:

set fill black
set stroke black
draw circle
set fill white
set stroke white
draw a smaller circle around same origin

for line:

set fill black
set stroke black
draw rectangle length and width of the line.

you will have to learn how to build shapes with those primatives.

You could also have a look at the adafruit gfx library. Unless you have one of their displays/boards, you might have to do a lot of code fudging.