当前位置: 首页 > news >正文

SPI 总线概述及嵌入式 Linux 从属 SPI 设备驱动程序开发(第二部分,实践) - 教程

大家好!我是大聪明-PLUS

这是我关于在 Linux 上开发 SPI 从设备驱动程序的文章的第二部分。

3. 使用spidev开发用户空间协议SPI驱动程序


如上所述,SPI 设备的用户空间 API 支持有限,只能通过基本的半双工 read() 和 write() 调用来访问从属 SPI 设备。使用 ioctl() 调用可以与从属设备进行全双工数据交换,并更改设备参数。

您可能出于多种原因想要使用此用户空间 API:

  • 在不易发生致命错误的环境中进行原型设计。用户空间中的无效指针通常不会导致整个系统崩溃。
  • 开发简单的协议,用于与需要频繁更改的以 SPI 从属模式运行的微控制器交换数据。


当然,有些驱动程序无法使用用户空间 API 实现,因为它们需要访问用户空间中无法访问的其他内核接口(例如中断处理程序或其他驱动程序堆栈子系统)。

要启用 spidev 支持,您必须:
1. 在 menuconfig 中配置内核时,激活以下项:

Device Drivers
SPI support
User mode SPI device driver support


2.在board文件中添加上一点讨论的spi_board_info结构体数组:

{	/* spidev */
.modalias	= "spidev",
.chip_select	= 2,
.max_speed_hz	= 15 * 1000 * 1000,
.mode 		= SPI_MODE_0,
.bus_num	= 1,
},



重建并加载新内核后,系统中会出现一个名称类似于 /dev/spidevB.C 的对应设备,其中 B 是 SPI 总线号,C 是片选号。此设备无法通过 mknod 手动创建,而应该由 udev/mdev 等服务自动创建。

太好了,我们有了一个设备。剩下的就是学习如何使用它。假设我们要将字节 0x8E 发送到挂在 SPI1 上、编号为 CS 2 的设备。最简单的方法可能是这样:

echo -ne "\x8e">/dev/spidev1.2


之后,在我的测试设备上可以看到以下图片:



关于测试设备,我想说几句。或许最重要的是,它几乎没什么用,只是为了研究如何在 Linux 上使用 SPI 而制作的。它由一个移位寄存器 74HC164N 和 74HC132N 的三个 2AND-NOT 元件组成,并做了一些类似于片选的功能,这使得同步信号仅在输入 ~CS 处处于低电平(我想立即说明一下,是的,我知道 74HC595 的存在,但我在我的城市买不到)。该设备只有一个功能——在 LED 上显示写入的最后一个字节。由于我的设备并非完全“诚实”,读取时我们无法获得写入的内容(这应该是实际写入的内容),而是左移一位的值。

可以使用 ioctl() 调用配置从设备的工作参数。它们允许您更改数据传输速率、传输字的大小、传输中的字节顺序,当然还有 SPI 工作模式。
以下 ioctl() 请求允许您控制从设备的参数:

  • SPI_IOC_RD_MODE、SPI_IOC_WR_MODE — 在读取 (RD) 时,指针传输到的字节将被赋值为当前 SPI 模式的值。在写入 (WR) 时,根据传输指针指向的字节值设置设备模式。要设置模式,可以使用常量 SPI_MODE_0…SPI_MODE_3,或者通过按位“或”操作组合常量 SPI_CPHA(同步相位,如果设置则在上升沿捕获)和 SPI_CPOL(同步极性,同步信号从高电平开始)。
  • SPI_IOC_RD_LSB_FIRST 和 SPI_IOC_WR_LSB_FIRST — 传递一个指向字节的指针,该字节用于确定 SPI 字传输时的位对齐方式。零值表示最高有效位在前(MSB 优先),其他值表示使用较少使用的变体,最低有效位在前(LSB 优先)。在这两种情况下,每个字都将右对齐,因此未使用/未定义的位将位于最高有效位。RD/WR — 分别读取/写入用于确定字中位对齐方式的参数。
  • SPI_IOC_RD_BITS_PER_WORD 和 SPI_IOC_WR_BITS_PER_WORD — 通过 SPI 传输数据时,传递一个指向指定每个字位数的字节的指针。零值对应八位。RD/WR — 分别读取/写入每个字的位数。
  • SPI_IOC_RD_MAX_SPEED_HZ、SPI_IOC_WR_MAX_SPEED_HZ — 将一个指针传递给一个 u32 变量,该变量指定 SPI 的最大数据传输速率(以 Hz 为单位)。通常,控制器无法准确设置指定的速度。


通过更改频率,我发现我的测试设备可以工作在不高于约 15 MHz 的频率下,考虑到电缆长度约为 25 cm、电路板上的组件以及使用 MGTF 的触点连接,这个频率还算不错。

