Linux串口编程短信篇(一) ——— 串口通信初始化

一. 前言

 前面关于串口通信,串口编程的文章也总结了许多,之前的有一篇文章,写的是编程实现busybox microcom 工具,将功能模块一一分开,其实有一点多此一举了,但是作为我学习Linux下串口编程的记录,我还是决定不修改这篇博客,但是,既然作为串口编程的c文件,应该将所有用于串口通信的功能模块归纳进一个c文件 comport.c ,头文件均放置在 comport.h 中,然后写一个main函数来进行调用,这样,才是一个更具有整体性和完整性的调试工具,后续也修改了不能发送短信的bug,如果需要源码的可以私聊.

二. 短信篇介绍

作为短信篇的开篇,有必要说明一下我想实现的是什么,需要掌握哪些知识…

无论是busybox 中的microcom还是我自己写出来的comport工具,都有一个头大问题,那就是,发送中文短信时,需要进行PDU的编码,我还记得刚拿到4g模块时,就遇到了很多问题,在ATD终于拨通我的手机那一瞬间,眼泪掉下来,紧接着又百度了AT发送短信的命令,看到发送短信还分为TEXT和PDU简直一脸懵逼,在尝试发送TEXT格式短信成功后,兴奋又准备去尝试PDU格式的短信,可是,当我的鼠标下移后,惊恐的发现,这是一个New World…

终于搞了半天才弄明白怎么发送PDU格式的短信,可是,这仅仅停留在别人全部把现成的编码方式告诉我,而且在UTF-8Unicode 时,用的还是网页在线转码(有点丢人),在这么一个较为繁琐的编码和计算之下,难免会遇到很多错误,下面是我遇到的一些错误:

  1. 再输入PDU编码前一步是输入AT+CMGS=len,len是PDU编码时的一个中间产物,短信发送时,只有输入的PDU编码严格满足这个值,短信才可以发送.
  2. 网上找了很多方法来实现 UTF-8Unicode ,有使用wchar_t类型借助wcseln函数实现的,有使用iconv函数实现的,但是最终我还是选择搞懂他们之间的转换规则,自己写代码来实现
  3. 编码时可能会漏东西,检测出错误还好,如果恰巧AT+CMGS的值是正确的,那接收到的短信就会是一些奇奇怪怪的文字
  4. 换卡以后,短信中心号码也变了,又要重新按照步骤处理短信中心号
  5. 电话号码也是需要处理的

总结,每发一次短信,都需要折腾很久,而且能一次成功的几率甚小,于是,我就打算写一个程序,只需要输入收件人号码,短信内容就可以发送短信了,就像手机一样,第一个版本需要手动输入短信中心号,不过后来优化了,下面就来看看我是怎么实现的吧~

三. 串口初始化

关于串口的初始化,我在下面这篇博客做了非常详细的讲解,这里,就总体的梳理梳理
传送门

3.1 流程图

打开串口
在这里插入图片描述
因为函数都是自己编写,所以可以统一返回值,我的函数如果不是特殊要求(后面的文章会提到),成功返回0,失败返回负数。

串口初始化
在这里插入图片描述
其他的就不过多赘述了,这些都是为发送短信而铺的基石,文章末尾的博客详细总结了这方面内容。

3.2 代码

串口的初始化,服务于后面的程序,只有先将串口的准备工作做完,才可以进行后面的工作

comport.h

/********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  comport.h
 *    Description:  This head file of comport.c
 *
 *        Version:  2.0.0(11/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      ChangeLog:  1, Release initial version on "11/07/20 16:09:33"
 *                 
 ********************************************************************************/
#ifndef  _COMPORT_H_
#define  _COMPORT_H_

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

#define SERIALNAME_LEN 64

typedef struct _st_ComportAttr {

    int               fd;        //串口文件描述符
    int               BaudRate;  //波特率
    int               DataBits;  //数据位
    char              Parity;    //奇偶校验位
    int               StopBits;  //停止位
    int               mSend_Len; //单次最大发送长度
    char              SerialName[SERIALNAME_LEN];  //串口名称
    struct termios    OldTermios;  //串口的原始属性
}ComportAttr;


