Cutting the rotary cord Mar 2017
Before moving on to the topic of this article, let’s figure out the problem that came up in the previous one, where adding an OLED display made the rotary encoder readout unreliable.
The problem is caused by the OLED’s display update code, which takes some time:
lcd-init ok. : a micros 1234 shownum micros swap - . ; ok. a 48444 ok.
That’s about 48 milliseconds to update the display. Over half of this time is caused by the way digits are drawn on the display, which uses a very crude approach: pixel by pixel drawing (!).
As a result, our code is not tracking pin changes for 48 ms. This not only means it can miss some rotary encoder pulses, but also that it may see some “impossible” transitions from one A/B pin readout to the next. This will happen every time a pulse comes in, i.e. when it matters.
The solution is to track pulses with interrupts. We could for example check the pins really often, say every millisecond in the SysTick interrupt handler, but it’s going to be a lot more effective to set up “edge-triggered external interrupts” for pins A & B.
Setting up EXTI’s on an STM32 µC is quite involved, as there’s a lot that has to
be configured just right: the NVIC has to handle two new interrupts and the EXTI
peripheral has to generate those interrupts on each falling edge on PA3 and PA5.
The code is in
As a test here, two counters are set up and incremented on each falling edge of PA3 and PA5, respectively. We then periodically read out those two counters and print them:
0 variable count3 0 variable count5 [...] : read-enc begin cr count3 @ . count5 @ . 500 ms again ;
Let’s see what happens:
count-pulses read-enc 0 0 0 0 23 20 97 67 144 82 159 86 189 130 235 171
Yep, now turning the knob generates interrupts - lots of them, in fact, due to switch bounce!
With interrupts, slow OLED updates go away because the pulses can continue to be processed while the OLED code does its thing. It’ll take a little bit more work and code to turn all these interrupts into a quadrature decoder, but that task will be postponed until the next article.
Back to the main goal of this article: cutting the cord!
The goal is to turn this rotary encoder demo into a wireless setup: one JeeNode Zero with the encoder knob, sending its values to a second JeeNode Zero with the OLED mounted:
The sending side is very straightforward, now that this recent article has laid the groundwork:
: step ( n -- ) counter +! 7 <pkt counter @ +pkt pkt>rf ;
I.e. on every step change: send out a packet with format code 7 and the current counter. The way to start this up is:
!s ex/rot4.fs read-enc
On the receiver end, we can start with the same
rxtestv receiver code as
before, printing out all the packets that come in:
!s ex/rot4.fs rxtestv 7 1001 7 1002 7 1003 7 1004 7 1005
And sure enough, it works. Packets are coming in as the knob is rotated! Also worth noting, is that the missed-pulses problem we had with OLEDs is considerably reduced, because sending a wireless packet takes far less time than updating an OLED display.
As a last step, we need to pick up the incoming values and show them on the receiver’s OLED:
: rxtestv ( -- ) rf-init lcd-init begin rf-recv ?dup if rf.buf 2+ swap 2- var-init var> if drop then \ ignore the format type var> if shownum then \ show the payload on OLED then again ;
That’s it. All the code is in
On the receiver, with OLED, we do:
Mecrisp-Stellaris RA 2.3.5 with M0 core for STM32L053C8 by Matthias Koch 64 KB <jz4> 3B5E0728 ram/flash: 4960 18432 free ok. !s ex/rot5.fs rxtestv
Whereas on the sender node, with the rotary encoder attached, we do this:
Mecrisp-Stellaris RA 2.3.5 with M0 core for STM32L053C8 by Matthias Koch 64 KB <jz4> 3B5E0729 ram/flash: 4960 18432 free ok. !s ex/rot5.fs read-enc
Now every control knob tweak is sent wirelessly to the other node and shown on the OLED. The only wire remaining is for power (and FTDI, since the code hasn’t been saved in flash yet).
The final task will be to really cut the cord on the sender and make the rotary knob portable…