PX4中MPU6000数据读取程序的实现过程

2023-05-16

MPU6000::measure() 函数解析

MPU6000::measure()
    mpu_report; //原始数据结构体对象
    report; //整合后数据结构体对象

    _interface->read(MPU6000_SET_SPEED(MPUREG_INT_STATUS,MPU6000_HIGH_BUS_SPEED),

    (uint8_t *)&mpu_report,sizeof(mpu_report)));

//注 _interface = new MPU6000_spi(); <------所以即使_interface是Device*,调用到的也是MPU6000_spi类实现的         //read(C++多态)
    check_registers();
    memcmp(&mpu_report.accel_x[0], &_last_accel[0], 6);
    memcpy(&_last_accel[0], &mpu_report.accel_x[0], 6);
    report.accel_x = int16_t_from_bytes(mpu_report.accel_x);
    report.accel_y = int16_t_from_bytes(mpu_report.accel_y);
    report.accel_z = int16_t_from_bytes(mpu_report.accel_z);
    report.temp = int16_t_from_bytes(mpu_report.temp);
    report.gyro_x = int16_t_from_bytes(mpu_report.gyro_x);
    report.gyro_y = int16_t_from_bytes(mpu_report.gyro_y);
    report.gyro_z = int16_t_from_bytes(mpu_report.gyro_z);

    /*
    * Swap axes and negate y
    */
    int16_t accel_xt = report.accel_y;
    int16_t accel_yt = ((report.accel_x == -32768) ? 32767 : -report.accel_x);
    int16_t gyro_xt = report.gyro_y;
    int16_t gyro_yt = ((report.gyro_x == -32768) ? 32767 : -report.gyro_x);

    剩下的是坐标变换、滤波、发布topic等操作...
return;

所以这么分析,发现读取操作应该是在_interface->read()这一步里面进行的,所以接下来进一步的解析_interface->read()这一个函数。
MPU6000_spi::read() 函数解析
int MPU6000_SPI::read(unsigned reg_speed, void *data, unsigned count)
{
/* We want to avoid copying the data of MPUReport: So if the caller
* supplies a buffer not MPUReport in size, it is assume to be a reg or reg 16 read
* and we need to provied the buffer large enough for the callers data
* and our command.
*/
uint8_t cmd[3] = {0, 0, 0};
//根据用户传入的buffer大小,决定最终用哪个buffer
uint8_t *pbuff  =  count < sizeof(MPUReport) ? cmd : (uint8_t *) data ;
//这一段不知道在干嘛
if (count < sizeof(MPUReport))  {
/* add command */
count++;
}
set_bus_frequency(reg_speed);
/* Set command */
pbuff[0] = reg_speed | DIR_READ ;   //看语法 像是设置读取速度
/* Transfer the command and get the data */
int ret = transfer(pbuff, pbuff, count);   //<-----实际传送数据的函数
//这一段也是传入buffer不够情况下的处理,不知道在干嘛
if (ret == OK && pbuff == &cmd[0]) {
/* Adjust the count back */
count--;
/* Return the data */
memcpy(data, &cmd[1], count);
}
return ret == OK ? count : ret;
}

从transfer函数跟踪进去,到最后可以发现下面SPI_XXX等一系列宏组成的一组操作,要了解传输过程做了什么,就需要去解析底下列出的这些宏,以SPI_SETFREQUENCY为例进行解析。
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);

跳转到定义发现它通过d调用了一个函数,那么现在就得看看_dev是什么东西了,在class SPI里发现了 struct spi_dev_s *_dev; 这样一句说明,那么接下来就要看struct spi_dev_s这个结构体是怎么样定义的了。
#define SPI_SETFREQUENCY(d,f) ((d)->ops->setfrequency(d,f))
struct spi_dev_s这个结构体的定义如下:
struct spi_dev_s
{
  FAR const struct spi_ops_s *ops;
};