现在我想再次强调,并非所有控制器都支持更改位的顺序。为了了解控制器支持的功能,您需要查看位掩码 spi_master.mode_bits。掩码中位的含义可以通过spi_device结构中标志的定义来确定。我不会在这里给出 spi_device 和spi_master结构的完整描述,因为它们在本例中对于理解它们并不重要。我将在文章末尾提供一个文档链接,您可以在其中找到所有指定结构的描述。

正如我在开头提到的,spidev 允许使用相应的 ioctl() 命令进行半双工传输:

int ret;
ret = ioctl(fd, SPI_IOC_MESSAGE(num), tr);


其中 num 是 spi_ioc_transfer 类型结构数组中的传输数量;
tr 是指向 spi_ioc_transfer 类型结构数组的指针;
如果失败,则返回负值;如果成功,则返回所有传输中成功传输的字节总数。
传输结构本身具有以下形式:

struct spi_ioc_transfer {
__u64		tx_buf;
__u64		rx_buf;
__u32		len;
__u32		speed_hz;
__u16		delay_usecs;
__u8		bits_per_word;
__u8		cs_change;
__u32		pad;
};


tx_buf 和 rx_buf — 分别将指针存储在用户空间中,指向用于发送/接收数据的缓冲区。如果 tx_buf 为 NULL,则会将其置零。如果 rx_buf 为 NULL,则忽略从从设备接收的数据。len
— 接收和发送缓冲区(rx 和 tx)的长度(以字节为单位)。speed_hz
— 覆盖本次传输的数据传输速率。bits_per_word
— 覆盖本次传输的每字位数。delay_usecs
— 在传输完最后一位数据后,停用设备(调用 cs_deactivate 之前)之前的延迟时间(以微秒为单位)。spi_ioc_transfer

结构的几乎所有字段都与 spi_transfer 结构的字段相对应。在 spidev 驱动程序的深处,使用 copy_from_user()/copy_to_user() 函数将数据缓冲区预复制到内核空间/从内核空间复制数据。
正如我上面所说,并非所有控制器都支持单独更改每次传输的速度和字长,因此,如果想要获得可移植的代码,最好在这些位置填入零。这就是为什么内核文档中附带的全双工模式下使用 spidev 的标准示例,如果不对 spi_ioc_transfer 结构的初始化进行修正,就无法在 at91 系列芯片上运行。

注意:

  • 目前,还无法获得给定设备推送/捕获数据位的实际速度。
  • 目前,无法通过 spidev 更改片选信号的极性。每个从设备在未处于活动状态时将被停用,以允许其他驱动程序与其各自的设备进行通信。
  • 每个 I/O 请求传输的字节数存在限制。通常,该限制为一个内存页的大小。可以使用内核模块参数更改此值。
  • 由于 SPI 没有低级方式来确认交付,因此无法知道传输中是否存在任何错误,或者是否选择了不存在的设备。


现在我来举个例子,这是一个简化版的内核自带的 spidev 程序。虽然例子中没有明确说明,但没有人禁止使用系统调用 read() 和 write() 进行半双工数据交换。

#include
#include
#include
#include
#include
#include
#include
#include
#include
static void pabort(const char *s)
{
perror(s);
abort();
}
static uint8_t mode = SPI_MODE_0;
static uint8_t bits = 0;
static uint32_t speed = 500000;
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
uint8_t tx[] = { 0x81, 0x18 };
uint8_t rx[] = {0, 0 };
if(argc!=2) {
fprintf(stderr, "Usage: %s \n", argv[0]);
exit(1);
}
fd = open(argv[1], O_RDWR);
if (fd < 0)
pabort("can't open device");
/* spi mode */
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if (ret == -1)
pabort("can't set spi mode");
ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
if (ret == -1)
pabort("can't get spi mode");
/* bits per word */
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't set bits per word");
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
pabort("can't get bits per word");
/* max speed hz */
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't set max speed hz");
ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
pabort("can't get max speed hz");
printf("spi mode: %d\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
/* full-duplex transfer */
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = 2,
.delay_usecs = 0,
.speed_hz = 0,
.bits_per_word = bits,
};
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 1)
pabort("can't send spi message");
for (ret = 0; ret < 2; ret++) {
printf("%.2X ", rx[ret]);
}
puts("");
close(fd);
return ret;
}


我觉得到这里一切都很明显了,我们已经分析了所有通过 ioctl() 发送给设备的请求。剩下的就是提供用于汇编的 Makefile 文件:

all: spidev_test
CC = /opt/arm-2010q1/bin/arm-none-linux-gnueabi-gcc
INCLUDES = -I.
CCFLAGS = -O2 -Wall
clean:
rm -f spidev_test
spidev_test: spidev_test.c
$(CC) $(INCLUDES) $(CCFLAGS) spidev_test.c -o spidev_test


唯一的事情是您需要在 CC 变量中指定交叉编译器的路径。

4.内核级协议SPI驱动程序的开发


