Zhao_kar 发表于 2023-10-24 20:36

【STM32L476RG】第七节——基于官方DMP库的简易计步器

<div class='showpostmsg'> 本帖最后由 Zhao_kar 于 2023-10-24 20:35 编辑

<p style="text-align: center;"><strong><span style="font-size:24px;">【STM32L476RG】第七节&mdash;&mdash;基于官方DMP库的简易计步器</span></strong></p>

<p>项目简介:</p>

<p>【STM32L476RG测评】&mdash;&mdash;最终成品:基于MPU6050的计步器</p>

<p>本次参加eeworld的ST开发板测评的活动,申请到了STM32L476RG这个板子,现已完成作品,故于此发布成品视频。</p>

<p><span style="color:#e74c3c;">项目大概简介:</span></p>

<p><span style="color:#e74c3c;">本项目使用L476的开发板完成了一个简易计步器</span></p>

<p><span style="color:#e74c3c;">1、使用mpu6050传感器,移植官方DMP的库,除了计步,还可以显示陀螺仪的两组基本参数。</span></p>

<p><span style="color:#e74c3c;">2、使用HC05与手机端口通信,使得手机端也能接收到计步器的数据。</span></p>

<p><span style="color:#e74c3c;">3、使用oled屏幕的SPI协议实现简单通信,可以在屏幕上显示步数。</span></p>

<p><span style="color:#e74c3c;">4、可以与电脑端的串口调试助手进行数据测试。</span></p>

<p><span style="color:#e74c3c;">5、可以使用串口屏进行显示(这里没有使用,是因为身边没有合适的供电电源了,单片机的供电已经远远无法支持如此多的设备了)。</span></p>

<p><span style="color:#1abc9c;">闲聊:从测评开始到现在,也写了很多报告了,最后这一篇,我打算整篇重点讲DMP库,毕竟前面的报告都把其他的功能都实现了,所以这里就讲DMP和总体的调测。</span></p>

<p><span style="color:#1abc9c;">先说一下主要步骤:</span></p>

<ul>
        <li><span style="color:#1abc9c;">移植官方的DMP库,毫无疑问,要cubemx配置IIC,然后移植官方的库,因为hal的存在,所以要进行删改</span></li>
        <li><span style="color:#1abc9c;">cubemx配置GPIO的SPI,为了使用OLED</span></li>
        <li><span style="color:#1abc9c;">配置USART,开了三个,分别用于,PC端调试,HC05,串口屏</span></li>
        <li><span style="color:#1abc9c;">串口屏界面配置,因为简介说的电源问题没用上,用的话参考串口屏那篇</span></li>
        <li><span style="color:#1abc9c;">调用mputest的主函数,放到main的里面,注,这个函数是死循环,如果要改的话可以自己看看,这边没有改</span></li>
        <li><span style="color:#1abc9c;">大体为先把外设初始化,然后是主函数使用mputest,进入计步器功能,然后循环打印步数,同时在这个函数里面使用oled函数,去让oled显示,这个函数里面自带printf,然后再使能串口,如果是串口屏改一下函数,然后就可以输出给各个串口接收端口了。</span></li>
</ul>

<p><strong><span style="color:#f1c40f;"><span style="font-size:20px;">一、DMP库的移植</span></span></strong></p>

<p>1、cubemx的初始配置(图三:总体引脚图参考)</p>

<ul>
        <li>RCC+SYS+时钟树略</li>
        <li>USART三个,略</li>
        <li>SPI四个引脚,之前讲过,略</li>
        <li>IIC配置,快速+DMA(下图1)
        <div style="text-align: center;"></div>

        <p>&nbsp;</p>
        </li>
        <li>NVIC打开(图2)
        <div style="text-align: center;"></div>

        <div style="text-align: center;"></div>
        </li>
</ul>

<div><span style="font-size:18px;"><strong><span style="color:#e74c3c;"><span style="background-color:#f1c40f;">2、官方库的文件配置(主要部分)</span></span></strong></span></div>

<ul>
        <li>这里只需要改lib文件</li>
        <li>路径大概为,如果不会翻墙的话简易去找某火,大概为:传感器\姿态_MPU6050\参考资料\官方文档及库原文件\motion_driver_6.12\mpl libraries\arm\keil</li>
        <li>这个路径下面会有如下几个压缩包
        <div style="text-align: center;"></div>
        </li>
</ul>

<p>&nbsp;</p>

<ul>
        <li>这边因为是L476,也就是M4内核,所以使用M4的文件,解压,lib放到下面的文件,你的单片机是什么核心用什么,f7的也用m4</li>
        <li>找到四个关键代码文件,把他复制出来,添加到工程目录,建议使用mpu6050,然后放到里面,如下四个
        <div style="text-align: center;"></div>
        </li>
        <li>其中mpl文件夹,就是我们要操作的,里面可以看到是没有lib的,有了也没关系,删掉它,把前面解压的lib放进去,如图2
        <div style="text-align: center;"></div>

        <div style="text-align: center;"></div>
        </li>
        <li>ps,前面有点乱,这边建议就是,先把四个文件放到工程目录,然后把工程目录的lib文件删掉,把解压缩的放进去,前面讲的有点乱,我的问题。</li>
        <li>keil加一下工程目录,不细说了,如图,补充一个,记得添加文件的时候,mpl要加lib,圈的那个就是lib,选择全部文件就可以看到了。
        <div style="text-align: center;"></div>
        </li>
        <li>因为是官方库,魔术棒&gt;C++&gt;Define那里,原先应该是haluser什么的,这里要加一下,改成:
        <pre>
<code class="language-cpp">USE_HAL_DRIVER,STM32L476xx,MPL_LOG_NDEBUG=1,MPU6050,EMPL,USE_DMP,EMPL_TARGET_STM32F4

