Support » Tic Stepper Motor Controller User’s Guide » 12. Writing PC software to control the Tic »
12.6. Example serial code for Windows in C
The example C code below uses the Windows API to communicate with a Tic via serial. It demonstrates how to set the target position of the Tic and how to read variables from it. For a very similar example that works on Linux and macOS, see Section 12.5.
To use this code, you will need to connect the Tic’s TX, RX, and GND pins to a serial port on your computer or a USB-to-serial adapter as described in Section 4.5.
The Tic’s control mode should be set to “Serial / I²C / USB”.
You will need to change the const char * device
line in the code in order to specify the correct serial port. The correct serial port name to use depends on your operating system and what type of serial port or USB-to-serial adapter you are using between the Tic and your computer.
The baud rate you select in the code should match the baud rate specified in the serial settings in the Tic Control Center.
You might notice that the Tic only performs the desired movement for about a second before it stops moving and the red LED turns on, indicating an error. This is because of the Tic’s command timeout feature: by default, the Tic’s “Command timeout” error will happen if it does not receive certain commands periodically (see Section 5.4 for details), causing the motor to stop. You can send a “Reset command timeout” command every second to get around this, or you can disable the command timeout feature using the Tic Control Center: uncheck the “Enable command timeout” checkbox in the “Serial” box.
// Uses Windows API serial port functions to send and receive data from a Tic. // NOTE: The Tic's control mode must be "Serial / I2C / USB". // NOTE: You will need to change the 'const char * device' line below to // specify the correct serial port. #include <stdio.h> #include <stdint.h> #include <windows.h> void print_error(const char * context) { DWORD error_code = GetLastError(); char buffer[256]; DWORD size = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, error_code, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer, sizeof(buffer), NULL); if (size == 0) { buffer[0] = 0; } fprintf(stderr, "%s: %s\n", context, buffer); } // Opens the specified serial port, configures its timeouts, and sets its // baud rate. Returns a handle on success, or INVALID_HANDLE_VALUE on failure. HANDLE open_serial_port(const char * device, uint32_t baud_rate) { HANDLE port = CreateFileA(device, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (port == INVALID_HANDLE_VALUE) { print_error(device); return INVALID_HANDLE_VALUE; } // Flush away any bytes previously read or written. BOOL success = FlushFileBuffers(port); if (!success) { print_error("Failed to flush serial port"); CloseHandle(port); return INVALID_HANDLE_VALUE; } // Configure read and write operations to time out after 100 ms. COMMTIMEOUTS timeouts = {0}; timeouts.ReadIntervalTimeout = 0; timeouts.ReadTotalTimeoutConstant = 100; timeouts.ReadTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = 100; timeouts.WriteTotalTimeoutMultiplier = 0; success = SetCommTimeouts(port, &timeouts); if (!success) { print_error("Failed to set serial timeouts"); CloseHandle(port); return INVALID_HANDLE_VALUE; } // Set the baud rate and other options. DCB state = {0}; state.DCBlength = sizeof(DCB); state.BaudRate = baud_rate; state.ByteSize = 8; state.Parity = NOPARITY; state.StopBits = ONESTOPBIT; success = SetCommState(port, &state); if (!success) { print_error("Failed to set serial settings"); CloseHandle(port); return INVALID_HANDLE_VALUE; } return port; } // Writes bytes to the serial port, returning 0 on success and -1 on failure. int write_port(HANDLE port, uint8_t * buffer, size_t size) { DWORD written; BOOL success = WriteFile(port, buffer, size, &written, NULL); if (!success) { print_error("Failed to write to port"); return -1; } if (written != size) { print_error("Failed to write all bytes to port"); return -1; } return 0; } // Reads bytes from the serial port. // Returns after all the desired bytes have been read, or if there is a // timeout or other error. // Returns the number of bytes successfully read into the buffer, or -1 if // there was an error reading. SSIZE_T read_port(HANDLE port, uint8_t * buffer, size_t size) { DWORD received; BOOL success = ReadFile(port, buffer, size, &received, NULL); if (!success) { print_error("Failed to read from port"); return -1; } return received; } // Sends the "Exit safe start" command. // Returns 0 on success and -1 on failure. int tic_exit_safe_start(HANDLE port) { uint8_t command[] = { 0x83 }; return write_port(port, command, sizeof(command)); } // Sets the target position, returning 0 on success and -1 on failure. // // For more information about what this command does, see the // "Set target position" command in the "Command reference" section of the // Tic user's guide. int tic_set_target_position(HANDLE port, int32_t target) { uint32_t value = target; uint8_t command[6]; command[0] = 0xE0; command[1] = ((value >> 7) & 1) | ((value >> 14) & 2) | ((value >> 21) & 4) | ((value >> 28) & 8); command[2] = value >> 0 & 0x7F; command[3] = value >> 8 & 0x7F; command[4] = value >> 16 & 0x7F; command[5] = value >> 24 & 0x7F; return write_port(port, command, sizeof(command)); } // Gets one or more variables from the Tic. // Returns 0 for success, -1 for failure. int tic_get_variable(HANDLE port, uint8_t offset, uint8_t * buffer, uint8_t length) { uint8_t command[] = { 0xA1, offset, length }; int result = write_port(port, command, sizeof(command)); if (result) { return -1; } SSIZE_T received = read_port(port, buffer, length); if (received < 0) { return -1; } if (received != length) { fprintf(stderr, "read timeout: expected %u bytes, got %lld\n", length, (int64_t)received); return -1; } return 0; } // Gets the "Current position" variable from the Tic. // Returns 0 for success, -1 for failure. int tic_get_current_position(HANDLE port, int32_t * output) { *output = 0; uint8_t buffer[4]; int result = tic_get_variable(port, 0x22, buffer, sizeof(buffer)); if (result) { return -1; } *output = buffer[0] + ((uint32_t)buffer[1] << 8) + ((uint32_t)buffer[2] << 16) + ((uint32_t)buffer[3] << 24); return 0; } int main() { // Choose the serial port name. const char * device = "\\\\.\\COM7"; // Choose the baud rate (bits per second). This must match the baud rate in // the Tic's serial settings. uint32_t baud_rate = 9600; HANDLE port = open_serial_port(device, baud_rate); if (port == INVALID_HANDLE_VALUE) { return 1; } int result; int32_t position; result = tic_get_current_position(port, &position); if (result) { return 1; } printf("Current position is %d.\n", position); int32_t new_target = position > 0 ? -200 : 200; printf("Setting target position to %d.\n", new_target); result = tic_exit_safe_start(port); if (result) { return 1; } result = tic_set_target_position(port, new_target); if (result) { return 1; } CloseHandle(port); return 0; }