Graphing on the V5 screen

I was asked a couple of day ago about how to draw graphs on the V5 screen, presumably to help with PID tuning and things like that. I’m sure there are some comprehensive libraries available in C or C++ that could be easily ported, but for simple visualization of motor velocity and variables it can be quite easy using the normal drawing commands that VEXcode has. Here’s a little demo that draws and scrolls simple data.

graphdemo

and the code that was used to do that.

graph demo Code
/*----------------------------------------------------------------------------*/
/*                                                                            */
/*    Module:       main.cpp                                                  */
/*    Author:       james                                                     */
/*    Created:      Fri Mar 20 2020                                           */
/*    Description:  V5 project                                                */
/*                                                                            */
/*----------------------------------------------------------------------------*/
#include "vex.h"
#include <vector>

using namespace vex;

// A global instance of vex::brain used for printing to the V5 brain screen
vex::brain       Brain;

// define your global instances of motors and other devices here
class graph {
  #define NUM_POINTS  480
  
  private:
    // class to hold points for a single graph line
    class points {
      public:
        uint32_t         *_points;
        vex::brain::lcd  &_screen;
        vex::color        _color;
      
        points( vex::brain::lcd &screen ) : _screen(screen) {
          // allocate memory on heap
          _points = new uint32_t[NUM_POINTS];
          // init everything to some value we consider invalid
          for(int i=0;i<NUM_POINTS;i++) {
            _points[i] = INT32_MAX;
          }
          // default line color
          _color = vex::white;
        }
        ~points(){
          // deallocate memory
          delete _points;
        }

        // draw the line
        // There's a variety of ways to do this, could be another property of the class
        void draw() {
          _screen.setPenColor( _color );
          for(int x=0;x<NUM_POINTS-2;x++) {
            if( _points[x] != INT32_MAX ) {
              _screen.drawLine( x, _points[x], x+1, _points[x+1]);
              _screen.drawCircle( x, _points[x], 2, _color );
            }
          }
        }

        // add a point to this line
        void addPoint( int value ) {
          for(int i=0;i<NUM_POINTS-1;i++) {
            _points[i] = _points[i+1];
          }
          _points[NUM_POINTS-1 ] = value;
        }

        // set color for this line
        void setColor( vex::color c ) {
          _color = c;
        }
    };
    
    public:
      vex::brain _brain;
      std::vector<graph::points *> _points;
      int   _origin_x;
      int   _origin_y;
      
      graph( int seqnum, int origin_x, int origin_y ) : _origin_x(origin_x), _origin_y(origin_y) {
        // allocate and store each line
        for( int i=0;i<seqnum;i++ ) {
          _points.push_back( new graph::points(_brain.Screen) );
        }

        // thread to render this graph
        thread( render, static_cast<void *>(this) );
      }
      ~graph(){
        // we should deallocate the vector members here really
      }

      // Thread that constantly draws all lines
      static int render(void *arg ) {
        if( arg == NULL)
          return(0);

        graph *instance = static_cast<graph *>(arg);

        while( 1) {
            // this will call render, no need for any other delays
            instance->draw();
        }

        return(0);
      }

      // Draw graph X and Y axis
      // modify to fit your needs
      void drawAxis() {
        _brain.Screen.setPenColor( vex::white );
        _brain.Screen.drawLine( _origin_x, 0, _origin_x, 240 );
        _brain.Screen.drawLine( 0, _origin_y, 480, _origin_y );
        for( int x=0;x<480;x+=20 ) {
          _brain.Screen.drawLine( x, _origin_y+5, x, _origin_y-5 );
        }
        for( int y=0;y<240;y+=20 ) {
          _brain.Screen.drawLine( _origin_x+5, y, _origin_x-5, y );
        }
      }

      // draw everything
      void draw() {
        _brain.Screen.clearScreen( vex::color(0x202020) );
        drawAxis();
        for(int id=0;id<_points.size();id++)
          _points[id]->draw();
        _brain.Screen.render();
      }

      // add a point to a particular sequence
      void addPoint( int id, int value ) {
        if( id < _points.size() )
          _points[id]->addPoint(value + _origin_y );
      }
      
      // set the color of this sequence
      void setColor( int id, vex::color c ) {
        if( id < _points.size() )
          _points[id]->setColor( c );
      }
};


int sinTask1( void *arg ) {
    if( arg == NULL ) return 0;

    graph *g = static_cast<graph *>(arg);
    int x = 0;
    while(1) {
      g->addPoint( 0, sin( x / 180.0 * 3.141) * 100 );
      if( x++ == 360 )
        x = 0;

      this_thread::sleep_for(20);
    }
}

int sinTask2( void *arg ) {
    if( arg == NULL ) return 0;

    graph *g = static_cast<graph *>(arg);
    int x = 180;
    while(1) {
      g->addPoint( 1, sin( x / 180.0 * 3.141) * 50 );
      if( x++ == 360 )
        x = 0;

      this_thread::sleep_for(10);
    }
}