//注释:原因见官方文档,在doc的第三个:App Note 3- Motion Driver 6.12 Porting Guide</code></pre>

        <div style="text-align: center;"></div>
        </li>
        <li>然后编译,必定报错,这里有两个注意,第一个是,后续的所有头文件,改成你的单片机型号,如
        <pre>
<code class="language-cpp">#include "stm32l4xx.h"
//我这里是l4,你的是多少该多少,比如你是f7,那就改f7</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>第二个是,报错应该会有说一个board-st_discovery找不到的,去motion_driver_6.12\arm\STM32F4_MD6\Projects\eMD6\DeviceSupport,两个文件里面分别包含c和h,添加到mpu6050文件夹。(记得在keil里面添加文件)</li>
        <li>还有就是,因为hal是usart,官方是uart,h的标注可以改一下</li>
        <li>然后log_stm32c里面会报错fputc,是因为移植库里面有usart的函数,只需要在usart里面加一个这个,然后还是会报错,因为默认是fputc,改一下其他文件里的名字就行&nbsp;
        <pre>
<code class="language-cpp">int fputcc(int ch)
{
HAL_UART_Transmit(&amp;huart1, (uint8_t *)&amp;ch, 1, 0xffff);
        HAL_UART_Transmit(&amp;huart2, (uint8_t *)&amp;ch, 1, 0xffff);
        HAL_UART_Transmit(&amp;huart3, (uint8_t *)&amp;ch, 1, 0xffff);
return ch;
}
//在usart.c里面加这个

//别忘了在usart.h加声明
/* USER CODE BEGIN Includes */
int fputcc(int ch);
/* USER CODE END Includes */
</code></pre>
        </li>
</ul>

<ul>
        <li>然后再board_discovery里面把board_init删掉,用不上这个,头文件还有一个RCC和USART的,这个也不要,因为hal有了</li>
        <li>然后第一个函数是一个延时的,因为hal自带,改成如下,记得在.h加声明
        <pre>
<code class="language-cpp">void mdelay(unsigned long nTime)
{
        HAL_Delay(nTime);
}

int get_tick_count(unsigned long *count)
{
      count = HAL_GetTick();
        return 0;
}

//h里面有
int get_tick_count(unsigned long *count);
void mdelay(unsigned long nTime);</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>然后在diver里面一个地方要改一下,在inv_mpu_dmp_motion_driver.c文件里面,有一个函数,里面的某个改成如下
        <pre>
<code class="language-cpp">int dmp_set_accel_bias(long *bias)
{
    long accel_bias_body;
    unsigned char regs;
    long long accel_sf;
    unsigned short accel_sens;

    mpu_get_accel_sens(&amp;accel_sens);
    accel_sf = (long long)accel_sens &lt;&lt; 15;
    __nop();//只改这一个,我是截取了一部分

//下面还有很多内容,就改上面的nop。</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>然后打开IIC,这里需要改一改,改成如下,这个函数来源于官方文件的emd6文件夹里面的,peripheral里的IIC有这个函数的说明。
        <pre>
<code class="language-cpp">/* USER CODE BEGIN 1 */
/**
* @briefWrites a Byte to a given register to the sensors through the
            control interface (I2C)
* @paramRegisterAddr: The address (location) of the register to be written.
* @paramRegisterValue: the Byte value to be written into destination register.
* @retval 0 if correct communication, else wrong communication
*/
int Sensors_I2C_WriteRegister(unsigned char slave_addr,
                                        unsigned char reg_addr,
                                        unsigned short len,
                                        const unsigned char *data_ptr)
{
        if(HAL_I2C_Mem_Write(&amp;hi2c1 ,slave_addr,reg_addr ,I2C_MEMADD_SIZE_8BIT, (uint8_t *)data_ptr,len,1000)!= HAL_OK)
        {
                return HAL_ERROR;
        }

        while(HAL_I2C_GetState(&amp;hi2c1) != HAL_I2C_STATE_READY)
        {
        }
       
        while(HAL_I2C_IsDeviceReady(&amp;hi2c1,slave_addr ,100 ,100) == HAL_TIMEOUT);
       
        while(HAL_I2C_GetState(&amp;hi2c1) != HAL_I2C_STATE_READY)
        {
        }
        return HAL_OK;
}

int Sensors_I2C_ReadRegister(unsigned char slave_addr,
                                       unsigned char reg_addr,
                                       unsigned short len,
                                       unsigned char *data_ptr)
{
                if(HAL_I2C_Mem_Read(&amp;hi2c1 ,slave_addr,reg_addr ,I2C_MEMADD_SIZE_8BIT, (uint8_t *)data_ptr,len,1000)!= HAL_OK)
        {
                return HAL_ERROR;
        }
        while(HAL_I2C_GetState(&amp;hi2c1) != HAL_I2C_STATE_READY)
        {
        }
        return HAL_OK;
}
/* USER CODE END 1 */</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>同时别忘了声明,在h里面添加
        <pre>
<code class="language-cpp">/* USER CODE BEGIN Prototypes */
int Sensors_I2C_ReadRegister(unsigned char Address, unsigned char RegisterAddr,
                                          unsigned short RegisterLen, unsigned char *RegisterValue);
int Sensors_I2C_WriteRegister(unsigned char Address, unsigned char RegisterAddr,
                                           unsigned short RegisterLen, const unsigned char *RegisterValue);

/* USER CODE END Prototypes */</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>最后把官方库的主函数搬到自己的代码里面,我这里放在了board_discovery.c里面,改一下名字,函数改成
        <pre>
<code class="language-cpp">void mpu_test</code></pre>
        </li>
        <li>
        <p>然后接下来是中断,在gpio.c里面加这个</p>

        <pre>
<code class="language-cpp">/* USER CODE BEGIN 2 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
        if(GPIO_Pin == GPIO_PIN_1)
        {
                gyro_data_ready_cb();
        }
}
/* USER CODE END 2 */

//注释:h里面别忘记包含discovery的h头文件</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>
        <p>然后discovery的h加个声明</p>

        <pre>
<code class="language-cpp">void mpu_test(void);
void gyro_data_ready_cb(void);</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>
        <p>然后剩下的就是对discovery的c文件进行修改了,里面会有一些多余的,我把完整的文件代码放在后面。</p>
        </li>
        <li>
        <p>差点忘了,还有一个是那个inv_mpu.c里面有一个地址,这边改成0XD0</p>

        <pre>
<code class="language-cpp">const struct hw_s hw = {
    .addr         = 0xD0,
    .max_fifo       = 1024,
    .num_reg      = 118,
    .temp_sens      = 340,
    .temp_offset    = -521,
    .bank_size      = 256
#if defined AK89xx_SECONDARY
    ,.compass_fsr    = AK89xx_FSR
#endif
};

//需要注意的是,里面有多个mpu系列,别搞错了,找到6050的改</code></pre>
        </li>
        <li>
        <p>然后移植就差不多了,下面就是显示部分。</p>
        </li>
</ul>

<p><strong><span style="font-size:20px;"><span style="color:#f1c40f;">二、显示+主函数</span></span></strong></p>

<p>1、主函数</p>

<pre>
<code>/* USER CODE BEGIN Includes */
#include "board-st_discovery.h"
#include "stdio.h"
#include "oled.h"
/* USER CODE END Includes */

void SystemClock_Config(void);


int main(void)
{
//初始化部分,包括IIC和SPI和串口和中断还有DMA
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_USART3_UART_Init();

/* USER CODE BEGIN 2 */
OLED_Init();//初始化
OLED_Clear();//清屏
OLED_Display_On();//打开显示
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
                printf("01\r\n");//调试的
                mpu_test();//计数函数
}
/* USER CODE END 3 */
}

