STM32CUBUMX配置RS485 modbus STM32(从机)亲测可用

news2024/9/21 19:02:25

————————————————————————————————————
⏩ 大家好哇!我是小光,嵌入式爱好者,一个想要成为系统架构师的大三学生。
⏩最近在开发一个STM32H723ZGT6的板子,使用STM32CUBEMX做了很多驱动,包括ADC、UART、RS485、EEPROM(IIC)、FLASH(SPI)、modbus等等。
⏩本篇文章对STM32CUBEMX在RS485通信的基础上做modbus通信做一个详细的使用教程。
⏩感谢你的阅读,不对的地方欢迎指正。
————————————————————————————————————

modbuspoll

  • 工具下载
  • modbus协议简介
    • Modbus协议类型
    • Modbus-Rtu协议
    • Modbus功能码
  • STM32CUBEMX配置
    • RS485配置
    • 定时器配置
  • 驱动代码
  • 测试结果
    • XCOM串口调试助手作为主机测试
    • modbus poll作为主机测试
  • 总结

工具下载

Modbus Poll是一个模拟Modbus协议主机的上位机软件,主要用于模拟测试跟其他从机设备通信的过程。与之成套存在的另一个软件–Modbus Slave,则是模拟Modbus协议从机的上位机软件。该软件内部封装标准Modbus协议栈,通过图形化界面使得操作更为简便。目前软件支持01、02、03、04、05、06、15、16功能码,异常报文检测,原始报文查看,数据记录等功能,是调试Modbus协议栈的好帮手。
下载链接:
链接:百度网盘下载链接modbus poll 7.0.1
提取码:lft0

modbus协议简介

参考:
值得收藏 Modbus RTU 协议详解
详解Modbus通信协议—清晰易懂

Modbus协议类型

串行端口存在多个版本的Modbus协议,而最常见的是下面四种:

  • Modbus-Rtu 远程终端控制系统 CRC16校验
  • Modbus-Ascii Ascii码表示数据 LRC校验
  • Modbus-Tcp TCP三种报文类型 无校验
  • ModbusPlus

我们这里使用Modbus-Rtu进行编写代码和测试。

Modbus-Rtu协议

在这里插入图片描述

Modbus功能码

Modbus规定了多个功能,那么为了方便的使用这些功能,我们给每个功能都设定一个功能码,也就是指代码。
在这里插入图片描述
既然搞清楚了原理,那么后面我们开始程序讲解:

STM32CUBEMX配置

RS485配置

参考我之前的文章:
STM32CUBUMX配置RS485(中断接收)–保姆级教程
一定要根据这个文章把RS485调通

定时器配置

首先我们要知道modbus通信的一帧数据是通过每一帧数据之间的间隔时间来确认的。

  • 当bps<19200时:超时时间是大于3.5个字节时间。
  • 当bps>19200时:超时时间是大于1750us

例如:
bps = 9600: 传输一个字节的时间是1/9600*10 = 10.4ms,3.5个字节时间就是3.5ms
bps = 115200:超时时间就是17500
定时器定时时间计算可以参考我之前的文章:
STM32CUBEMX配置 定时器中断
下面进行定时器配置
在这里插入图片描述
我们配置的PSC = 27500-1 ARR = 50-1 TIM3的时钟是275Mhz
所以:定时时间 T = 27500 * 50/275 * 10^6 = 0.00005s = 50us
定时器计数35次,也就是1800 刚好大于1750us ,符合modbus协议
在这里插入图片描述
开启定时器中断,优先级需要比串口中断更低

驱动代码

modbus.h

#ifndef MODBUS_H_
#define MODBUS_H_

#include "stm32H7xx_hal.h" //HAL库文件声明
#include "gpio.h"
#include "usart.h"

#define BUFFER_SIZE 600 //最大数据帧

typedef struct {
	uint8_t myadd;//从机设备地址
	uint8_t timrun;//定时器
	uint8_t slave_add;//主机要匹配的从机地址(本设备作为主机时)
	uint8_t reflag;//接收完成标志位,1:完成 0:未完成
	uint8_t Host_time_flag;//发送数据标志
	uint8_t recount;//接收到的字节数
	unsigned char rcbuf[BUFFER_SIZE];//接受数据帧
	unsigned char sendbuf[BUFFER_SIZE];//发送数据帧
	uint32_t timout;//超时时间 单位:ms
	uint32_t Host_Sendtime;//发送完上一帧后的时间计数 单位:ms
	
	
}MODBUS;
 // Modbus初始化函数
