Output Devices
Unless implementing Expansion Options, the Cow Pi circuit’s output devices are simple light emitting diodes and a display module, described here.
Light Emitting Diodes (LEDs)
An idealized diode allows current to flow in only one direction,[1] which is why we keep track of which end is tha anode and which is the cathode.

Fig. 16 In a diode, conventional current flows from the anode to the cathode (or, equivalently, electrons enter through the cathode and leave through the anode).
A light emitting diode, or LED, is a diode designed to emit light.[2]

Fig. 17 A light emitting diode emits light when current flows
Theory of Operation
A full discussion of the material science behind semiconductors is beyond the scope of this datasheet. The short version is that when making solid state electronics, the extrinsic semiconductor metal is doped with impurities. Some dopants cause the semiconductor material to have excess electrons in the valent shell, creating a region with excess negative charge (“N-type”). Other dopants cause the semiconductor material to a shortage of electrons in the valent shell – typically expressed as an excess of holes – creating a region with excess positive charge (“P-type”).
In a diode, when the voltage at the anode is sufficiently higher than the voltage at the cathode, electrons in the N region are able to leave the valent shell, cross the depletion region at the PN junction, and fill a hole. Meanwhile, new electrons enter through the cathode, replacing those that crossed the PN junction, and electrons leave through the anode, causing new holes to replace those that were filled. The mental image is of electrons moving away from the cathode toward the PN junction, and of holes moving away from the anode toward the PN junction.
Because of the quantum nature of electron shells, light will always be emitted when an electron fills a hole. In ordinary diodes, we don’t see this light because it might not be in the visible spectrum, most of the light is “lost” to internal reflection in the semiconductor material, and the opaque casing blocks what little light might be emitted. In LEDs, the dopants are selected to cause the emission to be at a very particular frequency.[3] Additional material is added to the semiconductor material to reduce internal reflection at that frequency. And, of course, the casing is translucent.
To avoid damaging the LED, the amount of current flowing through it must be limited – each LED’s datasheet specifies the maximum forward current.
A very conventional approach, which is used in the Cow Pi circuit, is to add a current limiting resistor.
Ohm’s Law allows us to determine the minimum resistance needed:
Your actual choice of resistance strikes a balance between the desired brightness, not stressing the microcontroller’s ability to dissipate heat at the pins, and not dipping too deep into your electrical current budget. [4]
Illuminating/Deluminating an LED
If you have an external power source other than the microcontroller board’s voltage regulator, then using a transistor to enable and disable current flow through the LED is a good way to reduce the current at the microcontroller’s pins to a few microamps. If all current is sourced from a USB cable through the microcontroller board, then using a transistor in that manner only adds to the circuit’s complexity while offering little benefit. For this reason, we drive the LEDs directly from the microcontroller board’s pins.
There are advantages to designing the circuit so that placing the pin at logic high causes the LED to illuminate, and there are advantages to designing the circuit so that placing the pin at logic low causes the LED to illuminate. In the end, we chose to be consistent with the built-in LEDs on the microcontroller boards used for the Cow Pi: logic high (boolean 1) causes the LED to illuminate, and logic low (boolean 0) causes the LED to deluminate.
The CowPi library’s cowpi_setup() function configures the pins that drive the LEDs as output pins, along with other configuration settings.
That done, illuminating/deluminating an LED is as simple as setting the pin’s logic value.
If you are not writing code using memory-mapped I/O, then you would do this with Arduino’s digitalWrite() function or the Raspberry Pi SDK’s gpio_put() function.
If you are writing code using memory-mapped I/O, then you would set the pin’s bit in the I/O bank’s output register, as described in the Microcontroller-Specific Details Section.
If the LED should be dark, then set the bit to 0.
If the LED should be lit up, then set the bit to 1.
Note
When using the Arduino Nano or Arduino Uno microcontroller board, the pin used for the internal LED (the Cow Pi’s left LED) is also used as the clock pin for SPI communication. If you are making frequent updates to the display module through SPI, this will likely render the left LED unusable. You can safely assume that any lab assignments will take this into account.
Note
When using the Arduino Nano or Arduino Uno microcontroller board, the pin used for the Cow Pi’s right LED is also used as the data pin for the Controller-In/Peripheral-Out SPI mode. Unless needed for one of the Expansion Options, we will not use this particular SPI mode. However, when SPI is enabled, the SPI hardware overrides that pin’s settings, changing it to an input pin.
As described in the Serial-Parallel Interface Section for the ATmega328P, the CowPi_stdio library handles this by enabling SPI only long enough to transmit data to the display module, and disabling SPI immediately thereafter. You may notice the right LED briefly dimming when you send updates to the display module through the SPI.
To Learn More
Adafruit and SparkFun have pages where you can learn more about LEDs:
All About LEDs at Adafruit
Light-Emitting Diodes (LEDs) at SparkFun
Display Modules
While it is possible to perform textual output (and input) through the serial terminal, we find it more interesting – and usually more meaningful – to provide textual and numerical output on a display module in the Cow Pi circuit, leaving the serial terminal for debugging and logging purposes. While the CowPi_stdio Library provides functions for fine-grained control of the display modules, we recommend using the higher-level interface that makes use of C file streams.
Each of the display modules described below makes use of the Serial-Parallel Interface (SPI) and/or the Inter-Integrated Circuit (I2C) protocol to send data to the display modules using as few wires as possible. We have found that an interesting memory-mapped I/O exercise is to manipulate the registers for these communication protocols to send data to the display modules.
Specific Display Modules
The overall Cow Pi hardware design is largely flexible for the choice of display module. We generally assume there is only one display module in the circuit, but this is not a hard-and-fast requirement. The CowPi_stdio library, however, only supports a limited number of display modules – those currently available are so either because we have used them in lab assignments (such as the 8-digit/7-segment display and the LCD character display) or because they are relatively low-hanging fruit from earlier efforts put into the library (such as the LED matrix display).
HD44780-driven LCD Character Display
See also
The HD44780 “dot-matrix liquid crystal display controller and driver LSI displays alphanumerics, Japanese kana characters, and symbols.” It can receive updates with 12 bits (4 control bits plus a byte to be acted upon) or with 8 bits (4 control bits plus a halfbyte to be acted upon). While these display modules can be driven directly from a microcontroller, Cow Pi circuits use a serial adapter (either a 74HC595 or 74AHCT595 shift register for SPI, or a PCF8574-based I2C adapter, or an Adafruit I2C/SPI Adapter).
The CowPi_stdio add_display_module() function (which is called by the CowPi cowpi_setup() function) configures a HD44780-based display module so that it can be controlled with 8 bits in parallel.
One of the tradeoffs is that each character or command byte must be transmitted as two halfbytes.
The CowPi_stdio library takes care of dividing the full byte into two halfbytes and passing each halfbyte to cowpi_hd44780_send_halfbyte in the appropriate order.
Important
When a halfbyte is passed to cowpi_hd44780_send_halfbyte, it will be in the lower 4 bits of the halfbyte argument, regardless of which of the two halfbytes it is.
The serial adapter converts the serial data coming from the microcontroller into the parallel data that the display module requires. For this to be effective, the function must pack the bits in the order that the serial adapter expects.
Data Byte for LCD1602 Display Module
The COWPI_DEFAULT bit order is described in Table 1.
When constructing a byte to place in the SPI or I2C Data Register:
Data Register |
Bit7 |
Bit6 |
Bit5 |
Bit4 |
Bit3 |
Bit2 |
Bit1 |
Bit0 |
|---|---|---|---|---|---|---|---|---|
HD44780 Bit |
D7 |
D6 |
D5 |
D4 |
BT |
EN |
RW |
RS |
Bit source |
|
backlight on/off |
latch data |
read/write |
|
|||
- Bits 7..4
The upper four bits are the
halfbyteargument passed to COWPI_HD44780_SEND_HALFBYTE(), left-shifted four places.- Bit 3
Bit 3 is a 1 if you want the display module’s backlight to illuminate, or 0 if you want it deluminated.[5]
- Bit 2
As described below, bit 2 is used to send a pulse to the HD44780 that instructs the display module that it should latch-in the halfbyte that it has received.
- Bit 1
Bit 1 informs the HD44780 whether data is being sent to it, or if a data request is being made of it; while it is possible to query the display module’s memory, the CowPi_stdio library does not support this feature, and bit 1 should always be 0.
- Bit 0
Bit 0 informs the HD44780 whether the halfbyte that it receives is part of a command or is part of a character; if the
is_commandargument passed to COWPI_HD44780_SEND_HALFBYTE() istrue, then bit 0 should be 0; otherwise, bit 0 should be 1.
Data Byte Sequence
Important
If you are going to write code to transmit data to a display module, see the Microcontroller-Specific Details for the specific mechanism to transmit a data byte for your particular microcontroller.
When the function executes the SPI Controller-Out/Peripheral-In sequence or the I2C controller-transmitter sequence (see the pseudocode in the Controller Transmitter Sequence Section), it will have three (3) data bytes to transmit.
First, the halfbyte needs to be sent without yet instructing the display module to latch-in the halfbyte:
bitwise_or( (halfbyte << 4), ((1 if backlight_on else 0) << 3), (0 << 2), (* not yet latching halfbyte *) (0 << 1), ((0 if is_command else 1) << 0) )
Second, the start of the “latch pulse” needs to be sent:
bitwise_or( (halfbyte << 4), ((1 if backlight_on else 0) << 3), (1 << 2), (* latch the halfbyte *) (0 << 1), ((0 if is_command else 1) << 0) )
The pulse needs to stay active for at least 0.5𝜇s. While there is a low-level AVR-libc function that can introduce a delay of nearly exactly 0.5𝜇s, we recommend introducing a 1𝜇s delay using the Arduino
delayMicroseconds()function, which is portable across all devices using the Arduino framework.
Third, the end of the “latch pulse” needs to be sent:
bitwise_or( (halfbyte << 4), ((1 if backlight_on else 0) << 3), (0 << 2), (* complete the latch *) (0 << 1), ((0 if is_command else 1) << 0) )
MAX7219-driven 8-Digit/7-Segment Display
See also
The MAX7219 is “serial input/output common-cathode display drivers that interface microprocessors (µPs) to 7-segment numeric LED displays of up to 8 digits, bar-graph displays, or 64 individual LEDs.” The MAX7219 receives data from the microcontroller exclusively via the SPI protocol. It has a 2-byte shift register (though only the lower 12 bits are used), so the microcontroller transmits two bytes between setting the chip select line low and setting it high. The MAX7219 expects the word to arrive most significant bit first, which means that the most significant byte needs to be the first of the two bytes that are transmitted.
The MAX7219’s word consists of:
Shift Register |
Bit15 |
Bit14 |
Bit13 |
Bit12 |
Bit11 |
Bit10 |
Bit9 |
Bit8 |
Bit7 |
Bit6 |
Bit5 |
Bit4 |
Bit3 |
Bit2 |
Bit1 |
Bit0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Usage |
(unused) |
Address |
Decimal Point |
Segment A |
Segment B |
Segment C |
Segment D |
Segment E |
Segment F |
Segment G |
||||||
Segment Location |
Top |
Upper Right |
Lower Right |
Bottom |
Lower Left |
Upper Left |
Middle |
|||||||||
The rightmost (least significant) digit on the display maps to address 1, and the leftmost (most significant) digit on the display maps to address 8. Addresses 9-15 are used to control functions such as the brightness level and BCD decode mode (for these control functions, the lower 8 bits serve a different purpose than specifying digit segments).

