从SPI协议学习PX4源码

一、SPI类 

SPI类的参数:设备名称,devname设备节点名称,总线,device片选信号线,SPI模式,时钟频率,中断。SPI类继承VDev类。

SPI协议在spi.cpp文件中,涉及到了cdev和device的操作。cdev字符设备是linux系统设备之一。还有块设备,网络设备。cdev是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据。字符设备是面向流的设备,包括键盘,显示屏,串口。linux用户程序通过设备文件来使用驱动程序操作字符设备。cdev与incode的关系:incode成员含有cdev结构体成员的指针。cdev与file_operations的关系:cdev_init()建立cdev与file_operations之间的连接,为字符设备驱动提供接口函数,比如open,read,write等。

字符设备驱动结构cdev介绍 - 知乎 (zhihu.com)

SPI初始化:

连接总线_dev = up_spiinitialize(_bus),取消选择设备使得引脚电平由高变低(取消片选信号),检查设备是否在线(默认在线),初始化cdev(SPI类是基于cdev类的派生类,初始化cdev会创建设备节点)。

SPI传输:

	case LOCK_PREEMPTION: {irqstate_t state = irqsave();result = _transfer(send, recv, len);irqrestore(state);}break;    case LOCK_THREADS:SPI_LOCK(_dev, true);result = _transfer(send, recv, len);SPI_LOCK(_dev, false);break;int SPI::_transfer(uint8_t *send, uint8_t *recv, unsigned len)
{SPI_SETFREQUENCY(_dev, _frequency);SPI_SETMODE(_dev, _mode);SPI_SETBITS(_dev, 8);SPI_SELECT(_dev, _device, true);/* do the transfer */SPI_EXCHANGE(_dev, send, recv, len);/* and clean up */SPI_SELECT(_dev, _device, false);return OK;
}

反复出现的LOCK是用来干什么的?SPI传输时,接收数据是在中断中,这个接口不会锁住总线,可能会扰乱非中断调用者。中断和非中断混合配置的设备要确保合适的互锁。发送和接收至少一个是非空。当lockmode为preemption时,锁住全部;为threads时,为spi_lock,锁住其他的进程。

SPI_EXCHANGE()前后出现的SELECT函数先开后关是为什么? select函数是控制片选信号,表示当前设备被选中或者被释放。
 

二、HMC_5883_SPI类

在HMC5883_spi.cpp文件中,涉及到ioctl函数。ioctl函数在cdev和Device类中也出现了。那么这个函数有什么意义和作用呢?ioctl函数是内核设计着希望将用户空间和内核空间的驱动模块的交互分成两部分,数据读写以及状态控制,互不干扰。在使用时,要求用户按照内核特定的方式进行命令码的封装和解析,实现应用层和内核空间更好的对接。ioctl函数本质上就是用户空间向内核空间提交一段具有特定含义的命令码,内核空间根据内核规定好的方式,对命令码进行解析,执行底层的操作。在arm架构下的linux内核中,每个命令码由32bit组成。功能码+设备类型码+数据传输大小+数据传输方向。数据传输方向:_IO,_IOR,_IOW,_IOWR;数据传递大小:使用宏定义进行命令码封装时,必须填写数据类型;设备类型码:每一个驱动通过一个唯一的字符来代表;功能码:由自己指定。每次自己写32位的命令码比较麻烦,内核中定义好的宏定义可以简化这一过程。ioctl函数有cmd和arg两个参数,cmd就是命令码,arg就是数据地址,fd是文件描述符。

HMC5883_spi.cpp

device::Device *HMC5883_SPI_interface( int bus) 这是HMC的SPI接口函数。函数体是创建HMC_SPI类的一个实例。

HMC_SPI类是SPI的派生类。在HMC_SPI的构造函数中设定SPI的各个参数值。在这个类中,有init,read,write,ioctl四个虚函数。

HMC_SPI::init()先调用了SPI::init(),然后在指定地址读取ID,查看是否正确。

HMC_SPI::ioctl()如果操作是magiocg_external返回0(即使这个传感器是在外部SPI总线上,它仍然是飞控的内部组件,所以总是返回0表示内部),如果操作是deviocg_deviceid返回CDev::ioctl (nullptr, operation, arg)。