void Modbus_Init(void);
void Modbus_Event(void);
void Modbus_Func3(void);
void Modbus_Func6(void);
void Modbus_Func16(void);
 void Modbus_Send_Byte(  uint8_t ch );
 int Modbus_CRC16(uint8_t buff[],int len);
#endif

modbus.c

#include "modbus.h"
MODBUS modbus;
uint16_t Reg[] ={0x0001,
            0x0012,
            0x0013,
            0x0004,
	        0x0025,
            0x0036,
            0x0007,
			0X0008,
           };//reg是提前定义好的寄存器和寄存器数据,要读取和改写的部分内容
 // Modbus初始化函数
 void Modbus_Init(void)
 {
   modbus.myadd = 0x01; //从机设备地址为2
   modbus.timrun = 0;    //modbus定时器停止计算
	 modbus.slave_add=0x02;//主机要匹配的从机地址(本设备作为主机时)
	 modbus.reflag = 0;//无数据包处理
	 modbus.Host_time_flag = 0;//发送数据标志
	 modbus.recount = 0;//接收到的字节数
	 modbus.timout = 0;//超时时间 单位:ms
	 modbus.Host_Sendtime = 0;//发送完上一帧后的时间计数 单位:ms
 }
					 
 // Modbus事件处理函数
 void Modbus_Event(void)
 {
 	uint16_t crc,rccrc;//crc和接收到的crc
 	//没有收到数据包
   if(modbus.reflag == 0)  //如果接收未完成则返回空
 	{
 	   return;
 	}
 	//收到数据包(接收完成)
 	//通过读到的数据帧计算CRC
 	//参数1是数组首地址,参数2是要计算的长度(除了CRC校验位其余全算)
 	crc = Modbus_CRC16(&modbus.rcbuf[0],modbus.recount-2); //获取CRC校验位
 	// 读取数据帧的CRC
 	rccrc = modbus.rcbuf[modbus.recount-2]*256+modbus.rcbuf[modbus.recount-1];//计算读取的CRC校验位
 	//等价于下面这条语句
 	//rccrc=modbus.rcbuf[modbus.recount-1]|(((uint16_t)modbus.rcbuf[modbus.recount-2])<<8);//获取接收到的CRC
 	if(crc == rccrc) //CRC检验成功 开始分析包
 	{	
 	   if(modbus.rcbuf[0] == modbus.myadd)  // 检查地址是否时自己的地址
 		 {
 		   switch(modbus.rcbuf[1])   //分析modbus功能码
 			 {
 			   case 0:             break;
 				 case 1:             break;
 				 case 2:             break;
 				 case 3:       Modbus_Func3();break;//这是读取寄存器的数据
 				 case 4:             break;
 				 case 5:             break;
          case 6:      Modbus_Func6();      break;//这是写入单个寄存器数据
 				 case 7:             break;
 				 case 8:             break;
 				 case 9:             break;
 				 case 16:     Modbus_Func16(); 			break;//写入多个寄存器数据
 			 }
 		 }
 		 else if(modbus.rcbuf[0] == 0) //广播地址不予回应
 		 {
 		    
 		 }	 
 	}	
 	 modbus.recount = 0;//接收计数清零
   modbus.reflag = 0; //接收标志清零
 }

 /*
 ********************************************************************************
 主机:03
 01  03      00 01     00 01          D5 CA	从地址01开始读读取一个寄存器的数据内容
 ID 功能码  起始地址  读取寄存器的个数
 从机返回:
 01  03       02       00 03          F8 45 返回了两个字节的数据,数据是00 03
 ID  功能码  几个字节  返回的数据内容
 
 ********************************************************************************
 */
 // Modbus 3号功能码函数
 // Modbus 主机读取寄存器值
 void Modbus_Func3(void)
 {
   uint16_t Regadd,Reglen,crc;
 	uint8_t i,j;	
 	//得到要读取寄存器的首地址
 	Regadd = modbus.rcbuf[2]*256+modbus.rcbuf[3];//读取的首地址
 	//得到要读取寄存器的数据长度
 	Reglen = modbus.rcbuf[4]*256+modbus.rcbuf[5];//读取的寄存器个数
 	//发送回应数据包
 	i = 0;
 	modbus.sendbuf[i++] = modbus.myadd;      //ID号:发送本机设备地址
 	modbus.sendbuf[i++] = 0x03;              //发送功能码
   modbus.sendbuf[i++] = ((Reglen*2)%256);   //返回字节个数
 	for(j=0;j<Reglen;j++)                    //返回数据
 	{
 		//reg是提前定义好的16位数组(模仿寄存器)
 	  modbus.sendbuf[i++] = Reg[Regadd+j]/256;//高位数据
 		modbus.sendbuf[i++] = Reg[Regadd+j]%256;//低位数据
 	}
 	crc = Modbus_CRC16(modbus.sendbuf,i);    //计算要返回数据的CRC
 	modbus.sendbuf[i++] = crc/256;//校验位高位
 	modbus.sendbuf[i++] = crc%256;//校验位低位
 	//数据包打包完成
 	// 开始返回Modbus数据
 	
 	RS485DIR_TX;//这是开启485发送
 	
 	for(j=0;j<i;j++)//发送数据
 	{
 	  Modbus_Send_Byte(modbus.sendbuf[j]);	
 	}
 	RS485DIR_RX;//这里是关闭485发送
 }

  // Modbus 6号功能码函数
 // Modbus 主机写入寄存器值
 void Modbus_Func6()  
 {
   uint16_t Regadd;//地址16位
 	uint16_t val;//值
 	uint16_t i,crc,j;
 	i=0;
   Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];  //得到要修改的地址 
 	val=modbus.rcbuf[4]*256+modbus.rcbuf[5];     //修改后的值(要写入的数据)
 	Reg[Regadd]=val;  //修改本设备相应的寄存器
 	
 	//以下为回应主机
 	modbus.sendbuf[i++]=modbus.myadd;//本设备地址
   modbus.sendbuf[i++]=0x06;        //功能码 
   modbus.sendbuf[i++]=Regadd/256;//写入的地址
 	modbus.sendbuf[i++]=Regadd%256;
 	modbus.sendbuf[i++]=val/256;//写入的数值
 	modbus.sendbuf[i++]=val%256;
 	crc=Modbus_CRC16(modbus.sendbuf,i);//获取crc校验位
 	modbus.sendbuf[i++]=crc/256;  //crc校验位加入包中
 	modbus.sendbuf[i++]=crc%256;
 	//数据发送包打包完毕
 	RS485DIR_TX;;//使能485控制端(启动发送)  
 	for(j=0;j<i;j++)
 	{
 	 Modbus_Send_Byte(modbus.sendbuf[j]);
 	}
 	RS485DIR_RX;//失能485控制端(改为接收)
 }

  //这是往多个寄存器器中写入数据
 //功能码0x10指令即十进制16
 void Modbus_Func16()
 {
 		uint16_t Regadd;//地址16位
 		uint16_t Reglen;
 		uint16_t i,crc,j;
 		
 		Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3];  //要修改内容的起始地址
 		Reglen = modbus.rcbuf[4]*256+modbus.rcbuf[5];//读取的寄存器个数
 		for(i=0;i<Reglen;i++)//往寄存器中写入数据
 		{
 			//接收数组的第七位开始是数据
 			Reg[Regadd+i]=modbus.rcbuf[7+i*2]*256+modbus.rcbuf[8+i*2];//对寄存器一次写入数据
 		}
 		//写入数据完毕,接下来需要进行打包回复数据了
 		
 		//以下为回应主机内容
 		//内容=接收数组的前6位+两位的校验位
 		modbus.sendbuf[0]=modbus.rcbuf[0];//本设备地址
 		modbus.sendbuf[1]=modbus.rcbuf[1];  //功能码 
 		modbus.sendbuf[2]=modbus.rcbuf[2];//写入的地址
 		modbus.sendbuf[3]=modbus.rcbuf[3];
 		modbus.sendbuf[4]=modbus.rcbuf[4];
 		modbus.sendbuf[5]=modbus.rcbuf[5];
 		crc=Modbus_CRC16(modbus.sendbuf,6);//获取crc校验位
 		modbus.sendbuf[6]=crc/256;  //crc校验位加入包中
 		modbus.sendbuf[7]=crc%256;
 		//数据发送包打包完毕
 		
 		RS485DIR_TX;;//使能485控制端(启动发送)  
 		for(j=0;j<8;j++)
 		{
 			Modbus_Send_Byte(modbus.sendbuf[j]);
 		}
 		RS485DIR_RX;//失能485控制端(改为接收)
 }

  void Modbus_Send_Byte(  uint8_t ch )
 {
 	/* 发送一个字节数据到USART2 */
   HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xff);	
	 //while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
 }
 
 int Modbus_CRC16(uint8_t buff[],int len)
{
    unsigned short tmp = 0xffff;
    unsigned short ret1 = 0;

    for(int n = 0; n < len; n++)  //此处的6 -- 要校验的位数为6个
     {
        tmp = buff[n] ^ tmp;
        for(int i = 0;i < 8;i++)  //此处的8 -- 指每一个char类型又8bit,每bit都要处理
        {
            if(tmp & 0x01)
             {
                tmp = tmp >> 1;
                tmp = tmp ^ 0xA001;
             }
            else
             {
                tmp = tmp >> 1;
             }
        }
    }

    ret1 = tmp >> 8;   //将CRC校验的高低位对换位置
    ret1 = ret1 | (tmp << 8);

    return ret1;
}

