stm32四种方式精密控制步进电机

在搭建完clion的开发环境后,我决定重写之前的项目并优化完善,争取做出完全可落地的东西,也结合要写的论文内容一同学习下去。

因此,首当其冲的就是回到步进电机控制领域,把之前使用中断溢出进行步进电机控制的方案进行进一步优化。

目前使用中断溢出控制步进电机,有如下几个问题:

1.有时电压不稳定/电压太低,电机转不起来。

2.电机频率太高有响声,频率太低有气声,需要设置一个合理的范围值,同时不会因为数值的事情用很长时间去调试电机。

3.电机转向会出问题,或是反应不过来,或是接线有问题。

4.只能通过改变psc预分频器和arr自动重装载值去改变电机速度,无法通过改变占空比的方式控制电机,且驱动器上合适的细分数需要自己调节。

因此我这次使用了四种方式控制电机

1.模拟io控制

2.中断溢出控制

3.定时器比较通道控制

4.定时器pwm控制

芯片选用的是stm32f103zet6,全程未使用编码器/PID,电机驱动器用的是最简单常见的TB6612,主打一个好复现且好上手。电机控制比较稳定,可以根据自己需要选择控制方式。本篇文章使用的硬件和这两篇文章里介绍的一致:

stm32精密控制步进电机(基础篇)_stm32 步进电机-CSDN博客

stm32精密控制步进电机(升级篇)_stm32微机控制-CSDN博客

与之前不同的是我把使能引脚也加上了(PA7控制ENA-;ENA+,PUL+和DIR+接到5v或3.3v引脚),这里可以用万用表验证一下。

四种方式的详细代码我放在了github上,都使用了freertos系统,欢迎移步下载,可以试着点个star,谢谢: 

https://github.com/Re-restart/four_ways_to_control_stepper


怎么完成电机接线

Aout1与Aout2(A+与A-)是同相,Bout1与Bout2(B+与B-)是同相。把同相的两条线拧在一起,会发现难以拧动电机;或是直接测试电阻,两个引脚之间电阻极大,那这两个电机引脚就是同相的。

如果驱动器是9v的,建议接12v电源+DCDC,这样电流会更加稳定,电机抖动现象就可以减轻很多。如果外部不是稳定的开关电源,只靠板子和12v电池供电,最好不要设置板子的脉冲和方向引脚为推挽上拉,设置推挽会抢占一些电压,可能给驱动器分配的电压就变少了。

进行电机微秒级延时

众所周知,HAL_Delay和Osdelay都是毫秒延时。这种情况下需要一个定时器去负责微秒的延时,我使用的方式是把TIM4里的预分频器psc设为71,定时器时钟源频率是72MHZ,定时器输入时钟频率就是:

72MHZ/(71+1)=1MHZ

把arr值设置为65535,这样读取出来的寄存器值就是1微秒增加一次,最后比较出的值小于要执行的微秒数就可以。

void delay_us(uint16_t us)
{
    __HAL_TIM_SET_COUNTER(&htim4, 0);  // 清零了 TIM4 的计数器,因此不再需要 start_time 变量来记录初始时间
    while (__HAL_TIM_GET_COUNTER(&htim4) < us);  // 等待计数达到 us
}

另外还有一种做法是设置一个volatile变量(代表这个变量不要被编译器优化,可能在外设或中断中改变它的值,每次访问该变量时需要从内存中读取),start_time是用来读取中断的初始值的,和清零函数__HAL_TIM_SET_COUNTER(&htim4, 0)的作用一致。

为了处理溢出问题,可以利用无符号整数的特性,即(uint16_t)(delay_time-start_time)。

volatile uint16_t start_time=0;
volatile uint16_t delay_time=0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM4) {
    delay_time++;
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

void delay_us(uint16_t us)
{
    start_time=delay_time;
    while((uint16_t)(delay_time-start_time)<us);
}

按键进行外部中断控制

先新建系统任务控制函数,利用两个板子自带的按钮做外部中断,控制电机左转/右转。这个按键逻辑是默认flag=1,flag_key=1。按下按键后,防抖10s,并把flag和flag_key全置为0。

按键松开后,按键引脚恢复到之前的电平,因为也不会同时摁,就用flag,flag_key和按键引脚松开后的状态共同判断,区分电机状态并执行stepper_turn函数。

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */

   for(;;) {
     if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
       osDelay(10);
       flag = 0;
     }

     if(flag == 0 && flag_key == 1) {
       if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
         stepper_turn(120,360,32,CW);
       }
       flag = 1;
     }
/
     if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == GPIO_PIN_RESET) {
       osDelay(10);
       flag_key = 0;
     }

     if(flag_key == 0 && flag == 1) {
       if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == GPIO_PIN_SET) {
         stepper_turn(120,360,32,CCW);
       }
     }
     flag_key = 1;
   }

电机引脚初始设置

中断和模拟io控制电机都是PA5为PUL-控制引脚,PA6为DIR-控制引脚,PA7为ENA-控制引脚。输入捕获和pwm用的都是TIM3-Channel1,也就是PC6。不是不能设置推挽引脚哈,这个主要看外界的电压状态,如果外界供电稳定,引脚电压也够,那全设置推挽上拉也没什么问题。但是我需要大多数情况下都没问题的引脚配置,所以会设置开漏不上拉,只有PA7是推挽上拉。

  /*Configure GPIO pins : PA5 PA6 */
  GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : PA7 */
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5|GPIO_PIN_6, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);


//

void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(timHandle->Instance==TIM3)
  {
  /* USER CODE BEGIN TIM3_MspPostInit 0 */

  /* USER CODE END TIM3_MspPostInit 0 */

    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**TIM3 GPIO Configuration
    PC6     ------> TIM3_CH1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    __HAL_AFIO_REMAP_TIM3_ENABLE();

  /* USER CODE BEGIN TIM3_MspPostInit 1 */

  /* USER CODE END TIM3_MspPostInit 1 */
  }

}

模拟IO控制步进电机

通过编写stepper_turn函数控制模拟IO。这里面注意一下,ENA-引脚是推挽上拉状态,初始为SET,此时ENA-引脚电平为0v。

已知ENA+引脚电平是3.3v或5v,构成使能信号有效,电机pulse引脚开始翻转,电机转动。电机停止转动时,使能引脚恢复到reset状态。

电机转动的时候,使能引脚PA7其实是有电压的,2v~3v的样子。可以看作这个引脚是来激活驱动器的。如果讨厌这个使能引脚/板子引脚资源比较少,可以不接使能+和使能-引脚,PUL+和DIR+接到5v或3.3v引脚,PUL-和DIR-用于控制电机即可。

tim是周期,angle是角度,subdivide是电机驱动器的细分数,dir是电机旋转方向。

void stepper_turn(int tim,float angle,float subdivide,uint8_t dir)
{
  int n,i;
  /*根据细分数求得步距角被分成多少个方波*/
  n=(int)(angle/(1.8/subdivide));
  if(dir==CW)        //顺时针
  {
    HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,HIGH);
  }
  else if(dir==CCW)//逆时针
  {
    HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,LOW);
  }
  /*开使能*/
  HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_SET);
  /*模拟方波*/
  for(i=0;i<n;i++)
  {
    HAL_GPIO_WritePin(MOTOR_PUL_GPIO_PORT,MOTOR_PUL_PIN,LOW);
    delay_us(tim/2);
    HAL_GPIO_WritePin(MOTOR_PUL_GPIO_PORT,MOTOR_PUL_PIN,HIGH);
    delay_us(tim/2);
  }
  /*关使能*/
  HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_RESET);
}

中断溢出控制步进电机

和之前文章里的类似,这里不再详细讲解。逻辑稍微改良了一下,不再通过读取引脚状态计数,而是每溢出两次,脉冲引脚翻转一次。这是因为中断需要保持实时性,需要尽量处理简单的逻辑。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM2) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
  if(htim->Instance==TIM5)
  {
    pulse=pulse+1;
    if(pulse%2==0){
      Pulse_Toggle();
    }
    __HAL_TIM_CLEAR_IT(&htim5, TIM_IT_UPDATE);
  }

  /* USER CODE END Callback 1 */
}

 中断跟其他方式相比,比较容易出现失控,所以启动之前需要先关闭中断,停止电机运行,然后再打开电机驱动和使能。