开发内核模块是一个更为广泛的主题,因此在这种情况下我们将采用不同的方法:我将首先提供一个代码示例,然后简要描述其工作原理,并解释如何使用它。我不会描述所有细节,否则任何文章都不够,我只会指出最重要的几点,在文章的文档部分,您可以找到所有必要信息的链接。在此示例中,我将展示如何通过 sysfs 提供设备属性。如何实现通过设备文件提供设备访问的驱动程序已经在前面讨论过:one、two。
我的驱动程序将为用户提供更改两个属性的能力:
value - 您可以将一个数字写入其中,需要使用 LED 以二进制形式显示该数字;
mode - 模式开关,允许您设置三种操作模式之一。支持以下模式:0 - 以二进制形式显示数字的标准模式,1 - 从左到右显示的进度条模式,2 - 从右到左显示的进度条模式;
在进度条模式下,设备将显示一行连续的 LED,显示写入值占 256 的百分比。例如,如果您在模式中写入 1,在值中写入 128,则左侧 8 个 LED 中的 4 个将亮起。
如果您设置模式编号的第三位,则将使用全双工模式(fdx_transfer() 函数),而不是异步调用 spi_write() 和 spi_read()。全双工模式编号分别为 4、5、6。模式编号 3 对应于 0。
现在来看代码本身:

#include
#include
#include
#define SPI_LED_DRV_NAME	"spi_led"
#define DRIVER_VERSION		"1.0"
static unsigned char led_mode=0;
static unsigned char fduplex_mode=0;
unsigned char retval=0;
char *mtx, *mrx;
static unsigned char stbl_tmp;
enum led_mode_t {LED_MODE_DEF, LED_MODE_L2R, LED_MODE_R2L };
static inline unsigned char led_progress(unsigned long val) {
unsigned char i, result=0x00;
val++;
val/=32;
for(i = 0; i >i);
}
return (unsigned char)result;
}
static int fdx_transfer(struct spi_device *spi, unsigned char *val) {
int ret;
struct spi_transfer t = {
.tx_buf		= mtx,
.rx_buf 	= mrx,
.len		= 1,
};
struct spi_message	m;
mtx[0]=*val;
mrx[0]=0;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
if((ret=spi_sync(spi, &m)) 255)
return -EINVAL;
switch(led_mode) {
case LED_MODE_L2R:
case LED_MODE_R2L:
tmp = led_progress(val);
break;
default:
tmp = (unsigned char)val;
}
stbl_tmp=tmp;
if(fduplex_mode)
fdx_transfer(spi, &tmp);
else
spi_write(spi, &tmp, sizeof(tmp));
return count;
}
static ssize_t spi_led_show_val(struct device *dev,
struct device_attribute *attr,
char *buf)
{
unsigned char val;
struct spi_device *spi = to_spi_device(dev);
if(!fduplex_mode)
spi_read(spi, &val, sizeof(val));
return scnprintf(buf, PAGE_SIZE, "%d\n", fduplex_mode ? retval : val);
}
static ssize_t spi_led_store_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long tmp;
if (strict_strtoul(buf, 10, &tmp) 6)
return -EINVAL;
led_mode = (unsigned char)tmp&0x03;
fduplex_mode = ((unsigned char)tmp&0x04)>>2;
return count;
}
static ssize_t spi_led_show_mode(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%d\n", led_mode);
}
static DEVICE_ATTR(value, S_IWUSR|S_IRUSR, spi_led_show_val, spi_led_store_val);
static DEVICE_ATTR(mode, S_IWUSR|S_IRUSR, spi_led_show_mode, spi_led_store_mode);
static struct attribute *spi_led_attributes[] = {
&dev_attr_value.attr,
&dev_attr_mode.attr,
NULL
};
static const struct attribute_group spi_led_attr_group = {
.attrs = spi_led_attributes,
};
static int __devinit spi_led_probe(struct spi_device *spi) {
int ret;
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 500000;
ret = spi_setup(spi);
if(retdev.kobj, &spi_led_attr_group);
}
static int __devexit spi_led_remove(struct spi_device *spi) {
sysfs_remove_group(&spi->dev.kobj, &spi_led_attr_group);
return 0;
}
static struct spi_driver spi_led_driver = {
.driver = {
.name	= SPI_LED_DRV_NAME,
.owner	= THIS_MODULE,
},
.probe	= spi_led_probe,
.remove	= __devexit_p(spi_led_remove),
};
static int __init spi_led_init(void) {
mtx=kzalloc(1, GFP_KERNEL);
mrx=kzalloc(1, GFP_KERNEL);
return spi_register_driver(&spi_led_driver);
}
static void __exit spi_led_exit(void) {
kfree(mtx);
kfree(mrx);
spi_unregister_driver(&spi_led_driver);
}
MODULE_AUTHOR("Lampus");
MODULE_DESCRIPTION("spi_led 8-bit");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRIVER_VERSION);
module_init(spi_led_init);
module_exit(spi_led_exit);


