Bar graph display on SSD1306 OLED

WhiteSpace

Well-known member
I have to confess to feeling pleased with myself - after a very large number of failures, with bars shooting all over the display and filling the screen in entirely random and infuriating fashion., and after a long process of trial and error rather than any real understanding of why it wasn't working, I have finally succeeded in creating a bar graph display for the little OLED display. It's intended for a two-motor control unit. I'm not sure yet whether I will control the motors using two pots to give analogue control - turn the knobs from full left (reverse) through centre (stop) to full right (forward) - or digitally with tactile switches like on a TV remote volume or channel control, which has only just occurred to me and might give tighter control. In case it's any use, I initially tried with bars of two bytes'/pixels' height, but they were just too narrow, so I settled on 3.

I'm sure others can improve on the coding - it took many attempts to work out how to keep each motor neatly within a pair of columns, rather than staggering all over the place. I've attached the code.

It looks like the photo attached. There's space to add characters or icons in the middle.

The key is this bit:

Rich (BB code):
DisplayLeftMotor: 

if b3 >= 21 and b3 <= 51 then; if motor is stopped or going forward, the bars are at or above the index line, so they start at row 1
row = 1
col = 0 
Gosub SetPosition

elseif b3 >= 0 and b3 < 21 then ; if motor is reversing, the bars are below the line, so they start at the line (row 3) and go downwards
row = 3
col = 0
gosub SetPosition
endif

for b10 = 1 to 2; two columns to give a nice wide bar
hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr) ; this does the top row
      inc row; then move down a row.
      gosub SetPosition
      inc ptr; move to the next byte 
            
hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
      inc row; then move down a row.
      gosub SetPosition
      inc ptr

hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
      inc row; then move down a row.
      gosub SetPosition
      inc ptr           

hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
      gosub SetPosition
      ptr = ptr - 3; then go back to the first byte to display the top byte in the next column
            
col = col + 12 ; then move to the next character space, 12 columns further on
      row = row - 3 ; go back to the top row, ready to write the right hand side of the bars
      gosub SetPosition
      
next b10
return
The control for each motor counts from 0 to 55 because I'm intending to transmit them from the control board to the vehicle via irout. The control for each motor will initially be counted from 0 to 255 as readadc, then divided by 5 (to give 0 to 51). The left motor will transmit an irout code from 0 to 51 and right motor will add 51 to give 52 to 103, so that the two motors will be transmitted in different ranges. That seemed to be the easiest way of getting two lots of motor controls within the 256 options available for irout. I'll update as this progresses.23370
 

Attachments

Last edited:

rq3

Senior Member
>SNIP< I have to confess to feeling pleased with myself -

And you should! Really nice job, especially since you did it yourself without running for help at the first glitch (like I do).
And all of the notes you took during the process will let you help others.

Good job.I like the logic and display.
 

WhiteSpace

Well-known member
I've had an interesting time experimenting with this. Last time I posted, the bar graphs were just demos, inc'ing and dec'ing by the code as a test. I've now tried them with both tactile switches and with pots with readadc to control them. Ultimately the output from the pots or the switches will be transmitted via irout to a remote vehicle to drive its motors. That's the next step. For now the issue has been to get the display on the remote control working. I've now settled on the pots as I had originally thought. The tactile switches were just too slow. In both cases the motor control value goes from 0 (full reverse) to 20 (slow reverse), then 21-30 (stop) and 31-51 (slow to full forward). Pressing the dec switch to decrease motor speed from full forward to slow forward took much longer than turning the pot, even though I also had a "stop" button that took the motor speed to

I've also changed the code quite a lot. Instead of building the bar graph as 3 or 4 bytes top to bottom, building a block 12 pixels/columns wide from top to bottom and then going back up to the top to build a second block 12 pixels wide, I now build a block 24 pixels wide, one row at a time. This is quite a bit quicker.

I've also changed from only sending instructions to display the bar graph elements above the line or below it (3 or 4 rows) to instructing the display for a full 6 rows. This was to prevent "orphan" bars like in this photo. When transitioning very quickly from one direction to another or to stop, the display was not clearing bars had been overtaken - so when transitioning quickly from full reverse (5 bars below the line) to stop, I was ending up with bars 3 to 5 left in place and a gap between them and the axis line. As far as I can work out, the pot was turning faster than the display was updating, so it didn't register the slow reverse instruction and didn't clear the previous bars. So now I just display top and bottom and this clears the orphan bars.

23415

The code looks like this - to send the display codes to scratchpad:

Rich (BB code):
;bars above the line (forward gear)
put 100, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00; 1 bar.  This now displays as 6 bytes, starting from the top.  The dividing line is the bottom bit of the 3rd byte down.  The 4th to 6th bytes go below the axis.  They are necessary in order to clear the bars below the line in the event that the pot is turned so quickly that the intermediate bars are not displayed and are then left as orphans.  
put 106, 0x00, 0x80, 0xBB, 0x00, 0x00, 0x00; 2 bars
put 112, 0x00, 0xB8, 0xBB, 0x00, 0x00, 0x00; 3 bars 
put 118, 0x80, 0xBB, 0xBB, 0x00, 0x00, 0x00; 4 bars 
put 124, 0xB8, 0xBB, 0xBB, 0x00, 0x00, 0x00; 5 bars
Then to readadc

