воскресенье, 22 ноября 2015 г.

Настройка рабочей среды для начала работы с OpenGL

Нарисовать раскрашенный треугольник, скопипастив пару строк кода из мануалов, значительно проще, чем подговить рабочее окружение. По крайней мере для того, кто с OpenGL сталкивается впервые.

Операционная система - Windows 7 x64 (она стоит на моем ноутбуке), а в качестве IDE выбран Visual Studio 2013 Express (очень удобный инструмент и доступен бесплатно).
Проблема в том, что нельзя просто взять и нарисовать треугольник.


Чтобы это сделать, нужно создать контекст, в котором мы будем рисовать и обрабатывать ввод-вывод. Для этого существуют специальные библиотеки. GLFW кажется мне достаточно подходящей для этой цели. Загружаем с сайта заголовочные файлы и бинарники. Но и этого недостаточно, потому что Visual Studio поставляется с заголовочными файлами для спецификации OpenGL 1.1, которая вышла ну очень давно, более 20 лет назад. Так что нам нужны еще свежие заголовочные файлы и я выбрал библиотеку GLEW, которая содержит не только свежие заголовочные файлы, но и еще много чего полезного.

Теперь приступаем к установке библиотек:
- распаковываем и скачанные архивы;
- копируем заголовочные файлы, чтобы Visual Studio мог ими пользоваться. В моем случае это было содержимое папок include в архивах, скопированное в папку "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include". Теперь там появились две новые папки с заголовочными файлами: GL и GLFW;
- копируем glfw3.lib, glew32.lib и glew32s.lib в "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\lib" (обратите внимание, что нужно использовать 32-битные версии этих файлов, т.к. Visual Studio генерирует 32-битные версии бинарных файлов);
- копируем glew32.dll и glfw3.dll в папку C:Windows\System (тоже используем 32-битные версии файлов).

Идем дальше. Открываем Visual Studio, создаем простое консольное приложение и настраиваем его: в "Configuration Properties => Linker => Input => Additional Dependencies" добавляем библиотеки glew32s.lib, glew32.lib, glfw3.lib, OpenGL32.lib:


Теперь можно попробовать запустить простой пример:
 
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
 
// Shader sources
const GLchar* vertexSource =
"#version 150 core\n"
"in vec2 position;"
"in vec3 color;"
"out vec3 Color;"
"void main() {"
"   Color = color;"
"   gl_Position = vec4(position, 0.0, 1.0);"
"}";
 
const GLchar* fragmentSource =
"#version 150 core\n"
"in vec3 Color;"
"out vec4 outColor;"
"void main() {"
"   outColor = vec4(Color, 1.0);"
"}";
 
int main(int argc, char* argv[])
{
 glfwInit();
 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
 
 glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
 
 GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", nullptr, nullptr); // Windowed
 //GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", glfwGetPrimaryMonitor(), nullptr); // Fullscreen
 
 glfwMakeContextCurrent(window);
 
 // Initialize GLEW
 glewExperimental = GL_TRUE;
 glewInit();
 
 // Create Vertex Array Object
 GLuint vao;
 glGenVertexArrays(1, &vao);
 glBindVertexArray(vao);
 
 // Create a Vertex Buffer Object and copy the vertex data to it
 GLuint vbo;
 glGenBuffers(1, &vbo);
 
 GLfloat vertices[] = {
  0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
  0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
  -0.5f, -0.5f, 0.0f, 0.0f, 1.0f
 };
 
 glBindBuffer(GL_ARRAY_BUFFER, vbo);
 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
 
 // Create and compile the vertex shader
 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
 glShaderSource(vertexShader, 1, &vertexSource, NULL);
 glCompileShader(vertexShader);
 
 // Create and compile the fragment shader
 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
 glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
 glCompileShader(fragmentShader);
 
 // Link the vertex and fragment shader into a shader program
 GLuint shaderProgram = glCreateProgram();
 glAttachShader(shaderProgram, vertexShader);
 glAttachShader(shaderProgram, fragmentShader);
 glBindFragDataLocation(shaderProgram, 0, "outColor");
 glLinkProgram(shaderProgram);
 glUseProgram(shaderProgram);
 
 // Specify the layout of the vertex data
 GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
 glEnableVertexAttribArray(posAttrib);
 glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);
 
 GLint colAttrib = glGetAttribLocation(shaderProgram, "color");
 glEnableVertexAttribArray(colAttrib);
 glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat)));
 
 //main loop
 while (!glfwWindowShouldClose(window))
 {
  if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
   glfwSetWindowShouldClose(window, GL_TRUE);
 
  // Clear the screen to black
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
 
  // Draw a triangle from the 3 vertices
  glDrawArrays(GL_TRIANGLES, 0, 3);
 
  glfwSwapBuffers(window);
  glfwPollEvents();
  //glfwShowWindow(window);
 }
 
 glDeleteProgram(shaderProgram);
 glDeleteShader(fragmentShader);
 glDeleteShader(vertexShader);
 
 glDeleteBuffers(1, &vbo);
 
 glDeleteVertexArrays(1, &vao);
 
 glfwTerminate();
 
 return 0;
}




