Pablo AlvarezWyatt Williams
Published © GPL3+

3D Printed Positioner for Terahertz Pulse Image Creation

Stepper motor controlled and 3D printed positioner to allow image scanning using pulsed terahertz waves for detection of breast cancer.

AdvancedShowcase (no instructions)20 hours499
3D Printed Positioner for Terahertz Pulse Image Creation

Things used in this project

Hardware components

Molex, LLC 08-50-0114
Used to crimp to the wire leads of the stepper motors which then go into a housing.
×8
Microchip Technology PIC24FJ256GA702-I/SP
Microprocessor used. Made by Microchip Technology.
×1
TE Connectivity AMP Connectors 640456-4
Goes onto the PCB and provides connectivity from motor drivers to motors.
×2
TE Connectivity AMP Connectors 7-87499-2
Housing for crimped motor wire leads.
×8
On Shore Technology Inc. ED281DT
Socket for holding the microchip processor.
×2
Allegro MicroSystems, LLC A4988SETTR-T
Driver used to control the stepper motors.
×2
Portescap 26M048B2U-V31
Stepper motor used for rotation and vertical movements.
×2
Amphenol FCI LD09P13A4GX00LF
Serial DB9 male connector. Used for communications.
×1
Triad Magnetics WSU120-0700
Wall adapter to provide 12V DC output through a barrel connector.
×1
CUI Inc. PJ-047AH
Goes onto PCB. Provides connection to the 12V DC wall adapter.
×1
Assmann WSW Components AK152-2
Straight through cable for serial communication.
×1
Panasonic Electronic Components ECA-1HM101
100uF electrolytic capacitor for 12V to 3.3V buck converter.
×1
Panasonic Electronic Components EEE-1EA331UP
330uF electrolytic capacitor for 12V to 3.3V buck converter.
×1
KEMET C0805C224K5RACTU
.22uF ceramic capacitor for motor drivers.
×4
Taiyo Yuden EMK325ABJ107MM-T
100uF ceramic capacitor for motor drivers
×2
Murata Electronics North America GRM21BR61E106KA73L
10uF ceramic capacitor for motor microchip processor.
×1
Samsung Electro-Mechanics CL21B104KCFSFNE
.1uF ceramic capacitors for motor drivers, microprocessor, and level shifter.
×13
Yageo RC0805FR-074K7L
4.7k resistors for PIC MCLR pin.
×2
Susumu RL1220T-R050-J
50m resistors for motor drivers.
×4
Yageo RC0805FR-07150RL
150 resistor for motor drivers.
×2
Yageo RC0805FR-071K2L
1.2k resistor for motor drivers.
×2
Yageo RC0805FR-074K99L
5k resistor for motor drivers.
×2
Pulse Electronics Power PE-52627NL
330uH for 12V to 3.3V buck converter.
×1
ON Semiconductor 1N5821G
30V 3A rated schottky diode for 12V to 3.3V buck converter.
×1
Wurth Electronics Inc. 615006138421
RJ11 jack for programming the microchip microprocessor.
×1
TE Connectivity ALCOSWITCH Switches 1825910-6
A reset button for the microprocessor.
×1
Amphenol FCI 68786-102LF
Jumpers for shunting certain pins to switch from programming mode to application mode.
×2
Amphenol FCI 67997-206HLF
Headers to switch from programming mode to application mode.
×1
Texas Instruments TL2575-33IN
Texas Instruments 12V to 3.3V buck converter to provide the logic voltage supply from from the wall adapter.
×1
On Shore Technology Inc. ED16DT
Socket to hold the 12V to 3.3V buck converter.
×1
595-MAX3232CDR Texas Instruments RS-232 Interface IC
Texas Instruments RS232 to IC interface. A level shifter to convert the logic voltage levels between the pc and microprocessor to allow communication.
×1
Microchip Technology MPLAB ICD 3 In-Circuit Debugger
A programming tool used to debug microprocessor.
×1
KPL32-32-12
1/8" Bore 32 Pitch Shaft Mount Pinion Gears (Steel), 12 teeth
×1
615226
1.00" Bore 32 Pitch Aluminum Hub Gear, 84 teeth
×1
625166
0.125" to 8mm Bore Set Screw Shaft Couplers
×1
545315
8mm lead screw nut, 0.770" pattern
×1
634050
12.00" 8mm lead screw
×1
561-K41
#4 Round Nylon Spacers, 1 inch
×4
634116
1/8" Round shaft, 2.00"
×1
625122
1/8" to 1/8" coupler
×1
#6-32 x 1/4" Pan Head
×4
#6-32 x 1-3/4" Hex Drive
×7
#6-32 Nut
×7
#4-40 x 3-8" Hex Drive
×4
Slip Ring
×1
Pillow Block Bearing
×1

