Aadhuniklabs
Published

Self Balancing WiFi Controlled Robot with PanTilt Camera

This self balancing mobile robot with pan & tilt camera can be navigated remotely from host PC without the need to be in line of sight.

IntermediateFull instructions provided3 hours3,186
Self Balancing WiFi Controlled Robot with PanTilt Camera

Things used in this project

Hardware components

Arduino Nano RP2040 Connect
Arduino Nano RP2040 Connect
×1
Raspberry Pi Pico
Raspberry Pi Pico
×1
6 DOF Sensor - MPU6050
DFRobot 6 DOF Sensor - MPU6050
×1
ESP8266 ESP-01
Espressif ESP8266 ESP-01
×1
ESP32 Camera Module Development Board
M5Stack ESP32 Camera Module Development Board
×1
12V to 5V Step Down DC Converter
or any general purpose 5V dc output converter
×1
Geared DC Motor, 12 V
Geared DC Motor, 12 V
or any general purpose 6 to 12V geared DC motor
×2
Servo Motor SG90 180 degree
DIYables Servo Motor SG90 180 degree
×2
Pan-Tilt HAT
Pimoroni Pan-Tilt HAT
×1
9V to 12V Battery
×1
Robot Base Frame / Chassis
×1
Jumper Wires
DIYables Jumper Wires
×1
DC Power Jack Connector
DIYables DC Power Jack Connector
×1

Software apps and online services

Computer Aided Simulation Program (CASP)

Story

Read more

Schematics

Connection Diagram for Arduino Nano RP2040 Connect

Connection Diagram for Raspberry Pi Pico

Models for Arduino Nano RP2040 Connect

Models for Raspberry Pi Pico

Code

Source Code For MCU target and native custom blocks

C/C++
Two custom blocks (one in the target model and the other in the native model) are used to develop logics that are not possible through CASP blocks User can take a look at the configuration and source code of the blocks and learn how to integrate the block in a CASP model along with other blocks.
// MCU target custome block header file code
//CustomBlockSelfBalancing720378270.h
/************************CASP_COPYRIGHT**********************************
** Copyright (c) AadhunikLabs. All rights reserved.
** This file is part of CASP.
** Licensees holding valid licenses may use this file in accordance
** with the license agreement provided with the software.
*************************************************************************/
#ifndef CUSTOMBLOCKSELFBALANCING720378270_H
#define CUSTOMBLOCKSELFBALANCING720378270_H

#include "sim_common_definitions.h"

//BLOCK_PREPROC_DEFS_HERE

class SHARED_LIB_TYPE CustomBlockSelfBalancing720378270
{
public:
    CustomBlockSelfBalancing720378270();
    ~CustomBlockSelfBalancing720378270();
    void Initialize(const volatile BLOCK_CONSTRUCTOR_PARAM_STRUCT *arg);
    void PreRun(const volatile GLOBAL_PRERUN_PARAM_STRUCT *arg, _FXDPT_FLOAT *pid_in0, _FXDPT_FLOAT *pid_in1, _INT32 *estop, _BYTE *data_in, _INT32 *comm_len, _INT32 *left_out0, _INT32 *left_out1, _INT32 *right_out0, _INT32 *right_out1, _FXDPT_FLOAT *servo0, _FXDPT_FLOAT *servo1, _INT32 *led0, _FXDPT_FLOAT *sen_offset, _FXDPT_FLOAT *sp_l, _FXDPT_FLOAT *sp_r, _INT32 *rst_i);
    void Run(const volatile GLOBAL_RUN_PARAM_STRUCT *arg, _FXDPT_FLOAT *pid_in0, _FXDPT_FLOAT *pid_in1, _INT32 *estop, _BYTE *data_in, _INT32 *comm_len, _INT32 *left_out0, _INT32 *left_out1, _INT32 *right_out0, _INT32 *right_out1, _FXDPT_FLOAT *servo0, _FXDPT_FLOAT *servo1, _INT32 *led0, _FXDPT_FLOAT *sen_offset, _FXDPT_FLOAT *sp_l, _FXDPT_FLOAT *sp_r, _INT32 *rst_i);
    void PostRun(const volatile GLOBAL_POSTRUN_PARAM_STRUCT *arg, _FXDPT_FLOAT *pid_in0, _FXDPT_FLOAT *pid_in1, _INT32 *estop, _BYTE *data_in, _INT32 *comm_len, _INT32 *left_out0, _INT32 *left_out1, _INT32 *right_out0, _INT32 *right_out1, _FXDPT_FLOAT *servo0, _FXDPT_FLOAT *servo1, _INT32 *led0, _FXDPT_FLOAT *sen_offset, _FXDPT_FLOAT *sp_l, _FXDPT_FLOAT *sp_r, _INT32 *rst_i);
public:
    //char* _dependent_obj;
    //char m_blk_path[SIM_BLK_PATH_MAX_SIZE];
    //port variable declaration
    //block internal parameter variable declaration
    _FLOAT pwm_min_offset, pwm_max_limit;
    _INT32 em_stop_value;
    //block internal initial condition variable declaration
private:
    _BYTE m_prerun_count;
    _UINT64 m_comm_tick_prev, m_speed_tick_prev;
    _INT32 m_left_dir_prev, m_right_dir_prev;
    _INT32 m_left_speed_prev, m_right_speed_prev;
    //
    _FLOAT m_angle_offset_cum[2];
    _FLOAT m_usr_offset;
};