/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
    Error_Handler();
}

/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = 0;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 40;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&amp;RCC_OscInitStruct) != HAL_OK)
{
    Error_Handler();
}

/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

if (HAL_RCC_ClockConfig(&amp;RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
    Error_Handler();
}
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
* @briefThis function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}

#ifdefUSE_FULL_ASSERT
/**
* @briefReports the name of the source file and the source line number
*         where the assert_param error has occurred.
* @paramfile: pointer to the source file name
* @paramline: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
   ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
</code></pre>

<p>2、discovery的变动部分</p>

<ul>
        <li>首先是计步器部分
        <pre>
<code class="language-cpp">static void read_from_mpl(void)
{
    long msg, data;
    int8_t accuracy;
    unsigned long timestamp;
    float float_data = {0};
                {
      unsigned long timestamp;
      get_tick_count(&amp;timestamp);
      if (timestamp &gt; hal.next_pedo_ms) {
            hal.next_pedo_ms = timestamp + PEDO_READ_MS;
            unsigned long step_count, walk_time;
            dmp_get_pedometer_step_count(&amp;step_count);
            dmp_get_pedometer_walk_time(&amp;walk_time);
            printf("Walked %ld steps over %ld milliseconds..\r\n", step_count,
            walk_time);//重点,计步器数据的打印
                        OLED_ShowNumber(59,0,step_count,2,16);//oled的显示计步器步数
      }
                }

}</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>然后是mpu函数的最后,加一个fresh,也就是oled的刷新
        <pre>
<code class="language-cpp">      if (new_data) {
            inv_execute_on_data();
            /* This function reads bias-compensated sensor data and sensor
             * fusion outputs from the MPL. The outputs are formatted as seen
             * in eMPL_outputs.c. This function only needs to be called at the
             * rate requested by the host.
             */
//                                                        MX_USART1_UART_Init();
//                                                        MX_USART2_UART_Init();
//                                                        MX_USART3_UART_Init();
                                                        //printf("01\r\n");
//上面不用管,调试的时候用的,原库没有刷新,这边加一个就行
            read_from_mpl();
                                        OLED_Refresh_Gram();</code></pre>
        </li>
        <li>
        <p>至此,移植完成,可以连接硬件和测试了,下面是discovery.c的代码</p>

        <pre>
<code class="language-cpp">#include "stm32l4xx.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "board-st_discovery.h"

#include "stdio.h"

#include "main.h"
   
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
#include "invensense.h"
#include "invensense_adv.h"
#include "eMPL_outputs.h"
#include "mltypes.h"
#include "mpu.h"
#include "log.h"
#include "packet.h"

#include "oled.h"
/* Private typedef -----------------------------------------------------------*/
/* Data read from MPL. */
#define PRINT_ACCEL   (0x01)
#define PRINT_GYRO      (0x02)
#define PRINT_QUAT      (0x04)
#define PRINT_COMPASS   (0x08)
#define PRINT_EULER   (0x10)
#define PRINT_ROT_MAT   (0x20)
#define PRINT_HEADING   (0x40)
#define PRINT_PEDO      (0x80)
#define PRINT_LINEAR_ACCEL (0x100)
#define PRINT_GRAVITY_VECTOR (0x200)

volatile uint32_t hal_timestamp = 0;
#define ACCEL_ON      (0x01)
#define GYRO_ON         (0x02)
#define COMPASS_ON      (0x04)

#define MOTION          (0)
#define NO_MOTION       (1)

/* Starting sampling rate. */
#define DEFAULT_MPU_HZ(20)

#define FLASH_SIZE      (512)
#define FLASH_MEM_START ((void*)0x1800)

