Chapter 1: Introduction to Embedded Systems

Jonathan Valvano and Ramesh Yerraballi

 

This chapter covers the basic foundation concepts needed to build upon in this course. Specifically we will look at embedded systems, number representation, digital logic, embedded system components, and computer architecture: the Central Processing Unit (Arithmetic Logic Unit, Control Unit and Registers), the memory and the Instruction Set Architecture (ISA).

Table of Contents:

Return to book table of contents

 

Video 1.0. Introduction, Examples of Embedded Systems

 Reference material relative to this chapter:

1.1. Embedded Systems

To better understand the expression embedded microcomputer system, consider each word separately. In this context, the word “embedded” means hidden inside so one can’t see it. The term “micro” means small, and a “computer” contains a processor, memory, and a means to exchange data with the external world. The word “system” means multiple components interfaced together for a common purpose. Systems have structure, behavior, and interconnectivity operating in a framework bound by rules and regulations. Another name for embedded systems is Cyber-Physical Systems, introduced in 2006 by Helen Gill of the National Science Foundation, because these systems combine the intelligence of a computer with the physical objects of our world. As shown in Figure 1.1.1, the term embedded microcomputer system refers to a device that contains one or more microcomputers inside. Microcontrollers, which are complete computers incorporating the processor, RAM, ROM and I/O ports into a single package, are often employed in an embedded system because of their low cost, small size, and low power requirements. 

Figure 1.1.1. An embedded system includes a microcomputer interfaced to external physical devices.

An embedded system includes a microcomputer with mechanical, chemical, and electrical devices attached to it, programmed for a specific dedicated purpose, and packaged up as a complete system. Any electrical, mechanical, or chemical system that involves inputs, decisions, calculations, analyses, and outputs is a candidate for implementation as an embedded system. Electrical, mechanical, and chemical sensors collect information. Electronic interfaces convert the sensor signals into a form acceptable for the microcomputer. For example, a tachometer is a sensor that measures the revolutions per second of a rotating shaft. Microcomputer software performs the necessary decisions, calculations, and analyses. Additional interface electronics convert the microcomputer outputs into the necessary form. Actuators can be used to create mechanical or chemical outputs. For example, an electrical motor converts electrical power into mechanical power.

If the embedded system is connected to the internet, it is classified as an Internet of Things (IoT).

Video 1.1.1. Components of an embedded system

Figure 1.1.2. Embedded systems have computers hidden inside.


: What is an embedded system?

1.2. Binary Information Implemented with MOS transistors

Information is stored on the computer in binary form. A binary bit can exist in one of two possible states. In positive logic, the higher voltage is called the ‘1’, true, asserted, or high state. The lower voltage is called the ‘0’, false, not asserted, or low state. Figure 1.2.1 shows the output of a typical complementary metal oxide semiconductor (CMOS) circuit. The left side shows the condition with a true bit at the output, and the right side shows a false at the output. The output of each digital circuit consists of a p-type MOS transistor “on top of” an n-type MOS transistor. In digital circuits, each transistor is either on or off. If the transistor is on, it is equivalent to a short circuit between its two output pins. Conversely, if the transistor is off, it is equivalent to an open circuit between its outputs pins. A P-channel MOS transistor will be on (connecting two output pins) if its input is low. An N-channel MOS transistor will be on (connecting two output pins) if its input is high.

Figure 1.2.1. A binary bit at the output is true if a voltage is present and false if the voltage is 0.


: What would have if both transistors were on in Figure 1.2.1?

: What would have if both transistors were off in Figure 1.2.1?

Every family of digital logic is different, but on the MSPM0+ microcontrollers from TI powered with 3.3 V supply, an input voltage between 2.31 and 3.6 V is considered high, and an input voltage between 0 and 0.99 V is considered low. In general we define VIH as smallest input voltage considered high. A voltage between 0 and 0.99 V is considered low, as drawn in Figure 1.2.2. In general we define VIL as largest input voltage considered high. Separating the two regions by 1.32 V allows digital logic to operate reliably at very high speeds. The design of transistor-level digital circuits is beyond the scope of this book. However, it is important to know that digital data exist as binary bits and encoded as high and low voltages.

Figure 1.2.2. Mapping between the input voltage and the corresponding digital meaning on an MSPM0 input pin.


If the information we wish to store exists in more than two states, we use multiple bits. A collection of 2 bits has 4 possible states (00, 01, 10, and 11). A collection of 3 bits has 8 possible states (000, 001, 010, 011, 100, 101, 110, and 111). In general, a collection of n bits has 2n states. For example, a byte contains eight bits and is built by grouping eight binary bits into one object, as shown in Figure 1.2.3. Another name for a collection of eight bits is octet (octo is Latin and Greek meaning 8.) Information can take many forms, e.g., numbers, logical states, text, instructions, sounds, or images. What the bits mean depends on how the information is organized and more importantly how it is used. This figure shows one byte in the state representing the binary number 01100111, which could mean 103. As a character, this same collection of bits represents the letter ‘g’. Again, the output voltage 3.3V means true or 1, and the output voltage of 0V means false or 0.

Figure 1.2.3. A byte is comprised of 8 bits, in this case representing the binary number 01100111.


: Why is there a gap between VIL and VIH?

: Our microcontroller is powered at 3.3V. Can you think of a reason to reduce the supply voltage to 1.65V?

 

Quiz 1.2

 


1.3. Numbers

A great deal of confusion exists over the abbreviations we use for large numbers. In 1998 the International Electrotechnical Commission (IEC) defined a new set of abbreviations for the powers of 2, as shown in Table 1.3.1. These new terms are endorsed by the Institute of Electrical and Electronics Engineers (IEEE) and International Committee for Weights and Measures (CIPM) in situations where the use of a binary prefix is appropriate. The confusion arises over the fact that the mainstream computer industry, such as Microsoft, Apple, and Dell, continues to use the old terminology. According to the companies that market to consumers, a 1 GHz is 1,000,000,000 Hz but 1 Gbyte of memory is 1,073,741,824 bytes. The correct terminology is to use the SI-decimal abbreviations to represent powers of 10, and the IEC-binary abbreviations to represent powers of 2. The scientific meaning of 2 kilovolts is 2000 volts, but 2 kibibytes is the proper way to specify 2048 bytes. The term kibibyte is a contraction of kilo binary byte and is a unit of information or computer storage, abbreviated KiB.

1 KiB = 210 bytes = 1024 bytes

1 MiB = 220 bytes = 1,048,576 bytes

1 GiB = 230 bytes = 1,073,741,824 bytes

 

These abbreviations can also be used to specify the number of binary bits. The term kibibit is a contraction of kilo binary bit, and is a unit of information or computer storage, abbreviated Kibit.

A mebibyte (1 MiB is 1,048,576 bytes) is approximately equal to a megabyte (1 MB is 1,000,000 bytes), but mistaking the two has nonetheless led to confusion and even legal disputes. In the engineering community, it is appropriate to use terms that have a clear and unambiguous meaning.

Value

SI          Decimal

SI          Decimal

 

Value

IEC          Binary

IEC          Binary

10001

k

kilo-

 

10241

Ki

kibi-

10002

M

mega-

 

10242

Mi

mebi-

10003

G

giga-

 

10243

Gi

gibi-

10004

T

tera-

 

10244

Ti

tebi-

10005

P

peta-

 

10245

Pi

pebi-

10006

E

exa-

 

10246

Ei

exbi-

10007

Z

zetta-

 

10247

Zi

zebi-

10008

Y

yotta-

 

10248

Yi

yobi-

Table 1.3.1. Common abbreviations for large numbers.

 

To solve problems using a computer we need to understand numbers and what they mean. Each digit in a decimal number has a place and a value. The place is a power of 10 and the value is selected from the set {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}. A decimal number is simply a combination of its digits multiplied by powers of 10. For example

                                1984 = 1103 + 9102 + 8101 + 4100

 

Fractional values can be represented by using the negative powers of 10. For example,

                                273.15 = 2102 + 7101 + 3100 + 110-1 + 510-2

 

