[APM32F4] APM32F411V Tiny Board测评】-6-基于压力播磨传感器和mpu6050的振动马达驱动

[复制链接]
 楼主| chenqiguang1998 发表于 2024-5-31 00:59 | 显示全部楼层 |阅读模式
本帖最后由 chenqiguang1998 于 2024-6-3 11:17 编辑


引脚配置,具体配置如下:
  • MPU6050:I2C1_SDA(PB7),I2C1_SCL(PB6)
  • 压力薄膜传感器:ADC1_IN1(PA1)
  • 振动马达:PWM1_CH1(PB4)
  • OLED:I2C2_SDA(PB3),I2C2_SCL(PB10)
  • 按键:KEY1(PA0),KEY2(PA1)

GPIO初始化

  1. void MX_GPIO_Init(void)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct = {0};

  4.     // Enable GPIO Clocks
  5.     __DAL_RCM_GPIOA_CLK_ENABLE();
  6.     __DAL_RCM_GPIOB_CLK_ENABLE();
  7.     __DAL_RCM_GPIOE_CLK_ENABLE();

  8.     // Configure GPIO pins for buttons (PA0, PA1)
  9.     GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
  10.     GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  11.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  12.     DAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  13.     // Configure GPIO pin for PWM (PB4)
  14.     GPIO_InitStruct.Pin = GPIO_PIN_4;
  15.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  16.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  17.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  18.     GPIO_InitStruct.Alternate = GPIO_AF1_TMR1;
  19.     DAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  20.     // EXTI interrupt init
  21.     DAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
  22.     DAL_NVIC_EnableIRQ(EXTI0_IRQn);

  23.     DAL_NVIC_SetPriority(EXTI1_IRQn, 2, 0);
  24.     DAL_NVIC_EnableIRQ(EXTI1_IRQn);
  25. }


I2C初始化部分

  1. I2C_HandleTypeDef hi2c1;
  2. I2C_HandleTypeDef hi2c2;

  3. void MX_I2C1_Init(void) {
  4.     hi2c1.Instance = I2C1;
  5.     hi2c1.Init.ClockSpeed = 100000;
  6.     hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  7.     hi2c1.Init.OwnAddress1 = 0;
  8.     hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  9.     hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  10.     hi2c1.Init.OwnAddress2 = 0;
  11.     hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  12.     hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  13.     DAL_I2C_Init(&hi2c1);
  14. }

  15. void MX_I2C2_Init(void) {
  16.     hi2c2.Instance = I2C2;
  17.     hi2c2.Init.ClockSpeed = 100000;
  18.     hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
  19.     hi2c2.Init.OwnAddress1 = 0;
  20.     hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  21.     hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  22.     hi2c2.Init.OwnAddress2 = 0;
  23.     hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  24.     hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  25.     DAL_I2C_Init(&hi2c2);
  26. }



ADC 初始化和功能设置


  1. ADC_HandleTypeDef hadc1;

  2. void MX_ADC1_Init(void) {
  3.     ADC_ChannelConfTypeDef sConfig = {0};

  4.     hadc1.Instance = ADC1;
  5.     hadc1.Init.ScanConvMode = DISABLE;
  6.     hadc1.Init.ContinuousConvMode = DISABLE;
  7.     hadc1.Init.DiscontinuousConvMode = DISABLE;
  8.     hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  9.     hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  10.     hadc1.Init.NbrOfConversion = 1;
  11.     DAL_ADC_Init(&hadc1);

  12.     sConfig.Channel = ADC_CHANNEL_1;
  13.     sConfig.Rank = 1U;
  14.     sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  15.     DAL_ADC_ConfigChannel(&hadc1, &sConfig);
  16. }

  17. u16 Get_Adc(ADC_HandleTypeDef *hadc1)
  18. {   
  19.     while(!__DAL_ADC_GET_FLAG(&hadc1, ADC_FLAG_EOC ));//等待转换结束
  20.     return DAL_ADC_GetValue(&hadc1); //返回最近一次ADC1规则组的转换结果
  21. }

  22. u16 Get_Adc_Average(ADC_HandleTypeDef *hadc1, u8 times) {
  23.     u32 temp_sum = 0;
  24.     u16 adc_value;
  25.     u8 t;
  26.     for(t = 0; t < times; t++) {
  27.         adc_value = Get_Adc(hadc1);
  28.         temp_sum += adc_value;
  29.         DAL_Delay(5);
  30.     }
  31.     return (times > 1) ? (u16)(temp_sum / times) : adc_value;
  32. }