目前只写了03(读寄存器)、06(写一个寄存器)、16(写多个寄存器)三个功能
stm32_h7xx_it.c

extern MODBUS modbus;

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
 	if( modbus.reflag==1)  //有数据包正在处理
 	  {
 		   return ;
 		}		
 	  modbus.rcbuf[modbus.recount++] = USART1_aRxBuffer[0];
 		modbus.timout = 0;
 		if(modbus.recount == 1)  //已经收到了第二个字符数据
 		{
 		  modbus.timrun = 1;  //开启modbus定时器计时
 		}
 
 	HAL_UART_Receive_IT(&huart1, (uint8_t *)USART1_aRxBuffer,1);  //添加的一行代码
  /* USER CODE END USART1_IRQn 1 */
  /* USER CODE END USART1_IRQn 1 */
}

/******************************************************************************
 * @ 函数名  : HAL_TIM_PeriodElapsedCallback
 * @ 功  能  : 定时器超时中断回调函数
 * @ 参  数  : htim 定时器名 
 * @ 返回值  : 无
 ******************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	 if (htim->Instance == TIM3) 
 	{
 		if(modbus.timrun != 0)//运行时间!=0表明
 		 {
 		  modbus.timout++;
 		  if(modbus.timout >=35)//大于1750us
 		  {
 		   modbus.timrun = 0;
 			 modbus.reflag = 1;//接收数据完毕
				modbus.timout = 0;
				USART1_RX_STA|=0x8000;
 		  }
 		}
 		modbus.Host_Sendtime++;//发送完上一帧后的时间计数
 		 if(modbus.Host_Sendtime>1000)//距离发送上一帧数据1s了
 			{
 				//1s时间到
 				modbus.Host_time_flag=1;//发送数据标志位置1
 				
 			}
 	}
}

上面写了RS485串口中断处理函数和定时器中断处理函数
main.c

HAL_TIM_Base_Start_IT(&htim3);  //启动定时器TIM3
	Modbus_Init();//本机作为从机使用时初始化
	RS485DIR_RX;//拉低PB5,更改RS485模式为接收

 while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	Modbus_Event();//本机作为从机使用时
  }
  /* USER CODE END 3 */
}