/* Use the serial port name(comport->SerialName) to open and assign the returned file descriptor to comport->fd */
int comport_open(ComportAttr *comport);

/* Set the serial port's baud rate, data bits, parity and other attributes, and set the related Peugeot bits for serial communication */
int comport_init(ComportAttr *comport);

/* Send the serial port command in the parameter and can handle the command with too long length */
int comport_send(ComportAttr *comport,char *sbuf,int sbuf_len);

/* Receive serial data, you can use select multiplexing to specify the time of read according to the value of timeout */
int comport_recv(ComportAttr *comport,char *rbuf,int rbuf_len,int timeout);

/* Close the serial port, clear the serial port buffer, and release the memory */
int comport_close(ComportAttr *comport);


#endif   /* ----- #ifndef _COMPORT_H_  ----- */


comport.c

/*********************************************************************************
 *      Copyright:  (C) 2020 LuXiaoyang<920916829@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  The file contains the functions of opening, closing, 
 *                  initializing, and communicating with the serial port.
 *                 
 *        Version:  1.0.0(11/07/20)
 *         Author:  LuXiaoyang <920916829@qq.com>
 *      ChangeLog:  1, Release initial version on "11/07/20 15:07:25"
 *                 
 ********************************************************************************/
#include "comport.h"

int comport_open(ComportAttr *comport)
{
    int                retval = -1;

    if(NULL == comport)
    {
        printf("%s,Invalid parameter\n",__func__);
        return retval;
    }

    /* 
     * O_NOCTTY表示打开的是一个终端设备,程序不会成为该
     * 端口的控制终端,O_NONBLOCK使得read处于非阻塞模式 
     *
     * */
    comport->fd = open(comport->SerialName,O_RDWR | O_NOCTTY | O_NONBLOCK);
    if(comport->fd < 0)
    {
        printf("%s,Open %s failed:%s\n",__func__,comport->SerialName,strerror(errno));
        return -1;
    }

    /* 检查串口是否处于阻塞态 */
    if((retval = fcntl(comport->fd,F_SETFL,0)) < 0)
    {
        printf("%s,Fcntl check faile.\n",__func__);
        return -2;
    }

    printf("Starting serial communication process ");

    /* 检查该文件描述符是否对应了终端设备 */
    if(0 == isatty(comport->fd))
    {
        printf("%s:[%d] is not a Terminal equipment.\n",comport->SerialName,comport->fd);
        return -3;
    }

    printf("Open %s successfully.\n",comport->SerialName);

    return 0;
}

int comport_close(ComportAttr *comport)
{
    if(tcflush(comport->fd,TCIOFLUSH))  //清零用于串口通信的缓冲区
    {
        printf("%s,Tcflush faile:%s\n",__func__,strerror(errno));
        return -1;
    }

    /* 将串口设置为原有属性 */
    if(tcsetattr(comport->fd,TCSANOW,&(comport->OldTermios)))
    {
        printf("%s,Set old options failed:%s\n",__func__,strerror(errno));
        return -2;
    }

    close(comport->fd);

    free(comport);

    return 0;
}

