学习记录:ESP32控制舵机 FREERTOS BLE
控制舵机
PWM信号
PWM信号是一种周期性变化的方波信号,它有两个关键参数:
- 周期(Period):一个完整的PWM信号的时间长度,通常用秒(s)或毫秒(ms)表示。
- 占空比(Duty Cycle):高电平(ON状态)持续时间与周期的比值,通常以百分比表示。占空比决定了信号的平均功率。
脉宽
脉宽(Pulse Width)是指PWM信号中高电平的持续时间。它与占空比密切相关,脉宽越长,占空比越大;脉宽越短,占空比越小。
PWM信号的工作原理
PWM信号通过快速切换开关,在一个固定周期内改变脉冲的宽度,以达到控制功率输出的目的。比如在电机控制中,通过调整PWM信号的占空比,可以控制电机的转速。占空比越高,电机转速越快;占空比越低,电机转速越慢。
例子说明
假设我们有一个周期为10毫秒的PWM信号:
-
占空比为50%:
- 脉宽:5毫秒
- 效果:在10毫秒的周期中,5毫秒为高电平,5毫秒为低电平。
-
占空比为25%:
- 脉宽:2.5毫秒
- 效果:在10毫秒的周期中,2.5毫秒为高电平,7.5毫秒为低电平。
-
占空比为75%:
- 脉宽:7.5毫秒
- 效果:在10毫秒的周期中,7.5毫秒为高电平,2.5毫秒为低电平。
代码# include <ESP32Servo.h>
Servo servo1; // 定义servo对象
Servo servo2; // 定义servo对象
int minUs=500; //0度时的脉宽,单位us
int maxUs=2500; //180度时的脉宽,单位us
int servo1Pin=15;
int servo2Pin=16;
int pos=-1;
bool up=true;void setup() {ESP32PWM::allocateTimer(1);//使用指定的硬件定时器servo1.setPeriodHertz(50);//指定PWM的频率servo2.setPeriodHertz(50);//指定PWM的频率servo1.attach(servo1Pin,minUs,maxUs);servo2.attach(servo2Pin,minUs,maxUs);// servo1.write(pos);//转到指定的角度(0~180)// servo1.detach();//不需要的时候将引脚和ledc分离
}void loop() {if(pos == 181)up=false;else if(pos==-1)up=true;if(up)pos++;elsepos--;servo1.write(pos);servo2.write(180-pos);delay(15);
}
1
FREERTOS
void task1(void *pt){pinMode(47,OUTPUT);while(1){digitalWrite(47,!digitalRead(47));// delay(1000);vTaskDelay(1000);}
}void task2(void *pt){pinMode(42,OUTPUT);while(1){digitalWrite(42,!digitalRead(42));vTaskDelay(2000);}
}
void setup() {xTaskCreate(task1,"Blink,47,19",1024,NULL,1,NULL);xTaskCreate(task2,"Blink,42,39",1024,NULL,1,NULL);
}void loop() {}
1. 任务堆栈大小的单位
在 FreeRTOS 中,任务堆栈大小的单位通常是字(word),而不是字节(byte)。在大多数微控制器平台上,一个字是 4 字节(32 位)。因此,当你指定堆栈大小时,实际上是指定了多少个 4 字节的块。
示例:
如果你指定 configMINIMAL_STACK_SIZE
为 128,则实际上分配的堆栈空间是 128 * 4 = 512 字节。
2. 优先级的作用
任务优先级决定了任务的执行顺序和调度策略。在 FreeRTOS 中,数值越高的优先级表示任务的重要性越高。任务调度器会优先执行优先级高的任务。当多个任务具有相同优先级时,它们将以时间片轮转的方式执行。
示例:
-
任务 A 优先级为 2
-
任务 B 优先级为 1
在这种情况下,任务 A 会优先于任务 B 执行。如果任务 A 阻塞或进入等待状态,任务 B 才会执行。
3.任务句柄的使用
任务句柄(Task Handle)用于在运行时引用特定任务。你可以使用任务句柄来挂起、恢复或删除任务。
l示例:
c
复制代码
#include <FreeRTOS.h>#include <task.h>#include <stdio.h>// 任务函数声明void vTaskFunction(void *pvParameters);// 全局变量,用于存储任务句柄
TaskHandle_t taskHandle = NULL;int main(void)
{xTaskCreate(vTaskFunction, // 任务函数"Task 1", // 任务名称configMINIMAL_STACK_SIZE, // 任务堆栈大小NULL, // 任务参数tskIDLE_PRIORITY + 1, // 任务优先级&taskHandle // 任务句柄);
// 启动调度器vTaskStartScheduler();
// 正常情况下,不会运行到这里for(;;);
}void vTaskFunction(void *pvParameters)
{for(;;){printf("Task is running\n");vTaskDelay(1000 / portTICK_PERIOD_MS);
// 示例:挂起任务vTaskSuspend(taskHandle);
// 示例:恢复任务vTaskResume(taskHandle);}
}
蓝牙BLE
-
服务器(Server)
服务器是 BLE 外设设备的一部分,负责管理多个服务并处理与中央设备的连接和通信。服务器本身不直接包含数据,而是通过服务和特性来组织和传输数据。
-
角色:在 BLE 通信中,服务器(通常是传感器设备)提供数据和功能,而中央设备(通常是手机或电脑)请求和使用这些数据和功能。
-
服务(Service)
服务是服务器中用于组织和描述特定功能的一组相关特性。每个服务都有一个唯一的标识符(UUID),用来标识和区分不同的服务。
-
角色:服务是功能的逻辑集合。例如,一个心率监测设备可能有一个“心率服务”,包含心率测量、心率控制点等特性。
-
特性(Characteristic)
特性是服务中的基本数据单元,包含实际的数据和相关属性。每个特性也有一个唯一的标识符(UUID),用于标识特性。特性可以包含一个值和零个或多个描述符。
-
角色:特性是传输数据的具体单元。例如,“心率服务”中的“心率测量”特性可能包含当前的心率值。
它们之间的关系
-
服务器包含多个服务。
-
每个服务包含多个特性。
-
每个特性包含一个值和零个或多个描述符。
图示关系
scss
复制代码
服务器 (Server)├── 服务1 (Service 1, UUID: 0000180f-0000-1000-8000-00805f9b34fb)│ ├── 特性1 (Characteristic 1, UUID: 00002a19-0000-1000-8000-00805f9b34fb)│ │ └── 描述符1 (Descriptor 1)│ └── 特性2 (Characteristic 2, UUID: 00002a1a-0000-1000-8000-00805f9b34fb)└── 服务2 (Service 2, UUID: 0000180a-0000-1000-8000-00805f9b34fb)├── 特性1 (Characteristic 1, UUID: 00002a29-0000-1000-8000-00805f9b34fb)└── 特性2 (Characteristic 2, UUID: 00002a2a-0000-1000-8000-00805f9b34fb)
示例代码中的对应关系
-
服务器:在代码中,通过
BLEDevice::createServer()
创建。 -
服务:在代码中,通过
pServer->createService(SERVICE_UUID)
创建。 -
特性:在代码中,通过
pService->createCharacteristic(CHARACTERISTIC_UUID, ...)
创建。
问题 1:pServer->setCallbacks(new MyServerCallbacks());
这一行代码的作用是什么?
解答:
这行代码的作用是为 BLE 服务器设置回调函数。具体来说,它将一个自定义的回调类 MyServerCallbacks
注册到 BLE 服务器 pServer
上。当设备连接或断开连接时,BLE 库会自动调用 MyServerCallbacks
类中的 onConnect
和 onDisconnect
方法,以便我们在这些事件发生时执行特定的操作。
问题 2:在什么情况下会调用服务器回调类?又是谁调用?
解答:
服务器回调类中的方法会在以下情况下被调用:
-
设备连接:当一个中央设备(如手机)连接到 ESP32 BLE 服务器时,BLE 库会调用
onConnect
方法。 -
设备断开连接:当中央设备断开连接时,BLE 库会调用
onDisconnect
方法。
这些方法是由 BLE 库自动调用的。当 BLE 库检测到连接或断开连接事件时,会触发相应的回调函数。
问题 3:pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE);
这段代码的作用是什么?|
符号是按位或吗?
解答:
这段代码的作用是创建一个新的 BLE 特性,并将其添加到服务中。具体步骤如下:
-
pService->createCharacteristic(...)
:这是一个方法调用,用于在服务中创建一个新的 BLE 特性。 -
CHARACTERISTIC_UUID
:这是特性的唯一标识符,用于区分不同的特性。 -
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
:这是特性的属性,指定该特性支持读取和写入操作。
|
是按位或(bitwise OR)运算符,用于将多个标志(flags)组合在一起。在这个上下文中,它用于同时设置特性的读取和写入属性。
问题 4:BLECharacteristic
类是从哪里来的?
解答:
BLECharacteristic
类定义在我们引入的头文件中,如 BLEDevice.h
和 BLEServer.h
。这些头文件包含了所有与 BLE 相关的类和函数。在代码的顶部,我们通过以下方式包含了这些头文件:
完整示例代码
#include <BLEDevice.h> // 包含 BLE 设备库
#include <BLEServer.h> // 包含 BLE 服务器库
#include <BLEUtils.h> // 包含 BLE 工具库
#include <BLE2902.h> // 包含 BLE 描述符库BLEServer *pServer = NULL; // 声明服务器指针
BLECharacteristic *pCharacteristic = NULL; // 声明 BLE 特性指针
bool deviceConnected = false; // 设备是否连接#define SERVICE_UUID "0000180f-0000-1000-8000-00805f9b34fb" // 服务的 UUID
#define CHARACTERISTIC_UUID "00002a19-0000-1000-8000-00805f9b34fb" // 特性的 UUID// 回调类,处理连接和断开事件
class MyServerCallbacks: public BLEServerCallbacks {void onConnect(BLEServer *pServer) {deviceConnected = true; // 设置设备连接标志Serial.println("客户端连接"); // 打印连接消息}void onDisconnect(BLEServer *pServer) {deviceConnected = false; // 重置设备连接标志Serial.println("客户端断开"); // 打印断开连接消息}
};// 回调类,处理特性写操作
class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {void onWrite(BLECharacteristic *pCharacteristic) {string value = pCharacteristic->getValue();Serial.print("Received Value: ");for (int i = 0; i < value.length(); i++) {Serial.print(value[i]);}Serial.println();}
};void setup() {Serial.begin(115200); // 初始化串口通信Serial.println("Starting BLE work!"); // 打印初始化消息BLEDevice::init("ESP32_BLE"); // 初始化 BLE 设备,并命名// 创建服务器pServer = BLEDevice::createServer();// 设置服务器回调pServer->setCallbacks(new MyServerCallbacks());// 创建服务BLEService *pService = pServer->createService(SERVICE_UUID);// 创建特性,支持读、写和通知属性pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID,BLECharacteristic::PROPERTY_READ |BLECharacteristic::PROPERTY_WRITE |BLECharacteristic::PROPERTY_NOTIFY);// 添加描述符,使能通知pCharacteristic->addDescriptor(new BLE2902());// 设置特性回调pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());// 启动服务pService->start();// 开始广播pServer->getAdvertising()->start();Serial.println("Waiting for a client connection to notify...");
}void loop() {if (deviceConnected) {// 更新特性值pCharacteristic->setValue("Hello World");// 通知客户端pCharacteristic->notify();// 通知间隔delay(1000);}
}
蓝牙控制舵机
#include <ESP32Servo.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>Servo servo1; // 定义 servo 对象
Servo servo2; // 定义 servo 对象
int minUs = 500; // 0度时的脉宽,单位us
int maxUs = 2500; // 180度时的脉宽,单位us
int servo1Pin = 37;
int servo2Pin = 36;
int pos = -1;
bool up = true;BLEServer *pServer = NULL; // 声明服务器指针
BLECharacteristic *pCharacteristic = NULL; // 声明 BLE 特性指针
bool deviceConnected = false; // 设备是否连接#define SERVICE_UUID "0000180f-0000-1000-8000-00805f9b34fb" // 服务的 UUID
#define CHARACTERISTIC_UUID "00002a19-0000-1000-8000-00805f9b34fb" // 特性的 UUIDvoid servo(unsigned char num) {while (num > 0) {for (int i = 0; i <= 360; i++) {if (pos == 181)up = false;else if (pos == -1)up = true;if (up)pos++;elsepos--;servo1.write(pos);servo2.write(180 - pos);delay(3);}num--;}Serial.println("完成一次摆动");
}// 回调类,处理连接和断开事件
class MyServerCallbacks: public BLEServerCallbacks {void onConnect(BLEServer *pServer) {deviceConnected = true; // 设置设备连接标志Serial.println("客户端连接"); // 打印连接消息}void onDisconnect(BLEServer *pServer) {deviceConnected = false; // 重置设备连接标志Serial.println("客户端断开"); // 打印断开连接消息}
};class MyCharacteristicCallbacks: public BLECharacteristicCallbacks {void onWrite(BLECharacteristic *pCharacteristic) {String value = pCharacteristic->getValue();Serial.print("Received Value: ");Serial.println(value); // 打印接收到的值if (value == "1") {servo(2);}}
};void setup() {Serial.begin(115200);ESP32PWM::allocateTimer(1); // 使用指定的硬件定时器servo1.setPeriodHertz(50); // 指定 PWM 的频率servo2.setPeriodHertz(50); // 指定 PWM 的频率servo1.attach(servo1Pin, minUs, maxUs);servo2.attach(servo2Pin, minUs, maxUs);// Serial.begin(115200); // 已在 setup 中初始化串口,可注释掉Serial.println("Starting BLE work!"); // 打印初始化消息BLEDevice::init("ESP32_BLE"); // 初始化 BLE 设备,并命名// 创建服务器pServer = BLEDevice::createServer();// 设置服务器回调pServer->setCallbacks(new MyServerCallbacks());// 创建服务BLEService *pService = pServer->createService(SERVICE_UUID);// 创建特性,支持读、写和通知属性pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID,BLECharacteristic::PROPERTY_READ |BLECharacteristic::PROPERTY_WRITE |BLECharacteristic::PROPERTY_NOTIFY);// 添加描述符,使能通知pCharacteristic->addDescriptor(new BLE2902());// 设置特性回调pCharacteristic->setCallbacks(new MyCharacteristicCallbacks());// 启动服务pService->start();// 开始广播pServer->getAdvertising()->start();Serial.println("Waiting for a client connection to notify...");
}void loop() {// 你的主循环代码
}
相关文章:

学习记录:ESP32控制舵机 FREERTOS BLE
控制舵机 PWM信号 PWM信号是一种周期性变化的方波信号,它有两个关键参数: 周期(Period):一个完整的PWM信号的时间长度,通常用秒(s)或毫秒(ms)表示。占空比…...

react中的useState和Hook、副作用
react的组件分为类组件和函数组件,Hook 是一种特殊的函数,可以让你在函数组件中使用类组件中才有的一些特性。useState、useEffect、useReducer都是Hook。其中useState用于在函数组件中添加状态,useEffect用于在函数组件中执行副作用…...

Linux嵌入式学习——数据结构——线性表的链式结构
线性表的链式存储 解决顺序存储的缺点,插入和删除,动态存储问题。 特点: 线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素,存储单元可以是连续的,也可以不连续。可以被存储在任意内存未被占…...

文本编辑 文本中的各种空格
参考资料 欧文の半角スペースは2種類ある!?无中断空格常见空格一览浅析什么是零宽度字符以及零宽度字符在实际中的应用场景空格象形字间隔无中断空格零宽间隔 目录 零. 各种空格在Notepad中的效果一. 半角空格二. 全角空格三. TAB空格四. 无中断空格4.1 定义4.2 H…...

Vue插槽 (Slots)详解
目录 前言基础插槽具名插槽作用域插槽默认插槽动态插槽名总结相关阅读 前言 Vue的插槽(Slots)是一个非常强大的特性,它允许你在组件的模板中嵌入父组件的内容。插槽使得组件之间的内容分发变得灵活,尤其在构建可复用组件时非常…...

Unity中有关Animation的一点笔记
也许更好的阅读体验 Animation Unity中Animation类并不是直接记载了和播放动画有关的信息,可以简单理解Animation为一个动画播放器,播放的具体内容就像卡带一样,当我们有了卡带后我们可以播放动画。 对应的则是编辑器中的组件 所以Anima…...

module federation模块联邦与微前端
module federation是什么 webpack5新增了module federation,module federation的作用,将每个构建(build)作为容器(这是一个概念),构建后的资源可以正常部署,同时还具备在运行时对外暴露其中的模块,这就意味着多个构建…...

日常开发记录分享——C#控件ToolTip实现分栏显示内容
文章目录 需求来源实现思路实施请看VCR等等别走,有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息,一开始使用的tooltip实现,但是里面的内容效果并不理想,需要有条理性,于是就想到能不能将展示的东西分列…...

Kettle下载安装
环境说明 虚拟机:Win7;MySql8.0 主机:Win11;JDK1.8;Kettle 9.4(Pentaho Data Integration 9.4)(下载方式见文末) 安装说明 【1】解压后运行Spoon.bat 【2】将jar包 复…...

最新版Golang pprof使用(引入、抓取、分析,图文结合)
最新版Golang pprof使用 🔥具体实践: Go调试神器pprof使用教程Golang线上内存爆掉问题排查(pprof) Github地址:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-pprof 引入pprof:import _ “net/http/pprof” …...

vue3学习记录1:emit的写法
emit是用于child组件向parent组件通信的工具,因为vue3的script可以设置为setup,写法同vue2有较大区别。 一、script setup - 直接写 <script lang"ts" setup>const emit defineEmits([close]);function handleClose() {emit(close);}…...

Visual Studio Code + vue快速安装配置Node.js+Vue+webpack+vscode
第一部分:Node.js 第一步:下载Node.js 方法1:链接 下载 | Node.js 中文网 (nodejs.cn) 方法2:百度网盘 链接:https://pan.baidu.com/s/1zIqu8H9rb_I1i-1OWD7swQ?pwdaurk 提取码:aurk --来自百度网盘…...

【Dart 教程系列第 49 篇】什么是策略设计模式?如何在 Dart 中使用策略设计模式
这是【Dart 教程系列第 49 篇】,如果觉得有用的话,欢迎关注专栏。 博文当前所用 Flutter SDK:3.22.1、Dart SDK:3.4.1 文章目录 一:什么是策略设计模式?二:为什么要使用策略设计模式࿱…...

BGP路由反射器
原理概述 缺省情况下,路由器从它的一个 IBGP对等体那里接收到的路由条目不会被该路由器再传递给其他IBGP对等体,这个原则称为BGP水平分割原则,该原则的根本作用是防止 AS内部的BGP路由环路。因此,在AS内部,一般需要每台…...

DolphinDB Web 端权限管理:可视化操作指南
在现代数据库管理中,高效和直观的权限管理对于用户的数据安全是至关重要的。过去 DolphinDB 用户需要依赖系统脚本来管理用户和权限,这对于缺乏技术背景的管理员来说既复杂又容易出错。 为了提升用户体验和操作效率,DolphinDB 目前在 Web 上…...

学习Vue2收藏这一篇就够了(如何创建Vue实例)
什么是Vue? Vue是什么:是一个用于构建用户界面的渐进式框架 什么是构建用户界面:基于数据动态渲染页面 什么是渐进式:循序渐进的学习 什么是框架:一整套完整的项目解决方案 创建Vue实例 核心步骤(4步…...

Mysql数据库第四次作业
mysql> create table student(sno int primary key auto_increment,sname varchar(30) not null unique,Ssex varchar(2) check (Ssex男 or Ssex女) not null,Sage int not null,Sdept varchar(10) default计算机 not null); mysql> create table Course(Con int primar…...

使用Docker搭建MySql的主从同步+ShardingSphere搭建Mysql的读写分离
参考课程 尚硅谷ShardingSphere5实战教程(快速入门掌握核心)_哔哩哔哩_bilibili 主服务器 创建容器 docker run -d \ -p 3306:3306 \ -v /kira/mysql/master/conf:/etc/mysql/conf.d \ -v /kira/mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT…...

数据结构:数据类型与抽象数据类型
数据类型与抽象数据类型 数据类型基本数据类型构造数据类型指针类型枚举类型 抽象数据类型(ADT)抽象数据类型的组成部分常见的抽象数据类型示例 数据类型与抽象数据类型的区别实现抽象数据类型的具体方式用数组实现栈用链表实现栈 总结 数据类型 数据类…...

西方逻辑史简介
西方逻辑史研究,对形式逻辑实现现代化,对加强西方哲学史研究,对开展科学方法论的研究都有重要意义。西方逻辑史一般被划分成古代、中世纪、现代三个历史时期。本文拟对这三个时期中的七个重要逻辑学家和逻辑学派:亚里士多德、斯多…...

【论文10】复现代码tips
一、准备工作 1.创建一个虚拟环境 conda create --name drgcnn38 python=3.8.18 2.激活虚拟环境 conda activate drgcnn38 注意事项 在Pycharm中终端(terminal)显示PS而不是虚拟环境base 问题如下所示 解决方法:shell路径改成cmd.exe 重启终端显示虚拟环境 3.安装torch …...

分布式缓存获取以及设置
1. 通用代码 public SysUser getCache(String sysUserId) {String cacheKey "litgery:warehouse:" sysUserId;// 尝试从缓存中获取数据CacheData cacheData redisUtils.get(cacheKey);if (null ! cacheData) {if (Boolean.TRUE.equals(cacheData.getExist())) {re…...

SMO算法,platt论文的原始算法及优化算法
platt论文:[PDF] Sequential Minimal Optimization : A Fast Algorithm for Training Support Vector Machines | Semantic Scholar 算法优化:[PDF] Improvements to Platts SMO Algorithm for SVM Classifier Design | Semantic Scholar 包含个人plat…...

2.3 openCv -- 对矩阵执行掩码操作
在矩阵上进行掩模操作相当简单。其基本思想是根据一个掩模矩阵(也称为核)来重新计算图像中每个像素的值。这个掩模矩阵包含的值决定了邻近像素(以及当前像素本身)对新的像素值产生多少影响。从数学角度来看,我们使用指定的值来做一个加权平均。 具体而言,掩模操作通常涉…...

【Django】 js实现动态赋值、显示show隐藏hide效果
文章目录 需要达到的前端效果预览:实现步骤复制bootstrp代码(buttons)复制bootstrp代码(Alert警告框)写js测试效果 需要达到的前端效果预览: {% load static %} <!DOCTYPE html> <html lang"…...

qt--做一个拷贝文件器
一、项目要求 使用线程完善文件拷贝器的操作 主窗口不能假死主窗口进度条必须能动改写文件大小的单位(自适应) 1TB1024GB 1GB1024MB 1MB1024KB 1KB1024字节 二、所需技术 1.QFileDialog 文件对话框 QFileDialog也继承了QDialog类,直接使用静态…...

Eclipse 搭建 C/C++ 开发环境以及eclipse的使用
一、下载、安装 MinGW 1、下载: 下载地址:MinGW - Minimalist GNU for Windows - Browse Files at SourceForge.net 点击“Download Latest Version”即可 下载完成后,得到一个名为 mingw-get-setup.exe 的安装文件。双击运行,安装即可。 …...

【初阶数据结构】复杂度算法题篇
旋转数组 力扣原题 方案一 循环K次将数组所有元素向后移动⼀位(代码不通过) 时间复杂度O(n2) 空间复杂度O(1) void rotate(int* nums, int numsSize, int k) {while (k--) {int end nums[numsSize - 1];for (int i numsSize - 1; i > 0; i--) {nums[i] num…...

20240725项目的maven环境报红-重新配置maven
1.在编辑器里面打开项目,导入源码 (1)找到项目的地址C:\Users\zzz\IdeaProjects\datasys,然后右击用idea编辑器打开。 (2)idea中上菜单栏打开open,然后输入file,选择源代码文件 2.…...

若依 ruoyi poi Excel合并行的导入
本文仅针对文字相关的合并做了处理 ,图片合并及保存需要另做处理!! 目标:Excel合并行内容的导入 结果: 1. ExcelUtil.java 类,新增方法:判断是否是合并行 /*** 新增 合并行相关代码:…...