主函数只需要写一个初始化,在循环中调用Modbus_Event()函数循环查询就可以了。

测试结果

XCOM串口调试助手作为主机测试

在这里插入图片描述
主机发送解析:
01 03 00 00 00 04 44 09

01:从机地址
03:功能码,读寄存器
00 00:读的起始地址
00 04:要读的数据的个数
44 09:校验码

从机返回解析:
==01 03 08 00 01 00 12 00 13 00 04 CD 12 ==

01:从机地址
03:功能码,读寄存器
08:数据的位数,8个字节
00 01 00 12 00 13 00 04:接收到四个数据:0001001200130004
CD 12:校验码

校验方式是CRC16
我们可以计算出来,从机返回的数据是没有问题的,然后我们后面使用modbus poll作为主机进行测试:

modbus poll作为主机测试

1.打开modbus poll,Setup->read/write defination:
在这里插入图片描述
2.Connection->connect
在这里插入图片描述
3.查看结果
在这里插入图片描述
开始显示的不是16进制,可以选中这些数据 Display->Hex - Ascall,这样就是16进制显示了,可以看到读取了我们程序中写的数据。
在这里插入图片描述
点击这个可以查看发送和返回的数据包
在这里插入图片描述
可以看到和我们刚才串口调试的是一样的结果。