#endif



//MCU target custom block cpp file source code
//CustomBlockSelfBalancing720378270.cpp
/************************CASP_COPYRIGHT**********************************
** Copyright (c) AadhunikLabs. All rights reserved.
** This file is part of CASP.
** Licensees holding valid licenses may use this file in accordance
** with the license agreement provided with the software.
*************************************************************************/
#include "CustomBlockSelfBalancing720378270.h"
#include "simcode.h"
#include "math.h"
#include "sim_helper_fg.h"

CustomBlockSelfBalancing720378270::CustomBlockSelfBalancing720378270(){
}

CustomBlockSelfBalancing720378270::~CustomBlockSelfBalancing720378270(){
}

void CustomBlockSelfBalancing720378270::Initialize(const volatile BLOCK_CONSTRUCTOR_PARAM_STRUCT *arg){
    m_prerun_count = 0;
}

void CustomBlockSelfBalancing720378270::PreRun(const volatile GLOBAL_PRERUN_PARAM_STRUCT *arg, _FXDPT_FLOAT *pid_in0, _FXDPT_FLOAT *pid_in1, _INT32 *estop, _BYTE *data_in, _INT32 *comm_len, _INT32 *left_out0, _INT32 *left_out1, _INT32 *right_out0, _INT32 *right_out1, _FXDPT_FLOAT *servo0, _FXDPT_FLOAT *servo1, _INT32 *led0, _FXDPT_FLOAT *sen_offset, _FXDPT_FLOAT *sp_l, _FXDPT_FLOAT *sp_r, _INT32 *rst_i){
    if(m_prerun_count++ != 1)
    {
        *pid_in0 = 0;
        *pid_in1 = 0;
        for(int i=0; i<20; ++i)
            data_in[i] = 0;
        *comm_len = 0;
        for(int i=0; i<3; ++i)
            estop[i] = 0;
        return;
    }
    m_left_dir_prev = 0;
    m_right_dir_prev = 0;
    m_left_speed_prev = 0;
    m_right_speed_prev = 0;
    //
    *servo0 = *((_FXDPT_FLOAT*)&data_in[20]);
    *servo1 = *((_FXDPT_FLOAT*)&data_in[24]);
    *led0 = data_in[28];
    //
    *left_out0 = *left_out1 = *right_out0 = *right_out1 = 0;
    *rst_i = 1000;
    *sp_l = *sp_r = 0;
    m_comm_tick_prev = m_speed_tick_prev = arg->rt_clk_tick;
    m_usr_offset = 0;
}

