LSM6DSV16X驱动移植+调试

LSM6DSV16X驱动移植+调试

背景介绍

工作需要,我这个搞硬件的也要开始往软件靠,作为中年老登,写点东西记录一下全过程。前后大概花了一周时间完成移植+调试,目前对器件也算是比较熟悉了。下面开始一个一个说吧。

器件相关

简单介绍所用的IMU和MCU平台。

IMU

这次需要做移植和调试的器件是LSM6DSV16X,一颗ST的6轴。下面是器件的主要参数。

LSM6DSV16X内建了一些常用的功能例如计步器、倾角测量、大运动监测(例如跌落,敲击等),这些高级的功能在本次调试中不会讲,我这里只做最简单的数据获取。

MCU

MCU选用ST的STM32F130ZET6,因为我的开发板型号就这个。这个系列的MCU资料很多,这里不再详述。我设计的目标是通过LSM6DSV16X的两个中断引脚(INT1 和INT2)来触发MCU的中断,然后打印原始数据和转换后的数据。配置IMU用SPI,打印用UART。根据我开发板的原理图,我使用SPI2和UART2,同时用PG2和PG3这两个管脚作为中断输入引脚。

软件准备

这里会描述写代码之前的准备,包括IMU的一些具体配置以及CUBE MX在具体项目上的应用,还有例程代码的获取等。

MCU项目构建

我的开发环境是Keil, 之前的学习过程中是有做过一些简单的例程,然后也是有相应的模板。这些模板的好处是结构清晰,哪些文件在哪里算比较熟悉。这次没有使用的原因是用了CubeMX,用完之后发现点点点比之前那种复制一堆文件到固定的文件夹里要方便不少,所以之后应该会沿用CubeMX。如果换平台的话再说吧。

Cube MX 构建过程

安装注册的过程略过,这里直接看怎么用。登录后界面这样:

忽略旁边的Recent Opened Projects. 第一次用的话是没有的。这里ST给了3种方式来开始构建项目。一般我们用第一种(ACCESS TO MCU SELECTOR),知道自己MCU的型号就可以了。点了之后,在MCU型号栏输入自己的MCU型号:

右手边就会出来两种,尾椎不一样,但其实是一样的,TR一般是包装形式,不用在意。选好之后,点击右上的Start Project:

等待一会,会进入这个界面:

这个就是项目生成的主界面了。配置的顺序是先配置时钟树,然后再配置别的。配置时钟树之前,首先要去System Core里启用RCC,这个是外部的时钟源。大部分的应用都会有外部晶振作为基准源,比内部的RC时钟要准很多:

我这里启用了HSE和LSE,外部晶振是8MHz/32.768KHz这两个,别的不用动,然后就可以配置时钟树了:

配好之后:

看着很复杂,其实自己配置只有这4个地方需要动。

第一个是选HSI还是HSE,我们肯定是选HSE了。

第二个是配置PLL的倍数,我们希望芯片越快越好,所以倍数选X9, 8X9= 72MHz

第三个是选SYSYCLK的时钟源,可以直接用HSE,但只有8MHz,我们肯定是选PLLCLK,这样就是72MHz了

第四个是APB1外设时钟,这个有限制,这颗MCU不能支持72MHz,所以在APB1 Prescaler里选了/2,得到72/2=36MHz

然后我习惯从接口开始配置。我们需要启用SPI2和UART2,然后使用PG2/PG3作为中断输入。同时,我这个开发板是有外部晶振的,一会到时钟树再说。

在Connectivity里点击启用SPI2,模式是全双工Master,同时不启用硬件NSS:

这里注意红框的部分,这个SPI通讯的数据速率,调试过程中一般是选得比较低,这里默认的Prescaler是2,导致波特率18M,太高了,我改成32,波特率就只有1.125M了。另外,SPI通讯的极性和相位按照IMU手册里配置(CPOL 和CPHA)。不启用硬件NSS的原因是开发板上SPI2的NSS 管脚PB12有别的用处。所以只好使用软件了。

然后就是串口了,这里使用USART2, 异步模式:

提一嘴,异步模式是不用时钟的,也就是双方按约定的波特率通讯,有点是只要3根线,低速场景适用。

然后就是配置GPIO,我们需要配置1根选通线,2根中断输入线。首先配置选通:

