串口,TTY,编程

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的初步用法了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值