社区导航

 
查看: 605|回复: 6

[设计过程分享] 用手机显示SensorTile MIC频谱

[复制链接]

4073

TA的帖子

8

TA的资源

版主

Rank: 6Rank: 6

发表于 2017-2-24 16:09:53 | 显示全部楼层 |阅读模式


SensorTile评估板上集成了一颗数字麦克风MP34DT04

官方提供了对应的例程AudioLoop

可以用来读取麦克风音频数据再通过耳机孔播放出来

AudioLoop的麦克风驱动和BLE驱动结合起来

再加入CMSISFFT代码就实现了采集麦克风数据

通过FFT转换得到频谱信息,再通过蓝牙发送到手机

手机接收到频谱数据通过chart显示出来

为了简化开发,蓝牙通信使用了广播方式将数据发送出来

过程中手机和SensorTile不建立链接

因为广播数据包的长度限制,一次只能广播22个字节数据

所以过程中只提取FFTl转换后的5个最大值对应的数据和索引

实现的效果如下

ezgif-2-0120fcc51c.gif

 

ezgif-2-cdfea704dd.gif

 

Android端使用了hello charts图表控件,开始的时候使用html+js的方式,数据太多显示起来会比较卡

Android代码如下

package cn.int8.sensortile.sensortilefft;

 

import android.bluetooth.BluetoothAdapter;

import android.bluetooth.BluetoothDevice;

import android.bluetooth.BluetoothManager;

import android.content.Context;

import android.content.pm.PackageManager;

import android.graphics.Color;

import android.os.Bundle;

import android.os.Looper;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;

import android.view.View;

 

import java.util.ArrayList;

import java.util.List;

 

import lecho.lib.hellocharts.gesture.ZoomType;

import lecho.lib.hellocharts.model.Axis;

import lecho.lib.hellocharts.model.AxisValue;

import lecho.lib.hellocharts.model.Line;

import lecho.lib.hellocharts.model.LineChartData;

import lecho.lib.hellocharts.model.PointValue;

import lecho.lib.hellocharts.model.ValueShape;

import lecho.lib.hellocharts.model.Viewport;

import lecho.lib.hellocharts.view.LineChartView;

 

public class MainActivity extends AppCompatActivity {

 

    private final static String TAG = MainActivity.class.getSimpleName();

    private LineChartView lineChart;

    private List<PointValue> mPointValues = new ArrayList<PointValue>();

    private List<AxisValue> mAxisXValues = new ArrayList<AxisValue>();

 

    private BluetoothAdapter mBluetoothAdapter;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        lineChart = (LineChartView)findViewById(R.id.line_chart);

        initLineChart();//初始化

 

        if (!getPackageManager().hasSystemFeature(

                PackageManager.FEATURE_BLUETOOTH_LE)) {

            finish();

        }

        final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

        mBluetoothAdapter = bluetoothManager.getAdapter();

        if (mBluetoothAdapter == null) {

            finish();

            return;

        }