总结

本次实验,我们在RS485通信的基础上实现了modbus-RTU协议,当然只写了03、06、16功能码,测试都是没有问题的,图方便我只放上了03的测试,你们可以把这个都测试一遍,甚至可以把他的功能写全面。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JSP--Java的服务器页面

jsp是什么&#xff1f; jsp的全称是Java server pages,翻译过来就是java的服务器页面。 jsp有什么作用&#xff1f; jsp的主要作用是代替Servlet程序回传html页面的数据&#xff0c;因为Servlet程序回传html页面数据是一件非常繁琐的事情&#xff0c;开发成本和维护成本都非常高…

zabbix5.0安装配置和日常管理使用笔记

服务端配置&#xff1a; zabbix官网&#xff08;有软件下载和安装步骤说明&#xff09; https://www.zabbix.com/cn/download 关selinux和防火墙 iptables -L 查看全部清空 建议给4G内存空间 获取zabbix下载镜像源 rpm -Uvh https://mirrors.aliyun.com/zabbix/zabbix/5.0/…

Slurm--资源管理系统

Slurm–资源管理系统 开源软件 SLURM 全称 Simple Linux Utility for Resource Management开源分布式资源管理软件可用于大型计算节点集群的高度可伸缩的集群管理器和作业调度系统 提供高效的资源与作业管理 状态监控资源管理作业调度 是用户使用计算资源的接口 作业提交 / 运…

【Vue3+Ts+Vite】配置页面切换过渡动画

文章目录 一、先看效果二、全量代码三、注意事项虽然Vue3支持 template 下存在多个根节点&#xff0c;但是 transition 过渡动画并不支持&#xff0c;要实现过渡动画的页面&#xff0c;都需要有一个根标签包裹页面内容&#xff0c;否则就会报如下警告: 四、相关文章友链本专栏记…

Vue 组件和计算属性(二)

一、组件 1.1 什么是组件 组件是可复用的 Vue 实例&#xff0c;说白了就是一组可以重复使用的模板&#xff0c;跟 JSTL 的自定义标签、Thymeleaf 的 th:fragment 等框架有着异曲同工之妙。通常一个应用会以一棵嵌套的组件树的形式来组织。 例如&#xff0c;你可能会有页头、侧…

vue + element UI Table 表格 利用插槽是 最后一行 操作 的边框线 不显示

在屏幕比例100%时 el-table添加border属性 使用作用域插槽 会不显示某侧的边框线&#xff0c;屏幕比例缩小或放大都展示 // 修复列的 边框线消失的bug thead th:not(.is-hidden):last-child {right:-1px;// 或者//border-left: 1px solid #ebeef5; } .el-table__row{td:not(.i…

Docker 容器转为镜像

# 容器转成镜像并指定镜像名称与版本号 # commit 时原有容器挂载的目录是不会被写入到新的镜像中去的&#xff0c;数据卷相关的都不会生效 # 但是 root 目录下新建的内容会写入到新的镜像中去 $ docker commit 容器ID 新镜像名称:版本号 $ docker commit -m"描述信息"…

2023年电赛---运动目标控制与自动追踪系统(E题)OpenMV方案

前言 &#xff08;1&#xff09;废话少说&#xff0c;很多人可能无法访问GitHub&#xff0c;所以我直接贴出可能要用的代码。此博客还会进行更新&#xff0c;先贴教程和代码 &#xff08;2&#xff09;视频教程&#xff1a; https://singtown.com/learn/49603/ &#xff08;3&a…

大数据技术之Clickhouse---入门篇---数据类型、表引擎