HMC_SPI::write和read都是在SPI::transfer进一步封装。需要注意的是,SPI::transfer是将发送和接收合为一个函数。这是因为SPI协议中,主从机的数据交互不需要应答位。当接收缓冲区满了之后或者发送缓冲区空了之后,都会触发同一个中断服务子函数。transfer函数只需要提供发数据的地址和接收数据的地址以及数据长度即可。transfer函数里调用的实际是firmware/nuttx/nuttx/arch /arm/src/stm32/stm32_spi.c的底层SPI库。

三、仿写

在PX4固件中,使用SPI协议的除了HMC磁力计还有MPU陀螺仪。所有SPI协议的传感器都是继承SPI这个类来实现SPI传感器驱动程序。构造函数,ioctl,read,write,init虚函数在类里重写。虚函数的重写要参照具体的硬件手册。

参照HMC的SPI文件,写MPU6000的SPI文件:

#ifdef PX4_SPIDEV_MPU6000device::Device *MPU6000_SPI_interface(int bus);class MPU6000_SPI : public device::SPI
{
public:MPU6000_SPI(int bus, spi_dev_e device);virtual ~MPU6000_SPI();virtual int	init();virtual int	read(unsigned address, void *data, unsigned count);virtual int	write(unsigned address, void *data, unsigned count);virtual int	ioctl(unsigned operation, unsigned &arg);};device::Device *
MPU6000_SPI_interface(int bus)
{return new MPU6000_SPI(bus, (spi_dev_e)PX4_SPIDEV_MPU6000);
}HMC5883_SPI::MPU6000_SPI(int bus, spi_dev_e device) :SPI("MPU6000_SPI", nullptr, bus, device, SPIDEV_MODE3, 11 * 1000 * 1000 )
{_device_id.devid_s.devtype = DRV_MAG_DEVTYPE_MPU6000;
}MPU6000_SPI::~MPU6000_SPI()
{
}int
MPU6000_SPI::init()
{int ret;ret = SPI::init();if (ret != OK) {DEVICE_DEBUG("SPI init failed");return -EIO;}// read WHO_AM_I valueuint8_t data[3] = {0, 0, 0};if (read(ADDR_ID_A, &data[0], 1) ||read(ADDR_ID_B, &data[1], 1) ||read(ADDR_ID_C, &data[2], 1)) {DEVICE_DEBUG("read_reg fail");}if ((data[0] != ID_A_WHO_AM_I) ||(data[1] != ID_B_WHO_AM_I) ||(data[2] != ID_C_WHO_AM_I)) {DEVICE_DEBUG("ID byte mismatch (%02x,%02x,%02x)", data[0], data[1], data[2]);return -EIO;}return OK;
}int
MPU6000_SPI::ioctl(unsigned operation, unsigned &arg)
{int ret;switch (operation) {case MAGIOCGEXTERNAL:return 0;case DEVIOCGDEVICEID:return CDev::ioctl(nullptr, operation, arg);default: {ret = -EINVAL;}}return ret;
}int
MPU6000_SPI::write(unsigned address, void *data, unsigned count)
{uint8_t buf[32];if (sizeof(buf) < (count + 1)) {return -EIO;}buf[0] = address | DIR_WRITE;memcpy(&buf[1], data, count);return transfer(&buf[0], &buf[0], count + 1);
}int
MPU6000_SPI::read(unsigned address, void *data, unsigned count)
{uint8_t buf[32];if (sizeof(buf) < (count + 1)) {return -EIO;}buf[0] = address | DIR_READ | ADDR_INCREMENT;int ret = transfer(&buf[0], &buf[0], count + 1);memcpy(data, &buf[1], count);return ret;
}#endif 

在MPU6000的构造函数中,与SPI一样,参数有bus,device,device_type,还有mode和频率。mode和频率要看传感器的手册。还有在后面的检查whoami时的ID号码。device是片选信号线,P4X4_SPIDEV_MPU6000。bus是SPI的总线。查看PX4的总线接口:PIXHAWK有三路SPI总线接口,一个给铁电存储器,一路给内置IMU,一路给外置的SPI。先看一下这个内置的SPI总线(给IMU,包括磁力计,陀螺仪,他们总线相同,但是片选信号不同,参考SPI的通讯图)。在使用SPI类来派生新类时,特别注意的是类的构造函数中的参数,要参考传感器的手册和硬件电路图,PX4的SPI外接的总线。