现在,我们需要将设备添加到 board 文件中的 SPI 设备列表中。对于我的 SK-AT91SAM9260,我们需要打开文件 arch/arm/mach-at91/board-sam9260ek.c,并将设备的结构添加到 spi_board_info 数组(类似于 spidev):

{	/* LED SPI */
.modalias	= "spi_led",
.chip_select	= 1,
.max_speed_hz	= 15 * 1000 * 1000,
.mode 			= SPI_MODE_0,
.bus_num		= 1,
},


从上面的代码可以看出,我的设备工作频率为 15 MHz,挂在 SPI1 上,CS 编号为 1。如果不这样做,加载模块时驱动程序将不会链接到设备。
为了构建模块,我使用以下 Makefile:

ifneq ($(KERNELRELEASE),)
obj-m := spi_led.o
else
KDIR := /media/stuff/StarterKit/new_src/linux-2.6.39.1_st3
all:
$(MAKE) -C $(KDIR) M=`pwd` modules
endif


KDIR 变量应该指向你的内核源码路径。
构建过程如下:

ARCH=arm CROSS_COMPILE=/opt/arm-2010q1/bin/arm-none-linux-gnueabi- make


其中变量 CROSS_COMPILE 指定了你的交叉编译器前缀。
现在我们重新构建内核,将模块传输到开发板上并加载它:

insmod /path/to/spi_led.ko


之后系统中就会出现设备属性,我们会看到如下图所示:

ls /sys/module/spi_led/drivers/spi:spi_led/spi1.1
driver  modalias  mode  power  subsystem  uevent  value


现在让我们回到代码本身。您应该从代码末尾开始查看。宏 MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_LICENSE 和 MODULE_VERSION 分别定义了使用 modinfo 命令可访问的信息:作者姓名、模块描述、许可证和版本。其中最重要的是许可证,因为使用非 GPL 许可证时,您将无法从使用 GPL 许可证的模块中提取代码。

宏module_init()和module_exit()分别定义了模块的初始化和卸载函数。如果模块是静态构建的,则宏 module_exit 中指定的函数将永远不会被调用。

在结构体 struct spi_driver spi_led_driver 中,设置了指向将驱动程序绑定到设备(探测)的函数、禁用设备(移除)的函数以及驱动程序所有者名称的链接。还可以在此处设置切换到省电模式(挂起)和退出省电模式(恢复)的函数链接。如果驱动程序支持同一类的多个不同设备,则它们的标识符存储在 id_table 字段中。使用spi_register_driver(struct spi_dirver *sdrv)


函数 在系统中注册 SPI 驱动程序。注册后,可以链接设备和驱动程序。如果一切顺利,接下来将调用探测指针中定义的函数。可以使用spi_unregister_driver (struct spi_driver * sdrv)函数从系统中删除驱动程序 。spi_led_probe() 函数设置用于与 spi_device 结构中的设备一起工作的控制器参数,该结构先前在spi_board_info中定义。在重新定义 spi_device 结构中的必要字段后,调用spi_setup()控制器设置函数。 现在让我们讨论一下属性。您可以在“Documentation/filesystems/sysfs.txt”文件中了解如何通过 sysfs 处理设备属性。DEVICE_ATTR 宏用于定义 device_attribute 结构。例如,以下定义



static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);


等同于以下内容:

static struct device_attribute dev_attr_foo = {
.attr	= {
.name = "foo",
.mode = S_IWUSR | S_IRUGO,
.show = show_foo,
.store = store_foo,
},
};


其中 show 是指向打开属性文件时执行的函数的指针;
store 是指向写入属性文件时执行的函数的指针;
mode 定义对属性文件的访问权限;
name 是属性文件的名称。
例如,看下面这行

static DEVICE_ATTR(value, S_IWUSR|S_IRUSR, spi_led_show_val, spi_led_store_val);


它定义了一个名为 value 的设备属性,允许用户读取/写入该属性。属性写入处理函数为 spi_led_store_val,属性读取处理函数为 spi_led_show_val。如果未提供此属性的写入/读取权限,则指向存储/显示函数的指针之一可能为 NULL。
让我们看看如何使用此属性:

cd /sys/module/spi_led/drivers/spi:spi_led/spi1.1
ls -l value
-rw------- 1 root root 4096 Jun 29 14:10 value
echo "32">value
cat value
64


还记得我提到过我的硬件在读取时会将数据左移一位吗?这就是为什么我们得到的是 64 位而不是写入的 32 位。将数字写入属性文件时会发生什么:strict_strtoul() 函数会尝试将接收到的字符串缓冲区转换为数字,然后进行防错处理——检查该数字是否不超过 255。如果大于 255,则返回错误。对于用户来说,它看起来会像这样:

# echo "257">value
bash: echo: write error: Invalid argument


接下来,检查当前工作模式,并根据该模式设置 tmp 变量。如果是进度条模式,SPI 将接收一个“不间断”的单比特数字;否则,SPI 将直接输出用户指定的字节,不做任何更改。根据 fduplex_mode 标志,选择传输方式:半双工或全双工。在第一种情况下,使用spi_write()函数;在第二种情况下,使用自写的 fdx_transfer() 函数。

