控制舵机
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++;
else
pos--;
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" // 特性的 UUID
void 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++;
else
pos--;
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() {
// 你的主循环代码
}