Sunday, September 23, 2012

Analog audio panel for PC

Have you ever struggled with audio settings in control panel in middle of a VoIP call? Or, wondered if the other guy can hear you properly? I have. My work requires great deal of remote conference calls using PC. The first thing I wonder always when starting is if my audio settings -- mic volume, input selection, mute/unmute, speaker volume -- are in order. Typically, they are not and it takes a while to fiddle them right. Even just controlling volume from desktop panel isn't conveniently quick enough.

What if you had the audio control panel, the dialog in most PCs where you fiddle audio, right next to your PC in physical form? With analog controls, VU meters and input/output selection present for instant access on your desk, you will have the pleasure of controlling them with hand.



I have been meaning to complete this project for some time, but it took a while to get all the parts ordered and PCB fabricated. Finally, it's coming together now. It's implemented as a shield for Arduino UNO and allows following capabilities:

  • Two VU meters - red bar and green bar for microphone level
  • Analog mic volume control
  • Analog speaker volume control
  • Touch switch for selecting mic inputs
  • Touch switch for selecting speaker outputs
  • Touch switch for mic mute/unmute
  • A buzzer to beep for touch feedback.
  • A display to show input or output selection.

If you came across this blog post by accident, then you most likely know Arduino :). For those who don't, it's an open source microcontroller platform based on an Atmel's 8 bit microcontroller (ATMega328p, specifically) and is incredibly popular among electronics hobbyists for its simplicity and versatility.

Electronics


The humble start was, of course, with a breadboard and spaghetti of wires :)


Schematic below shows how the electronics is implemented. All of the hardware and software is open source, so the eagle files containing schematics and PCB designs can be downloaded from sourceforge project site (once I create the project :)).



Following picture shows the parts before assembly. The PCB has two parts. One is the main board and the other is the capacitive touch panel.


All assembled


When mounting on a panel, the two touch panel screws can come from outside the panel. Another screw from top left can secure it additionally.

Display

The LED display and bar graphs are driven by MAX7219CNG which is an 8 x 8 matrix LED driver. 4 of the rows are used for driving 4x 7-segment displays and 2 rows for the VU meter graphs. The remaining 2 rows are not used. An excellent library called LedControl exists for Aruduino to control it -- download it from here.


(Image from MAX7219CNG datasheet)

The 4x 7-segment display is mainly used for showing input or output selection being made when their respective touch switches are activated, or current mute status if mic is muted. In addition, it also shows volume in percentage when either of the volume is changed.

The two bar graphs are used to show microphone level. One instantaneous level and other sustained level (which is useful to gauge mic volume heard on other side).

Touch Switches

The 3 touch switches are implemented as capacitive touch sensors, however with just enough sensitivity to make them only work as "touch". Look at this excellent article on Capacitive Sense for Arduino that explain how it works. There is no fancy hardware involved.

Fundamentally, it measures the time it takes to charge up the capacitor created by proximity of the hand. Then, based on a preset threshold in charging time, it would identify as "touch". The touch panel is created from a small PCB which is fabricated along side the main PCB. The CapSense library can be download from its github home.


Touch sensitivity resistors R2, R3 and R4

Depending on how the audio panel is used, the optimal values for these resistors will vary. I have found that when using as bare-bone with just the PCB (that is, without any enclosure), 1M ohm resistors work good. But if there is going to be an acrylic panel in the front, then 4.7M ohm resistors are better. The schematic shows 4.7M for them assuming there will be acrylic panel in front, if you don't plan on putting an enclosure, go for 1M.

Piezo Buzzer

There is a small buzzer on board. It's mainly used to give feedback on activating touch switches. But it can also be used to notify some events on desktop, example IRC events or Facebook notification (a sequence of beeps, for example). With a simple tone() function, the piezo buzzer can play any frequency. A tutorial on how to control the tone to play a melody is here.

Microphone and Speaker Volume control

Two 10K linear potentiometers serve to provide volume control for microphone and speaker. Two analog ports read their values intermediately and Arduino sends change in volume command to PC in percentage. The values are averaged over last 5 samples to smooth out any noise coming from the potentiometers. Interfacing the pots with Arduino is well explained in this article.

Firmware and PC software

The software is work in progress at the moment and I plan to have another blog post to cover it. In general, the hardware is proxying the state of its selections and levels to the software running on the PC. And PC software is sending status of the audio and mic levels. For now, there will be only Linux PC software.

The states are absolute and stored in internal EEPROM. This would allow for setting up the audio to the last settings when the audio panel USB is connected to PC (say, just before taking a VoIP call), overriding any software settings before.

Sunday, August 30, 2009

Finally, get ready for the ultimate linux phone: Nokia N900

Nokia n900 is the latest offering from Nokia that runs Maemo operating system based on debian linux. Basically, it's a fairly powerful computer in your pocket.



