【STM32】串口和printf

1.数据通信的基本知识

1.串行/并行通信

2.单工/半双工/全双工通信

类似于【广播  对讲 电话】

不是有两根线就是全双工,而是输入和输出都有对应的数据线。

3.同步/异步通信

区分同步/异步通信的根本:判断是否有时钟信号(时钟线)。如果有时钟线则是同步通信,如果没有时钟线则是异步通信。

4.波特率  VS  比特率

5.常见的串行通信接口

2.串口(RS-232)

1.什么是串口

实际上就是按位来对数据进行发送和接收

2.RS232电平和CMOS/TTL电平对比

3.设备间的RS232通信示意图

MAX323:将RS232电平---》TTL/CMOS电平

 4.STM32串口与电脑USB口通信示意图

USB/串口转换电路CH340C:USB电平---》TTL/CMOS电平

5.RS232异步通信协议

注意:异步通信协议中是没有使用到时钟线(SCLK)

3.STM32USART

1.STM32USART简介

USART虽然是同步和异步都可以进行使用,但是我们大多数情况下使用的都是异步通信

2.STM32USART主要特征

3.在选型手册中查看USART/UART对应的引脚定义

4.STM32F1的USART框图

1.引脚说明

2.接收/发送数据的存放/处理位置

数据要先放在DR寄存器中,然后再通过DR寄存器操作CPU

3.波特率的处理

5.框图简化版

4.设置USART/UART波特率

1.计算公式

baud:波特率是用户自己定义的,然后求出USARTDIV,然后分为整数部分和小数部分,在存储到USART_BRR寄存器

2.波特比率寄存器(BRR)

3.举个例子

4.公式推导

因为小数部分和整数部分都要往左移动4位,所以将整个值*16

5.USART寄存器

1.控制寄存器 1(USART_CR1)

2.控制寄存器 2(USART_CR2)

3. 控制寄存器 3(USART_CR3)

4. 数据寄存器(USART_DR)

1)具体传输多少位取决于寄存器USART_CR1中的位12M【字长】

2)设置好控制和波特率寄存器后,往该寄存器写入数据即可发送,接收数据则读该寄存器

5.状态寄存器(USART_SR)

根据TC位(发送完成位)可以知道能否发数据,根据RXNE位(读数据寄存器非空)知道是否收到数据。

6.需要配置的时序总结

6.USART/UART异步通信配置步骤

1.HAL_USART_Init

对USART进行初始化

2.HAL_USART_Receive_IT

USART的中断使能函数

这个函数是非阻塞式的,没有执行完也可以出来

3.HAL_USART_Receive

没有开启中断的UASRT

这个函数是阻塞式的,没有执行完不可以出来

7.通过串口接收或者发送一个字符

1.连接注意点

2.原理图分析

3.代码编写

usart_init()

相关的设置参数和开启usart的中断

hal_usart_mspinit()

1)使能USART1和对应的IO时钟

2)初始化IO

3)使能USART1中断,设置NVIC优先级

usart1_IRQHandler

中断回调服务函数

2)HAL_UART_iRQHandler()会清除中断

HAL_USART_RxCpltCallback

串口接收数据的回调函数

1.CubeMX和HAL库的串口实战

1、CubeMX中打开并设置串口

1.设置对应的RCC

2.查看原理图上对应的RX和TX

注意点:我们的串口对应的应该是RX--》TX,TX--》RX

我们要先查看原理图中是否已经帮我们接反了,如果没有则需要我们自己手动接反。

我们使用的STM32F103C8xx直接接入USB口即可

由上面分析可知,我们使能PA9和PA10即可

3.设置相关的USART

4.设置相关的时钟

2、串口操作

(1)阻塞模式串口发送:CPU不做其他事情

MCU的CPU一个字节一个字节的将要发送的内容丢给串口模块,然后看着串口模块将这个字节发送出去,然后CPU再去拿下一个字节来丢给串口模块。直到本次要发送的所有字节全部发完,CPU才会去做其他事。

