Where I work with the Adafruit Feather RP2040 (Feather), Mecrisp-Stellaris Forth (MSForth) and create Forth versions of ManPinTest and PinTest.
- Adafruit Feather RP2040
- Raspberry Pi RP2040 Getting Started
- RP2040 Datasheet
- Arm: Raspberry Pi RP2040: Our Microcontroller for the Masses
This entry will be very similar to the one using the RP2040 and MicroPython, the difference will is I will use MSForth instead.
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.
ManPinTest => blink_GPIO
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 ;