        mBluetoothAdapter.enable();

    }

    @Override

    protected void onResume() {

        super.onResume();

        scanLeDevice(true);

        Log.i(TAG, "startLeScan");

    }

    @Override

    protected void onPause() {

        super.onPause();

        scanLeDevice(false);

        Log.i(TAG, "stopLeScan");

    }

    private void scanLeDevice(final boolean enable) {

        if (enable) {

            mBluetoothAdapter.startLeScan(leScanCallback);

        } else {

            mBluetoothAdapter.stopLeScan(leScanCallback);

        }

    }

    private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {

        @Override

        public void onLeScan(final BluetoothDevice device, int rssi,final byte[] scanRecord) {

            String mac = device.getAddress();

            if(!mac.equals("FF:FF:FF:FF:FF:FF"))

            {

                return;

            }

            if (Looper.myLooper() == Looper.getMainLooper()) {

                drawFFT(scanRecord);

            } else {

                runOnUiThread(new Runnable() {

                    @Override

                    public void run() {

                        drawFFT(scanRecord);

                    }

                });

            }

        }

    };

    public static int getMax(int[] arr) {

        int max = arr[0];

        for (int i = 1; i < arr.length; i++) {

            if (arr[i] > max) {

                max = arr[i];

            }

        }

        return max;

    }

    public short getShort(byte[] b, int index) {

        return (short) (((b[index + 0] << 8) | b[index + 1] & 0xff));

    }

    public void drawLines(int[] yValues)

    {

        mAxisXValues.clear();

        mPointValues.clear();

        for(int i=0;i<yValues.length;i++) {

            mAxisXValues.add(new AxisValue(i).setLabel("" + i));

            mPointValues.add(new PointValue(i, yValues[i]));

        }

        initLineChart();

    }

    public void drawFFT(byte[] scanData)

    {

        mAxisXValues.clear();

        mPointValues.clear();

        int [] maxValues = new int[5];

        int [] maxIndexs = new int[5];

        for(int i=0;i<5;i++) {

            maxValues[i] =  getShort(scanData, i * 4 + 2);

            maxIndexs[i] = getShort(scanData, i * 4 + 2 + 2);

        }

        int maxIndex = getMax(maxIndexs);

        maxIndex = (int)(maxIndex * 1.2);

        if(maxIndex < 1000) maxIndex = 1000;

        for(int i=0;i<maxIndex;i++)

        {

            if(i%10 == 0)

            {

                mAxisXValues.add(new AxisValue(i).setLabel( i + "Hz"));

                mPointValues.add(new PointValue(i, 0));

            }

            for(int j=0;j<maxIndexs.length;j++)

            {

                if(maxIndexs[j] == i) {

                    mAxisXValues.add(new AxisValue(i).setLabel(+ "Hz"));

                    mPointValues.add(new PointValue(i, maxValues[j]));

                }

            }

        }

        initLineChart();

    }

    /**

     * 初始化LineChart的一些设置

     */

    private void initLineChart(){

        Line line = new Line(mPointValues).setColor(Color.parseColor("#FFCD41"));  //折线的颜色

        List<Line> lines = new ArrayList<Line>();

        line.setShape(ValueShape.CIRCLE);//折线图上每个数据点的形状  这里是圆形 (有三种 ValueShape.SQUARE  ValueShape.CIRCLE  ValueShape.SQUARE

        line.setCubic(false);//曲线是否平滑

        line.setStrokeWidth(1);//线条的粗细,默认是3

        line.setFilled(false);//是否填充曲线的面积

        line.setHasLabels(false);//曲线的数据坐标是否加上备注

//      line.setHasLabelsOnlyForSelected(true);//点击数据坐标提示数据(设置了这个line.setHasLabels(true);就无效)

        line.setHasLines(true);//是否用直线显示。如果为false 则没有曲线只有点显示

        line.setHasPoints(false);//是否显示圆点 如果为false 则没有原点只有点显示

        lines.add(line);

        LineChartData data = new LineChartData();

        data.setLines(lines);

 

        //坐标轴

        Axis axisX = new Axis(); //X

        axisX.setHasTiltedLabels(true);  //X轴下面坐标轴字体是斜的显示还是直的,true是斜的显示

//      axisX.setTextColor(Color.WHITE);  //设置字体颜色

        axisX.setTextColor(Color.parseColor("#D6D6D9"));//灰色

 

        axisX.setName("频率(Hz)");  //表格名称

        axisX.setTextSize(11);//设置字体大小

//      axisX.setMaxLabelChars(1); //最多几个X轴坐标,意思就是你的缩放让X轴上数据的个数7<=x<=mAxisValues.length

        axisX.setValues(mAxisXValues);  //填充X轴的坐标名称

        data.setAxisXBottom(axisX); //x 轴在底部

//      data.setAxisXTop(axisX);  //x 轴在顶部

        axisX.setHasLines(true); //x 轴分割线

 

 

        Axis axisY = new Axis();  //Y

        axisY.setName("幅度(CODE)");//y轴标注

        axisY.setTextSize(11);//设置字体大小

        data.setAxisYLeft(axisY);  //Y轴设置在左边

        //data.setAxisYRight(axisY);  //y轴设置在右边

        //设置行为属性,支持缩放、滑动以及平移

        lineChart.setInteractive(true);

        lineChart.setZoomType(ZoomType.HORIZONTAL);  //缩放类型,水平

        lineChart.setMaxZoom((float) 1);//缩放比例

        lineChart.setLineChartData(data);

        lineChart.setVisibility(View.VISIBLE);

 

        Viewport v = new Viewport(lineChart.getMaximumViewport());

        v.left = 0;

        v.right= 7;

        lineChart.setCurrentViewport(v);

    }

}

 

SensorTile端主要代码

 

/* Includes ------------------------------------------------------------------*/

#include "cube_hal.h"

 

/* USER CODE BEGIN Includes */

#include <string.h>

#include "hal_types.h"

#include "hci.h"

#include "eddystone_beacon.h"

 