The word "convergence" [1] has been tossed around for a very long time. A lot of big talks happened about how the concept is the future of consumer devices and how it will change the way people get the "one device to rule all media". I first heard about it in 2001 and I couldn't stop thinking we are in 2009, 8 years later, and the word has still been ... a buzz word.

The problem it seems is that to truly converge consumer electronic devices, one would literally need a computer. Yes, in a way laptop is a converged device -- you can essentially do anything that most electronic devices can do for you in separated devices (tv, radio, video, Internet, telephony, composition etc.). The only missing part is mobility; there is a set of devices that general computers can not yet cover -- the mobile devices.

This is where I claim that Nokia n900 is the first true convergence device or at least a meaningful beginning. Why? Because it's a decent computer and a cellular phone combined (specs), on an open maemo platform.

The software platform is the most interesting part. Based on debian like distribution, Maemo is like any other linux distrubtion and you can use it to write literally any software that you can do for your desktop or laptop computer. Of course, the processing power could be a limiting factor for some heavy applications, but that's largely a limiting factor born out of high power computers, not the limitation of functions that you want for a pocket computer (hint: most of your home media systems have really tiny CPUs). But it still sports one of the fasted hardware specs you can get in a mobile device now (600Mhz Arm cortex-A8 CPU, 256MB ram, 32GB drive, 3D hardware acceleration, gps, cellular, camera and everything that you can get in any modern mobile phone).

One might argue that modern "smartphones" have been around that does a lot of things already what N900 does now. That's true, however they are quite limited in how you can treat them, both hardware wise and software wise. For example, iPhone, android, palm-pre etc., while quite cheezy, are fairly distant from being a small computer that anyone can write apps freely and openly -- the very reason why your PC is versatile. N900 opens that field.

Here is a hands-on youtube video demonstrating N900 if you wish:



[1] Consumer devices convergence, not the other kinds like service convergence or technology convergence.

Sunday, May 17, 2009

Time to revive a dying laptop: dell inspiron 2650

I have a dell inspiron 2650 laptop that was bought 7 years ago. It did a fairly good job of serving me at the time. With nvidia GForce2 GO graphics chipset (hot at that time), I abused it with good amount of FPS games -- the keyboard mostly took the hit :). Now 7 years later, I was amazed to see it still surviving and running. All the important stuffs; motherboard, LCD display, CPU etc. are working perfectly. Considering how well it severed me, I decided to revive it. The following things were dead though:

1. Keyboard: This took the most hit. Arrow keys were non-functional, lost few key caps, and most annoyingly 'Fn' key was non-functioning. This means this keyboard was useless to control screen backlight. This was unfortunate because there is no software way to control screen brightness in this laptop. Cost: 20 USD

2. Harddrive was dead. But fortunately, I had a spare 40 GB, 2.5 hard disk lying around that was used as external usb storage. It was still in good condition for replacement, and had somehow previous ubuntu feisty installation (7.10) still on it. Cost: 0 USD.

3. RAM was 265 MB. It was good for the old time, but now it won't be enough to run GNOME desktop environment. Browsing around the net, I found that the max RAM it can take is 512 MB, in 2 DIMM slots -- each 256 MB module (it had 2 x 128 MB modules). A pair of 256 MB PC100 DIMM modules were quite cheap in ebay so I decided to max out the RAM. Cost: 20 USD.

4. Battery holds something like 20 mins of charge. Replacement battery with 4400 mAH cost 45 USD in ebay.

5. Power supply plug was broken (on the laptop side of the cord). The power brick itself was functioning quite well, but shame that the cord is directly hooked up from the brick. So there is no way you could upgrade only the cord (short of pulling it out from another brick). I decided to replace the whole brick. Cost: 20 USD.

6. CDROM drive was dead. Considering how little I use CD drive now a days (most things come from internet and my NAS storage anyways), I decided not to fix it.

9. Fan was quite noisy and active, indicating cooling problem with CPU. When I opened the laptop, I found good amount of dirts on heatsink and other places (no surprise :)). I bought a compressed air can and blow them out. Then followed by cleaning the fans with dry-wet cloth to remove sticky dirts. There were 2 fans, one for CPU heatsink and another smaller one for general laptop around the graphics heatsink. Another problem was that the thermal paste between the CPU and heatsink was dead dry and opening the heatsink invalidated it further. Bought a ceramic based thermal paste (those white ones) and applied it. Also, dropped a drop of cooking oil in the fans' hinges, just in case it helps :). Now they are less noisy and less active.

10. The LCD cover was cracked around the hinges. A bit of super glue helped there.

