安卓设备通过USB串口与STM32单片机通讯之四

本博文系JGB联合商务组的原创作品,引用请标明出处。

本博文接续上一篇的末尾章节。

(四) JGB01开发板的硬件制作和烧录部分

为方便初涉STM32单片机开发的读者,我在上一章节中介绍的本开发板所用的C代码项目包(仅供学习,已在Keil5.1编译通过)已上传资源中心,可免费下载,自行编译.
资源标签: JGB01开发板的C源码项目包  JavaUsbF103Blog.rar

另外,本USB串口调试器之APK项目包(仅供学习, 已在Android Studio 4.1中编译通过)也可免费下载了.
**资源标签: JGB联合商务组  USB串口调试器 HoHoUsbSerial **

在某宝上购得STM32F103最小板后,我们还要准备四个零件:

  • CP 2102 USB-TTL串口桥接器
  • DS18B20温度传感器(TO92封装)
  • 12K贴片电阻(1206封装, 也可用1/8W的金属膜电阻代替)
  • XH 2.54mm红黑双头电子线, 用于桥接器和最小板的+5V (红)及GND(黑) 分别连接.

     零件摆放实物图
    我把上一章节中介绍过的JGB01电路图反转一下,你会发现上面的实物图中的STM32F103最小板和这里的电路图标号为P2的元件引脚定义完全一致.

    这就很好办了,用镊子小心把对应的元件管脚作适当的弯曲,扭伸. 我们很容易让其各就各位并焊接.
    在这里我要说一下焊接, 很多单片机软件开发者不喜欢这个步骤.而是在开发调试时喜欢用一种叫杜邦线或面包板来连接各种电子零部件,其实这很容易造成接触不良进而调试出错.你甚至会完全无法查觉到这种硬件问题的存在而是去反复检查本来就是正确的代码. 因此在我的开发过程中是极力避免用这种杜邦线连接的. 没有别的,一个高可靠连接且布线清爽的开发板会让你的码砖事半功倍.

下面是焊接示意图: 其中的DS18B20的地线脚(1脚)穿过了板子右上角的地线过孔(过孔本来不是用来焊接元件的,但在这里因其孔径和铜箔面积都很大使用没有问题). 见下图红线示意:

Android 程序获取USB列表 安卓读取usb设备数据_Android 程序获取USB列表


(1)先焊接DS18B20

Android 程序获取USB列表 安卓读取usb设备数据_背景色_02


(2)在板子背面焊接12K电阻

Android 程序获取USB列表 安卓读取usb设备数据_Android 程序获取USB列表_03


(3)焊接双头电子线(焊接时注意红黑极性)

Android 程序获取USB列表 安卓读取usb设备数据_背景色_04


(4)焊接USB-TTL桥接器的TXD和RXD管脚 (插入板子前需弯折这两个管脚),最后红黑电子线插入+5V和GND管针(插入时注意极性)

Android 程序获取USB列表 安卓读取usb设备数据_开发板_05

(5)最后焊接购买此最小板时附送的黄色四针插座, 加工完成.

做好的JGB01开发板成品示意图(特点: 成本低廉,小巧可靠,还有很多的功能管脚可以扩展)

Android 程序获取USB列表 安卓读取usb设备数据_Android 程序获取USB列表_06

烧录此开发板时的连接图:

Android 程序获取USB列表 安卓读取usb设备数据_背景色_07

特别提示: 由于上图的四针插头与开发板黄色四针插座的插入是无方向限定的, 很容易插反而出错. 因此要小心地对好电源的正负极性再插入,否则有可能烧坏开发板!

烧录时的配置参数页面:
点击 keil5.1 的工具栏按钮【Options for Target…】会得到一个有多选项卡的配置界面,我用红线框出了各选项卡需重点关注的内容。

1.芯片型号

Android 程序获取USB列表 安卓读取usb设备数据_串口_08

  1. 芯片的时钟
  2. 与C编译器相关的配置
  3. 选定 JLink V9 访真设备来调试

    4A. 点击上图的按钮【Settings】可进一步配置JLink V9 访真设备的驱动设置。
    其中的port 为SW 表示使用开发板上的SWD四针端口。此图的三个红框内都有数据
    表示: 电脑,JLINK访真设备,JGB01开发板之间的连接都是正常的。

    4B. JLink V9 访真设备的驱动设置: STM32F103芯片的flash密度和烧录起始位置

    5 同样选定 JLink V9 访真设备来烧录

