怎么看网站开发的发展/seo是什么意思中文翻译
到目前为止的学习笔记,已经介绍了Linux下的platform总线框架、I2C总线框架,本篇笔记将介绍Linux下的SPI总线框架。与I2C总线一样,SPI是物理总线,也是一种很常用的串行通信协议。本章就来学习如何在Linux下编写SPI总线接口的设备驱动。本章实验的最终目的就是驱动STM32MP1开发板上的ICM-20608这个SPI接口的六轴传感器,可以在应用程序中读取ICM-20608的原始传感器数据。
SPI&ICM-20608简介
SPI简介
SPI的基础知识在之前学习裸机开发的时候学过了,这里就不再赘述。
STM32MP1 SPI简介
STM32MP1自带的SPI全称为:Serial peripheral interface。SPI特性如下:
- 全双工同步串口接口。
- 半双工模式。
- 可配置的主/从模式。
- 支持I2S协议。
- 在达到FIFO阈值、超时、操作完成以及发生访问错误时产生中断。
- 允许16位,24位或者32位数据长度。
- 支持软件片选和硬件片选。
STM32MP1的SPI可以工作在主模或从模式,本章使用主模式,此芯片有6个SPI
,其中SPI1-3是支持I2S协议。在主模式下,可以选择硬件片选和软件片选,如果使用了硬件片选,那么每一个SPI只支持一个外设,软件片选就可以支持无数个外设,本章实验不使用硬件片选信号,因为硬件片选信号只能使用指定的片选IO,软件片选的话可以使用任意的IO。
ICM-20608简介
ICM-20608是InvenSense出品的一款6轴MEMS传感器,包括 3轴加速度和3轴陀螺仪。
ICM-20608尺寸非常小,只有3x3x0.75mm,采用16P的LGA封装。ICM-20608内部有一个512字节的FIFO。陀螺仪的量程范围可以编程设置,可选择±250,±500,±1000和±2000°/s,加速度的量程范围也可以编程设置,可选择±2g,±4g,±8g和±16g。陀螺仪和加速度计都是16位的ADC,并且支持I2C和SPI两种协议,使用I2C接口的话通信速度最高可以达到400KHz,使用SPI接口的话通信速度最高可达到8MHz。开发板上的ICM-20608通过SPI接口和STM32MP157连接在一起。ICM-20608特性如下:
- 陀螺仪支持X,Y和 Z三轴输出,内部集成16位ADC,测量范围可设置±250,±500,±1000和±2000°/s。
- 加速度计支持X,Y和Z轴输出,内部集成16位ADC,测量范围可设置±2g,±4g,±8g和±16g。
- 用户可编程中断。
- 内部包含512字节的FIFO。
- 内部包含一个数字温度传感器。
- 耐10000g的冲击。
- 支持快速I2C,速度可达400KHz。
- 支持SPI,速度可达8MHz。
ICM-20608的3轴方向如下图所示:
ICM-20608的结构框图如下图所示:
如果使用IIC接口的话,ICM-20608的AD0引脚决定I2C设备从地址的最后一位,如果AD0为0的话ICM-20608从设备地址是0X68,如果AD0为1的话ICM-20608从设备地址为0X69。
本章使用SPI接口,与之前的I2C驱动芯片AP3216C一样,ICM-20608也是通过读写寄存器来配置和读取传感器数据,使用SPI接口读写寄存器需要16个时钟或者更多 (如果读写操作包括多个字节的话),第一个字节包含要读写的寄存器地址,寄存器地址最高位是读写标志位,如果是读的话寄存器地址最高位要为1,如果是写的话寄存器地址最高位要为0,剩下的7位才是实际的寄存器地址,寄存器地址后面跟着的就是读写的数据。下图列出了本章实验用到的一些寄存器和位:
Linux下SPI驱动框架
SPI总线框架和I2C总线框架很类似,都采用了主机控制器驱动和设备驱动分离的思想;
主机控制器也就是SoC的SPI控制器,例如STM32MP1的SPI控制器;而设备驱动对应的则是挂在SPI总线下的从机设备驱动程序。主机控制器针对具体的SOC平台,例如STM32MP1对于同一个SOC平台来说,SPI控制器驱动程序是不用动的,不管外接的是什么SPI从机设备,对应的控制器驱动程序都一样,所以重点就落在了种类繁多的SPI从机设备驱动开发了。SPI控制器驱动程序一般是不需要驱动开发工程师自己编写,SOC厂商会提供相应的主机驱动程序。
在Linux内核当中,与I2C总线框架一样,SPI总线框架(也可以叫做SPI子系统)也可以分为三个部分:
- SPI核心层:SPI核心层是Linux的SPI子系统的核心代码部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销、管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。在Linux系统中,SPI核心层的代码位于drivers/spi/spi.c。
- SPI控制器驱动层:每种处理器平台都有自己的SPI控制器驱动程序,它的职责是为系统中的SPI总线实现相应的读写方法。例如 STM32MP1就有6个SPI,那么就有6个SPI控制器,每个控制器都有一条特定的SPI总线的读写。SPI子系统使用struct spi_master数据结构体来描述SPI控制器。在内核源码drivers/spi目录下有很多以spi-xxxx.c命名的源文件,如下图所示:
这些文件就是具体平台对应的SPI控制器驱动程序,使用SPI核心层提供的接口向SPI子系统注册SPI控制器。
- SPI设备驱动层:SPI从设备对应的驱动程序,比如一些SPI接口的芯片器件对应的驱动程序。
SPI主机驱动
SPI主机驱动就是SoC的SPI控制器驱动,类似I2C总线的适配器驱动。SPI子系统使用spi_master结构体来描述SPI控制器,其实spi_master是一个宏,这个宏定义在include/linux/spi/spi.h文件中,如下所示:
#define spi_master spi_controller
所以由此可以知道,spi_master就是spi_controller结构体,该结构体定义在include/linux/spi/spi.h文件中,如下所示:
示例代码 45. 2.1 spi_controller 结构体
424 struct spi_controller {
425 struct device dev; /* device 对象 */
426
427 struct list_head list;
......
435 s16 bus_num; /* SPI 总线编号 */
......
440 u16 num_chipselect; /* 片选 */
441
442 /* some SPI controllers pose alignment requirements on DMAable
443 * buffers; let protocol drivers know about these requirements.
444 */
445 u16 dma_alignment;
446
447 /* spi_device.mode flags understood by this controller driver */
448 u32 mode_bits /* 模式位 */
......
455 /* limits on transfer speed */
456 u32 min_speed_hz; /* SPI 控制器支持的最小传输速率 */
457 u32 max_speed_hz; /* SPI 控制器支持的最大传输速率 */
458
459 /* other constraints relevant to this driver */
460 u16 flags; /* 传输类型标志 */
468
469 /* flag indicating this is an SPI slave controller */
470 bool slave; /* 标志该控制器是否为 SPI 从设备存在 */
471
472 /*
473 * on some hardware transfer / message size may be constrained
474 * the limit may depend on device transfer settings
475 */
476 size_t (*max_transfer_size)(struct spi_device *spi);
477 size_t (*max_message_size)(struct spi_device *spi);
478
479 /* I/O mutex */
480 struct mutex io_mutex
481
482 /* lock and mutex for SPI bus locking */
483 spinlock_t bus_lock_spinlock;
484 struct mutex bus_lock_mutex;
485
486 /* flag indicating that the SPI bus is locked for exclusive use */
487 bool bus_lock_flag;
......
495 int (*setup)(struct spi_device *spi)
......
527 int (*transfer)(struct spi_device *spi,
528 struct spi_message *mesg);
567 int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
568 int (*transfer_one_message)(struct spi_controller *ctlr,
569 struct spi_message *mesg);
......
607 };
第495行,SPI控制器的setup函数,类似于初始化函数。
第527行,SPI控制器的transfer函数,和i2c_algorithm中的master_xfer函数一样,控制器数据传输函数。
第568行,transfer_one_message函数,也用于SPI数据发送,用于发送一个spi_message,SPI的数据会打包成spi_message,然后以队列方式发送出去。
SPI主机端最终会通过transfer函数与SPI设备进行通信,因此对于SPI主机控制器的驱动编写者而言transfer函数是需要实现的,因为不同的SOC其SPI控制器不同,寄存器都不一样。和I2C适配器驱动一样,SPI主机驱动一般都是SOC厂商去编写的,所以作为SOC的使用者,这一部分的驱动就不用操心了。
SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后向Linux内核注册spi_master。
spi_master申请与释放
spi_alloc_master函数用于申请spi_master,函数原型如下:
struct spi_controller *spi_alloc_master(struct device *host, unsigned int size)
函数参数和返回值含义如下:
- host:设备,一般是platform_device中的dev成员变量。
- size:私有数据大小,可以通过spi_master_get_devdata函数获取到这些私有数据。
- 返回值: 申请到的spi_controller,也就是spi_master。
spi_master的释放通过spi_master_put函数来完成,当删除一个SPI主机驱动的时候就需要释放掉前面申请的spi_master,spi_master_put本质上是个宏:
#define spi_master_put(_ctlr) spi_controller_put(_ctlr)
spi_master_put函数最终通过调用spi_controller_put函数来完成spi_master释放,原型如下:
void spi_master_put(struct spi_controller *ctlr)
函数参数和返回值含义如下:
- ctlr:要释放的spi_master。
- 返回值:无。
spi_master的注册与注销
当spi_master初始化完成以后就需要将其注册到Linux内核,spi_master注册函数为spi_register_master,函数原型如下:
int spi_register_master(struct spi_controller *ctlr)
函数参数和返回值含义如下:
- ctlr:要注册的spi_master。
- 返回值:0,成功;负值,失败。
如果要注销spi_master的话可以使用 spi_unregister_master函数,此函数原型为:
void spi_unregister_master(struct spi_controller *ctlr)
函数参数和返回值含义如下:
- ctlr:要注销的spi_master。
- 返回值:无。
SPI设备驱动
spi设备驱动和i2c设备驱动也很类似,Linux内核使用spi_driver结构体来表示spi设备驱
动,在编写SPI设备驱动的时候需要实现spi_driver。spi_driver结构体定义在include/linux/spi/spi.h文件中,结构体内容如下:
可以看出,spi_driver和i2c_driver、platform_driver基本一样,当SPI设备和驱动匹配成功以后probe函数就会执行。
同样的,spi_driver初始化完成以后需要向Linux内核注册,spi_driver注册函数为spi_register_driver,函数原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
- sdrv:要注册的spi_driver。
- 返回值:0,注册成功;赋值,注册失败。
注销SPI设备驱动以后也需要注销掉前面注册的spi_driver,使用spi_unregister_driver函数完成spi_driver的注销,函数原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
- sdrv:要注销的spi_driver。
- 返回值:无。
spi_driver注册示例程序如下:
示例代码 45.2.2.2 spi_driver 注册示例程序
1 /* probe 函数 */
2 static int xxx_probe(struct spi_device *spi)
3 {
4 /* 具体函数内容 */
5 return 0;
6 }
7
8 /* remove 函数 */
9 static int xxx_remove(struct spi_device *spi)
10 {
11 /* 具体函数内容 */
12 return 0;
13 }
14 /* 传统匹配方式 ID 列表 */
15 static const struct spi_device_id xxx_id[] = {
16 {"xxx", 0},
17 {}
18 };
19
20 /* 设备树匹配列表 */
21 static const struct of_device_id xxx_of_match[] = {
22 { .compatible = "xxx" },
23 { /* Sentinel */ }
24 };
25
26 /* SPI 驱动结构体 */
27 static struct spi_driver xxx_driver = {
28 .probe = xxx_probe,
29 .remove = xxx_remove,
30 .driver = {
31 .owner = THIS_MODULE,
32 .name = "xxx",
33 .of_match_table = xxx_of_match,
34 },
35 .id_table = xxx_id,
36 };
37
38 /* 驱动入口函数 */
39 static int __init xxx_init(void)
40 {
41 return spi_register_driver(&xxx_driver);
42 }
43
44 /* 驱动出口函数 */
45 static void __exit xxx_exit(void)
46 {
47 spi_unregister_driver(&xxx_driver);
48 }
49
50 module_init(xxx_init);
51 module_exit(xxx_exit);
第1-36行,spi_driver结构体,需要SPI设备驱动人员编写,包括匹配表、 probe函数等。和i2c_driver、platform_driver一样,就不详细讲解了。
第39-42行,在驱动入口函数中调用spi_register_driver来注册spi_driver。
第45-48行,在驱动出口函数中调用spi_unregister_driver来注销spi_driver。
SPI设备和驱动匹配过程
SPI设备和驱动的匹配过程是由SPI总线来完成的,这点和platform、I2C等驱动一样,SPI总线为spi_bus_type,定义在drivers/spi/spi.c文件中,内容如下:
可以看出,SPI设备和驱动的匹配函数为spi_match_device,函数内容如下:
spi_match_device函数和i2c_match_device函数的对于设备和驱动的匹配过程基本一样。
第352行,of_driver_match_device函数用于完成设备树设备和驱动匹配。比较SPI设备节
点的compatible属性和of_device_id中的compatible属性是否相等,如果相当的话就表示SPI设备和驱动匹配。
第356行,acpi_driver_match_device函数用于ACPI形式的匹配。
第360行,spi_match_id函数用于传统的、无设备树的SPI设备和驱动匹配过程。比较SPI设备名字和spi_device_id的name字段是否相等,相等的话就说明SPI设备和驱动匹配。
第362行,比较spi_device中modalias成员变量和device_driver中的name成员变量是否
相等。
STM32MP1 SPI主机驱动分析
和I2C的适配器驱动一样,SPI主机驱动一般由SOC厂商编写好,打开stm32mp151.dtsi文件,找到如下内容:
重点来看一下第4行的compatible属性值,compatible属性为“st,stm32h7-spi”,在Linux内核源码中搜素这两个属性值即可找到STM32MP1对应的SPI主机驱动。STM32MP1的SPI主机驱动文件为drivers/spi/spi-stm32.c,在此文件中找到如下内容:
第1860行,“st,stm32h7-spi”匹配项,因此可知STM32MP1主机驱动就是spi-stm32.c这个文件。
第2154-2164行,从这里可以知道,该主机驱动程序是基于platform总线框架编写,platform_driver结构体变量为stm32_spi_driver,当platform总线下设备和设备驱动匹配成功之后就会执行stm32_spi_probe函数,同样当驱动模块卸载的时候就会执行stm32_spi_remove函数。
接下来重点来看下stm32_spi_probe函数做了些什么,函数如下所示:
示例代码 45. 3.3 stm32_spi_probe 函数
1866 static int stm32_spi_probe(struct platform_device *pdev)
1867 {
1868 struct spi_master *master;
1869 struct stm32_spi *spi;
1870 struct resource *res;
1871 struct reset_control *rst;
1872 int i, ret, num_cs, cs_gpio;
1873
1874 master = spi_alloc_master(&pdev->dev,sizeof(struct stm32_spi));
1875 if (!master) {
1876 dev_err(&pdev->dev, "spi master allocation failed\n");
1877 return -ENOMEM;
1878 }
......
1892 res p= latform_get_resource(&pdev, IORESOURCE_MEM, 0);
1893 spi->base = devm_ioremap_resource(&pdev->dev, res);
1894 if (IS_ERR(spi->base)) {
1895 ret = PTR_ERR(spi->base);
1896 goto err_master_put;
1897 }
1898
1899 spi->phys_addr = (dma_addr_t)res->start;
1900
1901 spi->irq = platform_get_irq(pdev, 0);
1902 if (spi->irq <= 0) {
1903 ret = spi->irq;
1904 if (ret != -EPROBE_DEFER)
1905 dev_err(&pdev->dev, "failed to get irq: %d\n", ret);
1906 goto err_master_put;
1907 }
1908 ret = devm_request_threaded_irq(&pdev->dev, spi->irq,
1909 spi->cfg->irq_handler_event,
1910 spi->cfg->irq_handler_thread,
1911 IRQF_ONESHOT, pdev->name, master);
1963 master->dev.of_node = pdev->dev.of_node;
1964 master->auto_runtime_pm = true;
1965 master->bus_num = pdev->id;
1966 master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH |
1967 SPI_LSB_FIRST | SPI_3WIRE;
1968 master->bits_per_word_mask = spi->cfg->get_bpw_mask(spi);
1969 master->max_speed_hz = spi clk_rate /spi->cfg->baud_rate_div_min;
1970 master->min_speed_hz = spi->clk_rate /spi->cfg->baud_rate_div_max;
1971 master->setup = stm32_spi_setup;
1972 master->prepare_message = stm32_spi_prepare_msg;
1973 master->transfer_one = stm32_spi_transfer_one;
1974 master->unprepare_message = stm32_spi_unprepare_msg;
......
2026 ret = spi_register_master(master);
......
2050 }
第1874行,通过调用spi_alloc_master函数为master指针申请内存,也就是实例化master。
第1901-1911行,获取中断号和注册中断函数。
第1963-1974行,对master变量进行初始化和赋值,从结果可以看到,master结构体中并没有设置transfer和transfer_one_message这两个用于SPI数据传输的函数,而是使用了transfer_one作为SPI数据传输的函数,对应的函数为stm32_spi_transfer_one,也就是该函数是STM32MP1 SPI数据传输的函数。
第2026行,调用spi_register_master函数向SPI子系统注册一个master。
这里简单看看stm32_spi_transfer_one函数的内容,如下所示(有省略):
第1709-1715行,如果启动了DMA那么就使用stm32_spi_transfer_one_dma来进行传输数据,没有用DMA的话就调用transfer_one_irq函数。这两个函数也就是控制寄存器来进行收发数据。
SPI设备驱动编写流程
SPI设备信息描述
IO的pinctrl子节点创建与修改
首先肯定是根据所使用的IO来创建或者修改pinctrl子节点。唯独要注意的是检查相应的IO有没有被其它的设备所使用,如果有多个pinctrl配置相同的IO是没有关系的,只要保证没有被设备调用就行。
SPI设备节点的创建与修改
采用设备树方式的情况下,SPI从机设备信息描述就通过创建相应的设备子节点来完成,可以打开stm32mp157d-atk.dts这个设备树文件,然后在里边创建一个SPI从机设备节点,描述该设备的相关信息。
SPI从机设备数据收发处理流程
SPI设备驱动的核心是spi_driver。当向Linux内核注册成功spi_driver以后就可以使用SPI核心层提供的API函数来对设备进行读写操作了。
首先是spi_transfer结构体,此结构体用于描述SPI传输信息,结构体内容如下:
第817行,tx_buf保存着要发送的数据。
第818行,rx_buf用于保存接收到的数据。
第819行,len是要进行传输的数据长度,SPI是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以spi_transfer中也就没有发送长度和接收长度之分。
spi_transfer需要组织成spi_message,spi_message也是一个结构体,内容如下:
在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init,函数原型如下:
void spi_message_init(struct spi_message *m)
函数参数和返回值含义如下:
- m:要初始化的spi_message。
- 返回值:无。
spi_message初始化完成以后需要将spi_transfer添加到spi_message队列中,这里要用到spi_message_add_tail函数,此函数原型如下:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
函数参数和返回值含义如下:
- t:要添加到队列中的spi_transfer。
- m:spi_transfer要加入的spi_message。
- 返回值:无。
spi_message准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待SPI数据传输完成 ,同步传输函数为spi_sync,函数原型如下:
int spi_sync(struct spi_device *spi, struct spi_message *message)
函数参数和返回值含义如下:
- spi:要进行数据传输的spi_device。
- message:要传输的spi_message。
- 返回值:无。
异步传输不会阻塞的等到SPI数据传输完成,异步传输需要设置spi_message中的complete成员变量,complete是一个回调函数,当SPI异步传输完成以后此函数就会被调用。SPI异步传输函数为spi_async,函数原型如下:
int spi_async(struct spi_device *spi, struct spi_message *message)
函数参数和返回值含义如下:
- spi:要进行数据传输的spi_device。
- message:要传输的spi_message。
- 返回值:无。
在本章实验中,采用同步传输方式来完成SPI数据的传输工作,也就是spi_sync函数。
综上所述,SPI数据传输步骤如下:
- 申请并初始化spi_transfer,设置spi_transfer的tx_buf成员变量,tx_buf为要发送的数据。然后设置rx_buf成员变量,rx_buf保存着接收到的数据。最后设置len成员变量,也就是要进行数据通信的长度。
- 使用spi_message_init函数初始化spi_message。
- 使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
- 使用spi_sync函数完成SPI数据同步传输。
通过SPI进行n个字节的数据发送和接收的示例代码如下所示:
示例代码 45.4.2.3 SPI 数据读写操作
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{int ret;struct spi_message m;struct spi_transfer t = {.tx_buf = buf,.len = len,};spi_message_init(&m); /* 初始化 spi_message */spi_message_add_tail(t, &m); /* 将 spi_transfer 添加到 spi_message 队列 */ret = spi_sync(spi, &m); /* 同步传输 */return ret;
};/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{int ret;struct spi_message m;struct spi_transfer t = {.rx_buf = buf,.len = len,};spi_message_init(&m); /* 初始化 spi_message */spi_message_add_tail(t, &m); /* 将 spi_transfer 添加到 spi_message 队列 */ret = spi_sync(spi, &m); /* 同步传输 */return ret;
};
硬件原理图分析
STM32MP1开发板上ICM20608原理如下图所示:
上图就是ICM-20608的硬件原理图。正点原子STM32MP1开发板的PZ0-3分别连接到ICM-20608的SCK、SDA、AD0和CS。其中6D_INT为ICM20608的中断引脚,连接到PA14引脚上,在本章实验上没有用到ICM20608的中断引脚。
实验程序编写
修改设备树
添加/查找ICM20608使用的IO的pinmux配置
首先在stm32mp15-pinctrl.dtsi文件中添加IO配置信息,ICM20608连接到了STM32MP157的SPI1接口,因此先在stm32mp15-pinctrl.dtsi里面搜索一下,看看有没有SPI1接口引脚配置(在正点原子的linux驱动开发的教程中是默认有的)。如果没有的话就自行添加,有的话就检查一下SPI1接口的引脚配置是否和自己所使用的硬件一致,不一致的话就要修改。修改后的引脚信息如下所示:
示例代码 45. 6.1.1 spi1 的 pinmux 配置
1 spi1_pins_a: spi1-0 {
2 pins1 {
3 pinmux = <STM32_PINMUX('Z', 0, AF5)>, /* SPI1_SCK */
4 <STM32_PINMUX('Z', 2, AF5)>; /* SPI1_MOSI */
5 bias-disable;
6 drive-push-pull;
7 slew-rate = <1>;
8 };
9
10 pins2 {
11 pinmux = <STM32_PINMUX('Z', 1, AF5)>; /* SPI1_MISO */
12 bias-disable;
13 };
14
15 pins3 {
16 pinmux = <STM32_PINMUX('Z', 3, GPIO)>; /* SPI1_NSS */
17 drive-push-pull;
18 bias-pull-up;
19 output-high;
20 slew-rate = <0>;
21 };
22 };
23
24 spi1_sleep_pins_a: spi1-sleep-0 {
25 pins {
26 pinmux = <STM32_PINMUX('Z', 0, ANALOG)>, /* SPI1_SCK */
27 <STM32_PINMUX('Z', 1, ANALOG)>, /* SPI1_MISO */
28 <STM32_PINMUX('Z', 2, ANALOG)>, /* SPI1_MOSI */
29 <STM32_PINMUX('Z', 3, ANALOG)>; /* SPI1_NSS */
30 };
31 };
示例代码45.6.3.1里15-21行是设置ICM20608的片选信号,直接复用为GPIO,也就是使用软件片选。
在SPI1节点下添加pinmux并追加icm20608子节点
在stm32mp157d-atk.dts文件,追加SPI1节点,追加如下所示内容:
示例代码 45. 6.1.2 追加内容的 spi 1 节点
1 spi1 {
2 pinctrl-names = "default", "sleep";
3 pinctrl-0 = <&spi1_pins_a>;
4 pinctrl-1 = <&spi1_sleep_pins_a>;
5 cs-gpios = <&gpioz 3 GPIO_ACTIVE_LOW>;
6 status = "okay";
7
8 spidev: icm20608@0 {
9 compatible = "alientek,icm20608";
10 reg = <0>; /* CS #0 */
11 spi-max-frequency = <8000000>;
12 };
13 };
第2-4行,设置IO要使用的pinmux配置。
第5行,“cs-gpios”属性是用来设置SPI的片选引脚。SPI主机驱动就会根据此属性去控制设备的片选引脚,本实验使用PZ3作为片选引脚。关于cs-gpios属性的详细描述可以参考
绑定文档:Documentation/devicetree/bindings/spi/spi-controller.yaml。如果一个SPI接口下连接了多个SPI芯片,那么cs-gpios属性里面就要添加所有SPI芯片的片选信号,比如:
cs-gpios = <&gpio1 0 0>, <&gpio1 1 0>, <&gpio1 2 0>, <&gpio1 3 0>;
上述描述说明此时SPI节点下有4个SPI芯片,第一个SPI芯片的片选引脚为gpio1_0,依次类推。
第8-12行,icm20608设备子节点,从第5行的cs-gpios节点可以看出,此时SPI接口下只有一个ICM20608,而且ICM20608的片选索引为0,因此@后面为0。注意,@后面的数字就是对应SPI芯片片选信号在cs-gpios中的索引值。第9行设置节点属性兼容值为“alientek,icm20608”,第10行reg属性表示icm20608所使用的片选,和第8行@后面的数字含义相同,这里也设置为0,也就是cs-gpios属性中的第一个片选信号。第11行设置SPI最大时钟频率为8MHz,这是ICM20608的SPI接口所能支持的最大的时钟频率。
编写ICM20608驱动
工程创建好以后新建icm20608.c和icm20608reg.h这两个文件,icm20608.c为ICM20608的驱动代码,icm20608reg.h是ICM20608寄存器头文件。先在icm20608reg.h中定义好ICM20608的寄存器,输入如下内容(有省略):
示例代码 45. 6.2.1 icm20608reg.h 文件内容
1 #ifndef ICM20608_H
2 #define ICM20608_H
3 /***************************************************************
4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
5 文件名 : icm20608reg.h
6 作者 : 左忠凯
7 版本 : V1.0
8 描述 : ICM20608寄存器地址描述头文件
9 其他 : 无
10 论坛 : www.openedv.com
11 日志 : 初版V1.0 2019/9/2 左忠凯创建
12 ***************************************************************/
13 #define ICM20608G_ID 0XAF /* ID值 */
14 #define ICM20608D_ID 0XAE /* ID值 */
15
16 /* ICM20608寄存器
17 *复位后所有寄存器地址都为0,除了
18 *Register 107(0X6B) Power Management 1 = 0x40
19 *Register 117(0X75) WHO_AM_I = 0xAF或0xAE
20 */
21 /* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
22 #define ICM20_SELF_TEST_X_GYRO 0x00
23 #define ICM20_SELF_TEST_Y_GYRO 0x01
24 #define ICM20_SELF_TEST_Z_GYRO 0x02
25 #define ICM20_SELF_TEST_X_ACCEL 0x0D
26 #define ICM20_SELF_TEST_Y_ACCEL 0x0E
27 #define ICM20_SELF_TEST_Z_ACCEL 0x0F
......
88 #endif
接下来编写icm20608.c文件。
icm20608设备结构体创建
这一部分跟之前的字符设备的设备结构体没有什么区别,主要需要加上struct spi_device结构体指针*spi,然后就是加上一系列的signed int类型的变量来存储从传感器中读取的数据。
icm20608的spi_driver注册与注销
对于SPI设备驱动,首先就是要初始化并向系统注册spi_driver。
对于设备树而言,就是要添加一个of_device_id结构体类型的icm20608_of_match[]数组,里面设置.compatible用来匹配。
然后需要写一个spi_driver结构体类型的icm20608_driver,作为SPI驱动结构体,里面要设置.probe,.remove,.driver里面设置.owner(这个一般就是THIS_MODULE),.name以及.of_match_table,就可以了。
然后就是驱动入口和驱动出口函数,这里就是在icm20608_init里面return出来spi_register_driver;在icm20608_exit里面调用spi_unregister_driver函数。
最后就是五个常规的操作就可以了。
probe&remove函数
probe函数,就在其中先通过devm_kzalloc分配内存空间,然后就是字符设备的老一套,alloc_chrdev_region创建设备号,cdev_init初始化cdev,然后cdev_add添加一个cdev,最后就是class_create以及device_create创建类和设备。
之后就是SPI的内容,首先spi->mode来设置SPI的四种工作模式的一种,然后spi_setup;之后就是调用自己写的icm20608_reginit来初始化内部寄存器,之后spi_set_drvdata来保存设置好的设备结构体。
remove函数跟之前的基本是一样的,除了要先通过spi_get_drvdata来获取一下SPI设备,然后就是4套操作来cdev_del,unregister_chrdev_region,device_destroy以及class_destroy。
icm20608寄存器读写与初始化
SPI驱动最终是通过读写icm20608的寄存器来实现的,因此需要编写相应的寄存器读写函数,并且使用这些读写函数来完成对icm20608的初始化。 具体的读写就是参考操作手册。
这里的话我就直接把代码贴上来,比较长,需要注意的就是在读写多个寄存器的时候,因为SPI是全双工,没有发送和接收长度的分别,同时读写N个字节需要封装N+1个字节(第1个是区分读写的,后面N个才是真实的传输数据)。
/** @description : 从icm20608读取多个寄存器数据* @param - dev: icm20608设备* @param - reg: 要读取的寄存器首地址* @param - val: 读取到的数据* @param - len: 要读取的数据长度* @return : 操作结果*/
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{int ret = -1;unsigned char txdata[1];unsigned char * rxdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->spi;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */if(!t) {return -ENOMEM;}rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL); /* 申请内存 */if(!rxdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,一共要读取len个字节长度的数据,*/txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */ t->tx_buf = txdata; /* 要发送的数据 */t->rx_buf = rxdata; /* 要读取的数据 */t->len = len+1; /* t->len=发送的长度+读取的长度 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 */if(ret) {goto out2;}memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */out2:kfree(rxdata); /* 释放内存 */
out1: kfree(t); /* 释放内存 */return ret;
}/** @description : 向icm20608多个寄存器写入数据* @param - dev: icm20608设备* @param - reg: 要写入的寄存器首地址* @param - val: 要写入的数据缓冲区* @param - len: 要写入的数据长度* @return : 操作结果*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{int ret = -1;unsigned char *txdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->spi;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */if(!t) {return -ENOMEM;}txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);if(!txdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,len为要写入的寄存器的集合,*/*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */t->tx_buf = txdata; /* 要发送的数据 */t->len = len+1; /* t->len=发送的长度+读取的长度 */spi_message_init(&m); /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m); /* 同步发送 */if(ret) {goto out2;}out2:kfree(txdata); /* 释放内存 */
out1:kfree(t); /* 释放内存 */return ret;
}/** @description : 读取icm20608指定寄存器值,读取一个寄存器* @param - dev: icm20608设备* @param - reg: 要读取的寄存器* @return : 读取到的寄存器值*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 data = 0;icm20608_read_regs(dev, reg, &data, 1);return data;
}/** @description : 向icm20608指定寄存器写入指定的值,写一个寄存器* @param - dev: icm20608设备* @param - reg: 要写的寄存器* @param - data: 要写入的值* @return : 无*/ static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{u8 buf = value;icm20608_write_regs(dev, reg, &buf, 1);
}/** @description : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、* : 三轴加速度计和内部温度。* @param - dev : ICM20608设备* @return : 无。*/
void icm20608_readdata(struct icm20608_dev *dev)
{unsigned char data[14];icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}/** ICM20608内部寄存器初始化函数 * @param - spi : 要操作的设备* @return : 无*/
void icm20608_reginit(struct icm20608_dev *dev)
{u8 value = 0;icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value); icm20608_write_onereg(dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */icm20608_write_onereg(dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */icm20608_write_onereg(dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */icm20608_write_onereg(dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */icm20608_write_onereg(dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */icm20608_write_onereg(dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
}
字符设备驱动框架
这里的驱动框架就比较常规。
在icm20608_read里面,与IIC一样,需要先读取filp的cdev首地址,再通过container_of来获取设备的首地址;然后调用已经写好的icm20608_readdata读取寄存器的值,然后把数据对应存入到自定义的data[7]数组之中,最后copy_to_user来传输。
open和release函数就是直接return 0就可以了。
file_operations这个操作函数集就是.open,.read以及.release。
编写测试APP
argc是传入了2个参数。
这里主要就是获取字符设备之后,filename=argv[1],然后open打开,在while死循环里面,通过read把数据读入databuf[7]数组里面,然后把数组的对应位置的值传入自己定义的变量里面;再根据寄存器设置的量程,把这些直接读取的数据转换成对应的真实值然后打印出来,最后usleep(100000)间隔100ms重复读取;跳出死循环外面close设备。
运行测试
内核使能SPI控制器
ST官方系统把SPI控制器的驱动编译成模块,需要把SPI控制器驱动编译进内核,这
样就可以在启动Linux内核的时候自动加载SPI控制器驱动,无需手动加载,方便使用。打开Linux内核图形化配置界面,按下路径找到对应的配置项:
-> Device Drivers -> SPI support (SPI [=y]) -> <*> STMicroelectronics STM32 SPI controller //编译进内核 |
如下图所示:
上图本来是选择为“M””,要改为“*”,也就是编译进内核。接着重新编译设备树和内核,运行以下命令进行编译:
make dtbs uImage LOADADDR=0XC2000040 -j32 |
使用新编译好的stm32mp157d-atk.dtb和uImage镜像启动系统,如果SPI控制器驱动工作正常就会有如下图所示提示信息:
如果没有输出上图中“spi_stm32 44004000.spi: driver initialized”这句话,那就要检查一下设备树和内配配置是否有问题,通过查看/sys/bus/spi/devices/下有没有spi相关的设备,就能够知道设备树配置是否正确,比如本例程如下图所示:
编译驱动程序
老样子,Makefile里面的obj-m改成icm20608.o,然后“make”就可以了。
编译测试APP
在icm20608App.c这个测试APP中用到了浮点计算,而STM32MP1是支持硬件浮点的,因此在编译icm20608App.c的时候就可以使能硬件浮点,这样可以加速浮点计算。使
能硬件浮点很简单,在编译的时候加入如下参数即可:
-march-armv7-a -mfpu-neon -mfloat=hard |
输入如下命令使能硬件浮点编译icm20608App.c这个测试程序:
arm-none-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -mfloat-abi=hard icm20608App.c -o icm20608App |
编译成功以后就会生成icm20608App这个应用程序,使用arm-linux-gnueabihf-readelf查看一下编译出来的icm20608App就知道了,输入如下命令:
arm-none-linux-gnueabihf-readelf -A icm20608App |
结果如下图所示:
从上图可以看出FPU架构为VFPv3,SIMD使用了NEON,说明icm20608App这个应用程序使用了硬件浮点。
运行测试
将上一小节编译出来icm20608.ko和icm20608App这两个文件拷贝到rootfs/lib/modules/5.3.41目录中,重启开发板,进入到目录lib/modules/5.3.41中。输入如下命令加载icm20608.ko这个驱动模块:
depmod //第一次加载驱动的时候需要运行此命令 modprobe icm20608.ko //加载驱动模块 |
当驱动模块加载成功以后使用icm20608App来测试,输入如下命令:
./icm20608App /dev/icm20608 |
测试APP会不断的从ICM20608中读取数据,然后输出到终端上,如下图所示:
可以看出,开发板静止状态下,Z轴方向的加速度为0.97g,这个就是重力加速度。对于陀螺仪来讲,静止状态下三轴的角速度应该在0°/S左右。ICM20608内温度传感器采集到的温度在39.51度,可以晃动一下开发板,这个时候陀螺仪和加速度计的值就会有变化。
总结
跟之前的I2C驱动是很类似的,具体SPI主机驱动是不需要我们自己写的,我们只需要写从机驱动,也就是对应的传感器芯片的驱动就可以了。
首先需要修改设备数,根据SPI的接口,在stm32mp15-pinctrl.dtsi中,修改电气属性,添加复用功能;然后在stm32mp157d-atk.dts设备树文件中,添加对应的spi节点,并与pinctrl关联。
然后要编写SPI驱动传感器或芯片的头文件,按照数据手册定义好寄存器的地址。
对于驱动程序而言,首先是设备结构体,这里就是要添加spi_device结构体的*spi,来完成对spi的设置。然后就是通过设备树,写一个SPI的驱动结构体spi_driver,定义probe和remove函数,然后在.driver里面写好.owner,.name和.of_match_table。of_match_table就是of_device_id结构体的数组,设置好.compatible就可以了。驱动的入口和出口就是调用spi_register_driver和spi_unregister_driver。
probe函数,首先来通过devm_kzalloc分配内存之后,走字符设备的流程,然后是初始化spi_device,通过spi->mode选定SPI工作模式,然后spi_setup初始化,之后调用自行编写的初始化寄存器的函数,并通过spi_set_drvdata保存spi结构体。
remove函数,就是通过spi_get_drvdata获取到spi结构体,然后注销字符设备驱动的四件套就可以了。
这里SPI实验是使用ICM20608,所以根据他的手册和寄存器,来编写对寄存器的读写函数,这里因为全双工就不区别收发的长度,只要注意长度是len+1就可以了。
字符设备的驱动框架就是老样子,这里注意在read里面,要跟I2C一样,先读cdev首地址,再container_of获取设备首地址。
相关文章:

正点原子嵌入式linux驱动开发——Linux SPI驱动
到目前为止的学习笔记,已经介绍了Linux下的platform总线框架、I2C总线框架,本篇笔记将介绍Linux下的SPI总线框架。与I2C总线一样,SPI是物理总线,也是一种很常用的串行通信协议。本章就来学习如何在Linux下编写SPI总线接口的设备驱…...

【计算机视觉】相机
文章目录 一、原始的相机:针孔相机(Pinhole Camera)二、针孔相机的数学模型三、真实相机四、透镜的缺陷 我的《计算机视觉》系列参考UC Berkeley的CS180课程,PPT可以在课程主页看到。 成像原理 一、原始的相机:针孔相机…...

Spring的条件注解,一篇文章盘得清清楚楚明明白白
前言 在Spring中,条件注解可根据特定的条件来决定是否创建或配置Bean,这些条件可以基于类、属性、环境等因素。通过使用条件注解,我们可以在Spring容器中更加灵活地管理和控制组件的创建和注入,帮助我们更加灵活地管理和控制Bean…...

Oracle (7)Online Redo Log Files
目录 一、Oracle Online Redo Log Files及其相关内容介绍 1、Online Redo Log Files简介 2、Online Redo Log Files特点 3、Online Redo Log Files文件组 4、多路复用文件 5、联机重做日志文件工作方式 6、LGWR什么时候写重做 7、LS和LSN 8、删除Redo文件成员 9、删除…...

物联网AI MicroPython传感器学习 之 PAJ7620手势识别传感器
学物联网,来万物简单IoT物联网!! 一、产品简介 手势识别传感器PAJ7620u2是一款集成3D手势识别和运动跟踪为一体的交互式传感器,传感器可以在有效范围内识别手指的顺时针/逆时针转动方向和手指的运动方向等。它可以识别13种手势&a…...

Affinity Photo 2.2.1 高端专业Mac PS修图软件
Affinity Photo Mac中文版是一款面向专业摄影师和其他视觉艺术家的专业图像处理软件,拥有众多专业高端功能,如Raw处理、PSD导入和导出、16位通道的编辑和ICC色彩管理以及兼容大量图片格式。是现在最快、最顺、最精准的专业修图软件。Affinity Photo Mac是…...

微服务-统一网关Gateway
网关的作用 对用户请求做身份认证、权限校验将用户请求路由到微服务,并实现负载均衡对用户请求做限流 搭建网关服务 创建新module,命名为Gateway,引入依赖(1.SpringCloudGateway依赖;2.Eureka客户端依赖或者nacos的服…...

【音视频|wav】wav音频文件格式详解
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 🤣本文内容🤣&a…...

网络工程综合试题(二)
1. SR技术有哪些缺点? SR(Segment Routing)技术是一种新兴的网络编程技术,它具有很多优点,但也存在一些缺点,包括: 部署复杂性:SR技术需要对网络进行改造和升级,包括更新…...

Android JNI/NDK 入门从一到二
1. 前言 最基础的创建JNI接口的操作,可以直接看这篇文章 : 第一个Android JNI工程, 本文会基于掌握创建JNI接口的操作的基础之上,来入门JNI/NDK。 2. 在JNI中打印日志 2.1 添加log模块 记得CMake中有log模块,不然编译不过 ta…...

吃瓜教程3|决策树
ID3算法 假定当前样本集合D中第k类样本所占比例为pk,则样本集合D的信息熵定义为 信息增益 C4.5算法 ID3算法存在一个问题,就是偏向于取值数目较多的属性,因此C4.5算法使用了“增益率”(gain ratio)来选择划分属性 CA…...

springboot动态数据源【非伪数据源】
说明:本文章的数据源不是在配置文件中配置两个或多个数据源,在业务方面对这些数据源来回切换,本文章中的数据源是可以动态添加,修改,切换的,废话不多说。 先看工程图: 1.pom.xml文件 <?x…...

如何改善设备综合效率(OEE)并提高工厂的生产力
在现代制造业中,提高设备综合效率(Overall Equipment Efficiency,OEE)是企业追求高效生产和优化生产能力的重要目标之一。OEE是一个关键的绩效指标,可以帮助企业评估设备的利用效率、生产效率和质量水平。本文将从三个…...

一文接入Android阿里Sophix热更新
最近公司项目渐趋成熟,已经不需要经常更新版本,并且更新版本对客户的影响特别大,但是日常维护难免需要更新代码,因此热修复的技术,就比较迫切了。 经过一段时间的对比,我们最终决定使用阿里的Sophix方案&am…...

【高阶数据结构】并查集和图
目录 1.数据结构--并查集 2.数据结构--图 1.图的基础概念 2.图的简单实现 2.1.邻接矩阵的图实现 2.2.邻接表的图实现 2.3.图的DFS和BFS 2.4.最小生成树 2.4.1.Kruskal(克鲁斯卡尔算法) 2.4.2.Prim(普里姆算法) 2.5.最短路径 2.5.1.Dijkstra(…...

Git 提交时提示 GPG 签名错误
本来应该一切都是正常的,但今天提交的时候提示 GPG 签名错误。 错误的信息就是 GPG 签名失败。 gpg: skipped "942395299055675C": No secret key gpg: signing failed: No secret key error: gpg failed to sign the data fatal: failed to write commi…...

vite+vue3实现 tomcat 的本地部署
背景: 很多开发小伙伴在本地开发完前端项目后,碍于服务端环境配置麻烦,想先试试在本地部署,已开发好的前端项目,由于很多文章都是文字性描述,不太直观,为了给大多数新手提供一个教程,…...

docker+playwright
windows10 docker playwright 难点在于windows下docker的安装,以及官方hub被墙的困难。 wsl2 wsl2 ubuntu docker git clone https://gitee.com/lineuman/lcs_playwright.git npm install npx playwright test docker端口怎么映射到主机上面? 设置重…...

php框架路由实现
在PHP中也有很多框架(如Laravel、CodeIgniter)提供了路由功能。下面是一个简单的PHP路由实现原理和示例代码: 路由实现原理: 客户端发起请求,请求的URL会被传递给Web服务器。Web服务器将请求传递给PHP解释器ÿ…...

在CentOS 7中手工打造和运行xml文件配置的Servlet,然后使用curl、浏览器、telnet等三种工具各自测试
下载Openjdk并配置环境变量 https://jdk.java.net/java-se-ri/11-MR2是官网下载Openjdk 11的地方。 sudo wget https://download.java.net/openjdk/jdk11.0.0.1/ri/openjdk-11.0.0.1_linux-x64_bin.tar.gz下载openjdk 11。 sudo mkdir -p /usr/openjdk11创建目录ÿ…...

单例模式.
目录 ♫什么是单例模式 ♫饿汉式单例模式 ♫懒汉式单例模式 ♫单例模式的线程安全问题 ♪原子性 ♪内存可见性与指令重排序 ♫什么是单例模式 单例模式是一种设计模式,通过巧用Java的现有语法,实现一个只能被创建一个实例的类,并提供一个全…...

2023年MathorCup高校数学建模挑战赛大数据挑战赛赛题浅析
比赛时长为期7天的妈杯大数据挑战赛如期开赛,为了帮助大家更好的选题,首先给大家带来赛题浅析,为了方便大家更好的选题。 赛道 A:基于计算机视觉的坑洼道路检测和识别 A题,图像处理类题目。这种题目的难度数模独一档…...

c++小惊喜——stringstream
当需要读取一行字符串时,我们通常会有将这个字符串分开的想法 #include<iostream> #include<sstream> using namespace std;int main() {string str;getline(cin, str);stringstream ssin(str);string s[10];int cnt 0;while (ssin >> s[cnt]) …...

ubuntu 18.04 编译安装flexpart 10.4(2023年) —— 筑梦之路
2023年10月29日 环境说明 操作系统版本:ubuntu 18.04 python版本:3.6.9 gcc版本:7.5.0 编译安装路径:/usr/local cmake: 3.10.2 所需要的源码包我已经打包放到我的资源。 2021年1月份已经写过一篇Ubuntu 编译安装的帖子F…...

深度学习(生成式模型)——DDIM:Denoising Diffusion Implicit Models
文章目录 前言为什么DDPM的反向过程与前向过程步数绑定DDIM如何减少DDPM反向过程步数DDIM的优化目标DDIM的训练与测试 前言 上一篇博文介绍了DDIM的前身DDPM。DDPM的反向过程与前向过程步数一一对应,例如前向过程有1000步,那么反向过程也需要有1000步&a…...

HashMap的遍历方式 -- 好几次差点记不起来总结了一下
public class HashMapDemo {public static void main(String[] args) {// 创建一个HashMap并添加一些键值对Map<String, Integer> hashMap new HashMap<>();hashMap.put("Alice", 25);hashMap.put("Bob", 30);hashMap.put("Charlie"…...

PostgreSQL 两表关联更新sql
PostgreSQL两表关联更新SQL如下: UPDATE user SET username ft.name, age ft.age FROM userinfo WHERE user.id ft.id; user 要更新的表 userinfo数据来源表...

R2R 的一些小tip
批次间控制器(Run-to-run Controller),以应对高混合生产的挑战。将最优配方参数与各种工业特征相关联的模型是根据历史数据离线训练的。预测的最优配方参数在线用于调整工艺条件。 批次控制(R2R control)是一种先进的工艺控制技术,可在运行(如批次或晶圆…...

UML中类之间的六种主要关系
UML中类之间的六种主要关系: 继承(泛化)(Inheritance、Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组…...

机器学习-朴素贝叶斯之多项式模型
多项式模型: 记住一定用于离散的对象,不能是连续的 于高斯分布相反,多项式模型主要适用于离散特征的概率计算,切sklearn的多项式模型不接受输入负值 因为多项式不接受负值的输入,所以样本数据的特征为数值型数据&…...