for(;;) {
     if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
       osDelay(10);
       flag = 0;
     }

     if(flag == 0 && flag_key == 1) {
       if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) {
         StopMotor();
         Dir_CW();
         StartMotor();
         HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_SET);
       }
       flag = 1;
     }

     if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == GPIO_PIN_RESET) {
       osDelay(10);
       flag_key = 0;
     }

     if(flag_key == 0 && flag == 1) {
       if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == GPIO_PIN_SET) {
         StopMotor();
         Dir_CC();
         StartMotor();
         HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_SET);//打开使能
       }
       flag_key = 1;
     }

     if(pulse > 3200) {
       pulse = 0;
       StopMotor();
       BlinkLEDs();
     }
  }

然后我把比较重要的操作都封装成函数,这样便于调用。HAL_TIM_Base_MspDeInit用于关闭定时器中断,HAL_TIM_Base_MspInit打开定时器中断。

void StopMotor(void) {
  HAL_TIM_Base_MspDeInit(&htim5);
  HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_RESET);
}

void StartMotor(void) {
  HAL_TIM_Base_MspInit(&htim5);
}

定时器比较通道控制步进电机

在这里通过PC6引脚控制步进电机,不需要考虑什么引脚推挽还是开漏的问题,直接设置通道就可以了。如果这里不加延时,电机会一直转。我设置过像HAL_TIM_OC_DelayElapsedCallback一样的回调函数去按照比较通道执行顺序计数,但是并不好用,会发现初始的库文件并没有弱定义这个函数,遂放弃这种方式。

void stepper_turn(uint8_t dir)
{
  if(dir==CW)        //顺时针
  {
    HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,HIGH);
  }
  else if(dir==CCW)//逆时针
  {
    HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,LOW);
  }
  /*开使能*/
  HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_SET);
  /* 启动比较输出并使能中断 */
  HAL_TIM_OC_Start_IT(&htim3,TIM_CHANNEL_1);
  for(uint16_t i=0;i<2000;i++) {
    delay_us(1000);
  }
  stepper_stop();
}

void stepper_stop(void) {
  HAL_TIM_OC_Stop_IT(&htim3,TIM_CHANNEL_1);
  HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,GPIO_PIN_RESET);
}

在定时器内设置速度,使能比较通道后关闭中断,需要中断时再去调用并打开中断。这里面电机速度计算过程如下,可算得电机速度周期是1.375ms。

72MHZ/(1+899)=0.08 \\ (109+1)/0.08=1.375ms

void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 899;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 109;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OC_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TOGGLE;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /*使能比较通道*/
  TIM_CCxChannelCmd(TIM3,TIM_CHANNEL_1,TIM_CCx_ENABLE);
  HAL_TIM_OC_Stop_IT(&htim3,TIM_CHANNEL_1);
  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);

}

定时器PWM方式控制步进电机

其实就是把使能比较通道方式换成pwm通道。 sConfigOC.Pulse/(arr+1)这个值是占空比,可以把它设置在tim周期的50%到80% 之间。

但其实这种方式个人认为并不利于电机控制,因为如果想旋转需要的角度,需要设置主从定时器和输入触发源,而且cubeide会初始化很多配置,其中就包括psc,arr和pulse占空比,不利于程序本身的赋值。而且开启PWM时,必须同时开启AFIO时钟!!必须配置对应引脚为复用输出!HAL_TIM_MspPostInit(&htim3);就是这个定时器引脚定义函数,所以下面的定时器配置里是HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);,假如设置HAL_TIM_PWM_Stop,在freertos里再打开,是驱动不了步进电机的。。。。。这件事情卡了我很久。

好处是它和输出比较方式都随时转换方向,而且不需要考虑推挽还是开漏引脚,控制是真的很稳。

void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 719;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = tim_per-1;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = tim_per/2;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */
  HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);

}

几种方式的优缺点总结

个人认为这里面设置最简单,相对来说最可控,占用单片机内部资源最少的还是模拟io方式,延时的问题通过定时器TIM4解决后,大大增加了可控的精度。而且实际测试时,12v供电它的引脚电压基本能稳到10v,且电机发热时一样可运行,也不需要特定的gpio驱动,是和模拟i2c一样的,比较好的方式,之后做项目可能会多用这种电机控制方式。