PWM 初始化和功能设置


  1. TMR_HandleTypeDef hTMR1;

  2. void MX_TMR1_Init(void) {
  3.     TMR_OC_InitTypeDef sConfigOC = {0};

  4.     hTMR1.Instance = TMR1;
  5.     hTMR1.Init.Prescaler = 0;
  6.     hTMR1.Init.CounterMode = TMR_COUNTERMODE_UP;
  7.     hTMR1.Init.Period = 255;
  8.     hTMR1.Init.ClockDivision = TMR_CLOCKDIVISION_DIV1;
  9.     hTMR1.Init.RepetitionCounter = 0;
  10.     if (DAL_TMR_PWM_Init(&hTMR1) != DAL_OK) {
  11.         DAL_ErrorHandler();
  12.     }

  13.     sConfigOC.OCMode = TMR_OCMODE_PWM1;
  14.     sConfigOC.Pulse = 0;
  15.     sConfigOC.OCPolarity = TMR_OCPOLARITY_HIGH;
  16.     sConfigOC.OCFastMode = TMR_OCFAST_DISABLE;
  17.     if (DAL_TMR_PWM_ConfigChannel(&hTMR1, &sConfigOC, TMR_CHANNEL_1) != DAL_OK) {
  18.         DAL_ErrorHandler();
  19.     }

  20.     DAL_TMR_PWM_MspInit(&hTMR1);
  21. }

  22. // 设置 PWM 占空比和频率的函数
  23. void setPWM(uint32_t dutyCycle, uint32_t frequency) {
  24.     uint32_t period = DAL_RCC_GetHCLKFreq() / frequency;
  25.     __DAL_TMR_SET_AUTORELOAD(&hTMRx, period);
  26.     __DAL_TMR_SET_COMPARE(&hTMRx, TMR_CHANNEL_x, dutyCycle);
  27.     __DAL_TMR_ENABLE(&hTMRx);
  28. }

  29. // 5. UART Initialization


  30. UART_HandleTypeDef huart1;

  31. void MX_USART1_UART_Init(void) {
  32.     huart1.Instance = USART1;
  33.     huart1.Init.BaudRate = 115200;
  34.     huart1.Init.WordLength = UART_WORDLENGTH_8B;
  35.     huart1.Init.StopBits = UART_STOPBITS_1;
  36.     huart1.Init.Parity = UART_PARITY_NONE;
  37.     huart1.Init.Mode = UART_MODE_TX_RX;
  38.     huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  39.     huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  40.     DAL_UART_Init(&huart1);
  41. }


马达控制功能函数

  1. // PWM 参数设置函数
  2. void set_soft_mode_params(void) {
  3.     setPWM(50, 1000);  // 设置占空比为 50%,频率为 1000Hz
  4. }

  5. void set_normal_mode_params(void) {
  6.     setPWM(80, 1500);  // 设置占空比为 80%,频率为 1500Hz
  7. }

  8. void set_strong_mode_params(void) {
  9.     setPWM(100, 2000);  // 设置占空比为 100%,频率为 2000Hz
  10. }

  11. // 渐进模式启动函数
  12. void start_progressive_mode() {
  13.     uint32_t step = 10;  // 渐进模式的步长
  14.     uint32_t currentDutyCycle = 0;  // 渐进模式的当前占空比

  15.     while (currentDutyCycle <= 100) {  // 渐进模式持续到占空比达到 100%
  16.         setPWM(currentDutyCycle, 1000);  // 设置当前占空比和频率
  17.         currentDutyCycle += step;  // 增加占空比
  18.         DAL_Delay(100);
  19.     }
  20. }


传感器基于模式启动函数

  1. void start_sensor_based_mode() {
  2. process_sensor_data_and_set_motor_strength();
  3.         DAL_Delay(100);
  4.     }
  5. }

  6. void set_stop_mode_params(void) {
  7.     setPWM(100, 2000);  // 设置占空比为 100%,频率为 2000Hz
  8. }


Button 检测功能

按键有按键一,按键二,分别是pa0 pa1,按下按键1时候菜单选中项向上一个,按下按键2时候,菜单选中项向下一个,两个同时按下一下为选中,并启动相应功能,
短时间按下两次为返回上级菜单,如果没有上级菜单则停止功能,同时按下两秒,则停止并停留在当前选项。菜单分为功能菜单和测试菜单,
功能菜单分为轻柔、普通、强力三种模式来驱动振动马达,测试菜单分为强弱渐进和依靠MPU6050和薄膜压力传感器读数为权重来计算强度;
功能菜单设置振动马达的力度基数,测试菜单来确定变化逻辑

  1. // 全局变量声明
  2. volatile uint32_t button1_press_tick = 0;
  3. volatile uint32_t button2_press_tick = 0;
  4. volatile uint8_t button1_pressed = 0;
  5. volatile uint8_t button2_pressed = 0;
  6. volatile uint8_t button1_double_click = 0;
  7. volatile uint8_t button2_double_click = 0;
  8. volatile uint8_t buttons_held = 0;

  9. // 定义菜单选项
  10. typedef enum {
  11.     MENU_FUNCTION,
  12.     MENU_TEST,
  13.     MENU_FUNCTION_SOFT,
  14.     MENU_FUNCTION_NORMAL,
  15.     MENU_FUNCTION_STRONG,
  16.     MENU_TEST_PROGRESSIVE,
  17.     MENU_TEST_SENSOR_BASED
  18. } Menu;

  19. Menu current_menu = MENU_FUNCTION;
  20. Menu previous_menu = MENU_FUNCTION_SOFT;

  21. // GPIO中断回调函数
  22. void DAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  23.     uint32_t current_tick = DAL_GetTick();

  24.     if (GPIO_Pin == GPIO_PIN_0) {
  25.         if (current_tick - button1_press_tick < 250) {
  26.             button1_double_click = 1;
  27.         }
  28.         button1_press_tick = current_tick;
  29.         button1_pressed = 1;
  30.     } else if (GPIO_Pin == GPIO_PIN_1) {
  31.         if (current_tick - button2_press_tick < 250) {
  32.             button2_double_click = 1;
  33.         }
  34.         button2_press_tick = current_tick;
  35.         button2_pressed = 1;
  36.     }
  37. }

  38. // 检查按钮保持状态
  39. void check_button_hold(void) {
  40.     uint32_t current_tick = DAL_GetTick();

  41.     if ((DAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) &&
  42.         (DAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET)) {
  43.         if (current_tick - button1_press_tick >= 2000) {
  44.             buttons_held = 1;
  45.         }
  46.     } else {
  47.         button1_press_tick = current_tick;
  48.         button2_press_tick = current_tick;
  49.         buttons_held = 0;
  50.     }
  51. }

  52. // 处理按钮按下事件
  53. void handle_button_press(void) {
  54.     if (buttons_held) {
  55.         // 两个按键同时按下并保持两秒,则停止并停留在当前选项
  56.         set_stop_mode_params();// 实现停止功能的逻辑
  57.         buttons_held = 0;
  58.     } else if (button1_double_click || button2_double_click) {
  59.         // 短时间内双击按钮,返回上一级菜单或停止功能
  60.         if (current_menu == MENU_FUNCTION || current_menu == MENU_TEST) {
  61.             // 如果是主菜单,则停止功能
  62.             set_stop_mode_params();// 实现停止功能的逻辑
  63.         } else {
  64.             // 返回上一级菜单
  65.             current_menu = previous_menu;
  66.             display_menu(current_menu);
  67.         }
  68.         button1_double_click = 0;
  69.         button2_double_click = 0;
  70.     } else if (button1_pressed) {
  71.         // 按键1按下,菜单项向上移动
  72.         navigate_menu(-1);
  73.         button1_pressed = 0;
  74.     } else if (button2_pressed) {
  75.         // 按键2按下,菜单项向下移动
  76.         navigate_menu(1);
  77.         button2_pressed = 0;
  78.     } else if (button1_pressed && button2_pressed) {
  79.         // 两个按键同时按下,选中当前菜单项并启动相应功能
  80.         execute_current_menu_function();
  81.         button1_pressed = 0;
  82.         button2_pressed = 0;
  83.     }
  84. }
  85. // 菜单导航函数
  86. void navigate_menu(int direction) {
  87.     previous_menu = current_menu;

  88.     switch (current_menu) {
  89.         case MENU_FUNCTION:
  90.             current_menu = (direction > 0) ? MENU_TEST : MENU_TEST;
  91.             break;
  92.         case MENU_TEST:
  93.             current_menu = (direction > 0) ? MENU_FUNCTION_SOFT : MENU_FUNCTION;
  94.             break;
  95.         case MENU_FUNCTION_SOFT:
  96.             current_menu = (direction > 0) ? MENU_FUNCTION_NORMAL : MENU_FUNCTION_STRONG;
  97.             break;
  98.         case MENU_FUNCTION_NORMAL:
  99.             current_menu = (direction > 0) ? MENU_FUNCTION_STRONG : MENU_FUNCTION_SOFT;
  100.             break;
  101.         case MENU_FUNCTION_STRONG:
  102.             current_menu = (direction > 0) ? MENU_TEST_PROGRESSIVE : MENU_FUNCTION_NORMAL;
  103.             break;
  104.         case MENU_TEST_PROGRESSIVE:
  105.             current_menu = (direction > 0) ? MENU_TEST_SENSOR_BASED : MENU_FUNCTION_STRONG;
  106.             break;
  107.         case MENU_TEST_SENSOR_BASED:
  108.             current_menu = (direction > 0) ? MENU_FUNCTION : MENU_TEST_PROGRESSIVE;
  109.             break;
  110.     }
  111.     display_menu(current_menu);
  112. }

  113. // 执行当前菜单功能
  114. void execute_current_menu_function(void) {
  115.     switch (current_menu) {
  116.         case MENU_FUNCTION_SOFT:
  117.             set_soft_mode_params();
  118.             break;
  119.         case MENU_FUNCTION_NORMAL:
  120.             set_normal_mode_params();
  121.             break;
  122.         case MENU_FUNCTION_STRONG:
  123.             set_strong_mode_params();
  124.             break;
  125.         case MENU_TEST_PROGRESSIVE:
  126.             start_progressive_mode();
  127.             break;
  128.         case MENU_TEST_SENSOR_BASED:
  129.             start_sensor_based_mode();
  130.             break;
  131.         default:
  132.             break;
  133.     }
  134. }


