Вы находитесь на странице: 1из 38

LESSON 9: UART

An embedded system often requires a means for communicating with the external world
for a number of possible reasons. It could be to transferring data to another device,
sending and receiving commands, or simply for debugging purposes. One of the most
common interfaces used in embedded systems is the universal asynchronous
receiver/transmitter (UART). When a board arrives in the hands of the software/firmware
team, the first step is typically to get the debug console functional. The debug console is a
serial interface which historically is implemented as RS-232 to connect with a PC serial port.
These days most PCs not longer have a serial port, so it is more commonly seen
implemented using USB, however the concept is the same. In this lesson, we will learn a
bit about the theory behind UART and RS-232, learn how to write a simple UART driver for
the MSP430, and create a menu which gives the user the ability to change the frequency
of the blinking LED during runtime.
It is important to distinguish the difference between the terms UART and RS-232. The UART
is the peripheral on the microcontroller which can send and receive serial data
asynchronously, while RS-232 is a signalling standard. RS-232 has no dependency on any
higher level protocol, however it does have a simple layer 1 (physical layer) set of standards
which must be followed. The UART module may support several features which allow it to
interface with various signaling standard such as RS-232 or RS-485 – another serial
interface commonly used in industrial applications.

RS-232

RS-232 is a point-to-point signalling standard, meaning only two devices can be


connected to each other. The minimum connection required for bidirectional
communication is three signals: transmit (TX), receive (RX), and ground. The separate
RX and TX lines mean that data can flow in both directions at the same time. This is called
full-duplex and it is the standard means for communicating over serial. However,
depending on the higher level protocols, there may be a need to block the transmitter while
receiving. This is called half-duplex. Hardware flow control can also be enabled in order
to mitigate the flow of data. Two optional lines RTS and CTS are provided for this function.
Typically RS-232 is used without hardware flow control and at full duplex. We are not
going to go into details on all the possible configurations, however you can read about it
here if you are interested.

RS-232 signals are different than than what we are used to in the digital world because
the voltage switches between negative and positive values. The standard defines signals
which typically vary from -5V to +5V, but can as much as -15V to +15V. The idle state of
the line is at the negative voltage level and is referred to as a ‘mark’. The logical value of
a mark is one (1). The positive voltage is called a ‘space’, and indicates a logic zero (0).
To begin a transmission of data, a start bit (space) is sent to the receiver. Then the data
is transmitted. The data can be in several possible formats depending what is supported
by both devices. To end a transmission, a stop bit (mark) is sent to the receiver, and the
held in the idle state. At least one stop bit is required, but two stop bits are often supported
as well.

When hooking up RS-232 to an MCU it is important to remember that the voltage levels
supported by the IO are different (0V – 3.3V), so an external transceiver is required to
convert the signals to the appropriate levels. If you try to connect RS-232 directly to the
MSP430 or most other microcontrollers it will not work and likely cause some damage.
The MAX232 and variants are some of of the most common RS-232 transceivers on the
market. It is extremely simple to use and can be easily breadboarded. Here is an
example of one I have built:

Fortunately, the MSP430 Launchpad has a serial to USB converter built right onto the
the board so this additional equipment is not required. Therefore, we won’t cover how to
build it in this tutorial, but if you would like to know more feel free to shoot me an email.
We will look in more detail at the MSP430 implementation later on.

Universal asynchronous receiver/transmitter (UART)


UART peripherals typically have several configurable parameters required to support
different standards. There are five parameters which must be configured correctly to
establish a basic serial connection:
▪ Baud rate: Baud rate is the number of symbols or modulations per second. Basically,
the baud rate indicates how many times the lines can change state (high or low)
per second. Since each symbol represents one bit, the bit rate equals the baud
rate. For example, if the baud rate is 9600, there are 9600 symbols sent per
second and therefore the bit rate is 9600 bits per second (bps) .
▪ Number of data bits: The number of data bits transmitted is typically between 5 and 8,
with 7 and 8 being the most common since an ASCII character is 7 bits for the
standard set and 8 bits for the extended.
▪ Parity: The parity can be even, odd, mark or space. The UART peripheral calculates
the number of 1s present in the transmission. If the parity is configured to even
and the number of 1’s is even then the parity bit is set zero. If the number of 1s is
odd, the parity bit is set to a 1 to make the count even. If the parity is configured to
odd, and the number of 1s is odd, then parity bit is set to 0. Otherwise it is set to 1
to make the count odd. Mark and space parity mean that the parity bit will either
be one or zero respectively for every transmission.
▪ Stop bits: The number of stop bits is most commonly configurable to either one or two.
On some devices, half bits are supported as well, for example 1.5 stop bits. The
number of stop bits determines how much of a break is required between
concurrent transmissions.
▪ Endianess: Some UART peripherals offer the option to send the data in either LSB
(least significant bit) or MSB (most significant bit). Serial communication of ASCII
characters is almost always LSB.
All of these parameters must be set to the same configuration on both devices for
successful communication. The following image is an example of a UART transmission.

Image courtesy of one of our very active members, Yury. Thanks!


Here we have a 5 bit transmission with an odd parity. Since there are an odd number of
1s in the transmission, the parity bit is 0. The data bit closest to the start bit is the LSB.
The number of stop bits is not defined since we only see one transmission. However if
there was 1 stop bit and we were running at 9600 baud, this configuration would be
abbreviated 9600 5O1. Other common configuration include:
9600 7E1 – 9600 baud, 7 bits data, even parity and 1 stop bit
9600 8N1 – 9600 baud , 8 bits data, no parity and 1 stop bit
115200 8N1 – 115200 baud, 8 bits data, no parity and 1 stop bit
The MSP430 UART
The MSP430 provides a module called the USCI (universal serial communications
interface) which supports multiple types of serial interfaces. There are two variants of the
USCI module each of which support specific interfaces:
USCI_A: UART and SPI
USCI_B: SPI and I2C
A given device may have none, one or more of each of these modules, depending on its
implementation. It is important to check in the datasheet to see exactly what is supported
in the device being used. Since USCI_A actually supports multiple standards, there are
many registers and settings. We will only concentrate on those relative to this lesson.
The register map for the USCI_A module is as follows:

TI MSP430x2xx Family Reference Manual (SLAU144J)


The first register, UCAxCTL0 or USCI_Ax Control Register 0 contains the configuration
for the protocol.

TI MSP430x2xx Family Reference Manual (SLAU144J)


▪ UCPEN: Parity enable
▪ 0 Parity disabled
▪ 1 Parity enabled
▪ UCPAR: Parity mode selection
▪ 0 Odd parity
▪ 1 Even parity
▪ UCMSB: MSB (most significant bit) first selection
▪ 0 LSB first
▪ 1 MSB first
▪ UC7BIT: Data length
▪ 0 8-bit data
▪ 1 7-bit data
▪ UCSPB: Number of stop bits
▪ 0 One stop bit
▪ 1 Two stop bits
▪ UCMODEx: USCI mode asynchronous mode (only valid when UCSYNC=0)
▪ 00 UART mode
▪ 01 Idle-line multiprocessor mode
▪ 10 Address-bit multiprocessor mode
▪ 11 UART mode with automatic baud rate detection
▪ UCSYNC: Synchronous/Asynchronous mode
▪ 0 Asynchronous (UART)
▪ 1 Synchronous (SPI)
The second control register, UCAxCTL1, USCI_Ax Control Register 1, configures the
USCI module in terms of clocking, enable, interrupts etc.

TI MSP430x2xx Family Reference Manual (SLAU144J)