struct spi_ops_s
{
  CODE int      (*lock)(FAR struct spi_dev_s *dev, bool lock);
  CODE void     (*select)(FAR struct spi_dev_s *dev, uint32_t devid,bool selected);
  CODE uint32_t (*setfrequency)(FAR struct spi_dev_s *dev, uint32_t frequency);
#ifdef CONFIG_SPI_CS_DELAY_CONTROL
  CODE int      (*setdelay)(FAR struct spi_dev_s *dev, uint32_t a, uint32_t b,uint32_t c);
#endif
  CODE void     (*setmode)(FAR struct spi_dev_s *dev, enum spi_mode_e mode);
  CODE void     (*setbits)(FAR struct spi_dev_s *dev, int nbits);
#ifdef CONFIG_SPI_HWFEATURES
  CODE int      (*hwfeatures)(FAR struct spi_dev_s *dev,spi_hwfeatures_t features);
#endif
  CODE uint8_t  (*status)(FAR struct spi_dev_s *dev, uint32_t devid);
#ifdef CONFIG_SPI_CMDDATA
  CODE int      (*cmddata)(FAR struct spi_dev_s *dev, uint32_t devid,bool cmd);
#endif
  CODE uint16_t (*send)(FAR struct spi_dev_s *dev, uint16_t wd);
#ifdef CONFIG_SPI_EXCHANGE
  CODE void     (*exchange)(FAR struct spi_dev_s *dev,FAR const void *txbuffer, FAR void *rxbuffer,size_t nwords);
#else
  CODE void     (*sndblock)(FAR struct spi_dev_s *dev,FAR const void *buffer, size_t nwords);
  CODE void     (*recvblock)(FAR struct spi_dev_s *dev, FAR void *buffer,size_t nwords);
#endif
  CODE int      (*registercallback)(FAR struct spi_dev_s *dev,spi_mediachange_t callback, void *arg);
};

这样一看,_dev指针指向一个结构体,那个结构体包含一个指针指向另一个结构体,另一个结构体包含一堆的函数指针...那么就得看_dev是怎么初始化的了,还有那一堆的函数指针指向了哪些函数...可以发现_dev在构造函数中被初始化为了NULL,但是init()函数中对它进行了重新赋值,_dev = px4_spibus_initialize(get_device_bus()); 就是这样赋值的,传入的参数是这类总线的哪一条总线。那么现在就得看看px4_spibus_initialize()这个函数里面做了些什么,才可能可以确定哪些函数指针指向哪里,宏定义函数调用了哪些函数。下面是Device类的一些设备属性。在get_device_bus()的时候有用到。

struct DeviceStructure {
enum DeviceBusType bus_type : 3;  //类型是什么,是IIC 还是SPI 还是 CAN...
uint8_t bus: 5;    // which instance of the bus type 在这种类型总线的哪一条总线上
uint8_t address;   // address on the bus (eg. I2C address)
uint8_t devtype;   // device class specific device type
};

union DeviceId {
struct DeviceStructure devid_s;
uint32_t devid;
};

protected:
union DeviceId _device_id;             /**< device identifier information */

px4_spibus_initialize()函数解析:
我们找到了如下的一个宏定义
#define px4_spibus_initialize(bus_num_1based)   stm32_spibus_initialize(bus_num_1based)

那么接下来就是要解析stm32_spibus_initialize()这个函数做了哪些配置。跳转到定义,发现函数的内容如下:可以看到返回值类型就是SPI类中_dev成员的类型,那么_dev对象里的指针,还有那些函数指针的初始化过程就应该在这个函数中了,我们来大概解析一下这个函数做了些什么。好像函数里初始化了若干条SPI总线,我们取我们关切的SPI1进行分析,其余的暂且删掉不看。
FAR struct spi_dev_s *stm32_spibus_initialize(int bus)
{
  FAR struct stm32_spidev_s *priv = NULL;
  irqstate_t flags = enter_critical_section();
  if (bus == 1)
    {
      /* Select SPI1 */
      priv = &g_spi1dev;
      /* Only configure if the bus is not already configured */
      if ((spi_getreg(priv, STM32_SPI_CR1_OFFSET) & SPI_CR1_SPE) == 0)
        {
          /* Configure SPI1 pins: SCK, MISO, and MOSI */
          stm32_configgpio(GPIO_SPI1_SCK);
          stm32_configgpio(GPIO_SPI1_MISO);
          stm32_configgpio(GPIO_SPI1_MOSI);
          /* Set up default configuration: Master, 8-bit, etc. */
          spi_bus_initialize(priv);
        }
    }
  else
    {
      spierr("ERROR: Unsupported SPI bus: %d\n", bus);
      return NULL;
    }
  leave_critical_section(flags);
  return (FAR struct spi_dev_s *)priv;
}