(2)中断模式串口发送:CPU轮询式查询

MCU的CPU向串口模块丢一个字节,然后串口模块慢慢发,CPU丢完这个字节后会跳出去做其他事情,等串口模块发完这个字节后会生成一个中断,中断会通知CPU过来继续丢下一个字节。

2.源码分析和串口发送的实现

1、阻塞式串口发送/接收

1.1 阻塞式发送:HAL_UART_Transmit(常用)

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;
  uint32_t tickstart = 0U;

  /* Check that a Tx process is not already ongoing */
  //查看状态
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
	//将串口模块锁住了,将变量的值设置为"HAL_LOCKED"
    __HAL_LOCK(huart);

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Init tickstart for timeout managment */
	//获取当前的时候,用于判断是否超时
    tickstart = HAL_GetTick();

    huart->TxXferSize = Size;//表示要发送的个数
    huart->TxXferCount = Size;//还要发送的个数=总个数-已经发送
    while (huart->TxXferCount > 0U)
    {
      huart->TxXferCount--;
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
     	 //等待标志发生,查看是否超时
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;//超时
        }
        tmp = (uint16_t *) pData;
        huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          pData += 2U;
        }
        else
        {
          pData += 1U;
        }
      }
      else
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
		//真正执行操作的代码
        huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
      }
    }
	//查看发送是否已经完成【阻塞等待串口发完】
    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    {
      return HAL_TIMEOUT;
    }

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;

    /* Process Unlocked */
	//解锁
    __HAL_UNLOCK(huart);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

1.2 阻塞式接收:HAL_UART_Receive(不常用)

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;
  uint32_t tickstart = 0U;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;

    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();

    huart->RxXferSize = Size;
    huart->RxXferCount = Size;

    /* Check the remain data to be received */
    while (huart->RxXferCount > 0U)
    {
      huart->RxXferCount--;
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
      //检查UART_FLAG_RXNE是否为空,如果为非空,表示接收到数据
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t *) pData;
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
          pData += 2U;
        }
        else
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
          pData += 1U;
        }

      }
      else
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
        }
        else
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
        }

      }
    }

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    return HAL_OK;
  }

3.延时时间的设置

如果我们设置的延时时间太长,则我们可能会在延时的时候错过一些字符的发送和接收。

 

4.总结

int main(void)
{
  //定义一个要进行发送的数据
  uint8_t sbuf[8]="stm32";
  //定义一个要进行接收的数据
  uint8_t rbuf[20]="";
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
		//第一个参数要传入的是地址
		//size:要发送的大小
		//0x0000ffff:超时时间
	//	HAL_UART_Transmit(&huart1,sbuf,5,0x0000ffff);
	//	HAL_Delay(1000);
		
		//阻塞式的接收:接收一个后马上接着下一个接收
		//此方法最好一次发送一个bit
		HAL_UART_Receive(&huart1,rbuf,1,0x0000ffff);
		//将接收到的数值发送出来
		HAL_UART_Transmit(&huart1,rbuf,1,0x0000ffff);
		//这个延时时间不能太久,要不然可能会在延时的时候接收了一个字符,从而错过这个
		//HAL_Delay(100);
  }
}

阻塞式的发送实际用的很多,因为编程简单。缺陷是浪费高速CPU的部分性能,没有追求到串口发送和整个系统性能的最高。

2、非阻塞式(中断)串口发送

1.HAL_UART_Transmit_IT

这个函数没有超时时间,因为我们不用等。

注意点::要打开中断

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* Check that a Tx process is not already ongoing */
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

//因为我们使用中断方式,则需要在串口内部定义一个buf,使得buf指向data的地址
    huart->pTxBuffPtr = pData;
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Transmit data register empty Interrupt */
	//使能UART发送数据寄存器空中断
    __HAL_UART_ENABLE_IT(huart, UART_IT_TXE);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

2.注意点 

我们使用这个中断式的应该打开Usart的中断

int main(void)
{
	//定义一个要进行发送的数据
	uint8_t sbuf[8]="stm32";
	//定义一个要进行接收的数据
	uint8_t rbuf[20]="";
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
		/**
			中断式发送和接收
		*/
		HAL_UART_Transmit_IT(&huart1,sbuf,5);
		HAL_Delay(100);
  }
}

3.printf的实现

printf的实现其实就是重载fputc函数

1.原始代码 

#ifdef __GNUC__
	#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
	#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}

2.注意点1: 

一定一定要勾选User MicroLIB

3.注意点2:

我们使用到了fputc,其中FILE中是定义在<stdio.h>中的宏定义

定义在usart.h中

int main(void)
{
  /* USER CODE BEGIN 1 */
	//定义一个要进行发送的数据
	uint8_t sbuf[8]="stm32";
	//定义一个要进行接收的数据
	uint8_t rbuf[20]="";
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
	float a=4.532;
	printf("a=%f\n",a);
	printf("test\r");
  }
}

4.串口接收编程实战

1、阻塞式串口接收

CPU等着过来。所以当CPU临时去处理其他事情时,可能会错过一些串口的输出。所以我们基本上不用。如果真的要使用,则要协调好延时和串口的接收和发送。

2、中断式串口接收

使用中断的方式向串口发送数据并且输出

1.先开启uart中断

2.写中断处理函数:HAL_UART_RxCpltCallback

在main函数中

//rc用来暂存和处理串口接收到的字节内容的
uint8_t receive_char;

//这个就是HAL库对接的中断处理程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//判断是否为usart1
	if(huart->Instance == USART1)
	{
		//这里就是真正的中断处理代码
		//我们这里的处理就是接收到一个字节后原封不动的发回去
		HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
		//等待本次接收完毕,同时开启下一次接收【更新receive_char】
		//HAL_UART_Receive_IT:开启中断处理流程
		while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
	}

}

3.注册中断

由上面分析可以知道,我们在进入中断处理函数中,是将接收到的字符发送出去,所以我们需要在外部先接收到字符【触发中断】,才可以发送。所以我们需要在其他模块初始化的位置先进入一次中断,接收串口发送的数据。

3.测试代码

此代码:在我们不向串口发送字符时,每3s发送一次a,当我们向串口发送数据时,会马上在串口输出。

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	//从此处进入中断处理程序
  HAL_UART_Receive_IT(&huart1, &receive_char, 1);
  while (1)
  {
	float a=4.532;
	printf("a=%f\n",a);
	HAL_Delay(3000);
  }
}

//这个就是HAL库对接的中断处理程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//判断是否为usart1
	if(huart->Instance == USART1)
	{
		//这里就是真正的中断处理代码
		//我们这里的处理就是接收到一个字节后原封不动的发回去
		HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
		//等待本次接收完毕,同时开启下一次接收【更新receive_char】
		//HAL_UART_Receive_IT:开启中断处理流程
		while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
	}

}

3.处理串口中断的流程

在初始化部分已经将中断连接起来,到时候产生中断则直接进入【HAL_UART_RxCpltCallback】这个函数

5.案例1:基于串口的命令shell实现

命令shell:发送一个命令返回一个回应

1.协议自定义

(1)指令集:指令1:add    指令2:sub
(2)指令结束符:';'【定义普通指令中不可能出现的】
(3)指令中遇到回车和空格、Tab等特殊字符怎么办

2.实现思路分析

1.定义一个缓冲区,存储接到的数据

2.定义一个索引值

值存储再buf中的第几个字节

3.代码编写

/* USER CODE BEGIN 4 */
//这个就是HAL库对接的中断处理程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//判断是否为usart1
	if(huart->Instance == USART1)
	{
		//将receive_char的数值存放再buf中
		rev_buf[tindex]=receive_char;//暂存
		tindex++;//指向下一个
		while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
	}

}

4.main函数编写

1)判断指令

2)buf和index要记得清空

6.串口实验

1.串口接收数据过程

2.串口发送数据过程

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

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

相关文章

