1.串口的硬件知识
串口的作用:打印,外接各种模块
一般串口通信通过,RX TX GND,三条线即可,也就是发送,接受,和地线。
波特率:单位时间内传送的码元符号的个数,例如9600表示每秒传输9600bit,前提是符号单元位1bit,这样波特率和比特率相等。如果传输1个Byte需要10bit,那么每秒传输960byte。
开始位:表示即将开始传输数据,高电平或者低电平
数据位:传输的数据,例如1Byte,一共8bit,Txd=Data[0]--->Data[7],接受数据是Rx引脚
校验位:奇偶校验,数据位+校验位中为1的位的个数是奇/偶数,例如,如果用奇校验,传输0x010000010,那么校验位就要输出高电平1,保证加起来为奇数。
停止位:恢复电平状态,以便下次传输
串行 or 并行
程序把内存中的数据写入FIFO通过移位器,逐位发送给PC端,反之亦然
2.TTY体系
TTY(TeleType)指Linux中的一类终端(Terminal)设备, 是一种字符设备。
我们来分析一下这个程序就知道了
#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>
/*
* 设置串口的参数配置函数,设置波特率、数据位、校验位、停止位等
* 参数:
* fd: 串口文件描述符
* nSpeed: 波特率
* nBits: 数据位
* nEvent: 校验位
* nStop: 停止位
*/
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio, oldtio;
// 获取当前串口配置,保存到oldtio中
if (tcgetattr(fd, &oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
// 清空newtio结构体,用于存储新的串口设置
bzero(&newtio, sizeof(newtio));
newtio.c_cflag |= CLOCAL | CREAD; // CLOCAL: 忽略控制线状态 | CREAD: 启用接收功能
newtio.c_cflag &= ~CSIZE; // 清除数据位字段
// 输入模式: 关闭标准输入的回显等功能
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 输出模式: 关闭标准输出的处理功能
newtio.c_oflag &= ~OPOST;
// 设置数据位: 7位或8位
switch (nBits) {
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
// 设置校验位: 奇校验'O',偶校验'E',无校验'N'
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: // 默认波特率9600
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
// 设置停止位: 1位或2位
if (nStop == 1)
newtio.c_cflag &= ~CSTOPB;
else if (nStop == 2)
newtio.c_cflag |= CSTOPB;
// 设置读取数据时的条件,VMIN为读取到1个字节即返回,VTIME为等待时间
newtio.c_cc[VMIN] = 1;
newtio.c_cc[VTIME] = 0;
// 刷新输入缓冲区
tcflush(fd, TCIFLUSH);
// 将设置立即生效
if ((tcsetattr(fd, TCSANOW, &newtio)) != 0) {
perror("com set error");
return -1;
}
return 0;
}
/*
* 打开串口设备
* 参数:
* com: 串口设备文件名 (如 "/dev/ttySAC1")
* 返回值:
* 成功返回文件描述符,失败返回-1
*/
int open_port(char *com)
{
int fd;
// 打开串口,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;
}
/*
* 主函数
* 参数:
* argc: 参数数量
* argv: 参数值数组
* 功能:
* 打开串口设备,设置串口参数,发送和接收数据
*/
int main(int argc, char **argv)
{
int fd;
int iRet;
char c;
// 如果参数不够,提示用户使用方法
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;
}
// 设置串口参数: 波特率115200,8个数据位,无校验,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;
}
从main函数开始
int main(int argc, char **argv);
这个函数是定义在C标准库中的命令行用法,argc就是命令行参数的个数,argv是一个指向char *的指针,也就是指向字符的指针(字符串),argv是数组,argv[0]表示命令行的第一个参数,一般是程序的名字,argv[1]就是第二个参数,以此类推。
比如这里的执行程序./uart /dev/ttyS0,第一个参数是程序名字,第二个参数是串口节点。
我们接着看,main函数中,
第一步:打开串口设备
---fd = open_port(argv[1]);,
我们跳到open_port这个函数,里面调用了
---fd = open(com, O_RDWR | O_NOCTTY);打开串口设备,并设置为读写模式(O_RDWR ),不控制终端(O_NOCTTY)
---fcntl(fd, F_SETFL, 0),是控制文件描述符的系统调用函数。
F_SETFL:表示设置文件描述符的状态标志
0:表示清除所有状态标志。这将文件描述符设置为默认的阻塞模式(即没有非阻塞模式、追加模式或其他特殊模式)。
第二步:设置串口参数: 波特率115200,8个数据位,无校验,1个停止位
set_opt(fd, 115200, 8, 'N', 1);
具体就是设置termios结构体,并使设置生效
struct termios {
tcflag_t c_iflag; // 输入模式标志
tcflag_t c_oflag; // 输出模式标志
tcflag_t c_cflag; // 控制模式标志
tcflag_t c_lflag; // 本地模式标志
cc_t c_cc[NCCS]; // 控制字符数组
speed_t c_ispeed; // 输入波特率
speed_t c_ospeed; // 输出波特率
};
---tcgetattr( fd,&oldtio),使用这个用于获取当前串口的设置,并将其存储在 oldtio
中。
bzero(&newtio, sizeof(newtio));用于清零结构体,也可以使用memste函数
接着就是设置结构体的各类参数。
---cfsetispeed它是 POSIX 标准中定义的函数之一,用于配置终端设备的串口参数。
---tcflush是一个用于刷新串口设备的输入、输出缓冲区的函数。它可以清空指定的缓冲区,以确保设备在进行新的 I/O 操作前处于一个已知的状态。
---tcsetattr
是一个用于设置终端设备(如串口)属性的函数。它将 termios
结构体中的配置应用到指定的终端设备上
第三步:从标准输入获取信息,写入串口,然后打印出来
相信看懂这个函数即可了解uart的初步用法了