Android:11.0
IDE:Android Studio
应2021年信号失真度测量检查装置的题目要求,需要在手机上显示波形,因此需要开发一个手机App来完成。开发方式有多种,出于性能考量这里选择原生开发,同时需具备一点点Android开发的常识。工程代码都放在最后
BluetoothSend
开发前由于是初次接触蓝牙模块和使用手机权限,为避免可能的后续问题先开发一个简单的蓝牙收发App,测试一下蓝牙通信。原理很简单,如图
需注意HC-05模块的VCC最好要接5V,直接使用USB转TTL的3.3V可能未必能驱动。
创建
选择Empty Activity创建,创建后会有一个简单的Hello模板
想要开发蓝牙应用,需要申请权限,在AndroidMainfest.xml里需要添加下面这几条权限语句
<!-- 基础蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- 定位权限(Android 10+ 需要) --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/> <!-- Android 12+ 需要额外权限 --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> <!-- 针对Android 13的附近设备权限 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
接下来是界面和逻辑代码,以现在大语言模型的智能程度,可以很轻易地让它生成。要求是让它仅在MainActivity里生成界面和逻辑代码,便于我们观察学习。
调试
蓝牙是手机的底层硬件资源,Android Studio自带的模拟器是基于qemu的,对于蓝牙的支持有限。若想达到理想效果,须真机调试,即在手机上启用USB调试,并连接至电脑。配对成功后,一般会自动选择真机(上方会显示手机型号)
效果如下:
实测
蓝牙连接后,在电脑上通过串口助手发送数据(波特率要正确),手机上也能正确接收数据,手机上发送的数据,电脑上也能接收到
查看
对于使用蓝牙的操作,前面需要做权限检查,用以申请权限
如果未做权限检查或者不需要做不想做,那么在函数前面需要加个注解
界面开发使用的是Jetpack Compose,像这种仅使用语言来描述的声明式开发,使用AI可以快速生成相当不错的界面,不符合要求可以短时间内随时改进
稍微了解一下回调、线程和协程之类的语法,就可以很方便地进行修改了。
学习过程比较清晰,让AI生成一个简单的案例,然后理解、手动修改,有了新的需求后让AI改进,再尝试理解,形成闭环。
DistortionMeter
创建
同理,创建一个Empty Activity工程,这次采用的是MVVM模式。不过处于时间考虑,并没有完全遵循,代码可能写得一坨,能用就行
基本目录组织如下,主要分成domain(业务逻辑)、ui(界面)和viewmodel(视图模型)三个部分。ui里有三个部分,分别是components(组件)、home(主界面)和theme(主题,但这里没用到)
这里的代码开发不像前面那样,可以用AI直接生成所有代码,而是需要自己对代码框架有个初步认识,知道哪个地方该放哪些代码,需要完成什么样的功能,模块与模块之间如何协同工作。
划分为不同部分后,再针对某一部分让AI生成代码就具有针对性,更加方便且高效。
代码
可以安装一个通义灵码的插件,安装后一般在右边的侧边栏。这里面有两种模式,一种是普通的AI对话,另一种则是下面这种AI程序员,添加上下文之后,它会根据要求更改代码,点击更改后的文件即可查看它更改了哪些。如果符合预期点击Accept即可自动应用更改,省去了复制粘贴
对话框的左下角可以切换不同模型,目前里面只有qwq-plus是推理模型
发送
开发这个应用的目的是接收单片机发来的数据,我们需要考虑数据的格式。最朴素的办法就是单片机重定向printf函数到串口,使用printf发送数据。
只不过蓝牙速率有限(此处或者应该说串口速率有限),使用printf发送数据实际上发送的是字符串,既耗时也耗费容量,且是完全暴露的。更好的方式是发送一个数据包(十六进制),单片机这里方便操作,手机App这里也容易解析
以我这里的情况,我可以发送失真度、采样率、信号频率、增益、波形数据和谐波成分这些信息,那么需要把这些信息打包(共计155字节,可根据自己情况更改)。同时在这个包的前面添加两个字节作为包头,第一个一般是0xA5,后面是版本号可加可不加。包的尾部可以添加一个字节作为校验和,以确保数据包接收的安全和稳定
#pragma pack(push, 1) typedef struct { uint8_t header; // 0xA5 uint8_t version; // 0x01 float thd; // 失真度 float freq; // 频率 float rate; // 采样率 float gain; // 增益 float wave[24]; // 波形数据 float harmonics[10];// 新增的10次谐波 uint8_t checksum; // 校验和 } SensorDataPacket; #pragma pack(pop)
波形数据为24点,因为采样率固定为输入信号频率的23倍,多加一个点避免波形不完整。
接下来就是把数据正确打包发送,除了前面的初始化包头和后面的校验和计算,中间的可根据自己的情况改,其实就是把获得的数据填充到包里面
void send_bluetooth_data() { SensorDataPacket packet; // 初始化包头 packet.header = 0xA5; packet.version = 0x01; // 填充基础参数 packet.thd = read_thd_sensor(); packet.freq = read_frequency(); packet.rate = SAMPLING_RATE; packet.gain = current_gain; // 填充波形和谐波数据 read_waveform(packet.wave); read_harmonics(packet.harmonics); // 新增谐波读取函数 // 计算校验和(需包含所有数据) uint8_t* p = (uint8_t*)&packet; packet.checksum = 0; for(int i=0; i<sizeof(packet)-1; i++) { // 排除checksum自身 packet.checksum ^= p[i]; } // 发送完整数据包 bluetooth_send((uint8_t*)&packet, sizeof(packet)); }
手机App这里只需要验证包和解包即可(在BluetoothManager.kt里),需要配合前面的打包逻辑
仿真
App开发完成后,单片机部分未必做好,可以写个python脚本来验证一下。与前面一样,蓝牙模块通过USB转TTL接到电脑上,由电脑发送模拟出的数据。脚本如下,波特率和串口需自行设置
import math import serial import struct import time import random # 配置串口参数 PORT = 'COM7' # 修改为实际端口 BAUDRATE = 115200 # 生成一个完整周期的正弦波数据(24个采样点) n_samples = 24 amplitude = 2047.5 # 确保峰峰值覆盖 0-4095 范围 offset = 2047.5 # 直流偏置居中 def create_packet(): # 全局变量 global amplitude """生成符合协议的数据包""" header = bytes([0xA5, 0x01]) # 包头和版本 # 生成随机测试数据 thd = random.uniform(0.5, 12) # 失真度 0.5%~5% freq = random.uniform(1000, 20000) # 频率 50Hz-20kHz rate = 44100.0+random.uniform(-100, 100) # 采样率 gain = float(random.randint(0, 3)) # 生成24个正弦波采样点 amplitude -= 60 if amplitude < 50: amplitude = 2047.5 phase_offset = random.uniform(-math.pi / 12, math.pi / 12) # ±15度随机相位偏移 wave = [] for i in range(n_samples): phase = 2 * math.pi * i / n_samples + phase_offset # 计算当前相位 value = amplitude * math.sin(phase) + offset # 原始正弦波值 noisy_value = value + random.gauss(0, 5) # 添加噪声(标准差可调) clamped_value = int(round(max(0, min(noisy_value, 4095)))) # 限幅+取整 wave.append(float(clamped_value)) # 新增的10次谐波成分(0-1之间的归一化幅值) harmonics = [random.uniform(0, 1) for _ in range(10)] # 10次谐波 # 打包二进制数据 data = struct.pack('<4f', thd, freq, rate, gain) wave_data = struct.pack('<24f', *wave) harmonic_data = struct.pack('<10f', *harmonics) # 新增谐波数据 # 计算校验和(需要包含新增的谐波数据) checksum = 0 full_packet = header + data + wave_data + harmonic_data # 更新完整数据包 for b in full_packet: checksum ^= b return full_packet + bytes([checksum]) def main(): with serial.Serial(PORT, BAUDRATE, timeout=1) as ser: print(f"开始模拟数据发送到 {PORT}...") try: while True: packet = create_packet() ser.write(packet) print(f"发送数据包: {len(packet)}字节") time.sleep(0.4) # 100ms间隔 except KeyboardInterrupt: print("\n停止发送") if __name__ == "__main__": main()
测试如下:
失真度检测App演示
工程
gitcode
BluetoothSend:项目首页 - Bluetooth - GitCode
DistortionMeter:项目首页 - DistortionMeter:2021年电赛题失真度检测App - GitCode
github
算了,没流量了