void CustomBlockSelfBalancing720378270::Run(const volatile GLOBAL_RUN_PARAM_STRUCT *arg, _FXDPT_FLOAT *pid_in0, _FXDPT_FLOAT *pid_in1, _INT32 *estop, _BYTE *data_in, _INT32 *comm_len, _INT32 *left_out0, _INT32 *left_out1, _INT32 *right_out0, _INT32 *right_out1, _FXDPT_FLOAT *servo0, _FXDPT_FLOAT *servo1, _INT32 *led0, _FXDPT_FLOAT *sen_offset, _FXDPT_FLOAT *sp_l, _FXDPT_FLOAT *sp_r, _INT32 *rst_i){
    //Note: run this block in sync with PID blocks otherwise PID integral reset signal may not work
    *servo0 = *((_FXDPT_FLOAT*)&data_in[20]);
    *servo1 = *((_FXDPT_FLOAT*)&data_in[24]);
    *led0 = data_in[28];
    //adjust set point angle w.r.t manual control
    _BIT comm_stopped = false;
    if(*comm_len > 0)
        m_comm_tick_prev = arg->rt_clk_tick;
    else
    {
        if(arg->rt_clk_tick - m_comm_tick_prev > 500000)
            comm_stopped = true;
    }
    //sensor offsets
    m_usr_offset += ((_FLOAT)*((_INT32*)&data_in[16])) * 0.01;
    _FLOAT offset = *sen_offset + m_usr_offset;
    //
    _FLOAT &spl = *sp_l;
    _FLOAT &spr = *sp_r;
    _INT32 left_dir = *((_INT32*)&data_in[0]);
    _INT32 left_speed = *((_INT32*)&data_in[4]);
    _INT32 right_dir = *((_INT32*)&data_in[8]);
    _INT32 right_speed = *((_INT32*)&data_in[12]);
    spl = spr = 0;
    //initially, speed is required to overcome static friction. reduce the speed after some time for stability
    if(left_speed != 0 || right_speed != 0)
    {
        if(m_left_speed_prev == 0 && m_right_speed_prev == 0)//user just given command to move or rotate
            m_speed_tick_prev = arg->rt_clk_tick;
        if(arg->rt_clk_tick - m_speed_tick_prev > 900000)
        {
            left_speed /= 4;
            right_speed /= 4;
        }
        else if(arg->rt_clk_tick - m_speed_tick_prev > 500000)
        {
            left_speed /= 2;
            right_speed /= 2;
        }
        else if(arg->rt_clk_tick - m_speed_tick_prev > 300000)
        {
            left_speed = left_speed*2/3;
            right_speed = right_speed*2/3;
        }
    }
    //reset manual control when user is not connected
    if(comm_stopped)
    {
        spl = 0;
        spr = 0;
        //*servo0 = *servo1 = 0;//we dont know offsets and it is not mandatory to make is zero
        *led0 = 0;
    }
    else
    {
        spl = left_speed;
        spr = right_speed;
        //set direction
        spl = (left_dir)?spl:-spl;
        spr = (right_dir)?spr:-spr;
        //to actual
        spl /= 19.0;// /19.0 for arduino rp2040 and /10.0 for Rpi Pico
        spr /= 19.0;// /19.0 for arduino rp2040 and /10.0 for Rpi Pico
    }
    spl += offset;
    spr += offset;
    //reset pid block integral term on following logics
    *rst_i = 0;
    if(left_speed == 0 && right_speed == 0)//only during braking
    {
        if(left_dir != m_left_dir_prev || right_dir != m_right_dir_prev)//when change in direction
            *rst_i = 10;
        if(left_speed != m_left_speed_prev || right_speed != m_right_speed_prev)
            *rst_i = 10;
    }
    *rst_i -= 1;
    //
    m_left_dir_prev = left_dir;
    m_right_dir_prev = right_dir;
    m_left_speed_prev = left_speed;
    m_right_speed_prev = right_speed;

    //process outputs
    _FLOAT in[2], out[2];
    in[0] = -*pid_in0;
    in[1] = -*pid_in1;
    for(int i=0; i<2; ++i)
    {
        out[i] = in[i];
        //absolute
        if(out[i] < 0) out[i] = -out[i];
        //map to minimum & maximum pwm outputs to turn the wheel
        out[i] = MapRangeT(out[i], (_FLOAT)pwm_max_limit, (_FLOAT)pwm_min_offset, (_FLOAT)100, (_FLOAT)0);
        //stop when the input angle is greater than set angle
        if(estop[1] > em_stop_value || estop[1] < -em_stop_value)
            out[i] = 0;
    }
    //set motor speed and direction
    if(in[0] < 0)
    {
        *left_out0 = out[0];
        *left_out1 = 0;
    }
    else
    {
        *left_out0 = 0;
        *left_out1 = out[0];
    }
    if(in[1] < 0)
    {
        *right_out0 = 0;
        *right_out1 = out[1];
    }
    else
    {
        *right_out0 = out[1];
        *right_out1 = 0;
    }
}