In a similar manner, each digit in a binary number has a place and a value. In binary numbers, the place is a power of 2, and the value is selected from the set {0, 1}. A binary number is simply a combination of its digits multiplied by powers of 2. To eliminate confusion between decimal numbers and binary numbers, we will put a subscript 2 after the number to mean binary. Because of the way the microcontroller operates, most of the binary numbers in this class will have 8, 16, or 32 bits. An 8-bit number is called a byte, and a 16-bit number is called a halfword. For example, the 8-bit binary number for 106 is

            011010102 = 027 + 126 + 125 + 024 + 123 + 022 + 121 + 020 = 64+32+8+2 = 106

 

: What is the numerical value of the 8-bit binary number 111111112?

Video 1.3.1. Binary representation

 

Interactive Tool 1.1

You have already learned how to convert from a binary number to its decimal representation. All you need to do is to calculate its value by multiplying each coefficient by its placeholder values and summing all of them together. If you want to practice, first think of an 8-digit binary number, and next type into the box from 1 to 8 binary digits. Try to calculate the decimal representation in your head. Then click "convert" to check your result.



Binary is the natural language of computers but a big nuisance for us humans. To simplify working with binary numbers, humans use a related number system called hexadecimal, which uses base 16. Just like decimal and binary, each hexadecimal digit has a place and a value. In this case, the place is a power of 16 and the value is selected from the set {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F}. As you can see, hexadecimal numbers have more possibilities for their digits than are available in the decimal format; so, we add the letters A through F, as shown in Table 1.3.2.  Hexadecimal representation is a convenient mechanism for us humans to define binary information, because it is extremely simple for humans to convert back and forth between binary and hexadecimal.

Video 1.3.2. Hexadecimal representation

Hex Digit

Decimal  Value

Binary Value

0

0

0000

1

1

0001

2

2

0010

3

3

0011

4

4

0100

5

5

0101

6

6

0110

7

7

0111

8

8

1000

9

9

1001

A or a

10

1010

B or b

11

1011

C or c

12

1100

D or d

13

1101

E or e

14

1110

F or f

15

1111

Table 1.3.2. Definition of hexadecimal representation.

 

For example, the hexadecimal number for the 16-bit binary 0001 0010 1010 1101 is

                                0x12AD = 1163 + 2162 + 10161 + 13160 = 4096+512+160+13 = 4781

 


Interactive Tool 1.2

You have already learned how to convert from a hexadecimal number to its decimal representation. All you need to do is to calculate its value by multiplying each coefficient by its placeholder values and summing all of them together. If you want to practice, Choose an 4-digit hexadecimal number number. Try to calculate the decimal representation. Then type the number in the following field and click "convert" to check your result.



: What is the numerical value of the 8-bit hexadecimal number 0xFF?

: Convert the binary number  010001012 to hexadecimal.

: Convert the binary number 1100101010112 to hexadecimal.

: Convert the hex number 0x40 to binary.

: Convert the hex number 0x63F to binary.

: How many binary bits does it take to represent 0x123456?

 

Precision is the number of distinct or different values. We express precision in alternatives, decimal digits, bytes, or binary bits. Alternatives are defined as the total number of possibilities. For example, an 8-bit number format can represent 256 different numbers. An 8-bit digital to analog converter (DAC) can generate 256 different analog outputs. An 8-bit analog to digital converter (ADC) can measure 256 different analog inputs. Table 1.3.3 illustrates the relationship between precision in binary bits and precision in alternatives. The operation [[x]] is defined as the greatest integer of x. E.g., [[2.1]]  [[2.9]]  and [[3.0]] are all equal to 3. The Bytes column in Table 2.1 specifies how many bytes of memory it would take to store a number with that precision assuming the data were not packed or compressed in any way.

Binary bits

Bytes

Alternatives

8

1

256

10

2

1024

12

2

4096

16

2

65536

20

3

1,048,576

24

3

16,777,216

30

4

1,073,741,824

32

4

4,294,967,296

n

[[n/8]]

2n

Table 1.3.3. Relationship between bits, bytes and alternatives as units of precision.

 

: How many bytes of memory would it take to store a 50-bit number?

A byte contains 8 bits as shown in Figure 1.3.1, where each bit b7,...,b0 is binary and has the value 1 or 0. We specify b7 as the most significant bit or MSB, and b0 as the least significant bit or LSB.

Figure 1.3.1. 8-bit binary format.

If a byte is used to represent an unsigned number, then the value of the number is

                                                N = 128b7 + 64b6 + 32b5 + 16b4 + 8b3 + 4b2 + 2b1 + b0

 

Notice that the significance of bit n is 2n. There are 256 different unsigned 8-bit numbers. The smallest unsigned 8-bit number is 0 and the largest is 255. For example, 000010102 is 8+2 or 10. The least significant bit can tell us if the number is even or odd.

 

: Convert the binary number 011010012  to unsigned decimal.

: Convert the hex number 0x54 to unsigned decimal.

The basis of a number system is a subset from which linear combinations of the basis elements can be used to construct the entire set. The basis represents the “places” in a “place-value” system. For positive integers, the basis is the infinite set {1, 10, 100, …}, and the “values” can range from 0 to 9. Each positive integer has a unique set of values such that the dot-product of the value vector times the basis vector yields that number. For example, 2345 is (…, 2,3,4,5)·(…, 1000,100,10,1), which is 2*1000+3*100+4*10+5. For the unsigned 8-bit number system, the basis elements are

                                                {1, 2, 4, 8, 16, 32, 64, 128}

 

The values of a binary number system can only be 0 or 1. Even so, each 8-bit unsigned integer has a unique set of values such that the dot-product of the values times the basis yields that number. For example, 69 is (0,1,0,0,0,1,0,1)·(128,64,32,16,8,4,2,1), which equals 0*128+1*64+0*32+0*16+0*8+1*4+0*2+1*1. Conveniently, there is no other set of 0’s and 1’s, such that set of values multiplied by the basis is 69. In other words, each 8-bit unsigned binary representation of the values 0 to 255 is unique.

One way for us to convert a decimal number into binary is to use the basis elements. The overall approach is to start with the largest basis element and work towards the smallest. More precisely, we start with the most significant bit and work towards the least significant bit. One by one, we ask ourselves whether or not we need that basis element to create our number. If we do, then we set the corresponding bit in our binary result and subtract the basis element from our number. If we do not need it, then we clear the corresponding bit in our binary result. We will work through the algorithm with the example of converting 100 to 8-bit binary, see Table 2.4. We start with the largest basis element (in this case 128) and ask whether or not we need to include it to make 100? Since our number is less than 128, we do not need it, so bit 7 is zero. We go the next largest basis element, 64 and ask, “do we need it?” We do need 64 to generate our 100, so bit 6 is one and we subtract 100 minus 64 to get 36. Next, we go the next basis element, 32 and ask, “do we need it?” Again, we do need 32 to generate our 36, so bit 5 is one and we subtract 36 minus 32 to get 4. Continuing along, we do not need basis elements 16 or 8, but we do need basis element 4. Once we subtract the 4, our working result is zero, so basis elements 2 and 1 are not needed. Putting it together, we get 011001002 (which means 64+32+4).

: In this conversion algorithm, how can we tell if a basis element is needed?

Observation: If the least significant binary bit is zero, then the number is even.

Observation: If the right-most n bits (least sign.) are zero, then the number is divisible by 2n.

Observation: Consider an 8-bit unsigned number system. If bit 7 is low, then the number is between 0 and 127, and if bit 7 is high then the number is between 128 and 255.

 

: Give the representations of the decimal 45 in 8-bit binary and hexadecimal.

: Give the representations of the decimal 200 in 8-bit binary and hexadecimal.

 

Interactive Tool 1.3

There are a few techniques for converting decimal numbers to binaries. One of them is consecutive divisions. We start by dividing the decimal number by 2. Then we iteratively divide the result (the quotient) by 2 until the answer is 0. The equivalent binary is formed by the remainders of the divisions. The last remainder found is the most significant digit. Enter a number between 0 and 255 in the following field and click convert to see an example. Try to convert a decimal number to binary.





 