OLED 显示功能

  1. typedef enum {
  2.     MENU_FUNCTION,
  3.     MENU_TEST,
  4.     MENU_FUNCTION_SOFT,
  5.     MENU_FUNCTION_NORMAL,
  6.     MENU_FUNCTION_STRONG,
  7.     MENU_TEST_PROGRESSIVE,
  8.     MENU_TEST_SENSOR_BASED
  9. } Menu;

  10. Menu current_menu = MENU_FUNCTION;
  11. Menu previous_menu = MENU_FUNCTION_SOFT;

  12. void display_menu(Menu menu) {
  13.     ssd1306_Fill(SSD1306_COLOR_BLACK);
  14.     ssd1306_SetCursor(0, 0);
  15.     ssd1306_WriteString("Menu:", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  16.     ssd1306_SetCursor(0, 16);

  17.     switch (menu) {
  18.         case MENU_FUNCTION:
  19.             ssd1306_WriteString("Function Menu", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  20.             break;
  21.         case MENU_TEST:
  22.             ssd1306_WriteString("Test Menu", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  23.             break;
  24.         case MENU_FUNCTION_SOFT:
  25.             ssd1306_WriteString("Soft Function", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  26.             break;
  27.         case MENU_FUNCTION_NORMAL:
  28.             ssd1306_WriteString("Normal Function", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  29.             break;
  30.         case MENU_FUNCTION_STRONG:
  31.             ssd1306_WriteString("Strong Function", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  32.             break;
  33.         case MENU_TEST_PROGRESSIVE:
  34.             ssd1306_WriteString("Progressive Test", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  35.             break;
  36.         case MENU_TEST_SENSOR_BASED:
  37.             ssd1306_WriteString("Sensor-Based Test", SSD1306_FONT_6X8, SSD1306_COLOR_WHITE);
  38.             break;
  39.     }
  40.     ssd1306_UpdateScreen();
  41. }

  42. void handle_buttons(void) {
  43.     if (button1_pressed) {
  44.         button1_pressed = 0;
  45.         if (buttons_held) {
  46.             current_menu = MENU_FUNCTION;
  47.         } else {
  48.             switch (current_menu) {
  49.                 case MENU_FUNCTION:
  50.                     current_menu = previous_menu;
  51.                     break;
  52.                 case MENU_TEST:
  53.                     current_menu = previous_menu;
  54.                     break;
  55.                 default:
  56.                     previous_menu = current_menu;
  57.                     current_menu = MENU_FUNCTION;
  58.                     break;
  59.             }
  60.         }
  61.     }

  62.     if (button2_pressed) {
  63.         button2_pressed = 0;
  64.         switch (current_menu) {
  65.             case MENU_FUNCTION:
  66.                 current_menu = MENU_TEST;
  67.                 break;
  68.             case MENU_TEST:
  69.                 current_menu = MENU_FUNCTION_SOFT;
  70.                 break;
  71.             case MENU_FUNCTION_SOFT:
  72.                 current_menu = MENU_FUNCTION_NORMAL;
  73.                 break;
  74.             case MENU_FUNCTION_NORMAL:
  75.                 current_menu = MENU_FUNCTION_STRONG;
  76.                 break;
  77.             case MENU_FUNCTION_STRONG:
  78.                 current_menu = MENU_TEST_PROGRESSIVE;
  79.                 break;
  80.             case MENU_TEST_PROGRESSIVE:
  81.                 current_menu = MENU_TEST_SENSOR_BASED;
  82.                 break;
  83.             case MENU_TEST_SENSOR_BASED:
  84.                 current_menu = MENU_FUNCTION;
  85.                 break;
  86.         }
  87.     }
  88.     display_menu(current_menu);
  89. }

mpu6050陀螺仪初始化和数据读取功能

  1. #include <math.h>

  2. <div style="color: #cccccc;background-color: #1f1f1f;font-family: 'Droid Sans Mono', 'monospace', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_ADDR</span><span style="color: #569cd6;">    </span><span style="color: #b5cea8;">0x68</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_REG_PWR_MGMT_1</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x6B</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_REG_SMPLRT_DIV</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x19</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_REG_CONFIG</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x1A</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_REG_GYRO_CONFIG</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x1B</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_REG_ACCEL_CONFIG</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x1</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_REG_ACCEL_XOUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x3B</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_ACCEL_XOUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x3</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_ACCEL_YOUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x3D</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_ACCEL_YOUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x3E</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_ACCEL_ZOUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x3F</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_ACCEL_ZOUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x40</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_TEMP_OUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x41</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_TEMP_OUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x42</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_GYRO_XOUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x43</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_GYRO_XOUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x44</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_GYRO_YOUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x45</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_GYRO_YOUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x46</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_GYRO_ZOUT_H</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x47</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_GYRO_ZOUT_L</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x48</span></div><div><span style="color: #c586c0;">#define</span><span style="color: #569cd6;"> </span><span style="color: #569cd6;">MPU6050_WHO_AM_I</span><span style="color: #569cd6;"> </span><span style="color: #b5cea8;">0x75</span></div></div>

  3. void MPU6050_Init(void) {
  4.     uint8_t check;
  5.     uint8_t Data;

  6.     // 检查设备ID
  7.     DAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x75, 1, &check, 1, 1000);
  8.     if (check == 0x68) {
  9.         // 唤醒MPU6050
  10.         Data = 0;
  11.         DAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_REG_PWR_MGMT_1, 1, &Data, 1, 1000);

  12.         // 设置采样率
  13.         Data = 7;
  14.         DAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_REG_SMPLRT_DIV, 1, &Data, 1, 1000);

  15.         // 设置加速度计和陀螺仪
  16.         Data = 0;
  17.         DAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_REG_ACCEL_CONFIG, 1, &Data, 1, 1000);
  18.         DAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_REG_GYRO_CONFIG, 1, &Data, 1, 1000);
  19.     }
  20. }

  21. void MPU6050_Read_Accel(float* Ax, float* Ay, float* Az) {
  22.     uint8_t Rec_Data[6];
  23.     int16_t Accel_X_RAW, Accel_Y_RAW, Accel_Z_RAW;

  24.     // 读取加速度计数据
  25.     DAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, MPU6050_REG_ACCEL_XOUT_H, 1, Rec_Data, 6, 1000);

  26.     Accel_X_RAW = (int16_t)(Rec_Data[0] << 8 | Rec_Data[1]);
  27.     Accel_Y_RAW = (int16_t)(Rec_Data[2] << 8 | Rec_Data[3]);
  28.     Accel_Z_RAW = (int16_t)(Rec_Data[4] << 8 | Rec_Data[5]);

  29.     *Ax = Accel_X_RAW / 16384.0;
  30.     *Ay = Accel_Y_RAW / 16384.0;
  31.     *Az = Accel_Z_RAW / 16384.0;
  32. }

  33. void MPU6050_Read_Gyro(float* Gx, float* Gy, float* Gz) {
  34.     uint8_t Rec_Data[6];
  35.     int16_t Gyro_X_RAW, Gyro_Y_RAW, Gyro_Z_RAW;

  36.     // 读取陀螺仪数据
  37.     DAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, MPU6050_REG_GYRO_XOUT_H, 1, Rec_Data, 6, 1000);

  38.     Gyro_X_RAW = (int16_t)(Rec_Data[0] << 8 | Rec_Data[1]);
  39.     Gyro_Y_RAW = (int16_t)(Rec_Data[2] << 8 | Rec_Data[3]);
  40.     Gyro_Z_RAW = (int16_t)(Rec_Data[4] << 8 | Rec_Data[5]);

  41.     *Gx = Gyro_X_RAW / 131.0;
  42.     *Gy = Gyro_Y_RAW / 131.0;
  43.     *Gz = Gyro_Z_RAW / 131.0;
  44. }

  45. // 计算倾角
  46. void calculate_tilt(float Ax, float Ay, float Az, float* roll, float* pitch) {
  47.     *roll = atan2(Ay, Az) * 180.0 / M_PI;
  48.     *pitch = atan2(-Ax, sqrt(Ay * Ay + Az * Az)) * 180.0 / M_PI;
  49. }



