Linux纯C串口开发

为什么要用纯C语言

为了数据流动加速,实现低配CPU建立高速数据流而不用CPU干预,避免串口数据流多次反复上升到软件应用层又下降低到硬件协议层。

在这里插入图片描述

关于termios.h

麻烦的是,在 Linux 中使用串口并不是一件最简单的事情。在处理 termios.h 标头时,有许多挑剔的设置隐藏在多个字节的位字段中。本文将试图帮助解释这些设置并向您展示如何在 Linux 中正确配置串行端口。

在这里插入图片描述

一切都是文件

在典型的 UNIX 风格中,串行端口由操作系统中的文件表示。这些文件通常在 /dev/ 中弹出,并以名称 tty* 开头。常见的名称如下:

  1. /dev/ttyACM0: ACM 代表 USB 总线上的 ACM 调制解调器。 Arduino UNO(和类似的)将使用此名称出现。
  2. /dev/ttyPS0:运行基于 Yocto 的 Linux 版本的 Xilinx Zynq FPGA 将使用此名称作为 Getty 连接到的默认串行端口。
  3. /dev/ttyS0:通常情况下标准 COM 端口用的此名称。如今,由于较新的台式机和笔记本电脑没有实际的 COM 端口,这种情况已不太常见。
  4. /dev/ttyUSB0: 大多数 USB 转串口电缆将使用这样命名的文件显示。
  5. /dev/pts/0 - 伪终端。这些可以使用 socat 生成。

下图展示了一块常见的开发板提供的串口设备:

在这里插入图片描述

要写入串行端口,请写入文件。要从串行端口读取,请从文件中读取。当然,这允许您发送/接收数据,但是如何设置串口参数,例如波特率、奇偶校验等。这是由特殊的 tty 配置 struct 设置的。

开发C代码

在这里插入图片描述

首先需要包含一些头文件

// C library headers
#include <stdio.h>
#include <string.h>// Linux headers
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()

然后我们要打开串行端口设备(在 /dev/ 下显示为文件),保存 open() 返回的文件描述符:

int serial_port = open("/dev/ttyUSB0", O_RDWR);// Check for errors
if (serial_port < 0) {printf("Error %i from open: %s\n", errno, strerror(errno));
}

您可能在此处看到的常见错误之一是 errno = 2 ,并且 strerror(errno) 返回 No such file or directory 。确保您拥有设备的正确路径并且该设备存在!

您可能在这里遇到的另一个常见错误是 errno = 13 ,即 Permission denied 。这通常是因为当前用户不属于dialout组的一部分而发生。使用以下命令将当前用户添加到 dialout 组:

sudo adduser $USER dialout

上述命令没有立即生效。您可以选择注销并重新登录,也可以使用其它工具让它立即生效。

此时,我们可以从技术上读取和写入串行端口,但它可能不起作用,因为默认配置设置不是为串行端口使用而设计的。所以现在我们将正确设置配置。

修改任何配置值时,最佳做法是仅修改您感兴趣的bit位,并保持字段的所有其它bit位不变。这就是为什么您会在下面看到设置位时使用 &=|= ,而不是 =

串口启动配置

我们需要访问 termios 结构才能配置串行端口。我们将创建一个新的 termios 结构体,然后使用 tcgetattr() 将串口的现有配置写入其中,然后根据需要修改参数并使用 tcsetattr()

// Create new termios struct, we call it 'tty' for convention
// No need for "= {0}" at the end as we'll immediately write the existing
// config to this struct
struct termios tty;// Read in existing settings, and handle any error
// NOTE: This is important! POSIX states that the struct passed to tcsetattr()
// must have been initialized with a call to tcgetattr() overwise behaviour
// is undefined
if(tcgetattr(serial_port, &tty) != 0) {printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
}

我们现在可以根据需要更改 tty 的设置,如以下部分所示。在我们开始之前,如果您感兴趣的话,这里是 termios 结构的定义(从 termbits.h 中提取):

struct termios {tcflag_t c_iflag;		/* input mode flags */tcflag_t c_oflag;		/* output mode flags */tcflag_t c_cflag;		/* control mode flags */tcflag_t c_lflag;		/* local mode flags */cc_t c_line;			/* line discipline */cc_t c_cc[NCCS];		/* control characters */
};

串口参数配置c_cflags

