Friday, September 04, 2015

Arduino Due: toggle open drain GPIO pin

I wanted a pin on my Arduino Due to switch between pulling down toward ground and turning off (going high impedance).

I tried using pinMode(x, INPUT);, but there's a bug in the arduino libraries that sets the output high when I switch back to pinMode(x, OUTPUT).  That's no good!

So I read up in the datasheet and found the register that enables open drain mode.  The pinout was also handy so I could see that pin 53 (according to Arduino) is port B, pin 14 according to the ARM chip.

Here's the code snippet.

 // Sketch for Arduino Due that enables open drain mode for pin B14  
 void setup() {  
  pinMode(53, OUTPUT);  
  digitalWrite(53, 0);  
  // Enable open drain mode on pin 14 of port B using the Multi-Driver Enable Register  
  REG_PIOB_MDER = 1 << 14;  
  // To switch it back to a normal output:  
  // REG_PIOB_MDDR = 1 << 14;  
 }  
 void loop() {  
  // Turn on B14 (should be about equivalent to digitalWrite(53, 1))  
  REG_PIOB_SODR = 1 << 14;  
  // Pause 1ms so it's easy to see on the scope  
  delay(1);  
  // Turn off B14  
  REG_PIOB_CODR = 1 << 14;  
  // Pause 2ms  
  delay(2);   
 }  

Wednesday, September 02, 2015

libfann bugs

Looking for a general purpose neural network library, fann seemed reasonable.  Jury's still out, but I see a number of shortcomings:


  • I see people warning against using its builtin input/output scaling.  So make sure your training set (inputs and outputs) is all scaled to [0,1]

  • fann_create_train(...) shows up in the docs but doesn't appear to actually exist (version 2.1.0)

  • I see no examples of fann_create_train_from_callback(), and the docs are unclear, but it looks like it allocates the memory itself and then calls the callback num_data times.  So the num_data value that gets passed to the callback is 1..n, and the two pointers point directly to the elements to be filled in.
  • #include <doublefann.h> with gcc -lfann caused data corruption for me, because the internal library calls had fann_type = float while my main program had fann_type = double.  So either use #include <fann.h> with gcc -lfann  or  #include <doublefann.h> with gcc -ldoublefann!

Saturday, June 13, 2015

Simple pump from a juice bottle


In some cars, the only way to add or check transmission fluid is to remove a hard-to-reach plug and add fluid until it starts pouring out the hole.  Since the hole is in the side of the transmission, there's no way to get a funnel into the hole.  You have to pump the fluid into the hole.

Auto parts stores will sell you a pump, but I discovered that all I needed was a water bottle and some tubing.  Drill two holes in the cap for the tubes, fill the bottle about halfway with fluid and force air into the short tube.  I was worried about air leaking between the hole and the tubing, but it came out just right, nice and tight.  It can still work without a perfect seal but it'll require more air.

