Use V5 Smart Port as Generic Serial Device [PROS]

From the testing that @Andrew_Strauss and I did, it appears that fopen with mode "rb" puts the internal RS-485 transceiver in reading mode, while the "wb" mode puts it in writing mode. As RS-485 is only half-duplex, there’s no way to (usefully) enable both reading and writing at the same time.

You shouldn’t have to use any special kernel API functions (in either VCS or PROS) to read/write, it should just be via device files (/dev/port## on VCS, /dev/## on PROS). Unfortunately, there appears to some internal issue in the PROS smart device driver that prevents it from working as-is; we’ve been talking to the devs but as of yet haven’t been able to find a solution. I’m going to look into it more when I have time; we’re planning on getting it working in PROS for VEX U.

In the meantime, here is the VCS code that I got working, based on this code from jpearman:

#include <stdio.h>
#include "robot-config.h"
          
using namespace vex;

int main() {
    // IO using stdio
    //**********************
    FILE *fp1, *fp2;
    fp1 = fopen( "/dev/port20", "wb" );
    fp2 = fopen( "/dev/port18", "rb" );
    while(1) {
      if( fp1 != NULL ) {
        fprintf(fp1, "Hello\r\n");
        fflush(fp1);
      }
      if( fp2 != NULL ) {
        char buf[128];
        int nRead = fread( buf, 1, 128, fp2 );
        if( nRead > 0 ) {
          for(int i=0;i<nRead;i++) {
            printf("%02X ", buf[i]);
          }
          printf("\n");
        }
      }
    this_thread::sleep_for(10);
    }
}
1 Like

Ok, thanks.
Do you know if there’s a way to use the micro USB port on the brain to do serial with an Arduino? Or any way to hikack the pros terminal connection?

You could theoretically use an arduino to grab the serial output from the USB. I say theoretically because afaik it’s only been done so far with small linux computers, like a raspberry pi, but that’s not to say it wouldn’t work.

By default, we have “stream multiplexing” enabled on the serial line, which effectively boils down to the idea that stdout and stderr streams are prefixed with sout and serr. If you want to, this can be disabled using fdctl and the SERCTL_DEACTIVATE macro.

The other thing we do is run the serial streams (including the serial input stream) through COBS, which you’ll almost certainly want to disable (unless you implement a COBS decoder on your client device of course). This can be done with fdctl and the SERCTL_DISABLE_COBS macro.

Once you’ve done that, you should be good to read raw serial data. One caveat is that there’s currently not a way to disable the PROS banner that we display on connection, so you’ll have to deal with that on the receiving end.

Thanks @hotel , I’ll give it a try.

Do you have any idea why reading from a smart port isn’t working?
It’d be a lot easier for us to just go through that since we’ve already got the hardware set up.

Not off the top of my head. As @nickmertin mentioned, we’re still looking into it.

If you are willing to help diagnose, the relevant code is here

Thanks, I took a look but am not sure I can be of much use.
Is there anyway to call vexDeviceGenericSerialReceive directly from user code?

@jpearman Would it be possible to trick the brain into thinking the Arduino is a motor. I.e. the Arduino would wait for a message from the brain, and then send a packet with my data disguised as a reply to motor.get_position()? Or would the V5 brain know it’s not a motor and ignore this?

We strip non-public symbols, so no. edit 2: yes, if you forward declare it

Not unless you can have the Arduino send the same device initialization info to the brain (devices initiate communication)
Edit: see below post for more

No, smart devices communicate at a much higher bit rate and we do now allow that when used as a generic port. In addition, smart devices act as the master, the motor initiates all communication.

If you don’t mind me asking, what are the baud rates allowed when used as a generic port?

So when I call motor.get_position() what is actually happening on the hardware level?
How does the value for position get from the motor to the brain?
All I’m trying to do is send a 16bit number from the Arduino to the brain, so could the Arduino just wrap it’s number in a message to the brain formatted as if it were from a motor, and then read this as if it were from a motor.
So all the PROS user code would be is

Motor m(port);
double valFromArduino = m.get_position();

(not sure if you caught my edit, but you can actually use the SDK function as long as you prototype it)

Great, thanks…
Do you know what the type of the first field should be? I saw device->device_info being passed, but I’m not sure of the type or what the value should be… Is it just the port number?

yea, don’t use the vexDeviceGenericSerialReceive version that takes opaque pointer to an internal structure, use the vexGenericSerialReceive that just needs 0 based index to a port.

2 Likes

Max theoretical baud tate is 921600, however, as baud rates are not derived from a standard oscillator (typically something like 18.432MHz would be used for standard rates) there will be an error between actual baud rate and the requested baud rate, in the case of 921600, perhaps 3%. So your millage will vary depending on how accurate the device you are communicating with is. At lower baud rates the error drops to become largely insignificant.

1 Like

Ok, thanks for the information.

I’m trying the code right now, and am getting an undefined reference error. Is this the correct function prototype

int vexGenericSerialReceive(int port, char* buffer, int len);

int32_t vexGenericSerialReceive( uint32_t index, uint8_t *buffer, int32_t length );

and define as extern “C”

1 Like

It’s working!
Thank you @hotel and @jpearman for your help.
For anyone interested, here’s my working code:

#include "main.h"

// Include sstream for serial parsing
#include <sstream>


// Prototypes for hidden vex functions to bypass PROS bug
extern "C" int32_t vexGenericSerialReceive( uint32_t index, uint8_t *buffer, int32_t length );
extern "C" void vexGenericSerialEnable(  uint32_t index, uint32_t nu );
extern "C" void vexGenericSerialBaudrate(  uint32_t index, uint32_t rate );

// Port to use for serial data
#define SERIALPORT 21
// Variable to put the gyro value into
double gyroValue = 0;


// Currently reads serial data & parses for gyro value
// Can be expanded to look for lidar distance, etc.
void serialRead(void* params) {
    
    // Start serial on desired port
    vexGenericSerialEnable( SERIALPORT - 1, 0 );
    
    // Set BAUD rate
    vexGenericSerialBaudrate( SERIALPORT - 1, 115200 );
    
    // Let VEX OS configure port
    pros::delay(10);
    
    // Serial message format:
    // D[LIDAR DIST]I[IR DATA]A[GYRO ANGLE]E
    // Example Message:
    // D50.2I128A12.32E
    
    while (true) {
        
        // Buffer to store serial data
        uint8_t buffer[256];
        int len = 256;
        
        // Get serial data
        int32_t nRead = vexGenericSerialReceive(SERIALPORT - 1, buffer, len);
        
        // Now parse the data
        if (nRead >= 9) {
            
            // Stream to put the characters in
            std::stringstream myStream("");
            bool recordAngle = false;
            
            // Go through characters
            for (int i = 0; i < nRead; i++) {
                // Get current char
                char thisDigit = (char)buffer[i];
                
                // If its special, then don't record the value
                if (thisDigit == 'D' || thisDigit == 'I' || thisDigit == 'A')
                    recordAngle = false;
                
                // Finished recieving angle, so put into variable
                if (thisDigit == 'E') {
                    recordAngle = false;
                    myStream >> gyroValue;
                }
                
                // If we want the digits, put them into stream
                if (recordAngle)
                    myStream << (char)buffer[i];
                
                // If the digit is 'A', then the following data is the angle
                if (thisDigit == 'A')
                    recordAngle = true;
                
            }
            
        }
    
        // Delay to let serial data arrive
        pros::delay(20);
        
    }
    
}


void opcontrol() {
 
    // Start serial task
    pros::Task gyroTask (serialRead);
    
    while (true) {
        
        // Print value to screen
        pros::lcd::print(4,"%f", gyroValue);
        
		pros::delay(20);
	}
    
}
5 Likes

Just for completeness, this is roughly how it works:
The brain keeps a per-port set of memory buffers that the hardware (FPGA fabric used to implement the serial ports) can directly access. When a motor sends an update (which it does on its own every 5ms), the whole 16B message gets stored into one of those buffers. When you issue get_position, the function implementation directly interprets the bytes of the message in the buffer as 32bit encoder ticks, then converts the value to the unit you specify based on the configured gear ratio and so on.
When you issue a command to the motor, it gets stored as a message in an outgoing buffer, where it waits for the next brain transaction. At that point, it gets automatically sent as a reply to the motor status message (if there is no command waiting, yet another, default reply is sent to the motor).

1 Like