#define PEDO_READ_MS    (1000)
#define TEMP_READ_MS    (500)
#define COMPASS_READ_MS (100)
struct rx_s {
    unsigned char header;
    unsigned char cmd;
};
struct hal_s {
    unsigned char lp_accel_mode;
    unsigned char sensors;
    unsigned char dmp_on;
    unsigned char wait_for_tap;
    volatile unsigned char new_gyro;
    unsigned char motion_int_mode;
    unsigned long no_dmp_hz;
    unsigned long next_pedo_ms;
    unsigned long next_temp_ms;
    unsigned long next_compass_ms;
    unsigned int report;
    unsigned short dmp_features;
    struct rx_s rx;
};
static struct hal_s hal = {0};

/* USB RX binary semaphore. Actually, it's just a flag. Not included in struct
* because it's declared extern elsewhere.
*/
volatile unsigned char rx_new;

unsigned char *mpl_key = (unsigned char*)"eMPL 5.1";

/* Platform-specific information. Kinda like a boardfile. */
struct platform_data_s {
    signed char orientation;
};

/* The sensors can be mounted onto the board in any orientation. The mounting
* matrix seen below tells the MPL how to rotate the raw data from the
* driver(s).
* TODO: The following matrices refer to the configuration on internal test
* boards at Invensense. If needed, please modify the matrices to match the
* chip-to-body matrix for your particular set up.
*/
static struct platform_data_s gyro_pdata = {
    .orientation = { 1, 0, 0,
                     0, 1, 0,
                     0, 0, 1}
};

#if defined MPU9150 || defined MPU9250
static struct platform_data_s compass_pdata = {
    .orientation = { 0, 1, 0,
                     1, 0, 0,
                     0, 0, -1}
};
#define COMPASS_ENABLED 1
#elif defined AK8975_SECONDARY
static struct platform_data_s compass_pdata = {
    .orientation = {-1, 0, 0,
                     0, 1, 0,
                     0, 0,-1}
};
#define COMPASS_ENABLED 1
#elif defined AK8963_SECONDARY
static struct platform_data_s compass_pdata = {
    .orientation = {-1, 0, 0,
                     0,-1, 0,
                     0, 0, 1}
};
#define COMPASS_ENABLED 1
#endif


/* Private define ------------------------------------------------------------*/

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* ---------------------------------------------------------------------------*/
/* Get data from MPL.
* TODO: Add return values to the inv_get_sensor_type_xxx APIs to differentiate
* between new and stale data.
*/
static void read_from_mpl(void)
{
    long msg, data;
    int8_t accuracy;
    unsigned long timestamp;
    float float_data = {0};
                {
      unsigned long timestamp;
      get_tick_count(&amp;timestamp);
      if (timestamp &gt; hal.next_pedo_ms) {
            hal.next_pedo_ms = timestamp + PEDO_READ_MS;
            unsigned long step_count, walk_time;
            dmp_get_pedometer_step_count(&amp;step_count);
            dmp_get_pedometer_walk_time(&amp;walk_time);
            printf("Walked %ld steps over %ld milliseconds..\r\n", step_count,
            walk_time);
                                                //printf("555");
                                                OLED_ShowNumber(59,0,step_count,2,16);
      }
                }

}

#ifdef COMPASS_ENABLED
void send_status_compass() {
        long data = { 0 };
        int8_t accuracy = { 0 };
        unsigned long timestamp;
        inv_get_compass_set(data, &amp;accuracy, (inv_time_t*) &amp;timestamp);
        MPL_LOGI("Compass: %7.4f %7.4f %7.4f ",
                        data/65536.f, data/65536.f, data/65536.f);
        MPL_LOGI("Accuracy= %d\r\n", accuracy);

}
#endif

/* Handle sensor on/off combinations. */
static void setup_gyro(void)
{
    unsigned char mask = 0, lp_accel_was_on = 0;
    if (hal.sensors &amp; ACCEL_ON)
      mask |= INV_XYZ_ACCEL;
    if (hal.sensors &amp; GYRO_ON) {
      mask |= INV_XYZ_GYRO;
      lp_accel_was_on |= hal.lp_accel_mode;
    }
#ifdef COMPASS_ENABLED
    if (hal.sensors &amp; COMPASS_ON) {
      mask |= INV_XYZ_COMPASS;
      lp_accel_was_on |= hal.lp_accel_mode;
    }
#endif
    /* If you need a power transition, this function should be called with a
   * mask of the sensors still enabled. The driver turns off any sensors
   * excluded from this mask.
   */
    mpu_set_sensors(mask);
    mpu_configure_fifo(mask);
    if (lp_accel_was_on) {
      unsigned short rate;
      hal.lp_accel_mode = 0;
      /* Switching out of LP accel, notify MPL of new accel sampling rate. */
      mpu_get_sample_rate(&amp;rate);
      inv_set_accel_sample_rate(1000000L / rate);
    }
}

static void tap_cb(unsigned char direction, unsigned char count)
{
    switch (direction) {
    case TAP_X_UP:
      MPL_LOGI("Tap X+ ");
      break;
    case TAP_X_DOWN:
      MPL_LOGI("Tap X- ");
      break;
    case TAP_Y_UP:
      MPL_LOGI("Tap Y+ ");
      break;
    case TAP_Y_DOWN:
      MPL_LOGI("Tap Y- ");
      break;
    case TAP_Z_UP:
      MPL_LOGI("Tap Z+ ");
      break;
    case TAP_Z_DOWN:
      MPL_LOGI("Tap Z- ");
      break;
    default:
      return;
    }
    MPL_LOGI("x%d\n", count);
    return;
}

