Maestro C Serial communication

Those tutorials don’t look so good. The first one requires you to use some 3rd-party component that the author wrote. The second one is just a table of contents (with no links!) as far as I can tell.

I tried to find a better example online, but I couldn’t. So I made one. The program below is an example program for sending and receiving bytes from the Maestro over a serial port in C on Windows. I tested it with Microsoft Visual C++ 2010 Express and also MinGW, but it should work with any compiler that lets you use windows.h.

I recommend that you try to get this to compile without modifying it. Then after you verify that it works, you can modify it to suit your needs.

–David Grayson

/* MaestroSerialExampleCWindows:
 *  Example program for sending and receiving bytes from the Maestro over a serial port
 *  in C on Windows.
 *
 *  This program reads the position of channel 0.
 *  If the position is less than 6000, it sets the target to 7000.
 *  If the position is 6000 or more, it sets the target to 5000.
 *  
 *  If channel 0 is configured as a servo channel, the servo should move
 *  when you run this program (except perhaps the first time you run it).
 *  If channel 0 is configured as a digital output, the output should toggle
 *  when you run this program.
 *
 *  All the Windows functions called by the program are documented on MSDN:
 *  http://msdn.microsoft.com/
 *
 *  The error codes that this program may output are documented on MSDN:
 *  http://msdn.microsoft.com/en-us/library/ms681381%28v=vs.85%29.aspx
 *
 *  The Maestro's serial commands are documented in the "Serial Interface"
 *  section of the Maestro user's guide:
 *  https://www.pololu.com/docs/0J40
 *
 *  REQUIREMENT: The Maestro's Serial Mode must be set to "USB Dual Port"
 *  or "USB Chained" for this program to work.
 */

#include <stdio.h>
#include <windows.h>

/** Opens a handle to a serial port in Windows using CreateFile.
 * portName: The name of the port.
 * baudRate: The baud rate in bits per second.
 * Returns INVALID_HANDLE_VALUE if it fails.  Otherwise returns a handle to the port.
 *   Examples: "COM4", "\\\\.\\USBSER000", "USB#VID_1FFB&PID_0089&MI_04#6&3ad40bf600004# */
HANDLE openPort(const char * portName, unsigned int baudRate)
{
	HANDLE port;
	DCB commState;
	BOOL success;
	COMMTIMEOUTS timeouts;

	/* Open the serial port. */
	port = CreateFileA(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (port == INVALID_HANDLE_VALUE)
	{
		switch(GetLastError())
		{
		case ERROR_ACCESS_DENIED:	
			fprintf(stderr, "Error: Access denied.  Try closing all other programs that are using the device.\n");
			break;
		case ERROR_FILE_NOT_FOUND:
			fprintf(stderr, "Error: Serial port not found.  "
				"Make sure that \"%s\" is the right port name.  "
				"Try closing all programs using the device and unplugging the "
				"device, or try rebooting.\n", portName);
			break;
		default:
			fprintf(stderr, "Error: Unable to open serial port.  Error code 0x%x.\n", GetLastError());
			break;
		}
		return INVALID_HANDLE_VALUE;
	}

	/* Set the timeouts. */
	success = GetCommTimeouts(port, &timeouts);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to get comm timeouts.  Error code 0x%x.\n", GetLastError());
		CloseHandle(port);
		return INVALID_HANDLE_VALUE;
	}
	timeouts.ReadIntervalTimeout = 1000;
	timeouts.ReadTotalTimeoutConstant = 1000;
	timeouts.ReadTotalTimeoutMultiplier = 0;
	timeouts.WriteTotalTimeoutConstant = 1000;
	timeouts.WriteTotalTimeoutMultiplier = 0;
	success = SetCommTimeouts(port, &timeouts);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to set comm timeouts.  Error code 0x%x.\n", GetLastError());
		CloseHandle(port);
		return INVALID_HANDLE_VALUE;
	}

	/* Set the baud rate. */
	success = GetCommState(port, &commState);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to get comm state.  Error code 0x%x.\n", GetLastError());
		CloseHandle(port);
		return INVALID_HANDLE_VALUE;
	}
	commState.BaudRate = baudRate;
	success = SetCommState(port, &commState);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to set comm state.  Error code 0x%x.\n", GetLastError());
		CloseHandle(port);
		return INVALID_HANDLE_VALUE;
	}

	/* Flush out any bytes received from the device earlier. */
	success = FlushFileBuffers(port);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to flush port buffers.  Error code 0x%x.\n", GetLastError());
		CloseHandle(port);
		return INVALID_HANDLE_VALUE;
	}

	return port;
}