Если все сделано правильно, мы должны увидеть такой результат:
Ссылки на использованные материалы:
Setting up Windows Environment Window and OpenGL context GLEW installation

вторник, 14 июля 2015 г.

Как используется оперативная память при статической и динамической линковке библиотек


Самый простой случай - статическое линкование, когда весь код и данные библиотеки включаются в файл:



То есть каждая программа, которая использует библиотеку с таким способом линковки, содержит свой собственный экземпляр библиотеки.

Теперь же рассмотрим динамическое линкование. Дела обстоят совсем по-другому:



Как видно из рисунка, программа, использующая динамическую линковку, создает только копии модифицируемых глобальных переменных библиотеки ("library data"), а непосредственно код библиотек ("library code") не копирует, а использует тот, что уже загружен в память, используя отображение (mapping) адресов функций из виртуального адресного пространства на физические адреса функций загруженной в память библиотеки. И каждая программа, вызывая функции динамически линкованной библиотеки, использует одни и те же физические адреса, что позволяет экономить память.

Подробнее здесь.

воскресенье, 23 ноября 2014 г.

asm.js vs plain c performance test

Много читал в разных местах про asm.js как утилиту, позволяющую портировать обычный код С/С++ в нечто, что можно вставить в веб-страницу и оно там будет работать без каких-либо дополнительных усилий со стороны браузера (не нужно ставить никаких плагинов, расширений и проч.), достаточно просто поддержки javascript, которая сегодня есть у всех популярных браузеров.
 
asm.js - это, по сути, обычный javascript, который активно используется в вебе. Подмножество javascript, если быть более точным. Больше ответов на другие вопросы на официальной странице и странице компилятора.

Очень захотелось лично попробовать это. Наваял небольшую программу:

#include "md5.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
 
int main()
{
    time_t t1;
    time(&t1);
    const unsigned int dataSize = 1024 * 1024 * 30;
    unsigned char * data;
    data = (unsigned char*) malloc (dataSize);
 
    srand((unsigned int) t1);
    for(int i = 0; i < dataSize; i++)
    {
        data[i] = rand() % 255;
    }
 
    MD5_CTX mdContext;
    for(int i = 0; i < 100; i++)
    {
        MD5Init(&mdContext);
        MD5Update(&mdContext, data, dataSize);
        MD5Final (&mdContext);
    }
 
    free(data);
 
    time_t t2;
    time(&t2);
 
    const double seconds = difftime(t2, t1);
    printf ("\n%3.2f seconds", seconds);
 
 
    return 0;
}

Что мы тут делаем. Выделяем кусок памяти в 30 мегабайт, заполняем его случайными числами, а потом 100 раз вычисляем хеш этого массива, освобождаем память. Сначала запустил как обычную консольную программу, а потом скомпилировал в html-файл, который открыл в chrome и firefox.

.pro-файл консольного приложения:
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt
QMAKE_CFLAGS_RELEASE += -O3 -std=c99
SOURCES += main.c


