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