65.9K
CodeProject 正在变化。 阅读更多。
Home

电机驱动板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (10投票s)

2017 年 9 月 25 日

CPOL

14分钟阅读

viewsIcon

23568

downloadIcon

475

关于如何使用 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 组件包括:
 

  • 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_ONSTATE_ONSTATE_TURNING_OFFSTATE_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 电机保护板。

    尽情享用!
© . All rights reserved.