【SSD20X平台Linux应用开发】串口应用编程

1. 串口的作用

UART:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),简称串口。

  • 调试:移植u-boot、内核、应用程序时,主要使用串口查看打印信息
  • 外接各种模块
    在这里插入图片描述

2. 硬件知识_UART硬件介绍

1. 串口的硬件介绍

UART的全称是Universal Asynchronous Receiver and Transmitter,即异步发送和接收。
串口在嵌入式中用途非常的广泛,主要的用途有:

  • 打印调试信息;
  • 外接各种模块:GPS、蓝牙;

串口因为结构简单、稳定可靠,广受欢迎。
通过三根线即可,发送、接收、地线

在这里插入图片描述
通过TxD->RxD把ARM开发板要发送的信息发送给PC机。
通过RxD->TxD线把PC机要发送的信息发送给ARM开发板。
最下面的地线统一参考地。

2. 串口的参数
  • 波特率:一般选波特率都会有9600,19200,115200等选项。其实意思就是每秒传输这么多个比特 位数(bit)。
  • 起始位:先发出一个逻辑”0”的信号,表示传输数据的开始。
  • 数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输。
  • 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据
  • 传送的正确性。
  • 停止位:它是一个字符数据的结束标志。

怎么发送一字节数据,比如‘A‘?
‘A’的ASCII值是0x41,二进制就是01000001,怎样把这8位数据发送给PC机呢?

  • 双方约定好波特率(每一位占据的时间);

  • 规定传输协议

     原来是高电平,ARM拉低电平,保持1bit时间;
     PC在低电平开始处计时;
     ARM根据数据依次驱动TxD的电平,同时PC依次读取RxD引脚电平,获得数据;
    

在这里插入图片描述
前面图中提及到了逻辑电平,也就是说代表信号1的引脚电平是人为规定的。
如图是TTL/CMOS逻辑电平下,传输‘A’时的波形:

在这里插入图片描述
在xV至5V之间,就认为是逻辑1,在0V至yV之间就为逻辑0。

如图是RS-232逻辑电平下,传输‘A’时的波形:

在这里插入图片描述
在-12V至-3V之间,就认为是逻辑1,在+3V至+12V之间就为逻辑0。
RS-232的电平比TTL/CMOS高,能传输更远的距离,在工业上用得比较多。
市面上大多数ARM芯片都不止一个串口,一般使用串口0来调试,其它串口来外接模块。

3. 串口电平

ARM芯片上得串口都是TTL电平的,通过板子上或者外接的电平转换芯片,转成RS232接口,连接到电
脑的RS232串口上,实现两者的数据传输。

在这里插入图片描述现在的电脑越来越少有RS232串口的接口,当USB是几乎都有的。因此使用USB串口芯片将ARM芯片上
的TTL电平转换成USB串口协议,即可通过USB与电脑数据传输。

4. 串口内部结构

ARM芯片是如何发送/接收数据?
如图所示串口结构图:

在这里插入图片描述
要发送数据时,CPU控制内存要发送的数据通过FIFO传给UART单位,UART里面的移位器,依次将数据
发送出去,在发送完成后产生中断提醒CPU传输完成。
接收数据时,获取接收引脚的电平,逐位放进接收移位器,再放入FIFO,写入内存。在接收完成后产生
中断提醒CPU传输完成。

三. TTY体系中设备节点的差别

  1. 傻傻分不清
    /dev/ttyS0、/dev/ttySAC0、/dev/tty、/dev/tty0、/dev/tty1、/dev/console,
    它们有什么差别?
设备节点含义
/dev/ttyS0、/dev/ttySAC0串口
/dev/tty1、/dev/tty2、/dev/tty3、虚拟终端设备节点
/dev/tty0前台终端
/dev/tty程序自己的终端,可能是串口、也可能是虚拟终端
/dev/console控制台,又内核的cmdline参数确定

TTY/Terminal/Console/UART,它们有什么差别

术语含义
TTY来自teletype,最古老的输入输出设备,现在用来表示内核的一套驱动系统
Terminal终端,暗含远端之意,也是一个输入输出设备,可能是真实设备,也可能是虚拟设备
Console控制台,含控制之意,也是一种Terminal,权限更大,可以查看内核打印信息
UART串口,它的驱动程序包含在TTY驱动体系之内
2. TTY名称的历史
2.1 电传机teletype

teletype,更准确地说是teleprinter,是一种通信设备,可以用来发送、接收文本信息。
teletype是一家公司的名字,它生产的teleprinter实在太有名,结果公司名变成了这类产品的名字:
teleprinter都被称为teletype了。

在这里插入图片描述
teletype被用来传输商业电报,想像一下:

  • 把两台teletype的线缆接在一起,或者使用无线技术连接两台teletype
  • 这边打字,另一边就可以接收到信息并通过纸张打印出来
  • 注意:这两台teletype可以离得很远
2.2 计算机需要控制
2.2.1 使用teletype

teletype的简称就是TTY。
最古老的计算机支持的是批处理模型:

  • 怎么编程?卡片打孔,然后喂给计算机。
  • 怎么得到输出信息?计算机根据结果在卡片上打孔,需要专人翻译这些卡片。

