In the previous articles in this series, we started coding the Propeller 2 MCU and we illuminated an LED using SPIN2. Now I will provide you with a solution to make it blink, and then I will continue with the IO pins. Let’s get started.

A simple solution

You can follow a simple ”turning it off and on again”-scheme. To do so, you need to following ingredients:

  • pinwrite()
  • repeat()
  • waitms()

The necessary steps include:

  • Turn the LED on
  • Wait 500ms
  • Turn the LED off
  • Wait 500ms
  • Repeat

The code will look like what you see in Figure 1. You can download it from the article webpage [1].

Parallax Propeller 2 and blinking an LED
Figure 1: Code to blink one LED.

After we have used pins as output, the next step would be to show how to use them as inputs. We are not going to so at this point. We will come to this topic, inputs, later. Having a kind of serial data output to talk to a PC makes showing what status we have read from an I/O pin a lot nicer than just blinking an LED. Furthermore, we can use this output to send status data and do some debugging of the code. The next stop in this series will be the smart pins.

Smart pins

These days, marketing teams like to call products “smart” (e.g., “smart city,” “smart data,” and “smart contracts”). With the smart pins, it is a different story, as they can be far more than just ordinary general-purpose IO pins. On other MCUs, you will sometimes have the ability to select from multiple functions for one pin. Some MCUs have, like the ESP32, an IO matrix that can route any internal IO signal form peripherals to any pin. In these cases an IO pin will still be just an IO pin, the functions of — for example — UART, SPI or ADC, will reside inside a dedicated block of hardware that is just connected to the pin itself. With the Smart Pins on the Propeller 2, this changes as the dedicated peripherals are no longer dedicated function blocks inside the MCU that are routed through a IO matrix but integrated, at least partially, into every IO pin. Hence the term smart pin.

Thankfully the user 'rayman' on the Parallax Forum [2] did a great job by providing the community with an overview of the smart pin internals. You can find his post abou the Propeller 2 and the image at [3] or you can also have a look at Figure 2.