static void android_orient_cb(unsigned char orientation)
{
        switch (orientation) {
        case ANDROID_ORIENT_PORTRAIT:
      MPL_LOGI("Portrait\n");
      break;
        case ANDROID_ORIENT_LANDSCAPE:
      MPL_LOGI("Landscape\n");
      break;
        case ANDROID_ORIENT_REVERSE_PORTRAIT:
      MPL_LOGI("Reverse Portrait\n");
      break;
        case ANDROID_ORIENT_REVERSE_LANDSCAPE:
      MPL_LOGI("Reverse Landscape\n");
      break;
        default:
                return;
        }
}


static inline void run_self_test(void)
{
    int result;
    long gyro, accel;

#if defined (MPU6500) || defined (MPU9250)
    result = mpu_run_6500_self_test(gyro, accel, 0);
#elif defined (MPU6050) || defined (MPU9150)
    result = mpu_run_self_test(gyro, accel);
#endif
    if (result == 0x7) {
        MPL_LOGI("Passed!\n");
      MPL_LOGI("accel: %7.4f %7.4f %7.4f\n",
                  accel/65536.f,
                  accel/65536.f,
                  accel/65536.f);
      MPL_LOGI("gyro: %7.4f %7.4f %7.4f\n",
                  gyro/65536.f,
                  gyro/65536.f,
                  gyro/65536.f);
      /* Test passed. We can trust the gyro data here, so now we need to update calibrated data*/

#ifdef USE_CAL_HW_REGISTERS
      /*
         * This portion of the code uses the HW offset registers that are in the MPUxxxx devices
         * instead of pushing the cal data to the MPL software library
         */
      unsigned char i = 0;

      for(i = 0; i&lt;3; i++) {
              gyro = (long)(gyro * 32.8f); //convert to +-1000dps
              accel *= 2048.f; //convert to +-16G
              accel = accel &gt;&gt; 16;
              gyro = (long)(gyro &gt;&gt; 16);
      }

      mpu_set_gyro_bias_reg(gyro);

#if defined (MPU6500) || defined (MPU9250)
      mpu_set_accel_bias_6500_reg(accel);
#elif defined (MPU6050) || defined (MPU9150)
      mpu_set_accel_bias_6050_reg(accel);
#endif
#else
      /* Push the calibrated data to the MPL library.
         *
         * MPL expects biases in hardware units &lt;&lt; 16, but self test returns
               * biases in g's &lt;&lt; 16.
               */
            unsigned short accel_sens;
            float gyro_sens;

                mpu_get_accel_sens(&amp;accel_sens);
                accel *= accel_sens;
                accel *= accel_sens;
                accel *= accel_sens;
                inv_set_accel_bias(accel, 3);
                mpu_get_gyro_sens(&amp;gyro_sens);
                gyro = (long) (gyro * gyro_sens);
                gyro = (long) (gyro * gyro_sens);
                gyro = (long) (gyro * gyro_sens);
                inv_set_gyro_bias(gyro, 3);
#endif
    }
    else {
            if (!(result &amp; 0x1))
                MPL_LOGE("Gyro failed.\n");
            if (!(result &amp; 0x2))
                MPL_LOGE("Accel failed.\n");
            if (!(result &amp; 0x4))
                MPL_LOGE("Compass failed.\n");
   }

}


/* Every time new gyro data is available, this function is called in an
* ISR context. In this example, it sets a flag protecting the FIFO read
* function.
*/
void gyro_data_ready_cb(void)
{
    hal.new_gyro = 1;
}
/*******************************************************************************/

/**
* @brief main entry point.
* @par Parameters None
* @retval void None
* @par Required preconditions: None
*/
                                 
void mpu_test(void)
{

inv_error_t result;
    unsigned char accel_fsr,new_temp = 0;
    unsigned short gyro_rate, gyro_fsr;
    unsigned long timestamp;
    struct int_param_s int_param;

#ifdef COMPASS_ENABLED
    unsigned char new_compass = 0;
    unsigned short compass_fsr;
#endif
//    board_init();

result = mpu_init(&amp;int_param);
if (result) {
      MPL_LOGE("Could not initialize gyro.\n");
}


    /* If you're not using an MPU9150 AND you're not using DMP features, this
   * function will place all slaves on the primary bus.
   * mpu_set_bypass(1);
   */

result = inv_init_mpl();
if (result) {
      MPL_LOGE("Could not initialize MPL.\n");
}

    /* Compute 6-axis and 9-axis quaternions. */
    inv_enable_quaternion();
    inv_enable_9x_sensor_fusion();
    /* The MPL expects compass data at a constant rate (matching the rate
   * passed to inv_set_compass_sample_rate). If this is an issue for your
   * application, call this function, and the MPL will depend on the
   * timestamps passed to inv_build_compass instead.
   *
   * inv_9x_fusion_use_timestamps(1);
   */

    /* This function has been deprecated.
   * inv_enable_no_gyro_fusion();
   */

    /* Update gyro biases when not in motion.
   * WARNING: These algorithms are mutually exclusive.
   */
    inv_enable_fast_nomot();
    /* inv_enable_motion_no_motion(); */
    /* inv_set_no_motion_time(1000); */

    /* Update gyro biases when temperature changes. */
    inv_enable_gyro_tc();

    /* This algorithm updates the accel biases when in motion. A more accurate
   * bias measurement can be made when running the self-test (see case 't' in
   * handle_input), but this algorithm can be enabled if the self-test can't
   * be executed in your application.
   *
   * inv_enable_in_use_auto_calibration();
   */
#ifdef COMPASS_ENABLED
    /* Compass calibration algorithms. */
    inv_enable_vector_compass_cal();
    inv_enable_magnetic_disturbance();
#endif
    /* If you need to estimate your heading before the compass is calibrated,
   * enable this algorithm. It becomes useless after a good figure-eight is
   * detected, so we'll just leave it out to save memory.
   * inv_enable_heading_from_gyro();
   */

    /* Allows use of the MPL APIs in read_from_mpl. */
    inv_enable_eMPL_outputs();

result = inv_start_mpl();
if (result == INV_ERROR_NOT_AUTHORIZED) {
      while (1) {
          MPL_LOGE("Not authorized.\n");
      }
}
if (result) {
      MPL_LOGE("Could not start the MPL.\n");
}

    /* Get/set hardware configuration. Start gyro. */
    /* Wake up all sensors. */
#ifdef COMPASS_ENABLED
    mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL | INV_XYZ_COMPASS);