如果把两台teletype的其中一台,替换为计算机,不就更方便了吗?可以即时输入指令、即时看到结
果。
于是teletype变成了计算机的终端、Terminal,远端之意。
teletype和计算机可以放在一个房间里,也可以放在很远很远的地方。

在这里插入图片描述
teletype是通过串口(UART)跟计算机相连的:

在这里插入图片描述

2.2.2 teletype被淘汰了

1960年代,CRT显示器+键盘,替代了teletype:

  • 显示器替代了纸张
  • 速度更快
  • 成本更低
  • 它仍然只是一个终端(terminal),通过线缆连接到计算机
  • 虽然不再是teletype,但是它的驱动程序仍然叫做TTY
    在这里插入图片描述
2.2.3 个人电脑和虚拟终端

上图长得像电脑,但是它只是一个终端,它要连接到计算机才能工作。
这才是电脑:计算单元(CPU、硬盘、内存等等)、终端(键盘、显示器)都齐全了:

在这里插入图片描述
硬件上只有一套键盘、显示器。
但是在Linux系统中,我们可以打开多个命令行程序(也叫terminal、shell),每个程序都对应一个"虚拟
终端

3. 在Ubuntu上演示

按住键盘:Ctrl+Alt+F3启动一个虚拟终端,Ctrl+Alt+F4再启动一个虚拟终端。
在里面切换为root用户

sudo passwd root // 如果su root不成功,就先设置root密码 
su root
3.1 各类设备节点的差别

由于历史原因,下图中两条红线之内的代码被称为TTY子系统。
它既支持UART,也支持键盘、显示器,还支持更复杂的功能(比如伪终端)。

在这里插入图片描述

3.2 /dev/ttyN(N=1,2,3,…)

/dev/tty3、/dev/tty4:表示某个程序使用的虚拟终端

// 在tty3、tty4终端来回切换,执行命令 
echo hello > /dev/tty3 
echo hi > /dev/tty4

3.3 /dev/tty0

/dev/tty0:表示前台程序的虚拟终端

  • 你正在操作的界面,就是前台程序
  • 其他后台程序访问/dev/tty0的话,就是访问前台程序的终端,切换前台程序时,/dev/tty0是变化 的
// 1. 在tty3终端执行如下命令 
// 2. 然后在tty3、tty4来回切换 
while [ 1 ]; do echo msg_from_tty3 > /dev/tty0; sleep 5; done

3.4 /dev/tty

/dev/tty表示本程序的终端,可能是虚拟终端,也可能是真实的中断。
程序A在前台、后台间切换,它自己的/dev/tty都不会变。

// 1. 在tty3终端执行如下命令 
// 2. 然后在tty3、tty4来回切换 
while [ 1 ]; do echo msg_from_tty3 > /dev/tty; sleep 5; done

3.5 Terminal和Console的差别

Terminal含有远端的意思,中文为:终端。Console翻译为控制台,可以理解为权限更大、能查看更多
信息。
比如我们可以在Console上看到内核的打印信息,从这个角度上看:

  • Console是某一个Terminal
  • Terminal并不都是Console。
  • 我们可以从多个Terminal中选择某一个作为Console
  • 很多时候,两个概念混用,并无明确的、官方的定义

3.6 /dev/console

选哪个?内核的打印信息从哪个设备上显示出来?
可以通过内核的cmdline来指定,
比如: console=ttyS0 console=tty
我不想去分辨这个设备是串口还是虚拟终端,
有没有办法得到这个设备?
有!通过/dev/console!
console=ttyS0时:/dev/console就是ttyS0
console=tty时:/dev/console就是前台程序的虚拟终端
console=tty0时:/dev/console就是前台程序的虚拟终端
console=ttyN时:/dev/console就是/dev/ttyN
console有多个取值时,使用最后一个取值来判断

四. TTY驱动程序框架

1. 行规程的引入

在这里插入图片描述
以下文字引用自参考资料解密TTY:
大多数用户都会在输入时犯错,所以退格键会很有用。这当然可以由应用程序本身来实现,但是根据
UNIX设计“哲学”,应用程序应尽可能保持简单。为了方便起见,操作系统提供了一个编辑缓冲区和一些
基本的编辑命令(退格,清除单个单词,清除行,重新打印),这些命令在行规范(line discipline)内
默认启用。高级应用程序可以通过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模
式(cooked and canonical)来禁用这些功能。大多数交互程序(编辑器,邮件客户端,shell,及所有
依赖curses或readline的程序)均以原始模式运行,并自行处理所有的行编辑命令。行规范还包含字符
回显和回车换行(译者注:\r\n 和 \n)间自动转换的选项。如果你喜欢,可以把它看作是一个原始的内
核级sed(1)。
另外,内核提供了几种不同的行规范。一次只能将其中一个连接到给定的串行设备。行规范的默认规则
称为N_TTY(drivers/char/n_tty.c,如果你想继续探索的话)。其他的规则被用于其他目的,例如管理
数据包交换(ppp,IrDA,串行鼠标),但这不在本文的讨论范围之内。

2. TTY驱动程序框架

在这里插入图片描述

六. 例子

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值