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 convention
  struct termios tty;

  // Read in existing settings, and handle any error
  if(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 echo
  tty.c_lflag &= ~ECHOE; // Disable erasure
  tty.c_lflag &= ~ECHONL; // Disable new-line echo
  tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
  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

  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 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 9600
  cfsetispeed(&tty, B9600);
  cfsetospeed(&tty, B9600);

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

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

  // Allocate memory for read buffer, set size according to your needs
  char 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 VTIME
  int 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 read
  int bytes;
  ioctl(fd, FIONREAD, &bytes);
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎么检测开关电源质量的好坏?测试的方法是什么?

开关电源的工作原理 开关电源(简称SMPS)是常见的一种电源供应器&#xff0c;是高频化的电能转换装置&#xff0c;可以将电压透过不同形式的架构转换为用户端所需求的电压或电流。具有体积小、功耗小、效率高、高可靠性的特点&#xff0c;被广泛应用在工业、军工设备、科研设备、…

机器学习---支持向量机的初步理解

1. SVM的经典解释 改编自支持向量机解释得很好 |字节大小生物学 (bytesizebio.net) 话说&#xff0c;在遥远的从前&#xff0c;有一只贪玩爱搞破坏的妖怪阿布劫持了善良美丽的女主小美&#xff0c;智勇双全 的男主大壮挺身而出&#xff0c;大壮跟随阿布来到了妖怪的住处&…

Docker compose容器编排

Docker compose容器编排 1、Docker compose简介 docker-compose是docker的编排工具&#xff0c;用于定义和运行一个项目&#xff0c;该项目包含多个docker容器&#xff0c;在如今的微服务时代&#xff0c;一个项目会存在多个服务&#xff0c;使用docker一个个部署操作的话就会…

React中的状态管理

目录 前言 1. React中的状态管理 1.1 本地状态管理 1.2 全局状态管理 Redux React Context 2. React状态管理的优势 总结 前言 当谈到前端开发中的状态管理时&#xff0c;React是一个备受推崇的选择。React的状态管理机制被广泛应用于构建大型、复杂的应用程序&#xf…

计算机毕业设计选题推荐-超市售货微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

注册中心ZK、nameServer、eureka、Nacos介绍与对比

前言 注册中心的由来 微服务架构是存在着很多跨服务调用,每个服务都存在着多个节点,如果有多个提供者和消费者,当提供者增加/减少或者消费者增加/减少,双方都需要感知发现。所以诞生了注册中心这个中间件。 市面上有很多注册中心,如 Zookeeper、NameServer、Eureka、Na…

OpenCV 笔记(4):图像的算术运算、逻辑运算

Part11. 图像的算术运算 图像的本质是一个矩阵&#xff0c;所以可以对它进行一些常见的算术运算&#xff0c;例如加、减、乘、除、平方根、对数、绝对值等等。除此之外&#xff0c;还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有…

企业 Tomcat 运维 部署tomcat反向代理集群

一、Tomcat 简介 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c; Tomcat和Nginx、Apache(httpd)、Web服务器一样&#xff0c;具有处理HTML页面的功能不过Tomcat处理静态HTML的能力不如Nginx/Apache服务器 一个tomcat默认并…

ip划分与私公网ip、ip的传递

报文问路&#xff1a;1、不知道跳转默认路由器&#xff0c;2、知道路径&#xff0c;向对应路径发出报文&#xff0c;3、路口路由器&#xff0c;下一步就是目标主机在哪。 想要通信必须同在一个局域网&#xff0c;其实将公网就可以看作一个大型的局域网。 在同一个局域网内发送…

61. 旋转链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;李歘歘的博客 &#x1f3c6; &#x1f33a;每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点&#xff0c;以及职场小菜鸡的生活。&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&am…

恶意软件防范和拦截: 提供防范恶意软件攻击的策略

恶意软件&#xff0c;或者俗称的“病毒”&#xff0c;一直是IT领域的一个严重威胁。这些恶意软件可以窃取敏感信息、损害系统稳定性&#xff0c;甚至对企业和个人造成重大经济损失。在这篇博客文章中&#xff0c;我们将讨论如何防范和拦截恶意软件攻击&#xff0c;包括使用反病…

【unity/vufornia】Duplicate virtual buttons with name.../同一个ImageTarget上多个按钮失灵

问题&#xff1a;在同一个ImageTarget上添加多个按钮时无法触发对应按钮的事件。 解决过程&#xff1a; 1.查看报错&#xff1a;“Duplicate virtual buttons with name...”这一行&#xff0c;顾名思义&#xff0c;命名重复。 2.英文搜索到以下文章&#xff0c;应该在inspe…

算法通过村第十八关-回溯|青铜笔记|什么叫回溯(中篇)

文章目录 前言回溯的核心问题撤销操作解释总结 前言 提示&#xff1a;阳光好的时候&#xff0c;会感觉还可以活很久&#xff0c;甚至可以活出喜悦。 --余秀华 回溯是非常重要的算法思想之一&#xff0c;主要解决一些暴力枚举也搞不定的问题&#xff08;这里埋个坑&#x1f4a3;…

Kubernetes 概述以及Kubernetes 集群架构与组件

目录 Kubernetes概述 K8S 是什么 为什么要用 K8S K8S 的特性 Kubernetes 集群架构与组件 核心组件 Master 组件 Node 组件 ​编辑 Kubernetes 核心概念 常见的K8S按照部署方式 Kubernetes概述 K8S 是什么 K8S 的全称为 Kubernetes,Kubernetes 是一个可移植、可扩…

多种方法解决leetcode经典题目-LCR 155. 将二叉搜索树转化为排序的双向链表, 同时弄透引用变更带来的bug

1 描述 2 解法一: 使用list列表粗出中序遍历的结果&#xff0c;然后再依次处理list中的元素并且双向链接 public Node treeToDoublyList2(Node root) {if(rootnull)return root;Node dummynew Node(-10000);List<Node>ansnew ArrayList<>();dfs2(root,ans);Node p…

Maven本地配置获取nexus私服的依赖

场景 Nexus-在项目中使用Maven私服&#xff0c;Deploy到私服、上传第三方jar包、在项目中使用私服jar包&#xff1a; Nexus-在项目中使用Maven私服&#xff0c;Deploy到私服、上传第三方jar包、在项目中使用私服jar包_nexus maven-releases 允许deploy-CSDN博客 在上面讲的是…

stm32中断

目录 简介 什么是NVIC 中断优先级 EXTI 简介 总结 hal库初始化代码 标准库初始化代码 简介 什么是中断&#xff1f;正常情况下&#xff0c;微处理器根据代码内容&#xff0c;按顺序执行指令。执行过程中&#xff0c;如果遇到其它紧急的事件需要处理&#xff0c;则先暂停当…

在ffmpeg中,如何把h264转换为rgb格式

在ffmpeg中&#xff0c;网络视频流h264为什么默认的转为YUV而不是其他格式 文章中介绍了&#xff0c;h264解码的时候是直接解码为yuv的&#xff0c;如果在使用的过程中 需要用到rgb的格式&#xff0c;我们该如何来转换这种格式呢&#xff1f; 在上面的文章中&#xff0c;我们已…

[架构之路-252/创业之路-83]:目标系统 - 纵向分层 - 企业信息化的呈现形态:常见企业信息化软件系统 - 企业应用信息系统集成

目录 第一章 什么是企业应用信息系统集成What 1.1 简介 1.2 架构 二、为什么需要企业应用信息系统集成Why 三、如何实现企业应用信息系统集成 3.1 步骤 3.2 企业应用集成的层次 3.3 业务流程重组 第一章 什么是企业应用信息系统集成What 1.1 简介 企业应用信息系统集…

【项目源码解析】某3C产品自动光学检测系统

解决方案源码解析思维导图 一、带有桁架机械手的自动光学检测系统介绍 二、关于机械手运动控制&#xff08;是否需要机器人学方面的知识&#xff09; 机械手的运动控制不需要深入了解机器人学方面的知识的情况包括&#xff1a; 预配置和任务单一性&#xff1a;如果机械手已经预…