termios 结构的 c_cflag 成员包含控制参数字段。

PARENB (Parity)

如果设置该位,则启用奇偶校验位的生成和检测。大多数串行通信不使用奇偶校验位,因此如果您不确定,请清除该位。

tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
tty.c_cflag |= PARENB;  // Set parity bit, enabling parity

CSTOPB (停止位)

如果设置该位,则使用两个停止位。如果该位被清除,则仅使用一个停止位。大多数串行通信仅使用一个停止位。

tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
tty.c_cflag |= CSTOPB;  // Set stop field, two stop bits used in communication

字节的位数

CS<number> 字段设置通过串行端口每个字节传输多少数据位。这里最常见的设置是 8 ( CS8 )。如果你不确定的话,一定要使用这个,我以前从来没有使用过串口,之前没有使用过8(但它们确实存在)。在使用 &= ~CSIZE 设置任何大小位之前,您必须清除所有大小位。

tty.c_cflag &= ~CSIZE; // Clear all the size bits, then use one of the statements below
tty.c_cflag |= CS5; // 5 bits 每字节
tty.c_cflag |= CS6; // 6 bits 每字节
tty.c_cflag |= CS7; // 7 bits 每字节
tty.c_cflag |= CS8; // 8 bits 每字节 (most common)

CRTSCTS(硬件流控制)

如果设置了 CRTSCTS 字段,则启用硬件RTS/CTS流控制。这是当端点之间有两条额外的电线时,用于在数据准备好发送/接收时发出信号的情况。这里最常见的设置是禁用它。在应该禁用它的时候启用它可能会导致您的串行端口接收不到数据,因为发送者将无限期地缓冲它,等待您“准备好”。少于3根线的串口一定没有这个功能,应该禁用。

tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
tty.c_cflag |= CRTSCTS;  // Enable RTS/CTS hardware flow control

有关与流量控制相关的其他设置,请参阅的串口流量控制相关文章。

CREAD 和 CLOCAL

设置 CLOCAL 禁用调制解调器特定的信号线,例如载波检测。它还可以防止在检测到调制解调器断开连接时向控制进程发送 SIGHUP 信号,这通常是一件好事。设置 CREAD 允许我们读取数据(我们绝对想要这样!)。

tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

c_lflag

禁用规范模式

UNIX系统提供两种基本的输入模式:规范模式和非规范模式。在规范模式下,当收到新行字符时处理输入。接收应用程序逐行接收该数据。在处理串行端口时,这通常是不受欢迎的,因此我们通常希望禁用规范模式。禁用规范模式:

tty.c_lflag &= ~ICANON;

此外,在规范模式下,某些字符(例如退格键)会被特殊处理,用于编辑当前文本行(擦除)。同样,如果处理原始串行数据,我们不希望使用此功能,因为它会导致特定字节丢失!

回应(Echo)

如果设置了该位,发送的字符将被回显。因为我们禁用了规范模式,所以我认为这些位实际上没有做任何事情,但以防万一禁用它们也没有什么坏处!串口默认启用了这个模式,因为测试硬件的正确性经常需要TX/RX短接。

tty.c_lflag &= ~ECHO; // Disable echo
tty.c_lflag &= ~ECHOE; // Disable erasure
tty.c_lflag &= ~ECHONL; // Disable new-line echo

禁用信号字符

当设置 ISIG 位时,将解释 INTRQUITSUSP 字符。我们不希望使用串行端口,因此请清除此位:

tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP

输入模式(c_iflag

termios的输入流与输出流是分开配置的。因为大部分场景下输入流与输出流的配置相同,所以termios显得比较麻烦。termios 结构的 c_iflag 成员包含输入处理的低级设置。 c_iflag 成员是 int

软件流控制(IXOFFIXONIXANY

清除 IXOFF 、 IXON 和 IXANY 会禁用软件流控制,这是我们不想要的:

tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl

禁用接收时字节的特殊处理

在将字节传递给应用程序之前,清除以下所有位将禁用串行端口接收字节时对字节的任何特殊处理。我们只想要原始数据!

tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

输出模式(c_oflag

termios 结构的 c_oflag 成员包含输出处理的低级设置。配置串行端口时,我们希望禁用对输出字符/字节的任何特殊处理,因此请执行以下操作:

tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT IN LINUX)
// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT IN LINUX)

OXTABSONOEOT 在 Linux 中都没有定义。然而,Linux 确实有似乎相关的 XTABS 字段。当针对 Linux 进行编译时,我只是排除这两个字段,串行端口仍然可以正常工作。

VMINVTIME (c_cc)

VMIN 和 VTIME 是许多程序员在尝试在 Linux 中配置串行端口时感到困惑的根源。需要注意的重要一点是, VTIME 的含义略有不同,具体取决于 VMIN 的含义。当 VMIN 为 0 时, VTIME 指定从 read() 调用开始时的超时。但当 VMIN > 0 时, VTIME 指定从第一个接收到的字符开始算起的超时时间。让我们探索不同的组合:

  1. VMIN = 0,VTIME = 0:无阻塞,立即返回可用内容
  2. VMIN > 0,VTIME = 0:这将使 read() 始终等待字节(具体多少由 VMIN 确定),因此 read() 可能无限期阻塞。
  3. VMIN = 0,VTIME > 0:这是对任意数量的字符的阻塞读取,具有最大超时(由 VTIME 给出)。 read() 将阻塞,直到有任意数量的数据可用或发生超时。这恰好是我最喜欢的模式(也是我使用最多的模式)。
  4. VMIN > 0、VTIME > 0:阻塞直至收到 VMIN 个字符,或在第一个字符过去后 VTIME 。请注意, VTIME 的超时直到收到第一个字符后才开始。

VMIN 和 VTIME 都定义为类型 cc_t ,我一直认为它是 unsigned char (1 字节)的别名。这将 VMIN 字符数的上限设置为 255,最大超时设置为 25.5 秒(255 分秒)。

收到数据后立即返回并不意味着一次只能获取 1 个字节。根据操作系统延迟、串行端口速度、硬件缓冲区和您无法直接控制的许多其他因素,您可能会收到任意数量的字节。例如,如果我们想等待最多 1 秒,一旦收到任何数据就返回,我们可以使用:

tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.
tty.c_cc[VMIN] = 0;

波特率

串行端口波特率不是像所有其他设置那样使用位字段,而是通过调用函数 cfsetispeed()cfsetospeed() 并传入指向 tty :

// Set in/out baud rate to be 9600
cfsetispeed(&tty, B9600);
cfsetospeed(&tty, B9600);

如果您想保持 UNIX 兼容,则必须从以下选项之一中选择波特率:

B0,  B50,  B75,  B110,  B134,  B150,  B200, B300, B600, B1200, B1800, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400, B460800

Linux 的某些实现提供了一个辅助函数 cfsetspeed() ,它同时设置输入和输出速度:

cfsetspeed(&tty, B9600);

自定义波特率

由于您现在完全意识到配置 Linux 串行端口并非小事,因此您可能不会因为设置自定义波特率同样困难而感到困惑。没有可移植的方法来执行此操作,因此请准备好尝试以下代码示例,以了解哪些内容适用于您的目标系统。

GNU/Linux 方法

如果您使用 GNU C 库进行编译,则可以放弃上面的标准枚举,只需直接为 cfsetispeed()cfsetospeed() 指定整数波特率,例如:

// Specifying a custom baud rate when using GNU C
cfsetispeed(&tty, 104560);
cfsetospeed(&tty, 104560);

termios2方法

此方法依赖于使用 termios2 结构,该结构类似于 termios 结构,但功能明显更多。我不确定 termios2 到底是在什么 UNIX 系统上定义的,但如果是的话,它通常是在 termbits.h 中定义的(它是在我正在做的带有 GCC 系统的 Xubuntu 18.04 上)这些测试):

struct termios2 {tcflag_t c_iflag;		/* input mode flags */tcflag_t c_oflag;		/* output mode flags */tcflag_t c_cflag;		/* control mode flags */tcflag_t c_lflag;		/* local mode flags */cc_t c_line;			/* line discipline */cc_t c_cc[NCCS];		/* control characters */speed_t c_ispeed;		/* input speed */speed_t c_ospeed;		/* output speed */
};

这与普通的旧 termios 非常相似,除了添加了 c_ispeed 和 c_ospeed 。我们可以使用这些来直接设置自定义波特率!我们几乎可以以与 termios 完全相同的方式设置除波特率之外的所有内容,除了从文件描述符读取/写入终端属性之外 - 而不是使用 tcgetattr() 和 tcsetattr() 我们必须使用 ioctl() 。

让我们首先更新我们的包含,我们必须删除 termios.h 并添加以下内容:

// #include <termios.h> This must be removed! 
// Otherwise we'll get "redefinition of ‘struct termios’" errors
#include <sys/ioctl.h> // Used for TCGETS2/TCSETS2, which is required for custom baud rates
struct termios2 tty;// Read in the terminal settings using ioctl instead
// of tcsetattr (tcsetattr only works with termios, not termios2)
ioctl(fd, TCGETS2, &tty);// Set everything but baud rate as usual
// ...
// ...// Set custom baud rate
tty.c_cflag &= ~CBAUD;
tty.c_cflag |= CBAUDEX;
// On the internet there is also talk of using the "BOTHER" macro here:
// tty.c_cflag |= BOTHER;
// I never had any luck with it, so omitting in favour of using
// CBAUDEX
tty.c_ispeed = 123456; // What a custom baud rate!
tty.c_ospeed = 123456;// Write terminal settings to file descriptor
ioctl(serial_port, TCSETS2, &tty);

请阅读上面关于 BOTHER 的评论。也许在你的系统上这个方法会起作用!

并非所有硬件都支持所有波特率,因此如果可以选择,最好坚持使用上述标准 BXXX 速率之一。如果您不知道波特率是多少,并且尝试与第三方系统通信,请尝试 B9600 ,然后 B57600 ,然后 B115200 因为它们是最常见的波特率。

使配置生效

更改这些设置后,我们可以使用 tcsetattr() 传递 tty termios 结构到硬件:

// Save tty settings, also checking for error
if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
}

