电机驱动板






4.78/5 (10投票s)
关于如何使用 Android 和 Arduino 通过蓝牙控制直流电机和 LED 的简要示例。
引言
多年前,我在某个网站上看到广告后,购买了我拥有的第一块 Arduino 板。由于我有大量空闲时间 ,我决定需要发展新的兴趣!为了了解它的工作原理以及我能用它做什么,我创建了许多用于执行各种任务的草图,并将其中一些与 Android 设备连接起来。
其中一个项目涉及通过蓝牙连接 Android 设备到一个 OneTouch® Ultra® 2 血糖仪,并从中提取 BG 记录。我选择这款特定型号的表,是因为其 RS-232 通信规范在线可用。它支持九个命令,用于执行下载 BG 记录、设置/获取仪表时钟、擦除仪表内存等操作。仪表串行端口使用我用旧电脑音频线制作的线缆连接到 Arduino 的 Rx/Tx 引脚(你可以购买预制的,但价格太贵了)。蓝牙盾被连接到 Arduino 板。配对并连接后,Android 设备会向仪表发送适当的命令,请求其 BG 记录。下载后,Android 代码会显示所有读数以及估算的 HbA1c。这是一次有趣的练习。
我最近购买的保护板之一是电机保护板。我突发奇想,想制作一个可以从 Android 设备控制的 3 轮或 4 轮车辆。在之前的项目中,我已经有了两个直流电机可以用于后轮。前面,我可以使用普通的轮子,不连接电机,或者甚至一个万向轮。我还需要某种“车身”来容纳组件。让 Arduino 草图和 Android 代码进行通信并不难,但随后我意识到,如果我想让这个“车辆”真正工作(不求精美,但至少能用),还需要更多的零件。当时,我还没有完全想清楚自己到底想要什么,所以不愿意再花钱。于是我改变了方向。
我把车辆部分的项目放在一边,然后决定以文章的形式展示我所拥有的内容。所以,我将展示我如何通过蓝牙将我的 Android 设备连接到 Arduino 电机保护板。用户界面将控制电机的速度和方向,以及两个 LED 的开关。让我们开始吧...
Arduino
- Arduino Uno Rev3
- Arduino 电机保护板 Rev3
- Seeed Studio 蓝牙保护板
- 直流电机
- 红色 LED
- 白色 LED
- 两个电阻
- 小型面包板
由于电机保护板的接头比蓝牙保护板的接头长,我选择按以下顺序堆叠它们:
Arduino 板在最下面。
电机保护板在中间。
蓝牙保护板在最上面。
现在我们需要设置直流电机。我使用的电机保护板可以控制两个直流电机。对于通道 A(电机 1),方向由数字引脚 12 设置,脉冲宽度调制 (PWM) 由数字引脚 3 设置,制动由数字引脚 9 设置,电机电流可以从模拟引脚 0 读取。对于通道 B(电机 2,如果我们使用的话),方向由数字引脚 13 设置,PWM 由数字引脚 11 设置,制动由数字引脚 8 设置,电机电流可以从模拟引脚 1 读取。所有这些都在电机的init()
函数中处理。
红色和白色 LED 分别连接到引脚 4 和 5。将所有电线连接到相应的引脚后,我们的硬件看起来如下。图片中的吸管被放在电机的轴上,以便我可以看到它朝哪个方向旋转。它还有助于减慢电机的速度,因为速度超过 200 时听起来就像要爆炸一样!
至于编码,setup()
函数在草图启动时被调用一次。在其中,我们将初始化一些变量、引脚模式和库。对于这个项目,我选择了蓝牙保护板通过引脚 6 和 7 进行通信。在蓝牙对象被构造之后,我们需要初始化它。这在setupBlueToothConnection()
中完成。该函数中的代码在此处 处 进行了描述。这一切看起来像#include <SoftwareSerial.h> #include <ArduinoMotorShieldR3.h> #define btTx 6 #define btRx 7 SoftwareSerial bluetoothSerial(btRx, btTx); ArduinoMotorShieldR3 motor; //====================================================================== void setup() { Serial.begin(19200); setupBlueToothConnection(); motor.init(); motor.setM1Speed(0); // initially off pinMode(4, OUTPUT); // red pinMode(5, OUTPUT); // white }
loop()
函数连续运行,允许程序改变、响应并主动控制 Arduino 板。在每次迭代中,它首先检查蓝牙保护板是否正在监听(来自 Android 设备的数据)。如果是,我们调用readBytesUntil()
,它会阻塞直到检测到终止符、读取到指定长度或超时(默认 1 秒)。当该函数返回并有数据需要处理时,我们检查它以确保是我们期望的。我们只关心数字 0-9、连字符、星号或井号。
如果找到星号,紧随其后的数字表示是打开还是关闭白色 LED。如果找到井号,紧随其后的数字表示是打开还是关闭红色 LED。否则,我们将找到的内容视为一个 [负] 数字,表示速度,并将其写入通道 A(电机 1)。此过程无限期地继续。这段代码看起来像void loop() { if (bluetoothSerial.isListening()) { char bufferIn[10] = {0}; int count = bluetoothSerial.readBytesUntil('\n', bufferIn, 10); if (count > 0) { bufferIn[count] = '\0'; // check the characters that were received bool bProceed = true; for (int x = 0; x < strlen(bufferIn) && bProceed; x++) { if (! isDesiredCharacter(bufferIn[x])) bProceed = false; } // only continue if accepted characters were received if (bProceed) { if (bufferIn[0] == '*') // white LED { if (bufferIn[1] == '0') digitalWrite(5, 0); else if (bufferIn[1] == '1') digitalWrite(5, 1); } else if (bufferIn[0] == '#') // red LED { if (bufferIn[1] == '0') digitalWrite(4, 0); else if (bufferIn[1] == '1') digitalWrite(4, 1); } else // DC motor { String str = bufferIn; int speed = str.toInt(); motor.setM1Speed(speed); } } } } } //====================================================================== bool isDesiredCharacter( char c ) { return (isDigit(c) || c == '-' || c == '*' || c == '#'); } //====================================================================== /* BTSTATE:0 = Initializing BTSTATE:1 = Ready BTSTATE:2 = Inquiring BTSTATE:3 = Connecting BTSTATE:4 = Connected */ void setupBlueToothConnection() { bluetoothSerial.begin(38400); // set baud rate to default baud rate 38400 bluetoothSerial.print("\r\n+STBD=38400\r\n"); bluetoothSerial.print("\r\n+STWMOD=0\r\n"); // set the bluetooth to work in slave mode bluetoothSerial.print("\r\n+STNA=SeeedBTSlave\r\n"); // set the bluetooth name as "SeeedBTSlave" bluetoothSerial.print("\r\n+STOAUT=1\r\n"); // permit paired device to connect bluetoothSerial.print("\r\n+STAUTO=0\r\n"); // auto-connection off delay(2000); // this delay is required bluetoothSerial.print("\r\n+INQ=1\r\n"); // make the slave bluetooth inquirable delay(2000); // this delay is required bluetoothSerial.flush(); }
将此草图发送到 Arduino 板后,它将初始化并开始通过蓝牙监听数据。现在,让我们开始进行 Android 端的工作。Android
在 Android 设备上运行的 Java 代码只稍微复杂一些。需要处理用户界面组件、设备的蓝牙适配器以及 Arduino 板和 Android 设备之间的实际连接。所有这些都必须协同工作。
在 activity 的onCreate()
方法中,它首先执行的操作之一是启用蓝牙适配器(如果尚未启用)。在允许此操作之前,应用程序会请求权限,如下所示:
完成此操作的代码,特别是Intent
,如下所示:mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter != null) { if (! mBluetoothAdapter.isEnabled()) { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivity(intent); } else { tvBluetoothStatus.setText("Enabled"); if (! MainApplication.isSocketConnected()) { btnConnect.setEnabled(true); Toast.makeText(this, "Click the Connect button to connect to a device.", Toast.LENGTH_LONG).show(); } } } else { tvBluetoothStatus.setText("Not supported"); showDialog(this, "Unsupported", "Bluetooth is not supported on this device."); }
启用后,我们的用户界面现在看起来如下。请注意,唯一启用的用户界面控件是 Connect 按钮。
与startActivityForResult()
不同,startActivity()
退出时您不会收到任何信息。但是,BluetoothAdapter.ACTION_REQUEST_ENABLE
Intent 会对此进行例外处理。会向onActivityResult()
回调发送通知。如果蓝牙已打开,resultCode
将为RESULT_OK
;如果用户拒绝请求或发生错误,则为RESULT_CANCELED
。相反,此应用程序将监听蓝牙打开或关闭时的ACTION_STATE_CHANGED
通知。我们将使用BroadcastReceiver
来设置此项以及其他通知。
在这个项目中,我们感兴趣的是以下通知:当蓝牙适配器 1) 启用或 2) 禁用时,以及 3) 当蓝牙设备连接或断开连接时。这些通知将由BTReceiver
类处理,稍后将进行讨论。设置此类接收器的代码如下:try { // unregister any previously registered receivers unregisterReceiver(receiver); } catch(IllegalArgumentException iae) // fail gracefully if null { } catch(Exception e) { Log.e(TAG, "Unregistering BT receiver: " + e.getMessage()); } receiver = new BTReceiver(this); IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); // turning on, on, turning off, off registerReceiver(receiver, filter); filter = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); registerReceiver(receiver, filter); filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED); registerReceiver(receiver, filter);
您可能会想知道unregisterReceiver()
的调用。当我开始从事这个项目时,我编写了代码并在纵向模式下在我的 Android 设备上进行了测试。有一天我可能不小心把它倾斜得太厉害了,它就切换到了横向模式。嗯。布局调整得很漂亮,事情似乎看起来井井有条,但当我尝试与用户界面交互时,却没有任何蓝牙通信与 Arduino 连接。哎呀!我发现的问题之一(是的,有很多)是接收器不再接收。没有抛出异常,但仍然没有通知。由于我在onCreate()
方法中重新注册了 Intent,我猜测最初的 Intent 被后续的创建所掩盖了。调用unregisterReceiver()
不仅会注销先前注册的接收器,还会删除为该接收器注册的所有过滤器(即 Intent)。现在我可以更改设备的屏幕方向,仍然接收蓝牙通知。
我发现的下一个问题与蓝牙连接本身有关。最初,我将BluetoothSocket
对象作为 activity 的一部分。只要没有连接,重启 activity 就可以了。但是,连接建立后,当 Android 设备的屏幕方向改变并且onCreate()
方法再次被调用时,socket 会被重新创建,导致与 Arduino 没有蓝牙通信。我首先考虑尝试在onSaveInstanceState()
和onRestoreInstanceState()
方法中保存/加载 socket 对象。理论上听起来不错,但没有产生预期的结果。然后我决定将BluetoothSocket
对象放在 application 类本身中。现在,当 Android 设备的屏幕方向改变并且 activity 被重新创建时,唯一的 application 实例会保持BluetoothSocket
对象不变。
我观察到的最后一个问题与直流电机或 LED 在 Android 设备屏幕方向改变时保持开启状态有关。在 activity 重新创建期间,UI 控件会被设置为默认的“关闭”状态,但 Arduino 仍然会为直流电机或 LED 供电。为了解决这个问题(即,使 UI 匹配),我能够使用onSaveInstanceState()
和onRestoreInstanceState()
方法。这看起来像@Override protected void onSaveInstanceState( Bundle outState ) { Log.d(TAG, "onSaveInstanceState()"); super.onSaveInstanceState(outState); int speed = seekbar.getProgress(); outState.putInt("SPEED", speed); boolean white = ledWhite.isChecked(); outState.putBoolean("WHITE", white); boolean red = ledRed.isChecked(); outState.putBoolean("RED", red); } @Override protected void onRestoreInstanceState( Bundle savedInstanceState ) { Log.d(TAG, "onRestoreInstanceState()"); super.onRestoreInstanceState(savedInstanceState); // did the orientation change (or some other event) while the motor was running or the LEDs were on? if (mBluetoothAdapter.isEnabled() && MainApplication.isSocketConnected()) { Log.d(TAG, "The device was rotated. Setting the motor and LEDs to what they were before rotation."); tvDeviceStatus.setText("Connected to " + MainApplication.strDeviceName); int speed = savedInstanceState.getInt("SPEED"); seekbar.setProgress(speed); boolean white = savedInstanceState.getBoolean("WHITE"); ledWhite.setChecked(white); boolean red = savedInstanceState.getBoolean("RED"); ledRed.setChecked(red); } else { Log.d(TAG, "The device was rotated but is neither enabled nor connected. Resetting the motor and LEDs"); // these should probably already be off, but just in case... seekbar.setProgress(50); ledWhite.setChecked(false); ledRed.setChecked(false); } }
上面很多代码和文字都与连接有关,我还没有展示。可以说,我有点本末倒置了。如前所述,一旦蓝牙适配器启用,Connect 按钮就会可用。单击它将启动一个次级 activity,显示先前配对设备的列表。
使用类似以下的代码获取此列表:ArrayList<String> arrItems = new ArrayList<String>(); BluetoothAdapter btnAdapter = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> pairedDevices = btnAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { for (BluetoothDevice device : pairedDevices) arrItems.add(device.getName() + "\n" + device.getAddress()); adapter.notifyDataSetChanged(); }
如 Arduino 代码所示,我们的蓝牙设备的名称是 SeeedBTSlave。单击该设备时,在onItemClicked()
处理程序中,我们会将其 MAC 地址打包到一个Intent
中,传回主 activity。主 activity 在onActivityResult()
中接收此响应,如下所示:@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Log.d(TAG, getClass().getName() + "::onActivityResult(" + resultCode + ")"); if (requestCode == 1 && resultCode == RESULT_OK) { String strAddress = data.getExtras().getString("device_address"); Connect(strAddress); } }
有了要连接的设备的 MAC 地址,我们现在可以进行实际连接了。当我最初将连接代码放在一起时,很自然地就直接使用了BluetoothDevice.createRfcommSocketToServiceRecord()
方法来创建一个 socket,并使用BluetoothSocket.connect()
方法来连接它。这奏效了,但只成功了一部分时间。我不断收到零星的 java.io.IOException: read failed, socket might closed or timeout, read ret: -1 错误。经过一番研究,我发现其他人也遇到了同样的问题。虽然不 100% 可靠,也无法保证其可用性,但许多人通过使用 Java 反射来访问createRfcommSocket()
函数取得了成功。使用该代码作为备用方法,我将我的connect()
函数更改为如下所示:private void Connect( String address ) { Log.d(TAG, getClass().getName() + "::Connect()"); if (mBluetoothAdapter == null || ! mBluetoothAdapter.isEnabled()) { Log.e(TAG, " Bluetooth adapter is not enabled."); return; } BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); Toast.makeText(this, "Connecting to device " + device.getName() + "...", Toast.LENGTH_LONG).show(); mBluetoothAdapter.cancelDiscovery(); try { MainApplication.btSocket = device.createRfcommSocketToServiceRecord(BT_UUID); MainApplication.btSocket.connect(); } catch(IOException ioe) { Log.e(TAG, " Connection failed. Trying fallback method..."); try { MainApplication.btSocket = (BluetoothSocket) device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(device, 1); MainApplication.btSocket.connect(); } catch(Exception e) { Log.e(TAG, " Connection failed."); showDialog(this, "Socket creation failed", e.getMessage() + "\n\nThe remote device may need to be restarted."); } } }
这样,如果主连接方法失败,我们就尝试备用方法。如果该方法也失败,我们就打包回家!
现在我们已经请求了连接,是时候开始监听通知了。前面我们注册了一个带有三个过滤器(即 Intent)的接收器。这通过BroadcastReceiver
扩展来处理。我选择将此扩展放在自己的文件中,而不是将其作为 activity 的内部类。这样做的一个缺点是,无法进行 UI 交互。换句话说,当蓝牙适配器启用或禁用时,或者设备断开连接时,我无法更新 UI 上的任何状态控件。一种解决方案是使用interface
。这个类看起来像public class BTReceiver extends BroadcastReceiver { private static String TAG = "Test2"; private Callback mCallback = null; public interface Callback { public void updateStatus( String strAction, int nState, String strDevice ); } //================================================================ public BTReceiver( Activity activity ) { if (! (activity instanceof Callback)) throw new IllegalStateException("Activity must implement 'Callback' interface."); Log.d(TAG, "BTReceive()"); mCallback = (Callback) activity; } //================================================================ @Override public void onReceive( Context context, Intent intent) { Log.d(TAG, getClass().getName() + "::onReceive()"); try { String strAction = intent.getAction(); if (strAction.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { int nState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); mCallback.updateStatus(strAction, nState, ""); } else if (strAction.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); MainApplication.strDeviceName = device.getName(); mCallback.updateStatus(strAction, -1, MainApplication.strDeviceName); } else if (strAction.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) mCallback.updateStatus(strAction, -1, ""); } catch(Exception e) { Log.e(TAG, e.getMessage()); } } }
一旦建立连接,UI 看起来如下,所有可用控件都已启用。您可以单击 Disconnect 按钮断开与 Arduino 的连接。您可以左右滑动SeekBar
来控制电机的速度。您可以使用相应的Switch
打开和关闭两个 LED。
当收到蓝牙通知时,我们可以简单地调用 activity 正在实现的接口方法。在updateStatus()
实现中,我们会根据收到的通知执行不同的操作。如果蓝牙设备已连接或断开连接,我们只需更新 UI 上的状态控件(如上所示)。适配器的状态可以改变四种方式:STATE_TURNING_ON
、STATE_ON
、STATE_TURNING_OFF
和STATE_OFF
。如果适配器正在启动,我们只显示一个简短的Toast
消息。大多数时候您可能甚至看不到此消息,因为启用适配器的 activity 屏幕上仍有自己的消息。一旦适配器实际启动,我们就会更新 UI 上的状态控件并显示一个简短的Toast
消息,提醒您现在可以单击 Connect 按钮来建立连接。如果适配器正在关闭,我们会向直流电机和两个 LED 发送“停止”消息。这些消息会在STATE_OFF
通知之前发送,以便在蓝牙 socket 关闭之前有时间处理它们。最后,如果适配器实际上已关闭,我们就会更新 UI 上的状态控件并关闭蓝牙 socket。所有这些的代码如下:@Override public void updateStatus( String strAction, int nState, String strDevice ) { Log.d(TAG, getClass().getName() + "::updateStatus()"); try { if (strAction.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { if (nState == BluetoothAdapter.STATE_TURNING_ON) // 11 Toast.makeText(MainActivity.this, "Bluetooth is turning on...", Toast.LENGTH_LONG).show(); else if (nState == BluetoothAdapter.STATE_ON) // 12 { tvBluetoothStatus.setText("Enabled"); Toast.makeText(MainActivity.this, "Click the Connect button to connect to a device.", Toast.LENGTH_LONG).show(); } else if (nState == BluetoothAdapter.STATE_TURNING_OFF) // 13 { Toast.makeText(MainActivity.this, "Bluetooth is turning off...", Toast.LENGTH_LONG).show(); // send 'stop' requests to the motor and LEDs before the BT pipe is disconnected in BluetoothAdapter.STATE_OFF seekbar.setProgress(50); // 50% sets motor to 0 ledWhite.setChecked(false); ledRed.setChecked(false); } else if (nState == BluetoothAdapter.STATE_OFF) // 10 { tvBluetoothStatus.setText("Disabled"); // for some reason, when the adapter is disabled, the socket remains 'open' so we need to explicitly close it if (MainApplication.btSocket != null) { MainApplication.btSocket.close(); MainApplication.btSocket = null; } } } else if (strAction.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { tvDeviceStatus.setText("Connected to " + strDevice); Toast.makeText(MainActivity.this, "Slide the bar left or right to control the speed of the motor.", Toast.LENGTH_LONG).show(); } else if (strAction.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) tvDeviceStatus.setText("Disconnected"); updateControls(); } catch(Exception e) { Log.e(TAG, e.getMessage()); } }
还有三个其他处理程序(或者两个,因为其中两个几乎相同)我们需要讨论:直流电机和两个 LED。直流电机的速度由SeekBar
控制,两个 LED 各由一个Switch
控制。对于后者,只需向 Arduino 发送适当的字节即可。两个字节中的第一个字节要么是白色 LED 的星号,要么是红色 LED 的井号。第二个字节是 0 表示关闭,1 表示打开。这两个处理程序的代码如下:ledWhite = (Switch) findViewById(R.id.ledWhite); ledWhite.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ) { Log.d(TAG, "White LED checked = " + isChecked); if (isChecked) writeData("*1"); else writeData("*0"); } }); ledRed = (Switch) findViewById(R.id.ledRed); ledRed.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ) { Log.d(TAG, "Red LED checked = " + isChecked); if (isChecked) writeData("#1"); else writeData("#0"); } });
这两个都相当简单。但是,对于直流电机,它本身并不复杂,但是存在将电机的 -400 到 +400 的范围映射到SeekBar
的 0-100 范围的问题。当SeekBar
处于 50% 位置时,它映射到直流电机的 0(关闭)。当SeekBar
处于 0% 或 100% 位置时,它分别映射到 -400 或 +400 的直流电机。请看这个说明:SeekBar position 0% 25% 50% 75% 100% DC motor value -400 -200 0 200 400
我选择了一种方法来转换一个值到另一个值,首先获取直流电机的取值范围,即 800。当调用onProgressChanged()
方法时,其中一个参数是百分比(即进度)。它是一个整数,所以我们需要将其除以 100 才能得到一个小数。现在我们可以将这个小数百分比乘以上面的范围,以得到我们在该范围内的距离。例如,如果SeekBar
在 25%,那么我们应该在 800 的 25% 处,即 200。值是正确的,但符号不对。由于 25% 在 0 的左边,我们的答案应该是负数。修复方法是简单地将电机的最小值 -400 加到我们的答案中。所以,-400 + 200 等于 -200,这就是 25% 的值。同样,如果SeekBar
在 75%,那么我们应该在 800 的 75% 处,即 600。符号是正确的,但值不对。再次,通过将电机的最小值 -400 加到我们的答案中,我们得到 -400 + 600 等于 200,这就是 75% 的值。至此,设置电机速度的代码如下:int MIN = -400; int MAX = 400; int SPAN = MAX - MIN; @Override public void onProgressChanged( SeekBar seekBar, int progress, boolean fromUser ) { Log.d(TAG, "Progress = " + progress + "%"); double dProgress = progress / 100.0; String strSpeed = String.format(Locale.getDefault(), "%d", (int) Math.floor(MIN + (SPAN * dProgress))); tvSpeed.setText(strSpeed); writeData(strSpeed); }
在这个练习过程中,我注意到SeekBar
和两个Switch
控件的监听器都会在 UI 或代码更改时得到通知。例如,我最初的代码会向白色 LED 发送“关闭”命令,然后将白色 LED 开关关闭。后来我发现,我可以移除发送“关闭”命令给白色 LED 的代码,只需使用关闭“白色”开关的代码即可。白色开关的处理程序将负责发送“关闭”命令给白色 LED。对两个 LED 和直流电机执行此操作,使我能够有一个单一的位置(而不是多个)向 Arduino 发送蓝牙数据。这让我想起了 20 多年前我在一张海报上看到的法国作家安托万·德·圣埃克苏佩里的一句话:“完美并非在无事可加之时达成,而是在无事可减之时达成。”
当两个 LED 都亮着,电机以相当快的速度旋转时,我们的屏幕看起来如下:
结论
毫无疑问,这个项目还有很大的改进空间,但我希望我能够成功地展示如何通过蓝牙使用 Android 设备控制 Arduino 电机保护板。
尽情享用!