#else
    mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL);
#endif
    /* Push both gyro and accel data into the FIFO. */
    mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL);
    mpu_set_sample_rate(DEFAULT_MPU_HZ);
#ifdef COMPASS_ENABLED
    /* The compass sampling rate can be less than the gyro/accel sampling rate.
   * Use this function for proper power management.
   */
    mpu_set_compass_sample_rate(1000 / COMPASS_READ_MS);
#endif
    /* Read back configuration in case it was set improperly. */
    mpu_get_sample_rate(&amp;gyro_rate);
    mpu_get_gyro_fsr(&amp;gyro_fsr);
    mpu_get_accel_fsr(&amp;accel_fsr);
#ifdef COMPASS_ENABLED
    mpu_get_compass_fsr(&amp;compass_fsr);
#endif
    /* Sync driver configuration with MPL. */
    /* Sample rate expected in microseconds. */
    inv_set_gyro_sample_rate(1000000L / gyro_rate);
    inv_set_accel_sample_rate(1000000L / gyro_rate);
#ifdef COMPASS_ENABLED
    /* The compass rate is independent of the gyro and accel rates. As long as
   * inv_set_compass_sample_rate is called with the correct value, the 9-axis
   * fusion algorithm's compass correction gain will work properly.
   */
    inv_set_compass_sample_rate(COMPASS_READ_MS * 1000L);
#endif
    /* Set chip-to-body orientation matrix.
   * Set hardware units to dps/g's/degrees scaling factor.
   */
    inv_set_gyro_orientation_and_scale(
            inv_orientation_matrix_to_scalar(gyro_pdata.orientation),
            (long)gyro_fsr&lt;&lt;15);
    inv_set_accel_orientation_and_scale(
            inv_orientation_matrix_to_scalar(gyro_pdata.orientation),
            (long)accel_fsr&lt;&lt;15);
#ifdef COMPASS_ENABLED
    inv_set_compass_orientation_and_scale(
            inv_orientation_matrix_to_scalar(compass_pdata.orientation),
            (long)compass_fsr&lt;&lt;15);
#endif
    /* Initialize HAL state variables. */
#ifdef COMPASS_ENABLED
    hal.sensors = ACCEL_ON | GYRO_ON | COMPASS_ON;
#else
    hal.sensors = ACCEL_ON | GYRO_ON;
#endif
    hal.dmp_on = 0;
    hal.report = 0;
    hal.rx.cmd = 0;
    hal.next_pedo_ms = 0;
    hal.next_compass_ms = 0;
    hal.next_temp_ms = 0;

/* Compass reads are handled by scheduler. */
get_tick_count(&amp;timestamp);

    /* To initialize the DMP:
   * 1. Call dmp_load_motion_driver_firmware(). This pushes the DMP image in
   *    inv_mpu_dmp_motion_driver.h into the MPU memory.
   * 2. Push the gyro and accel orientation matrix to the DMP.
   * 3. Register gesture callbacks. Don't worry, these callbacks won't be
   *    executed unless the corresponding feature is enabled.
   * 4. Call dmp_enable_feature(mask) to enable different features.
   * 5. Call dmp_set_fifo_rate(freq) to select a DMP output rate.
   * 6. Call any feature-specific control functions.
   *
   * To enable the DMP, just call mpu_set_dmp_state(1). This function can
   * be called repeatedly to enable and disable the DMP at runtime.
   *
   * The following is a short summary of the features supported in the DMP
   * image provided in inv_mpu_dmp_motion_driver.c:
   * DMP_FEATURE_LP_QUAT: Generate a gyro-only quaternion on the DMP at
   * 200Hz. Integrating the gyro data at higher rates reduces numerical
   * errors (compared to integration on the MCU at a lower sampling rate).
   * DMP_FEATURE_6X_LP_QUAT: Generate a gyro/accel quaternion on the DMP at
   * 200Hz. Cannot be used in combination with DMP_FEATURE_LP_QUAT.
   * DMP_FEATURE_TAP: Detect taps along the X, Y, and Z axes.
   * DMP_FEATURE_ANDROID_ORIENT: Google's screen rotation algorithm. Triggers
   * an event at the four orientations where the screen should rotate.
   * DMP_FEATURE_GYRO_CAL: Calibrates the gyro data after eight seconds of
   * no motion.
   * DMP_FEATURE_SEND_RAW_ACCEL: Add raw accelerometer data to the FIFO.
   * DMP_FEATURE_SEND_RAW_GYRO: Add raw gyro data to the FIFO.
   * DMP_FEATURE_SEND_CAL_GYRO: Add calibrated gyro data to the FIFO. Cannot
   * be used in combination with DMP_FEATURE_SEND_RAW_GYRO.
   */
    dmp_load_motion_driver_firmware();
    dmp_set_orientation(
      inv_orientation_matrix_to_scalar(gyro_pdata.orientation));
    dmp_register_tap_cb(tap_cb);
    dmp_register_android_orient_cb(android_orient_cb);
    /*
   * Known Bug -
   * DMP when enabled will sample sensor data at 200Hz and output to FIFO at the rate
   * specified in the dmp_set_fifo_rate API. The DMP will then sent an interrupt once
   * a sample has been put into the FIFO. Therefore if the dmp_set_fifo_rate is at 25Hz
   * there will be a 25Hz interrupt from the MPU device.
   *
   * There is a known issue in which if you do not enable DMP_FEATURE_TAP
   * then the interrupts will be at 200Hz even if fifo rate
   * is set at a different rate. To avoid this issue include the DMP_FEATURE_TAP
   *
   * DMP sensor fusion works only with gyro at +-2000dps and accel +-2G
   */
    hal.dmp_features = DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP |
      DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO |
      DMP_FEATURE_GYRO_CAL;
    dmp_enable_feature(hal.dmp_features);
    dmp_set_fifo_rate(DEFAULT_MPU_HZ);
    mpu_set_dmp_state(1);
    hal.dmp_on = 1;