如果特定项目需要快速切换电机方向,建议选择定时器比较通道控制,或以PWM方式控制电机。

另外,发现TIM1是没有办法设置TIM_OCMODE_TOGGLE方式的,只能设置TIM_OCMODE_TIMING,也查了相关手册,并没介绍这个地方,之后可能会继续完善看看这是怎么回事。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/978658.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

管理后台环境配置

1. 后端配置及启动 a. 软件安装 Java sdk 1.8 maven 3.6 intellij IDEA 2024 Visual C Redistributable mongodb mysql wsl &#xff08;管理员&#xff1a;wsl --install&#xff09; redis curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/shar…

Python学习第十八天之深度学习之Tensorboard

Tensorboard 1.TensorBoard详解2.安装3.使用4.图像数据格式的一些理解 后续会陆续在词博客上更新Tensorboard相关知识 1.TensorBoard详解 TensorBoard是一个可视化的模块&#xff0c;该模块功能强大&#xff0c;可用于深度学习网络模型训练查看模型结构和训练效果&#xff08;…

DeepSeek 大模型:带火算力,重塑 AI?

在全球人工智能蓬勃发展的当下&#xff0c;各类技术与模型持续迭代更新&#xff0c;深刻影响着各个行业的发展轨迹。DeepSeek 作为其中的重要参与者&#xff0c;快速崭露头角&#xff0c;在技术创新和市场拓展方面成果显著&#xff0c;对算力市场也产生了强大的带动效应。这引发…

(21)从strerror到strtok:解码C语言字符函数的“生存指南2”

❤个人主页&#xff1a;折枝寄北的博客 ❤专栏位置&#xff1a;简单入手C语言专栏 目录 前言1. 错误信息报告1.1 strerror 2. 字符操作2.1 字符分类函数2.2 字符转换函数 3. 内存操作函数3.1 memcpy3.2 memmove3.2memset3.3 memcmp 感谢您的阅读 前言 当你写下strcpy(dest, s…

wpf中如何让TextBox 显示字体的颜色为白色

在 WPF 中&#xff0c;要让 TextBox 的字体颜色显示为白色&#xff0c;可以通过以下方法实现&#xff1a; 方法 1&#xff1a;直接设置 Foreground 属性&#xff08;XAML&#xff09; 在 XAML 中直接为 TextBox 设置 Foreground 属性&#xff0c;使用 White 颜色&#xff1a; …

小白向-python实现插入排序算法

插入排序 一、插入排序的定义 插入排序&#xff08;Insertion Sort&#xff09;是一种稳定的排序算法&#xff0c;通过构建有序序列&#xff0c;逐步将新元素插入到正确位置&#xff0c;最终完成排序。 二、插入排序的发展历史 插入排序是一种古老且直观的排序算法&#xff…

python-leetcode-最长公共子序列