One of the first schemes to represent signed numbers was called one’s complement. It was called one’s complement because to negate a number, we complement (logical not) each bit. For example, if 25 equals 000110012 in binary, then –25 is 111001102. An 8-bit one’s complement number can vary from ‑127 to +127. The most significant bit is a sign bit, which is 1 if and only if the number is negative. The difficulty with this format is that there are two zeros +0 is 000000002, and –0 is 111111112. Another problem is that one’s complement numbers do not have basis elements. These limitations led to the use of two’s complement.

The two’s complement number system is the most common approach used to define signed numbers. It is called two’s complement because to negate a number, we complement each bit (like one’s complement), then add 1. For example, if 25 equals 000110012 in binary, then –25 is 111001112. If a byte is used to represent a signed two’s complement number, then the value of the number is

                                                N = -128b7 + 64b6 + 32b5 + 16b4 + 8b3 + 4b2 + 2b1 + b0

 

Observation: One usually means two’s complement when one refers to signed integers.

There are 256 different signed 8-bit numbers. The smallest signed 8-bit number is -128 and the largest is 127. For example, 100000102 equals -128+2 or -126.

 

: Convert the signed binary number 110110102 to signed decimal.

: Are the signed and unsigned decimal representations of the 8-bit hex number 0x95 the same or different?

For the signed 8-bit number system the basis elements are

                                                {1, 2, 4, 8, 16, 32, 64, -128}

 

Observation: The most significant bit in a two’s complement signed number will specify the sign.

Notice that the same binary pattern of 111111112 could represent either 255 or –1. It is very important for the software developer to keep track of the number format. The computer cannot determine whether the 8‑bit number is signed or unsigned. You, as the programmer, will determine whether the number is signed or unsigned by the specific assembly instructions you select to operate on the number. Some operations like addition, subtraction, and shift left (multiply by 2) use the same hardware (instructions) for both unsigned and signed operations. On the other hand, divide, and shift right (divide by 2) require separate hardware (instruction) for unsigned and signed operations.

 

Observation: To take the negative of a two’s complement signed number we first complement (flip) all the bits, then add 1.

A second way to convert negative numbers into binary is to first convert them into unsigned binary, then do a two’s complement negate. For example, we earlier found that +100 is 011001002. The two’s complement negate is a two-step process. First we do a logic complement (flip all bits) to get 100110112. Then add one to the result to get 100111002.

: Give the representations of  -54 in 8-bit binary and hexadecimal.

: Why can’t you represent the number 150 using 8-bit signed binary?

 

When dealing with numbers on the computer, it will be convenient to memorize some Powers of 2 as shown in Table 1.3.4.

exponent

decimal

20

1

21

2

22

4

23

8

24

16

25

32

26

64

27

128

28

256

29

512

210

1024 about a thousand

211

2048

212

4096

213

8192

214

16384

215

32768

216

65536

220

about a million

230

about a billion

240

about a trillion

Table 1.3.4. Some powers of two that will be useful to memorize.

 

 

: Use Table 1.3.4 to determine the approximate value of 232?

 

 A halfword or double byte contains 16 bits, where each bit b15,...,b0 is binary and has the value 1 or 0, as shown in Figure 1.3.2.

 

Figure 1.3.2. 16-bit binary format.

 

If a halfword is used to represent an unsigned number, then the value of the number is

                                N = 32768b15 + 16384b14 + 8192b13 + 4096b12

                                                + 2048b11 + 1024b10 + 512b9 + 256b8

                                                + 128b7 + 64b6 + 32b5 + 16b4 + 8b3 + 4b2 + 2b1 + b0

 

There are 65536 different unsigned 16-bit numbers. The smallest unsigned 16-bit number is 0 and the largest is 65535. For example, 00100001100001002 or 0x2184 is 8192+256+128+4 or 8580.

 

: Convert the 16-bit binary number 00100000011010102 to unsigned decimal.

: Convert the 16-bit hex number 0x1234 to unsigned decimal.

 

For the unsigned 16-bit number system the basis elements are

                                                {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768}

 

: Convert the unsigned decimal number 1234 to 16-bit hexadecimal.

: Convert the unsigned decimal number 10000 to 16-bit binary.

 

There are also 65536 different signed 16-bit numbers. The smallest two’s complement signed 16‑bit number is –32768 and the largest is 32767. For example, 11010000000001002 or 0xD004 is –32768+16384+4096+4 or –12284.

 

If a halfword is used to represent a signed two’s complement number, then the value of the number is

                                N = -32768b15 + 16384b14 + 8192b13 + 4096b12

                                                + 2048b11 + 1024b10 + 512b9 + 256b8

                                                + 128b7 + 64b6 + 32b5 + 16b4 + 8b3 + 4b2 + 2b1 + b0

 

: Convert the 16-bit hex number 0x1234 to signed decimal.

: Convert the 16-bit hex number 0xABCD to signed decimal.

For the signed 16-bit number system the basis elements are

                              {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, -32768}

 

Common Error: An error will occur if you use 16-bit operations on 8-bit numbers, or use 8-bit operations on 16-bit numbers.

Maintenance Tip: To improve the clarity of your software, always specify the precision of your data when defining or accessing the data.

: Convert the signed decimal number 1234 to 16-bit hexadecimal.

: Convert the signed decimal number –10000 to 16-bit binary.

 

A word on the ARM Cortex M will have 32 bits. Consider an unsigned number with 32 bits, where each bit b31,...,b0 is binary and has the value 1 or 0. If a 32-bit number is used to represent an unsigned integer, then the value of the number is

                                N = 231 b31 + 230 b30 + ... + 2b1 + b0 = sum(2i bi) for i=0 to 31


There are 232 different unsigned 32-bit numbers. The smallest unsigned 32-bit number is 0 and the largest is 232-1 (4,294,967,295). This range is 0 to about 4 billion. For the unsigned 32-bit number system, the basis elements are

                                 {1, 2, 4, ... , 229, 230, 231}

 

If a 32-bit binary number is used to represent a signed two’s complement number, then the value of the number is

                                N = -231 b31 + 230 b30 + ... + 2b1 + b0 = -231 b31 + sum(2i bi) for i=0 to 30

There are also 232 different signed 32-bit numbers. The smallest signed 32-bit number is -231 (-2,147,483,648) and the largest is 231-1 (+2,147,483,647). This range is about -2 billion to about +2 billion. For the signed 32-bit number system, the basis elements are

                                {1, 2, 4, ... , 229, 230, -231}

 

 

Interactive Tool 1.4

The computer does not distinguish between signed and unsigned numbers in memory. The interpretation is yours to make. Enter an 8-bit binary number in the following field and press "show" to see its value if interpreted as signed or unsigned integer. For convenience, you can also enter hexadecimal input with '0x' prefix.





Video 1.3.3. Signed versus unsigned numbers

 

Quiz 1.3

 

1.4. Introduction to Computers

Video 1.4.1. Computer Organization

A computer combines a processor, random access memory (RAM), read only memory (ROM), and input/output (I/O) ports. Computers are not intelligent. Rather, you are the true genius. Computers are electronic idiots. They can store a lot of data, but they will only do exactly what we tell them to do. Fortunately, however, they can execute our programs quite quickly, and they don’t get bored doing the same tasks over and over again. Software is an ordered sequence of very specific instructions that are stored in memory, defining exactly what and when certain tasks are to be performed. It is a set of instructions, stored in memory, that are executed in a complicated but well-defined manner. The processor executes the software by retrieving and interpreting these instructions one at a time. A microprocessor is a small processor, where small refers to size (i.e., it fits in your hand) and not computational ability.

A microcomputer is a small computer, where again small refers to size (i.e., you can carry it) and not computational ability. For example, a desktop PC is a microcomputer. Small in this context describes its size not its computing power. Consequently, there can be great confusion over the term microcomputer, because it can refer to a very wide range of devices from a PIC12C508, which is an 8-pin chip with 512 words of ROM and 25 bytes RAM, to the most powerful I7-based personal computer.

A pin is a signal or wire on the microcomputer. Figure 1.4.1 shows MSPM0G3507 has 64 pins. A port is a collection of pins, providing a physical connection between the computer and its outside world. Ports allow information to enter and exit the system. Information enters via the input ports and exits via the output ports. Other names used to describe ports are I/O ports, I/O devices, interfaces, or sometimes just devices. A bus is a collection of wires used to pass information between modules.