Rich (BB code):
LeftControl:
readadc B.3, b14; this uses a pot to give analogue control
let b3 = b14/5
then to instruct which bytes in the scratchpad to display

Rich (BB code):
SetUpLeftMotor:
        
; Reminder - motor options are: 0-20 (backwards), 21-30 (stop - the stop position needs to be fairly wide; when narrowed down to eg. 24-26 it was difficult to find the stop position), 31-51 (forward);

if b3 >= 21 and b3 < 31 then 
    ptr = 90; if left motor control is "stop", so only the axis line is displayed.  
    elseif b3 >= 31 AND b3 < 35 then ptr = 100 ; 1 bar, slow forward           
    elseif b3 >= 35 AND b3 < 39 then ptr = 106
    elseif b3 >= 39 AND b3 < 43 then ptr = 112
    elseif b3 >= 43 AND b3 < 47 then ptr = 118
    elseif b3 >= 47 AND b3 <= 51 then ptr = 124 ; 5 bars, full forward
    elseif b3 >= 17 AND b3 < 21 then ptr = 130 ; 1 bar, slow reverse
    elseif b3 >= 13 AND b3 < 17 then ptr = 136
    elseif b3 >= 9 AND b3 < 13 then ptr = 142
    elseif b3 >= 5 AND b3 < 9 then ptr = 148
    elseif b3 >= 0 AND b3 < 5 then ptr = 154 ;5 bars, full reverse
endif
And then finally to build the bars:

Rich (BB code):
DisplayLeftMotor: 
 
row = 1
col = 0 
Gosub SetPosition; the major change here is that instead of writing the bars as 2 blocks of 12 columns, it writes the bars as a single 24-column block, which is about 50% faster.

hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr) ; this does the top row
    inc row; then move down a row.
    gosub SetPosition
    inc ptr
        
hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
    inc row; then move down a row.
    gosub SetPosition
    inc ptr

hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
    inc row; then move down a row.
    gosub SetPosition
    inc ptr    
    
hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr) ; this does the top row
    inc row; then move down a row.
    gosub SetPosition
    inc ptr
        
hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
    inc row; then move down a row.
    gosub SetPosition
    inc ptr

hi2cout (0x40,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr,@ptr)
        
return
 

mortifyu

New Member
That is all pretty cool.

Perhaps using tactile buttons would be beneficial over a pot. Using tactiles would allow you controlled steps forward and backward and also you could make it so if both buttons were pressed it could return to definite stopped. Being going neither forward or backward. With a pot you’d need either a clear marking of where ‘stopped’ was or a mechanical notch built into the pot at the central point.


Regards,
Mort.
 

WhiteSpace

Well-known member
Thanks for the comments Mort. Yes, I had originally designed this with pots, and then tried tactile switches because I thought that they would give better control. The code looked like this:

Rich (BB code):
LeftButtons:

If LeftRevButton = 1 then           'Wait for a button press on the left reverse button
      let b3 = b3 -2 MIN 2          'decrease the value of b3 to feed into the counter.  Originally dec'd by one count rather than two, but it was very slow.  The MAX function is needed otherwise b3 decs through zero and goes to 255 and the display becomes a mess
endif


If LeftStopButton = 1 then          ; this sets the counter to the stop position (21-30 - in this case 25).  It stays stopped until you take your finger off the button
      let b3 = 25       
      do loop while LeftStopButton = 1 ;
endif

If LeftFwdButton = 1 then           
      let b3 = b3 + 2 MAX 51                    
endif
But in fact the switches were a lot slower than the pots. Even when I removed the debounce from the switches so that they would continue to inc/dec as long as my finger was pressed down, they were quite a bit slower. I didn't want to make the steps per button press too large, because the controls will translate onto PWM for left and right motors on the vehicle, and I want to retain some degree of sensitivity. At one point I had one control with switches and the other with a pot to allow a direct comparison. Slightly to my surprise overall I preferred the pots. You're right about the risk of missing a definite stop. To solve that I have kept a fairly large space for "stop" - 21-30 (compared with 0-20 for rev and 31-51 for fwd. In each case these figures are divided by 5 from the 0-255 readadc figure. So there's a fairly big gap to find a stop position. I did try a narrower gap - 24-26 at the extreme - but then it certainly was difficult to locate stop effectively.

My pots are these: https://www.rapidonline.com/iskra-pnz10za-10k-10k-pnz10za-preset-potentiometer-67-0466 and onto the knobs I have squeezed a couple of lego cogs - the small fat black ones - which give good fingertip control. At the moment they have stickers on with an arrow pointing to the centre position.
23416

I'll think of something a bit tidier, but they give quite good positioning. Happy to think again if anyone has better suggestions, though.
 
Top