Software apps and online services

Microchip Technology MPLAB® X Integrated Development Environment (IDE)
Program used to code the microprocessor
TeraTerm
An open source terminal emulator used to interface with the user.
Freedfm
Website used to check pcb potential issues.
Simplify3D
Software for advanced 3D print generation.
SOLIDWORKS

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Generic soldering equipment.
Flashforge Creator Pro
Commercially available 3D printer with heated bed compatible with ABS, PLA filament.

Story

Read more

Custom parts and enclosures

Mechanical Assembly

Entire packaged assembly including custom printed parts and off-the-shelf components. These were exported to STL files from SOLIDWORKS then printed on the Creator Pro using Simplify3D.

Schematics

Motor Controller Circuit Schematic

This is the circuit schematic drawn using cadence's OrCAD Capture CIS - Lite.

Motor Controller PCB Layout

The motor controller PCB layout drawn using Cadence's OrCAD PCB Designer Lite.

Code

Motor Controller Source Code

C/C++
Once the code is uploaded to the microprocessor it will communicate with a terminal emulator using UART protocols. Depending on how the user wants to move, the processor will send directions to the drivers to make them step the motors. The code is specialized more towards TeraTerm which is an open source terminal emulator. A few instructions are sent out to the terminal to adjust the screen display such as clearing the screen and moving the cursor position.
/*
 * File:   newmainXC16.c
 * Author: palvarez
 *
 * Created on March 16, 2018, 2:03 PM
 */

// PIC24FJ256GA702 Configuration Bit Settings

// 'C' source line config statements

// FSEC
#pragma config BWRP = OFF               // Boot Segment Write-Protect bit (Boot Segment may be written)
#pragma config BSS = DISABLED           // Boot Segment Code-Protect Level bits (No Protection (other than BWRP))
#pragma config BSEN = OFF               // Boot Segment Control bit (No Boot Segment)
#pragma config GWRP = OFF               // General Segment Write-Protect bit (General Segment may be written)
#pragma config GSS = DISABLED           // General Segment Code-Protect Level bits (No Protection (other than GWRP))
#pragma config CWRP = OFF               // Configuration Segment Write-Protect bit (Configuration Segment may be written)
#pragma config CSS = DISABLED           // Configuration Segment Code-Protect Level bits (No Protection (other than CWRP))
#pragma config AIVTDIS = OFF            // Alternate Interrupt Vector Table bit (Disabled AIVT)

// FBSLIM
#pragma config BSLIM = 0x1FFF           // Boot Segment Flash Page Address Limit bits (Boot Segment Flash page address  limit)

// FSIGN

// FOSCSEL
#pragma config FNOSC = FRC              // Oscillator Source Selection (Internal Fast RC (FRC))
#pragma config PLLMODE = DISABLED       // PLL Mode Selection (No PLL used; PLLEN bit is not available)
#pragma config IESO = ON                // Two-speed Oscillator Start-up Enable bit (Start up device with FRC, then switch to user-selected oscillator source)

// FOSC
#pragma config POSCMD = NONE            // Primary Oscillator Mode Select bits (Primary Oscillator disabled)
#pragma config OSCIOFCN = OFF           // OSC2 Pin Function bit (OSC2 is clock output)
#pragma config SOSCSEL = OFF             // SOSC Power Selection Configuration bits (SOSC is used in crystal (SOSCI/SOSCO) mode)
#pragma config PLLSS = PLL_PRI          // PLL Secondary Selection Configuration bit (PLL is fed by the Primary oscillator)
#pragma config IOL1WAY = ON             // Peripheral pin select configuration bit (Allow only one reconfiguration)
#pragma config FCKSM = CSDCMD           // Clock Switching Mode bits (Both Clock switching and Fail-safe Clock Monitor are disabled)