/* 初始化串口属性,设置串口用于通信 */
int comport_init(ComportAttr *comport)
{
    char                  baudrate[32] = {0};
    struct termios        NewTermios;


    memset(&NewTermios,0,sizeof(struct termios));
    memset(&(comport->OldTermios),0,sizeof(struct termios));
    if(!comport)
    {
        printf("Invalid parameter.\n");
        return -1;
    }

    /* 获取串口原始属性,这部分是备份 */
    if(tcgetattr(comport->fd,&(comport->OldTermios)))
    {
        printf("%s,Get termios to OldTermios failure:%s\n",__func__,strerror(errno));
        return -2;
    }

    /* 获取串口原始属性,这部分用于设置新属性 */
    if(tcgetattr(comport->fd,&NewTermios))
    {
        printf("%s,Get termios to NewTermios failure:%s\n",__func__,strerror(errno));
        return -3;
    }


    /* 修改控制模式,保证程序不会占用串口 */
    NewTermios.c_cflag |= CLOCAL;

/*  For example:
 *   
 *      c_cflag:   0 0 0 0 1 0 0 0
 *      CLOCAL:  | 0 0 0 1 0 0 0 0
 *              --------------------
 *                 0 0 0 1 1 0 0 0
 *                
 *  Finally:
 *
 *     c_flag = 0 0 0 1 1 0 0 0;
 *
 * */


    /* 启动接收器,能够从串口中读取输入数据 */
    NewTermios.c_cflag |= CREAD;


    /*  CSIZE字符大小掩码,将与设置databits相关的标致位置零 */
    NewTermios.c_cflag &= ~CSIZE;


/* For example:
 *
 *      CSIZE = 0 1 1 1 0 0 0 0 ---> ~CSIZE = 1 0 0 0 1 1 1 1
 *
 *      c_cflag:    0 0 1 0 1 1 0 0
 *      ~CSIZE:  &  1 0 0 0 1 1 1 1     
 *              -----------------------
 *                  0 0 0 0 1 1 0 0
 *
 * Finally:
 *
 *     c_cflag = 0 0 0 0 1 1 0 0
 *
 * */

    NewTermios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* 
 * ICANON: 标准模式
 * ECHO:   回显所输入的字符
 * ECHOE:  如果同时设置了ICANON标志,ERASE字符删除前一个所输入的字符,WERASE删除前一个输入的单词
 * ISIG:   当接收到INTR/QUIT/SUSP/DSUSP字符,生成一个相应的信号
 *
 * */


    NewTermios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
/* 
 * BRKINT: BREAK将会丢弃输入和输出队列中的数据(flush),并且如果终端为前台进程组的控制终端,则BREAK将会产生一个SIGINT信号发送到这个前台进程组
 * ICRNL:  将输入中的CR转换为NL
 * INPCK:  允许奇偶校验
 * ISTRIP: 剥离第8个bits
 * IXON:   允许输出端的XON/XOF流控
 *
 * */

    /* OPOST: 表示处理后输出,按照原始数据输出 */
    NewTermios.c_oflag &= ~(OPOST);
    
    if(comport->BaudRate)
    {
        sprintf(baudrate,"B%d",comport->BaudRate);
        cfsetispeed(&NewTermios,(int)baudrate); //设置输入输出波特率
        cfsetospeed(&NewTermios,(int)baudrate);
    }
    else
    {
        cfsetispeed(&NewTermios,B115200);
        cfsetospeed(&NewTermios,B115200);
    }

    /* 设置数据位 */
    switch(comport->DataBits)
    {
        case '5':
            NewTermios.c_cflag |= CS5;
            break;

        case '6':
            NewTermios.c_cflag |= CS6;
            break;

        case '7':
            NewTermios.c_cflag |= CS7;
            break;

        case '8':
            NewTermios.c_cflag |= CS8;
            break;

        default:
            NewTermios.c_cflag |= CS8;  //默认数据位为8
            break;
    }

    /* 设置校验方式 */
    switch(comport->Parity)
    {
        /* 无校验 */
        case 'n':
        case 'N':
            NewTermios.c_cflag &= ~PARENB;
            NewTermios.c_iflag &= ~INPCK;
            break;

        /* 偶校验 */
        case 'e':
        case 'E':
            NewTermios.c_cflag |= PARENB;
            NewTermios.c_cflag &= ~PARODD;
            NewTermios.c_iflag |= INPCK;
            break;

        /* 奇校验 */
        case 'o':
        case 'O':
            NewTermios.c_cflag |= PARENB;
            NewTermios.c_cflag |= PARODD;
            NewTermios.c_iflag |= INPCK;

        /* 设置为空格 */
        case 's':
        case 'S':
            NewTermios.c_cflag &= ~PARENB;
            NewTermios.c_cflag &= ~CSTOPB;

        /* 默认无校验 */
        default:
            NewTermios.c_cflag &= ~PARENB;
            NewTermios.c_iflag &= ~INPCK;
            break;
    }

    /* 设置停止位 */
    switch(comport->StopBits)
    {
        case '1':
            NewTermios.c_cflag &= ~CSTOPB;
            break;

        case '2':
            NewTermios.c_cflag |= CSTOPB;
            break;

        default:
            NewTermios.c_cflag &= ~CSTOPB;  //默认使用1位作为停止位
            break;
    }

    NewTermios.c_cc[VTIME] = 0;  //最长等待时间
    NewTermios.c_cc[VMIN] = 2;  //最小接收字符 

    comport->mSend_Len = 128;  //若命令长度大于mSend_Len,则每次最多发送为mSend_Len

    /*  清空用于串口通信的输入输出缓存区 */
    if(tcflush(comport->fd,TCIFLUSH))
    {
        printf("%s,Failed to clear the cache:%s\n",__func__,strerror(errno));
        return -4;
    }

    /* 设置串口属性,除了查看波特率的函数,其余用于串口通信的函数成功均返回0 */
    if(tcsetattr(comport->fd,TCSANOW,&NewTermios) != 0)
    {
        printf("%s,tcsetattr failure:%s\n",__func__,strerror(errno));
        return -5;
    }

    printf("Comport Init Successfully......\n");

    return 0;

}