Скрипт для компилирования html-страницы:
emcc -o2  main.c  -o md5.html -s TOTAL_MEMORY=40000000 

В скрипте параметр TOTAL_MEMORY указывает сколько памяти может потребить скрипт. С этим параметром надо обращаться осторожно и не указывать слишком больше число "про запас", без необходимости.
Файл md5.h я взял здесь.

Результаты:
console app: 14 sec, 31MB RAM
firefox: 36 sec,
chrome: 58 sec, 71 MB RAM

Обращает на себя внимание, что chrome работает заметно медленнее firefox. А последний не так радикально медленнее "чистого" си - всего в 2.5 раза. Потребление памяти тоже любопытно. Консольная программа ожидаемо заняла около 31 мегабайта памяти, страница chrome заняла 71 мегабайт. Всего в 2 раза больше, чем консольная. По-моему, очень и очень неплохо. Стоит сказать большое спасибо тем, кто создал и оптимизировал emscripten до такой степени, что javascript проигрывает C в быстродействии всего лишь в 2-4 раза и в потреблении памяти в 2 раза. Это делает возможным проивзодить некоторые достаточно тяжелые вычисления прямо на веб-странице, в браузере клиента.



пятница, 21 февраля 2014 г.

Ридер для магнитных лент KDM-96XX

Ниже представлен код для карт-ридера магнитных лент KDM-96XX.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <string.h>

#define CRTSCTS  020000000000    /* flow control */


#define KDM96XX_HEADER 0x60

#define KDM96XX_CONTROL_CLA 0x43

#define KDM96XX_CONTROL_EJECT_CMD 0x31
#define KDM96XX_CONTROL_PRESPOS_CMD 0x32
#define KDM96XX_CONTROL_INIT_CMD 0x33
#define KDM96XX_CONTROL_GETVER_CMD 0x34

#define KDM96XX_MSCARD_CLA 0x4D
#define KDM96XX_MSCARD_FREAD_CMD 0x31
#define KDM96XX_MSCARD_BREAD_CMD 0x32
#define KDM96XX_MSCARD_READCLEAR_CMD 0x33

char calculate_xor(char * data, int len)
{
    char rslt = 0;
    int i;
    for(i=0; i<len; i++)
        rslt ^= data[i];
   
    return rslt;
}

int set_interface_attribs (int fd, int speed, int parity, float timeoutSec)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
            printf("error from tcgetattr");
            return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // ignore break signal
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = (int)(timeoutSec * 10.0);// read timeout in sec

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        //tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr(fd, TCSANOW, &tty) != 0)
        {
            printf("error from tcsetattr");
            return -1;
        }
        return 0;
}

void set_blocking (int fd, int should_block, float timeoutSec)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
            printf ("error from tcgetattr");
            return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = (int)(timeoutSec * 10.0);  // read timeout in sec

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
            printf("error setting term attributes");
}

void show_char_vect_as_hex(char * buf, int size)
{
    printf("[");
    int i;
    for(i=0; i<size; i++)
    {
        printf("x%02x", (unsigned int) buf[i]);
        if(i != size-1)
            printf(", ");
    }
    printf("]");
}

void show_char_vect_as_dec(char * buf, int size)
{
    printf("[");
    int i;
    for(i=0; i<size; i++)
    {
        printf("%02d", (unsigned int) buf[i]);
        if(i != size-1)
            printf(", ");
    }
    printf("]");
}

void send_init_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_CONTROL_CLA;//instructions class
    data[4] = KDM96XX_CONTROL_INIT_CMD;//command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);   
    printf("\n'init', sent bytes: ");
    show_char_vect_as_hex(data, len);
    if(rslt < 0)
    {
        printf(" write error!!");
    }
}

