The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Software

General-purpose computers doing something specific

  • Recovering JPG Images From Damaged Flash Memory

    My DSC-F717 just crashed as I took a picture in the Basement Laboratory; it stalled while writing a picture to the memory stick. This camera is known to have problems with its ribbon cables and I’ve fixed it a few times before, but right now I have other things to do. Who knows? Maybe it’s a different problem altogether.

    Thus, the pertinent question is how to grab the image files from the Memory Stick, even with a damaged filesystem. This trick will work with any memory device, so it’s not just a Memory Stick thing.

    The camera crashed with the Memory Stick activity LED stuck on. Turning the camera off didn’t work: it jammed in the power-down sequence. So I poked the Reset button, which snapped the camera back to 1 January 2002, just after midnight. Alas, it now couldn’t read the Memory Stick, which meant either the filesystem is pooched or the camera’s card socket has gone bad again.

    Mmm, do you know where your camera’s Reset button is? It might not even have one, in which case you just yank the battery. If you can yank the battery, that is.

    Anyhow…

    With the camera turned off: extract the memory card, poke it into a suitable USB card reader, and poke that into your PC (running Linux, of course).

    If the filesystem isn’t too badly damaged, you’ll probably get a popup asking what to do with the new drive (the memory card). Dismiss that, as you don’t want anything writing to the memory without your explicit permission, which you won’t give.

    Figure out which device corresponds to the card and which partition to use:

    dmesg | tail
    [29846.600524] sd 5:0:0:2: [sdd] 1947648 512-byte hardware sectors (997 MB)
    [29846.606022] sd 5:0:0:2: [sdd] Write Protect is off
    [29846.606030] sd 5:0:0:2: [sdd] Mode Sense: 23 00 00 00
    [29846.606033] sd 5:0:0:2: [sdd] Assuming drive cache: write through
    [29846.608748]  sdd: sdd1
    

    In this case, the Memory Stick was intact enough to have a valid partition table, so it could be successfully mounted. However, you don’t want any attempt to write the data, just to keep a bad situation from getting worse. You do that by mounting the device read-only:

    sudo mount -o ro /dev/sdd1 /mnt/part

    You’ll need a suitable mount point; I created /mnt/part early on to hold manually mounted disk partitions.

    The FAT filesystem seemed to point to valid files, but attempting to display them didn’t work. So unmount the device before proceding:

    sudo umount /dev/sdd1

    Switch to /tmp or anywhere you have enough elbow room for a few files the size of the memory card.

    Run dd to make a bit-for-bit copy of the entire drive:

    sudo dd if=/dev/sdd of=memstick.bin bs=1M

    The bs=1M should dramatically speed up the transfer; the default is, IIRC, 512 bytes.

    After this, everything you do uses memstick.bin, not the memory card itself. If you’re paranoid like me, you’ll make a copy of the file before you do anything hazardous. You could even make two copies directly from the memory card and compare them, which might be instructive.

    To find your precious JPG images among the rubble, look for their magic Exif headers:

    strings -t x memstick.bin | grep Exif
      68006 Exif
     258006 Exif
     680006 Exif
     848006 Exif
     a18006 Exif
     bf0006 Exif
     e00006 Exif
     fe8006 Exif
    11e8006 Exif
    1420006 Exif
    ... snippage ...
    

    Because the strings search ignores the (presumably broken) FAT directory structure, you’ll find all the image files on the drive, whether or not they’ve been deleted, partially overwritten, or whatever. This is great for forensics and terrible if you’ve got something to hide. You have been warned.

    The -t x parameter returns the string’s starting offset in hex, which you need to find the actual starting offset of the file: it’s 6 bytes lower! Your mileage may vary, so be prepared to fiddle around a bit.

    You could write a bunch of code to parse the JPG header and extract exactly the number of bytes required, but this is no time for subtle gestures. I just yank out whatever the largest possible image file could be, because image-processing programs only process the valid stuff anyway. So, knowing that this camera produces images in the 2.4 MB range, this extracts the first image:

    dd if=memstick.bin of=image.jpg bs=$((16#1000)) count=1K skip=$((16#68))
    

    The $(( … )) notation evaluates the numeric expression within and the 16#… notation expresses a hexadecimal value.

    Soooo, bs=$((16#1000)) says that the blocksize is 4096 bytes, which you can deduce from the fact that all the Exif headers start 6 bytes from a multiple of 0x1000. Again, your camera may do things differently, but the general notion should get you started.

    If you’re fussy, you’ll note that the headers are actually on multiples of 0x8000 bytes, but using 0x1000 means you can read the high-order digits right off the strings dump. Why make things more complicated than necessary?

    Given a 4K blocksize, count=1K extracts a 4MB chunk: 1024 blocks of 4096 bytes each. That’s larger than the largest possible image file from this camera; adjust it to suit whatever you expect to find. Don’t be stingy, OK?

    The skip=$((16#68)) says to begin extracting data 0x68 blocks into memstick.bin. You read that value directly from the strings output, which is easy enough.

    You could write a tidy Bash script to eat the strings values and spit out the corresponding file chunks. I had few enough images this time to just do it manually, which beats having to debug some code…

    Good luck!

  • Terabyte Backup for the Backup

    Now that terabyte drives sell for under 100 bucks, there’s no longer any reason to worry about backup space. Just do it, OK?

    My file server runs a daily backup (using rsnapshot, about which more later) just around midnight, copying all the changed files to an external 500 GB USB drive. On the first of each month, it sets aside the current daily backup as a monthly set, so I have a month of days and a year of months.

    Roughly once a quarter, I copy the contents of that drive to another drive, empty the file system, and start over again.

    Right now there’s about 380 GB of files on the server, but rsnapshot maintains only one copy of each changed file on the backup drive and we typically change only a few tens of megabytes each day, sooo a 500 GB drive doesn’t fill up nearly as fast as you might think.

    Our daughter’s doing a science fair project involving ballistics and video recording, so she recently accumulated 36 GB of video files in short order… and re-captured the entire set several times. Of course the external drive filled up, so it’s time for the swap.

    Recently I picked up a 1 TB SATA drive and it’s also time to document that process.

    You will, of course, have already set up SSH and added your public key to that box, so you can do this from your Comfy Chair rather than huddling in the basement. You’ll also use screen so you can disconnect from the box while letting the session run overnight.

    Plug the drive into a SATA-to-USB converter, which will most likely pop up a cheerful dialog asking what you want to do with the FAT/NTFS formatted empty drive. Dismiss that; you’re going to blow it away and use ext2.

    Find out which drive it is

    dmesg | tail
    [25041.488000] sd 8:0:0:0: [sdc] 1953525168 512-byte hardware sectors (1000205 MB)
    [25041.488000] sd 8:0:0:0: [sdc] Write Protect is off
    [25041.488000] sd 8:0:0:0: [sdc] Mode Sense: 00 38 00 00
    [25041.488000] sd 8:0:0:0: [sdc] Assuming drive cache: write through
    [25041.488000]  sdc: sdc1

    Unmount the drive

    sudo umount /dev/sdc1

    Create an empty ext2 filesystem

    sudo mke2fs -v -m 0 -L Backup1T /dev/sdc1

    The -v lets you watch the lengthy proceedings. The -m 0 eliminates the normal 5% of the capacity reserved for root; you won’t be running this as a normal system drive and won’t need emergency capacity for logs and suchlike. The -L gives it a useful name.

    Create a mount point and mount the new filesystem

    sudo mkdir /mnt/part
    sudo mount /dev/sdc1 /mnt/part

    You may have to fight off another automounter intervention in there somewhere.

    The existing backup drive is in /etc/fstab for easy mounting by the the rsync script. Because it’s a USB drive, I used its UUID name rather than a /dev name that depends on what else might be plugged in at the time. To find that out

    ll /dev/disk/by-uuid/
     ... snippage ...
    lrwxrwxrwx 1 root root 10 2009-03-17 09:54 fedcdb1c-ec6e-4edc-be35-22915c82e46a -> ../../sdd1

    So then this gibberish belongs in /etc/fstab

    UUID=fedcdb1c-ec6e-4edc-be35-22915c82e46a /mnt/backup ext3 defaults,noatime,noauto,rw,nodev,noexec,nosuid 0 0

    Mount the existing backup drive and dump its contents to the new one:

    sudo mount /mnt/backup
    sudo rsync -aHu --progress --bwlimit=10000 --exclude=".Trash-1**" /mnt/backup/snapshots /mnt/part

    You need sudo to get access to files from other users.

    Rsync is the right hammer for this job; don’t use cp.

    The -a preserves all the timestamps & attributes & owners, which is obviously a Good Thing.

    The -H preserves hard links, which is what rsnapshot uses to maintain one physical copy in multiple snapshot directories; if you forget this, you’ll get one physical copy for every snapshot directory and run out of space on that 1 TB drive almost immediately.

    The -u does an update, which is really helpful if you must interrupt the process in midstream. Trust me on this, you will interrupt it from time to time.

    You will also want to watch the proceedings, which justifies –progress. There’s a torrent of screen output, but it’s mostly just comfort noise.

    The key parameter is –bwlimit=10000, which throttles the transfer down to a reasonable level and leaves some CPU available for normal use. Unthrottled USB-to-USB transfer ticks along at about 15 MB/s on my system, which tends to make normal file serving and printing rather sluggish. Your mileage will vary; I use –bwlimit=5000 during the day.

    You probably want to exclude the desktop trash files, which is what the –exclude=”.Trash-1**” accomplishes. If your user IDs aren’t in the 1000 range, adjust that accordingly.

    How long will it take? Figure 500 GB / 10 MB/s = 50 k seconds = 14 hours.

    That’s why you use screen: so you can shut down your Comfy Chair system overnight and get some shut-eye while rsync continues to tick along.

    When that’s all done, compare the results to see how many errors happen while shuffling the data around:

    sudo diff -q -r --speed-large-files /mnt/backup/snapshots/ /mnt/part/snapshots/ | tee > diff.log

    The sudo gives diff access to all those files, but the tee just records what it gets into a file owned by me.

    You’ll want to do that overnight, as it really hammers the server for CPU and I/O bandwidth. Batch is back!

    That was the theory, anyway, but it turned out the new drive consistently disconnected during the diff and then emerged with no filesystem. A definite disappointment after a half-day copy operation and something of a surprise given that diff is a read-only operation.

    A considerable amount of fiddling showed that running USB-to-USB copies simply didn’t work, even if the drives were on different inside-the-PC USB controllers, with failures occurring around a third of a terabyte. So, rather than debugging that mess, I wound up making copies directly from the file server’s internal drives, which ran perfectly but also ignored the deep history on the backup drive.

    But, eh, I’ve been stashing a drive in the safe deposit box for the last few years, so there should be enough history to go around…

  • Vital Browser Addition: Readability

    Short version: Go there, read about Readability, set it up, and browse happily ever after.

    Longer version: Readability chops away all the overdesigned Webbish crap around the text you want to read, reformats it in a single block, and lets you read without distractions.

    My configuration:

    • Style: Novel
    • Size: Large
    • Margin: Narrow

    I put it as the top bookmark in the sidebar, where I hit it rather often.

    It also works well for printing: nytimes.com articles now print quite legibly in four-up mode, which saves a ton o’ paper.

    Bonus: you can even print those pesky blogs that don’t print in Firefox.

    Minor disadvantage: if you want to print a whole article, you must get all the text on a single page. Some blogs / news outlets don’t let you do that, for reasons that should be obvious when you consider how nice Readability is.

    Just do it.

    You should, of course, be using the Firefox Adblock Plus Add-On to quiet your browsing even more. There used to be an ethical question about using ad-supported sites while running ad-blocking software, but that seems to have died out with the advent of pop-up/pop-under ads, animated GIFs, and relentless Flash junk.

  • Linux Install Tweaks: Firefox

    I wish there was a way to bulk-install a list of add-ons, rather than futzing around installing them one-by-one. On the other hand, this way you’re sure to get the latest-and-greatest, while not attempting to install anything that’s obsolete.

    Copy passwords & suchlike from your previous installation into ~/.mozilla/firefox/whatever

    • key3.db
    • signon3.txt

    Install add-ons:

    • Adblock Plus
    • GreaseMonkey — then get GoogleMonkeyR for two-up search results
    • Google Gears — for wordpress blogging
    • Image Zoom
    • Tab Focus — 250 ms delay
    • Print Context Menu
    • FireGestures
    • Linkification
    • PDF Download
    • Brief

    Install themes

    • Microfox
    • Littlefox
    • Classic Compact

    I like Microfox: it doesn’t waste vertical space on foo-foos.

    Kill off Flash cookies and disable all the sharing and enabling and local storage: does anyone really want Flash apps to use the microphone & camera by default? This technique is not obvious and you must do it for every user and every browser on each system.

    The Adobe Knowledgebase article is here:

    http://kb.adobe.com/selfservice/viewContent.do?externalId=52697ee8

    The actual Flash control panel is here:

    http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager02.html

    You should also search on the obvious keywords for more info.

    Exit Firefox, fire it up again, do the obvious tweakage, and you’re set!

  • Extracting Digital Camera Multi-burst Images

    Multi-burst image of trebuchet firing
    Multi-burst image of trebuchet firing

    Digital camera, at least the ones I have, include a “multi-burst” (Sony DSC-H5 and DSC-F717) or “multi-continuous” (Casio EX-Z850) shutter mode that takes a bunch of pictures in quick succession, then combines them into a single JPG image file.

    The Sony cameras create a 4×4 array. This image of a small trebuchet comes from the F717 and is 1280×960, so each sub-image is 320×240. The time between images is 1/30 second and the shutter speed is 1/125 second.

    Extracting the sub-images is trivially easy with the ImageMagick convert function:

    convert -crop 320x240 dsc02594.jpg shot-%02d.jpg
    Sub-image shot-11.jpg from the sequence
    Sub-image shot-11.jpg from the sequence

    You must specify the size of the sub-images to extract, which you can determine by RTFM or simple division, and convert extracts all the tiles into files named, in this case, shot-00.jpg through shot-15.jpg. The files appear in left-to-right, top-to-bottom order, which is most likely the sensible way for cameras to store them.

    The C printf-style format string %02d forces two-digit sequence numbers. You can omit that and the sequence will start with shot-0.jpg, but you must then contend with the usual hassles of shot-1.jpg and shot-10.jpg.

    Can you tell the designers were computer geeks?

    With 16 separate images in hand, you can have your way with them, using all your usual image-manipulation tools.

    ImageMagick can convert the images into an animated GIF:

    convert -delay 50 shot-*jpg shot-ani.gif
    Animated GIF from separate images
    Animated GIF from separate images

    That’s nigh onto 7 MB of image, which seems excessive for what it is, but there you have it. Obviously, you can de-res the images to fit the space available.

    The -delay 50 option should set the frame delay to 50 ticks at the default 100 ticks per second, but some display software ignores the frame rate within the file. Assuming, that is, that the usual spam filters don’t swat animated GIFs right out of the bitstream.

    You can also convert the images into a movie, as I discussed in more detail there. The ffmpeg program does a fine job of it:

    ffmpeg -r 3 -i shot-%02d.jpg shot.mp4

    Actually, convert can do that all by itself if you install mpeg2encode.

    One cannot upload movies to one’s free WordPress blog without buying a space expansion, so you’ll have to take my word that it works.

    The Sony cameras provide control over the interval between the images, allowing 1/30, 1/15, and 1/7.5 second intervals, but the Casio evidently just does the best it can. If you know the interval, you can determine interesting things like velocity and acceleration, so that’s something to look for when you’re buying a camera.

    Perhaps you can calibrate your camera using a pendulum?

    Memo to Self: Use the tripod!

  • Arduino Fast PWM: Faster

    Although you can change the Arduino runtime’s default PWM clock prescaler, as seen there, the default Phase-correct PWM might not produce the right type of output for the rest of your project’s circuitry.

    I needed a fixed-width pulse to drive current into a transformer primary winding, with a variable duty cycle (hence, period) to set the power supply’s output voltage. The simplest solution is Fast PWM mode: the output goes high when the timer resets to zero, goes low when the timer reaches the value setting the pulse width, and remains low until the timer reaches the value determining the PWM period.

    The best fit for those requirements is Fast PWM Mode 14, which stores the PWM period in ICRx and the PWM pulse width in OCRxA. See page 133 of the Fine Manual for details on the WGMx3:0 Waveform Generation Mode bits.

    I needed a 50 μs pulse width, which sets the upper limit on the timer clock period. Given the Diecimila’s 16 MHz clock, the timer prescaler can produce these ticks:

    • 1/1 = 62.5 ns
    • 1/8 = 500 ns
    • 1/64 = 4 μs
    • 1/256 = 16 μs
    • 1/1024 = 64 μs

    So anything smaller than 1/1024 would work. For example, three ticks at 1/256 works out to 48 μs, which is close enough for my purposes. A 1/8 prescaler produces an exact match at 100 ticks and gives a nice half-microsecond resolution for pulse width adjustments.

    The overall PWM period can vary from 200 μs to 10 ms, which sets the lower limit on the tick rate. The timer is 16 bits wide: 65535 counts must take more than 10 ms. The 1/1 prescaler is too fast at 4 ms, but the 1/8 prescaler runs for 32 ms.

    So I selected the 1/8 prescaler. The table on page 134 gives the CSx2:0 Clock-Select mode bits.

    Define the relevant values at the top of the program (uh, sketch)

    #define TIMER1_PRESCALE 8	// clock prescaler value
    #define TCCR1B_CS20 0x02	// CS2:0 bits = prescaler selection
    
    #define BOOST_PERIOD_DEFAULT	(microsecondsToClockCycles(2500) / TIMER1_PRESCALE)
    #define BOOST_ON_DEFAULT	(microsecondsToClockCycles(50) / TIMER1_PRESCALE)
    
    #define BOOST_PERIOD_MIN	(microsecondsToClockCycles(200) / TIMER1_PRESCALE)
    #define BOOST_PERIOD_MAX	(microsecondsToClockCycles(10000) / TIMER1_PRESCALE)
    

    The microsecondsToClockCycles() conversion comes from the Arduino headers; just use it in your code and it works. It’ll give you the right answer for 8 MHz units, too, but you must manually adjust the timer prescaler setting; that could be automated with some extra effort.

    Then, in the setup() routine, bash the timer into its new mode

    analogWrite(PIN_BOOSTER,1);	// let Arduino setup do its thing
    TCCR1B = 0x00;			// stop Timer1 clock for register updates
    
    TCCR1A = 0x82;			// Clear OC1A on match, Fast PWM Mode: lower WGM1x = 14
    ICR1 = BOOST_PERIOD_DEFAULT;	// drive PWM period
    OCR1A = BOOST_ON_DEFAULT;	// ON duration = drive pulse width
    TCNT1 = BOOST_ON_DEFAULT - 1;	// force immediate OCR1A compare on next tick
    TCCR1B = 0x18 | TCCR1B_CS20;	// upper WGM1x = 14, Clock Sel = prescaler, start running
    

    The Arduino analogWrite() function does all the heavy lifting to set the PWM machinery for normal use, followed by the tweakage for my purposes. All this happens so fast that the first normal PWM pulse would still be in progress, but turning the PWM timer clock off is a nice gesture anyway. Forcing a compare on the first timer tick means the first pulse may be a runt, but that’s OK: the rest will be just fine.

    What you don’t want is a booster transistor drive output stuck-at-HIGH for very long, as that will saturate the transformer core and put a dead short across the power supply: not a good state to be in. Fortunately, the ATmega168 wakes up with all its pins set as inputs until the firmware reconfigures them, so the booster transistor stays off.

    The PWM machinery is now producing an output set to the default values. In the loop() routine, you can adjust the timer period as needed

    noInterrupts();			// no distractions for a moment
    TCCR1B &= 0xf8;			// stop the timer - OC1A = booster may be active now
    
    TCNT1 = BOOST_ON_DEFAULT - 1;	// force immediate OCR1A compare on next tick
    ICR1 = BasePeriod;		// set new PWM period
    
    TCCR1B |= TCCR1B_CS20;		// start the timer with proper prescaler value
    interrupts();			// allow distractions again
    

    The ATmega168 hardware automagically handles the process of updating a 16-bit register from two 8-bit halves (see page 111 in the Manual), but you must ensure nobody else messes with the step-by-step process. I don’t know if the compiler turns off interrupts around the loads & stores, but this makes sure it works.

    Once again, setting the TCNTx register to force a compare on the next timer tick will cause a runt output pulse, but that’s better than a stuck-HIGH output lasting an entire PWM period. You can get fancier, but in my application this was just fine.

    You can update the PWM pulse width, too, using much the same hocus-pocus.

    And that’s all there is to it!

    Memo to Self: always let the Arduino runtime do its standard setup!

  • Reading a Quadrature Encoded Knob in Double-Quick Time

    The simple technique of reading a quadrature knob I described there works fine, except for the knob I picked for a recent project. That’s what I get for using surplus knobs, right?

    I picked this knob because it has a momentary push-on switch that I’ll be using for power; the gizmo should operate only when the knob is pressed. The rotary encoder part of the knob has 30 detents, but successive “clicks” correspond to rising and falling clock edges: the encoder has only 15 pulses in a full turn.

    So, while advancing the knob counter on, say, the falling edges of the A input worked, it meant that the count advanced only one step for every other click: half the clicks did nothing at all. Disconcerting, indeed, when you’re controlling a voltage in teeny little steps.

    Worse, the encoder contacts are painfully glitchy; the A input (and the B, for that matter) occasionally generated several pulses that turned into multiple counts for a single click.

    Fortunately, the fix for both those problems is a simple matter of software…

    The Arduino interrupt setup function can take advantage of the ATmega168’s ability to generate an interrupt on a pin change, at least for the two external interrupts that the Arduino runtime code supports. So it’s an easy matter to get control on both rising & falling edges of the A input, then make something happen on every click of the knob as you’d expect.

    The hardware is straightforward: connect the knob’s A output to INT0, the B  output to D7, and the common contact to circuit ground. Although you can use the internal pullups, they’re pretty high-value, so I added a 4.7 kΩ resistor to Vcc on each input. The code defining that setup:

    #define PIN_KNOB_A	2			// LSB - digital input for knob clock (must be 2 or 3!))
    #define IRQ_KNOB_A	(PIN_KNOB_A - 2)	//  set IRQ from pin
    #define PIN_KNOB_B	7			// MSB - digital input for knob quadrature
    

    Because we’ll get an interrupt for each click in either direction, we can’t simply look at the B input to tell which way the knob is turning. The classy way to do this is to remember where we were, then look at the new inputs and figure out where we are. This buys two things:

    • Action on each edge of the A input, thus each detent
    • Automatic deglitching of crappy input transitions

    So we need a state machine. Two states corresponding to the value of the A input will suffice:

    enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};

    A sketch (from one of these scratch pads) shows the states in relation to the knob inputs. Think of the knob as being between the detents for each state; the “click” happens when the state changes.

    Knob encoder states and inputs
    Knob encoder states and inputs

    In order to mechanize that, put it in table format. The knob state on the left shows where the knob was and the inputs along the top determine what we do.

    Knob state table
    Knob state table

    So, for example, if the knob was resting with input A = 0 (state KNOB_CLICK_0), then one detent clockwise means the inputs are 01. The second entry in the top row has a right-pointing arrow (→) showing that the knob turned clockwise and the next state is KNOB_CLICK_1. In that condition, the code can increment the knob’s position variable.

    The entries marked with X show glitches: an interrupt happened, but the inputs didn’t change out of that state. It could be due to noise or a glitchy transition, but we don’t care: if the inputs don’t change, the state doesn’t change, and the code won’t produce an output. Eventually the glitch will either vanish or turn into a stable input in one direction or the other, at which time it’s appropriate to generate an output.

    Two variables hold all the information we need:

    volatile char KnobCounter = 0;
    volatile char KnobState;
    

    KnobCounter holds the number of clicks the knob has made since the last time the mainline code read the value.

    KnobState holds the current (soon to be previous) state of the A input.

    Now we can start up the knob hardware interface:

    pinMode(PIN_KNOB_B,INPUT);
    digitalWrite(PIN_KNOB_B,HIGH);
    pinMode(PIN_KNOB_A,INPUT);
    digitalWrite(PIN_KNOB_A,HIGH);
    KnobState = digitalRead(PIN_KNOB_A);
    attachInterrupt(IRQ_KNOB_A,KnobHandler,CHANGE);
    

    An easy way to handle all the logic in the state table, at least for small values of state table, is to combine the state and input bits into a single value for a switch statement. With only eight possible combinations, here’s what it the interrupt handler looks like:

    void KnobHandler(void)
    {
    byte Inputs;
    	Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A);	// align raw inputs
    	Inputs ^= 0x02;								// fix direction
    
    	switch (KnobState << 2 | Inputs)
    	{
    	case 0x00 : // 0 00 - glitch
    		break;
    	case 0x01 : // 0 01 - UP to 1
    		KnobCounter++;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x03 :	// 0 11 - DOWN to 1
    		KnobCounter--;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x02 : // 0 10 - glitch
    		break;
    	case 0x04 : // 1 00 - DOWN to 0
    		KnobCounter--;
    		KnobState = KNOB_CLICK_0;
    		break;
    	case 0x05 : // 1 01 - glitch
    		break;
    	case 0x07 : // 1 11 - glitch
    		break;
    	case 0x06 :	// 1 10 - UP to 0
    		KnobCounter++;
    		KnobState = KNOB_CLICK_0;
    		break;
    	default :	// something is broken!
    		KnobCounter = 0;
    		KnobState = KNOB_CLICK_0;
    	}
    }
    

    Reading the knob counter in the main loop is the same as before:

    noInterrupts();
    KnobCountIs = KnobCounter;	// fetch the knob value
    KnobCounter = 0;		//  and indicate that we have it
    interrupts();
    

    And that’s all there is to it!