/* 向串口发送相关指令 */
int comport_send(ComportAttr *comport,char *sbuf,int sbuf_len)
{
    char     *ptr,*end;
    int       retval;

    if(!comport || !sbuf || sbuf_len <= 0)
    {
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }

    if(sbuf_len > comport->mSend_Len)  //指令长度实际长度大于单次发送的最大长度,则每次发送单次发送的最大长度
    {
        ptr = sbuf;
        end = sbuf + sbuf_len;

        do
        {
            if(comport->mSend_Len < (end - ptr))  //剩余长度大于单次发送的最大长度
            {
                retval = write(comport->fd,ptr,comport->mSend_Len);
                if(retval <= 0 || retval != comport->mSend_Len)
                {
                    printf("Write to com port[%d] failed:%s\n",comport->fd,strerror(errno));
                    return -2;
                }

                ptr += comport->mSend_Len;
            }
            else
            {
                retval = write(comport->fd,ptr,(end - ptr));  //剩余长度可一次性发送
                if(retval <= 0 || retval != (end - ptr))
                {
                    printf("Write to com port[%d] failed:%s\n",comport->fd,strerror(errno));
                    return -3;
                }

                ptr += (end - ptr);
            }
        }while(end > ptr);

    }

    else
    {
        retval = write(comport->fd,sbuf,sbuf_len);
        if(retval <= 0 || retval != sbuf_len)
        {
            printf("Write to com port[[%d] failed:%s\n",comport->fd,strerror(errno));
            return -4;
        }
    }

    return retval;
}

int comport_recv(ComportAttr *comport,char *rbuf,int rbuf_len,int timeout)
{
    int                   retval;
    fd_set                rset;
    struct timeval        time_out;

    if(!rbuf || rbuf_len <= 0)
    {
        printf("%s,Invalid parameter.\n",__func__);
        return -1;
    }

    if(timeout) //如果传入该参数,则调用select指定读的阻塞时间
    {
        time_out.tv_sec = (time_t)timeout;
        time_out.tv_usec = 0;

        FD_ZERO(&rset);
        FD_SET(comport->fd,&rset);

        retval = select(comport->fd + 1,&rset,NULL,NULL,&time_out);
        if(retval < 0)
        {
            printf("%s,Select failed:%s\n",__func__,strerror(errno));
            return -2;
        }

        else if(0 == retval)
        {
            printf("Time Out.\n");
            return 0;
        }

    }

    /* 延时,避免数据还未到 */
    usleep(1000);

    retval = read(comport->fd,rbuf,rbuf_len);
    if( retval <= 0)
    {
        printf("%s,Read failed:%s\n",__func__,strerror(errno));
        return -3;
    }

    return retval;

}

具体标志位的设置,函数的使用,comport_send(),comport_recv() 的实现思路,请参考下方博客,要想完成这个程序,搞懂初始化的步骤很有必要:

Linux串口编程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值