#include "stm32_bluenrg_ble.h"

#include "stm32xx_it.h"

#include "component.h"

#include "SensorTile_accelero.h"

#include "SensorTile_audio_in.h"

 

#include "arm_math.h"

#include "arm_const_structs.h"

/* USER CODE END Includes */

 

/** @addtogroup X-CUBE-BLE1_Applications

 *  @{

 */

 

/** @defgroup Beacon

 *  @{

 */

 

/** @defgroup MAIN

 * @{

 */

 

/* Private variables ---------------------------------------------------------*/

 

/* USER CODE BEGIN PV */

/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/* Private define ------------------------------------------------------------*/

#define AUDIO_CHANNELS                      1

#define AUDIO_SAMPLING_FREQUENCY                16000

#define AUDIO_IN_BUF_LEN   (AUDIO_CHANNELS*AUDIO_SAMPLING_FREQUENCY/1000) //16byte

 

#define FFT_LEN                     2048              //2048/16 = 128

/* Private macro -------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/

 

uint16_t PCM_Buffer[AUDIO_IN_BUF_LEN];

 

extern volatile float mic1_results[];

 

static void *LSM303AGR_X_0_handle = NULL;

 

/*--------------------fft----------------------------*/

//FFT输入数据

static float32_t fft_data[FFT_LEN];

extern float32_t fft_data1[FFT_LEN];

//取模

static float32_t fft_mod[FFT_LEN/2];

uint32_t fftSize = FFT_LEN/2;

uint32_t ifftFlag = 0;

uint32_t doBitReverse = 1;

int ii = 0;

uint8_t adv[22];

uint8_t advIndex = 0;

 

 

uint16_t maxValues[5];

uint16_t maxFqc[5];

uint8_t doFFT = 0;

/** @defgroup MAIN_Private_Function_Prototypes

 * @{

 */

/* Private function prototypes -----------------------------------------------*/

void SystemClock_Config(void);

void BlueNRG_Init(void);

void MX_GPIO_Init(void);

void fft(void);

 

/* USER CODE BEGIN PFP */

/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/**

 * @}

 */

 

/* USER CODE BEGIN 0 */

void EnterStopMode(void);

/* USER CODE END 0 */

 

/**

 * @brief  Main function to show how to use the BlueNRG Bluetooth Low Energy

 *         expansion board to implement a Eddystone Beacon device.

 *

 * @param  None

 * @retval None

 */

SensorAxes_t acceleration;

 

void initAcceleroSensor(void)

{

  if (BSP_ACCELERO_Init( LSM303AGR_X_0, &LSM303AGR_X_0_handle ) != COMPONENT_OK)

  {

//    while(1);

  } 

  BSP_ACCELERO_Sensor_Enable( LSM303AGR_X_0_handle );

}

 

void Accelero_Sensor_Handler( void *handle )

{

  uint8_t id;

  uint8_t status;

 

  BSP_ACCELERO_Get_Instance( handle, &id );

 

  BSP_ACCELERO_IsInitialized( handle, &status );

 

  if ( BSP_ACCELERO_Get_Axes( handle, &acceleration ) == COMPONENT_ERROR )

  {

    acceleration.AXIS_X = 0;

    acceleration.AXIS_Y = 0;

    acceleration.AXIS_Z = 0;

  }

  else

  {

   

  }

}

 

int main(void)

