嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十八)串口编程(下)

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十八)串口编程

六. Linux串口应用编程


参考资料:

Serial Programming Guide for POSIX Operating Systems:https://digilander.libero.it/robang/rubrica/serial.htm#CONTENTS

Linux串口编程:https://www.cnblogs.com/feisky/archive/2010/05/21/1740893.html

Linux串口—struct termios结构体:https://blog.csdn.net/yemingzhu163/article/details/5897156

这个是转载,排版更好看: https://www.cnblogs.com/sky-heaven/p/9675253.html

本节课程源码在GIT仓库里:

01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\14_UART\
01_app_send_recv
02_gps


1. 串口API

1670927090958.jpg

在Linux系统中,操作设备的统一接口就是:open/ioctl/read/write。

对于UART,又在ioctl之上封装了很多函数,主要是用来设置行规程。

所以对于UART,编程的套路就是:


open

设置行规程,比如波特率、数据位、停止位、检验位、RAW模式、一有数据就返回

read/write

怎么设置行规程?行规程的参数用结构体termios来表示,可以参考Linux串口—struct termios结构体:https://blog.csdn.net/yemingzhu163/article/details/5897156

1670927100999.jpg

这些函数在名称上有一些惯例:

tc:terminal contorl
cf: control flag


下面列出一些函数:

1670927118966.jpg

函数不多,主要是需要设置好termios中的参数,这些参数很复杂,可以参考Linux串口—structtermios结构体。


2. 串口收发实验


本实验用过把串口的发送、接收引脚短接,实现自发自收:使用write函数发出字符,使用read函数读

取字符。


2.1 接线


2.1.1 IMX6ULL

1670927148165.jpg


2.2 编程


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
  struct termios newtio,oldtio;
  if ( tcgetattr( fd,&oldtio) != 0) { 
  perror("SetupSerial 1");
  return -1;
  }
  bzero( &newtio, sizeof( newtio ) );
  newtio.c_cflag |= CLOCAL | CREAD; 
  newtio.c_cflag &= ~CSIZE; 
  newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
  newtio.c_oflag  &= ~OPOST;   /*Output*/
  switch( nBits )
  {
  case 7:
  newtio.c_cflag |= CS7;
  break;
  case 8:
  newtio.c_cflag |= CS8;
  break;
  }
  switch( nEvent )
  {
  case 'O':
  newtio.c_cflag |= PARENB;
  newtio.c_cflag |= PARODD;
  newtio.c_iflag |= (INPCK | ISTRIP);
  break;
  case 'E': 
  newtio.c_iflag |= (INPCK | ISTRIP);
  newtio.c_cflag |= PARENB;
  newtio.c_cflag &= ~PARODD;
  break;
  case 'N': 
  newtio.c_cflag &= ~PARENB;
  break;
  }
  switch( nSpeed )
  {
  case 2400:
  cfsetispeed(&newtio, B2400);
  cfsetospeed(&newtio, B2400);
  break;
  case 4800:
  cfsetispeed(&newtio, B4800);
  cfsetospeed(&newtio, B4800);
  break;
  case 9600:
  cfsetispeed(&newtio, B9600);
  cfsetospeed(&newtio, B9600);
  break;
  case 115200:
  cfsetispeed(&newtio, B115200);
  cfsetospeed(&newtio, B115200);
  break;
  default:
  cfsetispeed(&newtio, B9600);
  cfsetospeed(&newtio, B9600);
  break;
  }
  if( nStop == 1 )
  newtio.c_cflag &= ~CSTOPB;
  else if ( nStop == 2 )
  newtio.c_cflag |= CSTOPB;
  newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
  newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
                          * 比如VMIN设为10表示至少读到10个数据才返回,
                          * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
                          * 假设VTIME=1,表示: 
                          *    10秒内一个数据都没有的话就返回
                          *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
                          */
  tcflush(fd,TCIFLUSH);
  if((tcsetattr(fd,TCSANOW,&newtio))!=0)
  {
  perror("com set error");
  return -1;
  }
  //printf("set done!\n");
  return 0;
}
int open_port(char *com)
{
  int fd;
  //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
  fd = open(com, O_RDWR|O_NOCTTY);
    if (-1 == fd){
  return(-1);
    }
   if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
   {
    printf("fcntl failed!\n");
    return -1;
   }
   return fd;
}
/*
 * ./serial_send_recv <dev>
 */
int main(int argc, char **argv)
{
  int fd;
  int iRet;
  char c;
  /* 1. open */
  /* 2. setup 
  * 115200,8N1
  * RAW mode
  * return data immediately
  */
  /* 3. write and read */
  if (argc != 2)
  {
  printf("Usage: \n");
  printf("%s </dev/ttySAC1 or other>\n", argv[0]);
  return -1;
  }
  fd = open_port(argv[1]);
  if (fd < 0)
  {
  printf("open %s err!\n", argv[1]);
  return -1;
  }
  iRet = set_opt(fd, 115200, 8, 'N', 1);
  if (iRet)
  {
  printf("set port err!\n");
  return -1;
  }
  printf("Enter a char: ");
  while (1)
  {
  scanf("%c", &c);
  iRet = write(fd, &c, 1);
  iRet = read(fd, &c, 1);
  if (iRet == 1)
    printf("get: %02x %c\n", c, c);
  else
    printf("can not get data\n");
  }
  return 0;
}