读写串口数据

现在我们已经打开并配置了串口,我们可以对其进行读写了!

对Linux串口的写入是通过 write() 函数完成的。我们使用上面调用 open() 返回的 serial_port 文件描述符。

unsigned char msg[] = { 'H', 'e', 'l', 'l', 'o', '\r' };
write(serial_port, msg, sizeof(msg));

读取是通过 read() 函数完成的。你必须为 Linux 提供一个缓冲区来写入数据。

// Allocate memory for read buffer, set size according to your needs
char read_buf [256];// Read bytes. The behaviour of read() (e.g. does it block?,
// how long does it block for?) depends on the configuration
// settings above, specifically VMIN and VTIME
int n = read(serial_port, &read_buf, sizeof(read_buf));// n is the number of bytes read. n may be 0 if no bytes were received, and can also be negative to signal an error.

用完了记得要关闭

close(serial_port);

完整代码

// C library headers
#include <stdio.h>
#include <string.h>// Linux headers
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()int main() {// Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)int serial_port = open("/dev/ttyUSB0", O_RDWR);// Create new termios struct, we call it 'tty' for conventionstruct termios tty;// Read in existing settings, and handle any errorif(tcgetattr(serial_port, &tty) != 0) {printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));return 1;}tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size tty.c_cflag |= CS8; // 8 bits per byte (most common)tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)tty.c_lflag &= ~ICANON;tty.c_lflag &= ~ECHO; // Disable echotty.c_lflag &= ~ECHOE; // Disable erasuretty.c_lflag &= ~ECHONL; // Disable new-line echotty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSPtty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrltty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytestty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)tty.c_cc[VTIME] = 10;    // Wait for up to 1s (10 deciseconds), returning as soon as any data is received.tty.c_cc[VMIN] = 0;// Set in/out baud rate to be 9600cfsetispeed(&tty, B9600);cfsetospeed(&tty, B9600);// Save tty settings, also checking for errorif (tcsetattr(serial_port, TCSANOW, &tty) != 0) {printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));return 1;}// Write to serial portunsigned char msg[] = { 'H', 'e', 'l', 'l', 'o', '\r' };write(serial_port, msg, sizeof(msg));// Allocate memory for read buffer, set size according to your needschar read_buf [256];// Normally you wouldn't do this memset() call, but since we will just receive// ASCII data for this example, we'll set everything to 0 so we can// call printf() easily.memset(&read_buf, '\0', sizeof(read_buf));// Read bytes. The behaviour of read() (e.g. does it block?,// how long does it block for?) depends on the configuration// settings above, specifically VMIN and VTIMEint num_bytes = read(serial_port, &read_buf, sizeof(read_buf));// n is the number of bytes read. n may be 0 if no bytes were received, and can also be -1 to signal an error.if (num_bytes < 0) {printf("Error reading: %s", strerror(errno));return 1;}// Here we assume we received ASCII data, but you might be sending raw bytes (in that case, don't try and// print it to the screen like this!)printf("Read %i bytes. Received message: %s", num_bytes, read_buf);close(serial_port);return 0; // success
};

