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

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芯片手册中数据的读取时序进行阅读分析,在这里就不进行分析了。





























































































你可能感兴趣的:(学习分享)