Fig. 18 8 digit/7 segment display’s digit addresses and segment identifiers
Data Byte Sequence
Important
If you are going to write code to transmit data to a display module, see the Microcontroller-Specific Details for the sequence used to transmit a data byte using SPI for your particular microcontroller.
When the function executes the SPI Controller-Out/Peripheral-In sequence (see the pseudocode in the Controller Output/Peripheral Input Sequence Section), it will have two (2) data bytes to transmit: the digit’s address and the segment pattern.
8 (* signal the peripheral to receive data *)
9set_pin(select_pin, 0);
10 (* send the data that the peripheral needs *)
11spi->data := digit_address
12busy_wait_while(bit 7 of i2c->status = 0)
13spi->data := segment_bit_vector
14busy_wait_while(bit 7 of i2c->status = 0)
15set_pin(select_pin, 1);
MAX7219-driven LED Matrix
The MAX7219 is “serial input/output common-cathode display drivers that interface microprocessors (µPs) to 7-segment numeric LED displays of up to 8 digits, bar-graph displays, or 64 individual LEDs.” The MAX7219 receives data from the microcontroller exclusively via the SPI protocol. It has a 2-byte shift register (though only the lower 12 bits are used), so the microcontroller transmits two bytes between setting the chip select line low and setting it high. The MAX7219 expects the word to arrive most significant bit first, which means that the most significant byte needs to be the first of the two bytes that are transmitted.
The MAX7219’s word consists of:
Shift Register |
Bit15 |
Bit14 |
Bit13 |
Bit12 |
Bit11 |
Bit10 |
Bit9 |
Bit8 |
Bit7 |
Bit6 |
Bit5 |
Bit4 |
Bit3 |
Bit2 |
Bit1 |
Bit0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Usage |
(unused) |
Row or Column Address |
LED pattern within that row or column |
|||||||||||||
Unlike the 7-segment display, the MAX7219 datasheet does not pre-define the mapping of addresses to rows (or columns) nor of bits to the specific LEDs within each row or column.
We can safely assume, however, that whichever mapping is used, the rows or columns have sequential addresses, and the LEDs have sequential bits.
The CowPi_stdio library provides orientations and flips parameters to account for this variation so that when using a FILE stream, the display appears to be comprised of columns.
Within each apparent column, the bottommost LED corresponds to the column bit vector’s most significant bit, and the uppermost LED corresponds to the column bit vector’s least significant bit.
Addresses 9-15 are used to control functions such as the brightness level and how many rows (or columns) are displayed (for these control functions, the lower 8 bits serve a different purpose than specifying digit segments).
Data Byte Sequence
Important
If you are going to write code to transmit data to a display module, see the Microcontroller-Specific Details for the sequence used to transmit a data byte using SPI for your particular microcontroller.
When the function executes the SPI Controller-Out/Peripheral-In sequence (see the pseudocode in the Controller Output/Peripheral Input Sequence Section), it will have two (2) data bytes to transmit: the row/column’s address and the LED pattern.
8 (* signal the peripheral to receive data *)
9set_pin(select_pin, 0);
10 (* send the data that the peripheral needs *)
11spi->data := line_address
12busy_wait_while(bit 7 of i2c->status = 0)
13spi->data := LED_bit_vector
14busy_wait_while(bit 7 of i2c->status = 0)
15set_pin(select_pin, 1);
SSD1306-driven OLED Graphic Display
Todo
(Implement and) Describe SSD1306-driven OLED Graphic Displays
Communication Protocols
When a microprocessor or microcontroller needs to communicate with peripheral devices, the most straight-forward approach is to run all of the necessary lines in parallel. Besides its simplicity, it should be clear that for a given signalling rate, the data rate is proportional to the number of data lines. As a specific example, before the advent of USB, printers were typically connected to microcomputers using a 25-line parallel cable (8 lines for data, 4 for control, 5 for status, and the remainder as ground lines). The problems in that type of application stem from susceptibility to external noise, and mutual inductance and capacitance between the lines. These factors both limited the signalling rate and limited parallel cables’ length to a few meters at most.
For our purposes, another problem with parallel lines is that we don’t have that many pins available. For example, an HD44780-driven LCD character display has 8 lines for data and four for control; the HD44780 has a mode that can reduce the number of data lines to 4 – so we would “only” need 8 of the pins on our microcontroller board, a very significant fraction. If we moved all of the MAX 7219’s control functions to software, we would still need 16 lines for an 8-digit/7-segment display or an LED matrix display.
The solution is serial communication protocols. Some you are already familiar with, such as RS-232 or USB. Two protocols commonly used to communicate with microcontrollers’ peripherals are the Serial-Parallel Interface (SPI) protocol and the Inter-Integrated Circuit (I2C) protocol.
Serial-Parallel Interface (SPI)
The Serial-Parallel Interface protocol is a remarkably simple protocol.
SPI transmits and receives data using one or two data lines: if data only travels in one direction then only one data line is needed; if data travels in both directions, then two data lines are needed. SPI also requires a clock line to synchronize transmission and reception. Finally, each a “select” or “latch” line is required for each peripheral, to identify which peripheral is being addressed and to indicate when all data for a particular transmission have been sent.
In the case of the Cow Pi, in which data travels only from the controller to the peripheral, and in which there is a single peripheral, three lines are needed (not including power and ground).
Inter-Integrated Circuit (I2C)
The Inter-Integrated Circuit protocol is more complex than SPI but also more versatile.
I2C requires only two lines (not including power and ground), regardless of how many peripherals are being used: one for bi-directional data and one for a clock. Where SPI uses “select” lines to identify which peripheral is being addressed, each I2C peripheral is addressed using a 7-bit address. Unlike SPI, I2C permits multiple controllers to be present on the same I2C bus; arbitration for control of the bus is handled through the transmission of START, RESTART, and STOP bits. External pullup resistors on each of these two lines allow any device (whether it is a controller or a peripheral) on the I2C bus to generate signals on a line by grounding the line or letting it be pulled high by the resistor.
In the case of the Cow Pi, most of the I2C’s versatility is unused, but that doesn’t make I2C any less useful for us.