独占串口设备

谨慎的做法是尝试阻止其他进程同时读取/写入串行端口。实现此目的的一种方法是使用 flock() 系统调用

 if(flock(fd, LOCK_EX | LOCK_NB) == -1) {//输出错误消息}

获取RX有多少个字节可读取

您可以将 FIONREAD 与 ioctl() 一起使用来查看串行端口 1 的操作系统输入(接收)缓冲区中是否有任何可用字节。这在轮询式方法中非常有用,其中应用程序在尝试读取字节之前定期检查字节。

#include <unistd.h>
#include <termios.h>int main() {// ... get file descriptor here// See if there are bytes available to readint bytes;ioctl(fd, FIONREAD, &bytes);
}

ioctl() 函数将提供的指向整数 bytes 的指针写入可从串行端口读取的字节数。尽管获取和设置终端设置是通过文件描述符完成的,但这些设置适用于终端设备本身,并将影响正在使用或将要使用该终端的所有其他系统应用程序。这也意味着在文件描述符关闭后,甚至在更改设置的应用程序终止后,终端设置更改仍然存在。

作者:岬淢箫声
日期:2023年11月1日
版本:1.0
链接:http://caowei.blog.csdn.net

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/177557.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue入门——核心知识点

简介 Vue是一套用于构建用户界面的渐进式JS框架。 构建用户界面&#xff1a;就是将后端返回来的数据以不同的形式(例如&#xff1a;列表、按钮等)显示在界面上。渐进式&#xff1a;就是可以按需加载各种库。简单的应用只需要一个核心库即可&#xff0c;复杂的应用可以按照需求…

Golang Gin 接口返回 Excel 文件

文章目录 1.Web 页面导出数据到文件由后台实现还是前端实现&#xff1f;2.Golang Excel 库选型3.后台实现示例4.xlsx 库的问题5.小结参考文献 1.Web 页面导出数据到文件由后台实现还是前端实现&#xff1f; Web 页面导出表数据到 Excel&#xff08;或其他格式&#xff09;可以…

【算法挑战】设计一个支持增量操作的栈(含解析、源码)

1381.设计一个支持增量操作的栈 https://leetcode-cn.com/problems/design-a-stack-with-increment-operation/ 1381.设计一个支持增量操作的栈 题目描述方法 1: 用数组或链表模拟栈 数组复杂度分析链表复杂度分析代码 方法 2: 空间换时间 图解复杂度分析代码 题目描述 请…

与云栖的浪漫邂逅:记一段寻找云端之美的旅程

云端之旅 2023 年的云栖大会如约而至&#xff0c;这次云栖大会也是阿里新任掌门蔡老板当任阿里巴巴董事局主席以来的第一次。大会与以往有很多不一样的地方&#xff0c;其中 AIGC 更是本届大会的重点议题&#xff01;你会感叹&#xff0c;阿里还是猛啊&#xff01; 我逛了下展…

算法学习打卡day40|343. 整数拆分、96.不同的二叉搜索树

343. 整数拆分 力扣题目链接 题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1&#xff1a; 输入: n 2 输出: 1 解释: 2 1 …

一条 SQL 是如何在 MyBatis 中执行的

前言 MyBatis 执行 SQL 的核心接口为 SqlSession 接口&#xff0c;该接口提供了一些 CURD 及控制事务的方法&#xff0c;另外还可以通过 SqlSession 先获取 Mapper 接口的实例&#xff0c;然后通过 Mapper 接口执行 SQL&#xff0c;Mapper 接口方法的执行最终还是委托到 SqlSe…

Unity屏幕中涂鸦

LineRenderer LineRenderer是Unity中的一个组件&#xff0c;用于在场景中绘制简单的线段。 LineRenderer组件允许你通过设置一系列顶点来定义线段的形状和外观。它会根据这些顶点自动在场景中绘制出线段。 下面是LineRenderer的一些重要属性和方法&#xff1a; positionCou…