配置好之后,在System Core的GPIO选项里就会出现这个IO,点击之后在下面配置为推挽输出,默认拉高。因为PG7是作为选通线,SPI的协议里是选通之后拉低,默认状态就是高。这里一定要这样配置,否则SPI有可能跑不起来。没有配置OD(开漏)是因为开发板上这个管脚没有上拉,自己设计的时候如果有是可以做OD的。也可以写一下label,明确之后的用途。配置好之后这样:

之后配置中断:

这里以PE2为例,首先配置GPIO_EXTI,类似上面配置CS,这里配好之后这样:

这里触发模式是上升沿,因为IMU给的中断是默认低,数据OK了从管脚给个高,所以我监测了上升沿。PE3也是一样的:

中断管脚配置了,接下来就是中断向量表的配置,在System Core-NVIC里面:

因为我只要GPIO的中断,所以只使能了在EXTI line2 和EXTI line3。优先级都是2,0,这个在生成代码之后可以改。

然后就是在System Core的SYS里选一下Debug 模式:

根据你手头的调试器,选个合适的。我手头是ST Link,可以选JTAG或者是SW, 我选了SW。这个后面可以在Keil里修改。

到这里,基本的配置就结束了。接下来是项目配置:

需要写的是红框部分。我用的是Keil,所以在Toolchain/IDE选了MDK-ARM和V5的版本。

然后就可以生成代码了:

注意红框部分,我改成了拷贝必要的库,并且初始化外设的时候按.C和.H对生成。这样方便我们修改。生成好之后:

Core里面是刚才配置的外设相关的代码,Drivers里是HAL库相关的东西。我们的代码基本是在Core里修改或添加。MDK-ARM里面就是Keil的工程文件了,使用Keil打开:

这比一个个去复制粘贴可方便太多了。

ST官方例程获取

找了技术支持,官方的C驱动在:

c 复制代码
https://github.com/STMicroelectronics/STMems_Standard_C_drivers

在里面找到

进去之后:

driver 和examples都需要拉取到本地。具体过程搜一下,我记得我是用了个小工具,直接把这个目录打包下载了。然后就是添加到本地工程里。这个是driver:

这个是example:

基础移植

lsm6dsv16x_reg.c放到工程目录中Core\Src里,lsm6dsv16x_reg.h放到Core\Inc里。在使用example之前,先看看说明,不一定都是我们要的。README里的信息很重要:

按之前的需求,可能相关的就这几个例子了。带中断的就是lsm6dsv16x_read_data_irq.c 这个例子,但这个例子说只是INT1,并且只有加速度计的中断。那我们还得找一个陀螺仪和加速度计都有的。polling mode那个都有,fifo也看着都有。都弄进来看看什么成色。先看lsm6dsv16x_read_data_irq.c:

390行,还好。说明里写的是这个代码使用的板子,我用自己的开发板,SO...先看看例子的说明吧:

红框部分明确了,需要修改的就是:

