UART的全称是Universal Asynchronous Receiver and Transmitter,即异步发送和接收。
串口在嵌入式中用途非常的广泛,主要的用途有:
编写串口应用程序时需要先包含如下头文件:
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
int open_port(void)
{
int fd;
/* 打开串口
* "/dev/ttyf1" 串口文件名
* O_RDWR 以读写方式打开
* O_NOCTTY 不将此端口作为控制终端
* O_NDELAY 表示不关心 DCD 信号线的状态,同时它还将串口设置为非阻塞模式,在没有数据时进行读取返回0,
* 后面可以通过fcntl(fd, F_SETFL, 0)将其设置为阻塞式
**/
fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
/* 打开失败 */
perror("open_port: Unable to open /dev/ttyf1 - ");
}
else
{
/* 设置为阻塞式读取 */
fcntl(fd, F_SETFL, 0);
}
return (fd);
}
通过write函数写数据(发送数据),通过read函数都数据(接收数据)
通过close函数关闭串口设备
可以通过如下来读取或配置串口参数:
/* tcgetattr 获取串口配置参数, tcsetattr 设置串口配置参数
- fd
- optional_actions 配置模式:
- TCSANOW 改变立即发生
- TCSADRAIN 改变在写入 fd 的数据都被传输后生效
- TCSAFLUSH 改变在写入 fd 的数据都被传输后生效,且已接收但未读取的数据全部丢弃
- termios_p 串口配置参数
**/
int tcgetattr(int fd, struct termios *termios_p)
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p)
串口的配置参数保存在struct termios 结构体中,此结构体至少包含以下成员:
/* 输入控制标志 */
tcflag_t c_iflag;
/* 输出控制标志 */
tcflag_t c_oflag;
/* 控制模式标志 */
tcflag_t c_cflag;
/* 本地模式标志 */
tcflag_t c_lflag;
/* 行规程 */
cc_t c_cc[NCCS];
/* 输入波特率 */
int c_ispeed;
/* 输出波特率 */
int c_ospeed;
c_iflag |= (INPCK | ISTRIP)
启用软件流控制:
c_iflag |= (IXON | IXOFF | IXANY);
禁用软件流控:
c_iflag &= ~(IXON | IXOFF | IXANY);
/* 禁用OPOST选项时,将忽略 c_oflag中的所有其他选项位 */
c_oflag &= ~OPOST;
c_cflag &= ~CSIZE; /* 屏蔽字符大小位 */
c_cflag |= CS8; /* 选择 8 个数据位 */
设置奇偶校验:
/*无奇偶校验 (8N1) */
c_cflag &= ~PARENB;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS8;
/* 偶校验(7E1) */
c_cflag |= PARENB;
c_cflag &= ~PARODD;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS7;
/* 奇校验(7O1) */
c_cflag |= PARENB;
c_cflag |= PARODD;
c_cflag &= ~CSTOPB;
c_cflag &= ~CSIZE;
c_cflag |= CS7;
设置硬件流控制:
c_cflag |= CRTSCTS;
禁用硬件流控制:
c_cflag &= ~CRTSCTS;
c_lflag |= (ICANON | ECHO | ECHOE);
选择原始输入:
c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//有数据立即读取,并返回读取的字节数,无数据立即返回0
MIN = 0 , TIME = 0;
//在 TIME 指定的时间内有数据则返回读取的字节数,无数据返回0
MIN = 0 , TIME > 0;
//在最少读取到 MIN 个字节数才返回
MIN > 0 , TIME = 0;
//读取到的一个字节时启动计时,此后每收到一个字符都会重新计时,在最少读取到 MIN 个字节数或超时返回读取的字节数
MIN > 0 , TIME > 0;
/* 获取输入波特率 */
speed_t cfgetispeed(const struct termios *termios_p);
/* 获取输出波特率 */
speed_t cfgetospeed(const struct termios *termios_p);
/* 设置输入波特率 */
int cfsetispeed(struct termios *termios_p, speed_t speed);
/* 设置输出波特率 */
int cfsetospeed(struct termios *termios_p, speed_t speed);
/* 设置输入输出波特率 */
int cfsetspeed(struct termios *termios_p, speed_t speed);
在设备树stm32mp157d-atk.dtsi中引用uart3和uart4节点,并加入如下内容:
&usart3 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&usart3_pins_mx>;
pinctrl-1 = <&usart3_sleep_pins_mx>;
/delete-property/dmas;
/delete-property/dma-names;
status = "okay";
};
&uart5 {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&uart5_pins_mx>;
pinctrl-1 = <&uart5_sleep_pins_mx>;
/delete-property/dmas;
/delete-property/dma-names;
status = "okay";
};
在设备树stm32mp15-pinctrl.dtsi中引用的&pinctrl节点中加入如下内容:
uart5_pins_mx: uart5_mx-0 {
pins1 {
pinmux = <STM32_PINMUX('B', 12, AF14)>; /* UART5_RX */
bias-disable;
};
pins2 {
pinmux = <STM32_PINMUX('B', 13, AF14)>; /* UART5_TX */
bias-disable;
drive-push-pull;
slew-rate = <0>;
};
};
uart5_sleep_pins_mx: uart5_sleep_mx-0 {
pins {
pinmux = <STM32_PINMUX('B', 12, ANALOG)>, /* UART5_RX */
<STM32_PINMUX('B', 13, ANALOG)>; /* UART5_TX */
};
};
usart3_pins_mx: usart3_mx-0 {
pins1 {
pinmux = <STM32_PINMUX('D', 8, AF7)>; /* USART3_TX */
bias-disable;
drive-push-pull;
slew-rate = <0>;
};
pins2 {
pinmux = <STM32_PINMUX('D', 9, AF7)>; /* USART3_RX */
bias-disable;
};
};
usart3_sleep_pins_mx: usart3_sleep_mx-0 {
pins {
pinmux = <STM32_PINMUX('D', 8, ANALOG)>, /* USART3_TX */
<STM32_PINMUX('D', 9, ANALOG)>; /* USART3_RX */
};
};
在设备树stm32mp157d-atk.dts的aliases节点中增加如下内容:
//usart3的设备文件名是ttySTM1
//uart5的设备文件名是ttySTM2
serial1 = &usart3;
serial2 = &uart5;
用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,然后用新的.dtb文件启动系统
编写一个应用程序,从串口读取数据,然后显示读取到的字节数,并将读取的数据返回到PC,程序包含以下几个部分:
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
int set_port(int fd, int baud_rate, int n_bits, char parity, int n_stop)
{
struct termios options;
/* 读取配置 */
if(tcgetattr(fd, &options) < 0)
{
perror("SetupSerial 1");
return -1;
}
options.c_iflag = 0x00;
options.c_oflag = 0x00;
options.c_cflag = 0x00;
options.c_lflag = 0x00;
/* 禁用软件流控 */
options.c_iflag &= ~(IXON | IXOFF | IXANY);
if((parity == 'O') || (parity == 'E')) {
/* 启用输入奇偶校验并剥离奇偶校验位 */
options.c_iflag |= (INPCK | ISTRIP);
}
/* 选择原始输出 */
options.c_oflag &= ~OPOST;
/* 设置字符大小 */
options.c_cflag &= ~CSIZE;
switch(n_bits)
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
default:
options.c_cflag |= CS8;
break;
}
/* 设置奇偶校验 */
switch(parity)
{
case 'O':
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
break;
case 'E':
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
break;
case 'N':
default:
options.c_cflag &= ~PARENB;
break;
}
/* 禁用硬件流控制 */
options.c_cflag &= ~CRTSCTS;
/* 设置停止位 */
switch(n_stop)
{
case 2:
options.c_cflag |= CSTOPB;
break;
case 1:
default:
options.c_cflag &= ~CSTOPB;
break;
}
/* 启用接收器,并忽略 modem 控制线 */
options.c_cflag |= CLOCAL | CREAD;
/* 选择原始输入 */
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* 设置波特率 */
switch(baud_rate)
{
case 2400:
cfsetispeed(&options, B2400);
cfsetospeed(&options, B2400);
break;
case 4800:
cfsetispeed(&options, B4800);
cfsetospeed(&options, B4800);
break;
case 9600:
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
break;
case 115200:
default:
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
break;
}
/* 设置读取超时和每次读取的最小字节数 */
options.c_cc[VMIN] = 1;
options.c_cc[VTIME] = 1;
/* 刷新缓冲区 */
tcflush(fd, TCIFLUSH);
/* 配置串口 */
if((tcsetattr(fd, TCSANOW, &options))!=0)
{
perror("com set error");
return -1;
}
return 0;
}
int open_port(const char *com)
{
int fd;
/* 打开串口 */
fd = open(com, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
/* 打开失败 */
perror("open_port");
}
else
{
/* 设置为阻塞式读取 */
fcntl(fd, F_SETFL, 0);
}
return (fd);
}
int main(int argc, char **argv)
{
int fd;
int result;
char buffer[64];
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;
}
/* 设置串口设备 */
result = set_port(fd, 115200, 8, 'N', 1);
if(result < 0)
{
printf("set port err!\n");
return -1;
}
while(1)
{
/* 读取串口接收到的数据 */
result = read(fd, &buffer, sizeof(buffer));
if(result > 0)
{
printf(" read %d bytes\r\n", result);
/* 通过串口发送数据 */
result = write(fd, &buffer, result);
}
else
perror(NULL);
}
return 0;
}