四、补充:底层的关系

HMC5883_SPI类里面开启工作队列work_queue或者定时回调函数来读取传感器的值,然后通过ourb把数据发送出去。

SPI类里面的函数SPI_SETMODE,SPI_SELECT,SPI_EXCHANGE在底层驱动stm32_spi.c里面。底层的关系:在CDev::init()函数中,调用了int register_driver(const char *path, const struct file_operations*fops, mode_t mode, void *priv)之后就可以使用用户接口open,close,read,write等。每个字符设备驱动程序必须实现struct file_operation的实例;每个串口设备驱动程序必须实现struct spi_ops_s的实例。spi_ops_s结构体的成员是指向函数的指针。static   struct  stm32_spidev_sg_spi1dev 结构体会把 static const  struct spi_ops_s g_sp1iops包含在内,这样g_spi1dev就可以代表一个spi端口了,然后利用up_spiinitialize就可以初始化spi端口了

主要过程就是:每个spi端口都会有structspi_ops_s的实例,spi_ops_s结构体的成员是指向函数的指针,这样g_spi1dev就可以代表一个spi端口了;然后利用up_spiinitialize就可以初始化spi端口了;之后使用spi端口的传感器在初始化中都会调用SPI::init(),从而调用up_spiinitialize。可以发现spi的操作没有register(),SPI驱动程序通常不由用户代码直接访问,但通常绑定到另一个更高级别的设备驱动程序(例如mpu6000),绑定的顺序是:从硬件特定的SPI设备驱动程序获取struct spi_dev_s的实例,将该实例提供给较高级别设备驱动程序的初始化方法。(这部分自己还没搞懂,先搁到这里吧)

pixhawk px4 spi设备驱动_pixhawk驱动下载-CSDN博客

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

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

相关文章

pg_dump导出数据时报错no PostgreSQL user name specified in startup packet

pg_dump版本号&#xff1a;14.9 远程pgsql版本&#xff1a;12.13 (通过select version()语句查看) 报错信息&#xff1a; no PostgreSQL user name specified in startup packet pg_dump(72846,0x1dc7b6100) malloc: *** error for object 0x6f: pointer being freed was no…

Buildroot 之二 详解构建架构、流程、external tree、示例

构建系统 Buildroot 中的构建系统使用的是从 Linux Kernel(4.17-rc2) 中移植的 Kconfig(配置) + Makefile & Kbuild(编译)这套构建系统,移植后的源码位于 support/kconfig/ 目录下。Buildroot 本身是一个构建系统,与直接编译源码不同,因此,它对这套系统进行了比较…

web服务器集群试题

服务器IP地址规划&#xff1a;client&#xff1a;12.0.0.12/24&#xff0c;网关服务器&#xff1a;ens36:12.0.0.1/24、ens33&#xff1a;192.168.11.1/24&#xff1b;Web1&#xff1a;192.168.11.10/24&#xff0c;Web2&#xff1a;192.168.11.11/24&#xff0c;Nginx&#xf…