开源维修上门服务小程序SAAS系统源码 带完整搭建教程

在现代生活中&#xff0c;家电设备维修往往是一个耗时且繁琐的过程。消费者需要花费大量时间寻找合适的维修人员&#xff0c;并面临服务质量不稳定的风险。同时&#xff0c;对于维修人员来说&#xff0c;寻找客户和接收订单的过程也十分繁琐。因此&#xff0c;开发一款基于小程…

深入理解JVM虚拟机第二十五篇:详解JVM方法的绑定机制静态绑定和动态绑定,早期绑定晚期绑定,并编写代码从字节码角度证明这件事情

大神链接&#xff1a;作者有幸结识技术大神孙哥为好友&#xff0c;获益匪浅。现在把孙哥视频分享给大家。 孙哥链接&#xff1a;孙哥个人主页 作者简介&#xff1a;一个颜值99分&#xff0c;只比孙哥差一点的程序员 本专栏简介&#xff1a;话不多说&#xff0c;让我们一起干翻J…

【MediaFoundation】相关的概念

MF 概览 Media Foundation 提供了两种不同的编程模型&#xff0c;左边展示的是端到端的媒体数据模型&#xff0c;主要用在&#xff1a;播放URL或者文件&#xff0c;以及控制流。 在图表右侧展示的第二种模型中&#xff0c;应用程序可以从源头拉取数据&#xff0c;也可以将数据…

一文了解VR全景拍摄设备如何选择,全景图片如何处理

引言&#xff1a; 在如今的数字化时代&#xff0c;虚拟现实&#xff08;VR&#xff09;技术不仅为我们的生活增添了许多乐趣&#xff0c;也为摄影领域带来了新的摄影方式&#xff0c;那么VR全景拍摄如何选择设备&#xff0c;全景图片又怎样处理呢&#xff1f; 一. VR全景拍摄设…

uniapp项目笔记