Smart pin overview.
Figure 2: Smart pin overview. (Source: Rayman/Parallax, https://bit.ly/3qPhqRk)

In the “Overview of Smart Pin Functions” section, I include an extract from the Propeller 2 datasheet. You can see that one pin can serve for many functions. Currently, we are only interested in 11110* = async serial transmit to send out data for later debugging purposes. The first step is to figure out how to set the appropriate mode for the pin and if it can be done with pure SPIN2 or if we need to mix in a bit of Assembly language.

UART Configuration

This is the point where you start scratching your head if you are working with SPIN2 and the Propeller 2 for the first time. The current datasheet is, more or less, complete. But reading and understanding everything properly can take longer than expected. Our goal is a simple function, a tx(), that will take a character to transmit to a pin working as a UART. We will work with 115200 baud, 8 data bits, no parity and only one stop bit. The first thing to do is set up the pin.

You must do the following:

  • Set up the pin in async serial transmit
  • Set the baud rate and data bits
  • Enable Smart Pin

These three simple steps will enable one pin to be a transmitting UART pin. As we will later modify the code and reuse it, we put it into a function. A function simply contains code or code fragments that are used often inside a program. This avoids a copy and paste through the code and also allows maintenance to be done at one place. We will use a function and call it serial_start. This one will take no arguments and simplify to the three steps given above. The pin we use is currently hardcoded to be pin 57 (one of the LED pins also accessible at one of the edge connectors as seen in Figure 3).

Pinheader location
Figure 3: Location of the pinheader to get access to the LED pins.

Here the function starts with a PUB followed by the name of it. At the end you have a empty braces, showing that we take no arguments.

PUB serial_start()

WRPIN( 57, %01_11110_0  )         'set async tx mode for txpin

WXPIN( 57, ((217<<16) + (8-1 )) ) 'set baud rate to sysclock/115200 and word size to 8 bits

org                               'start of assembly part

dirh #57

end ' end of assembly part

Starting with line 1, you have, as I◦mentioned above, the function — more precisely, the function head. The next line will set the pin 57 into async serial transmit, notable at the 11110. The first bit is always zero; the two most significant bits shown here as ‘01’ denote that the pin shall be driven by the GPIO or Smart Pin function. We use here the SPIN2 WRPIN function to achieve our step one. The next function WXPIN writes, for a smart pin in async serial mode, the desired clock divider and data bits to be used. For simplicity we skip the part with the fraction baud rate divider for now. The value for the baud rate can be calculated by systemclock/baudrate — here 25 MHz/115200 baud — resulting in about 217. This result needs to be shifted by 16 to the right. For the number of bits to transfer, we use the formula (desired bits-1) and this leaves us with (8-1) bits.

This is all the magic needed to set up the transmission speed and data bits. The next three lines look different than the previous code. As this is showing a small assembly section, it may require few words of additional explanation. With org you can start a section of assembly instructions, and they are currently needed here. The assembly command dirh will enable the smart pin functions, as we need to do for our step three. What’s different is now how you tell what pin number we are working on, as this needs to be started with a ‘#’.

The last line end will end the assembly section. It also at the same acts in this special case as the end of the function itself. It would been great to avoid assembly but currently there is no SPIN2 equivalent for the dirh assembly instruction. The formatted and highlighted code can be seen in Figure 4.

serial_start() code
Figure 4: Code of serial_start().

Now that the pin is in the correct mode, we can continue and set up a function, one that sends a character and waits until it’s done. The function can be mostly grabbed from the datasheet [4], page 91. We have seen how to build functions that have no arguments, meaning nothing passed on to them. To send a character, it would be beneficial if we can pass what we like to transmit to the function. As we currently try to avoid as much assembler as possible, we will, where possible, use the SPIN2 functions instead of inline assembly.

Transmission

For transmission, we create a tx function that looks almost identical to our serial_start() function, as you can see at Figure 5.

tx function
Figure 5: Code for tx function.

The visible difference, besides the name, is the argument ‘val’ inside the brackets. ‘val’ will carry the character to be printed. Inside the function, we will first write the value into the transmission register of pin 57 using the WYPIN command. The following section consists again of a few lines assembly code. We need to wait until the busy flag for the transmitter is no longer set and the transmission is done. According to the datasheet, we must first wait three CPU cycles to read the flag in a consistent manner. This is achieved by the waitx instruction with the #1 parameter, as its execution takes two cycles + the amount specified for the function (here one cycle). The next line is a label called wait, in assembly language, a place where we later can jump to. RDPIN in assembly, as written here, reads the pin status with carry. This is denoted by the WC at the end of the statement. The carry bit, here serving as busy flag, is important as it denotes if the transmission has been completed.

RDPIN val, #57 WC reads the status including the carry bit into our val value. As the content is currently transmitted, we can repurpose the memory of val to read the smart pin status to it. The last command IF_C◦JMP◦#wait is a conditional jump; in basic it would be the infamous GOTO equivalent combined with an IF statement. Shortly translated it means: Have you read something with the carry bit (here busy flag) set? Go back to the wait label and start from there again, or else move on. Our transmission is done if the carry bit is no longer set, and therefore the function will run to its end and return to where it was called.

Assemble all parts

We can now assemble the code and insert after every pinwrite() to transmit a “0” or “1” by using tx(“0”) or tx(“1”) in our code, as shown in Figure 6.

Complete code
Figure 6: Complete code assembled.

To grab the output attach a USB serial converter. For this purpose, we grabbed our trusty Logic 16 and recorded the output of the LED and the serial transmission and show the result in Figure 7 and Figure 8.

Logic analyzer trace
Figure 7: Logic analyzer trace showing every 500 ms a transmitted character.
 Zoomed trace
Figure 8: Zoomed trace showing the character transmitted.


But how about strings? Would it be nice to just do a print(“Hello World”) and have that transmitted over serial like we can do in the Arduino world? Yes, this can be done and we will. The next step in our series about the Propeller 2 will be an introduction on this.

Propeller 2: Overview of smart pin functions:

00000= smart pin off (default)

00001= long repository(P[12:10] != %101)

00010= long repository(P[12:10] != %101)

00011= long repository(P[12:10] != %101)

00001= DAC noise(P[12:10]= %101)

00010= DAC 16-bit dither, noise(P[12:10]= %101)

00011= DAC 16-bit dither, PWM(P[12:10]= %101)

00100*= pulse/cycle output

00101*= transition output

00110*= NCO frequency

00111*= NCO duty

01000*= PWM triangle

01001*= PWM sawtooth

01010*= PWM switch-mode power supply, V and I feedback

01011= periodic/continuous: A-B quadrature encoder

01100= periodic/continuous: inc on A-rise & B-high

01101= periodic/continuous: inc on A-rise & B-high / dec on A-rise & B-low

01110= periodic/continuous: inc on A-rise {/ dec on B-rise}

01111= periodic/continuous: inc on A-high {/ dec on B-high}

10000= time A-states

10001= time A-highs

10010= time X A-highs/rises/edges -or- timeout on X A-high/rise/edge

10011= for X periods, count time

10100= for X periods, count states

10101= for periods in X+ clocks, count time

10110= for periods in X+ clocks, count states

10111= for periods in X+ clocks, count periods

11000= ADC sample/filter/capture, internally clocked

11001= ADC sample/filter/capture, externally clocked

11010= ADC scope with trigger

11011*= USB host/device(even/odd pin pair = DM/DP)

11100*= sync serial transmit(A-data, B-clock)

11101= sync serial receive(A-data, B-clock)

11110*= async serial transmit(baudrate)

11111= async serial receive(baudrate)

* OUT signal overridden

 


More on Propeller 2 and Related Topics

Want to learn more about topics like Parallax's Propeller 2? Take out an Elektor membership today and never miss an article, project, or tutorial.