Unity游戏中数据结构的使用(c#)

作为游戏开发者,我们不断处理大量数据——无论是玩家库存、敌人人工智能还是世界状态。有效组织和访问这些数据对于优化性能和创建沉浸式游戏体验至关重要。这就是数据结构发挥作用的地方! 数据结构为我们提供了有效存储、组织和操作数据的强大工具。通过利用 Unity 中的数据…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Stack容器组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Stack容器组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Stack容器组件 堆叠容器&#xff0c;子组件按照顺序依次入栈&#xff0c;后一…

Yarn:Node.js依赖管理的现代选择

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

HTML5七天学会基础动画网页10(2)

制作立方体 学完前面的基础内容&#xff0c;制作立方体是个不错的练习方法&#xff0c;先看成品 再分析一下&#xff0c;六个面让每个面旋转平移就可以实现一个立方体&#xff0c;来看代码: <title> 制作立方体</title> <style> *{ margin: 0; padding: 0; …

Kotlin:枚举类

点击查看枚举类中文文档 点击查看枚举类英文文档 枚举类的最基本的用法是实现类型安全的枚举&#xff1a; enum class Direction {NORTH, SOUTH, WEST, EAST }每个枚举常量都是一个对象。枚举常量用逗号分隔。 初始化 因为每一个枚举都是枚举类的实例&#xff0c;所以他们可…

零基础学习JS--基础篇--正则表达式

正则表达式 正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中&#xff0c;正则表达式也是对象。这些模式被用于 RegExp 的 exec 和 test 方法&#xff0c;以及 String 的 match、matchAll、replace、search 和 split 方法。 创建一个正则表达式&#xff1a; 你…

三大排序:冒泡、选择、插入

冒泡排序&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法。它通过比较相邻元素的大小&#xff0c;并交换它们的位置&#xff0c;使较大&#xff08;或较小&#xff09;的元素逐渐“浮”到数组的一端&#xff0c;从而实现排序的目的。 下面是冒…

【HTML】div设为输入框,巧妙添加placeholder效果

效果图 文本撑满时 初始化时 码 contenteditable"true" 设为可编辑状态 <divcontenteditable"true"placeholder"评个小论吧&#xff01;"class"textarea"></div><style>/* 设置最小高度&#xff0c;超过滚动显示 …

Qt/QML编程之路:QWidget和QML编程的差异(44)

前言: 如果你开始使用Qt,那么必然会遇到QWidget和QML,一开始甚至可能很迷糊,怎么一会qml一会QWidget,两者到底有啥区别? Qt是一个流行的跨平台应用程序框架,允许开发人员使用包括C++在内的各种编程语言创建图形用户界面(GUI)和其他类型的应用程序。Qt提供了一组库和…

python基础练习 VIP试题17道

一、阶乘计算 题目描述 输入一个正整数n&#xff0c;输出n!的值&#xff0c;其中n!123*…*n。 算法描述 n!可能很大&#xff0c;而计算机能表示的整数范围有限&#xff0c;需要使用高精度计算的方法。使用一个数组A来表示一个大整数a&#xff0c;A[0]表示a的个位&#xff0…

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 3、线条平滑曲面但有条纹

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata im…

操作系统:进程地址空间

目录 1.程序地址空间 1.1.程序地址空间的介绍 1.2.程序地址空间的本质 2.进程地址空间 3.Linux下的地址空间 1.程序地址空间 1.1.程序地址空间的介绍 我们在学习C/C时&#xff0c;对于各组分的地址分配在程序地址空间的不同模块 如图我们能够验证各组分的对应的地址排布位…

xss.haozi.me靶场“0x00-0x0A”通关教程

君衍. 一、靶场介绍二、第一关 0x00 不做限制三、第二关 0x01 文本闭合标签绕过四、第三关 0x02 双引号闭合绕过五、第四关 0x03 过滤括号六、第五关 0x04 编码绕过七、第六关 0x05 注释闭合绕过八、第七关 0x06 换行绕过九、第八关 0x07 删除标签十、第九关 0x08 多加空格绕过…

L2-002 链表去重 (25 分)(数组模拟)(附链表相关)

给定一个带整数键值的链表 L&#xff0c;你需要把其中绝对值重复的键值结点删掉。即对每个键值 K&#xff0c;只有第一个绝对值等于 K 的结点被保留。同时&#xff0c;所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15&#xff0c;你需要输出去重后…

负数的四舍五入

负数的四舍五入 标签:基础 System.out.println(Math.round(-0.2)); System.out.println(Math.round(-0.5)); System.out.println(Math.round(-0.6));0 0 -1理解:四舍五入,找一个离目标小数近的整数,-0.2和0近,-0.6和-1近,中间的往右靠

力扣每日一题 将标题首字母大写 模拟 String API

Problem: 2129. 将标题首字母大写 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 灵神题解 复杂度 ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public String capitalizeTitle(String title)…

vue3的组件间的v-model参数

v-model 参数 默认情况下&#xff0c;组件上的 v-model 使用 title作为 prop 和 update:title作为事件。我们可以通过向 v-model 传递参数来修改这些名称&#xff1a; zizhi是我自己创建的组件 <zizhi v-model:title"modelValue"></zizhi> 如何让父组件…