机器人姿态(17):安卓系统重力感应App开发

现在的手机都具有传感器,也基本都有低功耗蓝牙。利用加速度计可以写一些游戏,例如桌面台球等等手机小游戏。再加上蓝牙就可以做一些硬件设备,例如体感控制器,挥挥手就可以控制灯泡鼠标,等等。程序实现上,重力感应比较简单,蓝牙BLE相对复杂。

  • 重力感应

Android从SDK1.5开始就支持重力传感器,也称为加速度传感器。这个传感器可以检测手机姿态,手机反转倾斜程度可以由传感器检测到,类似三轴加速度计MPU-3050。

这个主要包括传感器扫描和一个回调函数:

//变量

private SensorManager sm;
private Sensor sensor;
private SensorEventListener mySensorListener;

//ͨ通过服务得到传感器管理对象
sm = (SensorManager) MainActivity.ma.getSystemService(Service.SENSOR_SERVICE);
sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);//得到重力传感器实例

mySensorListener = new SensorEventListener() {

public void onSensorChanged(SensorEvent event) {
//传感器获取值发生改变,在此处理
x = event.values[0]; //手机横向翻滚,x>0左翻,x<0右翻
y = event.values[1]; //手机纵向翻滚,y>0后翻,y<0前翻
z = event.values[2]; //屏幕朝向上下,z>0朝上,z<0 朝下

//传感器的精度发生改变时响应此函数
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

sm.registerListener(mySensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST);
//第一个参数是传感器监听器,第二个是需要监听的传感实例

以上x/y/z单位是重力加速度m/s^-2。

重力加速度传感器

  • 蓝牙通讯

这个主要包括设备扫描、交互控制、基础服务三部分

  1. 设备扫描

一、设备扫描

private BluetoothManager mBluetoothManager;

private BluetoothAdapter mBluetoothAdapter;
private DeviceListAdapter mDeviceListAdapter;//设备列表

private BluetoothDevice mBluetoothDevice;//设备

  1. 交互控制

//变量
private ExpandableListView mGattServicesList;//服务列表
private BluetoothLeService mBluetoothLeService;//服务

private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics;//特征列表

private BluetoothGattCharacteristic mNotifyCharacteristic;//特征

private final ServiceConnection mServiceConnection = new ServiceConnection() {…

//实例化ServiceConnection接口;重写onServiceConnected和onServiceDisconnected…

public void onServiceConnected(ComponentName componentName, IBinder service){

BluetoothLeService mBluetoothLeService =;//在这初始化这个关键服务类

private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {…

//处理蓝牙服务触发的事件

displayGattServices(mBluetoothLeService.getSupportedGattServices());

displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));

//可以设置一个按钮主动发送字符出去用于测试

mBluetoothLeService.sendSetting();

… …

public void onCreate(Bundle savedInstanceState) {…

Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);//在onCreate()里新建Intent

bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);//并绑定服务

//注意后面需要unbindService

 

protected void onResume() {…

registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());//注册分发事件的receiver,注意后面需要unregisterReceiver

boolean result = mBluetoothLeService.connect(mDeviceAddress);//建立服务

private void displayGattServices(List<BluetoothGattService> gattServices) {//示例

 

List<BluetoothGattService> gattServices;

//服务的gat列表

ArrayList<HashMap<String, String>>
gattServiceData;

//服务的MAP表

 

ArrayList<ArrayList<BluetoothGattCharacteristic>>;//特征的gat列表们

ArrayList<ArrayList<HashMap<String, String>>>
gattCharacteristicData;//特征的MAP表们

 

for (BluetoothGattService gattService : gattServices) {

//对于每项的gat服务

HashMap<String, String> newServiceData;

//对应该项的MAP服务

 

–ArrayList<BluetoothGattCharacteristic> newgattCharas;//特征的gat表们

–ArrayList<HashMap<String, String>> newCharacteristicGroupData;//特征的MAP表们

for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {

  1. 基础服务

//GATT负责BLE连接读写属Profile通用规范。Service是Characteristic的集合,例如一个“Heart Rate Monitor”的service可能包含多个Characteristics,其中一个Characteristic可能叫做“heart rate measurement”。Characteristic为一个数据类型,包括一个value以及零到多个对此value的描述(Descriptor),Descriptor对Characteristic的描述一般例如范围、计量单位等。

//变量

private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;

private BluetoothGatt mBluetoothGatt;

private int mConnectionState = STATE_DISCONNECTED;

//需要注意一些函数调用是异步的,需要得到的值不会立即返回,而会在BluetoothGattCallback的回调函数中返回,例如discoverServices与onServicesDiscovered回调。例如readCharacteristic要在onCharacteristicRead里面。setCharacteristicNotification要在onCharacteristicChanged里面。 writeCharacteristic会到onCharacteristicWrite里面。

//操作的回调函数CB。重写接收到的消息的处理。

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

//—1—状态改变

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

if (newState == BluetoothProfile.STATE_CONNECTED) {

–String intentAction = ACTION_GATT_CONNECTED;

–broadcastUpdate(intentAction);//广播意图,在交互控制里面处理

mBluetoothGatt.discoverServices();//发起活动,在交互控制里面处理

//—2—发现服务

public void onServicesDiscovered(BluetoothGatt gatt, int status) {

broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);//广播意图,在交互控制里面处理

//寻找服务之后,我们就可以和设备进行通信,比如下发配置值,获取设备电量什么的

//—3—读取到值

public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {

if (characteristic.getUuid().toString().equals(“00002a19-0000-1000-8000-00805f9b34fb”)){//根据UUID来判断读到的是什么值

broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic););//广播意图,在交互控制里面处理

//—4—收到上报值

public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {

//—5—特征被写

public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status)

{

//根据 characteristic.getValue()来判断是哪个值发送成功了

//比如连接上设备之后有一大串命令需要下发,调用多次写命令, 这样需要判断是不是所有命令都成功了

//操作的主动方法。常规用到的有:connect,discoverServices,disconnect,readCharacteristic,setCharacteristicNotification,getServices

//—1—connect

public boolean connect(final String address) {

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);//连接成功,到回调onConnectionStateChange

//—2—disconnect

public void disconnect() {

//—3—read

public void readCharacteristic(BluetoothGattCharacteristic characteristic) {

mBluetoothGatt.readCharacteristic(characteristic);

//—4—读操作示例

public void readBatrery(){

//—5—写操作示例
public void sendSetting(){

  • 读写示例

//中心设备向外围设备的写入命令字

public void WriteSetting(){
BluetoothGattService sendService
= mBluetoothGatt.getService(UUID.fromString(“0000fff0-0000-1000-8000-00805f9b34fb”));
//此处的0000180f…取决于询问硬件
if(sendService!=null) {
BluetoothGattCharacteristic sendCharacteristic
= sendService.getCharacteristic(UUID.fromString(“0000fff1-0000-1000-8000-00805f9b34fb”));
//此处的0000180f…取决于询问硬件
if(sendCharacteristic!=null) {
sendCharacteristic.setValue //(new byte[] { 0x01,0x02,0x03  });
(new byte[]  {0x24,0x4d,0x3c,} );//命令字
mBluetoothGatt.writeCharacteristic(sendCharacteristic);//写命令到设备,

}

//中心设备向外围设备的读取信息

低功耗蓝牙4.0BLE通讯

机器人姿态(15):安卓系统无线通讯App开发

  • 无线通讯

小车、平衡车、飞行器等,出于调试和使用目的,需要不依赖线缆连接的通讯功能。这个通常是手机。

小车或飞行器的MCU本身提供的是串口,如果需要无线的数据转发,要用到数据透传。所谓透传, 就是MCU把数据通过串口经由透传模块以无线电波而非线缆连接的形式传递出去。

T宝有很多现成模块:蓝牙透传(10米),wifi透传(100米),远距离的有2.4g透传(10km),目前常用的是蓝牙。

  • 蓝牙透传

蓝牙透传常见的有两种:一种使用BC417芯片的是蓝牙2.0版本,一般手机蓝牙都支持;另一种使用CC2540芯片的是蓝牙4.0版本,也就是BLE:Bluetooth Low Energy,要求手机硬件在4.0以上同时要求Android4.3(API level 16)以上,这样才支持BLE协议。

只要把这种模块的Rx/Tx连接到MCU相应串口的Rx/Tx,同时接好VCC/GND,就可以正常工作。

  • 手机程序

检验是否工作有两种方式:一个是在手机安装蓝牙App,通过App可以接收到发来的串口数据;一种是在PC机另安装一个蓝牙透传模块,也可以接收到数据。

要注意的地方,是在通过AT-command修改透传模块的波特率的时候,要注意以0x0A/0x0D结尾。

安卓App程序,整体很简单。

注意不要操作常规Services和Characteristics,只要根据UUID判断出硬件的蓝牙特征码,然后操作特征码即可。

Setting: 9600, Both NL and CR.
AT
OK
AT+BAUD=?
OK:BAUD=9600
AT+NAME=?
OK:NAME=LanQianTech
AT+ROLE=?
OK:ROLE=S
M: 主设备
S: 从设备

常见的问题,一般是App软件中的特征码和硬件模块的特征码不匹配,那么通信不成功。

机器人姿态(13):姿态控制(再续)

涉及的资源:中断0对左电机的旋转编码器计数,中断1对右电机编码器计数,定时器0产生20ms周期,串口1用于无线通讯。

使用中断较多,得合理分配优先级。

如果使用默认的,低优先级的这个串口中断会丢数据。

  • PID设置

PID设置

  • 效果

这个调的算是差不多:

这个振荡的厉害:

 

机器人姿态(11):姿态控制(续)

控制理论主要有经典控制、现代控制、智能控制三类。经典控制里面常用的有并联控制、串联控制两类。串联控制里面常用的是PID控制。

对于PID控制器,其传递函数为:

时域形式是

时域的另一种形式是使用时间常数

分析控制器的传递函数

可见极点位于原点,两个零点位于左平面,改变PID的三个参数可以改变零极点的位置。

按照自控原理,如果控制对象的响应稳定不受其它环节的影响,可以采用开环控制;反之如果被控对象受设定值、负载或者源端的影响而波动,应该选用闭环控制;闭环控制中常用的算法由比例、积分和微分三个部分组成,实际应用中通常只使用到其中的一项或两项,如P、PI、PD等。

  • PID

所谓比例增益,积分增益,是PID控制的参数。比例控制是最常用的控制手段之一,也是最符合人的感观的一种控制。例如控制一个加热器,设定目标温度为100度;那么当开始加热时反馈温度离设定温度相差很远,这时电流很大;当温度接近设定值100度时,则电流很小;当温度达到设定值100度时,则电流关闭。

对于滞后性不是很大的控制对象,使用比例控制方式就可以满足控制要求。

比例常与积分一起进行控制,构成PI控制,u(t) = Kp*e(t) + Ki∑e(t) +u0。积分项是一个历史误差的累积值,如果光用比例控制时要不就是达不到设定值要不就是振荡,在使用了积分项后就可以解决达不到设定值的静态误差问题。

因为PI系统中的Ki的存在会使整个控制系统的响应速度受到影响,为了解决这个问题,可在控制中增加微分项D,微分项主要用来解决系统的响应速度问题,u(t) = Kp*e(t) + Ki∑e(t) + Kd[e(t) – e(t-1)]+u0。

  • 伺服系统

众所周知伺服由3个反馈系统构成:位置环、速度环、电流环,根据使用的不同模式:力矩控制只需要调节电流环,速度控制需要调节电流环和速度环,位置控制需要调节电流环、速度环和位置环。越是内侧的环,越需要提高其响应性,不遵守这个原则,就会产生偏差和震动。

一般的,电流环为PI控制,速度环为PI控制以及力矩前馈控制,位置环为P控制以及速度前馈控制。电流环由于是最内侧的环,电机已确保其充分的响应性,所以一般只需要调整位置环和速度环。主要调整参数有三个:位置环的比例增益Kp、速度环的增益Kv和速度环积分时间常数I。

调整过程中,速度环的调整是整个系统调整中最关键的,也是最难调整的。但需要注意的是,在位置控制方式下,位置环增益和速度环参数有一定关联,当位置环增益提高对系统精度没有效果后,就需要再考虑提高速度环增益。

  • 参数整定

实凑法:

首先,设控制器积分系数I为0,微分系数D为0;控制系统投入闭环运行,由小到大改变比例系数K,让扰动信号作阶跃变化,观察控制过程,直到获得满意的控制过程为止。

其次,然后取比例系数P为当前值乘以某个经验值例如0.83,由小到大增加积分系数I,同样让扰动信号作阶跃变化,直至求得满意的控制过程。再积分系数I保持不变,改变比例系数P,观察控制过程有无改善,如有改善则继续调整,直到满意为止。否则,将原比例系数P增大一些,再调整积分系数I力求改善控制过程。如此反复试凑,直到找到满意的比例系数P和积分系数I。

最后,引入适当的微分系数D,此时可适当增大比例系数P和积分系数I。和前述步骤相同,微分系数的整定也需反复调整,直到控制过程满意为止。

经验法:

理想曲线两个波,前高后低四一剖;一看二调多分析,调节质量不会低。
参数整定找最佳,从小到大顺序查;先是比例后积分,最后再把微分加。
曲线振荡很频繁,比例度盘要放大;曲线漂浮绕大湾,比例度盘往小扳。
曲线偏离回复慢,积分时间往下降;曲线波动周期长,积分时间再加长。
曲线振荡频率快,先把微分降下来;动差大来波动慢。微分时间应加长。

振荡法:

首先,只采用比例控制,使Kp从0增加到临界值Kr,这个临界值是指系统首次出现持续振荡时的增益值(如果不论怎样调整Kp都不会出现持续振荡,则振荡法不能用),出现持续振荡后测出振荡周期Pr。然后,齐格勒-尼柯尔斯提出可以根据下表中的公式确定Kp、Ti、Td。

  • 作用

单独的P(比例)就是将差值进行成比例的运算,它的显著特点就是有差调节,有差的意义就是调节过程结束后,被调量不可能与设定值准确相等,它们之间一定有残差,残差具体值可以通过比例关系计算出。增加比例将会有效减小残差并增加系统响应,但容易导致系统激烈震荡甚至不稳定。

单独的I(积分)就是使调节器的输出信号的变化速度与差值信号成正比,如果差值大,则积分环节的变化速度大,这个环节的正比常数的比例倒数在伺服系统里通常叫为积分时间常数,积分时间常数越小意味着系统的变化速度越快,所以同样如果增大积分速度(也就是减小积分时间常数)将会降低控制系统的稳定程度,直到最后出现发散的震荡过程。这个环节最大的好处就是被调量最后是没有残差的。

单独的D(微分)就是根据差值的方向和大小进行调节的,调节器的输出与差值对于时间的导数成正比,微分环节只能起到辅助的调节作用,它可以与其他调节结合成PD和PID调节。它的好处是可以根据被调节量(差值)的变化速度来进行调节,而不要等到出现了很大的偏差后才开始动作,其实就是赋予了调节器以某种程度上的预见性,可以增加系统对微小变化的响应特性。