void CustomBlockSelfBalancing720378270::PostRun(const volatile GLOBAL_POSTRUN_PARAM_STRUCT *arg, _FXDPT_FLOAT *pid_in0, _FXDPT_FLOAT *pid_in1, _INT32 *estop, _BYTE *data_in, _INT32 *comm_len, _INT32 *left_out0, _INT32 *left_out1, _INT32 *right_out0, _INT32 *right_out1, _FXDPT_FLOAT *servo0, _FXDPT_FLOAT *servo1, _INT32 *led0, _FXDPT_FLOAT *sen_offset, _FXDPT_FLOAT *sp_l, _FXDPT_FLOAT *sp_r, _INT32 *rst_i){
}



//native (host PC) target custom block header file code
//RemCarManual927823855.h
/************************CASP_COPYRIGHT**********************************
** Copyright (c) AadhunikLabs. All rights reserved.
** This file is part of CASP.
** Licensees holding valid licenses may use this file in accordance
** with the license agreement provided with the software.
*************************************************************************/
#ifndef REMCARMANUAL927823855_H
#define REMCARMANUAL927823855_H

#include "sim_common_definitions.h"

#define UPDATE_INTERVAL 10UL//in milli secs
#define RC_SHIFT_GAIN (1.3f)

class SHARED_LIB_TYPE RemCarManual927823855
{
public:
    RemCarManual927823855();
    ~RemCarManual927823855();
    void Initialize(const volatile BLOCK_CONSTRUCTOR_PARAM_STRUCT *arg);
    void PreRun(const volatile GLOBAL_PRERUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out);
    void Run(const volatile GLOBAL_RUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out);
    void RunPlotStep(const volatile GLOBAL_RUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out);
    void PostRun(const volatile GLOBAL_POSTRUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out);
public:
    //port variable declaration
    //port dimension and size declaration
    //block internal parameter variable declaration
    _FLOAT base_speed, min_speed, max_speed, speed_step;
    _FLOAT rotate_speed;
    _FLOAT head_speed[2];
    _FLOAT head_hangle[3], head_vangle[3];//min,max,def
    _BYTE head_control;
    //block internal initial condition variable declaration
private:
    _FLOAT rotate_speed_inc, rotate_speed_dec;
    _BOOLEAN m_head_speed_reverse[2];
    int m_init_mouse_pos[2];//during last head movement
    int m_prev_mouse_pos[2];//prev position
    _UINT64 m_update_count;
};

#endif


//native (host PC) target custom block cpp file code
//RemCarManual927823855.cpp
/************************CASP_COPYRIGHT**********************************
** Copyright (c) AadhunikLabs. All rights reserved.
** This file is part of CASP.
** Licensees holding valid licenses may use this file in accordance
** with the license agreement provided with the software.
*************************************************************************/
#include "RemCarManual927823855.h"
#include "math.h"
#include "simcode.h"

RemCarManual927823855::RemCarManual927823855(){
}

RemCarManual927823855::~RemCarManual927823855(){
}

void RemCarManual927823855::Initialize(const volatile BLOCK_CONSTRUCTOR_PARAM_STRUCT *arg){
}