2.3 上机实验


2.3.1 IMX6ULL


先设置工具链:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihfexport PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linuxgnueabihf_sdk-buildroot/bin


编译、执行程序:

1. Ubuntu上
arm-buildroot-linux-gnueabihf-gcc -o serial_send_recv serial_send_recv.c
2. 板子上
/mnt/serial_send_recv /dev/ttymxc5


3. GPS模块实验


3.1 GPS简介


全球定位系统(Global Positioning System,GPS)是一种以空中卫星为基础的高精度无线电导航的定位系统,它在全球任何地方以及近地空间都能够提供准确的地理位置、车行速度及精确的时间信息。GPS主要由三大组成部分:空间部分、地面监控部分和用户设备部分。GPS系统具有高精度、全天候、用广泛等特点。

太空卫星部分由多颗卫星组成,分成多个轨道,绕行地球一周约12小时。每个卫星均持续发射载有卫星轨道数据及时间的无线电波,提供地球上的各种接收机来应用。

地面管制部分,这是为了追踪及控制太空卫星运行所设置的地面管制站,主要工作为负责修正与维护每个卫星能够正常运转的各项参数数据,以确保每个卫星都能够提供正确的讯息给使用者接收机来接收

使用者接收机(即用户设备),追踪所有的GPS卫星,并实时的计算出接收机所在位置的坐标、移动速度及时间。我们日常接触到的是用户设备部分,这里使用到的GPS模块即为用户设备接收机部分。


3.2 GPS模块硬件


GPS模块与外部控制器的通讯接口有多种方式,这里我们使用串口进行通讯,波特率为9600bps,1bit停止位,无校验位,无流控,默认每秒输出一次标准格式数据。

GPS模块外观如下图所示,通过排线与控制器进行供电和通讯。该模块为集成模块,没有相关原理图。

1670927263699.jpg


3.3 GPS模块数据格式


GPS使用多种标准数据格式,目前最通用的GNSS格式是NMEA0183格式。NMEA0183是最终定位格式,即将二进制定位格式转为统一标准定位格式,与卫星类型无关。这是一套定义接收机输出的标准信息,有几种不同的格式,每种都是独立相关的ASCII格式,逗点隔开数据流,数据流长度从30-100字符不等,通常以每秒间隔持续输出。

NVMEA0183格式主要针对民用定位导航,与专业RTCM2.3/3.0和CMR+的GNSS数据格式不同。通过NMEA0183格式,可以实现GNSS接收机与PC或PDA之间的数据交换,可以通过USB和COM口等通用数据接口进行数据传输,其兼容性高,数据传输稳定。这里我们使用串口进行是通讯,通信框图如下图所示。

1670927278760.jpg

我们使用串口接收数据,收到的数据包含:

$GPGGA(GPS定位数据)、
$GPGLL(地理定位信息)、
$GPGSA(当前卫星信息)、
$GPGSV(可见卫星状态信息)、
$GPRMC(推荐最小定位信息)、
$GPVTG(地面速度信息)。


这里我们只分析$GPGGA (Global Positioning System Fix Data)即可,它包含了GPS定位经纬度、质量

因子、HDOP、高程、参考站号等字段。其标准格式如下:

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh


$XXGGA语句各字段的含义和取值范围各字段的含义和取值范围见下表所示,XX取值有:

GPGGA:单GPS
BDGGA:单北斗
GLGGA:单GLONASS
GNGGA:多星联合定位

1670927318097.jpg

1670927327213.jpg


例子:

$GPGGA,074529.82,2429.6717,N,11804.6973,E,1,8,1.098,42.110,,,M,,*76。