Software: Upgraded bios to A13 version (the last update from Dell) and upgraded Ubuntu over the network from Feisty (7.10) to Hardy (8.04) to Interprid (8.10) and then to latest Ubuntu Jaunty (9.04). The upgrades went smoothly except for few gliches in nvidia driver update. Apparently nvidia stopped supporting Geforce2 go in its newer drivers. The last support was in version 96.something. Suspend does not work in Ubuntu 9.04 (a known regression), but I managed to get Hibernation to work by blacklisting 'intel_agp' and 'agpgard' modules as suggested in some forums. GNOME desktop runs smoothly with all the compiz eye candies.

So overall I spend around total of 130 EUR by ordering the items from US ebay, shipping charge and VAT tax, to have a fully functional laptop. Not bad for a spec of P4 1.7 GHz, 512 MB RAM, Geforce2 graphics, 40 GB drive and brand new keyboard and battery.

Now it's as good as new :)

Monday, April 27, 2009

Carrot-following vehicle with mindstorms nxt

After implementing Odometer, the next step is to implement a generic vehicle that navigates a given path. Path following is a basic requirement for an autonomous vehicle which tries follow a given path generated by some higher (more intelligent) entity as closely as possible.

The problem is fairly common and there is a selection of algorithms to choose from. The least complex, but not necessarily the most effective algorithm is the "carrot following" method. Vehicle.nxc now does this implementation in libnxter library.

In this method, the robot looks ahead in a circle towards the path to follow and takes the point of intersection with the path as a goal to chase. There will be two points of intersection between the look ahead circle and the path. The one closer to the direction of path is obviously the pick. If the look ahead circle does not intersect with the path, then the carrot is located at the nearest point (on the circle) towards the path. This is illustrated in following figure.



When a list of navigation points are used to describe the path robot should take, the current path is described by last navigation point visited and next navigation point to visit. In the above figure, P1(x,y) is the last visited point and P2(x,y) is the next point to visit. The robot switches to next navigation point as soon as current navigation point comes within the look ahead circle.

The two points of intersection can be computed with basic geometry of a circle intersecting a line. For example, this site gives a very well explanation of it. Of the two solutions, the point closest to P2(x,y) is selected as carrot. Function VehicleGetLookahead() implements this.

Robot(x,y) position and orientation is the current vector of the robot provided by the Odometer. Navigation points P1, P2, ... and so on are maintained in a queue internally which can be continuously fed with VehicleAddNavPoint() or VehicleAddNavPoints().

Driving the vehicle

Once carrot vector is computed, next thing is to steer the vehicle towards it and try to keep it always pointed at the carrot. This is actually a tough bit. The problem is that we have to make it somehow both smooth and stable. It might look like simple control problem where you just steer proportional to orientation error; I tried it and it doesn't work :). Power of the motors has to be taken into account as well, otherwise either it ended up being too unstable or doesn't have enough steering dynamics (making big round arcs when steering).

When mindstorms nxt is configured as a 2 wheels drive with differential steering, the steering dynamics is very high. Starting from going straight (i.e. no steering), you can steer it in-place which is the highest steering. To take advantage of this steering range, we have to control the power of vehicle as well. Just like in driving a car, lower power allows making sharper turns, but when moving fast there is a limit to how much steering you can do without destabilizing it.

After playing around a while and trying many different ways, I settled on a fairly good approach -- something that sounds quite familiar when you are driving a real car :).

First off, notice that steering and power are not linearly related -- that is, half the power doesn't mean that you can do half the steering. Steering capacity reduces dramatically as the power increases. I found that a typical inverse relation is much more fitting. Like in a way given in this graph.


X-axis is steering factor which can range from -100 to +100 (+/-100 steering is in-place steering where the two motors move in opposite direction equally, either left or right and 0 would mean not steering at all). Y-axis is the power level below which the vehicle needs to be to make the given turn. As you can see, the green line represents the power below which any steering is allowed (and is also the minimum power the vehicle should try to maintain all the time). If you flip the graph, it gives maximum steering the vehicle can make at a given power level.

So, the steering equation can be written as:


Conversely,


VehicleStep() implements the Driving loop that tries to keep the vehicle running at a power level not limited by the steering factor needed, or slows down to make necessary steering. This is exactly what happens when driving a car. You try to reduce the speed to make necessary turn, or speedup when there is less turning to make.

  • The orientation error is determined by subtracting robot orientation from that of carrot's and is multiplied by a constant to get proportional steering factor. This is the needed steering to reduce the orientation error as swiftly as possible.

  • If the current power level allows making this steering factor (the above graph or first equation), no problem and we set it. At the same time, it also means that we are driving the vehicle slower than it needs to so we increases the power a bit (accelerates).

  • Else the vehicle is moving too fast for the needed steering, so reduce the power a bit (braking) and determine the allowed steering level (flipped version of the graph or second equation) at this reduced power. This new 'allowed' steering factor will be lower than the requested one, but is what is actually possible with the current power level.


