Showing posts with label usability. Show all posts
Showing posts with label usability. Show all posts

Monday, January 31, 2011

Arduino notebook: an RGB LED and the color wheel (Part 3)

In my last article, I demonstrated use of a potentiometer to control the hue of an RGB LED. In the HSV color space, the hue is one of three parameters controling the color. The other two are saturation and value. With a single control, there isn't a way to vary the other two parameters to give us a greater color range. In this article, I show one technique to control any of the three color variables.

Designing the input

But first, a short design discussion. In order to change what the potentiometer does, we need some method to change its mode of operation. I toyed with several ideas. My preferred method was three buttons: one button puts it in hue change mode, the second button puts it in saturation change mode and finally, the third button puts it in value change mode. The user would press a button and twist the potentiometer, setting that button's associated value. I like this because there is a one-to-one mapping between the buttons and the mode. It makes it easy for the user to understand and use. The problem: I only have two buttons.

My next idea was no button pressed changes hue, one button pressed changes saturation and the other button pressed changes the value. However, as I thought this through, I realized this would not give the user a nice flow between modes since every time one of the buttons was released, the hue mode would be entered, setting it to whatever value the potentiometer was in for the end of the last mode. This would cause the colors to jump around as modes changed and the un-pressed mode, hue, would have to be changed last. This clearly would not work very well.

After further thought, I hit upon the idea of using one button to cycle through the three modes with each button press and using the other button to enter that mode (or in other words, activate the potentiometer). Sort of like one button controls a menu and the other selects and deselects that menu option. To implement this, two additional LEDs are needed to indicate the mode and a third LED to indicate whether the potentiometer is enabled or not. This allows switching between modes without changing the output of the LED until the mode is selected. When the mode is exited, the LED would not change color as it would have in the previous scenario.

I implemented this design. However, after building it, I didn't like it since a binary readout is used to indicate the mode. I considered this sub-optimal from a usability standpoint. How many people can read binary numbers? In addition to the usability issue, it required three LEDs in addition to the switches, increasing the parts count and clutter on the breadboard. It worked but just seemed pretty clunky.

As I thought about it some more, I realized I could use the buttons directly in a binary pattern to select the mode and simply use a short delay before entering the mode. In other words, press and hold one button to change hue. Press and hold the other button to change saturation. Press and hold both buttons to change the value. I like this. It will reduce the parts count and eliminate a level of abstraction in the user interface; the buttons will directly control the mode. This is what I'll present below.

Hardware changes

Starting with the last circuit with in the previous article, I added two switches, two resistors and some jumpers. If you look at the schematic, pins 12 and 13 are connected to ground through a resistor when the switch is open. This means these two pins are normally in the LOW state when digitalRead is called with their pin numbers. When the button is closed, they are connected to the 5V side and become HIGH. The resistor puts a load on the circuit when the button is closed. If the resistor is removed, a direct short between ground and power exists when the button closes; not a good thing to do.

That's it. Pretty simple circuit changes.RGB LED color wheel with HSV controls
Click the image for a larger view or here for a schematic.

Software changes

The software changes are a bit more involved. First, the simple part, I added two declarations for the pins. The next change is a semi-advanced technique, but first a bit of analysis.

There are two buttons. For each button, the software needs to time how long the user has pressed it and, when they have held it down long enough, switch into active mode. To do this, each button requires a timer and a memory of the last button state. The code is the same for both, but the memory to track their status needs to be separate.

To facilitate this type of design, where there are two sections of memory with the same functions working on it, I used something called a class. Put simply, a class associates data with code. To create a class, start with the class keyword followed by the name of the class. This is the name you as a programmer decide to call it. Since this class contains the state information for a mode switch, I called the class ModeSwitchState.

Next, I defined the state information tracked for each switch. This includes the pin the switch is connected to, a timer to track when the last change was made to the switch's state and the switch's last value (i.e. was it pressed or not). These three things are what are called private variables, as indicated by the private keyword. This means nothing outside of functions defined in the class can access their values. The big, technical term for this is data encapsulation. All that means is it's harder to accidentally change the wrong thing because the only things that can get to the memory is this one class.

After I defined the private memory, I created two functions that any code can use. The first is what's called the constructor. This simply means it's the function that is called when a new variable of this type is created. In this case, the constructor takes a parameter to indicate the Arduino pin the rest of the functions will use. Since the pin variable is private, this is the only way to set its value. The idea is you create a variable of this class type for each button to be controlled. The constructor also sets initial values for the other variables and calls pinMode for the pin it reads.

Finally, I wrote a function to read the current value of the pin and, if it's different from the last value, reset the timer and the last value read variables. It then returns true if the button is pressed and the difference between the timer and the current time is greater than 500 ms. Otherwise it will return false.