到了这里,我的本篇博文也快结束了,感谢你耐心细致地阅读到了这里。
你也许会说,这一章节的制作需要这么多的焊接,太麻烦了,有没有不需要焊接又能进行一些串口硬件的控制测试呢?

有的,我最后为你准备了一个《使用DB9串口的双路控制开关输入》的调试场景作为本博文系列的结束,希望对可能不是很熟悉硬件的你有所启发。
附注: DB9串口输出的是RS232电平(逻辑1为-3V到-15V,逻辑0为+3V到+15V),与大多数开发板上的TTL电平完全不同,因此若要使用DB9串口的RXD和TXD去连接JGB01开发板的TXD和RXD,中间则需要另外加接一块RS232-TTL电平转换板,会非常麻烦。

这里我需要一条在前面的章节中提到过的调试AT指令时用过的USB-RS232串口桥接线(PL2303芯片,DB9公头串口),还需三条杜邦线和一条金属短接线。作如下的跳接:

Android 程序获取USB列表 安卓读取usb设备数据_开发板_09

(图4-2A)

连接只需使用DSR, RTS, CTS 三个管脚。

其相应的电气接线图类似如下:

Android 程序获取USB列表 安卓读取usb设备数据_开发板_10

(图4-2B)

金属短接线短接不同颜色杜邦线后可分别模拟K1和K2这两个开关。
安卓设备连接此桥接线后,同样需要先打开PL2303芯片类的串口设备。

(1) 红绿线短接模拟K1,此时左一按钮的背景变为红色,表示有K1开关控制输入.

Android 程序获取USB列表 安卓读取usb设备数据_Android 程序获取USB列表_11


(图4-2C)(2) 红蓝线短接模拟K2,此时左二按钮的背景变为红色,表示有K2开关控制输入.

Android 程序获取USB列表 安卓读取usb设备数据_Android 程序获取USB列表_12


(图4-2D)

这个测试有较高的实用价值,我曾把它用于一个眼部超声波图像采集系统中,K1和K2是两个脚踏开关,用线连接到RS232串口母座,然后和上面的公头连接,中间不需要任何的单片机,K1是单帧采集,K2是连续采集,系统工作很可靠。象这种祖传秘笈很多通讯老手通常都是秘而不宣的. 下面我将为你揭开它的核心部分之面纱:即软件实现部分。
为理解软件设计思路,我们首先认识一点DB9的各个管脚定义,这些管脚的含义除了GND,RXD,TXD外, 其它的在通讯发展史中已慢慢地淡出人们的视野了,我们也不需要过多地理解,只需记住它们的英文名字和管脚号就好了.

1 CD 载波侦测(Carrier Detect)
2 RXD 接收数据(Receive)
3 TXD 发送数据(Transmit)
4 DTR 数据终端准备(Data Terminal Ready)
5 GND 地线(Ground)
6 DSR 数据准备好(Data Set Ready)
7 RTS 请求发送(Request To Send)
8 CTS 清除发送(Clear To Send)
9 RI 振铃指示(Ring Indicator)

在我这个硬件测试中我只需知道:
我的安卓设备在这里是DTE(数据终端设备)角色,而USB-RS232桥接线内部的PL2303则是DCE(数据通讯设备)角色。

因此对于安卓设备(DTE)来说:
DB9除去GND,RXD,TXD三个管脚外,理论上应该有六个管脚可以由APP进行软件的位操作,它们的输出/输入方向在本测试中应该是固定的,
这些位管脚分别是:
(1)输出位2个 : RTS , DTR
(2)输入位4个 : DSR , CTS , CD , RI

一般地: 对输出位的操作用setXXX, 对输入位的操作用getXXX,
其中XXX是上面六个管脚名字。具体操作参见后面的位检测线程代码.

其实, RTS , DTR也可以兼做输入位的,只是容易引起混乱,我这里不考虑它们的输入方向(理论上兼做输入位时需要事先用setXXX令其拉为高电平)。

我这里只需用一个输出位: RTS(7脚)及两个输入位: DSR(6脚)和CTS(8脚)来完成测试,其余的输出位和输入位有待你的进一步开发。

另外在本测试中我需要 RTS(7脚)恒定输出为高电平(实测为+6.2V)用于模拟电源+V, 以便在K1/K2开关按下后令DSR和CTS电平翻转。
对前面的APP的JAVA源代码的方法分别做如下增加即可:

  1. 增加两个全局变量:
