Mecrisp-Stellaris Forth: 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
- Raspberry Pi RP2040 Getting Started
- RP2040 Datasheet
- Arm: Raspberry Pi RP2040: Our Microcontroller for the Masses
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.
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.
blink_GPIO
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.