A very small microcomputer, called a microcontroller, contains all the components of a computer (processor, memory, I/O) on a single chip. As shown in Figure 1.4.1, the Atmel ATtiny, the Texas Instruments MSP430, and the Texas Instruments MSPM0G3507 are examples of microcontrollers. Because a microcomputer is a small computer, this term can be confusing because it is used to describe a wide range of systems from a 6-pin ATtiny4 running at 1 MHz with 512 bytes of program memory to a personal computer with state-of-the-art 64-bit multi-core processor running at multi-GHz speeds having terabytes of storage.

The computer can store information in RAM by writing to it, or it can retrieve previously stored data by reading from it. RAMs are volatile; meaning if power is interrupted and restored the information in the RAM is lost.

 

Figure 1.4.1. A microcontroller is a complete computer on a single chip.

Information is programmed into ROM using techniques more complicated than writing to RAM. From a programming viewpoint, retrieving data from a ROM is identical to retrieving data from RAM. ROMs are nonvolatile; meaning if power is interrupted and restored the information in the ROM is retained.

Figure 1.4.2 shows a simplified block diagram of a microcontroller based on the ARM® Cortex-M4 processor. It is a Harvard architecture because it has separate data and instruction buses. Instructions are fetched from flash ROM using the ICode bus at the same time as Data are exchanged with memory and I/O via the System bus interface.

Figure 1.4.2. Harvard architecture of an ARM® Cortex-M4-based microcontroller. Notice multiple buses allow simultaneous data flow.

The common bus in Figure 1.4.3 defines the von Neumann architecture, because a single bus is used for both data and instructions.

Figure 1.4.3. von Neumann architecture of an ARM® Cortex-M0-based microcontroller. Notice the single bus requires data to flow one at a time.

: What are the differences between a microcomputer, a microprocessor, and a microcontroller? 

: What are three differences between RAM and ROM?

: What is the difference between Harvard and von Neumann architectures?

 

Quiz 1.4

 

1.5. Memory

Figure 1.5.1 shows the memory map of the MSPM0G3507. A 32-bit entry requires four sequential locations. The I/O ports exist as memory-mapped locations. I/O ports are not memory, but we will write to an I/O port address to output and read from an I/O port address to input.

 

Figure 1.5.1. Memory map. Each address contains 8 bits or 1 byte.

Video 1.5.1. Memory Map Layout***needs recording***

When we store 16-bit data into memory it requires two bytes. Since the memory systems on most computers are byte addressable (a unique address for each byte), there are two possible ways to store in memory the two bytes that constitute the 16-bit data. Many main-frame computers, like the z/Architecture, implement the big endian approach that stores the most significant byte at the lower address. Most smaller computers (desktops, laptops, phones and microcontrollers) implement the little endian approach that stores the least significant byte at the lower address. The Cortex-M microcontrollers use the little endian format. Many ARM processors are biendian, because they can be configured to efficiently handle both big and little endian data. Instruction fetches on the ARM are always little endian. Figure 1.5.2 shows two ways to store the 16-bit number 1000 (0x03E8) at locations 0x20200850 and 0x20200851. We also could use either the big or little endian approach when storing 32-bit numbers into memory that is byte (8-bit) addressable. Figure 1.5.3 shows the big and little endian formats that could be used to store the 32-bit number 0x12345678 at locations 0x20200850 through 0x20200853.

Figure 1.5.2. Example of big and little endian formats of a 16-bit number (the Cortex M used little endian).

 

Figure 1.5.3. Example of big and little endian formats of a 32-bit number (the Cortex M used little endian).

In the previous two examples, we normally would not pick out individual bytes (e.g., the 0x12), but rather capture the entire multiple byte data as one nondivisable piece of information. On the other hand, if each byte in a multiple byte data structure is individually addressable, then both the big and little endian schemes store the data in first to last sequence. For example, if we wish to store the four ASCII characters ‘LM3S’, which is 0x4C4D3353 at locations 0x20200850 through 0x20200853, then the ASCII ‘L’=0x4C comes first in both big and little endian schemes, as illustrated in Figure 1.5.4.

Figure 1.5.4. Character strings are stored in the same for both big and little endian formats.

The terms “big and little endian” come from Jonathan Swift’s satire Gulliver’s Travels. In Swift’s book, a Big Endian refers to a person who cracks their egg on the big end. The Lilliputians were Little Endians because they insisted that the only proper way is to break an egg on the little end. The Lilliputians considered the Big Endians as inferiors. The Big and Little Endians fought a long and senseless war over the best way to crack an egg.

Common Error: An error will occur when data is stored in Big Endian by one computer and read in Little Endian format on another.

: Consider memory shown in Figure 1.5.4. What would you get if you did an 8-bit read from location 0x2020.0850?

: Consider memory shown in Figure 1.5.4. What would you get if you did a 16-bit read from location 0x2020.0850?

: Consider memory shown in Figure 1.5.4. What would you get if you did a 32-bit read from location 0x2020.0850?

 

Quiz 1.5

 

1.6. Pseudo-ops

A pseudo-op is an assembler directive to affect the assembly process. We use the .data pseudo-op to specify what information goes in RAM and the .text pseudo-op to specify what information what goes in ROM. We will use a template similar to Program 1.6.1 for most assembly programs in this class. The .equ pseudo-ops create symbols containing the address of some the I/O port registers. The .data pseudo-op means the subsequent code will be placed in volatile RAM. The .align 2 pseudo-op skips 0-3 bytes so the address of next object is word aligned (bottom two bits of the address are 0). The .space pseudo-op allocates 4 bytes (in RAM) for the variable named Stuff. The .text pseudo-op means the subsequent code will be placed in nonvolatile ROM. The .thumb pseudo-op tells the assembler to create Thumb code. The .global pseudo-op allows the label main to be accessed from another file. In particular, main will be the place the software starts on power up or on reset. The B Loop instruction creates an infinite loop, typical of embedded systems, causing the code between Loop and B Loop to be executed over and over.


       .equ GPIOA_DOE31_0,  0x400A12C0
       .equ GPIOA_DOUT31_0, 0x400A1280
       .equ GPIOA_DIN31_0,  0x400A1380
       .data
       .align 2
// Global variables defined here, will go in RAM
Stuff: .space 4
       .text
       .align 2
       .thumb
       .global main
main:
//assembly code to be executed once and first goes here
Loop:
// assembly code to be executed over and over goes here
       B Loop    
       .end     // end of file

Program 1.6.1. Template for assembly language programs.

: What is the purpose of the 4 in the Stuff: .space 4 pseudo-op?

: What is the purpose of the .data pseudo-op?

: What is the purpose of the .text pseudo-op?

 

Quiz 1.6

 

1.7. Instruction Set Architecture

Video 1.7.1. Instruction set architecture.

This section is a brief introduction to the ARM® Cortex™-M0+ instruction set architecture. There are many ARM® processors, and this class focuses on Cortex-M microcontrollers, which executes Thumb® instructions extended with Thumb-2 technology. This class will not describe in detail all the Thumb instructions. Rather, we focus on only a subset of the Thumb® instructions. This subset will be functionally complete without regard to minimizing code size or optimizing for execution speed. Furthermore, we will show the simple forms of instructions, but in many cases there are specific restrictions on which registers can be used and the sizes of the constants.

1.7.1. Registers

Registers are high-speed storage inside the processor. The registers are depicted in Figure 1.6.1. R0 to R12 are general purpose registers and contain either data or addresses. Register R13 (also called the stack pointer, SP) points to the top element of the stack. Register R14 (also called the link register, LR) is used to store the return location for functions. The LR is also used in a special way during exceptions, such as interrupts. Interrupts are covered in Chapter 5. Register R15 (also called the program counter, PC) points to the next instruction to be fetched from memory. The processor fetches an instruction using the PC and then increments the PC.

Figure 1.6.1. Registers on the ARM® Cortex-M processor.

: Where in memory should you put the stack? I.e., does the SP point to RAM or ROM?

: Where in memory should you put the program? I.e., does the PC point to RAM or ROM?

The ARM Architecture Procedure Call Standard, AAPCS, part of the ARM Application Binary Interface (ABI), uses registers R0, R1, R2, and R3 to pass input parameters into a C function. Also according to AAPCS we place the return parameter in Register R0.