c 复制代码
platform_write`, `platform_read`,
 * `tx_com` and 'platform_init'

别的部分先不看,看看这几个都干啥了。

c 复制代码
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
                              uint16_t len)
{
#if defined(NUCLEO_F401RE)
  HAL_I2C_Mem_Write(handle, LSM6DSV16X_I2C_ADD_L, reg,
                    I2C_MEMADD_SIZE_8BIT, (uint8_t*) bufp, len, 1000);
#elif defined(STEVAL_MKI109D)
  HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_RESET);
  HAL_SPI_Transmit(handle, &reg, 1, HAL_MAX_DELAY);
  HAL_SPI_Transmit(handle, (uint8_t*) bufp, len, HAL_MAX_DELAY);
  HAL_GPIO_WritePin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin, GPIO_PIN_SET);
#elif defined(STEVAL_MKI109V3)
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_RESET);
  HAL_SPI_Transmit(handle, &reg, 1, 1000);
  HAL_SPI_Transmit(handle, (uint8_t*) bufp, len, 1000);
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_SET);
#elif defined(SPC584B_DIS)
  i2c_lld_write(handle,  LSM6DSV16X_I2C_ADD_L & 0xFE, reg, (uint8_t*) bufp, len);
#elif defined(NUCLEO_H503RB)
  i3c_write(handle, i3c_dyn_addr, reg, (uint8_t*) bufp, len);
#endif
  return 0;
}
}

根据平台不同,都是条件编译,以STEVAL_MKI109为例(这个正好是SPI),这不就是SPI写吗,改成我们的就行了。platform_read也差不多,就是在用SPI读。改一下:

c 复制代码
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
                              uint16_t len)
{
    uint8_t tx_buf[256];
    tx_buf[0] = reg & 0x7F;                     // 写命令
    for (uint16_t i = 0; i < len; i++) {
        tx_buf[1 + i] = bufp[i];
    }
    HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_RESET);
    HAL_StatusTypeDef status = HAL_SPI_Transmit((SPI_HandleTypeDef*)handle, tx_buf, len + 1, 1000);
    HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_SET);

    return (status == HAL_OK) ? 0 : -1;
}

static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
                             uint16_t len)
{    
    uint8_t cmd = reg | 0x80;                   // 读命令
    HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_RESET);
    HAL_StatusTypeDef status = HAL_SPI_Transmit((SPI_HandleTypeDef*)handle, &cmd, 1, 1000);
    if (status == HAL_OK) {
        status = HAL_SPI_Receive((SPI_HandleTypeDef*)handle, bufp, len, 1000);
    }
    HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_SET);
    return (status == HAL_OK) ? 0 : -1;
}

然后看看tx_com:

c 复制代码
static void tx_com(uint8_t *tx_buffer, uint16_t len)
{
#if defined(NUCLEO_F401RE)
  HAL_UART_Transmit(&huart2, tx_buffer, len, 1000);
#elif defined(STEVAL_MKI109D)
  CDC_Transmit_FS(tx_buffer, len);
#elif defined(STEVAL_MKI109V3)
  CDC_Transmit_FS(tx_buffer, len);
#elif defined(SPC584B_DIS)
  sd_lld_write(&SD2, tx_buffer, len);
#elif defined(NUCLEO_H503RB)
  HAL_UART_Transmit(&huart3, tx_buffer, len, 1000);
#endif
}

这个好像是管print的,看结果到底通过那个接口打印。我们是要UART2,所以需要改成UART2:

c 复制代码
static void tx_com(uint8_t *tx_buffer, uint16_t len)
{
  HAL_UART_Transmit(&huart2, tx_buffer, len, 1000);
}

然后是platform_init:

c 复制代码
static void platform_init(void *handle)
{
#if defined(STEVAL_MKI109D)
  struct spi_conf spi_conf;

  /* init SPI bus communication */
  spi_conf.wire = WIRE_4;
  spi_init(&spi_conf);

  /* set VDD/VDDIO on DIL24 */
  set_vdd(LSM6DSV16X_VDD);
  set_vddio(LSM6DSV16X_VDDIO);
  delay(100);

#elif defined(STEVAL_MKI109V3)
  TIM3->CCR1 = PWM_3V3;
  TIM3->CCR2 = PWM_3V3;
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
  HAL_Delay(1000);

#elif defined(NUCLEO_H503RB)
  i3c_set_bus_frequency(handle, 1000000);
  i3c_rstdaa(handle);
  i3c_setdasa(handle, LSM6DSV16X_I2C_ADD_L, &i3c_dyn_addr, 1);
  i3c_set_bus_frequency(handle, 12500000);

#endif
}

这个好像是在初始化和IC的通讯接口。但是我们用CubeMX生成的代码里已经初始化过了,所以简单写个CS拉高就行(虽然好像没什么用):

c 复制代码
static void platform_init(void *handle)
{
    HAL_GPIO_WritePin(CS_GPIO_PORT, CS_GPIO_PIN, GPIO_PIN_SET);
    HAL_Delay(10);
}

这里没提到的还有一个platform_delay。我们都HAL库了,直接改:

c 复制代码
static void platform_delay(uint32_t ms)
{
  HAL_Delay(ms);
}

这个例子到底干了什么

跳过函数声明和一堆头文件还有变量声明,具体看看这个例子都对IMU做了什么。先看看代码:

c 复制代码
void lsm6dsv16x_read_data_irq_handler(void)
{
  drdy_event = 1;
}

/* Main Example --------------------------------------------------------------*/
void lsm6dsv16x_read_data_irq(void)
{
  lsm6dsv16x_pin_int_route_t pin_int;

  /* Initialize mems driver interface */
  dev_ctx.write_reg = platform_write;
  dev_ctx.read_reg = platform_read;
  dev_ctx.mdelay = platform_delay;
  dev_ctx.handle = &SENSOR_BUS;

  /* Init test platform */
  platform_init(dev_ctx.handle);

  /* Wait sensor boot time */
  platform_delay(BOOT_TIME);

  /* Check device ID */
  lsm6dsv16x_device_id_get(&dev_ctx, &whoamI);

  if (whoamI != LSM6DSV16X_ID)
    while (1);

  /* Restore default configuration */
  lsm6dsv16x_sw_por(&dev_ctx);

  /* Enable Block Data Update */
  lsm6dsv16x_block_data_update_set(&dev_ctx, PROPERTY_ENABLE);

#if defined(NUCLEO_H503RB)
  /* if I3C is used then INT pin must be explicitly enabled */
  lsm6dsv16x_i3c_int_en_set(&dev_ctx, 1);
#endif

  pin_int.drdy_xl = PROPERTY_ENABLE;
  lsm6dsv16x_pin_int1_route_set(&dev_ctx, &pin_int);
  //lsm6dsv16x_pin_int2_route_set(&dev_ctx, &pin_int);

  /* Set Output Data Rate.
   * Selected data rate have to be equal or greater with respect
   * with MLC data rate.
   */
  lsm6dsv16x_xl_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_120Hz);
  /* Set full scale */
  lsm6dsv16x_xl_full_scale_set(&dev_ctx, LSM6DSV16X_2g);

  /* Configure filtering chain */
  filt_settling_mask.drdy = PROPERTY_ENABLE;
  filt_settling_mask.irq_xl = PROPERTY_ENABLE;
  filt_settling_mask.irq_g = PROPERTY_ENABLE;
  lsm6dsv16x_filt_settling_mask_set(&dev_ctx, filt_settling_mask);
  lsm6dsv16x_filt_xl_lp2_set(&dev_ctx, PROPERTY_ENABLE);
  lsm6dsv16x_filt_xl_lp2_bandwidth_set(&dev_ctx, LSM6DSV16X_XL_STRONG);

  /* handle drdy events */
  while (1) {
    if (drdy_event) {
      lsm6dsv16x_data_ready_t drdy;

      drdy_event = 0;

      /* Read output only if new xl value is available */
      lsm6dsv16x_flag_data_ready_get(&dev_ctx, &drdy);

      if (drdy.drdy_xl) {
        /* Read acceleration field data */
        memset(data_raw_acceleration, 0x00, 3 * sizeof(int16_t));
        lsm6dsv16x_acceleration_raw_get(&dev_ctx, data_raw_acceleration);
        acceleration_mg[0] =
          lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[0]);
        acceleration_mg[1] =
          lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[1]);
        acceleration_mg[2] =
          lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[2]);
        snprintf((char *)tx_buffer, sizeof(tx_buffer),
                "Acceleration [mg]:%4.2f\t%4.2f\t%4.2f\r\n",
                acceleration_mg[0], acceleration_mg[1], acceleration_mg[2]);
        tx_com(tx_buffer, strlen((char const *)tx_buffer));
      }
    }
  }
}

中断服务函数里把drdy_event置1,然后就是下面的主函数。注释里写的都挺清楚了。

c 复制代码
  /* Initialize mems driver interface */
  dev_ctx.write_reg = platform_write;
  dev_ctx.read_reg = platform_read;
  dev_ctx.mdelay = platform_delay;
  dev_ctx.handle = &SENSOR_BUS;

这里的初始化,我们只有SENSOR_BUS不知道是什么,找到定义发现就是通讯接口,改成:

c 复制代码
  /* Initialize mems driver interface */
  dev_ctx.write_reg = platform_write;
  dev_ctx.read_reg = platform_read;
  dev_ctx.mdelay = platform_delay;
  dev_ctx.handle = &hspi2;

接下来是握手和恢复初始化设置:

c 复制代码
 /* Init test platform */
  platform_init(dev_ctx.handle);

  /* Wait sensor boot time */
  platform_delay(BOOT_TIME);

  /* Check device ID */
  lsm6dsv16x_device_id_get(&dev_ctx, &whoamI);

  if (whoamI != LSM6DSV16X_ID)
    while (1);

  /* Restore default configuration */
  lsm6dsv16x_sw_por(&dev_ctx);

  /* Enable Block Data Update */
  lsm6dsv16x_block_data_update_set(&dev_ctx, PROPERTY_ENABLE);

最后一个Enable Block Data Update,看了一下是操作CTRL3寄存器的,使能了一个功能,看看是什么。

使能之后,就可以保证数据完整了。没有读所有的数据之前,输出的数据是不变的。嗯,理解。然后:

c 复制代码
#if defined(NUCLEO_H503RB)
  /* if I3C is used then INT pin must be explicitly enabled */
  lsm6dsv16x_i3c_int_en_set(&dev_ctx, 1);
#endif

  pin_int.drdy_xl = PROPERTY_ENABLE;
  lsm6dsv16x_pin_int1_route_set(&dev_ctx, &pin_int);
  //lsm6dsv16x_pin_int2_route_set(&dev_ctx, &pin_int);

之后看到int1 route set,这肯定是配置INT1管脚的功能。drdy_xl在查看定义之后发现是加速度计的data ready标志,所以lsm6dsv16x_pin_int1_route_set(&dev_ctx, &pin_int); 就是把加速度计的data ready路由到INT1上。 仿照这个写一下INT2:

c 复制代码
  pin_int.drdy_xl = PROPERTY_ENABLE;
  lsm6dsv16x_pin_int1_route_set(&dev_ctx, &pin_int);
  
  pin_int.drdy_xl = PROPERTY_DISABLE;
  pin_int.drdy_g  = PROPERTY_ENABLE;
  lsm6dsv16x_pin_int2_route_set(&dev_ctx, &pin_int);

这样就把陀螺的data ready 路由到INT2上了。

然后:

c 复制代码
 /* Set Output Data Rate.
   * Selected data rate have to be equal or greater with respect
   * with MLC data rate.
   */
  lsm6dsv16x_xl_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_120Hz);
  /* Set full scale */
  lsm6dsv16x_xl_full_scale_set(&dev_ctx, LSM6DSV16X_2g);

设置输出的数据速率和全量程。我需要加陀螺的,而且数据率也不用改太高,60Hz足够:

c 复制代码
  /* Set Output Data Rate.
   * Selected data rate have to be equal or greater with respect
   * with MLC data rate.
   */
  lsm6dsv16x_xl_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_60Hz);
  lsm6dsv16x_gy_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_60Hz);
  /* Set full scale */
  lsm6dsv16x_xl_full_scale_set(&dev_ctx, LSM6DSV16X_2g);
  lsm6dsv16x_gy_full_scale_set(&dev_ctx, LSM6DSV16X_500dps);

然后是滤波器设置:

c 复制代码
  /* Configure filtering chain */
  filt_settling_mask.drdy = PROPERTY_ENABLE;
  filt_settling_mask.irq_xl = PROPERTY_ENABLE;
  filt_settling_mask.irq_g = PROPERTY_ENABLE;
  lsm6dsv16x_filt_settling_mask_set(&dev_ctx, filt_settling_mask);
  lsm6dsv16x_filt_xl_lp2_set(&dev_ctx, PROPERTY_ENABLE);
  lsm6dsv16x_filt_xl_lp2_bandwidth_set(&dev_ctx, LSM6DSV16X_XL_STRONG);

这块就得参考一下别的例子了,这里只有加速度计的配置。我们在lsm6dsv16x_read_data_polling.c 这个例子里找到:

c 复制代码
  /* Configure filtering chain */
  filt_settling_mask.drdy = PROPERTY_ENABLE;
  filt_settling_mask.irq_xl = PROPERTY_ENABLE;
  filt_settling_mask.irq_g = PROPERTY_ENABLE;
  lsm6dsv16x_filt_settling_mask_set(&dev_ctx, filt_settling_mask);
  lsm6dsv16x_filt_gy_lp1_set(&dev_ctx, PROPERTY_ENABLE);
  lsm6dsv16x_filt_gy_lp1_bandwidth_set(&dev_ctx, LSM6DSV16X_GY_ULTRA_LIGHT);
  lsm6dsv16x_filt_xl_lp2_set(&dev_ctx, PROPERTY_ENABLE);
  lsm6dsv16x_filt_xl_lp2_bandwidth_set(&dev_ctx, LSM6DSV16X_XL_STRONG);

拿来吧你~

到这里,终于看到while(1)了。所以这之前对IMU的配置就结束了,我们加个打印吧。

c 复制代码
  printf("IMU initialized!!\r\n");

看看循环里还干了什么(直接看lsm6dsv16x_read_data_polling.c):

c 复制代码
  while (1) {
    lsm6dsv16x_data_ready_t drdy;

    /* Read output only if new xl value is available */
    lsm6dsv16x_flag_data_ready_get(&dev_ctx, &drdy);

    if (drdy.drdy_xl) {
      /* Read acceleration field data */
      memset(data_raw_acceleration, 0x00, 3 * sizeof(int16_t));
      lsm6dsv16x_acceleration_raw_get(&dev_ctx, data_raw_acceleration);
      acceleration_mg[0] =
        lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[0]);
      acceleration_mg[1] =
        lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[1]);
      acceleration_mg[2] =
        lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[2]);
      snprintf((char *)tx_buffer, sizeof(tx_buffer),
              "Acceleration [mg]:%4.2f\t%4.2f\t%4.2f\r\n",
              acceleration_mg[0], acceleration_mg[1], acceleration_mg[2]);
      tx_com(tx_buffer, strlen((char const *)tx_buffer));
    }

    /* Read output only if new xl value is available */
    if (drdy.drdy_gy) {
      /* Read angular rate field data */
      memset(data_raw_angular_rate, 0x00, 3 * sizeof(int16_t));
      lsm6dsv16x_angular_rate_raw_get(&dev_ctx, data_raw_angular_rate);
      angular_rate_mdps[0] =
        lsm6dsv16x_from_fs2000_to_mdps(data_raw_angular_rate[0]);
      angular_rate_mdps[1] =
        lsm6dsv16x_from_fs2000_to_mdps(data_raw_angular_rate[1]);
      angular_rate_mdps[2] =
        lsm6dsv16x_from_fs2000_to_mdps(data_raw_angular_rate[2]);
      snprintf((char *)tx_buffer, sizeof(tx_buffer),
              "Angular rate [mdps]:%4.2f\t%4.2f\t%4.2f\r\n",
              angular_rate_mdps[0], angular_rate_mdps[1], angular_rate_mdps[2]);
      tx_com(tx_buffer, strlen((char const *)tx_buffer));
    }

    if (drdy.drdy_temp) {
      /* Read temperature data */
      memset(&data_raw_temperature, 0x00, sizeof(int16_t));
      lsm6dsv16x_temperature_raw_get(&dev_ctx, &data_raw_temperature);
      temperature_degC = lsm6dsv16x_from_lsb_to_celsius(
                           data_raw_temperature);
      snprintf((char *)tx_buffer, sizeof(tx_buffer),
              "Temperature [degC]:%6.2f\r\n", temperature_degC);
      tx_com(tx_buffer, strlen((char const *)tx_buffer));
    }
  }
}

这里它直接去读寄存器了,通过 lsm6dsv16x_flag_data_ready_get(&dev_ctx, &drdy); 对比irq里的,

c 复制代码
  while (1) {
    if (drdy_event) {
      lsm6dsv16x_data_ready_t drdy;

      drdy_event = 0;

      /* Read output only if new xl value is available */
      lsm6dsv16x_flag_data_ready_get(&dev_ctx, &drdy);

      if (drdy.drdy_xl) {
        /* Read acceleration field data */
        memset(data_raw_acceleration, 0x00, 3 * sizeof(int16_t));
        lsm6dsv16x_acceleration_raw_get(&dev_ctx, data_raw_acceleration);
        acceleration_mg[0] =
          lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[0]);
        acceleration_mg[1] =
          lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[1]);
        acceleration_mg[2] =
          lsm6dsv16x_from_fs2_to_mg(data_raw_acceleration[2]);
        snprintf((char *)tx_buffer, sizeof(tx_buffer),
                "Acceleration [mg]:%4.2f\t%4.2f\t%4.2f\r\n",
                acceleration_mg[0], acceleration_mg[1], acceleration_mg[2]);
        tx_com(tx_buffer, strlen((char const *)tx_buffer));
      }
    }
  }
}

结合一下,我们需要读的是加速度计和陀螺仪,我还想把原始数据打出来:

c 复制代码
  /* handle drdy events */
  while (1) {
    if (drdy_event) {
      lsm6dsv16x_data_ready_t drdy;

      drdy_event = 0;

      /* Read output only if new xl value is available */
      lsm6dsv16x_flag_data_ready_get(&dev_ctx, &drdy);

      if (drdy.drdy_xl) {
        /* Read acceleration field data */
        memset(data_raw_acceleration, 0x00, 3 * sizeof(int16_t));
        lsm6dsv16x_acceleration_raw_get(&dev_ctx, data_raw_acceleration);
        snprintf((char *)tx_buffer, sizeof(tx_buffer),
            "Acceleration [raw]:\t%d\t%d\t%d\r\n",
             data_raw_acceleration[0], data_raw_acceleration[1], data_raw_acceleration[2]);
        tx_com(tx_buffer, strlen((char const *)tx_buffer));          
        acceleration_mg[0] =
          lsm6dsv16x_from_fs8_to_mg(data_raw_acceleration[0]);
        acceleration_mg[1] =
          lsm6dsv16x_from_fs8_to_mg(data_raw_acceleration[1]);
        acceleration_mg[2] =
          lsm6dsv16x_from_fs8_to_mg(data_raw_acceleration[2]);
          
        snprintf((char *)tx_buffer, sizeof(tx_buffer),
            "Acceleration [mg]:\t%4.2f\t%4.2f\t%4.2f\r\n",
             acceleration_mg[0], acceleration_mg[1], acceleration_mg[2]);
        tx_com(tx_buffer, strlen((char const *)tx_buffer));
      }
      
      if (drdy.drdy_gy) {
      /* Read angular rate field data */
      memset(data_raw_angular_rate, 0x00, 3 * sizeof(int16_t));
      lsm6dsv16x_angular_rate_raw_get(&dev_ctx, data_raw_angular_rate);
      snprintf((char *)tx_buffer, sizeof(tx_buffer),
            "Angular rate [raw]:\t%d\t%d\t%d\r\n",
             data_raw_angular_rate[0], data_raw_angular_rate[1], data_raw_angular_rate[2]); 
      tx_com(tx_buffer, strlen((char const *)tx_buffer));        
      angular_rate_mdps[0] =
        lsm6dsv16x_from_fs500_to_mdps(data_raw_angular_rate[0]);
      angular_rate_mdps[1] =
        lsm6dsv16x_from_fs500_to_mdps(data_raw_angular_rate[1]);
      angular_rate_mdps[2] =
        lsm6dsv16x_from_fs500_to_mdps(data_raw_angular_rate[2]);
      snprintf((char *)tx_buffer, sizeof(tx_buffer),
              "Angular rate [mdps]:\t%4.2f\t%4.2f\t%4.2f\r\n",
              angular_rate_mdps[0], angular_rate_mdps[1], angular_rate_mdps[2]);
      tx_com(tx_buffer, strlen((char const *)tx_buffer));
      }
    }
  }
}

这里明显可以看到我们需要在中断处理函数里面把drdy_event 这个变量改成1。那么:

c 复制代码
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_2 || GPIO_Pin == GPIO_PIN_3)
    {
        drdy_event = 1;
    }
}

还需把print重定位到usart2上:

c 复制代码
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

int _write(int file, char *ptr, int len)
{
    for (int i = 0; i < len; i++)
    {
        HAL_UART_Transmit(&huart2, (uint8_t *)&ptr[i], 1, HAL_MAX_DELAY);
    }
    return len;
}

编译:

下载,连上串口看看打印:

示波器上看看中断情况:

相关推荐
l'm coming1 天前
[linux]内核启动加载驱动文件的流程
linux·arm开发·驱动开发·嵌入式
Code-keys1 天前
ARM NEON SIMD 编程实战:从音频信号处理到AI算子研发实战
arm开发·音视频·信号处理
lpl3129055091 天前
skynet 共享数据原理
服务器·c语言·lua
hahjee1 天前
【鸿蒙PC】kcp 移植:AtomCode Skills 4 步速通单文件 C 库适配
c语言·华为·harmonyos
AI科技星1 天前
《数术工坊:非欧射影录》类型:硬核光影·几何本源
c语言·开发语言·网络·量子计算·agi
QiLinkOS1 天前
极客与商业思维的融合实践(1)
c语言·数据库·c++·人工智能·算法·开源协议
有什么事1 天前
云手机多开哪个强?ARM架构:云手机多开的信任基石与性能核心
arm开发·智能手机·架构
头枝1 天前
ARM Cortex-M DWT CYCCNT 必须显式初始化,jlink调试时正常,使用时异常的问题
arm开发·stm32·单片机·问题·cyccnt
坚果派·白晓明1 天前
鸿蒙PC】libuv适配:AtomCode Skills一站式指南
c语言·c++·华为·ai编程·harmonyos·atomcode
c++之路1 天前
CMake 系列教程(五):进阶技巧
c语言·开发语言·c++