Good afternoon,
I have a peripheral device which communicates over usb over virtual serial port. Everything works well under Windows with generic ACM serial driver, for example with: https://www.kernel.org/doc/Documentation/usb/linux-cdc-acm.inf
Under Linux, it uses CDC ACM drivers. Everything in sys logs seems to work ok, but communication is behaving strangely. When I connect the device, about 10 bytes at the begining of communication are lost. Next, just each second command is received ok.
My questions are:
1) Communication protocol of this device doesn't use ASCII, it is binary (it can randomly contain control characters etc...). Should I use stty for configuring just speed, data bits, stop bits and parity, or something more is necessary to be set up for binary communication? (To ignore control bits in kernel and transmit every byte - raw data.)
2) Any idea, how to test if linux ACM drivers works properly, or which another drivers should i try for my CDC ACM device?
Thanks for any idea!
Linux will often mangle things like line-ending characters (0x0A and 0x0D) when you try to send them over a serial port, which can cause issues if they are actually binary data and not intended as line-ending characters.
Here is a snippet from Pololu that shows how to configure your serial port correctly and then send and receive a few bytes. Pay attention to the part that calls tcsetattr in particular.
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#ifdef _WIN32
#define O_NOCTTY 0
#else
#include <termios.h>
#endif
// Gets the position of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
int maestroGetPosition(int fd, unsigned char channel)
{
unsigned char command[] = {0x90, channel};
if(write(fd, command, sizeof(command)) == -1)
{
perror("error writing");
return -1;
}
unsigned char response[2];
if(read(fd,response,2) != 2)
{
perror("error reading");
return -1;
}
return response[0] + 256*response[1];
}
// Sets the target of a Maestro channel.
// See the "Serial Servo Commands" section of the user's guide.
// The units of 'target' are quarter-microseconds.
int maestroSetTarget(int fd, unsigned char channel, unsigned short target)
{
unsigned char command[] = {0x84, channel, target & 0x7F, target >> 7 & 0x7F};
if (write(fd, command, sizeof(command)) == -1)
{
perror("error writing");
return -1;
}
return 0;
}
int main()
{
const char * device = "/dev/ttyACM0"; // Linux
int fd = open(device, O_RDWR | O_NOCTTY);
if (fd == -1)
{
perror(device);
return 1;
}
#ifdef _WIN32
_setmode(fd, _O_BINARY);
#else
struct termios options;
tcgetattr(fd, &options);
options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF);
options.c_oflag &= ~(ONLCR | OCRNL);
options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tcsetattr(fd, TCSANOW, &options);
#endif
int position = maestroGetPosition(fd, 0);
printf("Current position is %d.\n", position);
int target = (position < 6000) ? 7000 : 5000;
printf("Setting target to %d (%d us).\n", target, target/4);
maestroSetTarget(fd, 0, target);
close(fd);
return 0;
}
You might be able to do the same thing with the stty command line utility.
Related
I am trying to read from a UART device in a Linux environment using a C program, but I experience different results respect to communicating to the UART using screen.
The C code I use to test the UART communication is the following:
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <getopt.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <signal.h>
#include <ctype.h>
bool loop;
void sigHandler(int32_t sig)
{
if(sig == SIGINT)
{
printf("Catched SIGINT");
loop = false;
}
}
int main(int argc, char *argv[])
{
char *devname = argv[1];
int fd = -1;
int nread = -1;
int nwrite = -1;
int ret;
struct termios t_new = {0};
struct termios t_old = {0};
signal(SIGINT, sigHandler);
fd = open(devname, O_RDWR | O_NOCTTY |O_NONBLOCK);
if(fd > 0)
{
printf("TTY open ! Configuring TTY");
}
else
{
fd = -1;
return 1;
}
ret = tcgetattr(fd, &t_old);
if(ret < 0)
{
perror("tcgetattr ");
close(fd);
fd = -1;
return 1;
}
t_new = t_old;
t_new.c_cflag = (B9600 | CS8 | CREAD );
t_new.c_oflag = 0;
t_new.c_iflag = 0;
t_new.c_lflag = 0;
ret = tcsetattr(fd, TCSANOW, &t_new);
loop = true;
while(loop)
{
char s[] = "at+gmi=?\r\n";
nwrite = write(fd, s, strlen(s));
if(nwrite == strlen(s))
{
fd_set rfd;
struct timeval tm = {.tv_sec = 0, .tv_usec = 500000};
FD_ZERO(&rfd);
FD_SET(fd, &rfd);
char buffer[64] = {0};
if(select(fd + 1, &rfd, NULL, NULL, &tm) > 0)
nread = read(fd, buffer, sizeof(buffer));
if(nread > 0)
printf("Reply is: %s\n", buffer);
}
usleep(500000);
}
}
But when I read the reply, it always includes the string I have sent.
I don't experience this problem using screen.
What is the best way to read from an UART in C using Linux ?
Could the multiplexed way (using select) causing the problems ?
EDIT
For completeness, the output is:
Reply is: at+gmi=?
OK
Also, sometimes I don't read anything.
But when I read the reply, it always includes the string I have sent.
Since your termios configuration obliterated the local echo attributes and you're sending an AT modem command, you should try sending an ATE0 command to disable echoing by the modem.
I don't experience this problem using screen.
This observation confirms that the connected modem has its echoing enabled.
The AT command is echoed (by the modem) as you type, but you don't object to this received data in this situation (because you want to see what you type).
If the modem did not have echoing enabled, then you would be complaining that what you type in screen was not visible.
IOW echo is desired when using a terminal emulator program (such as screen), but echoing needs to be disabled when sending data by a program.
What is the best way to read from an UART in C using Linux ?
(Technically you are not reading from a "UART", but rather from a serial terminal that fully buffers all input and output.)
Code that conforms to POSIX standard as described in Setting Terminal Modes Properly
and Serial Programming Guide for POSIX Operating Systems would be far better that what you have now.
I'm surprised that it works at all (e.g. CREAD is not enabled).
Could the multiplexed way (using select) causing the problems ?
Not the echo "problem".
Your program does not do anything that requires using select() and nonblocking mode.
Also, sometimes I don't read anything.
When you write code that is not POSIX compliant, you should not expect reliable program behavior.
I borrowed much of the following code from the Linux Serial HOWTO pages to create my program to read characters endlessly from the serial port. I'm using a USB to serial port adapter (made by micro innovations) so I can get a serial port. Linux recognizes the device as /dev/ttyUSB0.
When I use the device in other native linux programs (such as the output dump program), the adapter works flawlessly and data continuously comes in. However, with my program, the data comes in for roughly 10 seconds, then I receive no more data, yet I know the hardware that the adapter is connected to has more data to process.
Is there something in this code I can fix to allow me to receive unlimited characters as they arrive without me having to restart the program to receive more characters?
I still want to make the mainline code asynchronous to the serial routine as I will be adding more code later on.
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>
#define BAUDRATE B57600
#define MODEMDEVICE "/dev/ttyUSB0" /* My USB to serial converter */
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1
void signal_handler_IO (); /* definition of signal handler */
int wait_flag=TRUE; /* TRUE while no signal received */
int main(){
int fd,res;
struct termios oldtio,newtio;
struct sigaction saio; /* definition of signal action */
char buf[255];
/* open the device */
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY);
if (fd <0) {perror(MODEMDEVICE); return -1; }
/* install the signal handler before making the device asynchronous */
saio.sa_handler = signal_handler_IO;
saio.sa_flags=0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
/* allow the process to receive SIGIO */
fcntl(fd, F_SETOWN, getpid());
/* Make the file descriptor asynchronous */
fcntl(fd, F_SETFL, FASYNC);
tcgetattr(fd,&oldtio); /* save current port settings */
/* set new port settings for canonical input processing */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
newtio.c_cc[VMIN]=1;
newtio.c_cc[VTIME]=0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
/* loop while waiting for input. */
while (1) {
usleep(100000);
/* after receiving SIGIO, wait_flag = FALSE,
input is available and can be read */
if (wait_flag==FALSE) {
res=0;
/* Read up to 11 bytes which is remote packet length if file handle is valid */
if (fd){
res = read(fd,buf,11);
}
/* If there's at least 2 bytes (character plus ending null) then process it */
if (res > 1){
int n;
/* Print each character as 2-digit hex even if value is 0. */
for (n=0;n<(res-1);n++){
printf("%2X ",(unsigned char)buf[n]);
}
/* Print total number of characters received */
printf(" = %d\n",res);
}
wait_flag = TRUE; /* wait for new input */
}
/* Return to start of endless loop */
}
return 0;
}
/***************************************************************************
* signal handler. sets wait_flag to FALSE, to indicate above loop that *
* characters have been received. *
***************************************************************************/
void signal_handler_IO ()
{
wait_flag = FALSE;
}
Classic race condition:
The signal_handler_IO() may be called before your code sets wait_flag = TRUE; e.g. during those printf() calls.
At this point your program will simply stop.
A remedy could be setting the wait_flag immediately after the if - but I would recommend looking into linux poll() call for async I/O.
I'm trying to detect when a gpio pin goes from low to high and am having trouble. From what I've read I should be able to configure the pin as input this way:
# echo in > /sys/class/gpio/gpio51/direction
# echo rising > /sys/class/gpio/gpio51/edge
Next I try running a c program that waits for the rising edge using select. The code looks like this (notice I commented out an attempt to just read the file, since reading is supposed to block if you don't set O_NONBLOCK):
#include<stdio.h>
#include<fcntl.h>
#include <sys/select.h>
int main(void) {
int fd = open("/sys/class/gpio/gpio51/value", O_RDONLY & ~O_NONBLOCK);
//int fd = open("/sys/class/gpio/gpio51/value", O_RDONLY | O_NONBLOCK);
//unsigned char buf[2];
//int x = read(fd, &buf, 2);
//printf("%d %d: %s\n", fd, x, buf);
fd_set exceptfds;
int res;
FD_ZERO(&exceptfds);
FD_SET(fd, &exceptfds);
//printf("waiting for %d: %s\n", exceptfds);
res = select(fd+1,
NULL, // readfds - not needed
NULL, // writefds - not needed
&exceptfds,
NULL); // timeout (never)
if (res > 0 && FD_ISSET(fd, &exceptfds)) {
printf("finished\n");
}
return 0;
}
The program exits immediately no matter what the state of the pin (high or low). Can anyone see something wrong with the way I'm doing this?
PS. I have a python library that uses poll() to do just this, and the python works as expected. I pull the pin low, call the python, it blocks, pull the pin high and the code continues. So I don't think it is a problem with the linux gpio driver.
https://bitbucket.org/cswank/gadgets/src/590504d4a30b8a83143e06c44b1c32207339c097/gadgets/io/poller.py?at=master
I figured it out. You must read from the file descriptor before the select call returns. Here is an example that works:
#include<stdio.h>
#include<fcntl.h>
#include <sys/select.h>
#define MAX_BUF 64
int main(void) {
int len;
char *buf[MAX_BUF];
int fd = open("/sys/class/gpio/gpio51/value", O_RDONLY);
fd_set exceptfds;
int res;
FD_ZERO(&exceptfds);
FD_SET(fd, &exceptfds);
len = read(fd, buf, MAX_BUF); //won't work without this read.
res = select(fd+1,
NULL, // readfds - not needed
NULL, // writefds - not needed
&exceptfds,
NULL); // timeout (never)
if (res > 0 && FD_ISSET(fd, &exceptfds)) {
printf("finished\n");
}
return 0;
}
I'm using Linux Ubuntu and try to get serial communication to work.
Ok, what am I using...
I use a Raspberry Pi and connected it via USB/Serial-Adapter with an Inserial Measurement Unit (mulifunction sensor).
Just to clarify what i'm trying to do:
Establishing a connection betwenn Raspberry Pi and IMU.
To run IMU there are given steps i have to follow.
Power-on Sequence:
(a) power-on.
(b) Wait 800ms.
(c) Wait until NOT_READY bit goes to 0. NOT_READY is GLOB_CMD[3Eh]'s bit[10].
TXdata={0x3E,0x00,0x0d}. /* GLOB_CMD read command */
TXdata={0x3E,MSByte,LSByte,0x0d}. /* get response */
Confirm NOT_READY bit.
When NOT_READY becomes 0, it ends. Otherwise , please repeat (c).
(d) Confirm HARD_ERR bits. HARD_ERR is DIAG_STAT[3Ch]'s bit[6:5].
TXdata={0x3C,0x00,0x0d}. /* DIAG_STAT read command */
TXdata={0x3C,MSByte,LSByte,0x0d}. /* get response */
Confirm HARD_ERR is 00.
If HARD_ERR is 00, the IMU is OK. Otherwise, the IMU is faulty.
Register read and write:
[Read Example]
To read a 16bit-data from a register(addr=0x38).
TXdata={0x38,0x00,0x0d}. /* command */
RXdata={0x38,0x04,0x04,0x0d} /* response */
0x04 in 2nd byte of RXdata is Configuration mode.
0x04 in 3rd byte of RXdata is TAP=16.
Please note that read data unit is 16bit, and Most Significant Byte first.
-------------------------------------------------------------
[Write Example]
To write a 8bit-data into a register(addr=0x39).
TXdata={0xB9,0x01,0x0d}. /* command */
RXdata= w/o response
By sending this command, the IMU moves to Sampling mode.
Please note that write data unit is 8bit.
On my Linux Ubuntu there is a ttyUSB0 device given after connecting IMU.
So i tried to set Baudrate, Databits, Stopbits, Parity, flowcontrol.
First via stty-command, later with a simple c++-code.
I'm using this c++-code:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <cstdlib>
void SleepMs(int);
int main(int argc, char** argv)
{
int fd; // port file descriptor
char port[20] = "/dev/ttyUSB0"; // port to connect to
fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); // connect to port
if(fd == -1)
{
printf("Error while opening the port.\n");
return 1;
}
printf("Port opened successfully.\n");
fcntl(fd, F_SETOWN, getpid());
struct termios settings;
tcgetattr(fd, &settings);
settings.c_cflag &= ~(CBAUD | CSIZE | CREAD);
settings.c_cflag |= B230400;
settings.c_cflag |= CS8;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &settings); // apply the settings
int len = 7;
unsigned char bytes[len];
bytes[0] = 0x3E;
bytes[1] = 0x00;
bytes[2] = 0x0D;
bytes[3] = 0x3E;
bytes[4] = 0x00;
bytes[5] = 0x00;
bytes[6] = 0x0D;
int wr = write(fd, bytes, len);
unsigned char answer[32];
SleepMs(350);
int rd = -1;
int i;
while (rd==-1)
{
if(wr != 7)
{
printf("Error while sending!\n");
}
for(i=0; i<len; i++)
{
printf("%X sent\n", (unsigned int)bytes[i]);
SleepMs(350);
}
printf("\n");
printf("%d bytes sent.\n", wr);
printf("\n");
printf("Trying to read...\n");
printf("\n");
rd = read(fd, answer, 32);
SleepMs(350);
printf("%d\n", rd);
for(i=0; i<rd; i++)
{
printf("%X ", (unsigned int)answer[i]);
}
printf("\n\n");
}
close(fd);
return 0;
}
void SleepMs(int ms) {
usleep(ms*1000); //convert to microseconds
return;
}
If i start program, it tells me "Port open successfully" and writes given bytes in program.
But it receives no data.
I transmit 0x3E 0x00 0x0D to activate GLOB_CMD read command.
I have to confirm "Not Ready"-Bit is 0 but i dont get an answer with my serial connection.
So this is where i need your help, maybe someone got a hint for me.
How can i communicate with my IMU or via serialcommunication properly with Linux?
int wr = write(fd, bytes, len);
Your bytes array only needs to be 3 bytes long, so len should be 3. (The 0x3e is what the IMU should respond with, so it shouldn't be in your program except when checking the response.) When you read, you should only read the expected size of the answer (len=4). You don't need to sleep after writing, and probably not after reading.
I have the following C program:
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
int main()
{
int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd < 0)
{
perror("Could not open device");
}
printf("Device opened\n");
struct termios options;
tcgetattr(fd, &options);
cfmakeraw(&options);
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
tcsetattr(fd, TCSANOW, &options);
char txpacket[] = {0x23, 0x06, 0x00, 0x00, 0xdd, 0xf9};
ssize_t written = write(fd, txpacket, sizeof(txpacket));
printf("Written %d bytes\n", written);
printf("Starting to wait for target to respond\n");
while(1)
{
fd_set readset;
FD_ZERO(&readset);
FD_SET(fd, &readset);
int nCount = select(fd + 1, &readset, NULL, NULL, NULL);
if(nCount > 0)
{
if(FD_ISSET(fd, &readset))
{
int i;
char buffer[128];
ssize_t bytesread = read(fd, buffer, sizeof(buffer));
printf("Received %d bytes\n", bytesread);
for(i = 0; i < bytesread; i++)
{
printf(" %02x", buffer[i]);
}
}
}
}
}
This program opens the serial device /dev/ttyS0, writes a sequence of data to it and starts listening for a response. I get the following output:
Device opened
Written 6 bytes
Starting to wait for target to respond
Received 0 bytes
Received 0 bytes
Received 0 bytes
Received 0 bytes
Received 0 bytes
Received 0 bytes
...
And the application consumes 100% CPU. I'm not able to receive any data, even though the target hardware actually transmits it.
What is wrong?
read() returning 0 indicates the end-of-file condition. You should check for that and break out of the loop if it occurs.
As to what's causing that - end-of-file on a serial port indicates it has detected a hangup, meaning that the DCD line has been dropped.
You can set the CLOCAL flag in options.c_cflag to ignore the modem control lines, if your device doesn't set them properly.
You should try without the O_NONBLOCK flag. in raw mode, if the settings of c_cc[VMIN] and c_cc[VTIME] is 0, the serial port behave like this (according to man cfmakeraw) :
If data is available, read returns
immediately, with the lesser of the
number of bytes available, or the
number of bytes requested. If no data
is available, read returns 0
So what you should try is :
options->c_cc[VMIN]=1;