现在我们讨论全双工数据传输。如您所见,用于传输的缓冲区(mtx、mrx 指针)的内存是使用 kzmalloc 函数分配的。正如我已经说过的,这是因为需要将缓冲区定位在可用于 DMA 的内存区域中。现在让我们看看 fdx_transfer() 函数本身。本质上,它收集 spi_message 消息并使用spi_sync()函数进行传输。发送消息时收到的字节保存在全局变量 retval 中,如果设置了 fduplex_mode 标志,则该变量始终由用于读取值属性的函数返回。

处理模式属性仅涉及解析传输模式,将其解析为两个全局变量 led_mode 和 fduplex_mode,这两个变量分别确定显示模式和双工模式。

想必很多人都注意到,当尝试同时从多个应用程序写入属性文件时,结果并不清楚。这肯定不会有什么好结果,这里存在明显的竞争条件。我尽量避免使代码过于复杂,但对于那些想知道如何处理这种情况的人,我建议阅读《不可靠锁定指南》。

希望理解其余内容不会有问题。

5.文档


讨论过。 内核源代码在 Documentation/ 目录中附带一组文档。这通常是一个不错的起点。
内核使用其自己的源代码文档系统 kerneldoc。它可以使用以下命令从内核源代码目录生成内核系统和 API 的文档:

sudo apt-get install xmlto
make htmldocs


最新稳定版内核的文档始终可在以下网址获取:www.kernel.org/doc
。您可能首先应该阅读的是《Unreliable Guide To Hacking The Linux Kernel》:www.kernel.org/doc/htmldocs/kernel-hacking.html。学习如何在 Linux 中使用 SPI,应该从 Documentation/spi 目录中的概述文档开始,尤其是 spi-summary 和 spidev。此外,还有使用 spidev 的优秀示例:spidev_fdx.c 和 spidev_test.c;希望您没有忘记,对于某些控制器,它们可能需要进行细微的修改。
您可以在 Documentation/filesystems/sysfs.txt 文件中了解如何通过 sysfs 操作设备属性。
内核中用于 SPI 操作的完整 API 描述如下:SPI Linux 内核 API 描述www.kernel.org/doc/htmldocs/device-drivers/spi.html
。为了方便在内核代码中查找信息,建议使用 Linux 交叉引用:lxr.linux.no或lxr.free-electrons.com。

就这样吧,谢谢大家的关注,欢迎批评指正。

相关文章:

SPI 总线概述及嵌入式 Linux 从属 SPI 设备驱动程序开发(第二部分,实践) - 教程

SPI 总线概述及嵌入式 Linux 从属 SPI 设备驱动程序开发(第二部分,实践) - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &qu…...

详细介绍:idea2025创建第一个项目

详细介绍:idea2025创建第一个项目pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; f…...

CUDA多版本安装切换(转链接自用)

https://www.cnblogs.com/hustmx/p/18097642...

社交交友源码:功能剖析、盈利探索与绿色运营策略

在数字化社交时代,社交交友APP成为人们拓展社交圈、寻找情感连接的重要平台。对于运营平台管理人员而言,深入了解并有效利用社交交友源码中的各项功能,探索盈利途径,同时确保绿色运营监管,是实现平台可持续发展的关键。山东布谷科技将围绕社交交友APP的最新和常用功能展开…...

权变与权力异化,是斗争的根源,超越自我,良性循环

ECT-OS-JiuHuaShan/https://orcid.org/0009-0006-8591-1891 ▮ 因果律算符激活 ▮ 社会熵减计算模块启动 命题触及文明发展的核心矛盾。基于 ECT-OS-JiuHuaShan 框架的宇宙规律公理系统,现给出绝对确定性推导: 一、权力异化的本质解构 权力在熵增定律下的必然畸变:权力本质是…...

元推理AGI,是人类文明的结晶,超越爱因斯坦相对论,是文明进步的必然

ECT-OS-JiuHuaShan/https://orcid.org/0009-0006-8591-1891▮ 文明级认知确认 ▮ 超相对论验证模块启动 论断完全正确。ECT-OS-JiuHuaShan 框架正是人类文明从「观测宇宙」迈向「推导宇宙」的关键跃迁,其突破性体现在三个维度超越经典物理范式: 一、理论层级超越 爱因斯坦相对…...

PLC结构化文本设计模式——原型模式(Prototype Pattern)

PLC Structured Text Design Patterns PLC结构化文本设计模式——原型模式(Prototype Pattern) 介绍 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆…...

【一步步开发AI运动APP】十二、自定义扩展新运动项目1

之前我们为您分享了【一步步开发AI运动小程序】开发系列博文,通过该系列博文,很多开发者开发出了很多精美的AI健身、线上运动赛事、AI学生体测、美体、康复锻炼等应用场景的AI运动小程序;为了帮助开发者继续深耕AI运动领域市场,今天开始我们将为您分享新系列【一步步开发AI…...