{

  //HAL_EnableDBGStopMode();

 

  /* MCU Configuration----------------------------------------------------------*/

 

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  HAL_Init();

 

  /* Configure the system clock */

  SystemClock_Config();

 

  initAcceleroSensor();

  /* Initialize the BlueNRG SPI */

  BNRG_SPI_Init();

 

  /* Initialize the BlueNRG HCI */

  HCI_Init();

 

  /* Reset BlueNRG hardware */

  BlueNRG_RST();

 

  /* Init BlueNRG protocol */

  BlueNRG_Init();

 

 //   sprintf(buf,"\r\nhello:%d!",i);

 //   UpdateAdvStr(buf);

  /* Initialize beacon services */

  if (EDDYSTONE_BEACON_TYPE & EDDYSTONE_UID_BEACON_TYPE)

  {

    EddystoneUID_Start();

  }

  if (EDDYSTONE_BEACON_TYPE & EDDYSTONE_URL_BEACON_TYPE)

  {

    EddystoneURL_Start();

  }

  /* Configure Audio Input peripheral - DFSDM */ 

  BSP_AUDIO_IN_Init(AUDIO_SAMPLING_FREQUENCY, 16, AUDIO_CHANNELS); 

 

  /* Start Microphone acquisition */

  BSP_AUDIO_IN_Record(PCM_Buffer,0);

 

  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

  /* USER CODE END WHILE */

 

  /* USER CODE BEGIN 3 */

//    __WFI();

    HCI_Process();

//    EnterStopMode();

//   

//    HAL_Delay(100);

//    Accelero_Sensor_Handler(LSM303AGR_X_0_handle);

////    sprintf(buf,"%d,%d,%d",acceleration.AXIS_X,acceleration.AXIS_Y,acceleration.AXIS_Z);

////    sprintf(buf,"x:%d,y:%d,z:%d",acceleration.AXIS_X,acceleration.AXIS_Y,acceleration.AXIS_Z);

//    adv[0] = acceleration.AXIS_X >> 8;

//    adv[1] = acceleration.AXIS_X & 0xff;   

//    adv[2] = acceleration.AXIS_Y >> 8;

//    adv[3] = acceleration.AXIS_Y & 0xff;

//    adv[4] = acceleration.AXIS_Z >> 8;

//    adv[5] = acceleration.AXIS_Z & 0xff;

//    UpdateAdvStr(adv);

  }

}

 

void fft(void)

{  

  uint8_t i;

  float32_t maxValue;

  uint32_t maxIndex;

  /* Process the data through the CFFT/CIFFT module */

  arm_cfft_f32(&arm_cfft_sR_f32_len1024, fft_data, ifftFlag, doBitReverse);

   

  arm_cmplx_mag_f32(fft_data, fft_mod, fftSize);

  advIndex = 0;

  //提取最大的5个数和索引

  for(i=0;i<5;i++)

  {

    arm_max_f32(fft_mod, fftSize / 2, &maxValue, &maxIndex);

    //直流分量

    if(maxIndex == 0)

    {

      maxValues[i] = (int)(maxValue / FFT_LEN);

    }

    else

    {

      maxValues[i] = (int)(maxValue / FFT_LEN / 2);

    }

    //频率

    maxFqc[i] = maxIndex * AUDIO_SAMPLING_FREQUENCY/fftSize;

    //添加到广播数据

    adv[advIndex++] = maxValues[i] >> 8;

    adv[advIndex++] = maxValues[i];

    adv[advIndex++] = maxFqc[i] >> 8;

    adv[advIndex++] = maxFqc[i];

    //最大值置0,为了下一次取最大值(除当前最大值以外的最大值)

    fft_mod[maxIndex] = 0;

  }

  //更新广播数据

  UpdateAdvStr(adv);

 

}

//填充音频数据到fft数组

void FillInFFT(void)

{ 

  static uint32_t IndexOut = 0;

  uint32_t indexIn;

  //如果上一次FFT未完成则退出

  if(doFFT) return;

  for(indexIn=0;indexIn<AUDIO_IN_BUF_LEN;indexIn++)

  {

    //实部

    fft_data[IndexOut++] = (int16_t)PCM_Buffer[indexIn];//fft_data1[IndexOut];;//(int16_t)PCM_Buffer[indexIn];

    //虚部

    fft_data[IndexOut++] = 0;

  } 

  //数据填满后进行FFT转换

  if(IndexOut==FFT_LEN)

  {

    doFFT = 1;

    //FFT转换

    fft();

//    memset(fft_mod,0,sizeof(fft_mod)/sizeof(fft_mod[0]));

//    memset(fft_data,0,sizeof(fft_data)/sizeof(fft_data[0]));

    IndexOut=0;

    doFFT = 0;

  }

}

/**

* @brief  Transfer Complete user callback, called by BSP functions.

* @param  None

* @retval None

*/

void BSP_AUDIO_IN_TransferComplete_CallBack(void)

{

  FillInFFT();

}

 

/**

* @brief  Half Transfer Complete user callback, called by BSP functions.

* @param  None

* @retval None

*/

void BSP_AUDIO_IN_HalfTransfer_CallBack(void)

{

  FillInFFT();

}

 

#ifdef USE_FULL_ASSERT

 

/**

   * @brief Reports the name of the source file and the source line number

   * where the assert_param error has occurred.

   * @param file: pointer to the source file name

   * @param line: assert_param error line source number

   * @retval None

   */

void assert_failed(uint8_t* file, uint32_t line)

{

  /* USER CODE BEGIN 6 */

  /* User can add his own implementation to report the file name and line number,

     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

  /* USER CODE END 6 */

 

}

 