压力薄膜传感器初始化和读数函数



  1. #define PRESS_MIN    20
  2. #define PRESS_MAX    6000
  3. #define VOLTAGE_MIN  150
  4. #define VOLTAGE_MAX  3300

  5. uint8_t state = 0;
  6. uint16_t val = 0;
  7. uint16_t value_AD = 0;

  8. long PRESS_AO = 0;
  9. int VOLTAGE_AO = 0;

  10. long map(long x, long in_min, long in_max, long out_min, long out_max) {
  11.     return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
  12. }

  13. float readPressureSensor() {
  14.     uint16_t value_AD = 0;
  15.     uint16_t voltage_AO = 0;
  16.    float pressure_AO = 0;

  17.     value_AD = Get_Adc_Average(1, 10);  // 10次平均值
  18.     voltage_AO = map(value_AD, 0, 4095, 0, 3300);

  19.     if (voltage_AO < VOLTAGE_MIN) {
  20.         pressure_AO = 0;
  21.     } else if (voltage_AO > VOLTAGE_MAX) {
  22.         pressure_AO = PRESS_MAX;
  23.     } else {
  24.         pressure_AO = map(voltage_AO, VOLTAGE_MIN, VOLTAGE_MAX, PRESS_MIN, PRESS_MAX);
  25.     }

  26.     return pressure_AO;
  27. }


