Cheap I2C OLED display with 08M2

hippy

Technical Support
Staff member
Wonder if there is any way of making a converter picaxe i2c or serial to SMB with extra components??
Probably. And, if it is, it should be possible to do direct SMB from the PICAXE you have.

As said earlier it may be that what the chip is expecting is simply something which the PICAXE cannot do with HI2C commands but can be solved by bit-banging I2C. I will see if I can find code for doing that.
 

hippy

Technical Support
Staff member
This might work but untested -
Code:
#Picaxe 20M2
#Terminal 4800
#No_Data

Symbol SDA = B.5 : Symbol SDApin = pinB.5
Symbol SCL = B.7 : Symbol SCLpin = pinB.7

Do
  Input SCL ; SCL = 1
  Input SDA ; SDA = 1
  Pause 3000
  SerTxd( CR, LF )

  Gosub StartBit
  b0 = 0x16 : Gosub SendByte
  b0 = 0x20 : Gosub SendByte
  b0 = 21   : Gosub ReadBytes
  Gosub StopBit
  SerTxd("Manufacturer=") : b0=21 : Gosub ShowText

  Gosub StartBit
  b0 = 0x16 : Gosub SendByte
  b0 = 0x21 : Gosub SendByte
  b0 = 21   : Gosub ReadBytes
  Gosub StopBit
  SerTxd("Device=") : b0=21: Gosub ShowText

  Gosub StartBit
  b0 = 0x16 : Gosub SendByte
  b0 = 0x22 : Gosub SendByte
  b0 = 5    : Gosub ReadBytes
  Gosub StopBit
  SerTxd("Chemistry=") : b0=5: Gosub ShowText

  ; Day + Month*32 + (Year–1980)*512
  Gosub StartBit
  b0 = 0x16 : Gosub SendByte
  b0 = 0x18 : Gosub SendByte
  b0 = 2    : Gosub ReadBytes
  Gosub StopBit
  w0 = b1 * 256 + b2
  w1 = w0 & 31
  w2 = w0 / 32 & 15
  w3 = w0 / 512 + 1980
  SerTxd("Date=",#w3,"-",#w2,"-",#w1)
  w0 = b0 * 256 + b1
  w1 = w0 & 31
  w2 = w0 / 32 & 15
  w3 = w0 / 512 + 1980
  SerTxd(" or ",#w3,"-",#w2,"-",#w1, CR, LF)

Loop

ShowText:
  bPtr = 1
  Do
    b27 = @bPtrInc
    If b27 >= 0x20 And b27 <= 0x7E Then
      SerTxd( b27 )
    Else
      SerTxd( "<", #b27, ">" )
    End If
  Loop Until bPtr > b0
  SerTxd( CR, LF )
  Return

StartBit:
  Input SCL ; SCL = 1
  Input SDA ; SDA = 1
  Low   SDA ; SDA = 0
  Low   SCL ; SCL = 0
  Return

StopBit:
  Low   SCL ; SCL = 0
  Low   SDA ; SDA = 0
  Input SCL ; SCL = 1
  Input SDA ; SDA = 1
  Return

SendByte:
  bit7 = bit7 : Gosub SendBit
  bit7 = bit6 : Gosub SendBit
  bit7 = bit5 : Gosub SendBit
  bit7 = bit4 : Gosub SendBit
  bit7 = bit3 : Gosub SendBit
  bit7 = bit2 : Gosub SendBit
  bit7 = bit1 : Gosub SendBit
  bit7 = bit0 : Gosub SendBit
  Gosub ReadAck
  Return

SendBit:
  Low   SCL   ; SCL = 0
  If bit7 = 0 Then
    Low   SDA ; SDA = 0
  Else
    Input SDA ; SDA = 1
  End If
  Input SCL   ; SCL = 1
  Do : Loop Until SCLpin = 1
  Low   SCL   ; SCL = 0
  Return

ReadBytes:
  b27  = b0
  bPtr = 1
  Do
    Gosub ReadByte : @bPtrInc = b0
    b27 = b27 - 1
  Loop Until b27 = 0
  Return

ReadByte:
  Gosub ReadBit : bit7 = bit0
  Gosub ReadBit : bit6 = bit0
  Gosub ReadBit : bit5 = bit0
  Gosub ReadBit : bit4 = bit0
  Gosub ReadBit : bit3 = bit0
  Gosub ReadBit : bit2 = bit0
  Gosub ReadBit : bit1 = bit0
  Gosub ReadBit : bit0 = bit0
  Gosub ReadAck
  Return

ReadBit:
  Low   SCL ; SCL = 0
  Input SDA
  Input SCL ; SCL = 1
  Do : Loop Until SCLpin = 1
  bit0 = SDApin
  Low   SCL ; SCL = 0
  Return

ReadAck:
  Low   SCL ; SCL = 0
  Input SDA
  Input SCL ; SCL = 1
  Do : Loop Until SCLpin = 1
  If SDApin = 1 Then
    SerTxd( "NAK Error", CR, LF)
  End If
  Low   SCL ; SCL = 0
  Low   SDA ; SDA = 0
  Return
 

tecnition_ted

New Member
Hi Hippy
Tried code and this was the reply through serial.
I am also trying to get more information from the manufacture but TI are large and usually don't come up with the answer you asked them for

NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
Manufacturer=<255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255>
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
Device=<255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255><255>
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
NAK Error
Chemistry=<255><255><255><255><255>
NAK Error
NAK Error
NAK Error
NAK Error
Date=2107-15-31 or 2107-15-31
 

tecnition_ted

New Member
something interesting:

changed the original code used to read the BQ chip eeprom
to below. Just working on voltage at the moment. I now seem to get part of the correct volts (see pic) but the last two digest are incorrect maybe backwards??? or in wrong order ?? The actual battery voltage is around 7610 last two digest moving as normal

Code:
main:
i2cslave 0x16, i2cslow, i2cbyte                
readi2c 0x09,(b8,b9)                ; Read volt from eeprom
;readi2c 0x09,(b9)                ; Read volt from eeprom

readi2c 0x0E,(b10)            ; Read SOC from eeprom
;readi2c 0x10,(b11)            ; Read SOC from eeprom

readi2c 0x11,(b12)            ; Read time till empty mins from eeprom
;readi2c 0x17,(b13)            ; Read time till empty mins eeprom
debug                        ;
 

Attachments

hippy

Technical Support
Staff member
NAK Error
Manufacturer=<255><255><255><255><255> ...
That indicates nothing is being read, the device isn't responding. Or there's a bug in my code.

I now seem to get part of the correct volts (see pic) but the last two digest are incorrect maybe backwards??? or in wrong order ?? The actual battery voltage is around 7610 last two digest moving as normal
What does "7610" mean, where is that coming from, and what two digits are incorrect, may be in wrong order ?

Things will be much easier to follow if you simplify your program, cut all the cruft out, just work with the raw data ...
Code:
#terminal 4800
i2cslave 0x16, i2cslow, i2cbyte                
do
  readi2c 0x09,(b1,b0)                ; Read volt from eeprom
  sertxd( "b1=", #b1, TAB, "b0=", #b0, TAB, "w0=", #w0, CR, LF )
  pause 500
loop
 

tecnition_ted

New Member
Looking at the BQstudio tool the volts are correct but the last two digit's seem to bounce around a bit. It could be BQ studio filters the results slightly as they also seem to change up and down.. I have the SOC reading correct now also with the address 0x16 even though the chip is supposed to be on 0x17
 

tecnition_ted

New Member
I understand to work with raw data but I couldn't have come up with the code you have without you showing, I understand how it works but don't have all the syntax knowledge yet so have been taking parts of code from the main i2c code that works and I understand and bunching it together to try to get the picaxe to do what i want it to do.

I will get there!

It appears that the total code below works so far.

Code:
symbol counter = w0
symbol subcounter = b2
symbol fontlookup = b3
symbol pixels = b4
symbol displaybank = b5
symbol Volt = w4
symbol SOC = w5
symbol timetillemptymins = w6
symbol bTmp = w7
symbol bTemp = w8


; save Font codes in EEPROM

eeprom 0,(124,130,130,130,124)       ; 0 (ASCII 48)
eeprom 5,(136,132,254,128,128)       ; 1
eeprom 10,(196,162,162,146,140)       ; 2
eeprom 15,(68,130,146,146,108)       ; 3
eeprom 20,(48,40,36,254,32)               ; 4
eeprom 25,(110,138,138,138,114)       ; 5
eeprom 30,(124,146,146,146,100)       ; 6
eeprom 35,(2,2,2,2,254)                   ; 7
eeprom 40,(108,146,146,146,108)       ; 8
eeprom 45,(30,18,18,18,254)       ; 9 (ASCII 57)
eeprom 50,(254,18,18,18,254)       ; A (ASCII 65)
eeprom 55,(254,146,146,146,108)       ; B
eeprom 60,(254,130,130,130,130)       ; C
eeprom 65,(254,130,130,68,56)       ; D
eeprom 70,(254,146,146,146,130)       ; E
eeprom 75,(254,18,18,18,2)           ; F
eeprom 80,(254,130,130,146,242)       ; G
eeprom 85,(254,16,16,16,254)       ; H
eeprom 90,(130,130,254,130,130)       ; I
eeprom 95,(226,130,254,2,2)       ; J
eeprom 100,(254,16,40,68,130)       ; K
eeprom 105,(254,128,128,128,128)       ; L
eeprom 110,(254,2,254,2,254)       ; M
eeprom 115,(254,4,56,64,254)       ; N
eeprom 120,(254,130,130,130,254)       ; O
eeprom 125,(254,18,18,18,12)       ; P
eeprom 130,(124,130,162,66,188)       ; Q
eeprom 135,(254,18,50,82,140)       ; R
eeprom 140,(222,146,146,146,246)       ; S
eeprom 145,(2,2,254,2,2)           ; T
eeprom 150,(126,128,128,128,126)       ; U
eeprom 155,(30,96,128,96,30)       ; V
eeprom 160,(254,128,254,128,254)       ; W
eeprom 165,(198,40,16,40,198)       ; X
eeprom 170,(6,8,240,8,6)           ; Y
eeprom 175,(194,162,146,138,134)       ; Z (ASCII 90)





initialise:
; initialise i2c for display (ID 0x78) and then send power up instructions
                    
hi2csetup i2cmaster,$78,i2cfast,i2cbyte    
hi2cout 0x80,(0xAE,0x00,0x00,0xB0,0x20,0x00,0xD5,0x80,0xA8,0x1F,0xD3,0x00,0x8D, _
0x14,0xA1,0x00,0xC8,0xDA,0x02,0x81,0xCF,0xD9,0xF1, 0xDB,0x30,0xA4,0xA6)

gosub sendtodisplay             ; send 4 blank lines to clear display
let displaybank = 0             ; flag rows 0-3 for display
hi2cout 0x80,(0xAF)             ; turn display on


main:
i2cslave 0x16, i2cslow, i2cbyte                

readi2c 0x09,(b8,b9)                ; Read volt from eeprom
readi2c 0x0d,(b10)                  ; Read SOC from eeprom
readi2c 0x11,(b12,b13)            ; Read time till empty mins from eeprom

debug                        ; Show result


hi2csetup i2cmaster,$78,i2cfast,i2cbyte; 

; Insert your programme code here and then poke 28-91 with message to be displayed
;data to send to display (4 * 16)

poke 28,"T","i","m","e"," ","l","e","f","t"," "
bTmp = timetillemptymins / 1000 // 10 + "0" : Poke 38, bTmp
bTmp = timetillemptymins / 100  // 10 + "0" : Poke 39, bTmp
bTmp = timetillemptymins / 10   // 10 + "0" : Poke 40, bTmp
bTmp = timetillemptymins        // 10 + "0" : Poke 41, bTmp
poke 42," "," "
poke 44," "," ","V","o","l","t"," " ; 
bTmp = Volt / 1000 // 10 + "0" : Poke 51, bTmp
bTmp = Volt / 100  // 10 + "0" : Poke 52, bTmp
bTmp = Volt /  10  // 10 + "0" : Poke 53, bTmp
bTmp = Volt        // 10 + "0" : Poke 54, bTmp
poke 55," "," "," "," "," "
poke 60," "," ","S","o","c"," "," "
bTmp = SOC / 1000 // 10 + "0" : Poke 67, bTmp
bTmp = SOC / 100  // 10 + "0" : Poke 68, bTmp
bTmp = SOC / 10   // 10 + "0" : Poke 69, bTmp
bTmp = SOC        // 10 + "0" : Poke 70, bTmp
poke 71," "," "," "," "," "
poke 76," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "

displaybank = 1 - displaybank         ; toggle lines to display
gosub sendtodisplay             ; load message to display memory and point display at it

; Insert any further code you require here
    




goto main

sendtodisplay:                 ; sends characters in memory locations 28-91 to the display
for counter = 28 to 91
    PEEK counter, fontlookup     ; load character to output
    select case fontlookup
        case 48 to 57                            ; 0-9
            fontlookup = fontlookup - 48 * 5
        case 65 to 90                            ; A-Z
            fontlookup = fontlookup -65 * 5 + 50
        case 97 to 122                            ; a-z output as A-Z
            fontlookup = fontlookup - 97 * 5 + 50
        
        else
            fontlookup = 244     ; all other characters will display a blank
    endselect                    
    hi2cout 0x40,(0)             ; one blank column
    for subcounter = 0 to 4
        read fontlookup, pixels        
        hi2cout 0x40,(pixels)     ; followed by 5 columns per character
        fontlookup = fontlookup + 1
    next subcounter
    hi2cout 0x40,(0,0)         ; two blank columns
next counter                 ; this completes the 8x8 character output
If displaybank = 0 then
    hi2cout 0x80,(0x40)         ; point display to lines 0-3 of display memory
    else hi2cout 0x80,(0x60)     ; point display to lines 4-7 of display memory
Endif
return
 

hippy

Technical Support
Staff member
I have the SOC reading correct now also with the address 0x16 even though the chip is supposed to be on 0x17
The address is definitely 0x16 by default, according to page 77 of -

http://www.ti.com/lit/ug/sluubc1d/sluubc1d.pdf

"The bq40z50-R1 SMBus address (default 0x16) can be changed. The target address should be programmed in Address and the 2’s complement of that value should be programmed in Address Check.

The bq40z50-R1 will check these values upon exit from POR, and if the two data flash values are not valid or the programmed address is 0x00 or 0xFF, then the device defaults to 0x16".

Page 126 clarified the data format and 16-bit signed or unsigned is sent as LSB first, MSB second.

Anyway it's good to see that it's now working. Not sure why it did not earlier.
 
Last edited:

tecnition_ted

New Member
here is one if I use the attached code with a 20m2 all is OK. If i use a 08M2 then the i2c does not seem to read as I get the same read from the I2c all the time. If I remove the serial code it works ok with an i2c oel any ideas?

Code:
symbol counter = w0
symbol subcounter = b2
symbol fontlookup = b3
symbol pixels = b4
symbol displaybank = b5
symbol Volt = w4
symbol SOC = w5
symbol timetillemptymins = w6
symbol bTmp = w7



; save Font codes in EEPROM

eeprom 0,(124,130,130,130,124)       ; 0 (ASCII 48)
eeprom 5,(136,132,254,128,128)       ; 1
eeprom 10,(196,162,162,146,140)       ; 2
eeprom 15,(68,130,146,146,108)       ; 3
eeprom 20,(48,40,36,254,32)               ; 4
eeprom 25,(110,138,138,138,114)       ; 5
eeprom 30,(124,146,146,146,100)       ; 6
eeprom 35,(2,2,2,2,254)                   ; 7
eeprom 40,(108,146,146,146,108)       ; 8
eeprom 45,(30,18,18,18,254)       ; 9 (ASCII 57)
eeprom 50,(254,18,18,18,254)       ; A (ASCII 65)
eeprom 55,(254,146,146,146,108)       ; B
eeprom 60,(254,130,130,130,130)       ; C
eeprom 65,(254,130,130,68,56)       ; D
eeprom 70,(254,146,146,146,130)       ; E
eeprom 75,(254,18,18,18,2)           ; F
eeprom 80,(254,130,130,146,242)       ; G
eeprom 85,(254,16,16,16,254)       ; H
eeprom 90,(130,130,254,130,130)       ; I
eeprom 95,(226,130,254,2,2)       ; J
eeprom 100,(254,16,40,68,130)       ; K
eeprom 105,(254,128,128,128,128)       ; L
eeprom 110,(254,2,254,2,254)       ; M
eeprom 115,(254,4,56,64,254)       ; N
eeprom 120,(254,130,130,130,254)       ; O
eeprom 125,(254,18,18,18,12)       ; P
eeprom 130,(124,130,162,66,188)       ; Q
eeprom 135,(254,18,50,82,140)       ; R
eeprom 140,(222,146,146,146,246)       ; S
eeprom 145,(2,2,254,2,2)           ; T
eeprom 150,(126,128,128,128,126)       ; U
eeprom 155,(30,96,128,96,30)       ; V
eeprom 160,(254,128,254,128,254)       ; W
eeprom 165,(198,40,16,40,198)       ; X
eeprom 170,(6,8,240,8,6)           ; Y
eeprom 175,(68,32,16,8,68)       ; Z (ASCII 90)
eeprom 180,(194,162,146,138,134)       ; %

Pause 30
serout C.4,N2400,(254,1) ; Clear serial display 
Pause 30


initialise:
; initialise i2c for display (ID 0x78) and then send power up instructions
                    
hi2csetup i2cmaster,$78,i2cfast,i2cbyte    
hi2cout 0x80,(0xAE,0x00,0x00,0xB0,0x20,0x00,0xD5,0x80,0xA8,0x1F,0xD3,0x00,0x8D, _
0x14,0xA1,0x00,0xC8,0xDA,0x02,0x81,0xCF,0xD9,0xF1, 0xDB,0x30,0xA4,0xA6)

gosub sendtodisplay             ; send 4 blank lines to clear display
let displaybank = 0             ; flag rows 0-3 for display
hi2cout 0x80,(0xAF)             ; turn display on


main:
i2cslave 0xAA, i2cfast, i2cbyte                
readi2c 0x08,(b8)                ; Read volt from eeprom
readi2c 0x09,(b9)                ; Read volt from eeprom
pause 10
readi2c 0x02,(b10)            ; Read SOC from eeprom
;readi2c 0x03,(b11)            ; Read SOC from eeprom
pause 10
readi2c 0x10,(b12)            ; Read average current from eeprom
readi2c 0x11,(b13)            ; Read average current mins eeprom
debug                        ; Show result

hi2csetup i2cmaster,$78,i2cfast,i2cbyte; 

; Insert your programme code here and then poke 28-91 with message to be displayed
;data to send to display (4 * 16)

poke 28,"C","U","R","R","E","N","T"," "," "," "
bTmp = timetillemptymins / 1000 // 10 + "0" : Poke 38, bTmp
bTmp = timetillemptymins / 100  // 10 + "0" : Poke 39, bTmp
bTmp = timetillemptymins / 10   // 10 + "0" : Poke 40, bTmp
bTmp = timetillemptymins        // 10 + "0" : Poke 41, bTmp
poke 42," "," "
poke 44,"V","o","l","t"," "," "," "," "," "," " ; 
bTmp = Volt / 10000 // 10 + "0" : Poke 54, bTmp
bTmp = Volt / 1000 // 10 + "0" : Poke 55, bTmp
bTmp = Volt / 100  // 10 + "0" : Poke 56, bTmp
bTmp = Volt / 10   // 10 + "0" : Poke 57, bTmp
bTmp = Volt        // 10 + "0" : Poke 58, bTmp
poke 59," "
poke 60,"S","o","c"," "," "," "," "," "," "," "
;bTmp = SOC / 1000 // 10 + "0" : Poke 67, bTmp
;bTmp = SOC / 100  // 10 + "0" : Poke 68, bTmp
bTmp = SOC / 10   // 10 + "0" : Poke 70, bTmp
bTmp = SOC        // 10 + "0" : Poke 71, bTmp
poke 72,"z"," "," "," "," "
poke 76," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "

displaybank = 1 - displaybank         ; toggle lines to display
gosub sendtodisplay             ; load message to display memory and point display at it

; Insert any further code you require here
Pause 30    
serout c.4,N2400,(254,128) ; move to start of first line
serout c.4,N2400,("Time left   ",#w6)
serout c.4,N2400,(254,192) ; move to start of second line
serout c.4,N2400,("Charge      ",#SOC,"%")    




goto main

sendtodisplay:                                                ; sends characters in memory locations 28-91 to the display
for counter = 28 to 91
    PEEK counter, fontlookup                        ; load character to output
    select case fontlookup
        case 48 to 57                            ; 0-9
            fontlookup = fontlookup - 48 * 5
        case 65 to 90                            ; A-Z
            fontlookup = fontlookup -65 * 5 + 50
        case 97 to 122                            ; a-z output as A-Z
            fontlookup = fontlookup - 97 * 5 + 50
        case 123 to 124                            ; a-z output as A-Z
            fontlookup = fontlookup - 97 * 5 + 50
            else
            fontlookup = 244    ; all other characters will display a blank
    endselect                    
    hi2cout 0x40,(0)            ; one blank column
    for subcounter = 0 to 4
        read fontlookup, pixels        
        hi2cout 0x40,(pixels)        ; followed by 5 columns per character
        fontlookup = fontlookup + 1
    next subcounter
    hi2cout 0x40,(0,0)            ; two blank columns
next counter                    ; this completes the 8x8 character output
If displaybank = 0 then
    hi2cout 0x80,(0x40)            ; point display to lines 0-3 of display memory
    else hi2cout 0x80,(0x60)        ; point display to lines 4-7 of display memory
Endif
return
 

hippy

Technical Support
Staff member
any ideas?
No. If it works with a 20M2 then it should work with an 08M2. It might be worth trying a cut-down version of the code on the 20M2 then 08M2 and reporting back on the results -
Code:
#Terminal 4800
main:
i2cslave 0xAA, i2cfast, i2cbyte                
readi2c 0x08,(b8)                ; Read volt from eeprom
readi2c 0x09,(b9)                ; Read volt from eeprom
sertxd( "b8=", #b8, tab, "b9=", #b9, tab, "w4=", #w4, cr, lf )
pause 1000
goto main
I am not sure why the I2C device address is 0xAA when I was under the impression it should be 0x16.
 

tecnition_ted

New Member
Sorry I am altering the address and read area so that it can read different BMS chips. I hope to eventually make a routine that selects different addresses and memory locations from the input of a selector switch, but building up to that need to get everything working first then bugger it up.

This chip is an I2c BQ34z100R1 easy to read and gas gauge only
Bit baffled as just coped the working code straight to an 08M2 everything is working bar the reading of i2c ???

Thank you for all your help I'll try your suggestion and get back shortly.
 

tecnition_ted

New Member
Found the problem! the answer was within another forum. To read i2c and SMB there needs to be two 4.7k pull up resistors on the SCL and SDA pins, for some reason the 20M2 worked without them. I have also added these to the 20M2 and the occasional incorrect read has gone.

Next job sending the serial via Bluetooth to a serial logger then to mobile app

Thank you for the above code Hippy, sometimes you have to take a step backwards and simplify to go forward
 

hippy

Technical Support
Staff member
To read i2c and SMB there needs to be two 4.7k pull up resistors on the SCL and SDA pins, for some reason the 20M2 worked without them. I have also added these to the 20M2 and the occasional incorrect read has gone
Yes, the two pull-ups are essential for I2C and SMBus operation, things will be unreliable without them.

With those fitted it would be worth trying the earlier diagnostics code, modified to reflect what we now know about the actual battery management chip, to see if that now works -
Code:
#Picaxe 20M2
#Terminal 4800
#No_Data

Do
  Pause 3000
  SerTxd( CR, LF )

  HI2cSetup I2CMASTER, 0x16, I2CSLOW, I2CBYTE

  HI2cIn 0x20,(b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15,b16,b17,b18,b19,b20)
  SerTxd("Manufacturer=") : Gosub ShowText

  HI2cIn 0x21,(b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15,b16,b17,b18,b19,b20)
  SerTxd("Device=") : Gosub ShowText

  HI2cIn 0x22,(b0,b1,b2,b3,b4)
  SerTxd("Chemistry=") : Gosub ShowText

  ; Day + Month*32 + (Year–1980)*512
  HI2cIn 0x1B,(b0,b1)
  w1 = w0 & 31
  w2 = w0 / 32 & 15
  w3 = w0 / 512 + 1980
  SerTxd("Date=",#w3,"-",#w2,"-",#w1, CR, LF)

Loop

ShowText:
  bPtr = 1
  Do While bPtr <= b0
    If @bPtr >= 0x20 And @bPtr <= 0x7E Then
      SerTxd( @bPtrInc )
    Else
      SerTxd( "<", #@bPtrInc, ">" )
    End If
  Loop
  SerTxd( CR, LF )
  Return
Edit : Changed 0x18 register to 0x1B register for date. See below.
 
Last edited:

tecnition_ted

New Member
Will do and post results. Is it worth starting a new thread regarding attaching this to the serial Bluetooth and WiFi module? There will be questions along the way and I intend to grow the above code so it can read any BQ chip.
 

tecnition_ted

New Member
Here is the reply from the code above all working ok

[reply]

Manufacturer=AceOn
Device=2S14AHPROTOTYPE
Chemistry=LION
Date=2042-8-0

Manufacturer=AceOn
Device=2S14AHPROTOTYPE
Chemistry=LION
Date=2042-8-0

Manufacturer=AceOn
Device=2S14AHPROTOTYPE
Chemistry=LION
Date=2042-8-0

Manufacturer=AceOn
Device=2S14AHPROTOTYPE
Chemistry=LION
Date=2042-8-0
 

hippy

Technical Support
Staff member
Here is the reply from the code above all working ok
Excellent news, though I would guess there's something wrong with my date decoding.

Added : Turns out I was reading the wrong register 0x18 (one-eight ) not the 0x1B (one-bee) it should have been. Change the HI2CIN 0x18,(b0,b1) to HI2CIN 0x1B,(b0,b1) and you should get a more meaningful result.
 
Last edited:

SolidWorksMagi

Senior Member
Hi,

I took me awhile, but once I had basically figured it out I found a way to send data to the OLED display;

3D Printed miniFloppyBot + OLED eyes 2 taming the shrew

Now it's one of the X-Projects on my website: R2Pv1.com
 

tecnition_ted

New Member
Hi Hippy, can you help again please,
I have a problem where the value for current is a signed interger, I found another thread which had an example of how to display the value over serial. I modified the code to put the value into a word (well tried) and something strange happened, all the word values displayed on the i2c screen went to illegible. The heading words remained ok ie volt, time left etc. any ideas what I've done now! I cant get my head round the solution, is there any way of displaying the negative symbol when the current goes negative?

Code:
symbol counter = w0
symbol subcounter = b2
symbol fontlookup = b3
symbol pixels = b4
symbol displaybank = b5
symbol Volt = w4
symbol SOC = w5
symbol timetillemptymins = w6
Symbol Current = w10
symbol bTmp = w7
symbol bTemp = w8
Pause 30
serout C.4,N2400,(254,1) ; Clear serial display 
Pause 30


; save Font codes in EEPROM

eeprom 0,(124,130,130,130,124)       ; 0 (ASCII 48)
eeprom 5,(136,132,254,128,128)       ; 1
eeprom 10,(196,162,162,146,140)       ; 2
eeprom 15,(68,130,146,146,108)       ; 3
eeprom 20,(48,40,36,254,32)       ; 4
eeprom 25,(110,138,138,138,114)       ; 5
eeprom 30,(124,146,146,146,100)       ; 6
eeprom 35,(2,2,2,2,254)           ; 7
eeprom 40,(108,146,146,146,108)       ; 8
eeprom 45,(30,18,18,18,254)       ; 9 (ASCII 57)
eeprom 50,(254,18,18,18,254)       ; A (ASCII 65)
eeprom 55,(254,146,146,146,108)       ; B
eeprom 60,(254,130,130,130,130)       ; C
eeprom 65,(254,130,130,68,56)       ; D
eeprom 70,(254,146,146,146,130)       ; E
eeprom 75,(254,18,18,18,2)           ; F
eeprom 80,(254,130,130,146,242)       ; G
eeprom 85,(254,16,16,16,254)       ; H
eeprom 90,(130,130,254,130,130)       ; I
eeprom 95,(226,130,254,2,2)       ; J
eeprom 100,(254,16,40,68,130)       ; K
eeprom 105,(254,128,128,128,128)       ; L
eeprom 110,(254,2,254,2,254)       ; M
eeprom 115,(254,4,56,64,254)       ; N
eeprom 120,(254,130,130,130,254)       ; O
eeprom 125,(254,18,18,18,12)       ; P
eeprom 130,(124,130,162,66,188)       ; Q
eeprom 135,(254,18,50,82,140)       ; R
eeprom 140,(222,146,146,146,246)       ; S
eeprom 145,(2,2,254,2,2)           ; T
eeprom 150,(126,128,128,128,126)       ; U
eeprom 155,(30,96,128,96,30)       ; V
eeprom 160,(254,128,254,128,254)       ; W
eeprom 165,(198,40,16,40,198)       ; X
eeprom 170,(6,8,240,8,6)           ; Y
eeprom 175, (68,32,16,8,68)           ; Z (ASCII 90)
eeprom 180,(194,162,146,138,134)    ; % 




initialise:
; initialise i2c for display (ID 0x78) and then send power up instructions
                    
hi2csetup i2cmaster,$78,i2cfast,i2cbyte    
hi2cout 0x80,(0xAE,0x00,0x00,0xB0,0x20,0x00,0xD5,0x80,0xA8,0x1F,0xD3,0x00,0x8D, _
0x14,0xA1,0x00,0xC8,0xDA,0x02,0x81,0xCF,0xD9,0xF1, 0xDB,0x30,0xA4,0xA6)

gosub sendtodisplay             ; send 4 blank lines to clear display
let displaybank = 0             ; flag rows 0-3 for display
hi2cout 0x80,(0xAF)             ; turn display on
serout C.0,N2400,(254,1) ; Clear serial display

main:
i2cslave 0x16, i2cslow, i2cbyte                
readi2c 0x09,(b8,b9)                ; Read volt from eeprom
readi2c 0x0d,(b10,b11)            ; Read SOC from eeprom
readi2c 0x11,(b12,b13)            ; Read time till empty mins from eeprom
readi2c 0x0b,(b24,b25)            ; Read current from eeprom

;NEW CODE ATTEMPT
b24 = -10
if b24 >=$80 then write w10, $f0, $aa, $07, $05, b24, $ff, $55
else write w10, $f0, $aa, $07, $05, b24,$00,$55
    end if


debug                        


hi2csetup i2cmaster,$78,i2cfast,i2cbyte; 

; Insert your programme code here and then poke 28-91 with message to be displayed
;data to send to display (4 * 16)

poke 28,"T","i","m","e"," ","l","e","f","t"," "
bTmp = timetillemptymins / 10000 // 10 + "0" : Poke 38, bTmp
bTmp = timetillemptymins / 1000 // 10 + "0" : Poke 39, bTmp
bTmp = timetillemptymins / 100  // 10 + "0" : Poke 40, bTmp
bTmp = timetillemptymins / 10   // 10 + "0" : Poke 41, bTmp
bTmp = timetillemptymins        // 10 + "0" : Poke 42, bTmp
poke 43," "
poke 44,"V","O","L","T"," "," "," "," "," " ," "
bTmp = Volt / 10000 // 10 + "0" : Poke 54, bTmp; 
bTmp = Volt / 1000 // 10 + "0" : Poke 55, bTmp
bTmp = Volt / 100  // 10 + "0" : Poke 56, bTmp
bTmp = Volt /  10  // 10 + "0" : Poke 57, bTmp
bTmp = Volt        // 10 + "0" : Poke 58, bTmp
poke 59," "," "
poke 60,"s","o","c"," "," "," "," "," "," "," "
bTmp = SOC / 1000 // 10 + "0" : Poke 70, bTmp
bTmp = SOC / 100  // 10 + "0" : Poke 71, bTmp
bTmp = SOC / 10   // 10 + "0" : Poke 72, bTmp
bTmp = SOC        // 10 + "0" : Poke 73, bTmp
poke 74,"z"," "
poke 76,"C","U","R","R","E","N","T"," "," "," "
bTmp = Current / 1000 // 10 + "0" : Poke 86, bTmp
bTmp = Current / 100  // 10 + "0" : Poke 87, bTmp
bTmp = Current / 10   // 10 + "0" : Poke 88, bTmp
bTmp = Current        // 10 + "0" : Poke 89, bTmp
poke 90," "," "


displaybank = 1 - displaybank         ; toggle lines to display
gosub sendtodisplay             ; load message to display memory and point display at it

; Insert any further code you require here
;serout C.0,N2400,(254,140) ; Clear serial display 
Pause 30    
serout c.4,N2400,(254,128) ; move to start of first line
serout c.4,N2400,("Time left   ",#w6)
serout c.4,N2400,(254,192) ; move to start of second line
serout c.4,N2400,("Charge      ",#SOC,"%")

goto main

sendtodisplay:                 ; sends characters in memory locations 28-91 to the display
for counter = 28 to 91
    PEEK counter, fontlookup     ; load character to output
    select case fontlookup
        case 48 to 57                            ; 0-9
            fontlookup = fontlookup - 48 * 5
        case 65 to 90                            ; A-Z
            fontlookup = fontlookup -65 * 5 + 50
        case 97 to 123                            ; a-z output as A-Z
            fontlookup = fontlookup - 97 * 5 + 50
        
        else
            fontlookup = 244     ; all other characters will display a blank
    endselect                    
    hi2cout 0x40,(0)             ; one blank column
    for subcounter = 0 to 4
        read fontlookup, pixels        
        hi2cout 0x40,(pixels)     ; followed by 5 columns per character
        fontlookup = fontlookup + 1
    next subcounter
    hi2cout 0x40,(0,0)         ; two blank columns
next counter                 ; this completes the 8x8 character output
If displaybank = 0 then
    hi2cout 0x80,(0x40)         ; point display to lines 0-3 of display memory
    else hi2cout 0x80,(0x60)     ; point display to lines 4-7 of display memory
Endif
return
 

hippy

Technical Support
Staff member
If you have a signed word value you could display it with something like -

Code:
wTmp = ...
If wTmp < $8000 Then
 bTmp = "+"
Else
  bTmp = "-"
  wTmp = -wTmp
End If
Poke 86, bTmp
bTmp = wTmp / 10000 // 10 + "0" : Poke 87, bTmp
bTmp = wTmp / 1000  // 10 + "0" : Poke 88, bTmp
bTmp = wTmp / 100   // 10 + "0" : Poke 89, bTmp
bTmp = wTmp / 10    // 10 + "0" : Poke 90, bTmp
bTmp = wTmp         // 10 + "0" : Poke 91, bTmp
 

mrm

Well-known member
Hello Anyone,

If you go back to the start of this thread it begins with a simple ssd1306 based oled driver program from paraglider-nut which uses different memory locations to accomplish smoother screen drawing.

The display used in the original example uses horizontal bands of memory.

Does anyone have any practical experience with using the technique on screens with a squarer aspect ratio but still using less than half the total gddram?

Alternatively are there other techniques to smooth the screen redraw?

Another related question is how do you time operations on the smaller picaxes as there seem to be no micro or milliseconds clocks or rather there are but their data is not available in handy variables?
 

hippy

Technical Support
Staff member
Alternatively are there other techniques to smooth the screen redraw?
One option if using a 28X2/40X2 is to keep a bitmap of what you are going to display in Scratchpad, manipulate that, then churn it out to the display at high speed using HSPI or HI2C.

According to my notes that should take less than 40ms to update a complete 128x64 (1KB) screen at 32MHz. So 20ms for 128x32 or at 64MHz.

That's outputting 32 bytes per HSPIOUT or HI2COUT command ...
Code:
    ptr = 0
    Do
      HSpiOut( @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc, _
               @ptrInc, @ptrInc, @ptrInc, @ptrInc )
    Loop Until ptr = 0
There's very little to be gained from outputting more per HSPIOUT or HI2COUT and using less can be acceptable ...
Code:
  ; Display update time @ 32 MHz for SPI or I2C
  ;
  ;   1 lines,    4 bytes = 70.4 ms
  ;   2 lines,    8 bytes = 52.5 ms
  ;   4 lines,   16 bytes = 43.8 ms
  ;   8 lines,   32 bytes = 39.4 ms <-- 
  ;  16 lines,   64 bytes = 37.2 ms
  ;  32 lines,  128 bytes = 36.1 ms 
  ;  64 lines,  256 bytes = 35.6 ms 
  ; 128 lines,  512 bytes = 35.3 ms 
  ; 256 lines, 1024 bytes = 35.2 ms
Another related question is how do you time operations on the smaller picaxes as there seem to be no micro or milliseconds clocks or rather there are but their data is not available in handy variables?
The most pragmatic way is to set an output line high before you start and clear it when done, measure that with a logic analyser or digital scope. You could also use another PICAXE to execute a PULSIN to time it and report how long it took.

For things which take only very short periods one can execute them multiple times then subtract the time taken for the same loop without executing it.

But accurate timing of command execution time itself isn't at all easy because that does depend on where code is placed in memory. It's only microseconds though.
 

mrm

Well-known member
Thanks for the prompt responses.

I have experimented with various strategies for speeding up output on 14M and 20M/20X picaxes by building the bitmaps in memory and using code similar to your suggestion but the results were not as pleasing as the memory origin swap method in paraglider-nut's original post.

As a bit of background I somehow managed to miss paraglider-nut's simple code but arrived at something very similar by turning an Arduino objected oriented library of several thousand lines of C/C++ back into short procedural code and then translating this to picaxe basic. The resultant program which works astoundingly well on a picaxe 08m2 and even better on the 14m2 with hardware serial input apart from the screen update speed.

As for timing the use of an output to an external analyser seems the easiest way to go and could be accomplished with a simple Arduino sketch on a fast micro controller such as a samd51 or M7. How would you approach the timing task on a larger pickaxe such as a 20x2 or 28x2? From what I can glean from the data on these processors they have micro-second clocks and use overflows/interrupts to update the visible timer variables. Can they be accessed directly?
 

hippy

Technical Support
Staff member
From what I can glean from the data on these processors they have micro-second clocks and use overflows/interrupts to update the visible timer variables. Can they be accessed directly?
Using PEEKSFR you can access on-chip timers though there are some complexities on the M2's where you can be fighting with the PICAXE firmware. One such project -

 

AllyCat

Senior Member
Hi,
How would you approach the timing task on a larger pickaxe such as a 20x2 or 28x2? From what I can glean from the data on these processors they have micro-second clocks and use overflows/interrupts to update the visible timer variables. Can they be accessed directly?
You can use PEEKSFR (and POKESFR) commands to read/write most of the Hardware registers in the "base" PIC (into which the PICaxe interpreter is programmed). That includes the timers and various latches. The SFR (byte) values used by PICaxe are a reduced version of the longer addresses specified for the PICs and are different for X2 and M2 chips. All the Microchip data sheets for the base PICs can be found here.

All PICaxes operate with a basic Instruction clock period of 1 us, i.e. 1 MHz (or near multiples of 2, determined mainly by the SETFREQ command). In particular "Timer 1" is clocked at that frequency, but pre-loaded by the OS to give a 20 ms overflow period (for servo pulses). In M2s the overflow also drives a prescaler which increments the 1 second "time" variable, but sadly the prescaler is in software and not accessible. The PICaxe interpreter then uses from around 400 upwards Instruction Cycles to execute each "Basic" instruction.

Personally I always use M2s and have written a Code Snippet which can directly estimate the execution time of any single or multiple instructions up to a total of around 250,000 raw PIC Instruction Cycles (250 ms with the default "4 MHz" clock). You can find the code here. I believe it can be converted to work with X2s (mainly SFR address changes).

Cheers, Alan.
 

mrm

Well-known member
Using PEEKSFR you can access on-chip timers though there are some complexities on the M2's where you can be fighting with the PICAXE firmware. One such project -

Thanks for this link - the program looks quite daunting but will be very interesting to try.

One last question and then I will leave you in peace for a while. I see in the thread that you have pickaxe versions of Bressenham's algorithms for line and circle drawing. Can you point me to the link?
 

mrm

Well-known member
Hi,

You can use PEEKSFR (and POKESFR) commands to read/write most of the Hardware registers in the "base" PIC (into which the PICaxe interpreter is programmed). That includes the timers and various latches. The SFR (byte) values used by PICaxe are a reduced version of the longer addresses specified for the PICs and are different for X2 and M2 chips. All the Microchip data sheets for the base PICs can be found here.

All PICaxes operate with a basic Instruction clock period of 1 us, i.e. 1 MHz (or near multiples of 2, determined mainly by the SETFREQ command). In particular "Timer 1" is clocked at that frequency, but pre-loaded by the OS to give a 20 ms overflow period (for servo pulses). In M2s the overflow also drives a prescaler which increments the 1 second "time" variable, but sadly the prescaler is in software and not accessible. The PICaxe interpreter then uses from around 400 upwards Instruction Cycles to execute each "Basic" instruction.

Personally I always use M2s and have written a Code Snippet which can directly estimate the execution time of any single or multiple instructions up to a total of around 250,000 raw PIC Instruction Cycles (250 ms with the default "4 MHz" clock). You can find the code here. I believe it can be converted to work with X2s (mainly SFR address changes).

Cheers, Alan.
Thanks Alan / AllyCat for this informative link. I have downloaded it but not really had a chance to test it out yet. In fact I have not found the x2 chips as useful as I expected them to be so something that works on an M2 chip is quite welcome. Hippy's idea of setting a pin for use with an external timer is quite appealing provided there are spare pins and I have several spare X2 chips in reserve.

Your approach might allow timing to be done in situ in an existing program so that timing data could be gathered without the need for extra hardware. I will experiment with both approaches to see which is best.

Regards
 

hippy

Technical Support
Staff member
One last question and then I will leave you in peace for a while. I see in the thread that you have pickaxe versions of Bressenham's algorithms for line and circle drawing. Can you point me to the link?
No link as such but for drawing a line, which I suspect is Bressenham's algorithm, I have ...
Code:
#Macro DrawLine( a,b, c,d )
  x     = a
  y     = b
  xEnd  = c
  yEnd  = d
  Gosub Do_DrawLine
#EndMacro

Do_DrawLine:

  ; Draw a straight horizontal line optimisation
  If x = xEnd Then
    ; Set the first pixel which sets 'ptr' and
    ; also sets the bit number required in the
    ; 'dataByte' variable
    SetPixel(x,y)
    ; Get the pixel bit mask
    databyte = DCD dataByte 
    ; Then simply set the same bit in each of
    ; the subsequent display bytes
    Do While y < yEnd
      @ptrInc = @ptr | dataByte
      y = y + 1
    Loop
    Return
  End If

  ; Draw a straight vertical line optimisation
  If y = yEnd Then
    Do
      SetPixel(x,y)
      x = x + 1
    Loop Until x > xEnd
    Return
  End If

  dx = x - xEnd : If dx >= $80 Then : dx = -dx : End If
  dy = y - yEnd : If dy >= $80 Then : dy = -dy : End If
  If dx > dy Then
    ; Step along X-axis in one pixel steps
    If x > xEnd Then
      Swap x, xEnd
      Swap y, yEnd
    End If
    DD = dy + dy - dx
    Do While x <= xEnd
      SetPixel( x,y )
      If DD < $8000 Then
        If yEnd >= y Then
          y = y + 1
        Else
          y = y - 1
        End If
        DD = DD - dx
      End If
      DD = DD + dy
      x = x + 1
    Loop
  Else
    ; Step along Y-axis in one pixel steps
    If y > yEnd Then
      Swap y, yEnd
      Swap x, xEnd
    End If
    DD = dx + dx - dy
    Do While y <= yEnd
      SetPixel( x,y )
      If DD < $8000 Then
        If xEnd >= x Then
          x = x + 1
        Else
          x = x - 1
        End If
        DD = DD - dy
      End If
      DD = DD + dx
      y = y + 1
    Loop
  End If
  Return
For circles I have the following. This draws an arc of 45 degrees so mirrors it 8 times to get a full circle ...
Code:
#Macro DrawCircle(row,column,argRadius)
  xOrg = row
  yOrg = column
  radius = argRadius
  Gosub Do_DrawCircle
#EndMacro

Do_DrawCircle:

; Stefan Gustavson (stegu@itn.liu.se) 2003-08-20

Symbol dA = w7 ; dX
Symbol dB = w8 ; dY
Symbol R = radius

x=0
y=R
err = 4 * R : err = 5-err ; d=5-4*R;
dA=12
db = 8*R : dB = 20 - dB ; dB=20-8*R;
do while x <= y

        SetPixel(xOrg + x, yOrg + y)
        SetPixel(xOrg + y, yOrg + x)
        SetPixel(xOrg - y, yOrg + x)
        SetPixel(xOrg - x, yOrg + y)
        SetPixel(xOrg - x, yOrg - y)
        SetPixel(xOrg - y, yOrg - x)
        SetPixel(xOrg + y, yOrg - x)
        SetPixel(xOrg + x, yOrg - y)

  if err >= $8000 Then ;(d<0)
    err=err+dA
    dB=dB+8;
  else
    y=y-1;
    err=err+dB
    dB=dB+16;
  end if
  x=x+1
  dA=dA+8;
loop
return
And utility macros ...
Code:
#Macro AtPixel(row,column)
  ptr = row & $38 << 4
  ptr = column + ptr
#EndMacro

#Macro SetPixel(row,column)
  AtPixel(row,column)
  dataByte = row & 7
  @ptr = DCD dataByte | @ptr
#EndMacro
 

AllyCat

Senior Member
Hi,
One last question and then I will leave you in peace for a while. I see in the thread that you have pickaxe versions of Bressenham's algorithms for line and circle drawing. Can you point me to the link?
A long and rather rambling thread that I'd forgotten about, which neither the forum's or even a Google search found directly. But quite a lot of useful discussion of syntax and speed issues can be found here . However, it did reveal my lack of knowledge of some of the more obscure X2 (only) commands. ;)

Cheers, Alan.
 

mrm

Well-known member
No link as such but for drawing a line, which I suspect is Bressenham's algorithm, I have ...
Code:
#Macro DrawLine( a,b, c,d )
  x     = a
  y     = b
  xEnd  = c
  yEnd  = d
  Gosub Do_DrawLine
#EndMacro

Do_DrawLine:

  ; Draw a straight horizontal line optimisation
  If x = xEnd Then
    ; Set the first pixel which sets 'ptr' and
    ; also sets the bit number required in the
    ; 'dataByte' variable
    SetPixel(x,y)
    ; Get the pixel bit mask
    databyte = DCD dataByte
    ; Then simply set the same bit in each of
    ; the subsequent display bytes
    Do While y < yEnd
      @ptrInc = @ptr | dataByte
      y = y + 1
    Loop
    Return
  End If

  ; Draw a straight vertical line optimisation
  If y = yEnd Then
    Do
      SetPixel(x,y)
      x = x + 1
    Loop Until x > xEnd
    Return
  End If

  dx = x - xEnd : If dx >= $80 Then : dx = -dx : End If
  dy = y - yEnd : If dy >= $80 Then : dy = -dy : End If
  If dx > dy Then
    ; Step along X-axis in one pixel steps
    If x > xEnd Then
      Swap x, xEnd
      Swap y, yEnd
    End If
    DD = dy + dy - dx
    Do While x <= xEnd
      SetPixel( x,y )
      If DD < $8000 Then
        If yEnd >= y Then
          y = y + 1
        Else
          y = y - 1
        End If
        DD = DD - dx
      End If
      DD = DD + dy
      x = x + 1
    Loop
  Else
    ; Step along Y-axis in one pixel steps
    If y > yEnd Then
      Swap y, yEnd
      Swap x, xEnd
    End If
    DD = dx + dx - dy
    Do While y <= yEnd
      SetPixel( x,y )
      If DD < $8000 Then
        If xEnd >= x Then
          x = x + 1
        Else
          x = x - 1
        End If
        DD = DD - dy
      End If
      DD = DD + dx
      y = y + 1
    Loop
  End If
  Return
For circles I have the following. This draws an arc of 45 degrees so mirrors it 8 times to get a full circle ...
Code:
#Macro DrawCircle(row,column,argRadius)
  xOrg = row
  yOrg = column
  radius = argRadius
  Gosub Do_DrawCircle
#EndMacro

Do_DrawCircle:

; Stefan Gustavson (stegu@itn.liu.se) 2003-08-20

Symbol dA = w7 ; dX
Symbol dB = w8 ; dY
Symbol R = radius

x=0
y=R
err = 4 * R : err = 5-err ; d=5-4*R;
dA=12
db = 8*R : dB = 20 - dB ; dB=20-8*R;
do while x <= y

        SetPixel(xOrg + x, yOrg + y)
        SetPixel(xOrg + y, yOrg + x)
        SetPixel(xOrg - y, yOrg + x)
        SetPixel(xOrg - x, yOrg + y)
        SetPixel(xOrg - x, yOrg - y)
        SetPixel(xOrg - y, yOrg - x)
        SetPixel(xOrg + y, yOrg - x)
        SetPixel(xOrg + x, yOrg - y)

  if err >= $8000 Then ;(d<0)
    err=err+dA
    dB=dB+8;
  else
    y=y-1;
    err=err+dB
    dB=dB+16;
  end if
  x=x+1
  dA=dA+8;
loop
return
And utility macros ...
Code:
#Macro AtPixel(row,column)
  ptr = row & $38 << 4
  ptr = column + ptr
#EndMacro

#Macro SetPixel(row,column)
  AtPixel(row,column)
  dataByte = row & 7
  @ptr = DCD dataByte | @ptr
#EndMacro
Thank you for these. I will try them out and discover why my own attempts did not work. Regards
 

mrm

Well-known member
Hi,

A long and rather rambling thread that I'd forgotten about, which neither the forum's or even a Google search found directly. But quite a lot of useful discussion of syntax and speed issues can be found here . However, it did reveal my lack of knowledge of some of the more obscure X2 (only) commands. ;)

Cheers, Alan.
Thanks. Lots to read there.

Still getting to grips with the timing routines and also readying a picaxe 20x2 for use as an external timer so that the two approaches can be compared.

A crude cut and paste from your timer program produces a number but I have not figured out what it means. A simple test timing a PAUSE statement does not seem to return the length of the pause so I am back to testing in a more methodical way using your original program.
 

mrm

Well-known member
I got a very cheap I2C display (<£3) from eBay, intending to use it with an 08M2 but couldn't find much information about it. After a few hours research and some experimenting, I managed to get the display working and thought it might be helpful to share the results.



I started with the excellent information in this thread, which is for a different display but with much in common: http://www.picaxeforum.co.uk/archive/index.php/t-23134.html I also got inspiration for the font lookup from this thread: http://www.picaxeforum.co.uk/entry.php?30-Notes-behind-Magic-Morse

This is the display I purchased: http://www.ebay.co.uk/itm/162337263365?_trksid=p2057872.m2749.l2649&ssPageName=STRK:MEBIDX:IT

Although it says its 128 * 64, the display is actually 128 * 32 but the display memory holds 128 * 64. As each byte is written to the display memory it appears on screen so the display builds as it is sent. To avoid this, you can point the display to start at a specific memory location and as there is exactly enough memory for 2 screens of data you can write the message to the unused block and then point the display to it for instant update.

I needed to design a font to use with the display so I used excel to play around with different options. I settled on an 8 * 8 block for each character as this fitted very well with the display dimensions, giving four rows of sixteen characters. This is the spreadsheet I used:
https://www.dropbox.com/s/vw8mgrwu5hhi9dj/OLED Font.xls?dl=0
It converts the pixels into the eeprom commands needed to load the font into the 08M2. I only needed numbers and upper case characters (the programme will display any lower case characters as upper case). It would be straightforward to add any other characters that may be needed.

To send a message to the display you poke the required characters to memory locations 28 to 91 and then the programme looks up the font data and sends it to the display. When all the data has been sent, the display is pointed to the start of the latest message in display memory.

The code to drive the display is shown below. In this example the main programme just loads a message to memory locations 28 - 91, calls the sendtodisplay subroutine and then loops. As it only uses 413 bytes there is plenty of room for other code which could create its own messages.

Code:
symbol counter = w0
symbol subcounter = b2
symbol fontlookup = b3
symbol pixels = b4
symbol displaybank = b5

; save Font codes in EEPROM

eeprom 0,(124,130,130,130,124)       ; 0 (ASCII 48)
eeprom 5,(136,132,254,128,128)       ; 1
eeprom 10,(196,162,162,146,140)       ; 2
eeprom 15,(68,130,146,146,108)       ; 3
eeprom 20,(48,40,36,254,32)               ; 4
eeprom 25,(110,138,138,138,114)       ; 5
eeprom 30,(124,146,146,146,100)       ; 6
eeprom 35,(2,2,2,2,254)                   ; 7
eeprom 40,(108,146,146,146,108)       ; 8
eeprom 45,(30,18,18,18,254)               ; 9 (ASCII 57)
eeprom 50,(254,18,18,18,254)       ; A (ASCII 65)
eeprom 55,(254,146,146,146,108)       ; B
eeprom 60,(254,130,130,130,130)       ; C
eeprom 65,(254,130,130,68,56)       ; D
eeprom 70,(254,146,146,146,130)       ; E
eeprom 75,(254,18,18,18,2)           ; F
eeprom 80,(254,130,130,146,242)       ; G
eeprom 85,(254,16,16,16,254)       ; H
eeprom 90,(130,130,254,130,130)       ; I
eeprom 95,(226,130,254,2,2)               ; J
eeprom 100,(254,16,40,68,130)       ; K
eeprom 105,(254,128,128,128,128)       ; L
eeprom 110,(254,2,254,2,254)       ; M
eeprom 115,(254,4,56,64,254)       ; N
eeprom 120,(254,130,130,130,254)       ; O
eeprom 125,(254,18,18,18,12)       ; P
eeprom 130,(124,130,162,66,188)       ; Q
eeprom 135,(254,18,50,82,140)       ; R
eeprom 140,(222,146,146,146,246)       ; S
eeprom 145,(2,2,254,2,2)           ; T
eeprom 150,(126,128,128,128,126)       ; U
eeprom 155,(30,96,128,96,30)       ; V
eeprom 160,(254,128,254,128,254)       ; W
eeprom 165,(198,40,16,40,198)       ; X
eeprom 170,(6,8,240,8,6)           ; Y
eeprom 175,(194,162,146,138,134)       ; Z (ASCII 90)


initialise:
; initialise i2c for display (ID 0x78) and then send power up instructions
                   
hi2csetup i2cmaster,%01111000,i2cfast,i2cbyte   
hi2cout 0x80,(0xAE,0x00,0x00,0xB0,0x20,0x00,0xD5,0x80,0xA8,0x1F,0xD3,0x00,0x8D, _
0x14,0xA1,0x00,0xC8,0xDA,0x02,0x81,0xCF,0xD9,0xF1, 0xDB,0x30,0xA4,0xA6)

gosub sendtodisplay                    ; send 4 blank lines to clear display
let displaybank = 0                    ; flag rows 0-3 for display
hi2cout 0x80,(0xAF)                    ; turn display on


main:
; Insert your programme code here and then poke 28-91 with message to be displayed

poke 28," ","T","H","I","S"," ","I","S"," ","A"," ","T","E","S","T"," ";data to send to display (4 * 16)
poke 44,"O","F"," ","T","H","E"," ","O","U","T","P","U","T"," ","T","O"
poke 60,"T","H","E"," ","O","L","E","D"," ","D","I","S","P","L","A","Y"
poke 76,"F","R","O","M"," ","A","N"," ","0","8","M","2"," ","P","I","C"

displaybank = 1 - displaybank        ; toggle lines to display
gosub sendtodisplay                    ; load message to display memory and point display at it

; Insert any further code you require here

goto main

sendtodisplay:                                                ; sends characters in memory locations 28-91 to the display
for counter = 28 to 91
    PEEK counter, fontlookup                        ; load character to output
    select case fontlookup
        case 48 to 57                            ; 0-9
            fontlookup = fontlookup - 48 * 5
        case 65 to 90                            ; A-Z
            fontlookup = fontlookup -65 * 5 + 50
        case 97 to 122                            ; a-z output as A-Z
            fontlookup = fontlookup - 97 * 5 + 50
        else
            fontlookup = 244    ; all other characters will display a blank
    endselect                   
    hi2cout 0x40,(0)            ; one blank column
    for subcounter = 0 to 4
        read fontlookup, pixels       
        hi2cout 0x40,(pixels)        ; followed by 5 columns per character
        fontlookup = fontlookup + 1
    next subcounter
    hi2cout 0x40,(0,0)            ; two blank columns
next counter                    ; this completes the 8x8 character output
If displaybank = 0 then
    hi2cout 0x80,(0x40)            ; point display to lines 0-3 of display memory
    else hi2cout 0x80,(0x60)        ; point display to lines 4-7 of display memory
Endif
return
This is my first attempt at Picaxe programming, so any pointers to improve the way I've gone about it would be greatly appreciated and I hope it is of some use!
This code was very welcome as I had evolved largely similar code by returning an over-elaborate Arduino library to simple procedural code and then converted that to picaxe basic.

The only problem is the lazy screen update rate which you have solved nicely by varying the memory to display mapping. Courtesy of the forum I have acquired some code timing techniques and improved code execution speed but your memory mapping swap still gives the most pleasant visual result.

To evolve this technique it seems you have been able to understand the almost impenetrable technical data on the SSD1306.

Do you have any ideas how to switch the display memory start address to a specified page and column rather than just a horizontal page so that your technique can be applied to screens of a different aspect ratio (eg. 4:3) ?
 

hippy

Technical Support
Staff member
A crude cut and paste from your timer program produces a number but I have not figured out what it means. A simple test timing a PAUSE statement does not seem to return the length of the pause so I am back to testing in a more methodical way using your original program.
It's hard to advise when not knowing exactly what you are doing and what your code is.

This works for me, timing a PAUSE 2 on an 18M2 at 4 MHz, and measuring that with a 20X2 at 8MHz ...
Rich (BB code):
#Picaxe 18M2
#No_Data

Symbol OUT = B.3

Low OUT
Do
  Pause 10 : Toggle OUT : Gosub Dummy : Toggle OUT
  Pause 10 : Toggle OUT : Gosub Timed : Toggle OUT
Loop

Dummy:
  Return

Timed:
  Pause 2
  Return
Code:
#Picaxe 20X2
#Terminal 9600
#No_Data
#No_Table

Symbol IN       = C.0

Symbol baseline = w0
Symbol timed    = w1
Symbol actual   = w2
Symbol us       = w3

Do
  PulsIn IN, 1, baseline
  PulsIn IN, 1, timed
  If timed < baseline Then
    Swap baseline, timed
  End If
  actual = timed - baseline
  us = actual * 5
  SerTxd( #baseline, TAB, #timed, TAB, #actual, TAB, "= ", #us," us", CR, LF )
  Pause 1000
Loop
Code:
714   1168  454   = 2270 us
714   1175  461   = 2305 us
714   1168  454   = 2270 us
714   1174  460   = 2300 us
713   1168  455   = 2275 us
That seems about right; 2000us plus the command overhead of about 250us at 4MHz, given it's rough and ready and not calibrated.
 
  • Like
Reactions: mrm

mrm

Well-known member
It's hard to advise when not knowing exactly what you are doing and what your code is.

This works for me, timing a PAUSE 2 on an 18M2 at 4 MHz, and measuring that with a 20X2 at 8MHz ...
Rich (BB code):
#Picaxe 18M2
#No_Data

Symbol OUT = B.3

Low OUT
Do
  Pause 10 : Toggle OUT : Gosub Dummy : Toggle OUT
  Pause 10 : Toggle OUT : Gosub Timed : Toggle OUT
Loop

Dummy:
  Return

Timed:
  Pause 2
  Return
Code:
#Picaxe 20X2
#Terminal 9600
#No_Data
#No_Table

Symbol IN       = C.0

Symbol baseline = w0
Symbol timed    = w1
Symbol actual   = w2
Symbol us       = w3

Do
  PulsIn IN, 1, baseline
  PulsIn IN, 1, timed
  If timed < baseline Then
    Swap baseline, timed
  End If
  actual = timed - baseline
  us = actual * 5
  SerTxd( #baseline, TAB, #timed, TAB, #actual, TAB, "= ", #us," us", CR, LF )
  Pause 1000
Loop
Code:
714   1168  454   = 2270 us
714   1175  461   = 2305 us
714   1168  454   = 2270 us
714   1174  460   = 2300 us
713   1168  455   = 2275 us
That seems about right; 2000us plus the command overhead of about 250us at 4MHz, given it's rough and ready and not calibrated.
Thanks Hippy. The program used was the one linked to by Allycat in his post. In fact the crude cut and paste was too hasty so the result returned was appropriate to the M4 frequency only. I think the timing interval cannot exceed 64000muS which is why the pause statement was not timed.

Your code is however most useful as a 20x2 is now ready to run with your previously supplied timer code so that I can benchmark this code and also compare this with the results of Alleycat's code.
 

AllyCat

Senior Member
Hi,
Do you have any ideas how to switch the display memory start address to a specified page and column rather than just a horizontal page so that your technique can be applied to screens of a different aspect ratio (eg. 4:3) ?
Are there any different Aspect Ratios for these displays? Or do you just mean the "full sized" 8 rows (aka pages) version, which displays the complete 128 x 64 pixels?

The problem with this is that if all the pixels are displayed, then there are none "hidden", so you can't pop them up or exchange them with the present display. I've not used that display (yet) but a useful keyword for Google searches appears to be "scrolling" and I quickly found the following:

"The SSD1306 supports vertical scrolling by using the SSD1306_SETSTARTLINE command, which basically tells it where it should start reading its image buffer. Since the buffer wraps around, that lets us scroll it pixel-by-pixel."

#define SSD1306_SETSTARTLINE 0x40


There are also some references to Horizontal scrolling (of longer lines of characters) but I think that might be at the higher (A*****o) Library level, not directly in the SSD1306. However, I must admit that this display/thread has whetted my appetite. Unlike the "I2C expander backpacks" for the 16 x 2 and 20 x 4 LCD/OLED displays, it should be possible to read I2C data back from the display RAM. Also, since a 20 character row needs only 120 of the 128 pixels across the width, I'm considering the possibility of masking off the right hand 8 x 8 bytes to use as an additional "scratchpad" for an 08M2. ;)

Cheers, Alan.
 
  • Like
Reactions: mrm

hippy

Technical Support
Staff member
I can benchmark this code and also compare this with the results of Alleycat's code.
What exactly are you measuring and how long is it expected to take, and what PICAXE are you running what needs timing on ?

If you can tell us that it should be possible to suggest the best way to do that.

If you are only looking to see if doing something one way is faster than doing it another way that can usually be done with a repeated loop of what's being executed, issuing a piezo beep or sending a character out every time the loops complete. Using a stopwatch for timing. That's even good enough for determining short execution times -
Code:
#Picaxe 20X2
#Terminal 9600
#No_Data
#No_Table

Pause 5000
Do
  Sound C.0, (100,100)
  SerTxd( "X" )
  For w1 = 1 To 10000
    Gosub Timed
  Next
Loop

Timed:
  Pause 2
  Return
With nothing in the "Timed:" routine that takes about 17.12 seconds. With a PAUSE 2 added, that's about 39.50 seconds.

39.50 - 17.12 = 22.38 s = 22380000 us / 10000 = 2238 us per PAUSE 2 on a 20X2.

For a PAUSE 1 that's about 29.53 seconds

29.53 - 17.12 = 12.31 s = 12310000 us / 10000 = 1231 us per PAUSE 1 on a 20X2

So it seems the command overhead is about 230us though the difference is a combination of command variance, memory positioning, overhead and the odd microsecond variations as the firmware executes.
 
Last edited:

hippy

Technical Support
Staff member
since a 20 character row needs only 120 of the 128 pixels across the width, I'm considering the possibility of masking off the right hand 8 x 8 bytes to use as an additional "scratchpad" for an 08M2.
I don't think that works. As you say; the problem is if there are no hidden pixels, and I don't believe there is any means to mask out RAM from being shown, make them hidden. AIUI the display will pull 128x32 or 128x64 bits from memory and show them.

The start location of where to display from can be adjusted, but all the fancy windowing options are for writing into the RAM.
 

WhiteSpace

Well-known member
My understanding from having read the various threads on these SSD1306 OLEDs repeatedly over the last few weeks is that the RAM that they use can’t be read from, so if I understand that and AllyCat’s suggestion correctly, using the right hand 8 x 8 as extra storage wouldn’t work. I hesitate a little to chip in on a discussion between two experts, though! I’ve been working over the past couple of weeks on using one of these displays to show 3 or 4 separate pieces of information, each capable of being refreshed separately without needing to rewrite the entire 128 x 64. I haven’t quite finished, but I’ll post an update this evening in case that’s any use in this discussion. Essentially it’s just a question of specifying start column and row for the bit of the display that you want to write to, then inc’ing and dec’ing to build the characters. Doing so doesn’t wipe the rest of the display.
 

AllyCat

Senior Member
Hi,

By "masking", I meant something as simple as a piece of black tape. ;)

Yes, I wondered if Reading is possible and it looks as if WhiteSpace is probably correct. From the Data sheet:

"9.1 Data Read / Write To read data from the GDDRAM, select HIGH for both the R/W# (WR#) pin and the D/C# pin for 6800- series parallel mode and select LOW for the E (RD#) pin and HIGH for the D/C# pin for 8080-series parallel mode. No data read is provided in serial mode operation."

That's rather sad; a "strange" restriction which kills some of the ideas I had hoped to use. I wonder if I can find a workaround by reading some of the registers instead, or are they all write-only?

Cheers, Alan.
 

mrm

Well-known member
Hi,

Are there any different Aspect Ratios for these displays? Or do you just mean the "full sized" 8 rows (aka pages) version, which displays the complete 128 x 64 pixels?

The problem with this is that if all the pixels are displayed, then there are none "hidden", so you can't pop them up or exchange them with the present display. I've not used that display (yet) but a useful keyword for Google searches appears to be "scrolling" and I quickly found the following:

"The SSD1306 supports vertical scrolling by using the SSD1306_SETSTARTLINE command, which basically tells it where it should start reading its image buffer. Since the buffer wraps around, that lets us scroll it pixel-by-pixel."

#define SSD1306_SETSTARTLINE 0x40


There are also some references to Horizontal scrolling (of longer lines of characters) but I think that might be at the higher (A*****o) Library level, not directly in the SSD1306. However, I must admit that this display/thread has whetted my appetite. Unlike the "I2C expander backpacks" for the 16 x 2 and 20 x 4 LCD/OLED displays, it should be possible to read I2C data back from the display RAM. Also, since a 20 character row needs only 120 of the 128 pixels across the width, I'm considering the possibility of masking off the right hand 8 x 8 bytes to use as an additional "scratchpad" for an 08M2. ;)

Cheers, Alan.
Never having been a great fan of object oriented languages with variables two or three lines long joined by multiple sets of difficult to type double colons it was very encouraging to be able to drive an oled display with around 30 lines of code using a 08m2 picaxe and with enough memory left to attempt graphics although arithmetic on a picaxe proved challenging. The 14m2 proved to be an ideal fit as there is enough memory to allow for a screen buffer and bitmap graphics.

The only drawback is the slow drawing rate. Drawing speed on characters is seemingly not much improved by writing them first to a buffer and then outputting them to the gddram although this makes no sense and is possibly just an optical effect. Hence my interest in timing.

In January events prompted me to return to a homespun monitoring solution to oversee a water pumping system and I returned to the picaxe driven mini oled to display data also noticing paraglider-nut's program and its memory origin switching solution to the screen redraw problem.

The gddram on the SSD1306 controller consists of of 8 pages of 128 columns of 8 bits per page ie 128 x 64 pixels whereas a 64x48 oled would easily allow 2 screens if paraglider-nut's technique could be used.

In the Solomon Systems datasheet there is an example of setting the screen origin to col 3 of page 2 but it does not seem to work when tried and possibly the screen is hardwired to a certain set of pixels.

Having invested some time in making a small daughterboard for the oled with an 08m2 smd picaxe and other components so that everything fits into a small window in the corner of a switch box it would be nice to have a smooth screen redraw although in reality waiting for a fraction of a second as part of the display is updated is not critical.
 
Top