//使用PL2303的输入位检测线程要用到的变量
    //按钮原始背景色
    private int nSwitchOldColor =0 ;
    //退出输入位检测线程之标记
    private boolean bSwitchExit =false ;
  1. 增加如下核心代码块:输入位检测线程
    <本源码系JGB联合商务组原创作品,引用请标明出处>
//监视使用 PL2303 芯片的 USB-RS232 桥接线的两个外部轻触开关的按下动作
    //确保已事先令RTS(7脚 : 请求发送)为高电平
    //两个外部轻触开关的输入端分别是:DSR(6脚: 数据准备好) 和 CTS(8脚 : 清除发送)
    private void monitorExtSwitch(){

        //直接创建并启动一个匿名线程接口类的实例
        new Thread(new Runnable() {

            //20ms 步长计数器
            int cnt =0 ;

            //两个抗干扰计数器, 原理: 按钮需要持续按下并稳定保持200ms才算此按下动作有效
            int nDSR = 0;
            int nCTS = 0;

            @Override
            public void run() {

               while(!bSwitchExit) {

                  try {

                     if ((portA !=null) && portA.isOpen()) {

                         if ((cnt % 50) == 0){
                             //每隔1S令RTS(7脚 : 请求发送)为高电平
                             portA.setRTS(true);
                         }

                        //DSR外部轻触开关的按下
                        if (portA.getDSR()) {

                            nDSR += 1;
                            //稳定保持10*20ms以上
                            if (nDSR == 10) {
                                //UI界面的按钮使能
                                runOnUiThread(() -> {
                                    editRead.append("DSR外部轻触开关已按下\n");
                                    //[点亮]按钮背景色为红色
                                    buttLedxOn.setBackgroundColor(Color.RED);

                                });
                            }

                        } else {
                            nDSR = 0;
                            runOnUiThread(() -> {
                                //[点亮]按钮背景色恢复正常
                                buttLedxOn.setBackgroundColor(nSwitchOldColor);
                            });
                        }

                        //CTS外部轻触开关的按下
                        if (portA.getCTS()) {

                            nCTS += 1;
                            //稳定保持10*20ms以上
                            if (nCTS == 10) {
                                //UI界面的按钮使能
                                runOnUiThread(() -> {
                                    editRead.append("CTS外部轻触开关已按下\n");
                                    //[熄灭]按钮背景色为红色
                                    buttLedxOff.setBackgroundColor(Color.RED);

                                });
                            }


                        } else {
                            nCTS = 0;
                            runOnUiThread(() -> {
                                //[熄灭]按钮背景色恢复正常
                                buttLedxOff.setBackgroundColor(nSwitchOldColor);
                            });

                        }

                     }

                     //每隔20ms轮询一次
                     Thread.sleep(20);
                     cnt +=1 ;

                  } catch (Exception e) {
                      e.printStackTrace();
                  }


               }   //while(!bSwitchExit)   end


            }   //public void run() ... end

        }).start();


    }
  1. 按下【打开】按钮时增加: 开启输入位检测线程
    按下【关闭】按钮时增加: 退出输入位检测线程
public void onClick(View v) {

      // ......

       case R.id.buttOpen:

                //打开USB串行口A(可能需要运行期授权),(与原来相同)
                //......


                //目前仅知道PL2303芯片的九针桥接线支持直连两个外部轻触开关
                String strOpen = (String) spChip.getSelectedItem();
                if ( strOpen.indexOf("PL2303")>=0  ){

                    //保存按钮原始背景色
                    nSwitchOldColor =getResources().getColor(R.color.purple_500) ;
                    //清除监视两个外部轻触开关的位检测线程之标记
                    bSwitchExit =false ;
                    //开启监视外部轻触开关动作的位检测线程
                    monitorExtSwitch();
                    editRead.append("PL2303-监视直连开关的线程已开启\n");

                }

                break;


       case R.id.buttClose:


                //退出监视直连的两个外部轻触开关之位检测线程
                String strClose = (String) spChip.getSelectedItem();
                if ( strClose.indexOf("PL2303")>=0  ){
                    bSwitchExit =true ;
                    editRead.append("PL2303-监视直连开关的线程已退出\n");
                }

                 //关闭USB串行口A(与原来相同)
                 //......
                 
                break;

       // ......

}
  1. 活动销毁时增加 : 退出输入位检测线程
protected void onDestroy() {
        
        // ......
        
        //需退出监视两个外部轻触开关的输入位检测线程
        bSwitchExit =true ;


    }