最终找到了如下两个结构体变量,g_sp1iops和g_spi1dev。显然,可以看出函数指针是在这里初始化的了。#define SPI_SETFREQUENCY(d,f) ((d)->ops->setfrequency(d,f))这个宏定义函数就会通过一系列的指针最终调用到spi_setfrequency()这个函数,现在来解析一下spi_setfrequency()这个函数做了哪些设置来实现频率设置的。跳转到定义发现,是通过设定的频率,修改一些寄存器的值实现的。详细的设置过程要参照着STM32F427(对于pixhawk而言)的芯片手册查看,才能知道为什么那么设置。由于分析手册会使得篇幅太长就暂时不分析了。

static const struct spi_ops_s g_sp1iops =
{
  .lock              = spi_lock,
  .select            = stm32_spi1select,
  .setfrequency      = spi_setfrequency,
  .setmode           = spi_setmode,
  .setbits           = spi_setbits,
  .hwfeatures        = spi_hwfeatures,
  .status            = stm32_spi1status,
  .cmddata           = stm32_spi1cmddata,
  .send              = spi_send,
  .exchange          = spi_exchange,
  .sndblock          = spi_sndblock,
  .recvblock         = spi_recvblock,
  .registercallback  = stm32_spi1register,  /* Provided externally */
};


static struct stm32_spidev_s g_spi1dev =
{
  .spidev   = { &g_sp1iops },
  .spibase  = STM32_SPI1_BASE,
  .spiclock = STM32_PCLK2_FREQUENCY,
#ifdef CONFIG_STM32_SPI_INTERRUPTS
  .spiirq   = STM32_IRQ_SPI1,
#endif
#ifdef CONFIG_STM32_SPI_DMA
  .rxch     = DMACHAN_SPI1_RX,
  .txch     = DMACHAN_SPI1_TX,
#endif
};

在浏览的过程中发现,无论是配置还是读取少不了在代码中看到priv这个变量,那么priv这个变量的类型就变得很重要了,跳转到定义发现priv是一个struct stm32_spidev_s类型的指针,现在就要分析一下这个结构体的成员是哪些,各是什么作用的,这有助于理解程序的总体思想个框架。这个结构体的申明如下。他们的作用的话,在程序中有碰到再转到定义,结合程序中的用法,就大概能知道这些成员的作用是做什么的了。
struct stm32_spidev_s
{
  struct spi_dev_s spidev;     /* Externally visible part of the SPI interface */
  uint32_t         spibase;    /* SPIn base address */
  uint32_t         spiclock;   /* Clocking for the SPI module */
#ifdef CONFIG_STM32_SPI_INTERRUPTS
  uint8_t          spiirq;     /* SPI IRQ number */
#endif
#ifdef CONFIG_STM32_SPI_DMA
  volatile uint8_t rxresult;   /* Result of the RX DMA */
  volatile uint8_t txresult;   /* Result of the RX DMA */
  uint8_t          rxch;       /* The RX DMA channel number */
  uint8_t          txch;       /* The TX DMA channel number */
  DMA_HANDLE       rxdma;      /* DMA channel handle for RX transfers */
  DMA_HANDLE       txdma;      /* DMA channel handle for TX transfers */
  sem_t            rxsem;      /* Wait for RX DMA to complete */
  sem_t            txsem;      /* Wait for TX DMA to complete */
  uint32_t         txccr;      /* DMA control register for TX transfers */
  uint32_t         rxccr;      /* DMA control register for RX transfers */
#endif
  sem_t            exclsem;    /* Held while chip is selected for mutual exclusion */
  uint32_t         frequency;  /* Requested clock frequency */
  uint32_t         actual;     /* Actual clock frequency */
  uint8_t          nbits;      /* Width of word in bits (4 through 16) */
  uint8_t          mode;       /* Mode 0,1,2,3 */
};

