X2100: Difference between revisions

From Bare metal micro:bit
Jump to navigation Jump to search
No edit summary
No edit summary
 
(3 intermediate revisions by the same user not shown)
Line 3: Line 3:
{{FileTable|X2100|
{{FileTable|X2100|
{{FileRow|@primes.c@|Main program}}
{{FileRow|@primes.c@|Main program}}
{{FileLibrary}}
{{FileHardware}}
{{FileScripts|X2100}}}}
{{FileScripts|X2100}}}}


Line 66: Line 68:
[[Image:Serial-connection.png|800px]]
[[Image:Serial-connection.png|800px]]


{{Note|For this experiment, we don't need to send characters from the Raspberry Pi to the {{microbit}}, so the connection from @TXD@ on the Pi to @RX@ on the {{microbit}} is not really needed.  Also, since the grounds of the two boards are linked via USB, you can get away with omitting the ground connection too.  But including these connections does no harm, and gives a standard setup that will work for all ourt experiments.}}
{{Note|For this experiment, we don't need to send characters from the Raspberry Pi to the {{microbit}}, so the connection from @TXD@ on the Pi to @RX@ on the {{microbit}} is not really needed.  Also, since the grounds of the two boards are linked via USB, you can get away with omitting the ground connection too.  But including these connections does no harm, and gives a standard setup that will work for all our experiments.}}


Finally, invoke @minicom@ to show characters received over the {{RPi}}'s serial port, either by choosing the menu item {{Menu|Build>minicom (serial)}} in Geany, or by using the command
Finally, invoke @minicom@ to show characters received over the {{RPi}}'s serial port, either by choosing the menu item {{Menu|Build>minicom (serial)}} in Geany, or by using the command
Line 114: Line 116:


{{V2Box|Because the CPU of the the V2 is faster relative to the speed of UART transmission, fewer gaps appear in the output, and those gaps that do appear are smaller, so that the total time to find and print 500 primes is slightly shorter than ten seconds.  The big gaps in the output do not close up entirely, so that there is still a noticeable gap between @prime(368)@ and @prime(369)@.}}
{{V2Box|Because the CPU of the the V2 is faster relative to the speed of UART transmission, fewer gaps appear in the output, and those gaps that do appear are smaller, so that the total time to find and print 500 primes is slightly shorter than ten seconds.  The big gaps in the output do not close up entirely, so that there is still a noticeable gap between @prime(368)@ and @prime(369)@.}}
Once you have the logic analyser wired up, you may wish to keep your setup to use in the next experiment.


==Activity==
==Activity==
Line 198: Line 202:


In the place labelled Saleae Logic<ref>cheaply cloned logic analysers report a variety of device names; they really shouldn't claim to be 'Saleae Logic' because that's a brand name for a series of well made but more expensive devices, but nevertheless many of them do.</ref> in the screenshots shown above, you will probably see 'Demo device'.  Plug in your logic analyser, and follow the steps below.
In the place labelled Saleae Logic<ref>cheaply cloned logic analysers report a variety of device names; they really shouldn't claim to be 'Saleae Logic' because that's a brand name for a series of well made but more expensive devices, but nevertheless many of them do.</ref> in the screenshots shown above, you will probably see 'Demo device'.  Plug in your logic analyser, and follow the steps below.
# Click on the down-arrow and choose {{Menu|Connect to device}}.
* Click on the down-arrow and choose {{Menu|Connect to device}}.
# In Step 1, choose "fx2lafw" -- this identifies devices based on the Cypress FX2 chip running standard Logic Analyser Firmware.
* In Step 1, choose "fx2lafw" -- this identifies devices based on the Cypress FX2 chip running standard Logic Analyser Firmware.
# Leave Step 2 with the setting USB, and click on the {{Menu|Scan for devices}} button in Step 3.
* Leave Step 2 with the setting USB, and click on the {{Menu|Scan for devices}} button in Step 3.
# In Step 4, it's likely that exactly one device will appear; it may or may not be labelled 'Saleae Logic', but choose it anyway and click {{Menu|OK}}.
* In Step 4, it's likely that exactly one device will appear; it may or may not be labelled 'Saleae Logic', but choose it anyway and click {{Menu|OK}}.


{{Question|The PulseView program keeps freezing.  Is there any way to cure this?}}
{{Question|The PulseView program keeps freezing.  Is there any way to cure this?}}


Especially at high sample rates, communication between the {{RPi}} and the logic analyser needs to be fast, because the logic analyser has limited capacity to store data, and relies on the host computer to fetch it promptly.  I have found that the logic analyser is quite sensitive to the quality of the USB cable used to connect it to the Pi.  Try using a different cable and see if it helps.
Especially at high sample rates, communication between the {{RPi}} and the logic analyser needs to be fast, because the logic analyser has limited capacity to store data, and relies on the host computer to fetch it promptly.  I have found that the logic analyser is quite sensitive to the quality of the USB cable used to connect it to the Pi.  Try using a different cable and see if it helps.

Latest revision as of 14:36, 7 October 2024

Use a serial device to transmit characters.

Files

x10-serial:
primes.cMain program
lib.c, lib.hLibrary with implementation of printf
hardware.hHeader file with layout of I/O registers
startup.cStartup code
device.ldLinker script
MakefileBuild script
x10.geanyGeany project file

Demonstration

This program uses the serial port to output a sequence of prime numbers, demonstrating serial communication and the programming of the Universal Asynchronous Receiver/Transmitter (UART) that drives the serial port. The method used to find primes is (deliberately) ham-fisted, so that as the primes become larger, the delay between finding one prime and the next becomes significant. This causes gaps in the stream of characters output by this program, and paves the way for the next experiment, where the micro:bit's interrupt mechanism is used to allow the process of finding primes to overlap with the process of printing them. Like earlier programs, this one times itself using internal timer hardware, and also turns on an LED while it is working, so that the timing can be verified using a stopwatch or logic analyser.

This Demonstration section of the experiment is in three parts: first, we'll upload the program unchanged, and the program's output, a list of prime numbers, will appear in the minicom window in the usual way. At this stage, the UART of the Nordic chip is connected to the USB interface chip that is also part of the micro:bit board, and that chip sends the characters over the USB connection to the Raspberry Pi. In order to expose the signals produced by the UART, we will connect two pins of the micro:bit directly to the serial port of the Raspberry Pi, then modify the program so that the UART of the Nordic chip is connected to these pins instead of to the USB chip: this is part two of the demonstration. For part three, we will also connect the logic analyser to the same pins of the micro:bit, and we will then be able to capture and display the signals sent by the UART.

For parts two and three of this experiment to work, the Raspberry Pi's serial port must first be enabled; instructions for doing this appear in Appendix B.

Part one: the primes program

Begin by building and uploading the program primes.hex, then connect to the micro:bit with minicom in the usual way. On resetting the micro:bit, you should see it print a list of the first 500 prime numbers:

prime(1) = 2
prime(2) = 3
prime(3) = 5
prime(4) = 7
prime(5) = 11
...
prime(500) = 3571
11618 millisec

As you can see, the program uses a timer to measure the total time needed to find 500 primes and output them. Each character that appears in the output has been transmitted from the Nordic microcontroller to a second chip on the micro:bit board as a pattern of high and low voltages on a single wire, and from there to the Raspberry Pi over USB. In later parts of the experiment, will use the logic analyser to record the signals as they are transmitted by the Nordic chip and display them.

Part two: direct connection

The connection between the Nordic nRF51822 chip that runs our program and the USB interface chip on the micro:bit board is not conveniently accessible, so to find out what is going on at the electronic level, it's a good idea to divert the signals by another route. In this part of the experiment, we will modify the program to direct the UART of the Nordic chip to accessible pins of the micro:bit board, connect those pins to the serial port (also a UART) of the Raspberry Pi, and then invoke the minicom program on the Pi to show what is received on the serial port, not over USB.

To change the program, open it in Geany, and find the lines (near the top of primes.c) that read

#define TX USB_TX
#define RX USB_RX

Change these so that they read

#define TX PAD0
#define RX PAD1

Recompile and re-upload the program.

Now connect pads 0 and 1 and the ground pad of the micro:bit to pins of the Raspberry Pi according to the following table.

Raspberry Pi micro:bit Logic analyser
signal pin pin signal colour channel
GPIO 15 (RXD) 10 0 TX black 0
GPIO 14 (TXD) 8 1 RX brown 1
6 LED red 2
Ground 6 0V Gnd white Ground

Notice that the transmit line (TX) of one device connects to the receive line (RX) of the other, and vice versa. For convenience, I've also shown in the table the connections for the logic analyser that we shall use in Part three; you do not need to connect it now. There's no need to connect the 3.3V pad of the micro:bit to the Raspberry Pi, because the micro:bit will still receive its power over the USB connection.

Serial-connection.png

Note

For this experiment, we don't need to send characters from the Raspberry Pi to the micro:bit, so the connection from TXD on the Pi to RX on the micro:bit is not really needed. Also, since the grounds of the two boards are linked via USB, you can get away with omitting the ground connection too. But including these connections does no harm, and gives a standard setup that will work for all our experiments.

Finally, invoke minicom to show characters received over the Raspberry Pi's serial port, either by choosing the menu item Build>minicom (serial) in Geany, or by using the command

$ minicom -D /dev/serial0 -b 9600

The program should now work as before, displaying the first 500 primes, followed by its measurement of the time taken.

Part three

Part two of the experiment was really a preparation for this part, where we will also connect a logic analyser and see how characters are transmitted. The table given in Part two shows the connections that are needed: the white Ground connection of the logic analyser connects to the ground pin of the micro:bit, and channels 0 (black), 1 (brown) and 2 (red) connect to three of the signal pads. Two of the signal pads carry the transmit (TX) and receive (RX) signals of the UART, and the third shows when the LED is lit, so that we can make timing measurements. Plug both the micro:bit and the logic analyser in to USB ports on the Raspberry Pi.

micro:bit version 2

The pin assignments for LEDs on the V2 board are different, but fortuitously pin 6 works for our timing purposes on either board.

To capture data from the logic analyser, we will use the program PulseView, which you can start by choosing the Programming>PulseView from the main Raspberry Pi menu RPi-logo.png. If you start PulseView for the first time with the logic analyser plugged in, it may succeed in finding the device automatically; if not, see the Questions section.

When PulseView starts, you will very likely see that all 8 of the channels of the logic analyser are enabled, colour-coded like resistor values with black for D0 (digital channel 0), brown for D1, and so on. For now, only channels D0 (data transmitted by the micro:bit) and D2 (timing) are interesting, so simplify the display by choosing the Channels button PulseView-channels.svg and turning off all except D0 and D2.

Just to the left along the toolbar, click on the Configure button PulseView-configure.png and set the 'Pre-trigger capture ratio' to 1%; this will make the display show a bit of data before the trigger event that starts the capture, making it easier to see what is going on. Set up a capture of 2 M samples at 100 kHz: this will capture 20 seconds of data at a resolution that is plenty high enough to see all that happens on the serial line.

It's OK to click on the Run button in the top right and immediately click the Reset button of the micro:bit. You should see that there is a rapidly-changing signal on D0, and a long pulse on D2 that lasts until D0 has stopped wiggling. Using the scroll button on your mouse, you can zoom in on the signals to see the individual pulses on D0, some longer than others. If you zoom in far enough, you will start to see dots that denote the individual samples (at a rate of 100 000 per second, or one every 10 μs) taken by the logic analyser.

20 seconds of data

The next screenshot shows the result of zooming in on the very start of the activity. You can see that D2 goes high, and after a very short delay, changes on D0 start.

The first few milliseconds

For a more repeatable result, we can exploit that fact that line D2 goes high at the start of the program by setting a trigger. Click on the label D2 in red at the left of the traces, and choose the rising-edge icon PulseView-rising.svg. Now you can capture the beginning of the program neatly with the following actions.

  1. Press and hold the reset button on the micro:bit.
  2. Press and release the Run button in PulseView. You will see it turn red, rather than immediately green as before.
  3. Now release the reset button on the micro:bit. The logic analyser will detect the rising edge on D2 and begin capturing data.

The pulses on D0 represent the characters being output by the program using the UART on the micro:bit, captured by the UART in the Raspberry Pi, and displayed by minicom. You can use PulseView to decipher them in software by adding a decoder to the display. Click on the decoder button PulseView-decoder.svg and choose UART. Another trace will appear at the bottom of the PulseView window, and you can set it up by clicking on the UART label at the left:

  • Choose D0 as the RX line.
  • Set the baud rate to 9600 by clicking and typing.
  • Leave the number of data bits at 8 and parity at none.
  • Set the data format as ascii to see the characters as they will be printed.

If you zoom in a bit, you will start to see the individual characters deciphered; zooming in more, you will see labelled the individual bits the make up each character, and that every character begins with a 'start bit' and ends with a 'stop bit'. More details about this appear in the Background section below.

The first few characters decoded

You will see that each line of output ends with Carriage Return and Line Feed characters, shown in the PulseView display as [0D] and [0A], because the codes are 0x0d and 0x0a in hexadecimal. At first, each line of output is followed by the next line with no discernable gap, but later in the list of primes, gaps start to appear because of a delay in finding the next prime. For example, there is a big gap between the line of output that says

prime(368) = 2503

and the next one saying

prime(369) = 2521

because the micro:bit takes some time to establish that there are no primes between 2503 and 2521.

A gap in the output

micro:bit version 2

Because the CPU of the the V2 is faster relative to the speed of UART transmission, fewer gaps appear in the output, and those gaps that do appear are smaller, so that the total time to find and print 500 primes is slightly shorter than ten seconds. The big gaps in the output do not close up entirely, so that there is still a noticeable gap between prime(368) and prime(369).

Once you have the logic analyser wired up, you may wish to keep your setup to use in the next experiment.

Activity

  • What happens to the running time of the program if we modify it to print not the first 500 primes, but the first 500 primes that are more than 10 000 or more than 1 000 000?
  • Try increasing the baud rate for the serial line, and see what effect it has on the time taken by the program and appearance of gaps in the output. To change the baud rate, replace the constant UART_BAUD_9600 used in serial_init with another of the similar constants defined in hardware.h, such as UART_BAUD_115200. You will also need to change the connection speed used by minicom, and possibly increase the sample rate used by the logic analyser.

Background

Serial communication

GPIO pins let us communicate from a program to the outside world, but they are little more (as far as we've seen) than a direct connection between the bits of a register in the microcontroller and signal wires in the external circuit. A UART provides an example of a more elaborate I/O device, capable of actions independent of the CPU; the complexity this adds is necessary, because serial transmission must be done with timing that is quite precise. It's possible but tricky to produce serial output on a GPIO pin under direct CPU control by 'bit-banging', but more difficult to implement serial input well in that way.

The logic analyser traces shown above reveal that each character transmitted by the UART takes ten bit-periods, each 1/9600 s or 104 μs when the interface operates at 9600 baud. The character begins with a start bit during which the signal is 0V. There then follow eight bits of data, sent with the least significant bit first, with 0 encoded by 0V and 1 encoded by +3.3V. The character p has numeric code 0x74, or 01110000 in binary, so it is transmitted by setting the line low for 4 bit-periods, then high for 3, and low again for the last bit-period. The character finishes with a stop bit where the line is held high: this ensures that the start bit that begins the next character is clearly distinguished by a transition from high to low. This pattern, a start bit followed by 8 data bits, (no hardware parity bit), and a stop bit, is the most common variant of serial transmission, and is described by the abbreviation 8-N-1. There are ten bit-periods per character transmitted, so 9600 8N1 is capable of a peak transmission rate of 960 characters/sec. These conventions are standardised and often go by the term RS-232: but strictly speaking that standard requires voltage levels of ±12–15V suitable for use with old-fashioned teleprinters, and recent computers more commonly use voltages of 5V and 0V or (as here) 3.3V and 0v. Although we shall not use it, it's possible to add a parity bit to each character so that the receiving end can check whether a bit of the character has been corrupted in transmission.

Note that there is no clock signal shared between receiver and transmitter, so precise timing is essential. In order to achieve this, both transmission and reception are commonly implemented in hardware. The transmitter makes sure each bit-period lasts precisely 104μs, and the receiver, after seeing the falling edge of the start bit, will wait for 1.5 bit-periods in order to sample the first bit in the middle of its period; then it samples at 2.5, 3.5, ... bit-periods after the start so as to capture successive bits. (Interfaces commonly sample multiple times per bit-period and use majority voting to improve their immunity to noise.) The receiver can look for the stop bit to verify that the timing is reasonably accurate before announcing that it has received a character.

The serial interface is called a UART (for Universal Asynchronous Receiver/Transmitter) – Universal because it can deal with different speeds from 9600 baud and different encodings from 8N1; and Asynchronous because of the lack of a shared clock. The UART derives its timing by dividing the 16MHz system clock, and part of the configuration process is to set the division ratio to give an accurate bit-rate. The UART has two halves – a receiver and a transmitter – and a serial connection usually consists of two wires, on linking TX on one device to RX on the other, and the second linking RX to TX, for "full duplex" operation. It's possible to add two additional wires labelled RTS and CTS for 'hardware flow control', so that either end can signal to the other that it is not ready to receive any more characters and transmission should be delayed. As is common, we will not bother with these signals, assuming that both ends are able to consume characters at the highest rate they can be transmitted. At 9600 baud, this should not cause any problems.

A basic driver

The nRF51822 has a single UART that can be connected to the USB interface chip on the micro:bit board, and then appears as a USB serial interface on the host computer. We have already used this interface to experiment with assembly language programming. The UART is presented to a program running on the nRF51822 as a collection of device registers at fixed addresses, and predefined symbols like UART.TXD let us access these registers. Some of the registers are used for configuration, and we need not list them in detail, but two are used in the process of transmitting characters. By putting a character in the register UART.TXD, the program can start the UART transmitting the character over the serial line. To find out when the transmission is finished, the program can check another register, UART.TXDRDY, which is set to 1 when the UART is ready for another character. If another character is immediately put in UART.TXD, then it will follow the first character without a gap, as shown in the logic analysers traces shown earlier. If there is some delay before the program provides another character, then the TX line will just remain high, and the device on the other end of the wire will wait.

The main difficulty is that we mustn't ask the UART to transmit another character until it has finished with the previous one, or it will get confused, either cutting off the transmission of the first character in the middle, or maybe ignoring the second character. So before sending a character, we must ensure that UART.TXDRDY has been set to 1 by the device. The simplest way of doing this is to wait in a busy loop.

void serial_putc(char ch)
{
    while (! UART.TXDRDY) { /* do nothing */ }
    UART.TXDRDY = 0;
    UART.TXD = ch;
}

(This code omits a detail connected with transmitting the very first character.) This works, but it suffers from the problem that while the serial_putc function is waiting, no useful work is being done.

In the primes program, there is a loop that tests successive numbers to see if they are prime, and calls printf to print the ones that pass the test. This program is particularly stupid because it tests both even and odd numbers to see if they are prime, and we can be sure that after 2 has been printed, no more even primes will be found, but it serves our purpose in providing plenty of work for the processor to do.

void init(void)
{
     int n = 2, count = 0;

     serial_init();
     start_timer();

     while (count < 500) {
          if (prime(n)) {
               count++;
               printf("prime(%d) = %d\r\n", count, n);
          }
          n++;
     }

     stop_timer();
}

A simple implementation of printf is provided as part of the library lib.c that is incorporated in all our experiments; it assembles an array of characters that should be printed, then passes the array to a function putbuf that we must provide. Our implementation of putbuf passes each character in the array to the function serial_putc that was shown above, incidentally inserting a carriage return character in front of each newline character so as to follow the usual conventions for serial communication. This approach has serious implications for the performance of the program, because serial_putc contains a loop that waits for the previous character to be printed, and while it is waiting, the putbuf function and printf itself also wait, so that printf does not return to the main program until all the characters in a line of output have been passed to the UART. Until printf does return, the main program does not begin checking the next number to see if it is also prime, and that means that time is being wasted. A solution to this problem will appear in the next experiment, where we will use interrupt control to allow printing one line of output to overlap with preparation of subsequent lines, and that solution will later be refined (in Part 3 of the book) into an operating system that allows multiple tasks to proceed concurrently in a convenient way.

Hardware details

The UART driver in this program deals only with serial output, and not with input. A driver that handles input too can be found as part of the file fmain.c that was used for the experiments with assembly-language programming.

Initialising the UART is a bit involved, but the code can be copied from manufacturer's example programs, and amounts to making proper settings of several device registers. There is a device register that is set according to the baud rate, and another to set the format to 8-N-1. At the end come some assignments to clear the flag that indicates when transmission of the preceding character is finished, and to start the sending process.

The event flag UART.TXDRDY is set to 1 whenever a character has finished being transmitted, but there is no way to set it to 1 at the start of the program. (Assigning to UART.TXDRDY can clear the flag but cannot set it.) So there's an additional boolean variable txinit that's set to 1 at the very start of the program, and becomes 0 as soon as the first character is sent.

/* serial_init -- set up UART connection to host */
void serial_init(void)
{
    UART.ENABLE = UART_ENABLE_Disabled;

    UART.BAUDRATE = UART_BAUD_9600;     /* 9600 baud */
    UART.CONFIG = 0;                    /* format 8N1 */
    UART.PSELTXD = USB_TX;              /* choose pins */
    UART.PSELRXD = USB_RX;
    UART.ENABLE = UART_ENABLE_Enabled;
    UART.TXDRDY = 0;
    UART.STARTTX = 1;
    txinit = 1;
}

Actually, this program allows a small amount of overlap between printing one prime and looking for the next one, because serial_puts returns as soon as it has sent a character to the UART. One of the challenges below asks you to change this behaviour and find out whether it affects the running time of the program.

Challenges

  • Modify the program so that transmission of each character completes before serial_putc returns, rather than before transmitting the character on the next call. Does this have a measurable effect on the running time?
  • In place of primes, run the program echo from Experiment 1, which echoes characters types in the keyboard. Modify the program to use pads 0 and 1 for the UART to link directly to the Raspberry Pi so you can monitor the signals with the logic analyser. By triggering the logic analyser on the micro:bit's serial RX line, D1, you should be able to capture a character being sent to the micro:bit and it echoing the same character in response. What happens when a backspace is typed on the keyboard?

Questions

PulseView doesn't find my logic analyser. What can I do?

In the place labelled Saleae Logic[1] in the screenshots shown above, you will probably see 'Demo device'. Plug in your logic analyser, and follow the steps below.

  • Click on the down-arrow and choose Connect to device.
  • In Step 1, choose "fx2lafw" – this identifies devices based on the Cypress FX2 chip running standard Logic Analyser Firmware.
  • Leave Step 2 with the setting USB, and click on the Scan for devices button in Step 3.
  • In Step 4, it's likely that exactly one device will appear; it may or may not be labelled 'Saleae Logic', but choose it anyway and click OK.

The PulseView program keeps freezing. Is there any way to cure this?

Especially at high sample rates, communication between the Raspberry Pi and the logic analyser needs to be fast, because the logic analyser has limited capacity to store data, and relies on the host computer to fetch it promptly. I have found that the logic analyser is quite sensitive to the quality of the USB cable used to connect it to the Pi. Try using a different cable and see if it helps.

  1. cheaply cloned logic analysers report a variety of device names; they really shouldn't claim to be 'Saleae Logic' because that's a brand name for a series of well made but more expensive devices, but nevertheless many of them do.