while(1){

    unsigned long sensor_timestamp;
    int new_data = 0;
    get_tick_count(&amp;timestamp);

#ifdef COMPASS_ENABLED
      /* We're not using a data ready interrupt for the compass, so we'll
         * make our compass reads timer-based instead.
         */
      if ((timestamp &gt; hal.next_compass_ms) &amp;&amp; !hal.lp_accel_mode &amp;&amp;
            hal.new_gyro &amp;&amp; (hal.sensors &amp; COMPASS_ON)) {
            hal.next_compass_ms = timestamp + COMPASS_READ_MS;
            new_compass = 1;
      }
#endif
      /* Temperature data doesn't need to be read with every gyro sample.
         * Let's make them timer-based like the compass reads.
         */
      if (timestamp &gt; hal.next_temp_ms) {
            hal.next_temp_ms = timestamp + TEMP_READ_MS;
            new_temp = 1;
      }

    if (hal.motion_int_mode) {
      /* Enable motion interrupt. */
      mpu_lp_motion_interrupt(500, 1, 5);
      /* Notify the MPL that contiguity was broken. */
      inv_accel_was_turned_off();
      inv_gyro_was_turned_off();
      inv_compass_was_turned_off();
      inv_quaternion_sensor_was_turned_off();
      /* Wait for the MPU interrupt. */
      while (!hal.new_gyro) {}
      /* Restore the previous sensor configuration. */
      mpu_lp_motion_interrupt(0, 0, 0);
      hal.motion_int_mode = 0;
    }

    if (!hal.sensors || !hal.new_gyro) {
      continue;
    }   

      if (hal.new_gyro &amp;&amp; hal.lp_accel_mode) {
            short accel_short;
            long accel;
            mpu_get_accel_reg(accel_short, &amp;sensor_timestamp);
            accel = (long)accel_short;
            accel = (long)accel_short;
            accel = (long)accel_short;
            inv_build_accel(accel, 0, sensor_timestamp);
            new_data = 1;
            hal.new_gyro = 0;
      } else if (hal.new_gyro &amp;&amp; hal.dmp_on) {
            short gyro, accel_short, sensors;
            unsigned char more;
            long accel, quat, temperature;
            /* This function gets new data from the FIFO when the DMP is in
             * use. The FIFO can contain any combination of gyro, accel,
             * quaternion, and gesture data. The sensors parameter tells the
             * caller which data fields were actually populated with new data.
             * For example, if sensors == (INV_XYZ_GYRO | INV_WXYZ_QUAT), then
             * the FIFO isn't being filled with accel data.
             * The driver parses the gesture data to determine if a gesture
             * event has occurred; on an event, the application will be notified
             * via a callback (assuming that a callback function was properly
             * registered). The more parameter is non-zero if there are
             * leftover packets in the FIFO.
             */
            dmp_read_fifo(gyro, accel_short, quat, &amp;sensor_timestamp, &amp;sensors, &amp;more);
            if (!more)
                hal.new_gyro = 0;
            if (sensors &amp; INV_XYZ_GYRO) {
                /* Push the new data to the MPL. */
                inv_build_gyro(gyro, sensor_timestamp);
                new_data = 1;
                if (new_temp) {
                  new_temp = 0;
                  /* Temperature only used for gyro temp comp. */
                  mpu_get_temperature(&amp;temperature, &amp;sensor_timestamp);
                  inv_build_temp(temperature, sensor_timestamp);
                }
            }
            if (sensors &amp; INV_XYZ_ACCEL) {
                accel = (long)accel_short;
                accel = (long)accel_short;
                accel = (long)accel_short;
                inv_build_accel(accel, 0, sensor_timestamp);
                new_data = 1;
            }
            if (sensors &amp; INV_WXYZ_QUAT) {
                inv_build_quat(quat, 0, sensor_timestamp);
                new_data = 1;
            }
      } else if (hal.new_gyro) {
            short gyro, accel_short;
            unsigned char sensors, more;
            long accel, temperature;
            /* This function gets new data from the FIFO. The FIFO can contain
             * gyro, accel, both, or neither. The sensors parameter tells the
             * caller which data fields were actually populated with new data.
             * For example, if sensors == INV_XYZ_GYRO, then the FIFO isn't
             * being filled with accel data. The more parameter is non-zero if
             * there are leftover packets in the FIFO. The HAL can use this
             * information to increase the frequency at which this function is
             * called.
             */
            hal.new_gyro = 0;
            mpu_read_fifo(gyro, accel_short, &amp;sensor_timestamp,
                &amp;sensors, &amp;more);
            if (more)
                hal.new_gyro = 1;
            if (sensors &amp; INV_XYZ_GYRO) {
                /* Push the new data to the MPL. */
                inv_build_gyro(gyro, sensor_timestamp);
                new_data = 1;
                if (new_temp) {
                  new_temp = 0;
                  /* Temperature only used for gyro temp comp. */
                  mpu_get_temperature(&amp;temperature, &amp;sensor_timestamp);
                  inv_build_temp(temperature, sensor_timestamp);
                }
            }
            if (sensors &amp; INV_XYZ_ACCEL) {
                accel = (long)accel_short;
                accel = (long)accel_short;
                accel = (long)accel_short;
                inv_build_accel(accel, 0, sensor_timestamp);
                new_data = 1;
            }
      }
#ifdef COMPASS_ENABLED
      if (new_compass) {
            short compass_short;
            long compass;
            new_compass = 0;
            /* For any MPU device with an AKM on the auxiliary I2C bus, the raw
             * magnetometer registers are copied to special gyro registers.
             */
            if (!mpu_get_compass_reg(compass_short, &amp;sensor_timestamp)) {
                compass = (long)compass_short;
                compass = (long)compass_short;
                compass = (long)compass_short;
                /* NOTE: If using a third-party compass calibration library,
               * pass in the compass data in uT * 2^16 and set the second
               * parameter to INV_CALIBRATED | acc, where acc is the
               * accuracy from 0 to 3.
               */
                inv_build_compass(compass, 0, sensor_timestamp);
            }
            new_data = 1;
      }
#endif
      if (new_data) {
            inv_execute_on_data();
            /* This function reads bias-compensated sensor data and sensor
             * fusion outputs from the MPL. The outputs are formatted as seen
             * in eMPL_outputs.c. This function only needs to be called at the
             * rate requested by the host.
             */
//                                                        MX_USART1_UART_Init();
//                                                        MX_USART2_UART_Init();
//                                                        MX_USART3_UART_Init();
                                                        //printf("01\r\n");
            read_from_mpl();
                                        OLED_Refresh_Gram();
      }
    }
}