There are three status registers named Application Program Status Register (APSR), the Interrupt Program Status Register (IPSR), and the Execution Program Status Register (EPSR) as shown in Figure 1.7.2. These registers can be accessed individually or in combination as the Program Status Register (PSR). The N, Z, V, and C bits give information about the result of a previous ALU operation. In general, the N bit is set after an arithmetical or logical operation signifying whether or not the result is negative. Similarly, the Z bit is set if the result is zero. The C bit means carry and is set on an unsigned overflow, and the V bit signifies signed overflow. 

 

 

Figure 1.7.2. The program status register of the ARM® Cortex-M processor.

The T bit will always be 1, indicating the ARM® Cortex-M processor is executing Thumb® instructions. The ISR_NUMBER indicates which interrupt if any the processor is handling. Bit 0 of the special register PRIMASK is the interrupt mask bit. If this bit is 1, most interrupts and exceptions are not allowed. If the bit is 0, then interrupts are allowed. There are four levels of interrupt priority. The BASEPRI register defines the priority of the executing software. It prevents interrupts with lower or equal priority but allows higher priority interrupts. For example if BASEPRI equals 2, then requests with level 0 and 1 can interrupt, while requests at levels 2 or 3 will be postponed. A lower number means a higher priority interrupt. The details of interrupt processing will be presented in Chapter 5.

1.7.2. Syntax

Assembly language instructions have four fields separated by spaces or tabs. The label field is optional and starts in the first column and is used to identify the position in memory of the current instruction. You must choose a unique name for each label. The opcode field specifies the processor command to execute. The operand field specifies where to find the data to execute the instruction. Thumb instructions have 0, 1, 2, 3, or 4 operands, separated by commas. The comment field is also optional and is ignored by the assembler, but it allows you to describe the software making it easier to understand. You can add optional spaces between operands in the operand field. However, a // must separate the operand and comment fields. Good programmers add comments to explain the software.

Label      Opcode  Operands               Comment

Func: MOVS  R0, #100    // 100 means maximum

      BX    LR         

 

Observation: A good comment explains why an operation is being performed, how it is used, how it can be changed, or how it was debugged. A bad comment explains what the operation does. The comments in the above two assembly lines are examples of bad comments.

The assembly source code is a text file (with Windows file extension .s) containing a list of instructions. The assembler translates assembly source code into object code, which are the machine instructions executed by the processor. All object code is halfword-aligned. This means instructions can be 16 or 32 bits wide, and the program counter bit 0 will always be 0. The listing is a text file containing a mixture of the object code generated by the assembler together with our original source code.

Label   Opcode  Operand                 comment

Func: MOVS  R1,#0x64     // R1=100

      MULS  R0,R0,R1   // R0=100*input

      ADDS  R0,R0,#10  // R0=100*input+10

      BX    LR           // return 100*input+10

 

When we build a project all files are assembled or compiled then linked together. The linker decides exactly where in memory everything will be. After building the project, it can be downloaded, which programs the object code into flash ROM. You are allowed to load and execute software out of RAM. But for an embedded system, we typically place executable instructions into nonvolatile ROM. The map file shows where in memory your variables and instructions exist.

1.7.3. Reading from and writing to memory

The first action we present is bringing a constant value into a register. With immediate addressing mode, the data itself is contained in the instruction. Once the instruction is fetched no additional memory access cycles are required to get the data. Notice the number 100 (0x64) is embedded in the machine code of the MOVS instruction shown in Figure 1.6.3. 

      MOVS  R0,#100      // R0=100, immediate addressing


Figure 1.7.3. An example of immediate addressing mode, data is in the instruction.

The constant value allowed by MOVS is limited to 8-bit values. On the other hand, the LDR instruction can be used to bring any 32-bit value into a register. Notice the number 0x12345678 is not embedded in the machine code of the instruction, but rather stored in ROM a short distance away, as shown in Figure 1.7.4. The assembler automatically places the constant in ROM and calculates the appropriate PC-relative offset. At the time of execution, the PC is pointing to the next instruction.

      LDR R0,=0x12345678      // R0=0x12345678, PC-relative addressing


 

Figure 1.7.4. An example of using the LDR instruction to load any constant into a register.

Video 1.7.2. Assembly language access to RAM and ROM variables. ***needs recording***

A fundamental issue in program development is the differentiation between data and address. When we put the number 100 into Register R0, whether this is data or address depends on how the 100 is used. As presented in Program 1.6.1, we created a 32-bit global variable called Stuff. To write an initial value of 100 into this variable will take three instructions. In this example, R0 has data and R1 had an address. First, the MOVS instruction brings the desired value into R0. Second, the LDR instruction sets R1 to point to the global variable Stuff. Lastly, the STR instruction writes the value into the variable.

      MOVS R0,#100    
      LDR  R1,=Stuff    // R1 points to the variable Stuff
      STR  R0,[R1]      // Stuff=100


Figure 1.7.5. It takes three instructions to write to a global variable.

: How would you modify the above assembly code to write 0x0E to Port A output?

 

To read the value of a variable into a register will take two instructions. After executing these two instructions, R3 will have a copy of the value from the global Stuff. The first LDR instruction sets R2 to point to the global variable Stuff. The second LDR instruction will read the value from the variable. 

     LDR  R2,=Stuff    // R2 points to the variable Stuff
     LDR  R3,[R2]      // R3=Stuff

Figure 1.7.6. It takes two instructions to read from a global variable.


: How would you modify the above assembly code to read from Port A input?

An aligned access is an operation where a word-aligned address is used for a word, dual word, or multiple word access, or where a halfword-aligned address is used for a halfword access. Byte accesses are always aligned. The address of an aligned word access will have its bottom two bits equal to zero. An unaligned word access means we are accessing a 32-bit object (4 bytes) but the address is not evenly divisible by 4.  The following instructions support 32-bit memory access:

   LDR                        Load 32-bit word

   STR                        Store 32-bit word


Observation: 32-bit accesses using LDR and STR must be to word-aligned addresses.

  

The address of an aligned halfword access will have its bottom bit equal to zero. An unaligned halfword access means we are accessing a 16-bit object (2 bytes) but the address is not evenly divisible by 2.  The following instructions support 16-bit memory access:

   LDRH                     Load 16-bit unsigned halfword

   LDRSH                   Load 16-bit signed halfword (sign extend bit 15 to bits 31-16)

   STRH                     Store 16-bit halfword


Observation: 16-bit accesses using LDRH LSRSH and STRH must be to halfword-aligned addresses.

 

Transfers of one byte are allowed for the following instructions:

   LDRB                     Load 8-bit unsigned byte

   LDRSB                   Load 8-bit signed byte (sign extend bit 7 to bits 31-8)

   STRB                     Store 8-bit byte

 

When loading a 32-bit register with an 8- or 16-bit value, it is important to use the proper load, depending on whether the number being loaded is signed or unsigned. This determines what is loaded into the most significant bits of the register to ensure that the number keeps the same value when it is promoted to 32 bits. When loading an 8-bit unsigned number, the top 24 bits of the register will become zero. When loading an 8-bit signed number, the top 24 bits of the register will match bit 7 of the memory data (signed extend). Note that there is no such thing as a signed or unsigned store. For example, there is no STRSH; there is only STRH. This is because 8, 16, or all 32 bits of the register are stored to an 8-, 16-, or 32-bit location, respectively. No promotion occurs. This means that the value stored to memory can be different from the value located in the register if there is overflow. When using STRB to store an 8-bit number, be sure that the number in the register is 8 bits or less.


 

Figure 1.7.6. Assume these memory contents for Checkpoints 1.7.5 through 1.7.9.

: Assume R0 equals 0x20200850 at the time LDR R1,[R0] is executed. To what value will R1 become?

: Assume R0 equals 0x20200850 at the time LDRH R2,[R0] is executed. To what value will R2 become?

: Assume R2 equals 0 and R0 equals 0x20200850 at the time LDRSH R3,[R0,R2] is executed. To what value will R3 become?

: Assume R0 equal 0x20200850 at the time LDRB R4,[R0] is executed. To what value will R4 become?