// FWDT
#pragma config WDTPS = PS32768          // Watchdog Timer Postscaler bits (1:32,768)
#pragma config FWPSA = PR128            // Watchdog Timer Prescaler bit (1:128)
#pragma config FWDTEN = OFF              // Watchdog Timer Enable bits (WDT Enabled)
#pragma config WINDIS = OFF             // Watchdog Timer Window Enable bit (Watchdog Timer in Non-Window mode)
#pragma config WDTWIN = WIN25           // Watchdog Timer Window Select bits (WDT Window is 25% of WDT period)
#pragma config WDTCMX = WDTCLK          // WDT MUX Source Select bits (WDT clock source is determined by the WDTCLK Configuration bits)
#pragma config WDTCLK = LPRC            // WDT Clock Source Select bits (WDT uses LPRC)

// FPOR
#pragma config BOREN = ON               // Brown Out Enable bit (Brown Out Enable Bit)
#pragma config LPCFG = OFF              // Low power regulator control (No Retention Sleep)
#pragma config DNVPEN = ENABLE          // Downside Voltage Protection Enable bit (Downside protection enabled using ZPBOR when BOR is inactive)

// FICD
#pragma config ICS = PGD1               // ICD Communication Channel Select bits (Communicate on PGEC1 and PGED1)
#pragma config JTAGEN = OFF             // JTAG Enable bit (JTAG is disabled)

// FDEVOPT1
#pragma config ALTCMPI = DISABLE        // Alternate Comparator Input Enable bit (C1INC, C2INC, and C3INC are on their standard pin locations)
#pragma config TMPRPIN = OFF            // Tamper Pin Enable bit (TMPRN pin function is disabled)
#pragma config SOSCHP = ON              // SOSC High Power Enable bit (valid only when SOSCSEL = 1 (Enable SOSC high power mode (default))
#pragma config ALTI2C1 = ALTI2CDIS       // Alternate I2C pin Location (SDA1 and SCL1 on RB9 and RB8)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#include <stdio.h>

#include "xc.h"
#include <p24FJ256GA702.h>
//#include <uart.h>
#include <stdlib.h>
#define FCY 8000000/2 // instruction cycle freq
#include <libpic30.h>
#define BAUDRATE 9600
#define BRGVAL ((FCY/BAUDRATE)/16)-1

//Initialize UART1 peripheral
void UART_init(void) {
    TRISAbits.TRISA0 = 1; //SET PIN 2 AS INPUT
    TRISAbits.TRISA1 = 0; //SET PIN 3 AS OUTPUT
    RPINR18bits.U1RXR = 26; //set pin 2 as receive, RP26
    RPOR13bits.RP27R = 3; //set pin 3 as transmit, RP27
    U1STAbits.UTXISEL1 = 0; //SET TO INTERRUPT WHEN THE LAST CHARACTER IS SHIFTED OUT OF THE TRANSMIT SHIFT REGISTER
    U1STAbits.UTXISEL0 = 1;
    U1STAbits.URXISEL1 = 0; //SET TO INTERUPT ON AN RSR TRANSFER, MAKING THE RECEIVE BUFFER FULL
    U1STAbits.URXISEL0 = 1;
    U1MODEbits.UARTEN = 1; // ENABLES UART, PINS CONTROLLED BY UART AS DEFINED BY UEN<1:0>
    U1MODEbits.PDSEL1 = 1; //FUCNTION AS 8 BIT DATA, ODD PARITY
    U1MODEbits.PDSEL0 = 0;
    U1MODEbits.BRGH = 0; //SET BRG FOR STANDARD SPEED MODE
    U1BRG = BRGVAL; //SET THE BAUDRATE GENERATOR TO BRGVAL(9600) WHICH CAN BE CHANGED IN THE DEFINE FUNCTIONS
    U1STAbits.URXEN = 1; //RECEIVE IS ENABLED
    U1STAbits.UTXEN = 1; //TRANSMIT IS ENABLED
    U1MODEbits.RXINV = 0;
    
    
}

