I'm going to start with the app in my previous post and try to add timebase functions. I'll put that on the scope and see how accurate it is. I'll also try generating precise tones and measuring them.
Found this useful page on android system clock
http://developer.android.com/reference/android/os/SystemClock.html
This is a little trickier than it might seem, because I need to use a handler rather than tying up the phone 100% in a timing loop waiting for an event. But for now we need to get a clock running.
http://stackoverflow.com/questions/526524/android-get-time-of-chronometer-widget
This page had an example of accessing a chronometer that I was able to integrate to make a time base
However I did not need all the functionality in this example. just mChronometer.start, stop and setBase. Added buttons to start, stop and clear the timer. I get an output readout of ms, which is useful, but limits my maximum frequency to 1/2ms = 500Hz for a string of 101010 at this rate. Boo, Not good enough for most serial transmission applications.
Went off to try audio tone generation. Found some examples of playing tones, this was the best, incorporated this code that used the audio sample rate to set a tone frequency
http://stackoverflow.com/questions/2413426/playing-an-arbitrary-tone-with-android
I thought this might lead me to a faster timebase to use, but it turns out this sets a sample rate on the audio player as the timebase, it doesn't use the system clock. I'll put this on the scope and see how accurate it is.
Having an annoying problem that the emulator sound doesn't play, but my phone's does!
Went to Run->configurations and added -useaudio in the command line options field.
Didn't work. I'm not going to figure out this issue right now. I'll have to use the real phone for development for the moment. The computer audio is windows-fied.
Took a side trip and polished my app for a couple hours, working around making the image change when buttons were pressed so that I could learn to control what was on the screen and make GUIs.
http://developer.android.com/guide/topics/graphics/2d-graphics.html was a good example. Now I have a GUI with buttons, checkboxes, a picture that changes in response to button pushes, a timer, data entry, the camera LED turns on and off and a beep is played if a box is checked. Basically a starting point for many apps. I piled in every function I could think of.
Using an audio jack cable connected to the phone, I measured the frequency of the beep on the oscilloscope, it was about 1 / 2.3ms = 434.78Hz. Probably was really the 440Hz that was programmed by private final double freqOfTone = 440; // hz. I just couldn't measure it more accurately.
Next I set the frequency to 5000 Hz, knowing that the audio sample rate is 8000 Hz, so I am above Nyquist and the waveform is going to be distorted and aliased. The frequency is wrong because of the aliasing. Here is what it looked like:
I'll need to see if I can up the sample rate to 16000. Wow, that sounds a lot better. Much purer tone. Here is 5Khz at 16KHz sample.
Now here is 5kHz at 32kHz sample rate. Now we are in business. This tone sounds pure and the amplitude isn't getting modulated by much as it beats against the sample frequency.
So where are we? I'm up and comfortable with creating apps, having fun, although I'm sure I'd make a programmer cringe with my code. The system clock is in milliseconds, and is too slow to be very useful for applications other than human interface. The audio playback is pretty good up to 5kHz, I didn't push the audio sample rate up higher, but I imagine it won't go much past 44KHz (CD quality). Next I'm going to have to dive deeper in the hardware to realize my goal of high frequency LED modulation.
Meanwhile I'll post my relevant code at the end of this post and perhaps look into joining the app store as a distribution method. It is also posted here:
http://code.google.com/p/arduino-java-xyzcnc/downloads/detail?name=Siliconfish%20Blink.zip&can=2&q=#makechanges
Activity.java
package com.example.android.skeletonapp;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Chronometer;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import java.lang.reflect.Method;
import java.lang.Object;
import android.content.Context;
import android.util.Log;
/**
* This class provides a basic demonstration of how to write an Android
* activity. Inside of its window, it places a single view: an EditText that
* displays and edits some internal text.
*/
public class SkeletonActivity extends Activity {
// sets up menu items for the pop up menu
static final private int BACK_ID = Menu.FIRST;
static final private int CLEAR_ID = Menu.FIRST + 1;
static final private int BEEP_ID = Menu.FIRST + 2;
static final private int NOFLASH_ID = Menu.FIRST + 3;
// sets up the objects that are on the screen
private EditText mEditor;
private ImageView mImage;
private TextView mTextView;
private CheckBox mCheckBox;
private EditText mFreq;
Chronometer mChronometer;
// name for the exception debug log
private static final String TAG = "sifish.blink";
// variables for tone generation
private final int duration = 3; // seconds
private final int sampleRate = 32000;
private final int numSamples = duration * sampleRate;
private double sample[] = new double[numSamples];
private double infreqOfTone = 440.0; // hz
private byte generatedSnd[] = new byte[2 * numSamples];
Handler handler = new Handler();
public SkeletonActivity() {
}
/** Called with the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate our UI from its XML layout description.
setContentView(R.layout.skeleton_activity);
// Find the objects inside the screen layout xml, because we
// want to do various programmatic things with them.
mEditor = (EditText) findViewById(R.id.editor);
mImage = (ImageView ) findViewById(R.id.imageView1);
mChronometer = (Chronometer ) findViewById (R.id.chronometer1);
mTextView = (TextView) findViewById (R.id.TextView1);
mCheckBox = (CheckBox) findViewById (R.id.checkBox1);
mFreq = (EditText) findViewById(R.id.editFreq);
// Hook up button presses to the appropriate event handler.
((Button) findViewById(R.id.back)).setOnClickListener(mBackListener);
((Button) findViewById(R.id.clear)).setOnClickListener(mClearListener);
((Button) findViewById(R.id.flash)).setOnClickListener(mFlashListener);
((Button) findViewById(R.id.noflash)).setOnClickListener(mNoFlashListener);
// Do initial setups
mEditor.setText("Hello from Siliconfish!");
mImage.setImageDrawable(getWallpaper());
mFreq.setText(Double.toString(infreqOfTone));
mChronometer.setBase(SystemClock.elapsedRealtime());
mChronometer.stop();
mTextView.setText(Long.toString(SystemClock.elapsedRealtime() - mChronometer.getBase()));
genTone(infreqOfTone); //make sure the tone exists
}
/**
* Called when the activity is about to start interacting with the user.
*/
@Override
protected void onResume() {
super.onResume();
}
/**
* Called when your activity's options menu needs to be created.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// We are going to create two menus. Note that we assign them
// unique integer IDs, labels from our string resources, and
// given them shortcuts.
menu.add(0, BACK_ID, 0, R.string.back).setShortcut('0', 'b');
menu.add(0, CLEAR_ID, 0, R.string.clear).setShortcut('1', 'c');
menu.add(0, BEEP_ID, 0, R.string.beep).setShortcut('2', 'b');
menu.add(0, NOFLASH_ID, 0, R.string.noflash).setShortcut('3', 'x');
return true;
}
/**
* Called right before your activity's option menu is displayed.
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// Before showing the menu, we need to decide whether the clear
// item is enabled depending on whether there is text to clear.
menu.findItem(CLEAR_ID).setVisible(mEditor.getText().length() > 0);
return true;
}
/**
* Called when a menu item is selected.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case BACK_ID:
finish();
return true;
case CLEAR_ID:
mEditor.setText("");
return true;
case BEEP_ID:
mEditor.setText("! BEEP !");
// Play a sound Use a new thread as this can take a while
final Thread thread = new Thread(new Runnable() {
public void run() {
genTone(infreqOfTone);
handler.post(new Runnable() {
public void run() {
playSound();
}
});
}
});
thread.start();
return true;
case NOFLASH_ID:
mEditor.setText("MENU - NO FLASH");
setOn(false, null);
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A call-back for when the user presses the back button.
*/
OnClickListener mBackListener = new OnClickListener() {
public void onClick(View v) {
finish();
}
};
/**
* A call-back for when the user presses the clear button.
*/
OnClickListener mClearListener = new OnClickListener() {
public void onClick(View v) {
mEditor.setText("");
mChronometer.setBase(SystemClock.elapsedRealtime());
mImage.setImageDrawable(getWallpaper());
mTextView.setText(Long.toString(SystemClock.elapsedRealtime() - mChronometer.getBase()));
}
};
/**
* A call-back for when the user presses the flash button.
*/
OnClickListener mFlashListener = new OnClickListener() {
public void onClick(View v) {
setOn(true, null);
mChronometer.start();
mEditor.setText("*FLASH*");
mImage.setImageResource(R.drawable.scifiarmy_small);
mTextView.setText(Long.toString(SystemClock.elapsedRealtime() - mChronometer.getBase()));
if(mCheckBox.isChecked() ) {
//get the value entered by the user
try {
infreqOfTone = Double.parseDouble(mFreq.getText().toString());
} catch(NumberFormatException nfe) {
infreqOfTone = 440.0;
Log.e(TAG, "bad frequency input", nfe);
}
mEditor.setText(Double.toString(infreqOfTone));
// Play a sound Use a new thread as this can take a while
final Thread thread = new Thread(new Runnable() {
public void run() {
genTone(infreqOfTone);
handler.post(new Runnable() {
public void run() {
try {
playSound();
}
catch (Exception e) {
Log.e(TAG, "playSound problem", e);
}
}
});
}
});
thread.start();
} //if checked
}
};
/**
* A call-back for when the user presses the no flash button.
*/
OnClickListener mNoFlashListener = new OnClickListener() {
public void onClick(View v) {
setOn(false, null);
mChronometer.stop();
mEditor.setText("It is DARK");
mImage.setImageResource(R.drawable.thorsmall);
mTextView.setText(Long.toString(SystemClock.elapsedRealtime() - mChronometer.getBase()));
}
};
// code largely copied from http://www.java2s.com/Open-Source/Android/Tools/quick-settings/com/bwx/bequick/flashlight/Droid22Flashlight.java.htm
// Controls the camera flash LED
private Object mManager;
public boolean isOn(Context context) {
try {
Object manager = getManager();
if (manager != null) {
Method getFlashlightEnabledMethod = manager.getClass()
.getMethod("getFlashlightEnabled");
return (Boolean) getFlashlightEnabledMethod
.invoke(manager);
}
} catch (Exception e) {
Log.e(TAG, "", e);
}
return false;
}
public void setOn(boolean on, Context context) {
try {
Object manager = getManager();
if (manager != null) {
Method setFlashlightEnabledMethod = manager.getClass()
.getMethod("setFlashlightEnabled",
boolean.class);
setFlashlightEnabledMethod.invoke(manager, on);
}
} catch (Exception e) {
Log.e(TAG, "", e);
}
}
private Object getManager() {
if (mManager == null) {
try {
Class<?> managerClass = Class
.forName("android.os.ServiceManager");
Method methodGetService = managerClass.getMethod(
"getService", String.class);
IBinder hardwareService = (IBinder) methodGetService
.invoke(managerClass, "hardware");
Class<?> stubClass = Class
.forName("android.os.IHardwareService$Stub");
Method asInterfaceMethod = stubClass.getMethod(
"asInterface", IBinder.class);
mManager = asInterfaceMethod.invoke(stubClass,
hardwareService);
} catch (Exception e) {
Log.e(TAG, "", e);
}
}
return mManager;
}
// http://stackoverflow.com/questions/2413426/playing-an-arbitrary-tone-with-android
// functions for tone generation
void genTone(double freqOfTone){
// fill out the array
for (int i = 0; i < numSamples; ++i) {
sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone));
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
for (double dVal : sample) {
// scale to maximum amplitude
short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
void playSound(){
final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, numSamples,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
audioTrack.play();
}
}