#endif

 

 

void EnterStopMode(void)

{

  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);

}

 

/**

 * @brief  EXTI line detection callback.

 * @param  uint16_t GPIO_Pin Specifies the pins connected EXTI line

 * @retval None

 */

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

{

  HCI_Isr();

}

 

 

虾扯蛋

回复

使用道具 举报

752

TA的帖子

0

TA的资源

纯净的硅(初级)

Rank: 4

发表于 2017-2-24 16:56:11 | 显示全部楼层
好厉害啊!
大秦正声电子之家 http://yang96381.blog.163.com
提供蓝牙、zigbee芯片批量生产烧录服务:1带3脱机烧录工具,支持cc2540/cc2541,cc2530/2531,也支持nrf51822

回复 支持 反对

使用道具 举报

752

TA的帖子

0

TA的资源

纯净的硅(初级)

Rank: 4

发表于 2017-2-24 19:54:04 | 显示全部楼层
蓝牙5很快就要有实际产品了,包括智能手机以及德州仪器蓝牙mcu芯片,传输数据提高一倍。

点评

现在用BLE传大一点的数据的确有些力不从心  详情 回复 发表于 2017-2-24 22:41
大秦正声电子之家 http://yang96381.blog.163.com
提供蓝牙、zigbee芯片批量生产烧录服务:1带3脱机烧录工具,支持cc2540/cc2541,cc2530/2531,也支持nrf51822

回复 支持 反对

使用道具 举报

2762

TA的帖子

0

TA的资源

纯净的硅(初级)

Rank: 4

发表于 2017-2-24 20:24:12 | 显示全部楼层
因为广播数据包的长度限制,一次只能广播22个字节数据
这个是sensetile蓝牙本身限制还是协议限制?

点评

记错了,蓝牙规范是31个字节 这一块我掌握的也不够 在tBleStatus aci_gap_update_adv_data(uint8_t AdvLen, const uint8_t *AdvData);函数注释里有这样一句话 我在实际使用时长度最大设置到27个字节,去掉前2  详情 回复 发表于 2017-2-24 22:43
蓝牙BLE的限制  详情 回复 发表于 2017-2-24 21:44

回复 支持 反对

使用道具 举报

6055

TA的帖子

14

TA的资源

版主

Rank: 6Rank: 6

测评达人

发表于 2017-2-24 21:44:49 | 显示全部楼层
suoma 发表于 2017-2-24 20:24
因为广播数据包的长度限制,一次只能广播22个字节数据
这个是sensetile蓝牙本身限制还是协议限制?

蓝牙BLE的限制

回复 支持 反对

使用道具 举报

4073

TA的帖子

8

TA的资源

版主

Rank: 6Rank: 6

 楼主| 发表于 2017-2-24 22:41:09 | 显示全部楼层
大秦正声 发表于 2017-2-24 19:54
蓝牙5很快就要有实际产品了,包括智能手机以及德州仪器蓝牙mcu芯片,传输数据提高一倍。

现在用BLE传大一点的数据的确有些力不从心
虾扯蛋

回复 支持 反对

使用道具 举报

4073

TA的帖子

8

TA的资源

版主

Rank: 6Rank: 6

 楼主| 发表于 2017-2-24 22:43:14 | 显示全部楼层
suoma 发表于 2017-2-24 20:24
因为广播数据包的长度限制,一次只能广播22个字节数据
这个是sensetile蓝牙本身限制还是协议限制?

记错了,蓝牙规范是31个字节
这一块我掌握的也不够
在tBleStatus aci_gap_update_adv_data(uint8_t AdvLen, const uint8_t *AdvData);函数注释里有这样一句话
  1. * @brief Update advertising data.
  2. * @note This command can be used to update the advertising data for a particular AD type.
  3. *       If the AD type specified does not exist, then it is added to the advertising data.
  4. *       If the overall advertising data length is more than 31 octets after the update, then
  5. *       the command is rejected and the old data is retained.
复制代码


我在实际使用时长度最大设置到27个字节,去掉前2个字节还有25个数据可用,不知道是什么原因
虾扯蛋

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

  • 论坛活动 E手掌握

    扫码关注
    EEWORLD 官方微信

  • EE福利  唾手可得

    扫码关注
    EE福利 唾手可得

小黑屋|手机版|Archiver|电子工程世界 ( 京ICP证 060456

GMT+8, 2017-8-18 13:08 , Processed in 0.359514 second(s), 16 queries , Redis On.

快速回复 返回顶部 返回列表