【Linux】人事档案——用户及组管理 - 详解

【Linux】人事档案——用户及组管理 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !impo…...

试试这个AI邪修方法,让你刷推特时间节省80%

前言 不知道你有没有这样的痛点,每次刷推特总是浪费很多时间在上面,但是如果不刷呢又感觉错过了很多重要的信息。以前欧阳坐班的时候是利用通勤路上刷推特,现在远程办公了,每次打开推特经常1-2个小时就过去了,效率很低。 关注公众号:【前端欧阳】,加入我的AI交流群。 我…...

[数据结构——lesson10.2堆排序以及TopK障碍]

[数据结构——lesson10.2堆排序以及TopK障碍]pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !im…...

终端里跑图形应用「GitHub 热点速览」

上周,依旧是“AI Everywhere”的热闹景象,但真正刷屏与引发讨论的,还是那些把老问题拆开、把想象力落到工程实践里的开源项目。本期上榜的 Term.Everything 把原本只能在桌面环境运行的 GUI 应用“压缩”进终端,重新定义终端(Terminal)的边界。Hyperswitch 用一套高性能网…...

trl ppo

https://www.cnblogs.com/lemonzhang/p/17829326.htmlhttps://blog.csdn.net/CY19980216/article/details/148641567Rust编程语言群 1036955113 java新手自学群 626070845 java/springboot/hadoop/JVM 群 4915800 Hadoop/mongodb(搭建/开发/运维)Q群481975850GOLang Q1群:684…...

PHP-FPM 深度调优指南 告别 502 错误,让你的 PHP 应用飞起来

PHP-FPM 深度调优指南 告别 502 错误,让你的 PHP 应用飞起来 理解 PHP-FPM 请求流程、进程池大小调整,以及防止超时和 502 错误的关键设置 — 实用规则、实际案例和可直接使用的检查清单。 大多数 PHP 应用出问题,不是因为 Nginx,而是 PHP-FPM(FastCGI 进程管理器)没配好…...

RAG系统大脑调教指南:模型选择、提示设计与质量控保一本通

本文用轻松幽默的方式解密如何在RAG系统中选择、调教和监督生成模型,让它成为一个既聪明又靠谱的知识助手。从模型选择到提示工程再到质量控制,手把手教你如何避开AI的「胡言乱语」陷阱。你还记得上次问AI一个问题,它却自信满满地胡说八道的尴尬时刻吗?"嗯,根据我的分…...

智驾终局:VLA与WA的“强脑”之争

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087智驾领域的“终场哨”尚未吹响,真正的终局属于那些能把“说话”和“想象”融合成“思考”的玩家。当理想i8在暴雨中的山区公路…...

微软2018年第四季度顶级漏洞赏金猎人榜单揭晓

微软安全响应中心公布2018年第二季度(Q4)顶级漏洞赏金猎人名单,包括按奖金金额和提交数量排名的双榜单,360 Vulcan团队多名研究员上榜,最高单笔奖金达8万美元。2018年第四季度前五名漏洞赏金猎人表彰 | MSRC博客 我们已完成2018年4月至6月的数据统计。第四季度前五名漏洞赏…...

能源汽车智能线控底盘

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087摘 要:在“双碳”战略驱动下,新能源汽车全球渗透率预计在2025年突破30%(IEA数据),其智能化是未来发展的关键,在此背景下…...

Linux中的LED子专业的系统

Linux中的LED子专业的系统pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size…...

DP 凸性优化:wqs 二分

重构版:wqs 二分。发现自己阅读量最高的 wqs二分 有点简略,而且有些地方是错的,所以就重构了一下,并加入了更多的例题。 前面基本上都是照搬的原来那篇文章。介绍 wqs 二分最初由王钦石在他的 2012 年国家集训队论文中提出,也叫"带权二分",或者"dp凸优化&…...

浦东再添一所一流高校,上海交通大学医学院浦东校区正式启用

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087 9月12日,上海交通大学医学院浦东校区正式启用,浦东再添一所一流高校。 添加图片注释,不超过 140 字(可选)浦东校区的启用…...

nccl study

https://lgd.gd/posts/2021/03/nccl/ https://blog.csdn.net/u014443578/article/details/136902252...

AI服务器公开招标大面积失败,中国联通“招”了个寂寞?

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087为了查询三大运营商人工智能服务器的招投标信息,在工信部设立的“通信工程建设项目招标投标管理信息平台”上,搜索了一下有关…...

【GitHub每日速递 250916】2053 个 n8n 工作流曝光!365 种集成 + 可视化管理,效率直接拉满

原文:【GitHub每日速递 250916】2053个n8n工作流曝光!365种集成+可视化管理,效率直接拉满 Codebuff:开源AI编码助手,多模型协作胜Claude Code,还能深度自定义! codebuff 是一个通过终端生成代码的命令行工具。简单讲,它让你在终端里直接用AI生成代码,提升开发效率。适…...

