
电动升降桌
简介
通过按键去控制桌面的高度,实现不同高度人群使用,而且当我们在桌前座的太久,觉得累时,可以将它升高站着办公,实现人性化。
简介:通过按键去控制桌面的高度,实现不同高度人群使用,而且当我们在桌前座的太久,觉得累时,可以将它升高站着办公,实现人性化。开源协议
:BSD License
描述
项目说明
分为结构部分:本次我是淘的闲鱼售卖二手的畅腾升降桌架CTHT3-F4200 双电机3节桌架,(官方答复:噪声在42分贝左右,最大承重120KG,高度调节范围在600-1250mm)。
主控部分:采用ESP32作为主控芯片,选择它的原因有以下几点:
1.足够多的IO口(我差不多需要12个,那么单凭这一点,使用ESP32是比较合适的)。
2.足够多的中断(因为我需要检测电机的霍尔,那么中断就尤为重要,而ESP32每个IO口都可以作为独立中断使用,非常方便)。
3.需要具备I2C引脚,(需要控制OLED)。
4.需要足够大的EEPROM用来保存断电高度信息。
5.具备WIFI功能,方便后期物联网规划
电机控制部分:本次采用了BTS7960(实测7970也是可以的)电机驱动芯片。他的最大特点就是可以输出最大27V43A的电流。对于驱动升降桌的电机就完全够用了,同时他还需要一个较大的散热片,来进行芯片的散热,这样 就导致体积大了起来, 但对于桌子下面的空间就完全够用了。
主电源部分:由于我拆开电机后发现,单个电机为18V,4.3A蜗杆电机,通过测试发现,电机的正常旋转电流在2A左右,考虑到承重问题因此我选用了输出15V10A的AD-DC电源模块。大概15块左右。
显示部分:采用了0.96寸OLED作为高度显示屏幕,用于显示时间,闹钟设置,高度等。
快充部分:采用了IP5306快充芯片,最大输出功率可达45W.
开源协议
开源协议说明
这是"伯克利软件发行版"许可,是一个给予使用者很大自由度的协议,基本上可以认为是能“为所欲为”的使用。
可以自由使用、修改源工程,也可以将原工程修改后的工程作为开源或者专有再发布,但是需要满足下面的条件:
a、如果再发布的产品中包含源工程,则在工程中必须带有原来的BSD协议;
b、如果再发布的只是二进制类库或软件,则需要在类库或软件的文档和版权声明中包含原来代码中的BSD协议;
c、不可以用开源的作者或机构名字以及原来产品的名字去做市场推广。
项目相关功能
提示:
- 升降最高高度:120CM,最低高度:60CM;
- 桌面面积:120cmX60cm;
- 最大承重:50KG,且高度不能回弹;
- 电机数量:1-2个;
- 屏幕显示:时间,当前桌子高度;
- USB快充,快充 协议不限;
- 断电记忆,能够保证断电重启后桌子高度一致;
- 桌子升降时的噪音协议低于:45DB一以下;
- 支持闹钟提醒;
项目属性
本项目为首次公开,为本人原创项目。项目未曾在别的比赛中获奖。
项目进度
7月1日:升降桌到货调试,确定好各个模块大小及安装位置,开始进行控制板外壳建模。
7月3日:手动焊接控制板,编写代码,测试升降桌性能是否满足要求。
7月10日:PCB原理图设计包含(电机驱动,控制部分,OLED屏幕显示,按键控制,限位开关设置,USB快充设置)断电记忆与闹钟放在最后的调试部分。
7月13日:PCB图设计,打样
7月13日:电子元器件采购
7月20日:PCB焊接调试。
注:我怕时间会有冲突,调试时间不够,电源模块我会综合考虑有可能要到后面做,请大家谅解。
设计原理
首先对硬件部分进行测量,与芯片选型,从而确定合适的PCB尺寸,以及芯片类型,保证满足设计要求。
通过拆解电机,每个电机均有两个霍尔,那么当电机的磁环转动时,由于磁环分S,N极,会使得两个霍尔检测到不同的值,例如当1号霍尔检测到低电平,2号霍尔检测到高电平,则判断为电机在顺时针旋转,同时也表示桌面在上升,当获霍尔检测到1号电机为高电平,2号电机为低电平,则判断电机在逆时针旋转,同时桌面在下降。因此我设置了5个按键,分别为上升,下降,闹钟小时定时设置,闹钟分钟定时设置,清除键。
当上升按键按下时,ESP32输出两路PWM控制,两个桌腿电机上升,同时由于电机旋转,导致霍尔传感器检测到转动,触发中断并将读取到的脉冲发送给ESP32并记录下来,当上升到所需要高度时,通过调用函数公式计算出当前高度,显示在OLED上并且保存到EEPROM防止掉电丢失,经过反复计算测量得出,桌子在最大高度1200mm时,输出的脉冲为2569个脉冲,也就是说当高度达到1200mm时电机停止上升。
当下降按键按下时,ESP32输出两路PWM信号控制,两个桌腿下降,当检测到高度大于等于0时,表示已经到底了,停止下降,相同的道理,当下降到最低高度时脉冲为0.
获取网络时间及闹钟:当桌子上电后,ESP32会自动连接家里的WIFI,并获取网络时间,显示在OLED上,可以通过去按下相应的闹钟定时按钮设置所需要的时间来定时,同时OLED上会有显示,所定时的时间,通过读取网络时间,并将时间与定时时间进行对比,如果相等则开启蜂鸣器。
EEPROM部分:当上升或下降按键按下后则会将当前高度存入EEPROM,当断电之后,则会保存在EEPROM中,当再次上电,则会直接读取上次保存的高度。并在在该高度进行上升和下降的高度运算,防止在断电后,当前高度值清零。
电源部分:通过使用正点原子的可调电源进行测量,得知,在正常运行状态下,(可调电源设置为12V5A)单个电机电流在2.5A左右,由于电机参数为18V4.3A,考虑到承重,和启动电流,因此本次选用AC-DC15V10A电源模块。
软件说明
我将详细的对程序进行解析,如果不理解大家可以去看视频(B站:赵公子的PCB)
本次项目所用到的库函数
#include
#include
#include
#include
#include
#include
#include
#include
#include
对ESP32的引脚定义:
const int ENC_A = 22;//A霍尔传感器
const int ENC_B = 23;//B霍尔传感器
const int ENC_C = 34;
const int ENC_D = 35;
const int RPWM = 18;//RPWM引脚
const int LPWM = 19;//LPWM引脚
const int RPWM1 = 33;
const int LPWM1 = 32;
const int BuzzerPin = 21;//蜂鸣器
const int Button1 = 17;//上升按键
const int Button2 = 16;//下降按键
const int Button3 = 25;//小时按键
const int Button4 = 26;//分钟按键
const int Button5 = 27;//取消按键
接下来是wifi部分:
#define WIFI_SSID "OnePlus 9 Pro"
#define WIFI_PASSWORD "12345678"
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
//通过 "pool.ntp.org"可以准确获取当前区域的时间,并显示在OLED上
主要变量:
int alarmHour = 0; //闹钟小时
int alarmMinute = 0; //闹钟分钟
bool alarmOn = false; //闹钟状态
volatile int contador = 0; //霍尔1输出的脉冲数量
volatile bool direction = false; //false为上升,true为下降
volatile int contador1 = 0; //霍尔2输出的脉冲数量
volatile bool direction1 = false; //false为上升,true为下降
const int MIN_HEIGHT = 0; // 最低高度
const int MAX_HEIGHT = 60;//最高高度
const int MAX_PULSES = 2569; //编码器脉冲最大值
int currentHeight = 0; //当前高度
int currentHeight1 = 0;
bool alarmEnabled = false; // 闹钟是否开启
bool Button1State = HIGH; //按键状态
bool Button2State = HIGH;
bool Button3State = HIGH;
bool Button4State = HIGH;
bool Button5State = HIGH;
一个比较垃圾的桌子图案:
static const uint8_t image_data_Saraarray[1024] = { //动画OLGO
0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x7F,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xF0,0x00,0x00,0x00,0x00,
0x00,0x03,0xFF,0xFC,0x00,0x00,0x00,0x00,0x00,0x0F,0xFF,0xFE,0x00,0x00,0x00,0x00,
0x00,0x3F,0xFF,0xFF,0x80,0x00,0x00,0x00,0x00,0x7F,0xFF,0xFF,0xE0,0x00,0x00,0x00,
0x01,0xFF,0xFF,0xFF,0xF8,0x00,0x00,0x00,0x07,0xFF,0xFF,0xFF,0xFE,0x00,0x00,0x00,
0x1F,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x3F,0xFF,0xFF,0xFF,0xFF,0xC0,0x00,0x00,
0x7F,0xFF,0xFF,0xFF,0xFF,0xF0,0x00,0x00,0x7F,0xFF,0xFF,0xFF,0xFF,0xF8,0x00,0x00,
0x0F,0xFF,0xFF,0xFF,0xFF,0xFE,0x00,0x00,0x03,0xFF,0xFF,0xFF,0xFF,0xFF,0x80,0x00,
0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x00,0x00,0x7F,0xFF,0xFF,0xFF,0xFF,0xF0,0x00,
0x00,0x1F,0xFF,0xFF,0xFF,0xFF,0xFC,0x00,0x00,0x07,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,
0x00,0x03,0xFF,0xFF,0xFF,0xFF,0xFF,0xC0,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,
0x00,0x00,0x3F,0xFF,0xFF,0xFF,0xFF,0xF8,0x00,0x00,0x4F,0xFF,0xFF,0xFF,0xFF,0xFE,
0x00,0x00,0x63,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x6C,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0x66,0x7F,0xFF,0xFF,0xFF,0xF8,0x00,0x00,0x67,0x3F,0xFF,0xFF,0xFF,0xF0,
0x00,0x00,0x67,0x0F,0xFF,0xFF,0xFF,0x80,0x00,0x00,0x67,0x03,0xFF,0xFF,0xFF,0x00,
0x00,0x00,0x67,0x01,0xFF,0xFF,0xFC,0x00,0x00,0x00,0x67,0x00,0x3F,0xFF,0xF0,0x00,
0x00,0x00,0x67,0x00,0x1F,0xFF,0xE0,0x00,0x00,0x00,0x67,0x00,0x07,0xFF,0x80,0x00,
0x00,0x00,0x67,0x00,0x01,0xFC,0x00,0x00,0x00,0x00,0x67,0x00,0x00,0x79,0x00,0x00,
0x00,0x00,0x67,0x78,0x00,0x57,0x00,0x00,0x00,0x00,0x67,0xFE,0x00,0x67,0x00,0x00,
0x00,0x00,0x67,0xFE,0x00,0x77,0x00,0x00,0x00,0x00,0x67,0xF0,0x00,0x77,0x00,0x00,
0x00,0x00,0x67,0xE0,0x00,0x77,0x00,0x00,0x00,0x00,0xE7,0xC0,0x00,0x77,0x00,0x00,
0x00,0x01,0xE7,0x00,0x00,0x77,0x00,0x00,0x00,0x07,0xEC,0x00,0x00,0x77,0x00,0x00,
0x00,0x1F,0xF8,0x00,0x00,0x77,0x00,0x00,0x00,0x3F,0xE0,0x00,0x00,0x77,0x00,0x00,
0x00,0x7F,0x00,0x00,0x00,0x77,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x77,0x00,0x00,
0x00,0x04,0x00,0x00,0x00,0x77,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x77,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x77,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0xFE,0x00,
0x00,0x00,0x00,0x00,0x00,0x77,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0xF8,0x00,
0x00,0x00,0x00,0x00,0x00,0x77,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0xC0,0x00,
0x00,0x00,0x00,0x00,0x00,0xF7,0x80,0x00,0x00,0x00,0x00,0x00,0x03,0xF6,0x00,0x00,
0x00,0x00,0x00,0x00,0x0F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF0,0x00,0x00,
0x00,0x00,0x00,0x00,0x3F,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
接下来是setup()函数:
初始化串口:Serial.begin(115200);//初始化串口
初始化EEPROM:EEPROM.begin(EEPROM_SIZE);
初始化OLED:Wire.begin(OLED_SDA, OLED_SCL);
IO引脚初始化:
pinMode(ENC_A, INPUT); //霍尔引脚初始化
pinMode(ENC_B, INPUT);
pinMode(ENC_C, INPUT);
pinMode(ENC_D, INPUT);
pinMode(RPWM, OUTPUT); //电机驱动部分初始化
pinMode(LPWM, OUTPUT);
pinMode(RPWM1, OUTPUT);
pinMode(LPWM1, OUTPUT);
pinMode(BuzzerPin, OUTPUT);
pinMode(Button1, INPUT_PULLUP); //按键1为输入上拉模式
pinMode(Button2, INPUT_PULLUP); //按键2为输入上拉模式
pinMode(Button3, INPUT_PULLUP);
pinMode(Button4, INPUT_PULLUP);
pinMode(Button5, INPUT_PULLUP);
设置中断检测霍尔脉冲:
attachInterrupt(digitalPinToInterrupt(ENC_A), interrupcion, RISING);//上升沿触发
attachInterrupt(digitalPinToInterrupt(ENC_B), interrupcion, RISING);
attachInterrupt(digitalPinToInterrupt(ENC_C), interrupcion1, RISING);//上升沿触发
attachInterrupt(digitalPinToInterrupt(ENC_D), interrupcion1, RISING);
通过该函数判断当前是处于上升还是下降状态:
void interrupcion(){
int stateA = digitalRead(ENC_A);
int stateB = digitalRead(ENC_B);
if(stateA == HIGH && stateB == LOW){
contador++;
if(contador > MAX_PULSES){
contador = MAX_PULSES;
}
direction = true;//
}else if(stateA == LOW && stateB == HIGH){
contador--;
if(contador < 0){
contador = 0;
}
direction = false;
}
currentHeight = map(contador, 0 , MAX_PULSES, MIN_HEIGHT, MAX_HEIGHT);
}
注意:interrupcion1同理
读取EEPROM断电保存的值,并赋值到当前高度
currentHeight = EEPROM.read(0);
currentHeight1 = EEPROM.read(1);
currentHeight = constrain(currentHeight, MIN_HEIGHT, MAX_HEIGHT);
currentHeight1 = constrain(currentHeight1, MIN_HEIGHT, MAX_HEIGHT);
updatwMotorPosition(); //当我们断电时高度为30cm那么,当重新上电后初始值应当为30cm
void updatwMotorPosition() {
int pulses = map(currentHeight, MIN_HEIGHT, MAX_HEIGHT, 0, MAX_PULSES);
contador = pulses;
int pulses1 = map(currentHeight1, MIN_HEIGHT, MAX_HEIGHT, 0, MAX_PULSES);
contador1 = pulses1;
}
接下来是IOOP函数:
//OLED显示当前时间
display.clearDisplay(); //清除显示屏图像
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(20, 13);
display.print("T:");
display.println(timeClient.getFormattedTime()); //
//OLED显示定时时间
display.setCursor(20, 30);
display.print("DING:"); //"DING --定"
display.print(alarmHour); //小时
display.print(":");
display.println(alarmMinute);//分钟
checkAlarmTrigger(); //启动定时器函数
//将当前时间与设置时间进行比较,如果相等触发蜂鸣器
void checkAlarmTrigger(){
int currentHour = timeClient.getHours();
int currentMinute = timeClient.getMinutes();
if (alarmEnabled && currentHour == alarmHour && currentMinute == alarmMinute) {
digitalWrite(BuzzerPin, HIGH);
Serial.println("蜂鸣器报警");
Serial.print("currentHour: ");
Serial.println(currentHour);
Serial.print("currentMinute: ");
Serial.println(currentMinute);
Serial.print("alarmHour: ");
Serial.println(alarmHour);
Serial.print("alarmMinute: ");
Serial.println(alarmMinute);
Serial.print("alarmEnabled: ");
Serial.println(alarmEnabled);
// 可以在这里添加代码来在OLED上显示闹钟信息
delay(500);
cancelAlarm(); // 闹钟响铃后自动取消
}
}
接下来是按键读取部分
Button1State = digitalRead(Button1); //读取按键1的状态
Button2State = digitalRead(Button2); //读取按键2的状态
Button3State = digitalRead(Button3);
Button4State = digitalRead(Button4);
Button5State = digitalRead(Button5);
if(Button1State == LOW && currentHeight1 < MAX_HEIGHT){//如果按键1被按下桌腿上升
if(!direction){
ShangSheng();
if(currentHeight >= MAX_HEIGHT){
ShangShengStup();
}
}
EEPROM.write(0, currentHeight);
EEPROM.write(1, currentHeight1);
EEPROM.commit();
}else{
ShangShengStup();
}
if(Button2State == LOW && currentHeight1 > MIN_HEIGHT){//如果按键2被按下桌腿开始下降
if(direction){
XiaJiang();
if(currentHeight <= MIN_HEIGHT){
XiaJiangStup();
}
}
EEPROM.write(0, currentHeight);
EEPROM.write(1, currentHeight1);
EEPROM.commit();
}else{
XiaJiangStup();
}
if(Button3State == LOW){ //设置闹钟小时位
delay(200);
incrementAlarmHour();
}
if(Button4State == LOW){ //设置闹钟分钟位
delay(200);
incrementAlarmMinute();
}
if(Button5State == LOW){//清除闹钟
delay(200);
cancelAlarm();
}
电机旋转与停止函数:
void ShangSheng(){
digitalWrite(LPWM, LOW);
analogWrite(RPWM, 200);
digitalWrite(RPWM1, LOW);
analogWrite(LPWM1, 200);
}
void ShangShengStup(){
digitalWrite(LPWM, LOW);
analogWrite(RPWM, 0);
digitalWrite(RPWM1, LOW);
analogWrite(LPWM1, 0);
}
void XiaJiang(){
digitalWrite(RPWM, LOW);//顺时针旋转;
analogWrite(LPWM, 200);
digitalWrite(LPWM1, LOW);
analogWrite(RPWM1, 200);
}
void XiaJiangStup(){
digitalWrite(RPWM, LOW);
analogWrite(LPWM, 0);
digitalWrite(LPWM1, LOW);
analogWrite(RPWM1, 0);
}
闹钟函数:
void triggerBuzzer(){//触发闹钟
digitalWrite(BuzzerPin, HIGH);
delay(1000);
digitalWrite(BuzzerPin, LOW);
delay(1000);
}
void incrementAlarmHour() {//设置小时
alarmHour++;
alarmEnabled = true;
if (alarmHour > 23) {
alarmHour = 0;
}
}
void incrementAlarmMinute() {//设置分钟
alarmMinute++;
alarmEnabled = true;
if (alarmMinute > 59) {
alarmMinute = 0;
}
}
void cancelAlarm() {// 停止蜂鸣器的代码
alarmEnabled = false;
alarmHour = 0; // 清零闹钟小时
alarmMinute = 0; // 清零闹钟分钟
// 停止蜂鸣器的代码
digitalWrite(BuzzerPin, LOW);
}
实物展示
提示:作品的实物图片,图片可以加上说明。
控制面板做了可拆卸,可折叠设计
设计注意事项
提示:这里说明作品在制作中需要注意的一些注意事项(没有可以不写)
这个在电路图中使用的AMS1117,据目前判断,会出现较大的发热情况,初步判段我买的AMS117应该质量有问题,当然推荐大家更换位AS1117也是可以的。
在实际烧录程序时,需要先烧录一个清除ESP32,EEPROM的程序,防止读出错误高度。
快充部分的电路,箭头标出的几个电容可以根据需要去修改。
其他
B站:星火计划-电动升降桌硬件部分_哔哩哔哩_bilibili
演示视频:演示视频上传附件即可,附件最大只能长传50M的文件,大于50M的文件可放置在其他网盘或视频网站上,只需把地址链接放入这里即可
工程附件:参加活动的作品必须把工程相关的程序附件上传至开源平台或个人的代码存储云端,附件最大支持50M上传(请勿在立创工作区上传,有限制)
设计图

BOM


评论