//Initialize motor 1 (right side of pcb)
void motor1_init()
{
    //Set pins as outputs
    TRISBbits.TRISB0 = 0;
    TRISBbits.TRISB1 = 0;
    TRISBbits.TRISB2 = 0;
    TRISBbits.TRISB3 = 0;
    TRISBbits.TRISB4 = 0;
    TRISBbits.TRISB5 = 0;
    TRISBbits.TRISB6 = 0;
    TRISBbits.TRISB7 = 0;
    
    LATBbits.LATB0 = 0;//Arbitrarily set direction of motor
    LATBbits.LATB2 = 1;//Set SLEEP high to operate outputs normally
    LATBbits.LATB3 = 1;//Set RESET high to operate device normally
    LATBbits.LATB4 = 0;//MS3, MS2, & MS1 pins to operate step size as full step
    LATBbits.LATB5 = 0;
    LATBbits.LATB6 = 0;
    LATBbits.LATB7 = 0;//Set ENABLE low to operate device normally
    
}

//Initialize motor 2 (right side of pcb)
void motor2_init()
{
    //Set pins as outputs
    TRISBbits.TRISB8 = 0;
    TRISBbits.TRISB9 = 0;
    TRISBbits.TRISB10 = 0;
    TRISBbits.TRISB11 = 0;
    TRISBbits.TRISB12 = 0;
    TRISBbits.TRISB13 = 0;
    TRISBbits.TRISB14 = 0;
    TRISBbits.TRISB15 = 0;
    
    LATBbits.LATB8 = 0;//Arbitrarily set direction of motor
    LATBbits.LATB10 = 1;//Set SLEEP high to operate outputs normally
    LATBbits.LATB11 = 1;//Set RESET high to operate device normally
    LATBbits.LATB12 = 0;//MS3, MS2, & MS1 pins to operate step size as full step
    LATBbits.LATB13 = 0;
    LATBbits.LATB14 = 0;
    LATBbits.LATB15 = 0;//Set ENABLE low to operate device normally
}

//Function to send one byte of data to UART
void UART_send_char(char bt)
{
    while(!U1STAbits.TRMT);//Transmit buffer empty
    U1TXREG = bt;
}

//**Function to convert string to byte
void UART_send_string(char* st_pt)
{
    while(*st_pt) //if there is a char
        UART_send_char(*st_pt++); //process it as a byte data
}

//Function to get one byte of data from UART
char UART_get_char()   
{
    if(U1STAbits.OERR == 1)// check for Error 
    {
        U1STAbits.OERR = 0;//If error -> Reset 
    }
    while(!U1STAbits.URXDA);// hold the program till RX buffer is free
    return U1RXREG; 
}

//Function to get a string of data from UART
void UART_get_string(char line[])
{
    int i = 0;
    while(1) 
    {
        line[i] = UART_get_char();
        if (line[i] == '\n') break;//Test for an ENTER key press
        if (!U1STAbits.URXDA && line[i] == '\x1b')//Test for a single ESCAPE key press
        {
            i++;
            break;
        }
        if(line[i] == '\x08')//Test for a BACKSPACE key press
        {
            UART_send_string("\x1b[K");//clear the text on the terminal screen
            i = i-2;//decrement received array
        }
        i++;
    }
    line[i] = 0;//terminate string with null character
}

//Function to receive a single key press without the need for an ENTER termination
void UART_get_arrow(char manual[20])
{
    //char manual[20];
    int i = 0;
    while(1) 
    {
        manual[i] = UART_get_char();
        i++;
        __delay_ms(2);
        if (!U1STAbits.URXDA) break;
    }
    manual[i] = 0;
}