权重公式部分


通过压力的传感器和陀螺仪来自动选择振动的模式,
在原有的强力、普通、轻柔的基础上进行加强或者减弱,
比如压力传感器感受的压力增大就增加强度,直到到达上限制,比如陀螺仪感受到的角度变
化判断在脸部薄弱位置就减轻强度,陀螺仪的优先级高于压力传感器,如果在薄弱位置,
强度上限会减小,并且适当减小当前强度,如果在下颌、额头等位置就会适当增加当前强度,
通过一个权重公式,通过接收到的mpu6050的数据和压力传感器数据,并来确定当下的强度


//     压力传感器权重:PP
//     角度权重:AA
//     总强度权重:WW
//     振动强度:SS

// 权重公式逻辑

//     设定陀螺仪感应到的面部区域:例如,面部薄弱区域(脸颊)与强度较高区域(下颌、额头)。
//     陀螺仪感应到的角度范围与区域映射:
//         面部薄弱区域:角度范围A1 (比如 -45° < 角度 < 45°)
//         强度较高区域:角度范围A2 (比如 角度 < -45° 或者 角度 > 45°)
//     根据压力传感器和陀螺仪的权重公式调整振动强度:
//         W=P×KP+A×KAW=P×KP​+A×KA​
//         如果在面部薄弱区域,则总强度权重 WW 减小。
//         如果在强度较高区域,则总强度权重 WW 增加。


  1. #define KP 0.5   // 压力传感器权重
  2. #define KA 0.5   // 角度权重
  3. #define BASE_INTENSITY 50
  4. #define MAX_INTENSITY 255

  5. float calculate_vibration_intensity(float angle, float pressure) {
  6.     float intensity;

  7.     if (-45 <= angle && angle <= 45) {
  8.         // 面部薄弱区域,减少强度
  9.         intensity = (pressure * KP + angle * KA) * 0.5; // 减少强度权重
  10.     } else {
  11.         // 强度较高区域,增加强度
  12.         intensity = (pressure * KP + angle * KA) * 1.5; // 增加强度权重
  13.     }

  14.     // 确保强度在范围内
  15.     if (intensity > MAX_INTENSITY) {
  16.         intensity = MAX_INTENSITY;
  17.     } else if (intensity < BASE_INTENSITY) {
  18.         intensity = BASE_INTENSITY;
  19.     }
  20.     return intensity;
  21. }

  22. void complementary_filter(float* roll, float* pitch, float gx, float gy, float gz, float dt) {
  23.     static float roll_acc = 0, pitch_acc = 0;

  24.     float roll_gyro = *roll + gx * dt;
  25.     float pitch_gyro = *pitch + gy * dt;

  26.     float Ax, Ay, Az;
  27.     MPU6050_Read_Accel(&Ax, &Ay, &Az);
  28.     calculate_tilt(Ax, Ay, Az, &roll_acc, &pitch_acc);

  29.     *roll = ALPHA * roll_gyro + (1.0 - ALPHA) * roll_acc;
  30.     *pitch = ALPHA * pitch_gyro + (1.0 - ALPHA) * pitch_acc;
  31. }


  32. // 处理接收到的数据并确定强度的函数
  33. void process_sensor_data_and_set_motor_strength(void) {
  34.     // 读取加速度和陀螺仪数据
  35.     float Ax, Ay, Az;
  36.     float Gx, Gy, Gz;
  37.     MPU6050_Read_Accel(&Ax, &Ay, &Az);
  38.     MPU6050_Read_Gyro(&Gx, &Gy, &Gz);

  39.     // 计算倾角
  40.     float roll, pitch;
  41.     calculate_tilt(Ax, Ay, Az, &roll, &pitch);

  42.     // 使用互补滤波器融合数据
  43.     float dt = 0.01;  // 假设每10ms调用一次
  44.     complementary_filter(&roll, &pitch, Gx, Gy, Gz, dt);

  45.     // 读取压力传感器数据
  46.     float tmp_pressure = readPressureSensor();

  47.     // 计算振动马达强度
  48.     float intensity = calculate_vibration_intensity(roll,tmp_pressure);

  49.     // 设置振动马达的PWM占空比
  50.     set_motor_strength((uint8_t)intensity);
  51. }



