tabemann / zeptoforth

A not-so-small Forth for Cortex-M
MIT License
192 stars 18 forks source link

How to use SPI #10

Open ctrilsbach opened 2 years ago

ctrilsbach commented 2 years ago

Hi Travis,

I have changed this comment because I have just realised that I have confused I2C with SPI. Does Zeptoforth do I2C as well as I am trying to connect to an OLED display ?

Chris Trilsbach

tabemann commented 2 years ago

zeptoforth does not do I2C yet but I plan on adding support in the near future.

ctrilsbach commented 2 years ago

Thanks Travis. I look forward to trying it.

tabemann commented 2 years ago

Hey @ctrilsbach - I now have working I2C support for the Raspberry Pi Pico. Note that the latest build does not include slave-receiver NACK generation, while the repository includes it. I highly recommend building a new build (mind you this currently requires a Unix-like OS, Python 3, and pySerial - and note that people have reported issues with pySerial installation on xBSD), if you feel up to it, so not only you can have not only slave-receiver NACK generation, but also has a significant performance optimization with regard to compilation.

ctrilsbach commented 2 years ago

Hi Travis, Thanks I have been looking at your new I2C functionality. I am using a Raspberry Pi 4 to connect to the Pico using Picocom so should be able to build it. I am not sure what slave-receiver NACK is though and if I need it as all I am trying to do is drive a SSD1306 based display. I have this already working on Mecrisp/Tachyon and want to move over to Zeptoforth. A couple of questions from looking at the new words: With enable-i2c on the Pico which is I2C 0 and 1 ? The diagram I have shows default on GP4 and 5 so I presume this is 0 ? I need to send several sets of data to the display. I presume that I need to set up the data first and then use successive i2c-stop>’s with each block of data ? Or do I use >i2c’s and then a i2c-stop> with the last one. Thanks in advance. Chris

tabemann commented 2 years ago

Chris,

Don't worry about the slave-receiver NACK for what you are doing, as it is only relevant if you are implementing a slave device. Also, if you are using GP4 and GP5 you will want I2C peripheral 0.

For sending an individual block of data to the display I would use >i2c except for the last data in the block where I would use >i2c-stop, which signals a STOP condition once sending data is complete. If you are only sending data disregard i2c> and i2c-stop>, but these are used similarly for receiving data.

Travis

tabemann commented 2 years ago

Chris,

One note - don't try to use Picocom for building zeptoforth on the Pico - it just won't work because Picocom doesn't have line synchronization. For building images, first flash the UF2 image for the kernel with the BOOTSEL-USB Mass Storage Device mechanism, and then use make_uf2_image.sh (but mind you, because it is designed to build multiple images in a row it automatically erases everything but the kernel when it is done) or codeload3.py (which loads the code but does not in itself build an image, or erase anything). You could try using zeptocom.js, which is at https://tabemann.github.io/zeptocomjs/zeptocom.html to upload code, but that requires Chrome or Chromium or Opera and I don't know how well those will work on a RPi 4.

Travis

tabemann commented 2 years ago

Chris,

I have just made a new release, 0.46.1, which includes these changes, so there is no need for you to build zeptoforth from scratch just for them.

Travis

ctrilsbach commented 2 years ago

Great thanks Travis, downloading it now.

tabemann commented 2 years ago

Just a note - don't use [if] [else] [then] inside EVALUATE'd code, including code loaded from a FAT32 filesystem - it will break badly; I'm working on fixing that now.

ctrilsbach commented 2 years ago

Travis, Do you need to use enable-i2c ?

If I try the following, Forth locks up and has to be re-booted.

i2c import 0 enable-i2c 0 master-i2c

I notice in the test directory your i2c code doesn't seem to use enable-i2c

Chris

tabemann commented 2 years ago

Chris,

Thanks for catching that for me!

I did not catch that bug because I always ran it as:

i2c import
0 master-i2c
0 enable-i2c

enable-i2c is necessary, and it is recommended that master-i2c be run first, so I never caught that. But it indeed is a bug, because master-i2c temporarily disables the I2C peripheral selected if it is currently enabled, and there was a bug in that logic. I have now fixed it in the master and devel branches, but the fix is not in a release yet.

Travis

tabemann commented 2 years ago

Chris,

I just have made a new patch-level release, 0.46.3, which fixes this issue amongst other issues.

Travis

ctrilsbach commented 2 years ago

Travis,

Thanks for fixing the bug.

