Forth on the RP2040 and Pin Testing

Where I work with the Adafruit Feather RP2040 (Feather), Mecrisp-Stellaris Forth (MSForth) and create Forth versions of ManPinTest and PinTest.

Sources

Adafruit Feather RP2040

Adafruit Feather RP2040

Background

This entry will be very similar to the one using the RP2040 and MicroPython, the difference will is I will use MSForth instead.

Getting Started

I’m going to assume that the serial connection, Forth installation and the ability to write/edit/run Forth programs on the RP2040 already exists. If not see this entry.

The original ManPinTest needed a hard-coded pin number, on-time and off-time and it would output the pin number, frequency and duty cycle. One would use the Labrador to view the outcome. In this case, Forth is interactive and a nice test would be one which would accept a pin number and simply blink that pin. I’ll also change the name from ManPinTest to blink_GPIO as that is more descriptive or more, Forthy.

Due to my issues prior with confirming the Feather on-board LED was activated, I think its best to have a test to confirm the desired pin is in the right mode (F5). Then we will blink with the ability to interupt the blinking with a key press.

After 2 hours of fruitless endeavors, I began to write short words which I would need to blink a pin:

  • Set_F5 – make sure that the GPIO pin is in F5 mode
  • Set_OUT – make sure the GPIO pin is set as an output pin
  • Tog_Pin – toggle the pin value
  • Half_sec – easy mnemonic for remembering 1/2 of a second delay

Once I had those definitions well-debugged, the blink_pin word wrote itself!

: blink_GPIO ( GPIO -- ) 
    dup GPIO_F5
    dup GPIO_OUT 
      begin
        dup tog_GPIO
        tenth_sec ( -- )
      key? until drop
; 

The complete code is here, including HAL for specific definitions related to the RP2040 chip and Feather board.

\ Feather RP2040 localization
#13                 constant GP13
GP13                constant LED
#2                  constant minGPIO
#29                 constant maxGPIO
#0                  constant minTest
#3                  constant maxTest
#8                  constant padsize

: GPIO_ctrl ( GPIO -- ) \ get the address for the specific GPIO ctrl register
    #8 * IO_BANK0_GPIO0_CTRL +
;

\ print values of the GPIO_CTRL registers
: print_CTRL ( n -- ) \ print CTRL values upto GPIO n
    0 CR DO 
    I GPIO_ctrl @
    I . . CR
    LOOP 
;

: one_sec ( -- )        \ one sec ( -- )ond delay
    1000 ms
;

: half_sec ( -- )       \ half sec ( -- )ond delay
    500 ms
;

: qtr_sec ( -- )        \ quarter sec ( -- )ond delay
    250 ms
;

: tenth_sec ( -- )      \ tenth sec ( -- )ond delay
    100 ms
;

: GPIO_F5 ( GPIO -- )       \ ensure GPIO is in F5
    dup GPIO_ctrl 
    @ %11111 and
    5 = if drop else ." Not F5! " 5 swap GPIO_ctrl ! then
;

: GPIO_OUT ( GPIO -- )  \ set GPIO to output
    1 swap lshift GPIO_OE !
;

: tog_GPIO ( GPIO -- )  
    1 swap lshift GPIO_OUT_XOR !
;

: high_GPIO ( GPIO -- ) 
    1 swap lshift GPIO_OUT_SET !
;

: low_GPIO ( GPIO -- )  
    1 swap lshift GPIO_OUT_CLR !
;

: blink_GPIO ( GPIO -- ) 
    dup GPIO_F5
    dup GPIO_OUT 
      begin
        dup tog_GPIO
        tenth_sec ( -- )
      key? until drop
; 

Forth can look easier than it is, if you do it wisely.

PinTest => test_GPIO

PinTest will be a bit different in Forth as well. First, I’ve learned my lesson and will work on the program using short definitions! Second, I did need a state machine. And third, as Forth doesn’t need to be an infinite loop like C++ and can be more interactive like MicroPython, I can add a command for exit. It might be helpful to review the purpose of this test here.

In setting up the words, I’ve realized the nomenclature to call this a pin test is incorrect, it is a GPIO test in that the references are by GPIO and not by pin. For example to test the bottom pin on the “long side”, the pin reference is D4, while the GPIO reference is GP06. It is not an issue and it does simplify the references to be all numeric and a lookup or cross-reference won’t be needed.

Tested the RP2040 board using test_gpio, all pins worked! (Once I realized that pins 5 and 6 on the board are NOT GPIO 5 and 6, respectively. The pins are GPIO 7 and 8, respectively.)

July 30, 2021 Cleaned up the code quite a bit by:

  • changing print_ to “.” per Forth conventions as in .err_GPIO
  • changed exit sometimes a Forth word to adieu (quit was also taken)
  • refactored code to be more simple and more similar such as ctoGPIO and ctoTest are now quite similar
  • created range word for returning a flag based on min and max
\ test_gpio - Interactive program to test all gpio pins on a board
forgetram