▪ UCSSELx: USCI clock source selct
▪ 00 UCLK external clock source
▪ 01 ACLK
▪ 10 SMCLK
▪ 11 SMCLK
▪ UCRXEIE: Erroneous character received interrupt enable
▪ 0 Characters received with errors are dropped and no interrupt raised
▪ 1 Characters received with errors are retained and UCAxRXIFG is set
▪ UCBRKIE: Break character received interrupt enable
▪ 0 Receving a break character does raise an interrupt
▪ 1 Receiving a break character raises UCAxRXIFG
▪ UCDORM: Set USCI module to sleep mode (dormant)
▪ 0 Not in sleep mode
▪ 1 Sleep mode – certain characters can still raise an interrupt on UCAxRXIFG
▪ UCTXADDR: Transmit address marker – only valid for address-bit
multiprocessor mode
▪ 0 Next frame is data
▪ 1 Next frame is marked as an address
▪ UCTXBRK: Transmit break – all symbols in the transmission are low
▪ 0 Next frame is not a break
▪ 1 Next frame transmitted is a break
▪ UCSWRST: Module software reset – USCI is held in reset by default on power on or
device reset and must be cleared by software to enable the module
▪ 0 USCI operational – not in reset
▪ 1 Reset USCI module
Next we have the two baud rate control registers UCAxBR0 and UCAxBR1 as well as
the modulation control register UCAxMCTL. Sections 15.3.9 – 15.3.12 of the family
reference manual discuss how to calculate these values based on the desired baud rate.
However, TI has also provided us with a nice table in section 15.3.13 with suggested
values for commonly used baud rates and clock selections. To save us (and
the MSP430) some math, we will be using this table as a reference.
The UCAxSTAT register contains the status of the module.
TI MSP430x2xx Family Reference Manual (SLAU144J)
▪ UCLISTEN: Loopback (listen) enable. When enabled TX is fed into the RX
▪ 0 Loopback disabled
▪ 1 Loopback enabled
▪ UCFE: Framing error detect
▪ 0 No framing error detected
▪ 1 A frame with a low stop bit detected
▪ UCOE: Overrun error – a character was received and stored in UCAxRXBUF before it
was read by software (i.e. character is dropped). Must not be cleared by software
▪ 0 No overrun error detected
▪ 1 Overrun error detected
▪ UCPE: Parity error detect
▪ 0 No parity error detected
▪ 1 Parity error detected
▪ UCBRK: Break frame detect
▪ 0 No break frame detected
▪ 1 Break frame detected
▪ UCRXERR: Character received with an error. One or more other error bits will be set
when this bit is set. This bit is cleared by reading UCAxRXBUF
▪ 0 Character received does not contain an error
▪ 1 Character received contains error
▪ UCADDR: Address received – only in address-bit multiprocessor mode
▪ 0 Data received
▪ 1 Address received (address bit set)
▪ UCIDLE: Idle line detected – only in idle-line multiprocessor mode
▪ 0 Idle line not detected
▪ 1 Idle line detected
▪ UCBUSY: USCI module busy – either transmit or receive operation in progress
▪ 0 USCI not busy
▪ 1 USCI operation in progress
The SFR (special function register) IE2 contains the interrupt enable bits for the USCI
module.

TI MSP430x2xx Family Reference Manual (SLAU144J)