: Assume R2 equals 0 and R0 equals 0x20200850 at the time LDRSB R5,[R0,R2] is executed. To what value will R5 become?

1.7.4. Logical Operations

Boolean Logic has two states: true (1) and false (0). A binary operation produces a single result given two inputs. The logical and (&) operation yields a true result if both input parameters are true. The logical or (|) operation yields a true result if either input parameter is true. The exclusive or (^) operation yields a true result if exactly one input parameter is true. The logical operators are summarized in the table below. The logical instructions on the ARM Cortex-M processor take two inputs, one from a register and the other either from a register or from a constant. These operations are performed in a bit-wise fashion on two 32-bit input parameters yielding one 32-bit output result. The result is stored into the destination register. For example, the calculation r=m&n means each bit is calculated separately, r31=m31&n31, r30=m30&n30,..., r0=m0&n0.

In C, when we write logical operations as r=m&n; r=m|n; r=m^n; the logical operation occurs in a bit-wise fashion also described by the table below. However, in C, we define the Boolean functions as r=m&&n; r=m||n; For Booleans, the operation occurs in a word-wise fashion. For example, r=m&&n; means r will become zero if either m is zero or n is zero. Conversely, r will become 1 (any nonzero) if both m is nonzero and n is nonzero.
 

  A      B   A&B   A|B  A^B   A&(~B) 
 Rn     Op2   AND  ORR  EOR    BIC
  0    0   0   0   0   0
  0    1   0   1   1   0
  1    0   0   1   1   1
  1    1   1   1   0   0

Table 1.7.1. Logical operations performed by the Cortex-M processor.

All instructions place the result into the destination register Rd. If Rd is omitted, the result is placed into Rn, which is the register holding the first operand. The N and Z condition code bits are updated on the result of the operation. The second operand is a register. We use the ANDS instruction to mask or select bits. We use the ORRS instruction to set bits. We use the EORSinstruction to toggle bits (change from 0 to 1 or 1 to 0) bits. We use the BICS instruction to clear bits.

   ANDS Rd, Rn, Rm // Rd=Rn&Rm
   ORRS Rd, Rn, Rm // Rd=Rn|Rm
   EORS Rd, Rn, Rm // Rd=Rn^Rm
   BICS Rd, Rn, Rm // Rd=Rn&(~Rm)

Assume Port B bit 1 (PB1) is an output. Writing to the GPIOB_DOUT31_0 register performs output to general purpose input/output pins on Port B. Reading from GPIOB_DOUT31_0 returns the previous value written to this register. The following example sets the PB1 pin high. Notice that this code will leave the other pins unchanged.

// assembly version
    LDR  R0,=GPIOB_DOUT31_0 // pointer to output register
    LDR  R1,[R0]            // previous values
    MOVS R2,#0x02           // mask for bit 1    
    ORRS R1,R1,R2           // set bit 1    
    STR  R0,[R1]            // change actual output
// C version
   
GPIOB->DOUT31_0 = GPIOB->DOUT31_0 | 0x02;

 

Again, assume Port B bit 1 (PB1) is an output. The following example clears the PB1 pin low, without changing any other pins.

// assembly version
    LDR  R0,=GPIOB_DOUT31_0 // pointer to output register
    LDR R1,[R0]             // previous values
    MOVS R2,#0x02           // mask for bit 1    
    BICS R1,R1,R2           // clear bit 1    
    STR  R0,[R1]            // change actual output
// C version
   GPIOB->DOUT31_0 = GPIOB->DOUT31_0 & ~0x02;


: How would you change the above program to set the PB3 pin?

: How would you change the above program to toggle the PB2 pin?

1.7.5. Shift Operations

Like programming in C, the assembly shift instructions take two input parameters and yield one output result. In C, the left shift operator is << and the right shift operator is >>. E.g., to left shift the value in m by n bits and store the result in r we execute: r = m<<n. Similarly, to right shift the value in m by n bits and store result r, we execute: r=m>>n.

 

Figure 1.7.7. Shift operations move bits to the right or left.

The logical shift right (LSRS) is similar to an unsigned divide by 2n, where n is the number of bits shifted. A zero is shifted into the most significant position, and the carry flag will hold the last bit shifted out. The right shift operations do not round. For example, a right shift by 3 bits is similar to divide by 8. However, 15 right-shifted three times (15>>3) is 1, while 15/8 is much closer to 2. In general, the LSR discards bits shifted out.

The arithmetic shift right (ASRS) is similar to a signed divide by 2n. Notice that the sign bit is preserved, and the carry flag will hold the last bit shifted out. This right shift operation also does not round. Again, a right shift by 3 bits is similar to divide by 8. However, -9 right-shifted three times (-9>>3) is -2, while implementing -9 divided by 8 using the SDIV instruction yields -1. In general, the ASR discards bits shifted out, and the SDIV truncates towards 0.

The logical shift left (LSLS) operation works for both unsigned and signed multiply by 2n. A zero is shifted into the least significant position, and the carry bit will contain the last bit that was shifted out.

All shift instructions place the result into the destination register Rd. Rm is the register holding the value to be shifted. The number of bits to shift is either in register Rs, or specified as a constant n. The N and Z condition code bits are updated on the result of the operation. The C bit is the carry out after the shift. These shift instructions will leave the V bit unchanged.

Observation: Use logic shift for unsigned numbers and arithmetic shifts for signed numbers.


   LSRS Rd, Rm, Rs // logical shift right Rd=Rm>>Rs (unsigned)
   LSRS Rd, Rm, #n // logical shift right Rd=Rm>>n (unsigned)
   ASRS Rd, Rm, Rs // arithmetic shift right Rd=Rm>>Rs (signed)
   ASRS Rd, Rm, #n // arithmetic shift right Rd=Rm>>n (signed)
   LSLS Rd, Rm, Rs // shift left Rd=Rm<<Rs (signed, unsigned)
   LSLS Rd, Rm, #n // shift left Rd=Rm<<n (signed, unsigned)

: If R0=0xC0123456, what will be the value in R0 after LSRS R0,R0,#4 is executed?

: If R0=0xC0123456, what will be the value in R0 after ASRS R0,R0,#4 is executed?

: If R0=0xC0123456, what will be the value in R0 after LSLS R0,R0,#4 is executed?

1.7.6. Arithmetic Operations

When software executes arithmetic instructions, the operations are performed by digital hardware inside the processor. Even though the design of such logic is complex, we will present a brief introduction, in order to provide a little insight as to how the computer performs arithmetic. It is important to remember that arithmetic operations (addition, subtraction, multiplication, and division) have constraints when performed with finite precision on a processor. An overflow error occurs when the result of an arithmetic operation cannot fit into the finite precision of the register into which the result is to be stored. The N C V and Z condition code bits are updated on the result of the operation.

The immediate value #n can be any 8-bit unsigned constant. When Rd is absent, the result is placed back in Rn.

   ADDS Rd, Rn, Rm // Rd = Rn + Rm
   ADDS Rd, Rn, #n // Rd = Rn + n
   SUBS Rd, Rn, Rm // Rd = Rn - Rm
   SUBS Rd, Rn, #n // Rd = Rn - n
   CMP  Rn, Rm     // Rn - Rm
   CMP  Rn, #n     // Rn - n

  

The compare instruction CMP does not save the result of the subtraction but always sets the condition code. The compare instructions are used to create conditional execution, such as if-then, for loops, and while loops.

The addition and subtraction instructions set the condition code bits as shown in the following table. The addition and subtraction instructions work for both signed and unsigned values. As designers, we must know in advance whether we have signed or unsigned numbers. The computer cannot tell from the binary which type it is, so it sets both C and V. Our job as programmers is to look at the C bit if the values are unsigned and look at the V bit if the values are signed.

 Bit  Name Meaning after addition or subtraction
 N Negative Result is negative
 Z Zero Result is zero
 V Overflow Signed overflow
 C Carry Unsigned overflow

Table 1.7.2. Condition code bits contain the status of the previous arithmetic operation.

Observation: The carry bit, C, is set after an unsigned addition when the result is incorrect. The carry bit, C, is cleared after an unsigned subtraction when the result is incorrect.

Observation: The overflow bit, V, is set after a signed addition or subtraction when the result is incorrect.