That's it for the definition of this class. Next, I created two variables of this class type, called modeSwitch1 and modeSwitch2. The constructor is called with the values of the pin assignment previously defined.

And that's it for the advanced stuff. All the other changes are in the loop function.

First the loop gets the current mode state of each switch. Remember, since this is calling the function on the class, all the timer code is neatly tucked away out of sight from the main processing loop. All we have to do now is worry about getting the current value as defined by the readModeSwitch function. That's done by the first two lines.

Next I check to see if either of the switches are currently active. If so, then the potentiometer is read. Following this, either hue, saturation or value is changed to the pot's value based on which combination of switches are pressed. The case where both switches are pressed needs to be checked first, followed by the test for a single switch. If neither of these conditions are met, then the other switch must be pressed and its associated value changed.

Finally, after all this, the RGB color is computed and the LED is set to that color.

If neither switch is pressed, then nothing happens, the loop exits and immediately begins again.

Here's the final code.
int ledPins[] = { 11, 9, 10 };
int analogPin = 0;
int modeBit0pin = 12;
int modeBit1pin = 13;

int hueValue = 0;
double satValue = 1.0;
double lumValue = 1.0;

class ModeSwitchState
{
  private:
    int pin;
    long timer;
    boolean lastValue;

  public:
    ModeSwitchState(int switchPin)
      : pin(switchPin), timer(0), lastValue(false)
    {
      pinMode(pin, OUTPUT);
    }
    
    boolean readModeSwitch()
    {
      boolean curValue = digitalRead(pin) == HIGH;
      if (curValue != lastValue)
      {
        lastValue = curValue;
        timer = millis();
      }
      return curValue && ((millis() - timer) > 500);
    }
};

ModeSwitchState modeSwitch1(modeBit0pin), modeSwitch2(modeBit1pin);

void setup()
{
  pinMode(analogPin, INPUT);
  
  for(int i = 0; i < 3; i++)
    pinMode(ledPins[i], OUTPUT);
}

void loop()
{
  boolean mode1enabled = modeSwitch1.readModeSwitch();
  boolean mode2enabled = modeSwitch2.readModeSwitch();
  
  if (mode1enabled || mode2enabled)
  {
    int analogValue = analogRead(analogPin);

    if (mode1enabled && mode2enabled)
        hueValue = map(analogValue, 0, 1024, 0, 360);
    else if (mode1enabled)
        satValue = analogValue / 1024.0;
    else
        lumValue = analogValue / 1024.0;
  
    byte* colors = hsvToRgb(hueValue, satValue, lumValue);
    setColor(ledPins, colors);
  }
}

void setColor(int* led, const byte* color)
{
  for(int i = 0; i < 3; i++)
    analogWrite(led[i], 255-color[i]);
}

byte rgb[3];
byte* hsvToRgb(int h, double s, double v)
{
        // Make sure our arguments stay in-range
        h = max(0, min(360, h));
        s = max(0, min(1.0, s));
        v = max(0, min(1.0, v));
        if(s == 0)
        {
                // Achromatic (grey)
                rgb[0] = rgb[1] = rgb[2] = round(v * 255);
                return rgb;
        }
        double hs = h / 60.0; // sector 0 to 5
        int i = floor(hs);
        double f = hs - i; // factorial part of h
        double p = v * (1 - s);
        double q = v * (1 - s * f);
        double t = v * (1 - s * (1 - f));
        double r, g, b;
        switch(i)
        {
                case 0:
                        r = v;
                        g = t;
                        b = p;
                        break;
                case 1:
                        r = q;
                        g = v;
                        b = p;
                        break;
                case 2:
                        r = p;
                        g = v;
                        b = t;
                        break;
                case 3:
                        r = p;
                        g = q;
                        b = v;
                        break;
                case 4:
                        r = t;
                        g = p;
                        b = v;
                        break;
                default: // case 5:
                        r = v;
                        g = p;
                        b = q;
        }
        rgb[0] = round(r * 255.0);
        rgb[1] = round(g * 255.0);
        rgb[2] = round(b * 255.0);
        
        return rgb;
}

Conclusion and downloads

That's about all I'm going to do with the RGB LED for now. The source code for this and the past two articles can be downloaded here:
In the next article I will talk about the joys and sorrows of building a simple LCD kit to use with the Arduino.

Monday, November 15, 2010

Apple Raves