I am now getting an error "I2C target address not acknowledged" after running >i2c-stop .

My code so far to initialise a SSD1306 display is :-

i2c import pin import

create init_display $00 c, \ control byte for command stream $A6 c, \ set normal display $D5 c, $80 c, \ set display clock divider $8D c, $14 c, \ set charge pump $81 c, $7F c, \ set contrast $DA c, $12 c, \ set com pin map $AF c, \ set display on $21 c, $00 c, $7F c, \ set column 127 $22 c, $00 c, $03 c, \ set page 3

0 master-i2c 0 enable-i2c 0 4 i2c-pin 0 5 i2c-pin 4 pull-up-pin 5 pull-up-pin

0 7-bit-i2c-addr $3C 0 i2c-target-addr!

init_display 17 0 >i2c-stop

Thanks for your help

Chris

ps I have been using zeptocom.js to upload. Should you be able to type in the terminal window ?

tabemann commented 2 years ago

Chris,

Thanks a lot for trying out my I2C layer for the Raspberry Pi Pico. This is a bug I didn't catch because the address I was using ($55) for some reason just happened to not catch where bit patterns for target addresses were not being set properly (which is strange, as from looking back at the code it should never have worked for that value in the first place). I have now made a new patch-level release, 0.46.3.1, that fixes this issue.

Note that for zeptocom.js you are not supposed to be able to edit the terminal window; it is solely output alone, because it is showing what is being echoed by the MCU along with advisory messages from zeptocom.js itself.

Travis

tabemann commented 2 years ago

Chris,

BTW, apparently you probably want to use separate pull-up resistors (I use 4K7 ohm resistors) rather than using the built-in pull-up resistors, even though I have also heard that in many applications the built-in pull-up resistors work okay anyways.

Travis

tabemann commented 2 years ago

Chris,

I just ran your code, and while it did not have any obvious failures (didn't crash, didn't raise an exception, etc.), I should note that >i2c-stop returns the number of bytes that it has registered as having sent. I should note that there is an issue, which I have not solved yet, with registering failure to acknowledge the target address when no active I2C peripheral is on the other end — when I run your code it now does not register said failure the first time the last line is run and I have to run it again for the failure to be detected.

Travis

tabemann commented 2 years ago

I am going to investigate this issue more, as it ought to be failing right away rather than having to send more data for it to fail in this case.

ctrilsbach commented 2 years ago

Travis,

Glad that I could be of some help finding these bugs.

The display does work fine with the internal pull ups but I have taken your advice and used some external resistors just in case.

The display is working now, but not displaying correctly. I know the data is fine as I have already had it working but need to check my code to see if I have done something stupid.

I cannot get the dump word to work for some reason ? On other Forths I have used the syntax is dump ( addr u -- ) where u is the number of bytes. I wrote my own but wondered what I was doing wrong.

Chris

tabemann commented 2 years ago

Chris,

If it works fine with internal resistors, I would just use those. One way or another I probably would not combine internal resistors with external ones.

I would be really interested in seeing your Mecrisp/Tachyon code alongside your current zeptoforth code for this to see what's different (to see if you're using them differently or to see if there's a bug in my code).

BTW, dump has the signature ( start-addr end-addr -- ) where end-addr is one byte past the last byte one wants to display (i.e. to dump $20000000 through $200000FF one executes $20000000 $20000100 dump).

Travis

ctrilsbach commented 2 years ago

Travis,

Yes, I wasn't going to use the internal pull ups as well.

Peter's i2c code is a bit bang interface so you have to start a stream write bytes and send stop. So the main difference is that with his code I wrote directly to the display and with your layer I have to construct a buffer each time and then pass the address of it.

This is an example of using Tachyon that Peter sent me :-

MECRISP compiletoflash { SSD1306 128X64 OLED I2C CONTROLLER DISPLAYS 0.49" 64X32 0.66" 64X48 0.91" 128X32 0.96" 128x64 } --- buffer to handle any SSD1306 display ( use sdbuf for now ) SDBUF constant lcdbuf

--- select SSD1306 : SSD1306 I2C.START $78 I2C! ; --- Init SSD1306 : !LCD SSD1306 0 I2C! $8D I2C! $14 I2C! $A1 I2C! $C8 I2C! $AF I2C! I2C.STOP ; --- UPDATE OLED (actually the whole SSD1306 ) : LCDWR !LCD lcdbuf 8 0 DO --- select one cmd page# datastm SSD1306 $80 I2C! $B0 I + I2C! $40 I2C! --- stream 128 bytes data = 1024 128 0 DO DUP C@ I2C! 1+ LOOP I2C.STOP LOOP DROP ; --- clear the display : LCDCLR LCDBUF 1024 ERASE LCDWR ; --- write a test pattern : LCDPAT LCDBUF 1024 $AA FILL LCDWR ;