1.7.7. Multiply and Divide

To understand the multiplication algorithm, consider these two assembly functions that multiply two 8-bit unsigned integers, A and B, yielding a 16-bit result, R. In general, an n-bit number multiplied by an m-bit number yields an (n+m)-bit product. The slow algorithm, initializes R=0, and then adds R=R+A, B times. The slow algorithm is simple to understand, but very slow. In particular, if B is 255, then this algorithm requires 4*255+3=1023 instructions to complete. The fast algorithm is designed with the basis elements of a binary number in mind. If bit n of A is set, then R=R+2n*B. We initialize a loop counter at 8 because the multiplier is 8 bits, the product is initially 0. The LSRS instruction shifts the least significant bit into the carry, deciding whether or not the multiplier needs to be added. Each time through the loop the multiplier is shifted right and multiplicand is shifted left. This fast algorithm takes to 44 to 52 instructions to complete.

// Inputs: A=R0 is 8-bit unsigned
//         B=R1 is 8-bit unsigned    
// Output: R=A*B is 16-bit product
slowmul:
      MOVS R2,#0 // R2 is result R
loop: CMP  R1,#0 // quit when B=0
      BEQ  done
      ADDS R2,R2,R0 // R=R+A
      B    loop
done: MOVS R0,R2    // result in R0
      BX   LR
// Inputs: A=R0 is 8-bit unsigned
//         B=R1 is 8-bit unsigned    
// Output: R=A*B is 16-bit product
fastmul:
      MOVS R2,#8 // loop counter
      MOVS R3,#0 // product
loop: LSRS R0,R0,#1 // check next bit in multiplier
      BCC  next // skip if bit is 0
      ADDS R3,R1 // add multiplicand
next: LSLS R1,R1,#1 // next bit is twice the value
      SUBS R2,R2,#1
      BNE  loop
      MOVS R0,R3 // product
      BX   LR

Program 1.7.1. Two multiply algorithms.

Next, let's convert Program 1.7.1 to multiply two 16-bit unsigned integers. The same code on the left operates for any size inputs up to 32 bits (neither algorithm checks for overflow). To change the code on the right to 16 by 16, we simply change the loop counter to 16. If B is 65535, then the left algorithm requires 4*65535+3=262140 instructions to complete (256 times slower). However, for the fast algorithm, because the loop counter is doubled, it only runs twice as slow.

Multiply (MULS) uses 32-bit operands and produces a 32-bit result. This multiply instruction only saves the bottom 32 bits of the result. They can be used for either signed or unsigned numbers, but no overflow flags are generated. If the Rd register is omitted, the Rn register is the destination. The MULS does set the Z and N bits are set according to the result.

   MULS  Rd, Rd, Rm // Rd = Rd * Rm

: If one adds an n-bit number to another n-bit number, how total bits are in the sum?

: If one multiplies an n-bit number to an m-bit number, how total bits are in the product?

The Cortex M0+ does not have a division instruction. So, to divide we will need to implement the operation as a function. Consider these two assembly functions that divide a 32-bit unsigned dividend, A by a 16-bit unsigned divisor, B, yielding a 16-bit quotient, Q and a 16-bit remainder R. Notice that both these algorithms return two values (R0, R1), and therefore are not AAPCS compliant. The definition of unsigned divide is

A = Q*B+R assuming R<B

The slow algorithm counts how many times we can subtract the divisor from the dividend. It is easy to understand, but very slow. If the dividend is 65535 and the divisor is 1, the slow algorithm requires 65535*5+4=327679 instructions to execute. The fast algorithm is designed with the basis elements of a binary number in mind, modeled after we perform long division. We left shift the divisor under the dividend as many times as we can while the shifted divisor is less than the dividend. When then set the bit in the quotient and subtract the shifted divisor from the dividend. The fast algorithm takes to from 105 to 127 instructions to complete.

// Inputs:  A=R0 is 32-bit unsigned dividend
//          B=R1 is 16-bit unsigned divisor   
// Outputs: Q=A/B is 16-bit quotient
//          B=R1 is 16-bit remainder   
slowdiv: // R0 becomes the remainder, R
      MOVS R2,#0 // R2 is quotient Q
loop: CMP  R0,R1 // quit when R<B
      BLO  done
      ADDS R2,R2,#1 // Q=Q+1
      SUBS R0,R0,R1 // A=A-B
      B    loop
done: MOVS R1,R0    // remainder in R1
      MOVS R0,R2    // quotient in R0
      BX   LR
// Inputs:  A=R0 is 32-bit unsigned dividend
//          B=R1 is 16-bit unsigned divisor   
// Outputs: Q=A/B is 16-bit quotient
//          B=R1 is 16-bit remainder   
fastdiv:
      PUSH {R4,LR}
      LDR  R4,=0x00010000 // bit mask
      MOVS R3,#0  // quotient
      MOVS R2,#16 // loop counter
      LSLS R1,#15 // move divisor under dividend
loop: LSRS R4,R4,#1 // bit mask 15 to 0
      CMP  R0,R1 // need to subtract?
      BLO  next
      SUBS R0,R0,R1 // subtract divisor
      ORRS R3,R3,R4 // set bit
next: LSRS R1,R1,#1
      SUBS R2,R2,#1
      BNE  loop
      MOVS R1,R0 // remainder
      MOVS R0,R3 // quotient
      POP  {R4,PC}

Program 1.7.2. Two divide algorithms.

Observation: You will use the fast divide algorithm in Lab 6 to convert an integer into a sequence of ASCII characters by dividing the integer by 10, and using both the quotient and remainder.

 

Quiz 1.7

 

1.8. Flowcharts, Structured Programming and Conditional Statements

In this course we will construct software using only three basic building blocks: sequence, conditional, and loop, see Figure 1.8.1. We will use the conditional branch instructions to create this software block.


Figure 1.8.1. Structured programming uses these three basic structures.

Normally the computer executes one instruction after another in a linear fashion, which constitutes the sequence. In particular, the next instruction to execute is found immediately following the current instruction. We use branch instructions to deviate from this straight line path, creating the conditional and the loop. The following lists a few of the many branch instructions. The conditional branching must be preceded by an instruction that sets the condition code bits.

   B   label // unconditional branch to label
   BEQ label // branch to label if Z=1
   BNE label // branch to label if Z=0
   BMI label // branch to label if N=1
   BPL label // branch to label if N=0
   BCS label // branch to label if C=1
   BCC label // branch to label if C=0
   BVS label // branch to label if V=1
   BVC label // branch to label if V=0
  

One of the ways we affect execution flow is the conditional. To compare two values, they must be the same type.

First, we present an approach to conditionally test for individual bits. Let mask specify which bit to test. For example, to test bit 3, we set the mask to 0x08. In general the mask for bit n is 1<<n. The approach is
     Bring the entire value into R0
     Bring the 1<<n mask value into R1
     Check for bit n using ANDS R0,R0,R1
     Execute one of the following conditionals

      BEQ target // Branch if R0 bit n is 0
      BNE target // Branch if R0 bit n is 1


To illustrate the conditional, let's write software that sets bit 1 of variable V1 to 1 if bit 8 of variable V0 is set, see Figure 1.8.2 and Program 1.8.1. Notice, we skip over the body if bit 8 is clear.


Figure 1.8.2. Flowchart of a conditional statement.

For the assembly implementation, we assume variable V0 is in register R0 and V1 is in register R1. The mask for bit 8 is 0x0100. The assembly version destroys R2 R3.

     LDR  R2,=0x0100

     ANDS R2,R2,R0

     BEQ  next

     MOVS R2,#0x02 // mask for set bit 1

     ORRS R1,R1,R2 // set bit 1

next:

 