//function to display rotation menu
void display_rotation_menu()
{
    char rotation_string[20];
    while(rotation_string[0] != 27)//Test for ESCAPE key press
    {
        UART_send_string("\n==========ROTATE==========\n-Please choose a rotation option and press enter or press ESCAPE to go to main menu\n1. Clockwise\n2. Counter clock wise\n==========================\n");
        UART_get_string(rotation_string);
        switch(rotation_string[0])
        {
            case '1' :
                UART_send_string("\x1b[2J");//clear terminal
                UART_send_string("\x1b[H");//move cursor to the home position
                clock_wise_display();
                break;
            case '2' :
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                counter_clock_wise_display();
                break;
            case '\x1b' :
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                break;
            default:
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                break;
        }
    }
}

void clock_wise_display()
{
    char cw_string[20];
    while(cw_string[0] != 27)
    {
        UART_send_string("\n==========CLOCK WISE ROTATION==========\n-Please enter amount of degrees and press enter or press ESCAPE to return to previous menu\n=======================================\n");
        UART_get_string(cw_string);
        if(cw_string[0] == '\x1b')
        {
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            return;
        }
        else
        {
            UART_send_string("\n...in progress...");
            int num = atoi(cw_string);
            rotcw(num);
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
        }
    }
}

//function to call for displaying counter clockwise rotation menu and controlling to motors
void counter_clock_wise_display()
{
    char ccw_string[20];
    while(ccw_string[0] != 27)//test for ESCAPE
    {
        UART_send_string("\n==========COUNTER CLOCK WISE ROTATION==========\n-Please enter amount of degrees and press enter or press ESCAPE to go to previous menu\n===============================================\n");
        UART_get_string(ccw_string);
        if(ccw_string[0] == '\x1b')
        {
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            return;
        }
        else
        {
            UART_send_string("\n...in progress...");
            int num = atoi(ccw_string);
            rotccw(num);
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
        }
    }
}

// function to rotate the platform clock wise
void rotcw(int x)
{
    int N;
    int i;
    //set direction pin for appropriate motor
    LATBbits.LATB8 = 0;//uncomment one for one direction, different from other rotation function
    //LATBbits.LATB8 = 1;
    N = x*28; //N = number of steps X steps per degree, step size is quarter of a degree and a step in the first gear is a seventh of a degree of the second gear
    for( i = 0; i < N; i++)
	{
		LATBbits.LATB9 = 0;
        __delay_ms(1);
        LATBbits.LATB9 = 1;
        __delay_ms(1);
	}
}

// function to rotate the platform counter clock wise
void rotccw(int x)
{
    int i;
    //set direction pin for appropriate motor
    //LATBbits.LATB8 = 0;//uncomment one for one direction, different from other rotation function
    LATBbits.LATB8 = 1;
    int N = x*28; //N = number of steps X steps per degree, step size is quarter of a degree
    for( i = 0; i < N; i++)
	{
		LATBbits.LATB9 = 0;
        __delay_ms(1);
        LATBbits.LATB9 = 1;
        __delay_ms(1);
	}

}

//function to display shift menu
void display_shift_menu()
{
    char shift_string[20];
    while(shift_string[0] != 27)
    {
        UART_send_string("\n==========SHIFT==========\n-Please choose a shift option and press enter or press ESCAPE to go to main menu\n1. Raise\n2. Lower\n=========================\n");
        UART_get_string(shift_string);
        switch(shift_string[0]) 
            {
            case '1' :
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                raise_display();
                break;
            case '2' :
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                lower_display();
                break;
            case '\x1b' :
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                break;
            default:
                UART_send_string("\x1b[2J");
                UART_send_string("\x1b[H");
                break;
            }
    }

}

void raise_display()
{
    char raise_string[20];
    while(raise_string[0] != 27)
    {
        UART_send_string("\n==========RAISE PLATFORM==========\n-Please enter distance in micrometers to raise or press ESCAPE to go to previous menu\n==================================\n");
        UART_get_string(raise_string);
        if(raise_string[0] == 27)
        {
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            return;
        }
        else
        {
            UART_send_string("...in progress...");
            int num = atoi(raise_string);//change the string to an integer
            raise(num);
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
        }
    }
}