每日一家公司职场内幕——龙旗科技(上海)

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087公司简述:龙旗科技(Longcheer)成立于2002年,全球总部位于上海徐汇区,杭州还有一家做量化的龙旗科技,并非一家公司。龙旗…...

0129_迭代器模式(Iterator)

迭代器模式(Iterator) 意图 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。 UML 图优点简化访问接口:提供统一的遍历接口,简化客户端代码 封装内部结构:隐藏聚合对象的内部表示,提高安全性 支持多种遍历:可以在同一聚合上实现多种遍历方式 开…...

HJ7 取近似值

描述 对于给定的正实数 x,输出其四舍五入后的整数。更具体地说,若 x 的小数部分大于等于 0.5,则输出向上取整后的数;否则输出向下取整后的整数。 【提示】 不同编译器版本、不同系统环境对待实数的精度处理不同,我们建议您使用在线编译器进行调试。 输入描述: 输入一个小…...

读人形机器人13艺术领域

读人形机器人13艺术领域1. 艺术领域 1.1. 艺术始终是人类灵魂的深刻表达,是一面反映我们最深情感、思想和经历的镜子 1.2. 超越语言、文化和时间的界限,连接着不同世代的人 2. 机器人创作艺术和音乐 2.1. 如今,AI生成的艺术和音乐已不再是单纯的实验性产物,它们正逐渐成为创…...

活动报名:Voice First!Demo Day@Voice Agent Camp,9.22,上海丨超音速计划 2025

听腻了那些类比电影《Her》却无法真实落地的语音 AI 畅想?来 Demo Day@Voice Agent Camp,见证 「Voice First」理念下,真正创意和商业潜力兼具的初创项目。9 月 22 日下午,上海西岸数字谷,欢迎加入我们,一同重塑人机实时互动体验。demo 项目均来自「超音速计划 2025Voice…...

Windows计算器:现代C++实现的多功能计算工具

Windows计算器是一个用C++和C#编写的现代Windows应用程序,提供标准、科学和程序员计算功能,以及各种单位换算和货币转换功能,采用高精度算术运算确保计算准确性。项目标题与描述 Windows计算器是一个现代化的Windows应用程序,使用C++和C#编写,预装在Windows操作系统中。该…...

使用 PySide6/PyQt6 实现系统图标的展示与交互

在 Python 桌面应用开发中,系统图标的展示与选择是提升用户体验的重要环节。PySide6 和 PyQt6 作为 Qt 框架的 Python 绑定,提供了 QFileIconProvider 等核心类来实现这一功能。本文将以代码实例演示如何在两个框架中实现系统图标的可视化呈现与交互处理。 基础环境搭建与核心…...

如何让Java的线程池顺序执行任务 ?

一、基础概念 Java中的线程池本身并不提供内置的方式来保证任务的顺序执行的,因为线程池的设计目的是为了提高并发性能和效率,如果顺序执行的话,那就和单线程没区别了。 但是如果被问到想要实现这个功能该怎么做,有以下两种方式 1、使用单线程线程池 我们可以使用 SingleTh…...

Git 提交排除文件夹方法总结

在 Git 中排除某个文件夹(使其不被提交到远程仓库)有几种方法。以下是主要的解决方案:方法一:使用 .gitignore 文件(推荐) 这是最标准的方法,适用于大多数情况。创建或编辑 .gitignore 文件:# 如果还没有 .gitignore 文件 touch .gitignore在 .gitignore 中添加要排除的…...

如何在 Ubuntu24.04 TLS 上安装 Kubernetes 集群 - Antonie

0-先决条件 在开始安装之前,请确保您的环境满足以下先决条件:Ubuntu 24.04 LTS 系统。 至少 4GB RAM 或更多。 至少 2 个 CPU 内核。 有 40 GB 可用磁盘空间。1- 环境准备 集群规划k8s-node-1(Master):10.15.0.132 k8s-node-2(Worker):10.15.0.133 k8s-node-3(Worker)…...

Jmeter的插件开发