1.生成二维码 import uqrCode from /static/erweima.js uqrCode.make({canvasId: qrcode,componentInstance: this,text: JSON.stringify(item.id),size: 150,margin: 0,backgroundColor: #ffffff,foregroundColor: #000000,fileType: jpg,errorCorrectLevel: uqrCode.errorCor…

高质量实时渲染笔记

文章目录 Real-time shadows1 自遮挡问题2 解决阴影detach问题&#xff1f;3 Aliasing4 近似积分5 percentage closer soft shadows(PCSS)percenta closer filtering(PCF)PCSS的思想 6 Variance Soft Shadow Mapping (VSSM)步骤Moment Shadow Mapping 7 Distance field shadow …

LeetCode - 232.用栈实现队列 225.用队列模拟实现栈 (C语言,配图)

目录 232.用栈实现队列 225.用队列模拟实现栈 注&#xff1a;本文是基于C语言实现的代码&#xff0c;所以栈和队列是在力扣上制造实现的&#xff0c;如果你使用C等语言&#xff0c;可以忽略前面相当大部分的代码。 在栈模拟实现栈和队列之前&#xff0c;我们先来复习一下栈和…

Skywalking流程分析_4(插件的加载和不同版本的识别)

插件的结构 之前我们介绍了插件的加载&#xff0c;接下来就是真正开始进行插件的执行了&#xff0c;首先要看下插件的结构是怎么样的&#xff0c;以阿里的druid数据源为例 skywalking-plugin.def: druid-1.xorg.apache.skywalking.apm.plugin.druid.v1.define.DruidPooledCo…

【PG】PostgreSQL高可用方案repmgr部署(非常详细)

目录 简介 1 概述 1.1 术语 1.2 组件 1.2.1 repmgr 1.2.2 repmgrd 1.3 Repmgr用户与元数据 2 安装部署 2.0 部署环境 2.1 安装要求 2.1.1 操作系统 2.1.2 PostgreSQL 版本 2.1.3 操作系统用户 2.1.4 安装位置 2.1.5 版本要求 2.2 安装 2.2.1 软件包安装 2.2…

使用Filebeat+Kafka+Logstash+Elasticsearch构建日志分析系统

随着时间的积累&#xff0c;日志数据会越来越多&#xff0c;当您需要查看并分析庞杂的日志数据时&#xff0c;可通过FilebeatKafkaLogstashElasticsearch采集日志数据到Elasticsearch中&#xff0c;并通过Kibana进行可视化展示与分析。本文介绍具体的实现方法。 一、背景信息 …

科学上网导致Adobe软件运行弹出This non-genuine Adobe app will be disabled soon,尝试解决办法

之前介绍用防火墙拦截Adobe软件的出站规则可以解决软件的非正版弹窗&#xff0c;但是有的用户却不行是为什么&#xff0c;原因是使用了代理网络。因为Adobe此时跑的不是本地的流量而是代理的流量。所以防火墙拦截就不起作用了。 首先是之前介绍过的拦截方法&#xff0c;如果你没…

百度飞浆环境安装

前言&#xff1a; 在安装飞浆环境之前得先把pytorch环境安装好&#xff0c;不过关于pytorch网上教程最多的都是通过Anaconda来安装&#xff0c;但是Anaconda环境安装容易遇到安装超时导致安装失败的问题&#xff0c;本文将叫你如何通过pip安装的方式快速安装&#xff0c;其实这…

14——1

这句话的意思是&#xff0c;如图中月份12天数23时&#xff0c;就是1223&#xff1b;当月份9天数2时&#xff0c;就是0902. 可以看到在上面给出的数组元素中&#xff0c;并没有连续挨在一起的2023数字元素——就有人可能输出答案0。 所以这里要看一下—— ——子序列的含义&…

The 8th China Open Source Conference Successfully Concludes

由开源社主办的第八届中国开源年会&#xff08;COSCon23&#xff09;于 2023年10月29日在成都圆满收官。本次大会&#xff0c;为期两天&#xff0c;线下参会报名逾千人次&#xff0c;在线直播观看人数总计 168610 人&#xff0c;直播观看次数达 248725 次&#xff0c;官网累计浏…

网络编程 —— TCP 和 UDP 编程详解

目录 网络编程主要函数介绍 1. socket 函数 2. bind 函数 3. listen 函数 4. accept 函数 5. connect 函数 6. send 函数 7. recv 函数 8. recvfrom 函数 9. sendto 函数 TCP 和 UDP 原理上的区别 TCP 编程 服务端代码&#xff1a; 客户端代码&#xff1a; UDP 编…

nodejs+vue公益帮学网站的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

在当今高度发达的信息中&#xff0c;信息管理改革已成为一种更加广泛和全面的趋势。为确保中国经济的持续发展&#xff0c; 如何用方便快捷的方式使管理者在广阔的数据海洋里面查询、存储、管理和共享有效的数据信息&#xff0c;对我们的学习&#xff0c;工作和生活具有重要的现…

创造者设计模式

Bike package com.jmj.pattern.builder.demo01;public class Bike {private String frame;//车架private String seat;//车座public String getFrame() {return frame;}public void setFrame(String frame) {this.frame frame;}public String getSeat() {return seat;}public…

Webpack Bundle Analyzer包分析器

当我们需要分析打包文件dist里哪些资源可以进一步优化时&#xff0c;就可以使用包分析器插件webpack-bundle-analyzer。NPM上的介绍是使用交互式可缩放树图可视化 webpack 输出文件的大小。 我的是vue2项目。 1、webpack-bundle-analyzer插件的安装 $ npm install --save-dev…

接口测试vs功能测试

接口测试和功能测试的区别&#xff1a; 本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什…

Kyligence 入选 Gartner® 2023 客户之声报告,高分获评“卓越表现者”

近日&#xff0c;Gartner 发布了最新的《2023 分析和商业智能平台“客户之声”报告》(Voice of the Customer for Analytics and Business Intelligence Platforms, 2023, October 2023)。跬智信息&#xff08;Kyligence&#xff09;成功入选该报告&#xff0c;并凭借 4.7 分&a…