Project

General

Profile

i2c with forth on the blue pill?

Added by Rolf 3 months ago

Just tried i2c on the blue pill. i2c-init gives a proper "ok."-prompt, but "i2c." shows a lot of adresses of devices that do not exist. The driver is commented as "not working yet". May be this is the cause?


Replies (4)

RE: i2c with forth on the blue pill? - Added by jcw 3 months ago

Correct: the hardware I2C driver for F103 is not working (JNZ/L052 is ok).
But you can use the bit-banged version (it doesn't support clock stretching).

RE: i2c with forth on the blue pill? - Added by pragtich 2 months ago

Hi,

This is my first post in the forums, I am Joris, located in Enschede, the Netherlands. I am an engineer by day and play a little with microcontrollers in my spare time.

Was intrigued by the cheap STM32F103 boards and found the posts on using Mecrisp on them together with folie. It's become quite addictive.

I saw above remark as a challenge, and think I came up with something workable to use the hardware i2c on the blue pill. It has to be said, that I wonder if it is much more efficient than the bit banged protocol: the STM32F103 I2C hardware is very complex and you still end up doing lots of waiting and twiddling of bits.

Any way:
1. This follows the documentation (RM0008) and was not optimized, it is quite tricky to get the ACK/NAK and STOP right in receiver mode and I do not want to jinx it too early on. So many actions to keep track of.
2. It uses polling and is therefore not very efficient. I/one should probably add some pause-s and run it from a task, or might try an interrupt approach instead (INT restarting a waiting task, or using the interrupts to feed from/to a buffer, not sure yet). Serial comms (I'm using g6u/g6u-common.hex) die during i2c transactions. Frustrating during development! Needs hardware reset to recover.
3. Not sure about the timings; I find the clock configuration chapter in the manual quite illegible and had to make some guesses. Could someone with an oscilloscope perhaps check?
4. I should add some kind of timeout to the polling. It will now hang forever in many error cases: annoying. Perhaps throw/catch something on timeout?
5. Tested with an SSD1306 & homemade Si70xx driver. Still seeing some random hangs. Timing issues? Or simply not understanding the peripheral enough?
6. I copied the API methods from the flib/any/i2c-bb.fs, so other i2c libraries should work, not tested yet.
7. I still have compiletoram in the file to facilitate debugging / limit flash wear

Would be interested in others' opinions & testing. Don't expect fast progress: this is a low priority hobby project. Other's input is very welcome :-)

https://github.com/pragtich/embello/blob/master/explore/1608-forth/g6u/i2c.fs

forgetram
compiletoram

\ Define pins
[ifndef] SCL  PB6 constant SCL  [then]
[ifndef] SDA  PB7 constant SDA  [then]

$40005400 constant I2C1
$40005800 constant I2C2
     I2C1 $00 + constant I2C1-CR1
     I2C1 $04 + constant I2C1-CR2
     I2C1 $08 + constant I2C1-OAR1
     I2C1 $0C + constant I2C1-OAR2
     I2C1 $10 + constant I2C1-DR
     I2C1 $14 + constant I2C1-SR1
     I2C1 $18 + constant I2C1-SR2
     I2C1 $1C + constant I2C1-CCR
     I2C1 $20 + constant I2C1-TRISE

3  bit constant APB2-GPIOB-EN
0  bit constant APB2-AFIO-EN
21 bit constant APB1-I2C1-EN
21 bit constant APB1-RST-I2C1

$40021000 constant RCC
     RCC $00 + constant RCC-CR
     RCC $04 + constant RCC-CFGR
     RCC $10 + constant RCC-APB1RSTR
     RCC $14 + constant RCC-AHBENR
     RCC $18 + constant RCC-APB2ENR
     RCC $1C + constant RCC-APB1ENR

     0 variable i2c.cnt
     0 variable i2c.addr
     0 variable i2c.needstop

\ Checks I2C1 busy bit
: i2c-busy?   ( -- b) I2C1-SR2 h@ 1 bit and 0<> ;


\ Init and reset I2C. Probably overkill. TODO simplify
: i2c-init
  \ Reset I2C1
  APB1-RST-I2C1 RCC-APB1RSTR bis!
  APB1-RST-I2C1 RCC-APB1RSTR bic!

  \ init clocks
  APB2-GPIOB-EN APB2-AFIO-EN or RCC-APB2ENR bis!
  APB1-I2C1-EN                  RCC-APB1ENR bis!

  \ init GPIO
  IMODE-FLOAT SCL io-mode!  \ edited: manual says use floating input
  IMODE-FLOAT SDA io-mode!
  OMODE-AF-OD OMODE-FAST + SCL io-mode!  \ %1101 AF means I2C Using external pullup
  OMODE-AF-OD OMODE-FAST + SDA io-mode!  \ We need to connect external pullup R to SCL and SDA

  \ Reset I2C peripheral
   15 bit I2C1-CR1 hbis!
   15 bit I2C1-CR1 hbic!


  \ Enable I2C peripheral
  21 bit RCC-APB1ENR bis!  \ set I2C1EN
  $3F I2C1-CR2 hbic!       \ CLEAR FREQ field
  36 I2C1-CR2 hbis!        \ APB1 is 36 MHz

  \ clock rate clock-hz; USB = 72 MHz
  \ crystal 8mhz x 9
  \ PLL 72 MHz / 2
  \ 36 MHz AHB /6
  \ 6 MHz ADC
  \ 001D840A RCC-CFGR
  \ SW: 10    PLL as clock
  \ SWS: 10   PLL as clock
  \ HPRE: 0   AHB = SYSCLK
  \ PPRE1 100 APB1/PCLK1 = /2 must not be > 36 MHz
  \ PPRE2 000 APB2/PCLK2 = /1
  \ ADCPRE 10 ADCPRE = /6
  \ PLLSRC 1  HSE = PLL input
  \ PLLXTPRE 0 HSE /1
  \ PLLMUL 0111 PLL = cryst x9

  \ cryst 8MHz x9 = PLLCLK 72 MHZ
  \ SYSCLK = PLLCLK = 72 MHz
  \ SYSCLK / AHB prescale = 72MHz AHBCLK
  \ AHBCLK / APB1 = 36MHz
  \ AHBCLK / APB2 = 72MHz
  \ I2C = APB1 = 36MHz

  \ Configure clock control registers?!

  27           \ CCR 27?
  15 bit or    \ FM
  I2C1-CCR h!  \ FREQ = 36MHZ, 31 ns; DUTY=0 3x 31ns = 10 MHz; moet delen door 27 
  3  I2C1-TRISE h!         \ 2+1 for 1000ns SCL

  0  bit I2C1-CR1 hbis!    \ Enable bit
  10 bit I2C1-CR1 hbis!    \ ACK enable

  \ Wait for bus to initialize
  begin i2c-busy? 0= until
;



\ debugging
: i2c? cr I2C1-CR1 h@ hex. I2C1-CR2 h@ hex. I2C1-SR1 h@ hex. I2C1-SR2 h@ hex. ;

\ Low level register setting and checking
: i2c-DR!     ( c -- )  I2C1-DR c! ;                 \ Writes data register
: i2c-DR@     (  -- c ) I2C1-DR c@ ;                 \ Writes data register
: i2c-start!  ( -- )    8 bit I2C1-CR1 hbis! ;
: i2c-stop!   ( -- )    9 bit I2C1-CR1 hbis! ;
: i2c-AF-0 ( -- )  10 bit I2C1-SR1 hbic! ;      \ Clars AF flag
: i2c-START-0 ( -- )   8 bit I2C1-CR1 hbic! ;      \ Clears START condition
: i2c-SR1-flag? ( u -- ) I2C1-SR1 hbit@ ;
: i2c-ACK-1 ( -- ) 10 bit I2C1-CR1 hbis! ;
: i2c-ACK-0 ( -- ) 10 bit I2C1-CR1 hbic! ;
: i2c-POS-1 ( -- ) 11 bit I2C1-CR1 hbis! ;
: i2c-POS-0 ( -- ) 11 bit I2C1-CR1 hbic! ;

\ Low level status checking
: i2c-sb?  ( -- b)   0  bit i2c-SR1-flag? ;      \ Gets start bit flag
: i2c-nak? ( -- b)   10 bit i2c-SR1-flag? ;      \ Gets AF bit flag
: i2c-TxE? ( -- b)   7  bit i2c-SR1-flag? ;      \ TX register empty
: i2c-ADDR? ( -- b)  1  bit i2c-SR1-flag? ;      \ ADDR bit
: i2c-MSL? ( -- b)   0  bit I2C1-SR2 hbit@ ;      \ MSL bit

: i2c-SR1-wait ( u -- ) begin dup i2c-SR1-flag?         until drop ; \ Waits until SR1 meets bit mask
: i2c-SR1-!wait ( u -- ) begin dup i2c-SR1-flag? 0= until drop ;

0  bit constant i2c-SR1-SB
1  bit constant i2c-SR1-ADDR
2  bit constant i2c-SR1-BTF
6  bit constant i2c-SR1-RxNE
7  bit constant i2c-SR1-TxE
10 bit constant i2c-SR1-AF

\ Medium level actions, no or limited status checking

: i2c-start ( -- ) \ set start bit and wait for start condition
  i2c-start! i2c-SR1-SB i2c-SR1-wait ; 

: i2c-stop  ( -- )  i2c-stop! begin i2c-MSL? 0= until ; \ stop and wait

: i2c-probe ( c -- nak ) \ Sets address and waits for ACK or NAK
  i2c-start
  shl i2c-DR! \ Send address (low bit zero)
  i2c-SR1-AF i2c-SR1-ADDR or i2c-SR1-wait \ Wait for address sent
  i2c-nak?    \ Put AE on stack (NAK)
  i2c-AF-0
  i2c-stop
;

\ STM32 EV Events

: i2c-EV5   i2c-SR1-SB   i2c-SR1-wait ;
: i2c-EV6   i2c-SR1-ADDR i2c-SR1-AF or
                     i2c-SR1-wait
        I2C1-SR2 h@ drop ; \ Wait for address sent or AF
: i2c-EV8_1 i2c-SR1-TxE  i2c-SR1-wait ;
: i2c-EV7   i2c-SR1-RxNE i2c-SR1-wait ;
: i2c-EV7_2 i2c-SR1-BTF  i2c-SR1-wait ;

\ Compatibility layer

: i2c-addr ( u --) \ Start a new transaction and send address in write mode
  i2c-start
  shl dup i2c.addr !
  i2c-EV5

  i2c-DR!                   \ Sends address (write mode)
  i2c-EV6               \ wait for completion of addressing or AF
;

: i2c-xfer ( u -- nak) \ prepares for an nbyte reply. Use after i2c-addr. Stops i2c after completion.
    dup i2c.cnt !
    case     
      2 of    \ cnt = 2
    i2c-start  \ set start bit,  wait for start condition

    i2c.addr @ 1 or \ Send address with read bit
    i2c-DR!

    i2c-POS-1 i2c-ACK-1

    i2c-EV6 I2C1-SR2 @ drop \ wait for ADDR and clear
    i2c-ACK-0
    i2c-SR1-BTF i2c-SR1-wait \ wait for BTF
    i2c-stop!                \ set stop without waiting
    0 i2c.needstop !
      endof
      1 of endof        ( cnt = 1 )
      0 of          ( cnt = 0, probe only )
        i2c-nak? i2c-AF-0 i2c-stop
    0 i2c.needstop !
      endof
      ( default: n > 2 )
    i2c-start  \ set start bit,  wait for start condition

    i2c.addr @ 1 or \ Send address with read bit
    i2c-DR!
    i2c-EV6    \ wait until ready to read
    \ i2c-SR1-ADDR i2c-SR1-wait
    1 i2c.needstop !
    endcase
;

: >i2c  ( u -- ) \ Sends a byte over i2c. Use after i2c-addr
  i2c-EV8_1
  i2c-DR!
;

: i2c>

  i2c.needstop @
  if    \ need to do stop stuff when i2c-xfer could not
    i2c.cnt @
    case
      3 of          ( prepare for last bytes )
    i2c-EV7_2
    i2c-ACK-0
    i2c-DR@
    -1 i2c.cnt +!
    i2c-stop!
      endof
      2 of
    i2c-DR@
    -1 i2c.cnt +!
    0 i2c.needstop !
    \ no further special handling needed
    \ Last byte follows normal protocol
      endof         ( default action cnt > 3, simple receive )
      i2c-EV7    \ wait until data received
      i2c-DR@
      -1 i2c.cnt +!
    endcase

  else  \ stop stuff was handled in i2c-xfer
    i2c-EV7    \ wait until data received
    i2c-DR@
    -1 i2c.cnt +!
  then

  i2c.cnt @ 0=
  if
    i2c-POS-0 i2c-ACK-1
    i2c-DR@ drop        ( Do not understand why I need this )
  then
;


: i2c>h
    i2c>   i2c>  8 lshift or 
;


: i2c>h_inv
    i2c>  8 lshift i2c>  or 
;


\ High level transactions

: i2c. ( -- )  \ scan and report all I2C devices on the bus
  128 0 do
    cr i h.2 ." :"
    16 0 do  space i j +
      dup $08 < over $77 > or if drop 2 spaces else
        dup i2c-probe if drop ." --" else h.2 then
      then
    loop
  16 +loop ;


RE: i2c with forth on the blue pill? - Added by jcw 2 months ago

Niiiice!

Yeah, I recognise the pain - it took quite some head-scratching to get I2C h/w going on the JNZ's L052 as well. And indeed, with the same conclusion... what's the gain, in polled mode? - one benefit of the h/w driver is that it'll support clock-stretching, which a few hardware I2C chips seem to rely on. An interrupt- or task-bsed approach might benefit, but I don't expect it to become a major factor when the µC is the master and it's merely triggering a few sensors chips once in a while. If the µC has nothing else to do in the meantime anyway, then polled should work just as well, and will also allow the µC to sleep in between I2C bus activity.

Still, especially since you've given your driver the same API as the bit-banged version, I'd be delighted to merge it into the embello repository if/when you think the time is right. If you'd like to see this happen, I'd very much welcome a pull request.

(Groetjes uit Houten...)

-jcw

RE: i2c with forth on the blue pill? - Added by pragtich 2 months ago

[[[https://github.com/jeelabs/embello/pull/58]]]

Best regards,
Joris

    (1-4/4)