--- SOME SHORTCUTS FOR TESTING : LCD! lcdbuf $400 MOVE ; ' LCDCLR $0F CTRL! ' LCDPAT $1D CTRL!

--- AUTO UPDATE IN THE BACKGROUND ' LCDWR +POLL compiletoram END

This my code so far to hopefully put digit 0 on the display. It is working but only clearing half the display and the zero is not right.

i2c import pin import

variable bytecntr

create i2c-buf 1024 allot \ buffer for display

create digit0 $FFFFFCFC , $C3C30303 , $FFFF3333 , $0000FCFC , $3F3F0F0F , $30303333 , $3F3F3030 , $00000F0F ,

create init-display $00 c, \ control byte for command stream $A6 c, \ set normal display $D5 c, $80 c, \ set display clock divider $8D c, $14 c, \ set charge pump $81 c, $7F c, \ set contrast $DA c, $12 c, \ set com pin map $AF c, \ set display on $21 c, $00 c, $7F c, \ set max column 127 $22 c, $00 c, $03 c, \ set max pages 4

0 master-i2c 0 enable-i2c 0 4 i2c-pin 0 5 i2c-pin \ 4 pull-up-pin \ 5 pull-up-pin

0 7-bit-i2c-addr $3C 0 i2c-target-addr!

: dmp 20 0 do dup I + c@ . loop ;

: setup-display ( -- ) init-display 17 0 >i2c-stop drop ;

: store-char ( c -- ) \ put chars in array bytecntr @ i2c-buf + c! bytecntr @ 1 + bytecntr ! ;

: clear-screen 0 bytecntr ! 4 0 do \ 4 line display 0 bytecntr ! $00 store-char \ Control byte for command stream $B0 I + store-char \ set page to I $00 store-char \ set lower nibble of column start address to 0 $10 store-char \ set higher nibble of column start address to 0 i2c-buf bytecntr @ 0 >i2c-stop drop

    0 bytecntr !
    $40 store-char \ Control byte for data stream 
    128 0 do
        $00 store-char 
    loop
    i2c-buf bytecntr @ 0 >i2c-stop
    drop
loop

;

: load-digit 5 3 do \ two lines per digit 0 bytecntr ! $00 store-char \ Control byte for command stream $B0 I + store-char \ set page to I $00 store-char \ set lower nibble of column start address to 0 $10 store-char \ set higher nibble of column start address to 0 i2c-buf bytecntr @ 0 >i2c-stop drop

    0 bytecntr !
    $40 store-char \ Control byte for data stream 
    16 0 do
        digit0 bytecntr @ + c@ store-char
    loop
    i2c-buf bytecntr @ 0 >i2c-stop
    drop
loop

;

: main setup-display clear-screen load-digit ;

This is my first project in Forth so it probably looks a bit long winded.

Chris

tabemann commented 2 years ago

Chris,

It turns out that there might be a bug in the STOP generation where it's generating the STOP too soon, which I am working on fixing at the moment.

Travis

tabemann commented 2 years ago

Chris,

I have made a new release, 0.46.3.2, which should fix the STOP issue. Try it out and let me know how it works out.

Travis

ctrilsbach commented 2 years ago

Travis,

All working ok thanks. The screen fully clears now and I found a small bug in my code as well that was missing off the first byte of digit 0's data which wasn't helping.

Now I can change my rest of the code in my project to use Zeptoforth. It controls a stepper motor to move a camera and activates the shutter as well to do focus stacking. The display is needed to show information on the shoot. When it is finished I intend to publish the design on line and would rather the instructions on how to install Forth and the code to be straight forward and well documented rather then having to load Mecrisp then Tachyon which is not.

Hopefully I won't have to bother you any more for a while.

Thanks again.

Chris

tabemann commented 2 years ago

Chris,

Oh, I don't consider it a bother — I would much rather have feedback about zeptoforth (and would like bugs others find being reported) than not have any feedback, and I like hearing that you are using zeptoforth for an actual project (and would like to hear about your future progress with it). Please keep me informed about what you are doing and how things are going.

You're welcome,

Travis