代码拉取完成,页面将自动刷新
/**
* @file : serial.c
* @brief : Linux下串口驱动源文件
* @author : huenrong ([email protected])
* @date : 2021-03-26 20:51:12
* @author : huenrong
*
* @copyright : Copyright (c) 2021 胡恩荣
*
*/
#include "./serial.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <pthread.h>
#include <linux/serial.h>
#include <sys/ioctl.h>
// 串口名最大长度
#define SERIAL_NAME_MAX_LEN 15
// 串口最大数量
#define SERIAL_MAX_NUM 10
// 记录串口信息
typedef struct
{
// 串口名
char serial_name[SERIAL_NAME_MAX_LEN];
// 串口文件描述符
int serial_fd;
// 串口互斥锁
pthread_mutex_t serial_mutex;
} serial_info_t;
// 记录串口信息索引
static uint8_t g_serial_info_index = 0;
// 记录串口信息
static serial_info_t g_serial_info[SERIAL_MAX_NUM];
/**
* @brief 查找指定串口的信息
* @param info : 输出参数, 查找到的串口信息
* @param serial_name: 输入参数, 待查找的串口名
* @return 成功: 0
* 失败: -1
*/
static int find_serial_info(serial_info_t *info, const char *serial_name)
{
int ret = -1;
if ((!info) || (!serial_name))
{
return -1;
}
for (uint8_t i = 0; i < g_serial_info_index; i++)
{
ret = memcmp(g_serial_info[i].serial_name,
serial_name, strlen(serial_name));
if (0 == ret)
{
memcpy(info, &g_serial_info[i], sizeof(serial_info_t));
return 0;
}
}
return -1;
}
/**
* @brief 设置串口标准波特率
* @param options : 输出参数, 串口属性
* @param baud_rate: 输入参数, 波特率
* @return 成功: 0
* 失败: -1
*/
static int set_serial_std_baud_rate(struct termios *options,
const serial_baud_rate_e baud_rate)
{
if (!options)
{
return -1;
}
// 设置输入波特率
if (0 != cfsetispeed(options, baud_rate))
{
return -1;
}
// 设置输出波特率
if (0 != cfsetospeed(options, baud_rate))
{
return -1;
}
return 0;
}
/**
* @brief 设置串口特殊波特率
* @param options : 输出参数, 串口属性
* @param fd : 输入参数, 文件描述符
* @param baud_rate: 输入参数, 波特率
* @return 成功: 0
* 失败: -1
*/
static int set_serial_special_baud_rate(struct termios *options, const int fd,
const int baud_rate)
{
struct serial_struct serial = {0};
if (!options)
{
return -1;
}
// 设置波特率为38400
if (0 != cfsetispeed(options, B38400))
{
return -1;
}
if (0 != cfsetospeed(options, B38400))
{
return -1;
}
if (0 != ioctl(fd, TIOCGSERIAL, &serial))
{
return -1;
}
// 设置标志位和系数
serial.flags = ASYNC_SPD_CUST;
serial.custom_divisor = (serial.baud_base / baud_rate);
if (0 != ioctl(fd, TIOCSSERIAL, &serial))
{
return -1;
}
return 0;
}
/**
* @brief 设置串口数据位
* @param options : 输出参数, 串口属性
* @param data_bit: 输入参数, 数据位
* @return 成功: 0
* 失败: -1
*/
static int set_serial_data_bit(struct termios *options,
const serial_data_bit_e data_bit)
{
if (!options)
{
return -1;
}
options->c_cflag &= ~CSIZE;
options->c_cflag |= data_bit;
return 0;
}
/**
* @brief 设置串口奇偶检验位
* @param options : 输出参数, 串口属性
* @param parity_bit: 输入参数, 奇偶检验位, 默认为无校验'n'或'N'
* @return 成功: 0
* 失败: -1
*/
static int set_serial_parity_bit(struct termios *options,
const serial_parity_bit_e parity_bit)
{
if (!options)
{
return -1;
}
switch (parity_bit)
{
// 无校验
case E_SERIAL_PARITY_BIT_N:
{
options->c_cflag &= ~PARENB;
options->c_iflag &= ~INPCK;
break;
}
// 奇校验
case E_SERIAL_PARITY_BIT_O:
{
options->c_cflag |= (PARODD | PARENB);
options->c_iflag |= INPCK;
break;
}
// 偶校验
case E_SERIAL_PARITY_BIT_E:
{
options->c_cflag |= PARENB;
options->c_cflag &= ~PARODD;
options->c_iflag |= INPCK;
break;
}
// 默认为无校验
default:
{
options->c_cflag &= ~PARENB;
options->c_iflag &= ~INPCK;
break;
}
}
return 0;
}
/**
* @brief 设置串口停止位
* @param options : 输出参数, 串口属性
* @param stop_bit: 输入参数, 停止位, 默认为1位停止位
* @return 成功: 0
* 失败: -1
*/
static int set_serial_stop_bit(struct termios *options,
const serial_stop_bit_e stop_bit)
{
if (!options)
{
return -1;
}
switch (stop_bit)
{
// 1位停止位
case E_SERIAL_STOP_BIT_1:
{
options->c_cflag &= ~CSTOPB;
break;
}
// 2位停止位
case E_SERIAL_STOP_BIT_2:
{
options->c_cflag |= CSTOPB;
break;
}
// 默认为1位停止位
default:
{
options->c_cflag &= ~CSTOPB;
break;
}
}
return 0;
}
/**
* @brief 打开串口
* @param serial_name : 输入参数, 串口名(例: /dev/ttyS1)
* @param std_baud_rate : 输入参数, 标准波特率(若使用特殊波特率必须填SERIAL_BAUD_RATE_SPECIAL)
* @param special_baud_rate: 输入参数, 特殊波特率(不使用填任意值)
* @param data_bit : 输入参数, 数据位
* @param parity_bit : 输入参数, 奇偶检验位, 默认为无校验'n'或'N'
* @param stop_bit : 输入参数, 停止位, 默认为1位停止位
* @return 成功: 0
* 失败: -1
*/
int open_serial(const char *serial_name,
const serial_baud_rate_e std_baud_rate,
const int special_baud_rate,
const serial_data_bit_e data_bit,
const serial_parity_bit_e parity_bit,
const serial_stop_bit_e stop_bit)
{
int fd = -1;
int ret = -1;
// 串口信息
serial_info_t info = {0};
// 串口属性
struct termios options = {0};
if (!serial_name)
{
return -1;
}
// 超过支持的串口数量, 直接返回错误
if (g_serial_info_index > SERIAL_MAX_NUM)
{
return -1;
}
// 串口已打开, 先关闭串口
ret = find_serial_info(&info, serial_name);
if (0 == ret)
{
// 关闭串口
close_serial(info.serial_name);
}
// 打开串口
fd = open(serial_name, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0)
{
return -1;
}
// 获取终端属性
if (0 != tcgetattr(fd, &options))
{
close(fd);
return -1;
}
// 设置波特率
if (E_SERIAL_BAUD_RATE_SPECIAL != std_baud_rate)
{
if (0 != set_serial_std_baud_rate(&options, std_baud_rate))
{
close(fd);
return -1;
}
}
else
{
if (0 != set_serial_special_baud_rate(&options, fd, special_baud_rate))
{
close(fd);
return -1;
}
}
// 设置数据位
if (0 != set_serial_data_bit(&options, data_bit))
{
close(fd);
return -1;
}
// 设置奇偶检验位
if (0 != set_serial_parity_bit(&options, parity_bit))
{
close(fd);
return -1;
}
// 设置停止位
if (0 != set_serial_stop_bit(&options, stop_bit))
{
close(fd);
return -1;
}
// 一般必设置的标志
options.c_cflag |= (CLOCAL | CREAD);
options.c_oflag &= ~(OPOST);
options.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
options.c_iflag &= ~(ICRNL | INLCR | IGNCR | IXON | IXOFF | IXANY);
// 清空输入输出缓冲区
if (0 != tcflush(fd, TCIOFLUSH))
{
close(fd);
return -1;
}
// 设置最小接收字符数和超时时间
// 当MIN=0, TIME=0时, 如果有数据可用, 则read最多返回所要求的字节数,
// 如果无数据可用, 则read立即返回0
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
// 设置终端属性
if (0 != tcsetattr(fd, TCSANOW, &options))
{
close(fd);
return -1;
}
// 记录串口信息
memcpy(g_serial_info[g_serial_info_index].serial_name,
serial_name, strlen(serial_name));
g_serial_info[g_serial_info_index].serial_fd = fd;
// 初始化互斥锁
pthread_mutex_init(&g_serial_info[g_serial_info_index].serial_mutex, NULL);
g_serial_info_index++;
return 0;
}
/**
* @brief 关闭串口
* @param serial_name: 输入参数, 串口名(例: /dev/ttyS1)
* @return 成功: 0
* 失败: -1
*/
int close_serial(const char *serial_name)
{
int ret = -1;
for (uint8_t i = 0; i < g_serial_info_index; i++)
{
ret = memcmp(g_serial_info[i].serial_name,
serial_name, strlen(serial_name));
// 当前串口已打开
if (0 == ret)
{
// 关闭串口
ret = close(g_serial_info[i].serial_fd);
if (ret < 0)
{
return -1;
}
// 清空串口信息
g_serial_info[i].serial_fd = -1;
memset(g_serial_info[i].serial_name, 0, SERIAL_NAME_MAX_LEN);
// 销毁互斥锁
pthread_mutex_destroy(&g_serial_info[i].serial_mutex);
// 将串口信息放到数组最前面
memcpy(&g_serial_info[i], &g_serial_info[i + 1],
(sizeof(serial_info_t) * (SERIAL_MAX_NUM - i - 1)));
(g_serial_info_index > 0) ? g_serial_info_index-- : 0;
return 0;
}
}
return 0;
}
/**
* @brief 清空串口输入缓存
* @param serial_name: 输入参数, 串口名(例: /dev/ttyS1)
* @return 成功: 0
* 失败: -1
*/
int serial_flush_input_data(const char *serial_name)
{
int ret = -1;
serial_info_t info; // 串口信息
// 串口未打开, 直接返回成功
ret = find_serial_info(&info, serial_name);
if (0 != ret)
{
return 0;
}
ret = tcflush(info.serial_fd, TCIFLUSH);
return ret;
}
/**
* @brief 清空串口输出缓存
* @param serial_name: 输入参数, 串口名(例: /dev/ttyS1)
* @return 成功: 0
* 失败: -1
*/
int serial_flush_output_data(const char *serial_name)
{
int ret = -1;
serial_info_t info; // 串口信息
// 串口未打开, 直接返回成功
ret = find_serial_info(&info, serial_name);
if (0 != ret)
{
return 0;
}
ret = tcflush(info.serial_fd, TCOFLUSH);
return ret;
}
/**
* @brief 清空串口输入和输出缓存
* @param serial_name: 输入参数, 串口名(例: /dev/ttyS1)
* @return 成功: 0
* 失败: -1
*/
int serial_flush_both_data(const char *serial_name)
{
int ret = -1;
serial_info_t info; // 串口信息
// 串口未打开, 直接返回成功
ret = find_serial_info(&info, serial_name);
if (0 != ret)
{
return 0;
}
ret = tcflush(info.serial_fd, TCIOFLUSH);
return ret;
}
/**
* @brief 串口发送数据
* @param serial_name : 输入参数, 串口名(例: /dev/ttyS1)
* @param send_data : 输入参数, 发送数据
* @param send_data_len: 输入参数, 发送数据长度
* @return 成功: 0
* 失败: -1
*/
int serial_write_data(const char *serial_name, const uint8_t *send_data,
const uint32_t send_data_len)
{
int ret = -1;
serial_info_t info; // 串口信息
// 串口未打开, 直接返回失败
ret = find_serial_info(&info, serial_name);
if (0 != ret)
{
return -1;
}
// 加锁
pthread_mutex_lock(&info.serial_mutex);
ret = write(info.serial_fd, send_data, send_data_len);
if (send_data_len != ret)
{
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return -1;
}
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return 0;
}
/**
* @brief 串口接收数据
* @param recv_data : 输出参数, 接收数据
* @param serial_name : 输入参数, 串口名(例: /dev/ttyS1)
* @param recv_data_len: 输入参数, 接收数据长度
* @param timeout: : 输入参数, 接收超时(单位:ms)
* @return 成功: 实际接收数据长度
* 失败: -1
*/
int serial_read_data(uint8_t *recv_data, const char *serial_name,
const uint32_t recv_data_len, uint32_t timeout)
{
int ret = -1;
nfds_t nfds = 1; // 指定fds数组中的项目数
struct pollfd fds[1]; // 指定要监视的文件描述符集
int total_data_len = 0; // 已读取数据长度
int remain_data_len = 0; // 未读取数据长度
serial_info_t info; // 串口信息
// 串口未打开, 直接返回失败
ret = find_serial_info(&info, serial_name);
if (0 != ret)
{
return -1;
}
memset(recv_data, 0, recv_data_len);
remain_data_len = recv_data_len;
// 加锁
pthread_mutex_lock(&info.serial_mutex);
while (1)
{
// 设置需要监听的文件描述符
memset(fds, 0, sizeof(fds));
fds[0].fd = info.serial_fd;
fds[0].events = POLLIN;
ret = poll(fds, nfds, timeout);
// 返回负值, 发生错误
if (ret < 0)
{
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return -1;
}
// 返回0, 超时
else if (0 == ret)
{
// 如果超时后, 已读取数据长度大于0, 返回实际接收数据长度
if (total_data_len > 0)
{
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return total_data_len;
}
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return -1;
}
// 返回值大于0, 成功
else
{
// 判断是否是期望的返回
if (fds[0].revents & POLLIN)
{
// 从文件起始位置开始读数据
lseek(fds[0].fd, 0, SEEK_SET);
ret = read(fds[0].fd, &recv_data[total_data_len],
remain_data_len);
if (ret < 0)
{
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return -1;
}
// 计算已读取数据长度
total_data_len += ret;
// 计算剩余需要读取长度
remain_data_len = (recv_data_len - total_data_len);
// 读取完毕
if (total_data_len == recv_data_len)
{
break;
}
}
}
}
// 解锁
pthread_mutex_unlock(&info.serial_mutex);
return total_data_len;
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。