void RemCarManual927823855::PreRun(const volatile GLOBAL_PRERUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out){
    m_init_mouse_pos[0] = kb_in[257];
    m_init_mouse_pos[1] = kb_in[258];
    m_prev_mouse_pos[0] = kb_in[257];
    m_prev_mouse_pos[1] = kb_in[258];
    //
    *l_speed_out = 0;
    *l_dir_out = 1;
    *r_speed_out = 0;
    *r_dir_out = 1;
    //
    *v_angle_out = head_vangle[2];
    *h_angle_out = head_hangle[2];
    //
    rotate_speed_inc = 1.0f+(rotate_speed/100.0f);
    rotate_speed_dec = 1.0f-(rotate_speed/100.0f);
    speed_step = speed_step/100.0f;
    m_update_count = arg->rt_clk_tick;
    //
    for(int i=0; i<2; ++i)
    {
        m_head_speed_reverse[i] = (head_speed[i] < 0)?1:0;
        head_speed[i] = (head_speed[i] < 0)?-head_speed[i]:head_speed[i];
    }
}

void RemCarManual927823855::Run(const volatile GLOBAL_RUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out){
    if(arg->rt_clk_tick-m_update_count < UPDATE_INTERVAL*1000)
        return;
    m_update_count = arg->rt_clk_tick;
    //
    *l_speed_out = 0;
    *r_speed_out = 0;
    *l_dir_out = 1;
    *r_dir_out = 1;
    //
    _FLOAT bspeed = base_speed;
    if(kb_in[16])//shift pressed increase speed temporarly
        bspeed *= RC_SHIFT_GAIN;
    //adjust speed
    if(kb_in[34])//pg down
    {
        _FLOAT val = bspeed * (1.0f-speed_step);
        if(val > min_speed)
            bspeed = val;
    }
    else if(kb_in[33])//pg up
    {
        _FLOAT val = bspeed * (1.0f+speed_step);
        if(val < max_speed)
            bspeed = val;
    }
    //forward and backward
    if(kb_in[87])//W-forward
    {
        *l_speed_out = *r_speed_out = bspeed;
        if(kb_in[65])//A-left
        {
            *l_speed_out *= rotate_speed_dec;
            *r_speed_out *= rotate_speed_inc;
        }
        else if(kb_in[68])//D-right
        {
            *l_speed_out *= rotate_speed_inc;
            *r_speed_out *= rotate_speed_dec;
        }
        //check limits
        if(*l_speed_out > max_speed)
            *l_speed_out = max_speed;
        else if(*l_speed_out < min_speed)
            *l_speed_out = min_speed;
        if(*r_speed_out > max_speed)
            *r_speed_out = max_speed;
        else if(*r_speed_out < min_speed)
            *r_speed_out = min_speed;
        //
        *l_dir_out = 1;
        *r_dir_out = 0;
    }
    else if(kb_in[83])//S-reverse
    {
        *l_speed_out = *r_speed_out = bspeed;
        if(kb_in[65])//A-left
        {
            *l_speed_out *= rotate_speed_dec;
            *r_speed_out *= rotate_speed_inc;
        }
        else if(kb_in[68])//D-right
        {
            *l_speed_out *= rotate_speed_inc;
            *r_speed_out *= rotate_speed_dec;
        }
        //check limits
        if(*l_speed_out > max_speed)
            *l_speed_out = max_speed;
        else if(*l_speed_out < min_speed)
            *l_speed_out = min_speed;
        if(*r_speed_out > max_speed)
            *r_speed_out = max_speed;
        else if(*r_speed_out < min_speed)
            *r_speed_out = min_speed;
        //
        *l_dir_out = 0;
        *r_dir_out = 1;
    }
    //if no forward or reverse pressed. then rotate on center
    else//if(kb_in[87] == 0 && kb_in[83] == 0)
    {
        if(kb_in[65])//A-left
        {
            *l_speed_out = *r_speed_out = bspeed/2;
            *l_dir_out = 0;
            *r_dir_out = 0;
        }
        else if(kb_in[68])//D-right
        {
            *l_speed_out = *r_speed_out = bspeed/2;
            *l_dir_out = 1;
            *r_dir_out = 1;
        }
    }
    //head rotation
    if(head_control == 1)//from mouse
    {
        int x = kb_in[257];
        int y = kb_in[258];
        int diffx = x - m_init_mouse_pos[0];
        int diffy = y - m_init_mouse_pos[1];
        if(diffx != 0)
        {
            //if(x == m_prev_mouse_pos[0])//user stabilized mouse position. move head
            {
                if(kb_in[256] & 0b10)//if right button pressed then only move mouse
                {
                    if(m_head_speed_reverse[0])
                        *h_angle_out += (diffx * head_speed[0] * 0.1);
                    else
                        *h_angle_out -= (diffx * head_speed[0] * 0.1);
                    if(*h_angle_out < head_hangle[0])
                        *h_angle_out = head_hangle[0];
                    else if(*h_angle_out > head_hangle[1])
                        *h_angle_out = head_hangle[1];
                }
                m_init_mouse_pos[0] = x;
            }
        }
        if(diffy != 0)
        {
            //if(y == m_prev_mouse_pos[1])//user stabilized mouse position. move head
            {
                if(kb_in[256] & 0b10)//if right button pressed then only move mouse
                {
                    if(m_head_speed_reverse[1])
                        *v_angle_out -= (diffy * head_speed[1] * 0.1);
                    else
                        *v_angle_out += (diffy * head_speed[1] * 0.1);
                    if(*v_angle_out < head_vangle[0])
                        *v_angle_out = head_vangle[0];
                    else if(*v_angle_out > head_vangle[1])
                        *v_angle_out = head_vangle[1];
                }
                m_init_mouse_pos[1] = y;
            }
        }
        m_prev_mouse_pos[0] = x;
        m_prev_mouse_pos[1] = y;
    }
    else//from arrow keys
    {
        //37-left, 38-up,39-right,40-bottom
        if((kb_in[37] && m_head_speed_reverse[0] == false) || (kb_in[39] && m_head_speed_reverse[0] == true))
        {
            *h_angle_out += (head_speed[0]);
            if(*h_angle_out > head_hangle[1])
                *h_angle_out = head_hangle[1];
        }
        else if((kb_in[39] && m_head_speed_reverse[0] == false) || (kb_in[37] && m_head_speed_reverse[0] == true))
        {
            *h_angle_out += (-head_speed[0]);
            if(*h_angle_out < head_hangle[0])
                *h_angle_out = head_hangle[0];
        }
        if((kb_in[38] && m_head_speed_reverse[1] == false) || (kb_in[40] && m_head_speed_reverse[1] == true))
        {
            *v_angle_out += (-head_speed[1]);
            if(*v_angle_out < head_vangle[0])
                *v_angle_out = head_vangle[0];
        }
        else if((kb_in[40] && m_head_speed_reverse[1] == false) || (kb_in[38] && m_head_speed_reverse[1] == true))
        {
            *v_angle_out += (head_speed[1]);
            if(*v_angle_out > head_vangle[1])
                *v_angle_out = head_vangle[1];
        }
    }
    //center horizontal and vertical heads when G is pressed
    if(kb_in[71])
    {
        *h_angle_out = head_hangle[2];
        *v_angle_out = head_vangle[2];
    }
}

void RemCarManual927823855::RunPlotStep(const volatile GLOBAL_RUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out){
    Run(arg, kb_in, l_speed_out, l_dir_out, r_speed_out, r_dir_out, h_angle_out, v_angle_out);
}

void RemCarManual927823855::PostRun(const volatile GLOBAL_POSTRUN_PARAM_STRUCT *arg, _INT32 *kb_in, _FLOAT *l_speed_out, _INT32 *l_dir_out, _FLOAT *r_speed_out, _INT32 *r_dir_out, _FLOAT *h_angle_out, _FLOAT *v_angle_out){
}

Credits

Aadhuniklabs

Aadhuniklabs

7 projects • 15 followers
Makers and tech enthusiast since 2022

Comments