int cosTask1( void *arg ) {
    if( arg == NULL ) return 0;

    graph *g = static_cast<graph *>(arg);
    int x = 180;
    while(1) {
      g->addPoint( 2, cos( x / 180.0 * 3.141) * 50 );
      if( x++ == 360 )
        x = 0;

      this_thread::sleep_for(15);
    }
}

int triangleTask1( void *arg ) {
    if( arg == NULL ) return 0;

    graph *g = static_cast<graph *>(arg);

    int y = 0;
    int inc = 1;
    while(1) {
      g->addPoint( 3, y );

      if( abs(y+=inc) == 80 ) {
        inc = -inc;
      }

      this_thread::sleep_for(5);
    }
}
int main() {
    // 4 lines, axis at position 120, 120
    graph g( 4, 120, 120 );

    // set line colors
    g.setColor(0, vex::color::white );
    g.setColor(1, vex::color::red );
    g.setColor(2, vex::color::blue );
    g.setColor(3, vex::color::green );

    // and we are using separate tasks to add points to each line, this is a bit overkill
    thread t1( sinTask1, static_cast<void *>(&g) );
    thread t2( sinTask2, static_cast<void *>(&g) );
    thread t3( cosTask1, static_cast<void *>(&g) );
    thread t4( triangleTask1, static_cast<void *>(&g) );
    
    while(1) {
      this_thread::sleep_for(10);      
    }
}

It could actually be a little simpler than I show, no need to use a thread for each graph line really, all you need to do is create the graph instance and then decide how and when to add values for each line it draws. This graph scrolls as values are added, but obviously you could modify the code and just create static graphs. There’s also no scaling of data (it’s just using pixel resolution) but, again, that could easily be added.

The code also demonstrates one or two other more advanced techniques, it uses dynamic memory allocation for the data storage, it passes an object to each thread, and it uses a thread in the graph class that can be shared amongst several instances (although that wouldn’t make much sense in this case as graph drawing clears the screen each time)

37 Likes

Another demo, this time using lvgl in VEXcode from the project I had posted here a few months back.

(which now has prebuilt library included in the repo so you don’t need to build it).

graph_demo_2

Code uses the lvgl chart object.

Code
/*----------------------------------------------------------------------------*/
/*                                                                            */
/*    Module:       main.cpp                                                  */
/*    Author:       james                                                     */
/*    Created:      Sun Sep 15 2019                                           */
/*    Description:  V5 project                                                */
/*                                                                            */
/*----------------------------------------------------------------------------*/
#include "vex.h"
#include "v5lvgl.h"

using namespace vex;

void
create_chart(lv_obj_t * parent) {
  lv_obj_t * chart = lv_chart_create(parent, NULL);
  lv_chart_set_type(chart, LV_CHART_TYPE_POINT);
  lv_chart_set_point_count(chart, 460 );
  lv_obj_set_size(chart, 460, 220 );
  lv_obj_set_pos(chart, 10, 10);
  lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_SHIFT);
  lv_chart_series_t * s1 = lv_chart_add_series(chart, LV_COLOR_WHITE);
  lv_chart_series_t * s2 = lv_chart_add_series(chart, LV_COLOR_RED);

  int x = 0;
  int t = 0;
  
  while(1) {
    lv_chart_set_next(chart, s1, sin( x / 180.0 * 3.141) * 40 + 50 );
    if(t == 0)
      lv_chart_set_next(chart, s2, cos( x / 180.0 * 3.141) * 20 + 50 );
    if( x++ == 360 )
      x = 0;
    t = 1 - t;
    this_thread::sleep_for(5);
  }
}

int demo() {
    create_chart(lv_scr_act());
    return(0);
}

int main() {
    v5_lv_init();
    demo();

    while(1) {
        // Allow other tasks to run
        this_thread::sleep_for(10);
    }
}

Here’s the project with lvgl library included and the minor changes to the build system made (the compiler needs to know where the lvgl header files are and the library needed to be added for the linker).

lvglChartDemo.zip (463.3 KB)

14 Likes

YESSSS!!! So glad to see lvgl be introduced into VEXCode!!!

6 Likes

It’s not new, I created those github repos back in December. I also would suggest this is not for beginning programmers, you need to understand about how multiple C++ files and libraries work together.

6 Likes

Wow I didn’t realize there was lvgl support for VEXcode. I was seriously looking into PROS for all the fancy autonomous selector options, but it’s great that it’s also available for VEXcode!

3 Likes

Nice!

(Did I miss somewhere how you are creating the screenshots of the V5? The animated screenshots are especially nifty.)

1 Like

If you’re using LVGL you can write all your code on the PC testing it with the LVGL PC Simulator. If you isolate your graphics code you can just about drop it into V5 code with very few changes. Just don’t for get to change the horizontal and vertical resolution of the default display to V5 Brain size.

3 Likes