非DMA模式下的SPI数据传输函数如下所示:
static void spi_exchange_nodma(FAR struct spi_dev_s *dev, FAR const void *txbuffer,FAR void *rxbuffer, size_t nwords)
{
  FAR struct stm32_spidev_s *priv = (FAR struct stm32_spidev_s *)dev;
  DEBUGASSERT(priv && priv->spibase);
  spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
  /* 8- or 16-bit mode? */
  if (spi_16bitmode(priv))
    {
      /* 16-bit mode */
      const uint16_t *src  = (const uint16_t *)txbuffer;
            uint16_t *dest = (uint16_t *)rxbuffer;
            uint16_t  word;
      while (nwords-- > 0)
        {
          /* Get the next word to write.  Is there a source buffer? */
          if (src)
            {
              word = *src++;
            }
          else
            {
              word = 0xffff;
            }

          /* Exchange one word */
          word = spi_send(dev, word);


          /* Is there a buffer to receive the return value? */
          if (dest)
            {
              *dest++ = word;
            }
        }
    }
  else
    {
      /* 8-bit mode */
      const uint8_t *src  = (const uint8_t *)txbuffer;
            uint8_t *dest = (uint8_t *)rxbuffer;
            uint8_t  word;

      while (nwords-- > 0)
        {
          /* Get the next word to write.  Is there a source buffer? */
          if (src)
            {
              word = *src++;
            }
          else
            {
              word = 0xff;
            }

          /* Exchange one word */
          word = (uint8_t)spi_send(dev, (uint16_t)word);

          /* Is there a buffer to receive the return value? */
          if (dest)
            {
              *dest++ = word;
            }
        }
    }
}

这个函数会在顶层程序调用SPI_EXCHANGE(_dev, send, recv, len);这个宏时被调用到,至于为什么,参考前面SPI_SETFREQUENCY宏定义函数的调用过程分析。

在分析过程中我们可以经常的看到括号里面所示的这样子的代码(spi_getreg(priv, STM32_SPI_SR_OFFSET)),看名字大概可以猜测出它是从某个寄存器里面读取数据,那我们就知道了,从寄存器读取数据是要传入地址的,然后(*某个地址)取出它的数据。那么某个地址是怎么传入的呢,我们看到函数传入了两个参数,其中第二个参数是偏移,看到有偏移量了,那么还差个基址(因为 基址+偏移=实际内存地址)。那么基址从哪里来呢,我们看到还有一个参数是priv,它是一个指向某个结构体的指针,那么基址应该就在被指向的某个结构体里定义了,果然 跳转到定义 发现被指向的stm32_spidev_s结构体有一个uint32_t spibase;成员这就是某条SPI总线对应的寄存器的基址了。

以上就是MPU6000在获取数据时的调用过程了,如果想要知道完整的寄存器配置、MPU6000读取数据的时序在程序中具体怎么实现,那么要将上面的节选代码对照这STM32F427手册SPI部分的寄存器配置和MPU6000芯片手册中数据的读取时序进行阅读分析,在这里就不进行分析了。





























































