In essence, this is also a standard proportional controller trying to control both power and steering factor using orientation error as input, where power and steering are constrained by each other. This has worked fine so far with me. But I am sure there is a more elegant solution to it. If you find one, please let me know :).

Example

Here is the path used to test run the vehicle (this is not the exact trace of what is seen in following video, but rather approximately made up). Notice that it doesn't really touch the navigation points. Instead it tries to make smooth turns around them. However around sharp turns, it actually cuts too much corner. This is one drawback of carrot-following method.



Sunday, August 31, 2008

Two wheels drive odometry

Odometry is a way to keep track of robot (or vehicle) position from its wheels tachometer readings. Mindstorms nxt motors have integrated tachometers that make life easier to implement odometry. libnxter project implements odometery with the class Odometry2 and this article describes how it is implemented and what the geometry behind is. It uses integer Vector representation for all its geometry for performance on the tiny CPU it has got (not to mention NXC doesn't support floating point operations).

Basically, it uses standard integration of axial movements over time, taking mid position of the axial connecting the two wheels as the reference point of robot, and is what is tracked as current position. Naturally, the position of robot at time t is given by vector sum of its discrete displacements over time since the beginning:



Position change for each time step is approximated for low angles. As long as integration steps are performed quickly enough (but not very quickly where accuracy of tachometers would suffer), the position should be fairly accurate. Each time step is performed with a call to Odometer2Step() function.

At each step, the distance traveled D along the direction of current robot orientation is given by following equation. Actually, this is only true for small angles where D is approximately straight line. For more acute turns, D is curved and the equation is no more accurate. Therefore, the steps must be executed at fairly quick pace.




Where, and are distance traveled by left wheel and right wheel, respectively. It can be further written down using anglular rotation of each wheel and gear ratio:



Where, is wheel radius, is gear ratio (output to input) and and are angular rotation (radians) of left and right wheels respectively.

The angular rotation is essentially tachometer counts of each wheel, reduced by appropriate gear ratio. In Odometry2 implementation, gear ratio is given in percentage (for integer maths, of course) and tachometer counts are in degrees. So the final equation is:



Where, is wheel diameter, and are tachometer counts of left and right wheels, respectively.

Change in orientation of robot is given by:



Where, is wheel axial length.

After computing and , the new robot position vector is given as:





This is what is implemented in Odometry2Step() method.

Odometry unfortunately suffers from drift error if used over a long period of time without any correction. Despite how assuring the math is, there are several factors that can easily introduce errors during each step, starting from systematic errors (inaccuracy in wheel diameter, axial length etc.), quantization errors (A/D conversion, integer calculations etc.) to random errors. The errors actually grow quadratically with distance traveled and as a result they accumulate real fast. To compensate for the collective drift error, there needs to be a separate mechanism to periodically 'fix' it. Naturally, it's only useful to 'hold on' the tracking while some expensive localization computation is going on or waiting for some measurement/observation to happen.

Helpfully, Odometry2 maintains two positions - a comulative position and a delta position. Comulative position is robot's current position in world coordinate system. The delta position is its relative position from the point where drift was last corrected. The correction point creates the origin of delta coordinate system and delta integration happens on this frame-of-reference. Following diagram illustrates it:



The reason there is a separate delta integration in addition to comulative integration is to give room for robot localization to perform at slower pace (not unusual for small robots with slow CPU) or waiting for observation points to become measurable (losing GPS signal, landmarks not visible etc.). If nothing else, it can be used conveniently as a secondary position tracker in separate coordinate system, like a temporary tracking.

How it works is like this: Let's assume at position , the robot completed its last localization step ( is the result of that correction) and it starts the next round using then current position . Delta coordinate system is reset and this position becomes its origin. By the time it managed to finish the localization round, which could involved waiting for observations, taking measurements, calculating the observed position etc., the robot is already at position (notice the 'tick' mark). At the same time, it has been moving non-stop and also tracking deltaX.

But to use the result of this localization update, it must be applied at position, not later. So, it transforms deltaX position from its delta coordinate system back to world coordinate system after assuming the corrected position as the new delta frame-of-reference. The drift correction is shown in the diagram by the dotted line drawn from position. This translated position becomes the new comulative integral (). And the cycle repeats. Drift correction is implemented in method Odometer2SetDriftCorrection().

My next attempt is to implement Sonar and Radar.

Monday, August 04, 2008

libnxt name collision

After starting my own library for lego mindstroms nxt robotics kits, called libnxt, that provides basic robotics building blocks, I discovered another project already existing with similar name (but different purpose). My bad that I didn't do a basic research for a name. I guess I will rename my project to libnxter instead.

Update: Project is now renamed to libnxter; thanks to SF support team.

Friday, August 01, 2008

Partial solar eclipse in Helsinki

There was a partial solar eclipse visible from Helsinki on 1st Aug 2008, 12:00pm. Here is shot of it taken with my camera phone with a filter attached.