天空闲话 发表于 2024-7-19 14:36:58

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

 1.前言:

        本文章写于 2024通院微控竣事阶段。首先微控这个课吧,很耗费经历,对于很多组来说都是一道坎,尤其是头一次接触的上位机(安卓)部门。其次,这门课程的分工很重要,肯定要找到适合自己的三个队友,你永久不知道会分到什么样的队友,希望你不要酿成一个组里干所有活的夫役。
        由于本人在微控期间涉及了除了MCU和L610的所有部门(因为这两个有大佬队友来),以是本人想以一个初学者的角度,写一篇速通微控指南,大概会涵盖很多方面(如果我不懒的话),接下来,我先写下上位机部门的指南。由于本篇只是提供一个学习路线与debug的经历,不足之处,往大家包涵。
        对我而言,上位机部门更像是做裁缝,除了GUI别的部门真就东拼西凑,没整出来啥实际的东西,大多数时间都在写bug和debug(bushi,以是选择上位机分工,各位要有点心理预备,很多组末了都会因为上位机通宵,各位加油!
2.前期预备:

1.设备:

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

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

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

[*]Java环境配置:这个在数电课程大作业里做运算单元时应该都配过了,没配过的可以参考这篇文章:
Java环境配置
[*]Android studio版本的选择:以下Android studio简称AS。这里我建议选择2019版本,因为更加稳固,而且本文章的所有代码也是在2019版本上编写的,且AS的工程移植性极差,故建议使用2019版本。
为了方便同砚,我将如何下载和安装最新版AS的文章和2019版本的安装包都附录如下:
AS下载与安装
2019版AS下载
此外,AS的具体使用可以参考这篇文章:
AS根本使用
[*]国内镜像源:在初次使用AS大概导入信你的工程文件时候,肯定会面临gradle下载慢的问题,解决这个问题可以点击如下链接:
gradle镜像源替换
[*]众所周知,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.注意事项:


[*]肯定肯定不要拖动组件!!! 这样很轻易造成你的GUI下载下来一团乱麻。
[*]肯定要记着在Mainfest文件中定义页面优先级,不然很轻易下载一堆进来。
4.通信部门: 

        最大的boss来啦!不外不怕,咱们庖丁解牛,懂得代码用处天然做起来得心应手。
 1.硬件包的导入:

        这部门时第一步的预备部门,需要引入对应的驱动,这里呢老师们在学在西电的文件里有具体的解说,我就不外多赘述了,对应的文件也在学在西电的资料中,同砚们可以自行查阅。
2.串口助手: 

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

[*]包和导入:这些导入语句引入了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类

[*] 主类定义与成员变量定义:这些变量用于存储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; // 定义设备文件描述符

[*] onDestory:在活动销毁时,取消定时器并关闭串口。     @Override
    public void onDestroy() { // 重写onDestroy方法
      timer.cancel(); // 取消定时器
      if (devfd != -1) { // 如果设备文件描述符有效
            HardwareControler.close(devfd); // 关闭串口
            devfd = -1; // 重置设备文件描述符
      }
      super.onDestroy(); // 调用父类的onDestroy方法
    }

[*]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 + "!"); // 显示错误信息
      }
    }

[*]定时器和处理器(吸收部门):定时器每500毫秒发送一次消息给处理器,处理器检查串口是否有数据可读,如果有则读取数据并更新UI。     private final int BUFSIZE = 512; // 定义缓冲区大小
    private byte[] buf = new byte; // 定义缓冲区
    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); // 发送消息
      }
    };
[*]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.软件的签名与安装: 


[*]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
[*]签名后使用前面讲过的adb安装apk文件,应用就成功安装啦! 
5.调试方法: 


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

        玛雅,没想到一时兴起真的能写完这篇文章,这篇文章也是我的第一篇博客,希望能为学弟学妹们提供力所能及的资助。由于本文只是指南性质,多有不足请诸位包涵。有问题可以在品评区问我,看到就会回复!末了感谢你能够看到这里!
        微控这门课说难不难,说简单不简单,一个组里最少要有三个能人,能覆盖所有技术面,小组才能顺遂进行。希望大家都能调和相处,顺遂完成微控!
附录:


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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 微控指南针(1)西电通院微控上位机(安卓)指南 2024版本