/** Implements the Maestro's Get Position serial command.
 * channel: Channel number from 0 to 23
 * position: A pointer to the returned position value (for a servo channel, the units are quarter-milliseconds)
 * Returns 1 on success, 0 on failure.
 * For more information on this command, see the "Serial Servo Commands"
 * section of the Maestro User's Guide: https://www.pololu.com/docs/0J40 */
BOOL maestroGetPosition(HANDLE port, unsigned char channel, unsigned short * position)
{
	unsigned char command[2];
	unsigned char response[2];
	BOOL success;
	DWORD bytesTransferred;

	// Compose the command.
	command[0] = 0x90;
	command[1] = channel;

	// Send the command to the device.
	success = WriteFile(port, command, sizeof(command), &bytesTransferred, NULL);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to write Get Position command to serial port.  Error code 0x%x.", GetLastError());
		return 0;
	}
	if (sizeof(command) != bytesTransferred)
	{
		fprintf(stderr, "Error: Expected to write %d bytes but only wrote %d.", sizeof(command), bytesTransferred);
		return 0;
	}

	// Read the response from the device.
	success = ReadFile(port, response, sizeof(response), &bytesTransferred, NULL);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to read Get Position response from serial port.  Error code 0x%x.", GetLastError());
		return 0;
	}
	if (sizeof(response) != bytesTransferred)
	{
		fprintf(stderr, "Error: Expected to read %d bytes but only read %d (timeout). "
			"Make sure the Maestro's serial mode is USB Dual Port or USB Chained.", sizeof(command), bytesTransferred);
		return 0;
	}

	// Convert the bytes received in to a position.
	*position = response[0] + 256*response[1];

	return 1;
}

/** Implements the Maestro's Set Target serial command.
 * channel: Channel number from 0 to 23
 * target: The target value (for a servo channel, the units are quarter-milliseconds)
 * Returns 1 on success, 0 on failure.
 * Fore more information on this command, see the "Serial Servo Commands"
 * section of the Maestro User's Guide: https://www.pololu.com/docs/0J40 */
BOOL maestroSetTarget(HANDLE port, unsigned char channel, unsigned short target)
{
	unsigned char command[4];
	DWORD bytesTransferred;
	BOOL success;

	// Compose the command.
	command[0] = 0x84;
	command[1] = channel;
	command[2] = target & 0x7F;
	command[3] = (target >> 7) & 0x7F;

	// Send the command to the device.
	success = WriteFile(port, command, sizeof(command), &bytesTransferred, NULL);
	if (!success)
	{
		fprintf(stderr, "Error: Unable to write Set Target command to serial port.  Error code 0x%x.", GetLastError());
		return 0;
	}
	if (sizeof(command) != bytesTransferred)
	{
		fprintf(stderr, "Error: Expected to write %d bytes but only wrote %d.", sizeof(command), bytesTransferred);
		return 0;
	}

	return 1;
}

/** This is the first function to run when the program starts. */
int main(int argc, char * argv[])
{
	HANDLE port;
	char * portName;
	int baudRate;
	BOOL success;
	unsigned short target, position;

	/* portName should be the name of the Maestro's Command Port (e.g. "COM4")
	 * as shown in your computer's Device Manager.
	 * Alternatively you can use \\.\USBSER000 to specify the first virtual COM
	 * port that uses the usbser.sys driver.  This will usually be the Maestro's
	 * command port. */
	portName = "\\\\.\\USBSER000";  // Each double slash in this source code represents one slash in the actual name.

	/* Choose the baud rate (bits per second).
	 * If the Maestro's serial mode is USB Dual Port, this number does not matter. */
	baudRate = 9600;

	/* Open the Maestro's serial port. */
	port = openPort(portName, baudRate);
	if (port == INVALID_HANDLE_VALUE){ return -1; }

	/* Get the current position of channel 0. */
	success = maestroGetPosition(port, 0, &position);
	if (!success){ return -1; }
	printf("Current position is %d.\n", position);

	/* Choose a new target based on the current position. */
	target = (position < 6000) ? 7000 : 5000;
	printf("Setting target to %d (%d us).\n", target, target/4);

	/* Set the target of channel 0. */
	success = maestroSetTarget(port, 0, target);
	if (!success){ return - 1; }

	/* Close the serial port so other programs can use it.
	 * Alternatively, you can just terminate the process (return from main). */
	CloseHandle(port);

	return 0;
}