if((V0&0x0100) == 0x0100{

  V1 |= 0x02;

}

Program 1.8.1. Conditional to test individual bits.

To illustrate the loop, we will implement a simple unsigned multiply, see Figure 1.8.3 and Program 1.8.2. We assume R0(n0) and R1(n1) are the inputs and function will perform R0*R1, placing the product in R2(n2). We initialize a sum in R2, which will be converted to the product. In a while-loop the ending condition is tested before the loop statement. This is necessary because R1 might be 0, meaning the loop statement should never be executed. During each loop statement, R0 is added to R2, and R1 is decremented.


Figure 1.8.3. Flowchart of a conditional statement.

      MOVS R2,#0     // sum
loop: CMP  R1,#0     // is n1 equal 0?
      BEQ  done
      ADDS R2,R2,R0  // sum = sum+n0
      SUBS R1,R1,#1  // n1 = n1-1
      B    loop      // n1 = n1-1
done:

  sum = 0;
  while(n1){
    sum = sum+n0;
    n1 = n1-1;
  }

Program 1.8.2. While-loop to implement multiply.

Next, we present if-then for signed 32-bit integers. The approach is
     Bring the first value into R0
     Bring the second value into R1
     Compare the two values using CMP R0,R1
     Execute one of the following signed conditionals

      BEQ target // Branch if R0 equals R1
      BNE target // Branch if R0 does not equal R1
      BLT target // Branch if R0 is less than R1 (signed)
      BLE target // Branch if R0 is less than or equal to R1 (signed)
      BGE target // Branch if R0 is greater than or equal to R1 (signed)
      BGT target // Branch if R0 is greater than R1 (signed)


The approach is similar when comparing two unsigned 32-bit integers
     Bring the first value into R0
     Bring the second value into R1
     Compare the two values using CMP R0,R1
     Execute one of the following unsigned conditionals

      BEQ target // Branch if R0 equals R1
      BNE target // Branch if R0 does not equal R1
      BLO target // Branch if R0 is less than R1 (unsigned)
      BLS target // Branch if R0 is less than or equal to R1 (unsigned)
      BHS target // Branch if R0 is greater than or equal to R1 (unsigned)
      BHI target // Branch if R0 is greater than R1 (unsigned)


 

Quiz 1.8

 

1.9. Stack and functions

As our software becoming more complex, we will need to create functions. The SP points to data on the top of the stack. We use push to save data and pop to retrieve data. The stack operates in a last in first out manner. Let's present rules for using the stack
      1. Functions should have an equal number of pushes and pops
      2. Stack accesses should not be performed outside the allocated area
      3. Stack reads and writes should not be performed within the free area
      4. Stack push should first decrement SP, then store the data
      5. Stack pop should first read the data, and then increment


The stack grows downwards in memory as we push data on to it so, although we refer to the most recent item as the “top of the stack” it is actually the item stored at the lowest address! To push data on the stack, the stack pointer is first decremented by 4, and then the 32-bit information is stored at the address specified by SP. To pop data from the stack, the 32-bit information pointed to by SP is first retrieved, and then the stack pointer is incremented by 4. SP points to the last item pushed, which will also be the next item to be popped. The boxes in Figure 1.9.1 represent 32-bit storage elements in RAM. The grey boxes in the figure refer to actual data stored on the stack, and the white boxes refer to locations in the stack area, but do not contain stack data. This figure illustrates how the stack is used to push the contents of Registers R0, R1, and R2 in that order. Assume Register R0 initially contains the value 1, R1 contains 2, and R2 contains 3. The drawing on the left shows the initial stack. The software executes these six instructions in this order:

      PUSH {R0}
      PUSH {R1}
      PUSH {R2}
      POP {R3}
      POP {R4}
      POP {R5}



Figure 1.9.1. Stack picture showing three numbers first being pushed, then three numbers being popped. You are allowed to draw stack pictures so that the lowest address is on the top (like this one) or so that lowest address is on the bottom. It is important however to be clear, accurate, and consistent.

Remember the order in the register list does not matter. The register with the lower number is stored in memory with the lower address. These are the same instructions

      PUSH {R2,R5}
      PUSH {R5,R2}


We use the BL instruction to call a subroutine. The BL instruction will save the return address in LR and branch to the subroutine.  The BX LR will return the LR back into the PC, causing control flow to revert back to the place from which the function was called. If one function calls another it will need to save the LR on the stack. In general we will adhere to the following rules according to ARM Architecture Procedure Call Standard (AAPCS)
     We use R0,R1,R2,R3 as input parameters
     We use R0 as the return parameter.
     We can freely use R0,R1,R2,R3,R12 without needing to push or pop.
     We can use R4-R11, but must push the values at the start and pop the values at then end.

There are two aspects of a function in assembly. First the aspect is the definition. The first three lines of Program 1.9.1 are comments, which explain what the function does. The explanation describes the input parameters and output parameter. Line 4 creates an assembly label specifying the entry point of the function. In this example, lines 5 and 6 perform the body of the function. Comments inside the body should explain how the function works. Line 7 is the function return using the BX LR instruction. Figure 1.9.2 shows the flowchart syntax for a function


Figure 1.9.2. Flowchart of a function; left is function invocation, right is the function definition.

// Inputs:  x in R0          1
//          y in R1          2
// Outputs: z = 4*x+y in R0  3
Linear:          //          4
  LSLS R0,#2     // R0=4*x   5
  ADDS R0,R0,R1  // R0=4*x+y 6
  BX   LR        // return   7

Program 1.9.1. Function definition in assembly.

Second the aspect is the invocation. Before we invoke a function, we establish the input parameters. Assume a,b,c are global variables. Lines 8-11 set the input parameters R0 to a and R1 to b. Line 12 is the invocation using the BL instruction. After the function returns, we process the output result. In this case, the result is stored in c. The overall effect is to implement c=4*a+b

  LDR  R0,=a   //             8
  LDR  R0,[R0] // R0 is a     9
  LDR  R1,=b   //             10
  LDR  R1,[R1] // R1 is b     11
  BL   Linear  // invocation  12
  LDR  R1,=c   //             13
  STR  R0,[R1] // c = 4*a+b   14

Program 1.9.2. Function invocation in assembly.

: What happens to the LR when the BL instruction is executed?

: How does the BX LR instruction return from the function?

: What must you do if one function calls another function?

 

Quiz 1.9

 

1.10. Array of Structs

A struct is a object with containing multiple fields having different types. A list contains multiple structs. In Figure 1.10.1, each struct has three fields: an ID, an Exam1 and an Exam2. The first field is a 32-bit ID. The second and third fields are 16-bit grades. E.g., the student with ID=7 has an Exam1 of 96 and an Exam2 of 82. Because each struct is 8 bytes, they can be stored contiguously. The end of the list is signified by a ID value of -1, called a sentinel. Program 1.10.1 defines this list in ROM. The function ID2Exam1 takes an ID and looks up their Exam1. If R1 points to a struct, then [R1,#0] accesses the ID, [R1,#4] accesses Exam1, and [R1,#6] accesses Exam2. Notice in assembly, to change the pointer to the next struct we add 8. In C, we add 1, and the compiler creates code that actually adds 8.


Figure 1.10.1. A list is an array of structs. Each struct has multiple elements with different types.

      .text
      .align 2
List: .long 5 // ID
      .short 95,96 // Scores
n1:   .long 7
      .short 96,82
n2:   .long 2
      .short 94,99
n3:   .long 1
      .short 95,86
n4:   .long -1 // sentinel
      .short 0,0 // NA
ID2Exam1: // R0 is the ID
      LDR  R1,=List // pointer
      LDR  R3,=-1 // sentinel
look: LDR  R2,[R1,#0] // ID
      CMP  R2,R3
      BEQ  fail // end?
      CMP  R2,R0
      BEQ  yes // found
      ADDS R1,R1,#8 // next
      B    look
fail: MOVS R0,#0 // fail
      B    done
yes:  LDRH R0,[R1,#4] // Exam1
done: BX   LR

Program 1.10.1. A list with four structs stored in ROM, the fifth struct is the sentinel.

Quiz 1.10

 

1.11. Lab 1. Introduction to Cortex M Assembly

The details of the labs can be found in GoogleDocs:
Lab 1. Introduction to Cortex M Assembly

Go to Chapter 2: Interfacing to Switches and LEDs

This material was created to teach ECE319K at the University of Texas at Austin

Reprinted with approval from Introduction to Embedded Systems Using the MSPM0+, ISBN: 979-8852536594

  Creative Commons License
Embedded Systems - Shape the World by Jonathan Valvano and Ramesh Yerraballi is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
Based on a work at http://users.ece.utexas.edu/~valvano/mspm0/

.