void send_getver_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_CONTROL_CLA;//instructions class
    data[4] = KDM96XX_CONTROL_GETVER_CMD; //command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);   
    printf("\n'getver', sent bytes: ");
    show_char_vect_as_hex(data, len);
    if(rslt < 0)
    {
        printf(" write error!!");
    }
}
void send_report_the_presence_and_pos_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_CONTROL_CLA;//instructions class
    data[4] = KDM96XX_CONTROL_PRESPOS_CMD;//command
    data[5] = calculate_xor(data, len-1);   
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf(" send_report_the_presence_and_pos_cmd(), write error\n");
        return;
    }
    printf("\n'presence and position', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void send_fread_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_MSCARD_CLA;//instructions class
    data[4] = KDM96XX_MSCARD_FREAD_CMD;//command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf("send_fread_cmd(), write error\n");
        return;
    }
    printf("\n'fread', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void send_bread_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_MSCARD_CLA;//instructions class
    data[4] = KDM96XX_MSCARD_BREAD_CMD;//command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf("send_bread_cmd(), write error\n");
        return;
    }
    printf("\n'bread', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void send_cread_cmd(int fd)
{
    int len = 6;
    char data[len];
    data[0] = KDM96XX_HEADER;//header
    data[1] = 0x00;
    data[2] = 0x02;//data field size
    data[3] = KDM96XX_MSCARD_CLA;//instructions class
    data[4] = KDM96XX_MSCARD_READCLEAR_CMD; //command
    data[5] = calculate_xor(data, len-1);
    int rslt = write(fd, data, len);
    if(rslt < 0)
    {
        printf("send_cread_cmd(), write error\n");
        return;
    }
    printf("\n'cread', sent bytes: ");
    show_char_vect_as_hex(data, len);
}

void read_and_show_resp(int fd)
{
    //read first 4 bytes (message header)
    int transmitted = -1;
    int headerSize = 3;
    char header[headerSize];
    memset(header, 0, headerSize);
    transmitted = read(fd, header, headerSize);
    if(transmitted < 0)
    {
        printf("read_and_show_resp(), read header error\n");
        return;
    }
    printf("\nheader: ");
    show_char_vect_as_hex(header, transmitted);
    //calculate message body size and read it
    int dataSize = ((unsigned int)header[1]) * 256 + (unsigned int) header[2] + 1; //plus 1 byte for LRC
    char data[dataSize];
    memset(data, 0, dataSize);
    transmitted = read(fd, data, dataSize);
    if(transmitted < 0)
    {
        printf("read_and_show_resp(), read message body error\n");
        return;
    }
    printf("\nbody: ");
    show_char_vect_as_hex(data, transmitted);
}

void show_help()
{
    printf("program {serial port device} {command name} [timeout in seconds] \n");
    printf("command name : fread, bread, cread, getver, init, pres \n");   
    printf("usage example: \n");
    printf("./program /dev/ttyS0 fread 2.5 \n");
}

void clear_buf(int fd)
{
    set_interface_attribs (fd, B9600, 0, 0.1);  // set speed to 9600 bps, 8n1 (no parity)
    set_blocking (fd, 0, 0.1);                // set no blocking
    int bufSize = 1024;
    char buf[bufSize];
    read(fd, buf, bufSize);
}

int main(int argc, char *argv[])
{
    float timeoutSec = 2.5;

    if(argc < 2)
    {
        printf("serial port name was omitted\n");
        show_help();
        return 0;
    }   
    if(argc < 3)
    {
        printf("command name was omitted\n");
        show_help();
        return 0;
    }   
   
    char *portname = argv[1];   // like "/dev/ttyS0"
    char *cmdStr = argv[2]; // "read", "init", "pres"
   
    if(argc >= 4) //if there is timeout in command line - set it
    {
        timeoutSec = atof(argv[3]);
    }

    int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0)
    {
        printf("error opening %s, retval %d\n", portname, fd);
        return -1;
    }
   
    clear_buf(fd);

    set_interface_attribs (fd, B9600, 0, timeoutSec);  // set speed to 9600 bps, 8n1 (no parity)
    set_blocking (fd, 0, timeoutSec);                // set no blocking
       
    if(strcmp("init", cmdStr) == 0)
    {
        send_init_cmd(fd);
    }
    if(strcmp("getver", cmdStr) == 0)
    {
        send_getver_cmd(fd);
    }
    if(strcmp("pres", cmdStr) == 0)
    {
        send_report_the_presence_and_pos_cmd(fd);
    }
    if(strcmp("fread", cmdStr) == 0)
    {
        send_fread_cmd(fd);
    }
    if(strcmp("bread", cmdStr) == 0)
    {
        send_bread_cmd(fd);
    }
    if(strcmp("cread", cmdStr) == 0)
    {
        send_cread_cmd(fd);
    }
   
    read_and_show_resp(fd);

    printf("\n");
   
    close(fd);

    return 0;
}