3.4 编程


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
  struct termios newtio,oldtio;
  if ( tcgetattr( fd,&oldtio) != 0) { 
  perror("SetupSerial 1");
  return -1;
  }
  bzero( &newtio, sizeof( newtio ) );
  newtio.c_cflag |= CLOCAL | CREAD; 
  newtio.c_cflag &= ~CSIZE; 
  newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
  newtio.c_oflag  &= ~OPOST;   /*Output*/
  switch( nBits )
  {
  case 7:
  newtio.c_cflag |= CS7;
  break;
  case 8:
  newtio.c_cflag |= CS8;
  break;
  }
  switch( nEvent )
  {
  case 'O':
  newtio.c_cflag |= PARENB;
  newtio.c_cflag |= PARODD;
  newtio.c_iflag |= (INPCK | ISTRIP);
  break;
  case 'E': 
  newtio.c_iflag |= (INPCK | ISTRIP);
  newtio.c_cflag |= PARENB;
  newtio.c_cflag &= ~PARODD;
  break;
  case 'N': 
  newtio.c_cflag &= ~PARENB;
  break;
  }
  switch( nSpeed )
  {
  case 2400:
  cfsetispeed(&newtio, B2400);
  cfsetospeed(&newtio, B2400);
  break;
  case 4800:
  cfsetispeed(&newtio, B4800);
  cfsetospeed(&newtio, B4800);
  break;
  case 9600:
  cfsetispeed(&newtio, B9600);
  cfsetospeed(&newtio, B9600);
  break;
  case 115200:
  cfsetispeed(&newtio, B115200);
  cfsetospeed(&newtio, B115200);
  break;
  default:
  cfsetispeed(&newtio, B9600);
  cfsetospeed(&newtio, B9600);
  break;
  }
  if( nStop == 1 )
  newtio.c_cflag &= ~CSTOPB;
  else if ( nStop == 2 )
  newtio.c_cflag |= CSTOPB;
  newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
  newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
                          * 比如VMIN设为10表示至少读到10个数据才返回,
                          * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
                          * 假设VTIME=1,表示: 
                          *    10秒内一个数据都没有的话就返回
                          *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
                          */
  tcflush(fd,TCIFLUSH);
  if((tcsetattr(fd,TCSANOW,&newtio))!=0)
  {
  perror("com set error");
  return -1;
  }
  //printf("set done!\n");
  return 0;
}
int open_port(char *com)
{
  int fd;
  //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
  fd = open(com, O_RDWR|O_NOCTTY);
    if (-1 == fd){
  return(-1);
    }
   if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
   {
    printf("fcntl failed!\n");
    return -1;
   }
   return fd;
}
int read_gps_raw_data(int fd, char *buf)
{
  int i = 0;
  int iRet;
  char c;
  int start = 0;
  while (1)
  {
  iRet = read(fd, &c, 1);
  if (iRet == 1)
  {
    if (c == '$')
    start = 1;
    if (start)
    {
    buf[i++] = c;
    }
    if (c == '\n' || c == '\r')
    return 0;
  }
  else
  {
    return -1;
  }
  }
}
/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF> */
int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{
  char tmp[10];
  if (buf[0] != '$')
  return -1;
  else if (strncmp(buf+3, "GGA", 3) != 0)
  return -1;
  else if (strstr(buf, ",,,,,"))
  {
  printf("Place the GPS to open area\n");
  return -1;
  }
  else {
  //printf("raw data: %s\n", buf);
  sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);
  return 0;
  }
}
/*
 * ./serial_send_recv <dev>
 */
int main(int argc, char **argv)
{
  int fd;
  int iRet;
  char c;
  char buf[1000];
  char time[100];
  char Lat[100]; 
  char ns[100]; 
  char Lng[100]; 
  char ew[100];
  float fLat, fLng;
  /* 1. open */
  /* 2. setup 
  * 115200,8N1
  * RAW mode
  * return data immediately
  */
  /* 3. write and read */
  if (argc != 2)
  {
  printf("Usage: \n");
  printf("%s </dev/ttySAC1 or other>\n", argv[0]);
  return -1;
  }
  fd = open_port(argv[1]);
  if (fd < 0)
  {
  printf("open %s err!\n", argv[1]);
  return -1;
  }
  iRet = set_opt(fd, 9600, 8, 'N', 1);
  if (iRet)
  {
  printf("set port err!\n");
  return -1;
  }
  while (1)
  {
  /* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF>*/
  /* read line 读函数*/
  iRet = read_gps_raw_data(fd, buf);
  /* parse line 解析函数*/
  if (iRet == 0)
  {
    iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);
  }
  /* printf  */
  if (iRet == 0)
  {
    printf("Time : %s\n", time);
    printf("ns   : %s\n", ns);
    printf("ew   : %s\n", ew);
    printf("Lat  : %s\n", Lat);
    printf("Lng  : %s\n", Lng);
    /* 纬度格式: ddmm.mmmm */
    sscanf(Lat+2, "%f", &fLat);
    fLat = fLat / 60;
    fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');
    /* 经度格式: dddmm.mmmm */
    sscanf(Lng+3, "%f", &fLng);
    fLng = fLng / 60;
    fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0');
    printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);
  }
  }
  return 0;
}


3.5 接线


3.5.1 IMX6ULL

1670927376994.jpg


3.6 上机实验


3.6.1 IMX6ULL


先设置工具链:

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihfexport PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linuxgnueabihf_sdk-buildroot/bin


编译、执行程序:

1. Ubuntu上
arm-buildroot-linux-gnueabihf-gcc -o gps_read gps_read.c
2. 板子上
/mnt/gps_read /dev/ttymxc5
相关文章
|
3月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
8天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
60 13
|
1月前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
116 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
3月前
|
Shell Linux Python
python执行linux系统命令的几种方法(python3经典编程案例)
文章介绍了多种使用Python执行Linux系统命令的方法,包括使用os模块的不同函数以及subprocess模块来调用shell命令并处理其输出。
84 0
|
1月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
107 8
|
1月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
375 6
|
1月前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
86 3
|
1月前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
79 2