星光下的赶路人star的个人主页 今天没有开始的事&#xff0c;明天绝对不会完成 文章目录 1、数据类型1.1 整型1.2 浮点型1.3 布尔型1.4 Decimal型1.5 字符串1.6 枚举类型1.7 时间类型1.8 数组 2、表引擎2.1 表引擎的使用2.2 TinyLog2.3 Memory2.4 MergeTree2.4.1 Partition by分…

华为OD机试真题 JavaScript 实现【取出尽量少的球】【2023Q1 200分】,附详细解题思路

目录 一、题目描述游戏规则如下&#xff1a;限制规则一&#xff1a;限制规则二&#xff1a; 二、输入描述三、输出描述四、解题思路五、JavaScript算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 刷的越多&#xff0c;抽中…

《golang设计模式》第一部分·创建型模式-04-抽象工厂模式(Abstract Factory)

文章目录 1. 概述1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 1.1 角色 AbstractFactory&#xff08;抽象工厂&#xff09;&#xff1a;它声明了一组用于创建产品的方法&#xff0c;每一个方法对应一种产品。ConcreteFactory&#xff08;具体工厂&#xf…

phpstudy 进行 composer 全局配置

背景 因为注意到&#xff0c;使用 phpStudy 进行环境搭建时&#xff0c;有时需要使用 composer 每次都需要查找资料进行配置&#xff0c; 在此进行记录笔记&#xff0c;方便有需要的道友借鉴 配置 版本&#xff1a;composer1.8.5&#xff08;phpStudy8 当前只能安装这一个版本…

CAD批量转PDF的简单方法,三个步骤轻松完成转换

PDF格式的图纸可以在各种设备和软件上打开&#xff0c;因为PDF是一种跨平台的格式&#xff0c;不受操作系统或软件版本的影响。这意味着CAD图纸可以更容易地在不同的设备和操作系统之间传输&#xff0c;而无需担心兼容性问题&#xff0c;可以使图纸更易于共享、浏览和保护&…

Vue进阶(幺叁陆): transition标签实现页面跳转动画

文章目录 一、前言二、方案实现三、延伸阅读 transition标签四、拓展阅读 一、前言 在Vue项目开发过程中&#xff0c;应用全家桶vue-router实现路由跳转&#xff0c;且页面前进、后退跳转过程中&#xff0c;分别对应不同的切换动画。vue-router 切换页面时怎么设置过渡动画&am…

Pytorch深度学习-----神经网络之非线性激活的使用(ReLu、Sigmoid)

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

Doccano工具安装教程/文本标注工具/文本标注自己的项目/NLP分词器工具/自然语言处理必备工具/如何使用文本标注工具

这篇文章是专门的安装教程&#xff0c;后续的项目创建&#xff0c;如何使用&#xff0c;以及代码部分可以参考这篇文章&#xff1a; NER实战&#xff1a;(NLP实战/命名实体识别/文本标注/Doccano工具使用/关键信息抽取/Token分类/源码解读/代码逐行解读)_会害羞的杨卓越的博客-…

【uniapp 样式】使用setStorageSync存储历史搜索记录

<template><view><view class"zhuangbox u-flex"><u--inputplaceholder"请输入关键字搜索"border"surround"shapecircleprefixIcon"search"prefixIconStyle"font-size: 22px;color: #909399"v-model&q…

vSphere ESXI 7.0 网络规划

ESXi 网络 业务网络、Vmotion&#xff08;漂移&#xff09;、管理网络、存储网络 ESXi 管理网络 vCenter Server 管理网络 vCenter Server SSO域名 Single Sign-on域名&#xff1a;在没有指定的情况下&#xff0c;默认填写 vsphere.local VMware vSphere整体解决方案和网络…

uniapp点击图片放大预览

阐述 有些时候我们在用uniapp显示图片时&#xff0c;有的不宜全部显示到屏幕上&#xff0c;uniapp提供了一个非常好用的api。 实现方式如下&#xff1a; <template><view class"content"><image class"logo" src"/static/images/a.…

Nacos 持久化实例 删不掉问题( Please unregister instance first )

文章目录 一、报错现象&#xff1a;二、问题解决过程&#xff1a;三、最终解决方案&#xff1a;四、注销实例API&#xff1a; &#x1f50e;请直接看第三部分&#x1f50e; 一、报错现象&#xff1a; 二、问题解决过程&#xff1a; 尝试将服务下线&#xff1a; 再次点击删除按…