i2c with forth on the blue pill?
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?
RE: i2c with forth on the blue pill? - Added by jcw 4 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 4 months ago
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.
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 :-)
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 3 months ago
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...)
RE: i2c with forth on the blue pill? - Added by pragtich 3 months ago