//function to call for displaying down menu and controlling motors
void lower_display()
{
    char lower_string[20];
    while(lower_string[0] != 27)
    {
        UART_send_string("\n==========LOWER PLATFORM==========\n-Please enter distance in micrometers to lower or press ESCAPE to go to previous menu\n==================================\n");
        UART_get_string(lower_string);
        if(lower_string[0] == 27)
        {
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            return;
        }
        
        else
        {
            UART_send_string("...in progress...");
            int num = atoi(lower_string);//change the string to an integer
            lower(num);
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
        }
    }
}

//function to raise the platform
void raise(int x)
{
    float N = x/1.38;
	int i;
    //set direction pin for appropriate motor
    LATBbits.LATB0 = 0;//uncomment one for one direction, different from other shifting function
    //LATBbits.LATB0 = 1;
     //N = number of steps required to achieve the shift distance
    for( i = 0; i < N; i++)
	{
        LATBbits.LATB1 = 0;
        __delay_ms(1);
        LATBbits.LATB1 = 1;
        __delay_ms(1);
	}
}

//function to lower the platform
void lower(int x)
{
	int i;
    //set direction pin for appropriate motor
    //LATBbits.LATB0 = 0;//uncomment one for one direction, different from other shifting function
    LATBbits.LATB0 = 1;
    float N = x/1.38; //N = number of steps required to achieve the shift distance
    for( i = 0; i < N; i++)
	{
		LATBbits.LATB1 = 0;
        __delay_ms(1);
        LATBbits.LATB1 = 1;
        __delay_ms(1);
	}
}

//function to manually move the motors
void manual_movement()
{
    char manual_string[20];
    UART_send_string("\n==========MANUAL MOVEMENT==========\n-Use the arrow keys to move or press ESCAPE to go back to main menu\n[UP] = UP\n[DOWN] = DOWN\n[LEFT] = Clockwise rotation\n[RIGHT] = Counterclockwise rotation\n==================================\n");
	while(1)
	{
		UART_get_arrow(manual_string);
        if(manual_string[0] ==27 && manual_string[1] == NULL)//ESCAPE
        {
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            return;
        }
        //Raise
        else if(manual_string[0] == 27 && manual_string[1] ==  '[' && manual_string[2] == 'A') //function to step up once
        {
            UART_send_string("\x1b[1B");
            raise(10);
        }
        //lower
        else if(manual_string[0] == 27 && manual_string[1] == '['  && manual_string[2] == 'B')
        {
            UART_send_string("\x1b[1A");
            lower(10);
        }
        //clockwise
        else if(manual_string[0] == 27 && manual_string[1] ==  '[' && manual_string[2] == 'C')
        {
            UART_send_string("\x1b[1D");
            rotcw(3);
        }
        //counterclock wise
        else if(manual_string[0] == 27 && manual_string[1] ==  '[' && manual_string[2] == 'D')
        {
            UART_send_string("\x1b[1C");
            rotccw(3);
        }
        else
        {
            UART_send_string("non key\n");
        }
	}
}

int main(void) {
    ANSB = 0;//set as digital pins
    ANSA = 0;
    UART_init();//initialize UART1 module
    motor1_init();//initialize the 2 motor drivers
    motor2_init();
    char get_string[20];
    UART_send_string("*\x1b[2J");
    UART_send_string("\x1b[H");
    UART_send_string("UART communications initialized and ready");
    while(1) 
    {
        UART_send_string("\n==========MAIN MENU==========\n-Please choose a menu option and press enter\n1. Rotate platform\n2. Raise or lower platform\n3. Manually move positioner\n=============================\n");
        UART_get_string(get_string);
        switch(get_string[0]) 
        {
        case '1' :
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            display_rotation_menu();
            break;
        case '2' :
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            display_shift_menu();
            break;
        case '3' :
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            manual_movement();
            break;
        default:
            UART_send_string("\x1b[2J");
            UART_send_string("\x1b[H");
            break;
        }
    }
    return 0;
}

Credits

Pablo Alvarez

Pablo Alvarez

1 project • 2 followers
Wyatt Williams

Wyatt Williams

1 project • 1 follower

Comments