\ test_GPIO Messages
: .enter_GPIO grey ." Enter GPIO to test or a for adieu: (#2-29) " black ;
: .GPIO_UT grey ." GPIO under test is: " black ;
: .GPIO_range cr grey ." Interactive GPIO test, GPIO range is " minGPIO . ." - " maxGPIO . cr black ;
: .enter_Tests grey ." Enter Test to run: (0-3) " black ;
: .Test_range grey ." Tests: 0=> new GPIO 1=> High 2=> Low  3=> Blink once " black ;
: .adieu green ." Adieu " black ;
: .err_GPIO fuchsia ." Error: Check GPIO range: " black ;
: .err_Test fuchsia ." Error: Check Test range: " black ;
: .err_input fuchsia ." Error: Check input value: " black ;
: .err_val_low fuchsia ." Error: Value too low: " black ;
: .err_val_high fuchsia ." Error: Value too high: " black ;
: .err_not_number fuchsia ." Error: Not a number: " black ;
: .err_stack red ." Error: Depth of stack not 0 " black ;

\ state variables
false variable adieu_state \ the state in which to adieu the program
: clr_adieu false adieu_state ! ;       \ continue testing
: set_adieu true adieu_state ! ;        \ adieu test_GPIO
: adieu_state? adieu_state @ ;
: .adieu_st ." adieu_state is " adieu_state @ . ;

false variable test0_state \ the state in which to stop testing
: clr_test0 false test0_state ! ;   \ test not 0, continue testing
: set_test0 true test0_state ! ;    \ test is 0, adieu testing
: test0_state? test0_state @ ;
: .test0_st ." Test_state is " test0_state @ . ;

false variable GPIO_state \ the state in which to request a GPIO pin
: clr_gpio_st false GPIO_state ! ;  \ GPIO is known, request Test
: set_gpio_st true GPIO_state ! ;   \ GPIO is unknown, request GPIO
: GPIO_state? GPIO_state @ ;
: .GPIO_st ." GPIO_state is " GPIO_state @ . ;

13 variable gpio \ GPIO to be tested, initialized to on-board LED

: adieu? ( c -- c T | c F ) \ test if char is 'a' to adieu
    dup [char] a =
;

: check_stack
    depth 0<>
    if .err_stack red .s black
    then
;

: error_GPIO ( n -- ) \ print GPIO error, GPIO value, clear GPIO_state
    .err_GPIO . 
    clr_gpio_st
;

: error_input ( n -- ) \ print input error, input value, clear GPIO_state
    .err_input . 
    clr_gpio_st
;

: error_NaN ( n -- ) \ print error, value entered, set flag to false for bad value
    .err_not_number .
;

: error_Test ( n -- ) \ print error, value entered, set flag to false for bad value
    .err_Test .
;

: one_digit ( c -- n T | c F ) \ test if char is decimal , return decimal or error
   dup decimal digit
   if swap drop true
   else false
   then
;

: two_digits ( addr -- n T | c F ) \ test if two char are decimal, return decimal or error
   decimal c@ digit 
   if 10 * pad 1 + c@ digit 
        if + true 
        else drop pad 1 + c@ false 
        then
   else pad c@ false 
   then 
;

: get_1c ( -- n ) \ get only 1 char into buffer, pad
    pad 1 accept ;

: get_2c ( -- n ) \ get upto 2 char into buffer, pad
    pad 2 accept ; 

: ctoGPIO ( -- n T | c F)  \ get one or two char and make them decimals or error
    get_2c 1 - 0= if pad c@ one_digit else pad two_digits then
;

: ctoTest ( n -- n T| c F ) \ get one char and return decimal or error
    get_1c drop pad c@ one_digit
;

: range? ( min max v -- T | F ) \ returns flag as to value being in range, inclusive
    dup >r          ( min max v ) ( v )
    >= swap r>      ( f min v ) ( )
    <= and          ( f )
;

: adieu ( n -- ) \ drop GPIO value, set states to adieu and print adieu
    set_adieu set_gpio_st set_test0
    drop 
    cr .adieu 
;

\ Tests to run: 0=> new GPIO 1=> High 2=> Low  3=> Blink once
: test_0 ( -- ) \ Leave testing and request new GPIO
    cr set_test0 set_gpio_st clr_adieu
;
: test_1 ( -- ) \ Set GPIO High
    gpio @
    high_GPIO
    clr_test0
;
: test_2 ( -- ) \ Set GPIO Low
    gpio @ 
    low_GPIO
    clr_test0
;
: test_3 ( -- ) \ Blink GPIO once
    gpio @ 
    dup tog_GPIO
    tenth_sec
    tog_GPIO
    qtr_sec
    clr_test0
;
: tests ( n -- ) \ Run test based on value or print error
    case
        0 of test_0 endof
        1 of test_1 endof
        2 of test_2 endof
        3 of test_3 endof
        dup .err_Test . clr_test0
    endcase
;

: get_test ( -- ) \ testing loop, continue to loop until test0_state is true
    decimal
    begin 
        cr .enter_Tests 
        ctoTest
        if tests
        else error_NaN
        then
        test0_state?
    until
;

: init_test ( -- ) \ clear adieu and testing states, set GPIO state and request GPIO
    erase_pad set_gpio_st clr_adieu clr_test0 
    cr .enter_GPIO
;

: init_gpio ( n -- ) \ initialize GPIO to be F5 and an output, initial state not saved
    cr
    dup gpio ! 
    dup .GPIO_UT . 
    dup GPIO_F5 GPIO_OUT
    set_gpio_st
;

: test_gpio ( -- )
    .GPIO_range 
    begin
        .Test_range
        begin   
            init_test
            ctoGPIO
            if dup minGPIO maxGPIO rot range?
                if init_gpio
                else error_GPIO
                then
            else adieu?
                if adieu
                else error_input
                then
            then
            GPIO_state?
        until
            test0_state? not
            if get_test
            then
        adieu_state?
    until
    check_stack
;

Comments powered by Talkyard.