深圳专业专业网站设计公司,网站域名申请,自考免费自学网站,女人做春梦视频网站一、SPI类
SPI类的参数#xff1a;设备名称#xff0c;devname设备节点名称#xff0c;总线#xff0c;device片选信号线#xff0c;SPI模式#xff0c;时钟频率#xff0c;中断。SPI类继承VDev类。 SPI协议在spi.cpp文件中#xff0c;涉及到了cdev和device的操作。c…一、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)取消选择设备使得引脚电平由高变低取消片选信号检查设备是否在线默认在线初始化cdevSPI类是基于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,readwrite等。每个字符设备驱动程序必须实现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博客