1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:m, n len(text1), len(text2)dp [[0] * (n 1) for _ in range(m 1)]for i in range(1, m 1):for j in range(1, n …

mac电脑中使用无线诊断.app查看连接的Wi-Fi带宽

问题 需要检查连接到的Wi-Fi的AP硬件支持的带宽。 步骤 1.按住 Option 键&#xff0c;然后点击屏幕顶部的Wi-Fi图标&#xff1b;2.从下拉菜单中选择 “打开无线诊断”&#xff08;Open Wireless Diagnostics&#xff09;&#xff1b;3.你可能会看到一个提示窗口&#xff0c;…

什么是模型量化和模型蒸馏?

文章目录 一、模型量化二、模型蒸馏三、二者有联系吗&#xff1f;四、示例场景五、总结 一、模型量化 模型量化&#xff08;Model Quantization&#xff09;是一种优化技术&#xff0c;通过将模型的参数和计算从高精度&#xff08;如 32 位浮点数&#xff0c;FP32&#xff09;…

Asp.Net Web API| React.js| EF框架 | SQLite|

asp.net web api EF SQLiteReact前端框架 设计一个首页面&#xff0c;包含三个按钮分别对应三类用户&#xff08;数据查看&#xff0c;设计人员&#xff0c;管理员&#xff09;&#xff0c;当点击管理员的时候弹出一个前端页面可以输入信息&#xff08;以学生数据为例&#…

英文论文查重,Turnitin和IThenticate两个系统哪个更合适?

Turnitin系统和IThenticate系统都是检测英文论文的查重系统&#xff0c;但是两者之间还是有一些不一样的。 下面针对这两个系统给大家具体分析一下。 一、Turnitin系统 Turnitin检测系统&#xff1a; https://truth-turnitin.similarity-check.com Turnitin是世界上主流的…

Unity Dedicated Server 控制台 输出日志LOg 中文 乱码

现象: 中文乱码 原因: Unity打包出来的.exe文件&#xff0c;语言一栏是英文&#xff0c;VS控制台出来不一样 解决方案: 新建.bat文件 &#xff0c;并使用命令chcp 65001&#xff0c;运行时启动.bat&#xff0c;而不是.exe, 改不了exe属性&#xff0c;虽然有点奇怪&#xff…

Cesium高级开发教程之四十三:缓冲区分析#面

一、简介 基本概念:面缓冲区分析是指围绕一个给定的面几何对象,根据指定的距离,在面的外部或内部生成一个新的面状区域。例如,对于一个表示湖泊的面要素,通过设置一定的缓冲距离,可以在湖泊周围生成一个环状的缓冲区域,用于分析湖泊周边的生态环境影响范围等;或者在一个…

18439二维前缀和

18439二维前缀和 ⭐️难度&#xff1a;中等 &#x1f4d6; &#x1f4da; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int n scanner.nextInt();int m scanner.nextInt();int q s…

PwnLab详细解答

一、主机发现 arp-scan -l靶机ip&#xff1a;192.168.55.153 二、端口识别、目录枚举、指纹识别 2.1端口识别 nmap -p- 192.168.55.1532.2目录枚举 dirb http://192.168.55.153枚举出来的敏感目录找到了文件上传网站和上传的地址 2.3指纹识别 nmap 192.168.55.153 -sV -…

傅里叶分析

傅里叶分析之掐死教程&#xff08;完整版&#xff09;更新于2014.06.06 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析不仅仅是一个数学工具&#xff0c;更是一种可以彻底颠覆一个人以前世界观的思维模式。但不幸的是&#xff0c;傅里叶分析的公式看起来太复…

unity学习56:旧版legacy和新版TMP文本输入框 InputField学习

目录 1 旧版文本输入框 legacy InputField 1.1 新建一个文本输入框 1.2 InputField 的子物体构成 1.3 input field的的component 1.4 input Field的属性 2 过渡 transition 3 控件导航 navigation 4 占位文本 placeholder 5 文本 text 5.1 文本内容&#xff0c;用户…

详解Tomcat下载安装以及IDEA配置Tomcat(2023最新)

目录 步骤一&#xff1a;首先确认自己是否已经安装JDK步骤二&#xff1a;下载安装Tomcat步骤三&#xff1a;Tomcat配置环境变量步骤四&#xff1a;验证Tomcat配置是否成功步骤五&#xff1a;为IDEA配置Tomcat 步骤一&#xff1a;首先确认自己是否已经安装JDK jdk各版本通用安…

《Qt动画编程实战:轻松实现头像旋转效果》

《Qt动画编程实战&#xff1a;轻松实现头像旋转效果》 Qt 提供了丰富的动画框架&#xff0c;可以轻松实现各种平滑的动画效果。其中&#xff0c;旋转动画是一种常见的 UI 交互方式&#xff0c;广泛应用于加载指示器、按钮动画、场景变换等。本篇文章将详细介绍如何使用 Qt 实现…

从零构建知识库:AI如何实现“问题即答案”?

在当今这个信息爆炸的时代&#xff0c;如何高效地获取和利用知识成为了各行各业面临的共同挑战。构建知识库&#xff0c;作为整合、存储和检索信息的重要手段&#xff0c;正在逐步成为企业提升竞争力的关键。而AI技术的加入&#xff0c;更是让这一过程实现了质的飞跃&#xff0…