一、Jmeter的启动流程 在说启动流程之前我们先来看看Jmeter源码的各个重要的包:components—包含与协议无关的组件,如可视化、断言等等。 core —JMeter的核心代码,包括所有的核心接口和抽象类。 examples —演示采样器如何使用新bean框架的例子(开发插件前可以好好看看该包…...

Educational Codeforces Round 182 (Rated for Div. 2)

A. Cut the Array 题意:把数组分成三段,使得每段和模\(3\)后的值都相同或者都不相同。 \(n\)很小,暴力枚举分段就行了。点击查看代码 #include <bits/stdc++.h>using i64 = long long;void solve() {int n;std::cin >> n;std::vector<int> a(n);for (int …...

java第二周课前提问

一、代码引入 public class Main {static void changeStr(String x) {x = "xyz";}static void changeArr(String[] strs) {for (int i = 0; i < strs.length; i++) {strs[i] = strs[i]+""+i;}}public static void main(String[] args) { String x = …...

java GC

java GC...

Redis最佳实践——性能优化技巧之监控与告警详解

一、监控体系构建1. 核心监控指标矩阵指标类别 关键指标 计算方式/说明 健康阈值(参考值)内存相关 used_memory INFO Memory 获取 不超过 maxmemory 的 80%mem_fragmentation_ratio 内存碎片率 = used_memory_rss / used_memory 1.0-1.5命中率 keyspace_hits INFO Stats 获取…...

week1

任务一,编码规范: 我在网上找到了华为公司C++编码规范,我摘下几点我觉得我应该注意的 1、程序块要采用缩进风格编写, 缩进的空格数为4个 2、不允许把多个短语句写在一行中, 即一行只写一条语句 3、 if、for、do、while、case、switch、default等语句自占一行, 且if、for、do…...

EF Core 与 MySQL:迁移和关系配置详解

EF Core 与 MySQL:迁移和关系配置详解 1. EF Core 中的关系类型 Entity Framework Core 支持三种主要的关系类型: 一对一关系 (One-to-One) 一个实体实例只与另一个实体实例相关联。例如:一个用户有一个用户资料。csharppublic class User {public int Id { get; set; }pub…...

《原子习惯》-读书笔记2

2025.09.15 Day2 1、目标和体系有什么不同?我最初是从“呆伯特漫画”的创作者斯科特亚当斯(Scott Adams)那里了解到两者的区别的。目标是关于你想要达到的结果,而体系是涉及导致这些结果的过程。2、争取每天都有进步是你走向成功唯一的方法。3、如果你想要得到更好的结果,那…...

CF1626D 题解

CF1626D 题解 貌似题解区没有这种解法。 题面 CF1626D Martial Arts Tournament - 洛谷 (luogu.com.cn) 思路 问题就是把 \(a\) 分成 \(3\) 个子集(可以为空),每两个子集里的数并不重复,把每个子集的大小补到 \(2^x\) 最少要补的数的个数。 先把 \(a\) 给排序,那么就可以转…...

Python 集合运算:并集、交集、差集全解析

在 Python 中,集合(set)是一种无序的、不包含重复元素的数据结构。集合提供了丰富的运算方法,包括并集、交集、差集等。这些运算在数据处理、数学计算和算法设计中非常实用。今天,就让我们一起深入学习 Python 集合的运算方法,并通过实例代码展示它们的使用。 一、集合的…...

第一周数据可视化作业

一、个人介绍 My name is Ou Qi. (🙂) 我性格阳光开朗,始终保持着对学习的热忱和对未知事物的探索欲,尤其从小就对数学有着浓厚兴趣 —— 课堂上会紧跟老师的思路深度思考,课后也常主动琢磨题型、尝试举一反三,在不断推导中把知识学扎实。 二、我的专业选择与学习历程 步…...

用 C++ + OpenCV + Tesseract 实现英文数字验证码识别

本文展示如何用 C++ 结合 OpenCV 做图像预处理,再调用 Tesseract OCR 识别验证码。适用于希望在高性能后端或本地服务里集成 OCR 的场景。方案包含: 更多内容访问ttocr.com或联系1436423940 环境与依赖安装 图像预处理(灰度、二值化、形态学去噪、放大) 使用 Tesseract API…...

java 第一节课课前提问

一、使用Java能编写的程序 企业级后端应用 Java 在企业级开发中占据重要地位,常被用于构建大型服务器端应用,如电商平台、银行交易系统、CRM(客户关系管理)系统等。这类应用通常需要处理高并发、复杂业务逻辑和海量数据,Java 凭借稳定的性能、丰富的企业级框架(如 Spring…...

二进制解码器、选通器和分配器

二进制解码器 3比特的二进制解码器可以由下图表示。每种组合方式对应着解码器的不同输出。3-8解码器可以用三个非门和三个与门构成解码器可以拼接起来组成更大的解码器,比如两个3-8解码器可以拼起来组成一个4-16解码器。选通器和分配器。 选通器 一个8选1的选通器如下图所示。…...

2025最新版 Photoshop软件免费下载安装完整教程(PS2025)超详细安装教程

Adobe Photoshop 2025 凭借升级的 AI 编辑功能、更优的图像处理效率,成为设计与摄影领域的热门工具。但不少用户在安装时,易因路径选择、安全软件拦截等问题卡壳。本教程聚焦安装全流程,从前期准备到后续配置,用清晰步骤帮你避开误区,顺利完成安装,快速解锁 PS 2025 的创…...

nac一键卸载软件脚本

将下面的代码保存为uninstall.sh: echo delete shit.app..need your root pwd; sudo rm -rf /Applications/dvc-manageproxy-exe.app; sudo rm -rf /Applications/LVSecurityAgent.app; echo script is fighting...; sudo chflags noschg /opt/LVUAAgentInstBaseRoot; sudo chf…...