void mdelay(unsigned long nTime)
{
        HAL_Delay(nTime);
}

int get_tick_count(unsigned long *count)
{
      count = HAL_GetTick();
        return 0;
}</code></pre>

        <p>&nbsp;</p>
        </li>
</ul>

<p><strong><span style="color:#f1c40f;"><span style="font-size:20px;">三、视频演示</span></span></strong></p>

<p>1、基本视频演示(2min)</p>

<p>69cbe15d05a38e69a3a35d2726e8b37f<br />
&nbsp;</p>

<p>2、详细步数测试(30s)</p>

<p>a29398ada54085bebf8d26aef8dc58c9<br />
&nbsp;</p>

<p>3、在官方上的视频讲解</p>

<p>见链接:<a href="https://training.eeworld.com.cn/course/68166">【STM32L476RG测评】&mdash;&mdash;最终成品:基于MPU6050的计步器-EEWORLD大学堂</a></p>

<p><span style="font-size:20px;"><strong><span style="color:#f1c40f;">四、总结</span></strong></span></p>

<p>1、先说本次测评学到的东西,关键是MPU6050,说实在,计算什么的我也不是很熟悉,本次也只是一个简单的入门,而且时间和篇幅有限,我也不可能在报告里面把计算部分说完,我的唯一建议是去看官方给的那三个文档,看完后就大概知道他的官方库都是在干什么的了,虽然有不少hal和标准冲突的问题,不过最终还是成功移植了官方的库。</p>

<p>2、最终完成的作品相当的简陋,最大的问题有几个</p>

<ul>
        <li>板子本身尺寸就大</li>
        <li>各个模块通过杜邦线的连接非常麻烦,且容易出错</li>
        <li>供电电源没做好,本来打算加串口屏的,没有电源啥也不是</li>
        <li>个人感觉,说实在,如果想做一个完整的成品,我觉得应该是最小核心板+MPU6050+几个通信外设</li>
        <li>而且视频里因为杜邦线,演示都不好演示,传感器最好还是绑定在一个板子上面</li>
        <li>也就是说,其实应该画个扩展板,上面包含传感器+几个通信外设,然后这个板子直接连接上去就好了。</li>
</ul>

<p>3、开发过程中的遇到的问题及探索出的解决方案;</p>

<p>说实在,问题我没整合,都是那里要注意,就在那里写上了,本次DMP库其实还好,移植之后,报错基本上都是hal和官方库不兼容+一部分函数的冲突导致的,修修补补就好了。<br />
4、代码分析见文章主要内容,基本上都有,这些函数都不算难,其他的函数你也不用考虑,基本上都是官方算法<br />
5、测评总结和对厂商的建议等</p>

<ul>
        <li>首先是计步器的便携性,STM32L476RG是一款低功耗微控制器,适用于便携式设备。</li>
        <li>MPU6050传感器是用于测量加速度和角速度的,这些都是主要参数,同时官方库可以提供步数计数所需的算法和数据。</li>
        <li>HC-05蓝牙模块允许与其他设备通信,以实现数据传输或远程监控。</li>
        <li>使用USART屏幕和SPI OLED显示屏提供了用户友好的界面,可以显示步数和其他相关信息。</li>
        <li>然后我这一次没有加休眠,毕竟能源管理上,考虑低功耗设计是很重要的,最好还是确保在不使用时进入休眠模式,延长电池寿命。</li>
        <li>厂商建议,说实在stm32没什么好吐槽的,hal和标准库什么的各有优劣,唯一不满的就是板子太大</li>
        <li>但是因为上面的stlink,这个测评板,也能理解,真要做产品和实物还是得自己画pcb。</li>
</ul>

<p>最后谢谢各位的观看,希望本次测评的所有内容对大家有所帮助,感谢本次eeworld提供的板卡测评机会</p>

<p>PS:后面应该还有几天,看看有没有时间吧,我打算测测这个板卡的ADC和DAC,还有最近在做键盘,拿过这个板子跑过hid和4*4矩阵键盘测试,有时间的话再更新吧。</p>

<div></div>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

lugl4313820 发表于 2023-10-25 07:15

大佬很有经验呀,希望继续在论坛多分享好的资料、经验!
页: [1]
查看完整版本: 【STM32L476RG】第七节——基于官方DMP库的简易计步器