About two months ago I ranted about some issues I had with my new Apple MacBook Pro. For the most part, after additional time with the machine, they are all pretty much still valid. I'm still not used to the keyboard layout; I keep reaching for keys that don't exist. The menus at the top of the screen still escape me.[1] As for finding other machines on the network, that could be a bigger issue than it is for me. Once things are mapped, the O/S seems to do a decent job of finding them. As long as the network topology is stable, it's not a huge issue. It's simply an occasional annoyance rather than a day-in/day-out frustration. And finally, the stability isn't as bad as it first appeared. Now that things are setup and working, I haven't had any crashes or freezes. I don't think it's good as Windows or Linux, but it's not as bad as it first seemed.

With that recap and update of what I don't like, this article is about what I do like. And there is plenty.

Battery life

The battery life on this thing is superb. I haven't actually timed it or have any hard data, but seat of the pants performance feels as good as any laptop I've had with much bigger and heavier batteries. If I was a real road warrior, the lack of being able to change batteries might be a problem, but in the last 10-plus years of using a laptop, I've really only needed a backup set of batteries a couple times. For my uses, if it lasts a day of off-and-on use, it's good. And this has done that and then several hours of continuous use in the evenings. Perhaps one of the reasons the battery lasts so long is because of the next item...

Sleep/hibernate

I've never had a laptop that is so consistent in it's ability to go to sleep and wake up when needed. This issue has been one of the biggest ongoing technical flaws in all the other laptops I've had in the past. They were either slow enough at it or crashed regularly enough that I didn't use this feature very much. The Mac does it so smoothly and seamlessly that I do it quite frequently. This might be part of the reason my battery life is better: I'll close the lid and put it to sleep in cases where in the past I'd leave it on. For example, those times when I know I won't need it for five minutes but don't want to shutdown. I guess these periods of inactivity were using more battery than I gave them credit for and they added up so the difference is note-worthy.

Networking

Wow, what can I say about the networking connectivity. It just works. Flawlessly. Everytime. It finds and connects to networks easily and intuitively. Once I've connected to a network, it remembers it and reconnects again without hassle. I can be connected to one network, sleep the machine, go somewhere else with an already known network and begin working again as if nothing has changed. I never knew how much of a hassle network connectivity was until I didn't have the noise anymore.

Delightful surprises

I just noticed this the other day and it's an example of the subtle attention to detail that is throughout much of the system: the Finder's date column adjusts the format based on the width of the column. If the column is narrow, it shows mm/dd/yy. Widen it a bit and it shows mm/dd/yy hh:mm. Some more space and it starts using abbreviations. It continues giving more detail like this until everything is spelled out completely.

Summary

So, all in all, it's a really nice machine. It's not so much nicer I'd never own anything else and, as I mentioned, there are some real annoying "features" about it. On a scale of 1 to 10, I'd give it a good, solid 8. Fairly comparable to the other high-end Dell's I've had in the past.

1. Literally, they escape my notice; I simply don't see the menus up at the top of the main screen. I'll be working with a program wondering how to do something, looking around frustrated. And then, when I'm about to give up thinking the s/w is brain dead, I remember to look at the top. This is particularly pronounced when working with multiple monitors. The menu isn't even on the same screen as the application.

Thursday, September 9, 2010

Apple Rants

I first played with an Apple computer in the early '80s at a local computer store. My next experience was with a Macintosh an employer purchased to play on. Over the years, I've worked on mini-computers, S-100 based computers, PCs with various versions of DOS, Unix and Windows. Despite friendly jabs I may give friends, I don't really consider myself a computer or operating system bigot. I've watched the maturing of Apple computers from the side-lines, fairly impressed by the changes I've seen over the last 10 or so years. I have a number of friends who are Mac fans and have observed the improvements over their shoulders. I saw a lot of Apples as Windows machines at a recent Microsoft conference. More than once I've heard that the MacBook was the best Windows laptop around.

Given all this, when I recently needed to get a new computer I decided to take the plunge and get a MacBook Pro. I was pretty excited to get something so different than my usual fare. It arrived last week and I've spent the better part of the weekend and last couple days configuring it and a new Windows server I got at the same time. Overall, I really like it. The hardware design, the look and feel, the fit and finish are superb. It feels really solid. It feels like a BMW or Mercedes compared to the Dell's Chevy or Ford feel. As good as the hardware is, the operating system software doesn't seem to match up. The following paragraphs rant about some of the issues I have with it.

The biggest issue has to be the Copy/Cut/Paste key mappings. Why are they different than Windows and Linux? What was the reasoning that said they should do something different from the rest of the industry that has no real value? To be different? In my opinion, this is a major impediment to people new to OS X feeling comfortable with it. On SuperUser.com I did find a hint to be able to remap the keys, so they now work as I expect. I wonder though how many people just put up with it as frustrated users.[1]

