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

做电子相册的网站wordpress 页面 文章 区别

做电子相册的网站,wordpress 页面 文章 区别,做网站充值系统,国内购物网站案例分析到目前为止的学习笔记#xff0c;已经介绍了Linux下的platform总线框架、I2C总线框架#xff0c;本篇笔记将介绍Linux下的SPI总线框架。与I2C总线一样#xff0c;SPI是物理总线#xff0c;也是一种很常用的串行通信协议。本章就来学习如何在Linux下编写SPI总线接口的设备驱…到目前为止的学习笔记已经介绍了Linux下的platform总线框架、I2C总线框架本篇笔记将介绍Linux下的SPI总线框架。与I2C总线一样SPI是物理总线也是一种很常用的串行通信协议。本章就来学习如何在Linux下编写SPI总线接口的设备驱动。本章实验的最终目的就是驱动STM32MP1开发板上的ICM-20608这个SPI接口的六轴传感器可以在应用程序中读取ICM-20608的原始传感器数据。 SPIICM-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_messageSPI的数据会打包成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_masterspi_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_messagespi_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。mspi_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: icm206080 { 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函数。 最后就是五个常规的操作就可以了。 proberemove函数 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_delunregister_chrdev_regiondevice_destroy以及class_destroy。 icm20608寄存器读写与初始化 SPI驱动最终是通过读写icm20608的寄存器来实现的因此需要编写相应的寄存器读写函数并且使用这些读写函数来完成对icm20608的初始化。 具体的读写就是参考操作手册。 这里的话我就直接把代码贴上来比较长需要注意的就是在读写多个寄存器的时候因为SPI是全双工没有发送和接收长度的分别同时读写N个字节需要封装N1个字节(第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;}/* 一共发送len1个字节的数据第一个字节为寄存器首地址一共要读取len个字节长度的数据*/txdata[0] reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */ t-tx_buf txdata; /* 要发送的数据 */t-rx_buf rxdata; /* 要读取的数据 */t-len len1; /* 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 , rxdata1, 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;}/* 一共发送len1个字节的数据第一个字节为寄存器首地址len为要写入的寄存器的集合*/*txdata reg ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata1, buf, len); /* 把len个寄存器拷贝到txdata里等待发送 */t-tx_buf txdata; /* 要发送的数据 */t-len len1; /* 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); /* 陀螺仪低通滤波BW20Hz */icm20608_write_onereg(dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW21.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个参数。 这里主要就是获取字符设备之后filenameargv[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 LOADADDR0XC2000040 -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 -mfloathard 输入如下命令使能硬件浮点编译icm20608App.c这个测试程序 arm-none-linux-gnueabihf-gcc -marcharmv7-a -mfpuneon -mfloat-abihard icm20608App.c -o icm20608App 编译成功以后就会生成icm20608App这个应用程序使用arm-linux-gnueabihf-readelf查看一下编译出来的icm20608App就知道了输入如下命令 arm-none-linux-gnueabihf-readelf -A icm20608App 结果如下图所示 从上图可以看出FPU架构为VFPv3SIMD使用了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所以根据他的手册和寄存器来编写对寄存器的读写函数这里因为全双工就不区别收发的长度只要注意长度是len1就可以了。 字符设备的驱动框架就是老样子这里注意在read里面要跟I2C一样先读cdev首地址再container_of获取设备首地址。
http://www.dnsts.com.cn/news/31701.html

相关文章:

  • seo优化6个实用技巧兰州网站优化排名
  • 海南住房城乡建设网站网站开发技术有什么软件
  • 网站建设技术员分为前端 后端网站在工信部备案如何做
  • 高新区建网站外包关于志愿者网站开发的论文
  • 哪个nas可以做网站光谷网站建设哪家好
  • 贷款织梦网站模板搜索引擎调价平台哪个好
  • 渭南网站建设与维护祥云网站建设
  • 建设一个购物网站流程100平米全包装修价格
  • 网站营销方法有哪些内容邢台专业做网站的地方
  • 宁波做网站优化公司上海企业网络推广方案
  • wordpress 站内搜索最简单的静态网站
  • 做一个企业网站花费济南网络安全公司
  • 一个网站怎么做2个服务器微信小程序成本
  • 邯郸做移动网站价格表wordpress伪静态教程
  • 长沙私人做网站虚拟主机网站淘客网站建设
  • 跨境电商发展现状如何新网站排名优化怎么做
  • 网站静态页面模板网站建设费用怎么入账
  • 网站做下载功能艺术字体在线生成器英文
  • 深圳 网站制作 哪家wordpress获取自定义字段的值
  • 地方网站类型在线crm视频
  • 网站前端跟后端怎么做怎么查名字有没有被注册商标
  • 中山网站建设公司哪家好phpcms 做购物网站
  • 怎样把自己的网站进行推广wordpress评论者名字
  • 苏州营销型网站开发公司外贸seo优化公司
  • hao123网站难做吗国内有wix做的好的网站
  • 香橼做空机构网站架设网站的目的
  • 中山做网站的公司胶州建设局网站
  • 深圳外贸网站推广六盘水建设网站
  • 电商网站建设案例怎么建设网站页面
  • lamp网站开发黄金组合制作网站后台