本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PX4中MPU6000数据读取程序的实现过程 的相关文章

  • ardupilot & px4 书写自己的app & drivers (二)

    新建任务列表任务 打印时间 任务列表 const AP Scheduler span class hljs tag Task span Copter span class hljs tag scheduler tasks span span
  • PX4通过I2C方式添加自定义传感器(3)

    添加自定义传感器并实现数据的发送和订阅 1 前期准备 1 1 建立文件夹和相关文件配置 我是在src drivers distance sensor文件夹下操作的 xff0c 当然其他文件夹下都类似 首先建立了两个文件夹angle sour
  • 【2020-8-9】APM,PX4,GAZEBO,MAVLINK,MAVROS,ROS之间的关系以及科研设备选型

    0 概述 无人机自主飞行平台可以分为四个部分 xff1a 动力平台 xff0c 飞行控制器 xff0c 机载电脑和模拟平台 动力平台 xff1a 负责执行飞行任务 xff0c 包括螺旋桨 电机 机架等 xff0c 用于科研的一般都是F380
  • PX4位置控制offboard模式说明

    offboard模式的开发及应用 一 px4固件的模式 px4固件支持10几种飞行模式 xff0c 从代码结构上分析 xff0c 分为基本模式 自定义模式和自定义子模式 1 基本模式 基本模式又分为 xff0c 位置控制模式 自稳模式 手动
  • PX4 Bootloader下载及编译过程中的问题解决

    买来的雷迅的板子都是Bootloader已经烧进去了 xff0c Fireware也已经刷进去了 如果是自制的板子 xff0c 上位机根本没法识别板子 xff0c 必须先烧写下载Bootloader后编译好的bin文件 这篇记一下自己下载及
  • 初学PX4之环境搭建

    文章转自 xff1a http www jianshu com p 36dac548106b 前言 前段时间linux崩溃了 xff0c 桌面进去后只有背景 xff0c 折腾好久没搞定 xff0c 为了节省时间索性重装了系统 xff0c 同
  • PX4进入系统控制台以及运行程序

    这里提供进入控制台两种办法 1 运行 Tools mavlink shell py dev ttyACM0 是我进入Px4系统控制台的命令 xff0c 进入之后应该是这样 Pixhawk src Firmware Tools mavlink
  • PX4模块设计之四:MAVLink简介

    PX4模块设计之四 xff1a MAVLink简介 1 MAVLink PX4 应用简介2 MAVLink v2 0新特性3 MAVLink协议版本4 MAVLink通信协议帧4 1 MAVLink v1 0 帧格式4 2 MAVLink
  • PX4模块设计之六:PX4-Fast RTPS(DDS)简介

    64 TOC PX4模块设计之六 xff1a PX4 Fast RTPS DDS 简介 基于PX4开源软件框架简明简介的框架设计 xff0c 逐步分析内部模块功能设计 PX4 Fast RTPS DDS 具有实时发布 订阅uORB消息接口
  • PX4-4-任务调度

    PX4所有的功能都封装在独立的模块中 xff0c uORB是任务间数据交互和同步的工具 xff0c 而管理和调度每个任务 xff0c PX4也提供了一套很好的机制 xff0c 这一篇我们分享PX4的任务调度机制 我们以PX4 1 11 3版
  • px4仿真无法起飞问题(Failsafe enabled: no datalink)

    报错信息 问题描述 xff1a 使用JMAVSim和gazebo仿真px4起飞时报错如下 xff1a WARN commander Failsafe enabled no datalink 说不安全 解决方法 打开QGC 就可以起飞了
  • 【px4】运行mavsdk中的offboard example

    运行MAVSDK中的offboard例子时无人机不执行 想控制无人机前后左右移动 xff0c 在按照官方教程实现offboard 插件的时候 发现用action插件能正常起飞和降落 但是一旦执行到offboard的插件代码的时候就会自动降落
  • Px4源码框架结构图

    此篇blog的目的是对px4工程有一个整体认识 xff0c 对各个信号的流向有个了解 xff0c 以及控制算法采用的控制框架 PX4自动驾驶仪软件 可分为三大部分 xff1a 实时操作系统 中间件和飞行控制栈 1 NuttX实时操作系统 提
  • px4下载指定版本的固件、git用法

    https hub fastgit org PX4 PX4 Autopilot git describe tag 查看当前版本号 git tag l 查看所有版本 xff0c 也就是打个tag git checkout v1 9 1 跳转到
  • PX4飞控之自主返航(RTL)控制逻辑

    本文基于PX4飞控1 5 5版本 xff0c 分析导航模块中自护返航模式的控制逻辑和算法 自主返航模式和导航中的其他模式一样 xff0c 在Navigator main函数中一旦触发case vehicle status s NAVIGAT
  • PX4软件在环仿真注意点

    注 xff1a 最新内容参考PX4 user guide 点击此处 PX4下载指定版本代码和刷固件的三种方式 点击此处 PX4sitl固件编译方法 点击此处 PX4开发指南 点击此处 PX4无人机仿真 Gazebo 点击此处 px4仿真 知
  • PX4飞控的PPM接收机

    xff08 一 xff09 原理图 xff1a PX4飞控的PPM输入捕获由协处理器完成 xff0c 接在A8引脚 xff0c 对应Timer1的通道1 xff08 二 xff09 PPM协议 xff1a PPM的每一帧数据间隔为20ms
  • 步骤三:PX4,Mavros的下载安装及代码测试

    1 安装Mavros sudo apt install ros melodic mavros ros melodic mavros extras 2 安装Mavros相关的 geographiclib dataset 此处已经加了ghpro
  • PX4项目学习::(五)模块代码启动流程

    54条消息 PX4 模块代码启动流程 zhao23333的博客 CSDN博客
  • 大神浅谈无人机飞控软件设计 系统性总结

    写在前面 深感自己对飞控软件 算法的知识点过于杂乱 很久没有进行系统的总结了 因此决定写几篇文章记录一些飞控开发过程的知识点 主要是针对一些软件 算法部分进行讨论 如内容有错误 欢迎指出 1 飞控软件的基本模块 无人机能够飞行主要是依靠传感

随机推荐