Note, the undefined bits may be used by other modules depending on the specific
device. See the device data-sheet for more information.
▪ UCA0TXIE: USCI_A0 transmit interrupt enable
▪ 0 Transmit interrupt disabled
▪ 1 Transmit interrupt enabled
▪ UCA0RXIE: USCI_A0 receive interrupt enable
▪ 0 Receive interrupt disabled
▪ 1 Receive interrupt enabled
The SFR IFG2 contains the interrupt enable bits for the USCI module.
TI MSP430x2xx Family Reference Manual (SLAU144J)
Note, the undefined bits may be used by other modules depending on the specific
device. See the device data-sheet for more information.
▪ UCA1TXIFG: USCI_A0 transmit complete interrupt flag
▪ 0 No interrupt pending
▪ 1 Interrupt pending
▪ UCA1RXIFG: USCI_A0 receive interrupt flag
▪ 0 No interrupt pending
▪ 1 Interrupt pending
Note that these values fields are only for USCI_A0. If there is a second USCI_A module
(USCI_A1), equivalent fields are in registers UC1IE and UC1IFG respectively.
To receive and transmit data respectively there are two 8-bit registers, UCAxRXBUF and
UCAxTXBUF. When the USCI is configured for 7-bit mode, the MSB of both of these
registers is unused. To initiate a transfer, the data is copied to UCAxTXBUF. This also
clears UCAxTXIFG (transmit complete interrupt flag). Once the transmission is
complete, UCAxTXIFG will be set. Similarly, when data is received on line, it is stored in
UCAxRXBUF and UCAxRXIFG (receive interrupt flag) is set. The data is held in this
register until it is read by software or another frame is received, in which case it is
overwritten and UCAxSTAT[UCOE] is set. When UCAxRXBUF is read by software,
UCAxRXIFG is cleared.
Registers UCAxIRTCTL, UCAxIRRCTL, UCAxABCTL are not required for standard
UART mode and therefore will not be covered in this lesson. The former 2 are for
infrared devices, while the latter is for UART with auto baud rate detection.
The code
For this tutorial we want to implement a UART driver with some simple APIs which can
be used to print a menu and accept user input. The goal is to create a menu which will
allow us to change the frequency of the blinking LED. We will not spend much time on
the implementation of the menu as it is not important for the purposes of learning how to
use the UART. Get the latest code from github to get started.
When programming for your desktop, there are plenty of ways using the standard library
to print and read from the console. The most commonly used is printf, however there are
others such as puts, putchar, and getchar which are more limited but simpler to
implement. Our UART driver will follow this model, however we do not have the concept
of stdin and stdout, file descriptors and all the rest that comes along with the actual
implementation. In fact, the standard C library we have as part of gcc (newlib), has the
full implementation, however it is too big (takes too much memory) for the
MSP430G2553. Try to use snprintf or printf and you will soon run of of space in the text
section (where the code goes). Perhaps it would fit on some of the bigger devices,
however in embedded programming, unless you are running a full blown OS such as
Linux, the standard C library is often specifically written only with the functionality
required. For example, printf may not support all the formatters, there are no actual file
descriptors and often it accesses the UART directly.
Before implementing the functions to read and write, we must initialize the USCI
peripheral. The UART configuration we will be using is 9600 8N1. The MSP430G2553
has one USCI_A module, so we will write a the driver specifically for it. Two new files
have been created, uart.c and uart.h located in the src and include directories
respectively. The function uart_init is implemented as follows:
1 int uart_init(uart_config_t *config)
2 {
int status = -1;
3
4 /* USCI should be in reset before configuring - only configure once */
5 if (UCA0CTL1 & UCSWRST) {
6 size_t i;
7
8 /* Set clock source to SMCLK */
UCA0CTL1 |= UCSSEL_2;
9
10 /* Find the settings from the baud rate table */
11 for (i = 0; i < ARRAY_SIZE(baud_tbl); i++) {
12 if (baud_tbl[i].baud == config->baud) {
13 break;
}
14 }
15
16 if (i < ARRAY_SIZE(baud_tbl)) {
17 /* Set the baud rate */
18 UCA0BR0 = baud_tbl[i].UCAxBR0;
19 UCA0BR1 = baud_tbl[i].UCAxBR1;
20 UCA0MCTL = baud_tbl[i].UCAxMCTL;
21
22 /* Enable the USCI peripheral (take it out of reset) *
23 UCA0CTL1 &= ~UCSWRST;
24 status = 0;
25 }
26 }
27
28 return status;
29 }
30
31
32
The function takes one argument of type uart_config_t from include/uart.h, which is for
the most part a placeholder structure for any attributes which need to be configured. For
now, the baud rate is the only member.
1 typedef struct
2 {
3 uint32_t baud;
4 } uart_config_t;
The baud rate must be defined as a 32-bit unsigned integer because as we learned
earlier, baud rates up to 115200 are common, and this integer value does not fit into the
native integer size of 16 bits.
The USCI module is held in reset by default. We can easily check if it has already been
initialized by checking the value of UCA0CTL1[UCA0CTL1]. It is important to keep the
USCI in reset until the configuration is complete and ready to communicate. Next the
USCI clock is set to SMCLK, which is 1MHz. To set the baud rate, we will use the table
from the reference manual. Rather than calculating the values for each register, which is
fairly complex and would be quite heavy mathematically for the MSP430, it is more
efficient to simply save the register values in a table that can be referenced for a given
baud rate. The table structure looks like this:
1 struct baud_value
2 {
3 uint32_t baud;
4 uint16_t UCAxBR0;
5 uint16_t UCAxBR1;
6 uint16_t UCAxMCTL;
7 };
Currently we will only support 9600 baud, since this is the maximum of the serial USB
interface of the Launchpad. Therefore the table will have only one entry as defined
below:
1 const struct baud_value baud_tbl[] = {
2 {9600, 104, 0, 0x2}
3 };
The initialization function will take the baud rate passed in the configuration structure
and iterate through the list of supported baud rates until a match is found. The register
values are copied from the table into the peripheral. The default register values for
USCA0CTL0 configure the device for 8 bit data, no parity and 1 stop bit, so no further
configuration is required. The module is taken out of reset and is ready to go.
A note on the above code: the ‘for’ loop iterates through the baud rate table using a
macro ARRAY_SIZE which is defined in a new file include/defines.h. This file will be the
default location to put any generic macros or hash defines. This particular macro makes
it very simple to calculate the size of an array. Since in C an array must have a defined
size at compile time, you can use the sizeof() operator to find number of bytes required
to store the whole array. Dividing this value by the size of one element in the array – by
convention we use the first one – gives the number of elements in the array. This value
will be determined at compile time so there is no runtime penalty for the division.
The first IO function we have is uart_getchar, which reads one character at a time from
the UART. If there are no characters to read, it returns -1, commonly referred to in *nix
talk as EOF (end of file). In this simple implementation, we will not implement any UART
interrupts since polling is not required. However, the interrupt flag IFG2[UCA0RXIFG]
can be read to determine if a character has been received. If it has, the character is read
from UCA0RXBUF.
1 int uart_getchar(void)
2 {
3 int chr = -1;
4
5 if (IFG2 & UCA0RXIFG) {
6 chr = UCA0RXBUF;
7 }
8
9 return chr;
10 }
The next function to implement is uart_putchar, to print a character to the console.
Before transmitting we have to check that the transmit buffer is ready – it has completed
the previous transmission – by reading the transmit interrupt flag IFG2[UCA0TXIFG].
When the interrupt flag is set, the USCI module is ready for more data. It is cleared
automatically by the hardware when the data is put into the transmit buffer UCA0TXBUF.
1 int uart_putchar(int c)
2 {
3 /* Wait for the transmit buffer to be ready */
4 while (!(IFG2 & UCA0TXIFG));
5
6 /* Transmit data */
7 UCA0TXBUF = (char ) c;
8
9 return 0;
10 }
Note, that this function can return before the transmission has completed. This is
efficient in the sense that while the UART is pushing out the data, the CPU has some
time to get the next piece of data ready or perform some other task. There is even more
efficient possibilities using interrupts, but we’ll cover that in a later lesson.
The final function is uart_puts, which is really just an extension of uart_putc that can print
a string rather than individual characters.The implementation is exactly the same as
uart_putc, except we iterate through the string until NULL is found, which indicates the
end of the string.
1 int uart_puts(const char *str)
2 {
3 int status = -1;
4
5 if (str != NULL) {
6 status = 0;
7
8 while (*str != '\0') {
9 /* Wait for the transmit buffer to be ready */
10 while (!(IFG2 & UCA0TXIFG));
11
12 /* Transmit data */
13 UCA0TXBUF = *str;
14
15 /* If there is a line-feed, add a carriage return */
16 if (*str == '\n') {
17 /* Wait for the transmit buffer to be ready */
18 while (!(IFG2 & UCA0TXIFG));
19 UCA0TXBUF = '\r';
20 }
21
22 str++;
23 }
24 }
25
26 return status;
27 }
There is one additional feature that I like to add for robustness. When writing to the
terminal in Linux, using ‘\n’ to create a new line is valid. However, it depends on the
terminal settings and may not always be the case. The character ‘\n’ is line feed
character. The terminology derives from the good old days of typewriters, which when
you press enter, the roller would move the paper up one line. However, the head also
has to return back to the start (left side) of the page. This is called a carriage return,
whose ASCII character representation is ‘\r’. These two characters together make what
is today commonly called a newline, which we do all the time by pressing the enter key.
In a terminal emulator however, such as Tera Term or minicom, they must both be
received (this can be sometimes be disabled), otherwise the text will continue from the
same position on the next line. For example, “HellonWorldn” would display like this:

To avoid having to use “\n\r” everywhere, we can make this function handle both, by
checking if the current character is a line feed and automatically adding a carriage
return.
It is important to note, we prefixed all these functions with uart_ not only because they
are part of the UART API, but because we do not want to conflict with the standard C
library routines. Depending on how the library is implemented, you may be able to
override some of the functions, but it can be unsafe and unpredictable. If you really want
to write a custom standard C library, there are linker options which can tell gcc to not
include them. This means however that none of the standard header files are accessible,
and therefore must all be redefined in your software.
The UART driver must now be integrated with our existing application. First we need to
add the initialization to the board.c file. In addition, the pin muxing of P1.1 and P1.2 must
be configured to USCI TX and RX. Below is an excerpt from the board_init function.
1 /* Set P1.3 interrupt to active-low edge */
2 P1IES |= 0x08;
3
4 /* Enable interrupt on P1.3 */
5 P1IE |= 0x08;
6
7 /* Configure P1.1 and P1.2 for UART (USCI_A0) */
8 P1SEL |= 0x6;
9 P1SEL2 |= 0x6;
10
11 /* Global interrupt enable */
12 __enable_interrupt();
13
14 watchdog_enable();
15
16 /* Initialize UART to 9600 baud */
17 config.baud = 9600;
18
19 if (uart_init(&config) != 0) {
20 while (1);
21 }
Next we can start modifying the main loop to create our menu. The implementation of
the menu isn’t all that important so we won’t go into much detail, but if you have any
questions about it feel free to ask. The important thing is to understand how the UART is
being accessed.
To build a menu, the API defined in include/menu.h provides a structure called
menu_item which contains the text and the callback of the each selection.
1 struct menu_item
2 {
3 const char *text;
4 int (*handler)(void);
5 };
The caller creates a list of menu items representing with the desired options and
callbacks. It is best to create this array as a static const, as typically we do not want it to
be modified. Then the array is passed into the function menu_init in src/menu.c, which
initializes the menu. This function will also display the menu.
1 void menu_init(const struct menu_item *menu, size_t count)
2 {
3 /* Limit menu size to 9 options */
4 if (count < 9) {
5 count = 9;
6 }
7
8 _current_menu = menu;
9 _current_menu_size = count;
10
11 display_menu();
12 }
To read the user input and make a selection, menu_run can be invoked. The function
does not block, meaning that if there is no user input, it will return immediately. This is
required for our application because we don’t want the menu to block all other
functionality. Internally, the function calls uart_getchar to read the characters received
from the UART. It accepts numbers only, and if the enter key is pressed, it will determine
if the value entered is within the limits of the menu and will execute the callback.
Whenever a character is received, it must be echoed back to the console, so that the
user can see what was typed. Otherwise, it will feel like they are typing into the abyss.
1 void menu_run(void)
2 {
3 static unsigned int value = 0;
4 int c = uart_getchar();
5
6 if ((c >= '0') && (c <= '9')) {
7 value *= 10;
8 value += c - '0';
9 uart_putchar(c);
10 } else if ((c == '\n') || (c == '\r')) {
11 if ((value > 0) && (value <= _current_menu_size)) {
12 /* Invoke the callback */
13 if (_current_menu[value - 1].handler != NULL) {
14 uart_puts("\n");
15 if (_current_menu[value - 1].handler() != 0) {
16 uart_puts("\nError\n");
17 }
18 }
19 } else {
20 uart_puts("\nInvalid selection\n");
21 }
22
23 display_menu();
24 value = 0;
25 } else {
26 /* Not a valid character */
27 }
28 }
One more API is provided more as a helper function for the callback functions,
menu_read_uint. Often a menu option itself will require user input, and in our case we
want to be able to input a frequency for the blinking LED. Unlike menu_run, this
functions is blocking but takes care of petting the watchdog. It will return the unsigned
integer value enter by the user.
1 unsigned int menu_read_uint(const char *prompt)
2 {
3 unsigned int value = 0;
4
5 uart_puts(prompt);
6
7 while (1) {
8 int c = uart_getchar();
9
10 watchdog_pet();
11
12 if ((c >= '0') && (c <= '9')) {
13 value *= 10;
14 value += c - '0';
15 uart_putchar(c);
16 } else if ((c == '\n') || (c == '\r')) {
17 uart_puts("\n");
18 break;
19 } else {
20 /* Not a valid character */
21 }
22 }
23
24 return value;
25 }
To put it all together, we can take a look at main.c. First we build the menu in the global
namespace with a single option, change the frequency of the blinking LED.
1 static const struct menu_item main_menu[] =
2 {
3 {"Set blinking frequency", set_blink_freq},
4 };
Then in our main() function we print out a welcome message using the uart_write()
function. Next the menu is initialized with our main menu, and it will be printed out the
terminal. Note that we use the macro ARRAY_SIZE here as well to pass in the number
of menu items.
In the existing while loop, we make a call to menu_run in order to continuously monitor
for user input. When the user selects option 1, the callback function defined in the main
menu, set_blink_freq, will be invoked.
1 static int set_blink_freq(void)
2 {
3 const unsigned int value = menu_read_uint("Enter the blinking
4
5 if (value > 0) {
6 _timer_ms = 1000 / value;
7 }
8
9 return (value > 0) ? 0 : -1;
10 }
The value returned from menu_read_uint is validated to make sure there is no dividing
by zero. Then the frequency entered is divided by 1000 to get the timer timeout period in
ms. The value is stored in a new global variable called _timer_ms. Even though this
variable is global, we do not have to disable interrupts as we have done with the timers
in the last lesson. It is only modified by the user in the callback, and read by the main
while loop. Therefore, the access is sequential and does not require a critical section or
a volatile identifier either. In addition, it is important to see how the variable is being used
to set the timer period. The timer API only permits the period to be set when it is created,
therefore to change the blinking frequency, the user has to stop and restart the the timer
using the push button.
1 int main(int argc, char *argv[])
2 {
3 (void) argc;
4 (void) argv;
5
6 if (board_init() == 0) {
7 int timer_handle = -1;
8
9 uart_puts("\n*********************************************
10 uart_puts("\nSimply Embedded tutorials for MSP430 Launchpa
11 uart_puts("\nsimplyembedded.org");
12 uart_puts("\nVersion: 0.9");
13 uart_puts("\n"__DATE__);
14 uart_puts("\n*********************************************
15
16 menu_init(main_menu, ARRAY_SIZE(main_menu));
17
18 while (1) {
19 watchdog_pet();
20 menu_run();
21
22 /**
23 * If blinking is enabled and the timer handle is
24 * negative (invalid) create a periodic timer
25 */
26 if (_blink_enable != 0 ) {
27 if (timer_handle < 0) {
28 timer_handle = timer_create(_timer_ms, 1, blin
29 }
30 } else {
31 if (timer_handle != -1) {
32 timer_delete(timer_handle);
33 timer_handle = -1;
34 }
35 }
36 }
37 }
38
39 return 0;
40 }
Note how the timer_create function now takes the variable _timer_ms rather than the
hardcoded value 500 as it did previously.
The setup
Since UART is relatively slow, it is sometimes implemented bit-banged using standard
GPIOs rather than with the USCI peripheral as we have. On the Launchpad, TI has
given us the option to use either software UART (bit-banging) or the hardware UART
(USCI) with some jumper settings on the board. They made some changes between rev
1.4 and 1.5 to facility this functionality, so the jumper settings between the two are
different. If your board is older than rev 1.4, I suspect it will be the same, but if not please
inform me.
In both cases, the board is shipped with the jumpers set for software UART, therefore
we have to change them. On the rev 1.4 boards, you will need some jumper cables,
since you need to cross the pins like this:
On rev 1.5, they made it a bit easier and you simply need to rotate the two jumpers 90
degrees as follows:
Now your hardware should be ready to go. When you connect your Launchpad to the
USB port on your computer, the device will enumerate as two classes: HID (human
interface device) required for the programming and debugging, and CDC
(communications device class) for the UART. In Windows, if you check in the device
manager, you will see that the device is not found. This is normal, and TI supplies
drivers for both channels (more on this later). On Linux (running as a host), the CDC
channel comes up as /dev/ttyACMx (where x is an integer value) and can be read
directly as if it were a regular serial port. However, connect the debugger using
mspdebug, and now you lost your serial connection. The way the debugger and serial
port were implemented on the Launchpad is somewhat flawed. What they tried to do is
valid, but for some reason it is unfortunately quite flakey, especially in Linux. Only one
can run at a time, which is a bit inconvenient, but what’s worse the CDC channel doesn’t
work at all in VirtualBox. I tried for days recompiling kernel modules, different setups
etc… with no luck. There are few options/workarounds which worked for me and you can
decide which is best for you.
Option 1: Running in a VM with Windows host using Tera Term in Windows for serial
If you have been following these tutorials from the beginning, you may have set up your
environment as I have, a Windows host and Linux guest running in VirtualBox.
Unfortunately, the workaround for this setup is the most clumsy of the options. I’m also
not the biggest fan because I prefer minicom (and Linux) over Tera Term, but it is fairly
reliable nonetheless. The other thing I don’t like about this option is that you have to
install drivers on Windows. I will show you how to do it as cleanly as possible.
1 Download the MSPWare package from TI’s website.Don’t donwload all of CCS, just
MSPWare. I was going to make the drivers easily accessible, but its under export
control so unfortunately that wasn’t an option. Install the package. It should create
a new directory under your C drive called ‘ti’.
2 Now open the device manager in Windows, and look for MSP430 Application UART. It
should be under ‘Other Devices’ since Windows can’t find the driver
3 Right click and select ‘Update Driver Software’, and in the prompt following, select
‘Browse my computer for driver software’
4 In the textbox on the next page, type in
C:timspMSPWare_2_00_00_41examplesboardsMSP-EXP430G2MSP-EXP430G2
Software ExamplesDrivers and click next
5 Once the driver is installed, it should appear under the ‘Ports’ section, and should be
assigned a COM port (mine is COM4 for example)
6 Download and install Tera Term
7 Open Tera Term and under the ‘Setup’ menu select ‘Serial’
1 Set the COM port to match what showed in the Device Manager
2 Set the baud rate to 9600
3 Set data to 8 bit
4 Set parity to none
5 Set stop bits to 1
6 Set flow control to none
8 Save this setup as default by selecting ‘Save Setup’ under the ‘Setup’ menu
You should now have serial access and see the menu print out in Tera Term. If you do
not see it, reset the device using S1 or press enter a few times. Now heres the trick to
this method. When you attach the Launchpad to VirtualBox, you will lose access to the
serial port, so close Tera Term first. Now in Linux, program debug etc.. as usual. If you
want to go back to serial, make sure mspdebug is closed, and unplug the Launchpad
from the USB port. Wait a few seconds, plug it back in and open Tera Term. You should
have serial access again.
Option 2: Linux host environment
If you are following along with a Linux host, minicom is my serial terminal of choice.
Minicom is all command line, so if you are not comfortable with that, then you can install
putty from the repositories. If you choose to use minicom and are having problems
setting it up, I can answer any questions you may have. Once you have your terminal
installed, you can plug in the Launchpad and open up /dev/ttyACM0 (or whatever port
yours came up as). You should see the serial output being printed at this time. Now if
you want to use the debugger, close minicom and open mspdebug. You should be able
to program and debug. If you want to go back to serial, you must close minicom, unplug
the device, wait a few seconds and plug it back in again before opening minicom.
Option 3: Use an external UART to USB converter
The pitfall with both of the previous options is that you cannot use mspdebug and access
the menu at the same time, making debugging difficult. This may not be an issue for now
since the code provided should work without modification, however it is ideal to have this
capability. To achieve this, you can use a UART to USB converter (this one
from Sparkfun is cheap and easy to use) or serial to USB converter with the MAX3232
(the 3.3V compatible version of the MAX232 – see the bread boarded picture from
above). With a UART to USB, you can simply remove the jumpers from the Launchpad
for the TX and RX lines, and connect the device straight onto the headers using some
jumper cables.
Testing the UART
Now that you have your device and PC all set up for UART, reset the device and take a
look at the menu. We have only one option for now (we will add to this in the future),
which will set the frequency of the blinking LED. Select this option and enter a frequency
of 2Hz. From the code described earlier, we know that this only sets a variable
containing the timer period. For it to take effect, you must use the push button to start
the blinking. Now select the menu option again and change the frequency to 4Hz. Stop
and restart the blinking. You should see the LED blink twice as fast. In the next lesson,
we will look at improving our UART driver to handle receiving characters even when the
CPU is busy doing other stuff.

Of the two signals on an I2C bus, one is the clock (SCL) and the other the data (SDA).
The clock is always driven by the master, while the data is bidirectional, meaning it can
be driven by either the master or the slave. Both signals are open drain and therefore
high impedance (think open circuit) unless driven. A pull-up resistor is required on each
line to pull it high in the idle state. When a device drives either of the lines, the open
drain output pulls the line low. This design has the advantage that it can support bus
arbitration without the chance of bus contention at the electrical level. In other words, if
two devices are driving the line, they will not physically damage each other. This is
especially useful in multi-master mode – which is defined by the standard – when there
are multiple masters communicating with the same or different slaves on the same bus.
Bus arbitration (which master has control of the bus) is supported by the physical
interface using the open drain design.
The disadvantage however, is that the the bus speed is limited especially over distance
and across multiple devices (the limiting factor is in fact capacitance – max 400pF).
Therefore, the speed originally specified in the I2C bus standard was 100kHz. Almost all
I2C devices will support this rate. However, because higher speeds are obviously
desirable, fast mode was introduced to increase the supported rate up to 400kHz. Most
devices support these two standard modes. There are higher speed modes as well, but
the speed of the bus is determined by the slowest device on the bus, as well as the PCB
design and layout.
The voltage levels of I2C are not defined by the specification. Instead it defines a high or
low symbol relative to Vcc. This makes the bus flexible in the sense that devices
powered with 5V can run I2C at 5V, while devices that run on 3.3V can communicate at
3.3V. The pitfall comes when devices are powered at different levels and need to
communicate with each other. You cannot connect a 5V I2C bus to a 3.3V device. For
this scenario the design would require a voltage level shifter on the I2C bus between the
two devices. Voltage level shifters specifically designed for I2C applications are
available.
The I2C protocol is defined by a set of conditions which frame a transaction. The start of
a transmission always begins with a START condition during which the master leaves
SCL idle (high) while pulling SDA low. The falling edge of SDA is the hardware trigger for
the START condition. Now all the devices on the bus are listening. Conversely, a
transaction is ended with a STOP condition, where the master leaves SCL idle and
releases SDA so it goes high as well. In this case, the rising edge of SDA is the
hardware trigger for the STOP condition. A STOP condition can be issued at any time by
the master.

I2C-bus specification and user manual (UM10204)


Immediately after the START, the master must send a single byte which comprises of
the device address and the read/write (R/W) bit. The device address is the first 7 bits
(the most significant bits) while R/W is always bit 0.

I2C-bus specification and user manual (UM10204)


It is important to remember that the address of a device is sometimes provided already
shifted left by one while the hardware may expect the unshifted address. Other times the
unshifted address may be provided, but the hardware expects the shifted address. This
is the most often error when a device does not respond.
The R/W bit indicates to the slave whether the master will be – well – reading or writing
to/from the slave device. It determines the direction of transmission of the subsequent
bytes. If it is set to read (high) the slave will be transmitting, while if it set to write (low)
the master will be transmitting.
The address is transmitted most significant bit first. A single bit is transmitted on SDA for
each clock cycle on SCL – therefore transmitting a byte takes 8 clock cycles. After each
byte is sent or received, the 9th clock cycle is reserved for the ACK/NACK
(acknowledge/not acknowledge) symbol. Depending on the transaction, an ACK can be
driven by the either the master or the slave. To signal an ACK, the device pulls SDA low
on the 9th clock cycle, while the other device reads the state of the line. If it is low, it is
interpreted as an ACK, if it left idle a NACK.

I2C-bus specification and user manual (UM10204)


For the case when the device address is transmitted, the slave device with the matching
address should respond with an ACK. If there is no device on the bus with a matching
address, the master will receive a NACK and should end the transaction.
Next comes the data. The data transmitted or received can be as small as one byte up
to as many bytes supported by the slave device. Typically a slave should NACK any
data byte if it unable to accept any more or if it is busy. The transmitting device must
stop transmitting upon receiving the NACK. Once the number of bytes requested has
been written or read, the master completes the transaction with the STOP condition.
The standard defines three transaction formats:
▪ Master transmitter slave receiver – the master sends the first byte with the R/W bit
cleared (write). All subsequent data bytes are transmitted by the master and
received by the slave.
▪ Master receiver slave transmitter – the master sends the first byte with the R/W bit set
(read). All subsequent data bytes are transmitted by the slave to the master.
▪ Combined format – effectively format 1 + 2 consecutively with no STOP condition in
the middle. Instead of the STOP, there is what is called a repeated START
condition which is exactly the same as a START but not preceded by a STOP.
The slave device
Before we move onto implementing the driver, lets take a look at the slave device we will
be communicating with. A very commonly used I2C device is the EEPROM. EEPROMs
are typically used to store small amounts of data which don’t change very often such as
serial number, hardware revision, manufacturing date, etc… The specific EEPROM we
will be using is the Atmel AT24C02D, a 2Kb EEPROM with up to 1 million write cycles
and can operate at speeds up to 1MHz. Keep in mind that memory devices are often
advertised in terms of bits and not bytes, so 2Kb = 2 kilobits, which is (2048/8) 512 bytes
– not that that much memory. Even though it has an endurance of 1 million write cycles
(meaning the device is guaranteed to be able to perform at least 1 million writes before
failing), writing to an EEPROM is quite slow so it is not really intended to be used to
store data at runtime.
One of the reasons I choose this device is because it is available in a DIP package so it
is easy to breadboard. Taking a look at the datasheet, we can see the package
description and pin layout for the DIP.

Atmel AT24C02D Datasheet (Atmel-8871E)


Pins 1 – 3 are the customizable address bits A0 – A2 for the I2C device address. The
top 4 bits (A3 – A6) are hard coded in the device (we’ll see what these are shortly). Let’s
say a hardware designer needs three EEPROMs on the same I2C bus. If they all had
the same address, all the devices would respond to every request – obviously not
desirable. These pins allow the designer to change the lower three bits of the address so
up to eight of these devices can coexist on the same bus. These pins are internally
pulled to ground inside the device, so we can leave them not connected unless you want
to change the address (although Atmel does recommend connecting them when
possible). Pin 4 is the ground pin, so that will be connected that to the ground rail. Pins 5
and 6 are the I2C lines – these will be connected to the MSP430’s SCL and SDA pins
which we will configure in software. However, as we learned previously, they require
pull-up resistors. Typical values for pull-up resistors on an I2C bus range from 4.7kOhms
to 10kOhms. We will use 4.7kOhms for this the breadboard circuit. Pin 7 is the write
protect signal. Because EEPROMs are often used to store static data that should never
change (i.e. a serial number or a MAC address), the designer can ensure the contents
cannot be overwritten by pulling the write protect line high in hardware. Since we want to
be able to write to the EEPROM, we will tie this pin to ground. Finally pin 8, Vcc, will be
connected to the Vcc rail. With all this connected so far, the breadboard looks like this:
Now let’s take a look at the device address in section 7 of the EEPROM datasheet.
Since we left pins A0 – A2 floating, the lower 3 bits of the address will be 0b000. The
datasheet specifies that the upper 4 bits will be 0xA (0b1010). This is the diagram
provided:

Atmel AT24C02D Datasheet (Atmel-8871E)


So your initial thought might be that we have to address the device as 0xA0. Although
this is what the datasheet implies and what is physically transmitted, it is not technically
correct. The actual device address should not include the R/W bit. Therefore it should be
shifted right by 1 making it 0b1010000 = 0x50. This will be important when we
implement the driver.
Most I2C EEPROMs typically support the same types of transactions. The master device
can either write to or read from the EEPROM. Writing to the EEPROM can take two
forms: byte write and page write. Both require that the first data byte is the address to
write to. For example, if the master wants to write to address 0x10, the first data byte will
be 0x10. This address is stored in the EEPROMs internal current address register. The
next data byte is the actual value to write to the EEPROM. Upon receiving this byte, the
EEPROM should respond with an ACK. If the master than sends a STOP, this
transaction is a byte write.

Atmel AT24C02D Datasheet (Atmel-8871E)


The master also has the option to continue sending data to the slave, until either the
EEPROM responds with a NACK – indicating it is busy – or a full page is written. This is
called a page write.

Atmel AT24C02D Datasheet (Atmel-8871E)


A page write need not transmit a full page however. It is up to a page of data that can be
written in a single transaction. In the case of the AT24C02D, the page size is 8 bytes.
After each byte is received, the current address register in the EEPROM is incremented
automatically. However, only the 3 least significant bits in the address will increment. If
the master sends more than 8 bytes, those bits will wrap around and the first address
will be overwritten. It is therefore important to limit each transaction to a maximum of 8
bytes and then a new transaction with the updated address (incremented by 8 each
time) be initiated. Note that both byte write and page writes are of the transaction format
1 – master transmitter slave receiver.
Reading data back from the EEPROM can be performed using one of 3 operations:
current address read, random address read and sequential read. The current address
read makes use of the EEPROM’s current address register to read the next address in
the device. Like writes, each byte that is read increments the current address. However,
instead of the address wrapping around a single page, it wraps across the whole device
memory space. After one byte is read, if the master issues a NACK it is done reading
data and it should subsequently send a STOP.
Atmel AT24C02D Datasheet (Atmel-8871E)
However, if the master responds with an ACK after the first data byte, it will be expecting
more data. This current address read becomes a sequential read, which basically means
that multiple bytes are read out of the slave device. The master will continue to ACK
each data byte until it is done receiving the number of bytes it requires. On the last data
byte it must respond with a NACK and then a STOP condition.

Atmel AT24C02D Datasheet (Atmel-8871E)


Notice that both current address read and sequential read (when following a current
address read) are in the transaction format 2 – master receiver slave transmitter.
But what if we need to read from a specific address – which is most often the case? This
is where message format 3 – combined format – comes into play with the random
address read. First the master transmits the device address with the R/W bit set to write
and one data byte containing the address to read from. Then it invokes a repeated
START condition, changes direction to read and reads a single byte from the EEPROM.
Atmel AT24C02D Datasheet (Atmel-8871E)
Sequential mode can also be applied to the random address read. Just like with the
current address read, instead of NACKing the first byte, the master continues to ACK
until it has read the desired number of bytes.
I2C with the USCI Module
On the MSP430, the peripheral which implements I2C is the USCI module. In previous
lessons, we looked at USCI_Ax which implements UART and SPI. The USCI_Bx module
implements I2C and SPI.
Let us review the USCI module registers, specifically those fields which apply to I2C.
Note that the same module is used to configure the MSP430 as a slave device, which
we will cover in another lesson. Those fields have been marked as such.
The first configurations register, UCBxCTL0, USCI_Bx Control Register 0, contains the
configuration for the protocol.

TI MSP430x2xx Family Reference Manual (SLAU144J)


BIT FIELD DESCRIPTION
7 UCA10 Address mode (slave only)
0b0: 7 bit address mode
0b1: 10 bit address mode
6 UCSLA10 Slave address mode
0b0: 7 bit address mode
0b1: 10 bit address mode
5 UUCMM Multi-master environment (slave only)
0b0: Single master environment
0b1: Multi-master environment
3 UCMST Master/slave mode
0b0: Slave mode
0b1: Master mode
2-1 UCMODEx USCI mode
0b00: 3-pin SPI (not valid for I2C)
0b01: 4-pin SPI STE=1 (not valid for I2C)
0b10: 4-pin SPI STE=0 (not valid for I2C)
0b11: I2C
0 UCSYNC Synchronous/Asynchronous mode
0b0: Asynchronous (Invalid for I2C)
0b1: Synchronous (SPI/I2C)
The second control register, UCBxCTL1, USCI_Bx Control Register 1, configures the
USCI module in terms of clocking and is used by the driver to generate the
START/STOP/ACK conditions.

TI MSP430x2xx Family Reference Manual (SLAU144J)


BIT FIELD DESCRIPTION
7-6 UCSSELx USCI clock source select
0b00: UCLK external clock source
0b01: ACLK
0b10: SMCLK
0b11: SMCLK
4 UCTR Transmitter/receiver mode – sets R/W
0b0: Receiver (read from the slave)
0b1: Transmitter (write to the slave)
3 UCTXNACK Transmit a NACK
0b0: Send ACK
0b1: Send NACK
2 UCTXSTP Generate a STOP condition
0b0: No STOP generated
0b1: STOP generated (automatically cleared upon completion)
1 UCTXSTT Generate a START condition
0b0: No START generated
0b1: START generated (automatically cleared upon completion)
0 UCSWRST USCI module software reset
0b0: USCI operational – not in reset
0b1: Reset USCI module
The UCBxSTAT register contains the status of the module.

TI MSP430x2xx Family Reference Manual (SLAU144J)


BIT FIELD DESCRIPTION
6 UCSCLLOW SCL line held low
0b0: SCL not held low
0b1: SCL held low
5 UCGC General call address received
0b0: No general call address received
0b1: A general call address was received
4 UCBBUSY Busy busy
0b0: Bus free – no transaction in progress
0b1: Bus busy – transaction in progress
3 UCNACKIFG Not acknowledged interrupt flag
0b0: No interrupt pending
0b1: Interrupt pending
2 UCSTPIFG STOP condition interrupt flag
0b0: No interrupt pending
0b1: Interrupt pending
1 UCSTTIFG START condition interrupt flag
0b0: No interrupt pending
0b1: Interrupt pending
0 UCALIFG Arbitration lost interrupt flag
0b0: No interrupt pending
0b1: Interrupt pending
The SFR IFG2 contains the interrupt status bits for the USCI module.

TI MSP430x2xx Family Reference Manual (SLAU144J)


BIT FIELD DESCRIPTION
3 UCB0TXIFG USCI_B0 transmit complete interrupt flag
0b0: No interrupt pending
0b1: Interrupt pending
2 UCB0RXIFG USCI_B0 receive interrupt flag
0b0: No interrupt pending
0b1: Interrupt pending
Note that the undefined bits may be used by other modules depending on the specific
device. See the device data-sheet for more information. Also, these fields are only for
USCI_B0. If there is a second USCI_B module (USCI_B1), equivalent fields are in
registers UC1IE and UC1IFG respectively.
Next we have the two baud rate control registers UCBxBR0 and UCBxBR1, the low and
high bytes which form the prescaler value. Well see how to configure these later in the
lesson.
The UCBxI2CSA is the slave address register. This is where the driver writes the
address of the slave and the hardware will automatically shift the address left by one bit
to accommodate the R/W bit.
To receive and transmit data there are two 8-bit registers, UCBxRXBUF and
UCBxTXBUF respectively. To send data (not including the device address byte), data is
written to UCBxTXBUF. This also clears UCAxTXIFG (transmit complete interrupt flag).
Once the transmission is complete, UCAxTXIFG will be set. Similarly, when data is
received on line, it is stored in UCAxRXBUF and UCAxRXIFG (receive interrupt flag) is
set. The data is held in this register until it is read by software. When UCAxRXBUF is
read by software, UCAxRXIFG is cleared.
Registers IE2 and UCBxI2COA, are only required for interrupt based drivers and slave
configuration respectively and therefore will not be covered in this lesson.
Implementing the driver
Now that we have a high level understanding of the USCI module register set in I2C
mode, let’s get coding. The I2C driver will be quite simple – it will not use interrupts at
this point, only polling – not the best implementation for several reasons covered
previously (blocking, power consumption, etc…) but it will suffice for learning the basics.
First we will start off with a simple initialization routine which will live in a new source file
src/i2c.c. This function will be responsible for configuring the features which will not
change during runtime.
1 int i2c_init(void)
2 {
3 /* Ensure USCI_B0 is in reset before configuring */
4 UCB0CTL1 = UCSWRST;
5
6 /* Set USCI_B0 to master mode I2C mode */
7 UCB0CTL0 = UCMST | UCMODE_3 | UCSYNC;
8
9 /**
10 * Configure the baud rate registers for 100kHz when sourcing
11 * where SMCLK = 1MHz
12 */
13 UCB0BR0 = 10;
14 UCB0BR1 = 0;
15
16 /* Take USCI_B0 out of reset and source clock from SMCLK */
17 UCB0CTL1 = UCSSEL_2;
18
19 return 0;
20 }
The module is setup for master mode I2C by setting UCMODEx to 0b11 (I2C), UCMST
to 0b1 (master) and UCSYNC to 0b1 (I2C is a synchronous protocol) in the UCB0CTL0
register. Everything else can remain at the default values as they are sufficient for our
use case.
Next the clock source is selected to be SMCLK by setting UCSSELx to 0b10 in
UCB0CTL1. Based on our clock module configuration, this means the USCI module will
be running at 1MHz. With the source clock frequency configured, we can now setup the
baud rate registers. The baud rate registers act a divider. We want the I2C bus to run at
the standard 100kHz, so the divider value must be 1MHz / 100kHz = 1000000 / 100000
= 10. Therefore, we only need to set the low byte UCB0BR0 to 10. Now that everything
is set up, finally we can take the USCI module out of reset by clearing UCSWRST in
UCB0CTL1.
The initialization function should be called from board_init along with the rest of the
hardware initialization. The pins need to be configured as well. From the pinout in the
device datasheet for the MSP430G2553, SCL and SCK are located on P1.6 is and P1.7
respectively.
TI MSP430G2x53 Datasheet (SLAS735J )
These two pins must be configured to work with the USCI block by setting the applicable
bits in P1SEL and P1SEL2 high. Recall the reason we put these pin configurations here
and not in the driver is to help isolate the driver implementation from the board specific
configuration. Now board_init should look like this:
1 [...]
2 /* Configure P1.1 and P1.2 for UART (USCI_A0) */
3 P1SEL |= 0x6;
4 P1SEL2 |= 0x6;
5
6 /* Configure P1.6 and P1.7 for I2C */
7 P1SEL |= BIT6 + BIT7;
8 P1SEL2 |= BIT6 + BIT7;
9
10 /* Global interrupt enable */
11 __enable_interrupt();
12
13 watchdog_enable();
14
15 /* Initialize UART to 9600 baud */
16 config.baud = 9600;
17
18 if (uart_init(&config) != 0) {
19 while (1);
20 }
21
22 if (i2c_init() != 0) {
23 while (1);
24 }
In a new header file include/i2c.h, we will define the I2C device structure which for now
only consists of the device address of the slave device. In the future it may include other
device specific parameters.
1 struct i2c_device
2 {
3 uint8_t address;
4 };
Next we will write the transfer function. The transfer function should require the device
structure as a parameter so that it can support multiple slave devices on the same bus. It
should also be able to handle all three transaction formats so it will require two buffers,
one to transmit and another to receive. It will also need to know the length of these
buffers. Instead of making this function take a huge argument list, we will define another
structure – i2c_data – in i2c.h which will encapsulate both transmit and receive buffers
and their respective sizes.
1 struct i2c_data
2 {
3 const void *tx_buf;
4 size_t tx_len;
5 void *rx_buf;
6 size_t rx_len;
7 };
Now the transfer function only takes two parameters, the i2c_device structure and
i2c_data structure.
1 int i2c_transfer(const struct i2c_device *dev, struct i2c_data *da
2 {
3 int err = 0;
4
5 /* Set the slave device address */
6 UCB0I2CSA = dev->address;
7
8 /* Transmit data is there is any */
9 if (data->tx_len > 0) {
10 err = _transmit(dev, (const uint8_t *) data->tx_buf, data-
11 }
12
13 /* Receive data is there is any */
14 if ((err == 0) && (data->rx_len > 0)) {
15 err = _receive(dev, (uint8_t *) data->rx_buf, data->rx_len
16 } else {
17 /* No bytes to receive send the stop condition */
18 UCB0CTL1 |= UCTXSTP;
19 }
20
21 return err;
22 }
The function begins by setting the slave device address in the UCB0I2CSA register. The
following transactions will therefore be directed at this device. To support all three I2C
transaction formats we need to first consider the transmit buffer. If there are bytes to
transmit, these are sent first, so check the size of the transmit buffer is greater than zero
– if so transmit the buffer. The actual writing of the buffer to the hardware is broken out
into a separate function for the sake of keeping functions small and readable. Once the
transmit is complete, and if there are no errors, then it’s time to see if the master needs
to read any data from the slave. If so, then call the receive function. If there are no bytes
to receive, then the transaction is complete and the master should issue the STOP
condition by setting UCTXSTP in the UCB0CTL1 register.
Let’s quickly verify how this covers all three I2C transaction formats.
Master transmitter slave receiver: The transmit buffer will have data and therefore the
length should be non-zero. Data will be transmitted to the slave. The receive buffer will
have a length of zero so master does not receive any data from the slave. Therefore
immediately after the transmit is complete the STOP condition will be set.
Master receiver slave transmitter: The transmit buffer will have a length of zero.
Therefore the transmit section of the function will be skipped. The length of the receive
buffer should be greater than zero and therefore the master will read that number of
bytes from the slave and then the STOP condition will be set.
Combined format: In this case both the transmit and receive buffers are greater than
zero. Start by transmitting the required number of bytes. If no errors have occurred, a
repeated START condition will be issued and the master will receive data from the slave.
Once that is complete, the STOP condition will be set.
Based on this quick analysis, we can see that this function will provide the flexibility
required to support all three I2C formats and therefore should support any I2C slave
device.
Now let’s take a look at how the driver transmits data from the master to the slave.
1 static int _transmit(const struct i2c_device *dev, const uint8_t *
2 {
3 int err = 0;
4 IGNORE(dev);
5
6 /* Send the start condition */
7 UCB0CTL1 |= UCTR | UCTXSTT;
8
9 /* Wait for the start condition to be sent and ready to transm
10 while ((UCB0CTL1 & UCTXSTT) && ((IFG2 & UCB0TXIFG) == 0));
11
12 /* Check for ACK */
13 err = _check_ack(dev);
14
15 /* If no error and bytes left to send, transmit the data */
16 while ((err == 0) && (nbytes > 0)) {
17 UCB0TXBUF = *buf;
18 while ((IFG2 & UCB0TXIFG) == 0) {
19 err = _check_ack(dev);
20 if (err < 0) {
21 break;
22 }
23 }
24
25 buf++;
26 nbytes--;
27 }
28
29 return err;
30 }
The transmission begins by setting the START condition. On the MSP430, this is done
by setting UCTXSTT in the UCB0CTL1 register. Since the master is transmitting data to
the slave, the UCTR bit needs to be set as well, which puts the USCI module in transmit
mode. The hardware will now set the START condition and send the first byte with with
I2C device address and R/W bit after which the UCTXSTT bit will be cleared and the
transmit interrupt flag UCB0TXIFG in IFG2 set. Before transmitting the data however, we
must check to make sure a slave acknowledged the initial byte. This is a common check,
so it has been broken out into its own function which we’ll take a look at in more detail
shortly. If the master received an ACK from the slave device, then it is safe to load the
first data byte into the transmit buffer. Again we wait until the transmit interrupt flag is set
and check for the ACK. The master must receive an ACK for every data byte before
transmitting the next one. A slave device may NACK additional data if it busy so
receiving a NACK would be an indicator to the master to stop transmitting. This cycle is
repeated until all the data has been transmitted (or the transaction is forced to stop by a
NACK). Notice at the end of the transmit function that we do not send a STOP condition
because there may be data to receive, in which case a there should be a repeated
START condition and not a STOP condition.
Next, let’s take a look at how the master receives data from the slave device.
1 static int _receive(const struct i2c_device *dev, uint8_t *buf, si
2 {
3 int err = 0;
4 IGNORE(dev);
5
6 /* Send the start and wait */
7 UCB0CTL1 &= ~UCTR;
8 UCB0CTL1 |= UCTXSTT;
9
10 /* Wait for the start condition to be sent */
11 while (UCB0CTL1 & UCTXSTT);
12
13 /*
14 * If there is only one byte to receive, then set the stop
15 * bit as soon as start condition has been sent
16 */
17 if (nbytes == 1) {
18 UCB0CTL1 |= UCTXSTP;
19 }
20
21 /* Check for ACK */
22 err = _check_ack(dev);
23
24 /* If no error and bytes left to receive, receive the data */
25 while ((err == 0) && (nbytes > 0)) {
26 /* Wait for the data */
27 while ((IFG2 & UCB0RXIFG) == 0);
28
29 *buf = UCB0RXBUF;
30 buf++;
31 nbytes--;
32
33 /*
34 * If there is only one byte left to receive
35 * send the stop condition
36 */
37 if (nbytes == 1) {
38 UCB0CTL1 |= UCTXSTP;
39 }
40 }
41
42 return err;
43 }
Receiving data requires the master to send the START condition and slave device
address byte but this time with the R/W bit cleared by clearing UCTR, putting the USCI
module in receive mode. UCTXSTT is set to start the transaction and once the first byte
is sent, UCTXSTT will be cleared by the hardware. Now the slave will begin sending
data, but in the case of master receive mode the ACK is driven by the master rather than
the slave. The master must NACK the last data byte it wants to receive. Otherwise, the
slave does not know to stop sending data and a bus error or device error may result. If
the receive buffer is only one byte, as soon as the first byte has finished transmitting, the
stop bit, UCTXSTP in UCB0CTL1, must be set. In master receive mode setting this bit
sends the NACK and then issues the STOP condition. The slave will still ACK the I2C
device address byte, so this must be verified by the master. Assuming the ACK was
received, the slave device will begin sending over data, each byte triggering the receive
interrupt flag. Once the flag is set the data received is in the UCB0RXBUF register and
can be read out. This is repeated for all data bytes until there is only one left to receive.
The master must NACK the last data byte and then issue a STOP condition, so before
receiving it we must set UCTXSTP.
Finally, let’s take a look at how to handle the N/ACK from the slave device.
1 static int _check_ack(const struct i2c_device *dev)
2 {
3 int err = 0;
4 IGNORE(dev);
5
6 /* Check for ACK */
7 if (UCB0STAT & UCNACKIFG) {
8 /* Stop the I2C transmission */
9 UCB0CTL1 |= UCTXSTP;
10
11 /* Clear the interrupt flag */
12 UCB0STAT &= ~UCNACKIFG;
13
14 /* Set the error code */
15 err = -1;
16 }
17
18 return err;
19 }
There are a few conditions in which the master might receive a NACK as we have seen.
It could be the address does not match, or the slave can no longer receive any data. To
check for a NACK, the NACK interrupt flag field, UCNACKIFG in the status register
UCB0STAT, should be read. When the master receives a NACK, it should abort the
transaction. Therefore, it must send a STOP condition and should clear the interrupt flag.
Using the driver to write and read data
Now that the driver is written, we can use it to store data to the EEPROM. Lets connect
the breadboard to the MSP430 LaunchPad. Vcc and ground are straightforward – they
are simply connected to the Vcc and ground pins on the LaunchPad. Pins P1.6 and P1.7
were configured for SCL and SDA respectively in board.c, so those pins can be
connected to the EEPROM pins 6 and 5 on the breadboard.

To test out our driver, we will create two new menu options to read and write a single
byte to the EEPROM. Currently they only support reading and writing one byte of data
but they could be extended to ask the user for a length, or you can modify the code to
change the size of the buffers.
1 static int eeprom_read(void)
2 {
3 int err;
4 struct i2c_device dev;
5 struct i2c_data data;
6 uint8_t rx_data[1];
7 uint8_t address;
8
9 dev.address = 0x50;
10
11 address = (uint8_t) menu_read_uint("Enter the address to read:
12
13 data.tx_buf = &address;
14 data.tx_len = sizeof(address);
15 data.rx_len = ARRAY_SIZE(rx_data);
16 data.rx_buf = (uint8_t *) rx_data;
17
18 err = i2c_transfer(&dev, &data);
19
20 if (err == 0) {
21 uart_puts("\nData: ");
22 uart_puts(_int_to_ascii(rx_data[0]));
23 uart_putchar('\n');
24 }
25
26 return err;
27 }
28
29 static int eeprom_write(void)
30 {
31 int err;
32 struct i2c_device dev;
33 struct i2c_data data;
34 uint8_t write_cmd[2];
35
36 dev.address = 0x50;
37
38 write_cmd[0] = menu_read_uint("Enter the address to write: ");
39 write_cmd[1] = menu_read_uint("Enter the data to write: ");
40
41 data.tx_buf = write_cmd;
42 data.tx_len = ARRAY_SIZE(write_cmd);
43 data.rx_len = 0;
44
45 err = i2c_transfer(&dev, &data);
46
47 return err;
48 }
In both cases the user is asked to enter the address. The read function points the
transmit buffer to the address and sets the length to 1 byte, which is standard for this
device (other EEPROMs or I2C devices that have a bigger address space may require
more than 1 byte for the address). The receive buffer points to the rx_data array, which
has been defined with one element. If you want to increase the number of bytes read,
the size of this array can be modified. The i2c_transfer function is called and and the
received data is printed out to the serial port. For example, try to read the data at
address 0x7 – here is a screenshot of the I2C transaction from an oscilloscope.

The blue trace is SCL and the yellow trace SDA. We can see the first byte being
transmitted is 0xA1 ((device address << 1) | write = (0x50 << 1) | 0x1 = 0xA1). On the
9th clock cycle, the SDA line is low, indicating that the EEPROM acknowledged the first
byte. Then the address to read from is transmitted. Over the next 8 clock cycles, the
SDA line toggles to 0b00000111 = 0x7. Again on the 9th clock cycle the EEPROM
acknowledges. Since a read is a combined format transaction, both SDA and SCL are
released high and the repeated START condition is issued. However, at the end of the
first image, you can see both lines are held low for quite some time. This is called clock
stretching and it is implemented by the hardware to delay the next byte in the
transaction. In this case, the EEPROM is saying ‘wait for me to retrieve the data!’. When
it is done, the master can continue clocking in the byte. Now the first byte is 0xA0
((device address) << 1 | read = (0x50 << 1) | 0 = 0xA0). The EEPROM acknowledges
once more and the next 8 clock cycles it transmits the data byte back to the master. In
this case the data at address 0x7 was 0xFF – the ‘erased’ value of an EEPROM. The
transaction ends with the STOP condition and both lines return to idle.
The write function is similar except that the user is also prompted for the value to write.
The transmit buffer is pointed to the write_cmd array which has a length of 2 bytes, one
for the address and the other for the data. Again, this could be increased in size to write
more data. The receive buffer isn’t set but the length must be set to 0 to indicate to the
driver there are no bytes to receive. If I now write the value 55 (0x37) to address 0x7, the
transaction will look like this:

The first byte being transmitted is 0xA1 ((device address << 1) | write = (0x50 << 1) | 0x1
= 0xA1). On the 9th clock cycle, the SDA line is low, indicating that the EEPROM
acknowledged the first byte. Then the address to write is transmitted. Over the next 8
clock cycles, the SDA line toggles to 0b00000111 = 0x7. Again on the 9th clock cycle
the EEPROM acknowledges and then the master starts to transmit the data and we can
see the SDA line toggle to 0b00110111 = 55. The transaction once again ends with the
STOP condition and both lines return to idle.
This test code is not really how an EEPROM would be accessed in practice but at least
we can test our interface, driver and hardware setup. In the next tutorial we will look
more at reading and writing from the EEPROM and demonstrate some real-life use
cases.
Share this:

Вам также может понравиться