This tubing was the perfect diameter to thread into the nozzle of the air gun on my compressor, but I think it would have worked just as well to simply blow into the short tube.  (I tried it with water just now and it seemed to work fine, but watch out for fumes and don't let any fluid come back up the short tube!)

The tubing shown here is for the ice maker in my fridge.

Thursday, May 28, 2015

Mary Meeker Internet Trends 2015

http://kpcbweb2.s3.amazonaws.com/files/90/Internet_Trends_2015.pdf

Slides of interest to me:

p.47: 6 of top 10 mobile apps are for messaging
p.68: Social network use in 12-24 year olds: Instagram 32%,Twitter 24%, Facebook 14% (down from 35% in 2013)
p.69: 78% of millennials spend >2h per day on smartphone
p.70: 44% of millennials use smartphone camera at least once / day
p.103: Americans receiving government benefits 50% in 2012 vs. 30% in 1983.
p.110: #2 top work value for millenials is "Flexible Working Hours"
p.113: Millenials seen as more narcissistic, open to change, creative than Gen X
p.117: USA Smartphone penetration 64%
p.132: 72% of NYC airbnb hosts depend on it for rent/mortgage
p.163: 7% of Xaomi phone buyers (China) buy a Xaomi home product
p.165: India starting to take off in Internet penetration (very cool chart)
p.169: India #2 in % of internet traffic via mobile
p.170: 41% of India e-commerce is via mobile


Monday, March 23, 2015

Steam Linux audio problems

No audio at all from steam games?  Try 'pavucontrol'.  It'll show you when games are trying to play audio and where they're playing it to. In the "configuration" tab I had to disable built-in audio and switch my "HDA NVidia" device to "Digital Surround 5.1 (HDMI) Output" from "Digital Stereo (HDMI) Output".

Sunday, March 22, 2015

GQL to CSV exporter for Google App Engine

The App Engine admin panel will let you run GQL queries against your datastore, but it won't let you download the results as a CSV. So I wrote a [very] quick and [very] dirty handler that does, which also has the handy side effect that you can put your own access controls on it. That means you can give someone on your team read-only access to your datastore without also giving them access to the admin panel.

If you're using db instead of ndb,  I believe the main thing to change is ndb.gql() to GqlQuery().

 # Very quick and dirty example of how to provide unfettered read  
 # access to your datastore with export to CSV. Be sure to add appropriate  
 # access controls and watch out for security risks (like XSS)  
 #  
 # Don't forget to:  
 # from google.appengine.ext import ndb  
 # from google.appengine.ext.ndb import metadata  
 class GqlPage(webapp2.RequestHandler):  
  def get(self):  
   limited = True  
   row_limit = 1000  
   # Tricky to distinguish absence of 'limit' checkbox when you  
   # first hit the URL from when you submitted with an unchecked box  
   if self.request.get('download', 'nope') != 'nope' and \  
     self.request.get("limit", "nope") == "nope":  
    limited = False  
   
   query = self.request.get('query', "empty")  
   
   is_csv = False  
   if self.request.get('download') == 'Download':  
    is_csv = True  
    self.response.headers['Content-Type'] = "text/csv"  
   else:  
    self.response.write('<html><body>')  
    self.response.write('<form action="/gql" method=POST>')  
    self.response.write('<textarea name=query rows=10 cols=80 placeholder="select * from...">')  
    if query != "empty":  
     self.response.write(cgi.escape(query))  
    self.response.write('</textarea><br>')  
    self.response.write("<input type=submit name=download value=View>")  
    self.response.write('<input id=foo type=submit name=download value=Download')  
    self.response.write(' onclick="document.getElementById(\'results_div\').innerHTML=\'\';">')  
   
    self.response.write("Limit response to " + str(row_limit) + " rows:")  
    self.response.write("<input name=limit type=checkbox ")  
    if limited:  
     self.response.write("checked")  
    self.response.write('><br></form>')  
   
    self.response.write("Examples for available tables:<br>")  
    for kind in metadata.get_kinds():  
     self.response.write("select * from " + kind + "<br>")  
   
    self.response.write('<br><div id="results_div"><pre>')  
   
   if query != 'empty' and query != '':  
    results = []  
    if limited:  
     results = ndb.gql(query).fetch(row_limit)  
    else:  
     results = ndb.gql(query).fetch()  
   
    writer = csv.writer(self.response.out)  
   
    row_count = 0  
    first_row = True  
    for row in results:  
     row_dict = row.to_dict()  
     keys = sorted(row_dict.keys())  
   
     # Write column labels as first row  
     if first_row:  
      first_row = False  
      self.response.write("#")  
      writer.writerow(keys)  
   
     values = []  
     for k in keys:  
      value = str(row_dict[k])  
      if is_csv:  
       values.append(value)  
      else:  
       values.append(cgi.escape(value))  
   
     writer.writerow(values)  
   
     row_count += 1  
     if not is_csv and limited and row_count == row_limit:  
      self.response.write("\n[Truncated at " + str(row_limit) + " lines]")  
   
   if not is_csv:  
    self.response.write('</pre></div>')  
    self.response.write("</body></html>")  
   
  def post(self):  
   return self.get()  
   

Friday, March 20, 2015

Linux data acquisition with an Agilent Infiniium MSO9404A oscilloscope

I need to capture some analog data.  My coworker has a National Instruments USB data acquisition module, but as far as I can tell, the drivers are all proprietary and focused on using Labview.

So instead I used a fancy 9000-series Agilent Infiniium scope.  I plugged my laptop's ethernet cable into the scope, then used the Windows control panel on the scope to set its IPv4 address to 192.168.1.1.  I set my laptop to 192.168.1.2, and found that I could ping the scope.  So far so good.

Back in the day, test equipment was controllable over a serial GPIB bus.  Today's fancy gear uses the same conventions over TCP/IP.  There's a confusing mess of acronyms like VXI-11 and VISA, and a bunch of half-baked libraries and crappy-looking system drivers that appear to be required to use them.  pyvisa looks nice, but wants a proprietary National Instruments driver distributed as a .iso(!).  Not my cup of tea.

Then I ran across this MATLAB example that's basically just chatting with the device over TCP/IP. That led me to Agilent's Programmer's Reference guide for the 9000-series scopes.

After fighting with the 1100-page manual for a few hours, I came up with the following settings that let me get samples at a specific rate like I would from an ADC.

Note that you can also interactively talk to the scope using nc or telnet to port 5025 while you're experimenting.

 #!/usr/bin/python  
   
 # Using ethernet to talk to an Agilent Infiniium MSO9404A  
 # See also the "Infiniium 9000A Programmer's Reference"  
   
 import socket  
 import sys  
   
 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 sock.connect(("192.168.1.1", 5025))  
   
 def cmd(s):  
  sock.send(s + "\n")  
   
 def resp():  
  r = sock.recv(1048576)  
  while not r.endswith("\n"):  
   r += sock.recv(1048576)  
  return r  
   
 print "Querying scope"  
   
 cmd("*IDN?")  
 print "Scope identifies as: ", resp(),  
 cmd("*RST")  
   
 # This doesn't seem to affect the samples we receive  
 cmd(":timebase:range 1E-6")  
 cmd(":timebase:delay 0")  
   
 cmd(":channel1:range 5")  
 cmd(":channel1:offset 2.5")  
 cmd(":channel1:input dc")  
   
 cmd(":trigger:mode edge")  
 cmd(":trigger:slope positive")  
 cmd(":trigger:level chan1,2.5")  
   
 cmd(":system:header off")  
   
 cmd(":acquire:mode rtime") # Realtime mode; don't average multiple passes  
 cmd(":acquire:complete 100")  
   
 cmd(":waveform:source channel1")  
 cmd(":waveform:format ascii")  
   
 cmd(":acquire:count 1")  
 cmd(":acquire:srate:auto off")  
 cmd(":acquire:srate 4000")  
 cmd(":acquire:points:auto off")  
 cmd(":acquire:points 16")  
 # This was on by default, and took me a long time to figure out why  
 # I was getting ~16x the number of samples I requested.  
 cmd(":acquire:interpolate off")  
   
 cmd(":digitize channel1")
# This should block until the capture is done, since we used :digitize
 cmd(":waveform:data?")  
   
 sample_string = resp().rstrip().rstrip(",")  
 ascii_samples = sample_string.split(",")  
   
 samples = []  
 for f in ascii_samples:  
  try:  
   samples.append(float(f))  
  except:  
   print "Couldn't convert:", f  
   
 print "Got ", len(samples), " samples. Values 1-10:", samples[:10]