// Main Function

  1. int main(void) {
  2.     DAL_Init();
  3.     SystemClock_Config();
  4.     MX_GPIO_Init();
  5.     MX_I2C1_Init();
  6.     MX_I2C2_Init();
  7.     MX_ADC1_Init();
  8.     MX_TMR1_Init();
  9.     MX_TMR2_Init();
  10.     MX_USART1_UART_Init();
  11.     ssd1306_Init();
  12.    
  13.     // 显示初始模式
  14.     display_mode(current_mode);

  15.     // 启动PWM
  16.     DAL_TMR_PWM_Start(&hTMR1, TMR_CHANNEL_1);

  17.     // 主循环
  18.     while (1) {
  19.         // 处理按键
  20.         handle_button_press();
  21.         
  22.     }
  23. }
定时器配置


  1. TMR_HandleTypeDef htmr2;

  2. void MX_TMR2_Init(void) {
  3.     htmr2.Instance = TMR2;
  4.     htmr2.Init.Prescaler = 8399;
  5.     htmr2.Init.CounterMode = TMR_COUNTERMODE_UP;
  6.     htmr2.Init.Period = 9999;
  7.     htmr2.Init.ClockDivision = TMR_CLOCKDIVISION_DIV1;
  8.     htmr2.Init.AutoReloadPreload = TMR_AUTORELOAD_PRELOAD_DISABLE;
  9.     DAL_TMR_Base_Init(&htmr2);

  10.     DAL_TMR_Base_Start_IT(&htmr2);

  11.     DAL_NVIC_SetPriority(TMR2_IRQn, 0, 0);
  12.     DAL_NVIC_EnableIRQ(TMR2_IRQn);
  13. }
  14. // 定时器中断服务函数
  15. float roll, pitch, gx, gy, gz, dt = 0.01;
  16. void TMR2_IRQHandler(void) {
  17.     if (__DAL_TMR_GET_FLAG(&htmr2, TMR_FLAG_UPDATE) != RESET) {
  18.         if (__DAL_TMR_GET_IT_SOURCE(&htmr2, TMR_IT_UPDATE) != RESET) {
  19.             __DAL_TMR_CLEAR_IT(&htmr2, TMR_IT_UPDATE);
  20. process_sensor_data_and_set_motor_strength();
  21.         }
  22.     }
  23. }



星辰大海不退缩 发表于 2024-6-22 21:10 | 显示全部楼层
按键IO检测是依靠中断嘛?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

10

主题

59

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部