General Purpose Input/Output
Prof. Leod ECE3375, Winter 2022
This lesson continues the discussion of assembly language, with specific emphasis to the code base for the ARM®Cortex-A9 processor. The basics of general purpose input/output is discussed. This is of limited utility for class-room demonstration, as I can’t plug a cool custom peripheral into an online simulator, but it is an important topic to understand none-the-less.
Copyright By PowCoder代写 加微信 powcoder
Memory Mapped Peripherals
The ARM®Cortex-A9 is a memory mapped microcontroller. This means that all peripherals attached to the microprocessor are as- signed a fixed address in memory space. Controlling or receiving input data from these peripherals is therefore as simple as writing to, or reading from, that memory address. For example, consider the DE1-SoC development board that uses the ARM®Cortex-A9.
• There are 10 LEDs assigned to address 0xFF200000. Each of these LEDs is equivalent to a single bit in the 4-byte (32-bit) memory block starting at address 0xFF200000. 1 To light up the 3 right-most LEDs, simply write 0x7 (i.e., 0b111) to that address. These LEDs will remain illuminated until another value is written to the LED band address, or until the micro- controller is powered off.
• There are 10 switches assigned to address 0xFF200040. Again, each of these switches is equivalent to a single bit. To accept toggled switches as input to your program, simply read from that address.
Most peripherals are more complicated than LEDs and switches, and will have some structure. This means that more than one word in memory is assigned to that peripheral, and different words have different purposes. Finally, it is common to refer to the different memory addresses of the peripheral as registers, even though they may not have much in common with the registers in the CPU.
1 Because a word is 32 bits, each peripheral is assigned am integer multiple of 32-bits in ad- dress space — whether or not that much space is needed — so writing or reading a register’s worth of data to or from that peripheral doesn’t
“leak” out into adjacent memory space.
Input/Output Pins
All memory mapped peripherals built into the microcontroller chip have the same basic connection structure as the memory cells discussed previously.
• The peripheral is physically enabled when the address bus activates the appropriate minterm that corresponds to the pre-defined memory address of that peripheral.
• Additional logic will combine the addressing minterm with some control flags to distinguish between reading and writing to that peripheral.
• The data bus will also connect to that peripheral.
An example of a simplified output port is shown in Figure 1. This is representative of any peripheral that can only accept output from the microcontroller — for example, the LED bank mentioned above. Figure 1 is a generic output port, as just an output pin is shown instead of a hardware peripheral.
Q7, Q6, …, Q0
En Q[7..0] N-bit Latch
Figure 1: Simplified schematic of an 8-bit output port.
• Here the peripheral is enabled by supplying both the correct address to the address bus, and a control bit indicating that a str instruction was provided.
• A flip-flop or latch is used (actually 8 parallel latches, as there aree 8 data lines) to control the output instead of a tri-state buffer. This way, when output is not enabled, the value on the Q7, …, Q0 lines persists as the last value written. 2
An example of a simplified input port is shown in Figure 2. This is representative of any peripheral that can only accept input from the microcontroller — for example, the set of switches mentioned above. Again, Figure 2 represents a generic input port, as just an input pin is shown instead of a hardware peripheral.
2 This is evident when using the online- simulator: After writing a value to the LED bank and illuminating some LEDs, those LEDs remain illuminated while the program contin- ues, until a subsequent write to the LED bank changes the values.
Figure 2: Simplified schematic of an 8-bit input port. Either a “pull-up” resistor Rup or a “pull- down” resistor Rdn is used to ensure a digital input is present even when the input pin is floating.
Q7, Q6, …, Q0
• Again, the peripheral is enabled by the correct address and a control bit indicating that a ldr instruction was provided.
• Here a tri-state buffer is used (actually, set of 8 parallel tri- state buffers, as there are 8 data lines) to separate the data bus from the input port. Note the orientation of the tri-state buffer allows the external value Q7, …, Q0 to be placed on the data bus, but not vice-versa.
• This circuit schematic assumes the input from the peripheral is already digital.
A digital input port needs either a pull-up or a pull-down resistor, otherwise the result of a read operation is unpredictable if the mi- crocontroller attempts to read from a floating port. The difference between pull-up and pull-down is basically a matter of taste: with a pull-down resistor an unconnected pin will always read zero, while with a pull-up resistor an unconnected pin will always read one. Ei- ther state is fine, and simply needs to be accounted for in software when probing that port. The only situation in which there is a dif- ference between pull-up and pull-down is when there is an external signal at that port.
• Whenever the signal is the complement of the normal floating state of the port, 3 a current will flow across the resistor.
• To minimize power loss, the resistances used are usually rela- tively large.
• However, if you know that the external signal typically “rests” in a particular digital state, choosing a microcontroller with the equivalent input will minimize power loss. A peripheral that only rarely measures a signal, and is active high (i.e.,
3 So whenever a signal of 1 is sent to a pull- down port, or a signal of 0 is sent to a pull-up port.
zero is the “off” or “resting” state), can cause a lot of unneces- sary power loss when connected to a pull-up input port.
Another consideration is if the external signal comes from a circuit with a large intrinsic capacitance. In that case the combination of large capacitance and large pull-up/pull-down resistance creates a slow transient. This configuration can take a long time (in terms of microcontroller processor speeds) to settle to a “good” digital value.
Many of the ports on a microcontroller are configured to be bidirec- tional: able to act as either input or output ports, and configurable in software. Although the LED bank and set of switches previously discussed as peripherals obviously only have output and input fun- tionality, 4 respectively, these peripherals are not physically on the microcontroller chip — rather they are connected to some ports
on the microcontroller. Making particular ports limited to input or output by the physical hardware present limits the functionality of the microcontroller, and so is something a manufacturer would typically try to avoid.
An example of a simplified bidirectional port is shown in Figure 3. This circuit assumes that the CPU ensures that only one of the str and ldr control bits can be set at a time.
• The str and ldr control bits cannot both be set without mak- ing both tristate buffers active — if the peripheral is providing an input Q7, Q6, …, Q0 at the same time the Qout flip-flop is providing output to the pin, bad things can happen.
• Otherwise, the Qout flip-flop preserves the state in between str operations while the pin is acting as an output, and the
4 I tested it out on the simulator: you actually can read from the LED bank — it just returns the state of the LEDs. So basically (in the simulator, anyway, I don’t know about the actual hardware) the LED bank acts as a memory cell that can light up. However, while you can write to the switches in software without triggering an error message, it doesn’t do anything.
Figure 3: Simplified schematic of an 8-bit bidi- rectional port. Either a “pull-up” resistor Rup or a “pull-down” resistor Rdn is used to ensure a digital input is present even when a floating pin is configured as input.
Q7, Q6, …, Q0
En Q[7..0] N-bit Latch
pull-up/pull-down resistors ensure that a floating pin pro- vides a digital signal when acting as an input.
• The pull-up/pull-down resistors will drive some (unneces- sary) current when the pin is acting as an output. Additional tri-state buffers could be added to block this, but usually aren’t — the complexity (and intrinsic power loss) of addi- tional tri-state buffers exceeds the cost of some parasitic power loss.
General Purpose Input/Output
As mentioned above, we refer to the memory addresses assigned to memory-mapped peripherals as registers. There are three basic kinds of registers for an peripheral.
Definition: A status register is the part of the peripheral that indicates the current operating status of that peripheral. Status registers are typically read-only.
Definition: A control register is the part of the peripheral that defines the present operating method. Control registers are typically write-only.
Definition: A data register is the part of the peripheral that accepts or produces data. These are usually read/write.
Depending on the complexity of the peripheral, it may have some or all of these types of registers, and may have more than one of each type.
Most external peripherals connect to a microcontroller the same way: through a general purpose input/output port. These ports have a simple standard architecture.
• The physical pins in the port on the microcontroller chip are mapped to a data register in memory space, with at least one bit in memory for each pin on the port.
• A control register of the same width is mapped in memory space. This register allows the individual pins on the port to be
set to input or output. Usually this register is a word higher (or lower) than the data register in memory space.
• Usually these registers are addressed by word, even though the physical port is often less than 32 bits.
• The general purpose input/output control registers on the DE1-SoC board interpret bitwise values of 1 to set the corre- sponding pin as an output, and a value of 0 to set the corre- sponding pin as an input.
Since everything is done in the simulator there isn’t really the op- tion to plug in cool custom peripherals this year, so these details are of limited importance. However, the basic concept of reading/writ- ing individual bits is useful for both general purpose input/output and for specific peripherals.
31302928272625242322212019181716151413121110 9 8 7 6 5 4 3 2 1 0 [base+0x04]GPIOControlRegister 31302928272625242322212019181716151413121110 9 8 7 6 5 4 3 2 1 0 [base]GPIODataRegister
The structure of a GPIO port on an ARM system is shown in Fig- ure 4. As mentioned above, for a generic microcontroller you may expect the control register to be above or below the data register. For an ARM system the control register is above the data register. The simulator has two GPIO ports mapped to parallel ports (at addresses 0xFF200060 and 0xFF200070).
Figure 4: Structure of an ARM GPIO port.
• On the simulator, these ports are represented by 32 check boxes.
• When the check boxes are dark black, they are configured as input. Input can be set by checking/unchecking each box.
• When the check boxes are greyed out, they are configured as output. The output will be shown as a checked/unchecked box.
I do recommend playing around with the simulator GPIO port if you are unsure of any of the conceptual details discussed in this lesson.
Example: Consider an 8-bit GPIO port that is connected to a peripheral that is a mix of LEDs and push buttons. The top four bits are connected to LEDs, and the bottom four bits are connected to push buttons.
• Assume the peripheral is constructed in a reasonably idiot-proof fashion. Attempting to read from an LED always returns zero, and attempting to write to a push button does nothing.
To properly use this port, we should set the control register to 0x1111 0000, as shown in Figure 5. This defines all of the LEDs as output and the push buttons as inputs.
Now assume you did not listen very well during this lesson (hard to imagine, I know!), and instead set the control regis- ter to 0x1010 1010.
• You press down on all the switches.
• The control register has only assigned two switches as input, and the peripheral always returns zero from the LEDs improperly set to inputs.
• Therefore, only the value 0x0000 0101 is returned, rather than the expected 0x0000 1111.
This is shown in Figure 6.
1 1 0 0 0 0 GPIOControlRegister 0 0 0 0 0 0 GPIODataRegister
Figure 5: A mixed LED/push button peripheral, connected to a GPIO port with a properly- defined control register.
1 0 1 0 1 0 GPIOControlRegister 0 0 0 1 0 1 GPIODataRegister
Figure 6: A mixed LED/push button peripheral, connected to a GPIO port with a poorly-defined control register. When attempting to read, only two switches are properly configured.
With the same poorly-set control register, you now try to illu- minate all the LEDs by writing 0x1111 1111 to the peripheral.
• Writing a value to a push button does nothing, regardless of whether or not the pin is set as output.
• Only two of the LEDs are set as output, so only two LEDs will light up.
1 0 1 0 1 0 1 0 GPIOControlRegister 1 1 1 1 1 1 1 1 GPIODataRegister
Figure 7: A mixed LED/push button peripheral, connected to a GPIO port with a poorly-defined control register. When attempting to write, only two LEDs are properly configured.
Interacting with Peripherals with C
Assembly language is convenient for simple programs to demon- strate various functionalities of the ARM®Cortex-A9, as I have done in my notes.
• This is mostly because it is very easy to directly interact with hardware peripherals: just load the memory address of the peripheral into a register and start reading/writing to it.
• The downside is that functional programming, loops, and case-based conditionals are all rather complicated in Assem- bly, and writing useful subroutines involves a lot of “busy work”.
For programs written to solve problems rather than play with periph- erals, a higher-level programming language is probably a better choice than Assembly. In this case, the most common language is C. A whole bunch of demos for coding in C was provided in last week’s lesson material.
Writing programs in C makes nested functions and complicated data manipulation much easier to code, but the trade-off is that the interaction with hardware is a bit clumsy.
• Basically you need to define a pointer to the memory-mapped peripheral, then you can manipulate the data at that address.
• Because the peripheral is at a fixed memory address rather than a memory address that is determined by the compiler,
the keyword const should be used after the data type to clar- ify that the address is fixed and will not change.
• Because the peripheral is external to the CPU the keyword volatile should be used to let the compiler know that the contents at this address may change without explicit instruc- tions from the CPU. 5
• You can write the peripheral address directly into your code, but using precompiler directives to label the address will make your code more readable.
As mentioned above, this is all done with pointers.
• To define a pointer, use a “*” after the variable type in the
declaration.
• “*” is also the dereferencing operator, that implies the given operation is to be done to the memory address the pointer references, rather than the pointer itself.
• “&” is the addressing operator, that looks up the memory ad- dress of a variable rather than the value of that variable.
Perhaps you are much better at this than I am, but often I get con- fused with all the “*” operators present in C code — especially C code for a microcontroller.
5 Basically we use the keyword volatile to prevent the compiler from trying to optimize any of the code that involves that peripheral.
Just in case you are in the same boat, here are some examples:
unsigned int n = 2;
unsigned int *n_ptr = &n; *n_ptr=4; //setsn=4
n_ptr = 2; // changes n_ptr address
Here n_ptr is defined as a pointer to an unsigned integer, and initialized with the address in memory used by the previously- defined unsigned integer n.
• When the code *n_ptr=4 is executed, the value of n will change to 4.
• When the final line of code, n_ptr=4 is executed, the pointer will now point to memory address 4 (which, in all probability, is not the address of n anymore).
Using “*” can get confusing, especially when writing microcon- troller in C. For example, if you wanted to store a constant (such as 0xA5, say) to a fixed memory address (such as 0x01FC, say) you could use the C code:
*((unsigned int*)(0x01FC)) = 0xA5;
Here the first “*” used in the above line of code is the dereferenc- ing operator, indicating that it is the contents of the following mem- ory address that should be modified rather than the memory ad- dress itself. The second “*” is part of the variable type, namely that the following number (0x01FC) is the address to an unsigned integer variable.
As another example, if you want store the contents of a known memory address (say 0xFF2001FF) to a variable, you could write:
unsigned char x = *(unsigned char *)(0xFF2001FF);
Here we are only reading a single byte of data, as the variable type is unsigned char and the pointer is similarly a unsigned char *. Note that the actual address is not a byte: it is a word.
Finally, as shown in the example code from last week, if you have a pointer to a structure you can use the visually-intuitive arrow operator “->”.
// extremely important structure
typedef struct _TwoInt {
int x2; } TwoInt;
TwoInt* c = &b;
c->x1 = 3; //sets b.x1 = 3 (*c).x2 = 1; //sets b.x2 = 1
As shown above, S->var is functionally the same as (*S).var.
As a working example, the following code turns on the first LED:
#define LED_BASE 0xFF200000
void main () {
volatile unsigned int* const LED_ptr = (unsigned int *) LED_BASE;
*LED_ptr = 1; //turn on the right-most LED }
This could also be done in one line as *((unsigned int *)(0xFF200000))=1;.
Bitwise Logic in C
Implementing bitwise logic in C is perhaps a bit easier than in As- sembly language. One nice thing is that C effectively has unary operators that apply to, and eventually replace, a variable. Some of these are summarized in Table 1. The only tricky thing is remember- ing the difference between bitwise logic and variable logic.
• && is the logical and. x && y evaluates as true (1) or false (0), regardless of the size of x,y. If both x and y are non-zero, this expression will evaluate as true.
• & is the bitwise and. x & y returns a sequence of bits that is the same size as x,y. Bit n in this sequence is 1 if both bits n from x and y are 1, otherwise it is zero.
• || is the logical or. x || y evaluates as true (1) or false (0), regardless of the size of x,y. If either of x and y are non-zero, this expression will evaluate as true.
• | is the bitwise or. x | y returns a sequence of bits that is the same size as x,y. Bit n in this sequence is 1 if either bit n from x or y is 1, otherwise it is zero.
As noted in the table, there is a bitwise exclusive or ^. However there is no equivalent logical exclusive or that applies to an entire variable. There are also convenient logical and bitwise complement operators.
Subtraction
Multiplication
Bitwise And
Bitwise Or
Bitwise Exclusive Or Bitwise Logical Shift Left Bitwise Logical Shift Right
Short Form
x += y; x -= y; x *= y; x /= y; x &= y; x |= y; x ^= y; x <<= y; x >>= y;
x=x+y; x=x-y; x=x*y; x=x/y; x=x&y; x=x
程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com