栈及其栈的模拟实现和使用

1. 栈(Stack) 1.1 概念 栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO &#xff08; Last In First Out &#xff09;的原则…

初识FFmpeg

前言 无意间见到群里的小伙伴展示视频工具。功能比较多&#xff0c;包括视频编码修改&#xff0c;画质处理&#xff0c;比例处理、名称提取&#xff0c;剪辑、标题拆解。因此开始了FFmpeg学习。以下摘自百度百科的解释。 FFmpeg是一套可以用来记录、转换数字音频、视频&#xf…

【Proteus仿真】【Arduino单片机】简易电子琴

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用无源蜂鸣器、按键等。 主要功能&#xff1a; 系统运行后&#xff0c;按下K1-K7键发出不同音调。 二、软件设计 /* 作者&#xff1a;嗨小易&a…

视频平台跨网级联视频压缩解决方案

一、 简介 视频监控领域对带宽有着较大的需求&#xff0c;这是因为视频流需要实时占用网络带宽资源。视频监控的传输带宽是组网结构的基础保障&#xff0c;关系到视频监控的稳定性、可靠性和可拓展性等因素。例如&#xff0c;720P的视频格式每路摄像头的比特率为2Mbps&#xff…

【机器学习合集】模型设计之网络宽度和深度设计 ->(个人学习记录笔记)

文章目录 网络宽度和深度设计1. 什么是网络深度1.1 为什么需要更深的模型浅层学习的缺陷深度网络更好拟合特征学习更加简单 2. 基于深度的模型设计2.1 AlexNet2.2 AlexNet工程技巧2.3 VGGNet 3. 什么是网络宽度3.1 为什么需要足够的宽度 4. 基于宽度模型的设计4.1 经典模型的宽…

在IDEA运行spark程序(搭建Spark开发环境)

建议大家写在Linux上搭建好Hadoop的完全分布式集群环境和Spark集群环境&#xff0c;以下在IDEA中搭建的环境仅仅是在window系统上进行spark程序的开发学习&#xff0c;在window系统上可以不用安装hadoop和spark&#xff0c;spark程序可以通过pom.xml的文件配置&#xff0c;添加…

【洛谷算法题】P5710-数的性质【入门2分支结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5710-数的性质【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格式&a…

开源库存管理系统InvenTree的安装

本文是应网友 shijie880500 要求折腾的&#xff1b; 什么是 InvenTree &#xff1f; InvenTree 是一个开源的库存管理系统&#xff0c;提供强大的低级别库存控制和零件跟踪。InvenTree 系统的核心是 Python/Django 数据库后端&#xff0c;它提供了一个管理界面&#xff08;基于…

绝缘检测原理和绝缘电阻计算方法

文章目录 简介绝缘检测功能绝缘检测原理绝缘电阻检测的常用方法不平衡电桥法 绝缘电阻绝缘电阻的计算 绝缘检测开启或关闭为什么根据 V1 &#xff1c; V2 或 V1 ≥ V2 判断是上桥臂并入电阻还是下桥臂并入电阻 简介 绝缘检测是判断动力&#xff08;正、负&#xff09;总线与外…

Flutter三棵树的创建流程

一、Flutter常见的家族成员 Widget常见的家族成员 Element常见的家族成员 Render常见的家族成员 二、示例代码对应的Flutter Inspector树 示例代码&#xff1a;MyApp->MyHomePage->ErrorWidget&#xff0c;包含了StatelessWidget、StatefulWidget、LeafRenderObjectWid…

位运算与简单应用

一.位运算的基本概念&#xff1a; 首先&#xff0c;位运算是针对二进制的&#xff0c;(数字本来int,4字节,下面假设为1字节)。比如数字12&#xff0c;它的二进制本来是&#xff1a; 0000 0000 0000 0000 0000 0000 0000 1100 因为前面的数字大都是0&#xff0c;所以为了简写…

火影忍者游戏攻略大公开!成为忍者大师的秘诀揭秘

大家好&#xff01;作为火影忍者游戏的玩家&#xff0c;我们都希望能够在游戏中成为优秀的忍者大师&#xff0c;战胜强大的对手。为了帮助大家实现这一目标&#xff0c;我想分享一些实用的攻略和技巧。 首先&#xff0c;熟悉忍者技能是成为忍者大师的基础。在火影忍者游戏中&am…