//The values for speed are B115200, B230400, B9600, B19200, B38400, B57600, B1200

пятница, 19 апреля 2013 г.

Работа в Linux с последовательным портом.

Для работы с последовательным портом удобнее всего использовать библиотеки типа boost, но только в том случае, если писать на C++, который на данный момент менее популярен, чем старый добрый C. Еще в преимущества можно добавить тот факт, что понятность кода, использующего синтаксис, созданный пару дестяков лет назад, заметно выше, чем всякие биндинги, плейсхолдеры и модели событийного программиования, которые густо замешаны с шаблонами в библиотеке boost. Простейший пример открытия, чтения и записи см. ниже.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <string.h>
 
int openSerialPort(const char * devName)
{
    //read and write in sync mode
    int hSerial = open(devName, O_RDWR | O_NOCTTY | O_SYNC);
    if(hSerial < 0)
        printf("open device \"%s\" error: %d\n", devName, hSerial);
 
    struct termios port_settings;
    bzero(&port_settings, sizeof(port_settings));
    //
    port_settings.c_cflag |= CREAD;
    port_settings.c_cflag |= CLOCAL;
    //
    port_settings.c_cc[VMIN] = 100;
    //delay, x0.2 sec
    port_settings.c_cc[VTIME] = 2;
    //flush serial port
    int fres = tcflush(hSerial, TCIFLUSH);
    if (fres < 0)
    {
        printf("tcflush() error %d\n", fres);
    }
    //set baud rate
    fres = cfsetispeed(&port_settings, B9600);
    if (fres < 0)
    {
        printf("cfsetispeed() error %d\n", fres);
    }
    fres = cfsetospeed(&port_settings, B9600);
    if (fres < 0)
    {
        printf("cfsetospeed() error %d\n", fres);
    }
    //set character size: 8.
    port_settings.c_cflag &= ~CSIZE;
    port_settings.c_cflag |= CS8;
    //set flow cotrol: none
    port_settings.c_cflag &= ~(CRTSCTS);
    //set parity: none
    port_settings.c_cflag &= ~(PARENB);
    port_settings.c_iflag |= IGNPAR;
    //set stop bits: 1
    port_settings.c_cflag &= ~(CSTOPB);
 
 
    //apply port settings
    fres = tcsetattr(hSerial, TCSANOW, &port_settings);
    if (fres < 0)
    {
        printf("tcsetattr() error %d\n", fres);
    }
    return hSerial;
}
 
int main(int argc, char* argv[])
{
    if(argc < 2) 
    {
        printf("One parameter is omitted\n");
        return -1;
    }
    const char * devName = argv[1]; // "/dev/ttyS0"
    int hSerial = openSerialPort(devName);  
    if(hSerial < 0) 
    {
        return hSerial;
    }        
 
    const char sendVect[5] = {0x01, 0x02, 0x03, 0x04, 0x05};
 
    int byteCount = write(hSerial, sendVect, sizeof(sendVect));
    if (byteCount < 0)
    {
        printf("write error %d\n", byteCount);
    }
 
    char buf[250];
    bzero(buf, sizeof(buf));
 
    byteCount = read(hSerial, buf, sizeof(buf));
    if (byteCount < 0)
    {
        printf("read error %d\n", byteCount);
    }
    else 
    {
        printf("response: \"%s\" \n", buf);
    }     
 
    close(hSerial);
 
    return 0;
}


Как использовать VMIN и VTIME читать здесь:
http://www.unixwiz.net/techtips/termios-vmin-vtime.html

Остальное должно быть ясно из комментариев к коду.