微控指南针(1)西电通院微控上位机(安卓)指南 2024版本

 1.前言:

        本文章写于 2024通院微控结束阶段。首先微控这个课吧,很耗费经历,对于很多组来说都是一道坎,尤其是头一次接触的上位机(安卓)部分。其次,这门课程的分工很重要,一定要找到适合自己的三个队友,你永远不知道会分到什么样的队友,希望你不要变成一个组里干所有活的苦力。

        由于本人在微控期间涉及了除了MCU和L610的所有部分(因为这两个有大佬队友来),所以本人想以一个初学者的角度,写一篇速通微控指南,可能会涵盖很多方面(如果我不懒的话),接下来,我先写下上位机部分的指南。由于本篇只是提供一个学习路线与debug的经历,不足之处,往大家海涵。

        对我而言,上位机部分更像是做裁缝,除了GUI别的部分真就东拼西凑,没整出来啥实际的东西,大多数时间都在写bug和debug(bushi,所以选择上位机分工,各位要有点心理准备,很多组最后都会因为上位机通宵,各位加油!

2.前期准备:

1.设备:

        通院采用的设备为friendly arm公司Smart4418SDK,个人认为设备主要有以下几点重要须知:

  1. 注意启动方式:设备主要有两种启动方式,SD卡启动和内存启动,详情可以看老师在学在西电课程章节中上传的视频。很多时候不一定是设备坏了,可能是误触了相关开关。
  2. 注意关机方式:一定要先长按关机再拨开关,最后拔电源线。由于设备年久失修,这样是最好的保证设备不烂在手里的方式(bushi。具体演示也可以看老师在学在西电课程章节中上传的视频。
  3. 注意打开USB调试和检查数据线:首先是打开USB调试,这个闲言少叙,大家能上这个专业多少都了解,不会的也可以去看老师发的视频。最重要的是检查发的数据线是否可用,因为很多数据线都是学长自己配的,原装数据线十不存一(不要问我怎么知道的)。所以有时候无法连接并不是你的问题,或许换一根数据线就好了。
  4. 注意UART口的选择,这里建议选择UART3,最适合接线,其余的或多或少都有问题。

 2.软件:

        软件分以下几个部分来说:

  1. Java环境配置:这个在数电课程大作业里做运算单元时应该都配过了,没配过的可以参考这篇文章:
    Java环境配置
  2. Android studio版本的选择:以下Android studio简称AS。这里我建议选择2019版本,因为更加稳定,而且本文章的所有代码也是在2019版本上编写的,且AS的工程移植性极差,故建议使用2019版本。
    为了方便同学,我将如何下载和安装最新版AS的文章和2019版本的安装包都附录如下:
    AS下载与安装
    2019版AS下载
    此外,AS的具体使用可以参考这篇文章:
    AS基本使用
  3. 国内镜像源:在初次使用AS或者导入信你的工程文件时候,一定会面临gradle下载慢的问题,解决这个问题可以点击如下链接:
    gradle镜像源替换
  4. 众所周知,AS是一款很难卸载的软件,每次需要更换版本都十分困难,这里我将引用一篇文章来为大家介绍AS的卸载:
    彻底卸载AS

 3.adb的安装与使用:

        在初期GUI制作阶段,可能通过AS里自带的程序就可以将软件下载到上位机中。但是,我们在后期需要调用串口时,需要经过签名生成apk文件,然后将apk文件安装进上位机中,如此你所编写的软件才能顺利的打开串口进行与MCU的通信。

        虽然可以通过VIVO手机助手进行链接和下载(蓝厂就是最diao的!!!),但我还是更推荐adb安装,更快,效率更高,同时还有一定的调试功能。

        这里我也是引用一篇教程,通俗易懂。至于VIVO手机助手,大家可以查阅学长的视频教学。

adb的安装及使用详解

3.GUI的界面制作:

1.GUI界面的设计:

        在大约第五周的时候,老师会让你们交一份GUI的设计稿。这个以后是要跟着后续功能的,所以说建议慎重一点,跟组员讨论敲定后再设计。

         在设计GUI时,有两种设计方式,一种类似于制作PPT,就用office和WPS就可以完成,因为这玩意本来也不高端,就是一层皮。还有一种方式就是使用Figma这款设计软件,他好就好在免费且有汉化社区,而且可以适配平板,还可以实现跳转,可以帮你理清前期逻辑,这玩意学习成本比较低,我就不在此赘述了,有兴趣的同学可以在B站上看up的课程,但是自学效率更快,具体链接如下:

Figma教程

2.GUI的制作

        这个没什么可以说的,也是看b站的速成课,同时学在西电中老师也放了一些指南。纯前端边边的东西,学习成本不高,一周左右速通差不多。具体课程附录如下:

GUI零基础

(ps:这个老师讲的挺不错的)

3.注意事项:

  1. 一定一定不要拖动组件!!! 这样很容易造成你的GUI下载下来一团乱麻。
  2. 一定要记住在Mainfest文件中定义页面优先级,不然很容易下载一堆进来。

4.通信部分: 

        最大的boss来啦!不过不怕,咱们庖丁解牛,懂得代码用处自然做起来得心应手。

 1.硬件包的导入:

        这部分时第一步的准备部分,需要引入对应的驱动,这里呢老师们在学在西电的文件里有详细的讲解,我就不过多赘述了,对应的文件也在学在西电的资料中,同学们可以自行查阅。

2.串口助手: 

        在第一次进行数据链联调时,我们往往都是先利用上位机的串口助手来进行的,这个串口助手的工程我会附录在文章末尾,具体使用不用我说大家应该都会,接下来我们将会对串口助手的java代码进行一个详解。

  1. 包和导入:这些导入语句引入了Android开发所需的各种类和接口,以及用于硬件控制的FriendlyARM库。
    package com.example.sang.testserial; // 定义包名
    
    import android.app.Activity; // 导入Activity类
    import android.content.res.Configuration; // 导入Configuration类
    import android.os.Bundle; // 导入Bundle类
    import android.view.View; // 导入View类
    import android.view.View.OnClickListener; // 导入OnClickListener接口
    import android.widget.Button; // 导入Button类
    import android.widget.EditText; // 导入EditText类
    import android.widget.ScrollView; // 导入ScrollView类
    import android.widget.TextView; // 导入TextView类
    import android.util.Log; // 导入Log类
    import android.text.Html; // 导入Html类
    import android.widget.Toast; // 导入Toast类
    import java.util.Timer; // 导入Timer类
    import java.util.TimerTask; // 导入TimerTask类
    import com.friendlyarm.FriendlyThings.HardwareControler; // 导入HardwareControler类
    import com.friendlyarm.FriendlyThings.BoardType; // 导入BoardType类
    
    import android.os.Handler; // 导入Handler类
    import android.os.Message; // 导入Message类
    import android.content.Context; // 导入Context类
    import android.content.Intent; // 导入Intent类
    
  2.  主类定义与成员变量定义:这些变量用于存储UI组件、串口配置和数据缓冲区等信息。
    public class MainActivity extends Activity implements OnClickListener { // 定义MainActivity类,继承Activity并实现OnClickListener接口
    
        private static final String TAG = "SerialPort"; // 定义日志标签
        private TextView fromTextView = null; // 定义TextView变量
        private EditText toEditor = null; // 定义EditText变量
        private final int MAXLINES = 200; // 定义最大行数
        private StringBuilder remoteData = new StringBuilder(256 * MAXLINES); // 定义StringBuilder用于存储接收到的数据
    
        // NanoPC-T4 UART4
        private String devName = "/dev/ttyAMA3"; // 定义串口设备名
        private int speed = 115200; // 定义串口波特率
        private int dataBits = 8; // 定义数据位
        private int stopBits = 1; // 定义停止位
        private int devfd = -1; // 定义设备文件描述符
    
  3.  onDestory:在活动销毁时,取消定时器并关闭串口。
        @Override
        public void onDestroy() { // 重写onDestroy方法
            timer.cancel(); // 取消定时器
            if (devfd != -1) { // 如果设备文件描述符有效
                HardwareControler.close(devfd); // 关闭串口
                devfd = -1; // 重置设备文件描述符
            }
            super.onDestroy(); // 调用父类的onDestroy方法
        }
    
  4. onCreat:在活动创建时,根据设备的方向加载不同的布局文件,初始化UI组件,并尝试打开串口。如果串口打开成功,启动定时器定期检查数据。
        @Override
        public void onCreate(Bundle savedInstanceState) { // 重写onCreate方法
            super.onCreate(savedInstanceState); // 调用父类的onCreate方法
            if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { // 如果设备处于横屏模式
                setContentView(R.layout.serialport_dataprocessview_landscape); // 设置横屏布局
            } else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { // 如果设备处于竖屏模式
                setContentView(R.layout.serialport_dataprocessview); // 设置竖屏布局
            }
    
            String winTitle = devName + "," + speed + "," + dataBits + "," + stopBits; // 构建窗口标题
            setTitle(winTitle); // 设置窗口标题
    
            ((Button)findViewById(R.id.sendButton)).setOnClickListener(this); // 设置发送按钮的点击监听器
    
            fromTextView = (TextView)findViewById(R.id.fromTextView); // 获取TextView组件
            toEditor = (EditText)findViewById(R.id.toEditor); // 获取EditText组件
    
            /* no focus when begin */
            toEditor.clearFocus(); // 清除焦点
            toEditor.setFocusable(false); // 设置不可获得焦点
            toEditor.setFocusableInTouchMode(true); // 设置在触摸模式下可获得焦点
    
            devfd = HardwareControler.openSerialPort(devName, speed, dataBits, stopBits); // 打开串口
            if (devfd >= 0) { // 如果串口打开成功
                timer.schedule(task, 0, 500); // 启动定时器,每500毫秒执行一次任务
            } else { // 如果串口打开失败
                devfd = -1; // 重置设备文件描述符
                fromTextView.append("Fail to open " + devName + "!"); // 显示错误信息
            }
        }
    
  5. 定时器和处理器(接收部分):定时器每500毫秒发送一次消息给处理器,处理器检查串口是否有数据可读,如果有则读取数据并更新UI。
        private final int BUFSIZE = 512; // 定义缓冲区大小
        private byte[] buf = new byte[BUFSIZE]; // 定义缓冲区
        private Timer timer = new Timer(); // 定义定时器
        private Handler handler = new Handler() { // 定义处理器
            public void handleMessage(Message msg) { // 重写handleMessage方法
                switch (msg.what) { // 根据消息类型进行处理
                    case 1: // 如果消息类型为1
                        if (HardwareControler.select(devfd, 0, 0) == 1) { // 检查串口是否有数据可读
                            int retSize = HardwareControler.read(devfd, buf, BUFSIZE); // 读取串口数据
                            if (retSize > 0) { // 如果读取到数据
                                String str = new String(buf, 0, retSize); // 将数据转换为字符串
                                remoteData.append(str); // 将数据追加到remoteData中
    
                                if (fromTextView.getLineCount() > MAXLINES) { // 如果TextView中的行数超过最大行数
                                    int nLineCount = fromTextView.getLineCount(); // 获取当前行数
                                    int i = 0; // 定义计数器
                                    for (i = 0; i < remoteData.length(); i++) { // 遍历remoteData
                                        if (remoteData.charAt(i) == '\n') { // 如果遇到换行符
                                            nLineCount--; // 行数减一
    
                                            if (nLineCount <= MAXLINES) { // 如果行数小于等于最大行数
                                                break; // 退出循环
                                            }
                                        }
                                    }
                                    remoteData.delete(0, i); // 删除多余的行
                                    fromTextView.setText(remoteData.toString()); // 更新TextView内容
                                } else { // 如果行数未超过最大行数
                                    fromTextView.append(str); // 追加数据到TextView
                                }
    
                                ((ScrollView)findViewById(R.id.scroolView)).fullScroll(View.FOCUS_DOWN); // 滚动到最底部
                            }
                        }
                        break;
                }
                super.handleMessage(msg); // 调用父类的handleMessage方法
            }
        };
        private TimerTask task = new TimerTask() { // 定义定时任务
            public void run() { // 重写run方法
                Message message = new Message(); // 创建消息对象
                message.what = 1; // 设置消息类型
                handler.sendMessage(message); // 发送消息
            }
        };
  6. onClick:当发送按钮被点击时,获取toEditor中的文本并通过串口发送。如果发送成功,清空输入框并更新显示区域。
        public void onClick(View v) { // 重写onClick方法
            switch (v.getId()) { // 根据点击的视图ID进行处理
                case R.id.sendButton: // 如果点击的是发送按钮
                    String str = toEditor.getText().toString(); // 获取输入框中的文本
                    if (str.length() > 0) { // 如果文本不为空
                        if (str.charAt(str.length() - 1) != '\n') { // 如果文本末尾不是换行符
                            str = str + "\n"; // 添加换行符
                        }
                        int ret = HardwareControler.write(devfd, str.getBytes()); // 发送数据到串口
                        if (ret > 0) { // 如果发送成功
                            toEditor.setText(""); // 清空输入框
    
                            str = ">>> " + str; // 添加前缀
                            if (remoteData.length() > 0) { // 如果remoteData不为空
                                if (remoteData.charAt(remoteData.length() - 1) != '\n') { // 如果remoteData末尾不是换行符
                                    remoteData.append('\n'); // 添加换行符
                                    fromTextView.append("\n"); // 在TextView中添加换行符
                                }
                            }
                            remoteData.append(str); // 将数据追加到remoteData中
                            fromTextView.append(str); // 将数据追加到TextView中
    
                            ((ScrollView)findViewById(R.id.scroolView)).fullScroll(View.FOCUS_DOWN); // 滚动到最底部
                        } else { // 如果发送失败
                            Toast.makeText(this, "Fail to send!", Toast.LENGTH_SHORT).show(); // 显示错误信息
                        }
                    }
                    break;
            }
        }
    }
    

以上就是串口助手代码的所有详解。 

3.代码的更改: 

        如果你需要发送固定字符,可以按照我下面的代码对于onClick的部分进行更改:

   int ret;
    public void onClick(View v) {
            switch (v.getId()) {

            case R.id.btn_login1_2:
                String str4 = "*d0300";
                String str5 = "*d3000";
                if (str77 == 2) {
                    ret = HardwareControler.write(devfd, str4.getBytes());//这是发送数据的代码,ret是发送的字节数的返回值
                }else{
                    ret = HardwareControler.write(devfd, str5.getBytes());
                }

                    if (ret > 0) {          //如果返回的ret大于0,说明发送成功了
                        Toast.makeText(this, "sucess!", Toast.LENGTH_SHORT).show();
                        Intent intent =null;
                        intent = new Intent(MainActivity2.this,MainActivity6.class);
                        startActivity(intent);
                    } else {
                        Toast.makeText(this, "Fail to send!", Toast.LENGTH_SHORT).show();
                    }

                break;

            case R.id.btn_login1_1:
                Intent intent = null;
                intent = new Intent(MainActivity2.this,MainActivity5.class);
                startActivity(intent);
                break;

//以上是加了按钮的代码,没有报错

            }
        }
    }

         接收部分可以添加如下代码对于数据帧进行替换:

 if (str.contains("你的数据帧")) {
                                str77=2;
                                str = str.replaceAll("你的数据帧", "想要显示的字符");
                                str = str + "\n";

                                  final Toast toast = Toast.makeText(getApplicationContext(), "Green light ahead", 1000);
                                  View toastView = toast.getView();
                                //toastView.setBackgroundResource(R.drawable.toast_bg);// 设置背景色为红色
                                TextView toastText = new TextView(getApplicationContext());
                                toastText.setText("想要显示的字符");
                                toastText.setTextColor(Color.GREEN); // 设置文本颜色为黑色
                                toastText.setTextSize(200); // 设置字体大小为100dp
                                  toast.setGravity(Gravity.CENTER, 0, 0);
                                  toast.setView(toastText); // 将TextView设置为Toast的视图
                                  toast.show();

        此外,对于只有按钮发送没有接受数据框的界面,可以查询代码详解删除对应的界面设置。

4.软件的签名与安装: 

  1. cmd操作:我们对于软件的签名有两种方式,老师在学在西电的教程都有提及,这里我们主要将用命令行来签名:
    1.打开你的工程中如下对应的路径:工程名称\app\build\outputs\apk\debug
    2.将Help1(文件打包与文末)中的 platform.pk8/platform.x509.pem/signapk.jar 粘贴进文件夹。
    3.随后打开cmd窗口,首先输入X:(X为对应的盘)命令进行跳盘操作,随后使用 cd 文件路径 的指令转到对应的文件夹下。
    4.最后输入如下命令即可完成签名。
     java -jar .\signapk.jar .\platform.x509.pem .\platform.pk8 .\app-debug.apk .\Serial1-Signed.apk
  2. 签名后使用前面讲过的adb安装apk文件,应用就成功安装啦! 

5.调试方法: 

  1. 界面闪退问题与logcat调试:
            如果碰到跳转后停止运行无法打开界面,多半时在组件定义中出现问题,比如button的定义,textview的定义等等,建议可以问问AI(喜) 。这里我们介绍一种调试方法,是利用了AS中的logcat日志调试,可以比较清楚的解决组件定义问题。
            具体教程如下链接,使用logcat在界面崩溃时可以定位到java文件的某一行,还是挺好用的。
    logcat使用详解
  2. 按钮闪退:
            如果成功进入通信页面但是点击按钮闪退,则可能是按钮对应onClick的问题,这里可以给每一步都加一个弹窗,看看到底是那一步出了问题无法发送数据,有点类似于C语言里的debug手段。

5.后记: 

        玛雅,没想到一时兴起真的能写完这篇文章,这篇文章也是我的第一篇博客,希望能为学弟学妹们提供力所能及的帮助。由于本文只是指南性质,多有不足请诸位包涵。有问题可以在评论区问我,看到就会回复!最后感谢你能够看到这里!

        微控这门课说难不难,说简单不简单,一个组里最少要有三个能人,能覆盖所有技术面,小组才能顺利进行。希望大家都能和谐相处,顺利完成微控!

附录:

  1. 所有提到的文件:串口助手工程与Help1 
  2. 调试好的一个java文件:调试(由于组员意见,设置了密码,需要用可以评论说明,讨论后会决定是否公开)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值