The next issues seems like a huge anachronism. Circa 1985, before windowing PC operating systems had multi-tasking, each application took over the screen. In those days, putting the Menu bar at the top of screen made sense. However, as soon as you could have more than one application open at a time and visible on screen, basic user experience guidelines dictate the menu bar should be with the window it controls, not at the top of the screen. Given their big emphasis on design in some areas, why Apple thinks violating basic user design principles of keeping similar functions close to where they're used as it relates to the menu bar completely escapes me.[2]

Microsoft has used the SMB protocol for computer discovery and file sharing for a long time. It has become the defacto standard. I find it incredible that there is no way to browse the network and find computers dynamically on the Mac. It seems one can only connect to a networked computer if you already know the name. And to connect to it you have to use a fairly cryptic "smb://computer_name:139" syntax. Really? ... Really? Linux has "just worked" in this regard for many years. And it's worked overall better than Windows itself has. It's about time Apple caught up to the real world.

Finally, Snow Leopard just seems less stable than Linux and Windows have in a long time. Its stability feels like Windows did for the Windows 95 release. Several times in last couple days things have gotten wonky that were fixed by rebooting. A number of times when this happened, shutdown didn't work. It just hung after clearing the screen; I had to power down by holding the power button until it powered off. I can't remember the last time that happened on a Windows or Linux box.

Given all these rants, I don't want to give the impression I don't like my new machine. It is speedy. It is solid physically. And really, overall, I haven't had a ton of problems. Perhaps the problems I have had are more remarkable and obvious given the cleanness of the rest of the system. I'm not yet ready to do as some have and install something else as the base operating system and run OS X in a virtual machine.

2. See "The Structure Principle" at Principles of User Interface Design. Many people talk about this principle such as this one and this one.

Thursday, November 6, 2008

Splash screens are evil! 8 ways to improve them.

Ok, perhaps evil is too strong a word, but ideally splash screens should not be needed. They represent a failure on the part of the development team to make an application that can load fast enough that the user isn't left wondering what happened when they double click its icon. The best case is to make the program load fast enough that it's not needed. As a developer, I realize minimizing application start-up time can be a really hard problem, possibly hard enough that it's not worth the resources to invest in this area of optimization.

Faced with this reality, here are some guidelines, splash screen etiquette if you will, to make it less obnoxious. These are things I've come up with as a user of many different software packages over the years within the context of having done a fair bit of reading in the area of user interaction and experience. I find it interesting that after a bit of searching on the topic, both on the web and in some dead-tree resources, I have found many people talking about splash screens' visual design but no one describing their functionality.

1. Not centered: that's where I'm working!

I typically have a minimum of three applications open. Quite frequently I have two or three times that number. When I start an application, the odds are I have something else I'm working on, and want to continue working on, while the new application is starting. The center of the screen is where my focus is normally going to be for those other applications. Therefore, the center of the screen is the worst place the splash screen can go. It's going to be right in the way of my doing anything else.

2. Not modal: I'm trying to do something else.

The splash screen should should not be system modal. I am working on something else while the new application tries to get going. There is no reason to think the splash screen is more important than what I'm working on. The splash screen should not ever cover up my current work.

3. Not focused: I'm not typing to you.

The starting application should not grab focus unless no other application has it. If another application has focus, it should maintain it. If I'm typing, I'm not typing into the not yet started application, I'm typing to the existing application. (Microsoft Office Outlook team, do you hear me?!?)

4. Not too big: you're not that important.

All the splash screen needs to do is tell me it's working. To do this it just needs a small, unobtrusive animation. I'm willing to give splash screens 5% of my screen. If it can't let me know it's there in a 300 x 300 pixel box, the designers need to rethink what they're trying to do. Ideally, I should be able to size it to the dimensions I want, including minimized.

5. Not fixed: I want that space.

Even when it's not centered, I should still be able to move it where I want it so it's out of the way. No matter where the designers set it by default, there is always going to be the case where, for someone, that location is in the way. Make the silly thing movable, like any other window on my system.

6. Not default: remember what I've told you.

After I've told you where and how big, remember it! Next time I start the application, odds are my system is going to be about the same configuration. I've gone through the hassle of moving it and sizing it; I shouldn't have to do it again tomorrow.

7. Not a non-task: be on the task bar.

The splash screen should cause a button to appear on the task bar; preferably the same button that will be used by the main form when it finally appears. This way, one of the sizes I can give it (see #4 above) is minimized. This allows me to know the application is starting without it having to take any of my precious screen space.

8. Not needed: delay initialization.

Finally, the ideal is to not need a splash screen at all. Consider making the main form visible immediately with things disabled. As modules are loaded and initialized, then build the form and enable the various areas for the user.