软硬件交互
如何通过Arduino用旋钮控制烤箱界面
本教程将教会如何通过ProtoPie Connect连接Arduino连接ProtoPie实现旋钮控制烤箱界面。
ProtoPie
August 24, 2023
最终效果展示
物料准备
1. ProtoPie专业版或企业版账号
所有涉及软硬件交互的功能,都需要使用ProtoPie Connect。而目前(2023年4月19日)只有付费账号才能登录ProtoPie Connect。前往购买地址
2. 下载ProtoPie Connect和ProtoPie Player
在该页面 注册会员并登录后,可以看到下载按钮。如果已有账号,同样点击注册会员,在注册页面选择“登录账号”后,可在该页面看到如下按钮
4. Arduino和旋钮
案例中使用的是Arduino Uno,你可以淘宝、京东等平台购买。以下是推荐的购买地址(无商业推广目的)
5. Arduino代码部分
Arduino本身需要运行代码,才能接收发硬件的信号。如果你还不清楚如何去安装Arduino并运行代码,请先参考该视频 。
Arduino Sketch文件(代码部分)
/* Originally coded by Yvan / https://www.brainy-bits.com/arduino-rotary-encoder-ky-040/ */
/* Optimized for ProtoPie by Tony KIm / https://www.protopie.cn/ */
// Rotary Encoder Module connections
const int A_CLK=13; // A CLOCK signal
const int A_DT=12; // A DATA signal
const int A_SW=11; // A Switch
const int B_CLK=10; // B CLOCK signal
const int B_DT=9; // B DATA signal
const int B_SW=8; // B Switch
const int C_CLK=7; // C CLOCK signal
const int C_DT=6; // C DATA signal
const int C_SW=5; // C Switch
const int D_CLK=4; // D CLOCK signal
const int D_DT=3; // D DATA signal
const int D_SW=2; // D Switch
// Variables to debounce Rotary Encoder
long TimeOfLastDebounce = 0;
int DelayofDebounce = 0.01;
// Store previous Pins state
int Previous_A_CLK;
int Previous_A_DT;
int Previous_B_CLK;
int Previous_B_DT;
int Previous_C_CLK;
int Previous_C_DT;
int Previous_D_CLK;
int Previous_D_DT;
void setup() {
// Put current pins state in variables
Serial.begin(115200);
Previous_A_CLK=digitalRead(A_CLK);
Previous_B_CLK=digitalRead(B_CLK);
Previous_C_CLK=digitalRead(C_CLK);
Previous_D_CLK=digitalRead(D_CLK);
Previous_A_DT=digitalRead(A_DT);
Previous_B_DT=digitalRead(B_DT);
Previous_C_DT=digitalRead(C_DT);
Previous_D_DT=digitalRead(D_DT);
// Set the Switch pin to use Arduino PULLUP resistors
pinMode(A_SW, INPUT_PULLUP);
pinMode(B_SW, INPUT_PULLUP);
pinMode(C_SW, INPUT_PULLUP);
pinMode(D_SW, INPUT_PULLUP);
}
void loop() {
// If enough time has passed check the rotary encoder
if ((millis() - TimeOfLastDebounce) > DelayofDebounce) {
check_rotary(); // Rotary Encoder check routine below
Previous_A_CLK=digitalRead(A_CLK);
Previous_B_CLK=digitalRead(B_CLK);
Previous_C_CLK=digitalRead(C_CLK);
Previous_D_CLK=digitalRead(D_CLK);
Previous_A_DT=digitalRead(A_DT);
Previous_B_DT=digitalRead(B_DT);
Previous_C_DT=digitalRead(C_DT);
Previous_D_DT=digitalRead(D_DT);
TimeOfLastDebounce=millis(); // Set variable to current millis() timer
}
// Check if Rotary Encoder switch was pressed
if (digitalRead(A_SW) == LOW) {
Serial.println("a_btn"); // Send "a_btn" to ProtoPie
}
if (digitalRead(B_SW) == LOW) {
Serial.println("b_btn"); // Send "b_btn" to ProtoPie
}
if (digitalRead(C_SW) == LOW) {
Serial.println("c_btn"); // Send "c_btn" to ProtoPie
}
if (digitalRead(D_SW) == LOW) {
Serial.println("d_btn"); // Send "d_btn" to ProtoPie
}
}
// Check if Rotary Encoder was rotated
// Debounce codes - it eliminates bounce effects
void check_rotary() {
// Encoder A
if ((Previous_A_CLK == 0) && (Previous_A_DT == 1)) {
if ((digitalRead(A_CLK) == 1) && (digitalRead(A_DT) == 0)) {
Serial.println("a_cw"); // Send "a_cw" to ProtoPie
}
if ((digitalRead(A_CLK) == 1) && (digitalRead(A_DT) == 1)) {
Serial.println("a_ccw"); // Send "a_ccw" to ProtoPie
}
}
if ((Previous_A_CLK == 1) && (Previous_A_DT == 0)) {
if ((digitalRead(A_CLK) == 0) && (digitalRead(A_DT) == 1)) {
Serial.println("a_cw"); // Send "a_cw" to ProtoPie
}
if ((digitalRead(A_CLK) == 0) && (digitalRead(A_DT) == 0)) {
Serial.println("a_ccw"); // Send "a_ccw" to ProtoPie
}
}
if ((Previous_A_CLK == 1) && (Previous_A_DT == 1)) {
if ((digitalRead(A_CLK) == 0) && (digitalRead(A_DT) == 1)) {
Serial.println("a_cw"); // Send "a_cw" to ProtoPie
}
if ((digitalRead(A_CLK) == 0) && (digitalRead(A_DT) == 0)) {
Serial.println("a_ccw"); // Send "a_ccw" to ProtoPie
}
}
if ((Previous_A_CLK == 0) && (Previous_A_DT == 0)) {
if ((digitalRead(A_CLK) == 1) && (digitalRead(A_DT) == 0)) {
Serial.println("a_cw"); // Send "a_cw" to ProtoPie
}
if ((digitalRead(A_CLK) == 1) && (digitalRead(A_DT) == 1)) {
Serial.println("a_ccw"); // Send "a_ccw" to ProtoPie
}
}
//Encorder B
if ((Previous_B_CLK == 0) && (Previous_B_DT == 1)) {
if ((digitalRead(B_CLK) == 1) && (digitalRead(B_DT) == 0)) {
Serial.println("b_cw"); // Send "b_cw" to ProtoPie
}
if ((digitalRead(B_CLK) == 1) && (digitalRead(B_DT) == 1)) {
Serial.println("b_ccw"); // Send "b_ccw" to ProtoPie
}
}
if ((Previous_B_CLK == 1) && (Previous_B_DT == 0)) {
if ((digitalRead(B_CLK) == 0) && (digitalRead(B_DT) == 1)) {
Serial.println("b_cw"); // Send "b_cw" to ProtoPie
}
if ((digitalRead(B_CLK) == 0) && (digitalRead(B_DT) == 0)) {
Serial.println("b_ccw"); // Send "b_ccw" to ProtoPie
}
}
if ((Previous_B_CLK == 1) && (Previous_B_DT == 1)) {
if ((digitalRead(B_CLK) == 0) && (digitalRead(B_DT) == 1)) {
Serial.println("b_cw"); // Send "b_cw" to ProtoPie
}
if ((digitalRead(B_CLK) == 0) && (digitalRead(B_DT) == 0)) {
Serial.println("b_ccw"); // Send "b_ccw" to ProtoPie
}
}
if ((Previous_B_CLK == 0) && (Previous_B_DT == 0)) {
if ((digitalRead(B_CLK) == 1) && (digitalRead(B_DT) == 0)) {
Serial.println("b_cw"); // Send "b_cw" to ProtoPie
}
if ((digitalRead(B_CLK) == 1) && (digitalRead(B_DT) == 1)) {
Serial.println("b_ccw"); // Send "b_ccw" to ProtoPie
}
}
//Encorder C
if ((Previous_C_CLK == 0) && (Previous_C_DT == 1)) {
if ((digitalRead(C_CLK) == 1) && (digitalRead(C_DT) == 0)) {
Serial.println("c_cw"); // Send "c_cw" to ProtoPie
}
if ((digitalRead(C_CLK) == 1) && (digitalRead(C_DT) == 1)) {
Serial.println("c_ccw"); // Send "c_ccw" to ProtoPie
}
}
if ((Previous_C_CLK == 1) && (Previous_C_DT == 0)) {
if ((digitalRead(C_CLK) == 0) && (digitalRead(C_DT) == 1)) {
Serial.println("c_cw"); // Send "c_cw" to ProtoPie
}
if ((digitalRead(C_CLK) == 0) && (digitalRead(C_DT) == 0)) {
Serial.println("c_ccw"); // Send "c_ccw" to ProtoPie
}
}
if ((Previous_C_CLK == 1) && (Previous_C_DT == 1)) {
if ((digitalRead(C_CLK) == 0) && (digitalRead(C_DT) == 1)) {
Serial.println("c_cw"); // Send "c_cw" to ProtoPie
}
if ((digitalRead(C_CLK) == 0) && (digitalRead(C_DT) == 0)) {
Serial.println("c_ccw"); // Send "c_ccw" to ProtoPie
}
}
if ((Previous_C_CLK == 0) && (Previous_C_DT == 0)) {
if ((digitalRead(C_CLK) == 1) && (digitalRead(C_DT) == 0)) {
Serial.println("c_cw"); // Send "c_cw" to ProtoPie
}
if ((digitalRead(C_CLK) == 1) && (digitalRead(C_DT) == 1)) {
Serial.println("c_ccw"); // Send "c_ccw" to ProtoPie
}
}
//Encorder D
if ((Previous_D_CLK == 0) && (Previous_D_DT == 1)) {
if ((digitalRead(D_CLK) == 1) && (digitalRead(D_DT) == 0)) {
Serial.println("d_cw"); // Send "d_cw" to ProtoPie
}
if ((digitalRead(D_CLK) == 1) && (digitalRead(D_DT) == 1)) {
Serial.println("d_ccw"); // Send "d_ccw" to ProtoPie
}
}
if ((Previous_D_CLK == 1) && (Previous_D_DT == 0)) {
if ((digitalRead(D_CLK) == 0) && (digitalRead(D_DT) == 1)) {
Serial.println("d_cw"); // Send "d_cw" to ProtoPie
}
if ((digitalRead(D_CLK) == 0) && (digitalRead(D_DT) == 0)) {
Serial.println("d_ccw"); // Send "d_ccw" to ProtoPie
}
}
if ((Previous_D_CLK == 1) && (Previous_D_DT == 1)) {
if ((digitalRead(D_CLK) == 0) && (digitalRead(D_DT) == 1)) {
Serial.println("d_cw"); // Send "d_cw" to ProtoPie
}
if ((digitalRead(D_CLK) == 0) && (digitalRead(D_DT) == 0)) {
Serial.println("d_ccw"); // Send "d_ccw" to ProtoPie
}
}
if ((Previous_D_CLK == 0) && (Previous_D_DT == 0)) {
if ((digitalRead(D_CLK) == 1) && (digitalRead(D_DT) == 0)) {
Serial.println("d_cw"); // Send "d_cw" to ProtoPie
}
if ((digitalRead(D_CLK) == 1) && (digitalRead(D_DT) == 1)) {
Serial.println("d_ccw"); // Send "d_ccw" to ProtoPie
}
}
}
连接与运行
1. 连接面包版
按照下面的示意图将4个旋钮和Arduino Uno相连
2. Arduino同ProtoPie Connect 的连接
- 通过USB接口将Arduino和装有ProtoPie Connect的电脑相连
- 运行Arduino代码 ,请注意,此时的接口应该是你的usb接口
- 运行代码
- 打开serial monitor检测信号是否正常进入,确认后须关闭serial monitor (serial monitor窗口和ProtoPie connect不能同时开启)
- 打开ProtoPie Connect,选择右上角的Plugin,在点击Run之前先点击Arduino
- 选择在b这步 相同的接口,并将Baud Rate设置为115200.
- 现在当你调节旋钮的时候,应该可以看到数据进入到Connect了。
3. 通过ProtoPie Connect运行Pie文件
- 点击ProtoPie Connect左上角的加号,选择本地文件,选择要和Arduino连接的Pie文件 。
- 导入后,点击预览图标,原型就可在网页端打开
- 此时物理按钮就可以操控屏幕上的视觉效果了
4. 在装有Player的平板上运行交互
- 你可以用数据线现将平板/手机和电脑相连,这样原型可以在离线环境下从Connect同步到Player。如果希望通过Wifi连接,请一定确保电脑和移动端连在同一个网络环境下。(手机热点可以帮你确保连在同一个网络下)
- 打开ProtoPie Player,点击USB图标或扫码键。与此同时点击ProtoPie Connect上的Connect键(预览图标旁边的),选择相应的连接方式。如果在连接中遇到问题可参考指导手册 。现在你就可以看到和demo视频中一样的效果啦。
要点解析
1. 旋钮信息发送的原理
关于Knob旋钮的工作原理,可参考这份中文说明 。在Arduino中,每当旋钮的状态发生变化,都需要发送数据。而发送数据的代码是:
Serial.println("message")
2. 发送的信息
Knob旋钮发出3种数据:顺时针旋转(CW)、逆时针旋转(CCW)和按钮(BTN)。因为有4个旋钮,所以分别对4个旋钮的3种数据进行命名:
- A旋钮:a_cw , a_ccw , a_btn
- B旋钮:b_cw , b_ccw , b_btn
- C旋钮:c_cw , c_ccw , c_btn
- D旋钮:d_cw , d_ccw , d_btn
3. 按钮旋转的信息与交互的对应
每当收到逆时针旋转的信息时,则画面上相应图层逆时针旋转5度
每当收到顺时针旋转的信息时,则画面上相应图层顺时针旋转5度
每当收到按钮被按下的信息时,则将画面上的旋钮归位。
4. 按钮旋转的角度和功能icon的对应
根据画面上旋钮图层(knob-a,knob-b,knob-c,knob-d)的旋转角度范围,让相应的功能icon亮起。角度和功能区的对应关系如下: