Chinaunix首页 | 论坛 | 博客
  • 博客访问: 92507
  • 博文数量: 34
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 275
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-13 23:05
文章分类

全部博文(34)

文章存档

2011年(1)

2010年(7)

2009年(26)

我的朋友

分类:

2009-06-29 21:21:33

Lab Manual Section 2: LED Labs

2-0: Introduction to C for the MSP430

This lab is due on «TODO».

Objectives

  1. Have an understanding of how to use memory-mapped IO on output ports by turning on the device's LED in C.
  2. Learn how to compile C routines into object files and how to link object files into an ELF file.
  3. Learn how to transmit ELF files to the MSP430.

      Further Reading

      1. man gcc
      2. man ld
      3. info gcc
      4. info ld

          Memory on x86 Systems vs. Memory on the MSP430

          In this lab, you will learn how to compile C programs for the MSP430 microcontroller (often called an MCU, or a microcontroller unit). The following program turns on an LED wired to the least significant bit of an i/o port called "p1" mapped to memory address 0x21:

          int main (void) {
          	char* p1out;
          
          	/* Get a reference to output port 1 */
          	p1out = (char*) 0x0021;
          
          	/* Turn on the LED */
          	*p1out = 0x01;
          
          	for(;;) // loop forever 
          	  ;     // no such thing as "exiting" from an embedded controller
          }
          

          led.c, a simple program to turn the LED on on the MSP430. (Lab 2-0)

          Compiling and Linking for the MSP430

          The primary purpose of this lab is to introduce the tools used to cross-compile programs for the MSP-430 and to load them into memory.

          Note that a program development tool such as a compiler, assembler, debugger, etc. is just a program that can be potetially compiled for any computer. For example,the C compiler gcc you have been using that generates pentium code has been compiled to run on the pentium computers in our lab. We have instaled a full set of "cross tools" that run on our lab's pentium computers that generate and manipulate MSP-430 code. For convenience, we named them all with a MSP-430 prefix. For example:

          Native tool name Cross tool name
          gcc msp430-gcc
          as msp430-as
          gdb msp430-gdb
          objdump msp430-objdump
          nm msp430-nm

          Using these tools, you can compile led.c to an msp430 "elf" file (elf is a binary file format) as follows:

          $ ls
          led.c
          $ msp430-gcc -g led.c -c -o led.o
          $ ls
          led.c  led.o
          $ msp430-gcc led.o -o led.elf
          $ ls
          led.c  led.elf  led.o
          

          Use the program objdump to disassemble the code in led.elf using the following command to determine the instructions that set the least significant bit of port1, which should be a few instructions after the symbol main. Note the addresses of these instructions.

          $ msp430-objdump --disassemble led.elf
          

          Transferring ELF Files to the MSP430

          The ez-430's are USB dongles including a USB port that allows them to be connected to a computer. To get ready to load a program to the MSP430, there are a couple of steps you need to take:

          1. Plug in the MSP430 to a USB port.
          2. Open up a new terminal. The next command will run in the new terminal.
          3. Start msp430-gdbproxy:
            $ msp430-run-gdbproxy
            

              Back in our old terminal, let's now run gdb to debug our program:

              $ msp430-gdb led.elf
              GNU gdb 5.1.1
              Copyright 2002 Free Software Foundation, Inc.
              GDB is free software, covered by the GNU General Public License, and you are
              welcome to change it and/or distribute copies of it under certain conditions.
              Type "show copying" to see the conditions.
              There is absolutely no warranty for GDB. Type "show warranty" for details.
              This GDB was configured as "--host=i686-pc-linux-gnu --target=msp430".
              (gdb)
              

              In order to get gdb to speak to the "MSP430" target via USB, you must give it a few configuration commands:

              (gdb) setremoteaddresssize 64
              (gdb) setremotetimeout 999999
              (gdb) target remote localhost:2000
              Remote debugging using localhost:2000
              0x0000fc00 in ?? ()
              
              

              The "monitor erase" command erases the MSP-430's flash memory:

              (gdb) monitor erase all
              Erasing target flash - all... Erased OK
              (gdb)
              

              The "load" command tells gdb to load the MSP-430's memory with the program:

              (gdb) load
              Loading section .text, size 0x58 lma 0xfc00
              Loading section .vectors, size 0x20 lma 0xffe0
              Start address 0xfc00, load size 120
              Transfer rate: 960 bits in <1 sec, 30 bytes/write.
              (gdb)
              

              Gdb thinks that the MSP-430 is a paused program, so to run it, you can tell gdb to permit the proram to continue executing:

              (gdb) c
              Continuing.
              

              And now, the LED should be lit! (call your TA if it is not). You can tell GDB to take command again by typing a control-c (which should stop the program being executed).

              Assignment

              Your assignment is to familiarize yourself with the commands discussed in this lab and to modify led.c to flash the LED on and off. A couple of hints:

              • to turn the LED off, just write a zero to the least significant bit of the output port
              • to delay, just write a loop that counts up to some big number, perhas 50,000. Since the loop does nothing, the compiler might just optimize it away, but this can be prevented by declaring the count to be a volatile int.

              Grading Criteria

              1. Your program should blink the LED on and off when it's run. We don't care how fast.
              2. Your code should be syntactically correct.
              3. The TA will need to watch you compile, link, convert, and upload your program.
              4. The TA will need to observe a demonstration of your running program.

                  2-0: Introduction to C for the MSP430

          2-1: Introduction to Assembly

          Objectives

          1. Have an understanding of how to use memory-mapped IO on output ports by turning on the device's LED in C.
          2. Write an assembly program that will turn the MSP430's LED on.
          3. Learn how to assemble MSP430 assembly code into a .o file and how to link a .o file into a .elf file.

          Given

          1. Here is a sample assembly program demonstrating how to turn the MSP430's LED on:
            	.arch			msp430x110
            	.p2align		1,0
            	.text
            
            main:	.globl	main
            	; Set up port 1 for output
            	mov.b	#1,		&0x0022
            
            on:	; Turn on the LED
            	mov.b	#1,		&0x0021
            
            	; Loop forever
            loop:	jmp			loop
            
            off:	; Turn off the LED
            	mov.b	#0,		&0x0021
            

            Assembly demonstration program to be distributed to student. (Lab 2-1)

          Useful Advice

          • Assembly source files typically end in .s.
          • You can assemble programs (that is, go from a .s file to a .o file) using a command similar to the following:
            $ msp430-as led.s -o led.o
            
          • After assembling to a .o file, you need to link into a .elf file. You can do that with a command like this one:
            $ msp430-gcc led.o -o led.elf
            
          • After you have a .elf file, you can debug it just as before using gdb.
          • In the example code provided above, you'll see that we provide you with a label off. If you were to jump here, it would turn the LED off. However, note that it is unreachable in the program we provided you.
          • You could accomplish a delay loop in assembly using something similar to the following:
                    ; Sleep a while
                    mov     #0xffff,        r4
            sleep:  cmp     #1,             r4
                    jeq     done ; jump to done after we're done waiting
                    sub     #1,             r4
                    jmp     sleep
            

          Assignment

          1. Extend the program provided to blink the LED on and off in the pattern described as follows. We don't care how fast.
          2. We'd like that on-and-off pattern to be an SOS pattern. In case you don't know, that's a pattern like this:
            . . . ---   ---   ---   . . .
            
          3. The . symbols are known as dits and the --- symbols are called dahs. We'll say that a dit should be half the length of a dah.
          4. The SOS pattern should repeat indefinitely.

          Grading Criteria

          1. You must show your TA the instructions in main from led.c that turn the LED on and explain how they work.
          2. Your program should visibly blink the LED on and off when it's run. We don't care how fast.
          3. The TA will need to watch you cross-compile your program using make

          2-2: Function call/return in assembly

          Objectives

          1. Demonstrate understanding of GCC's MSP430 ABI by writing a function in assembler called from C.
          2. Have an understanding of the instruction timing by tuning a loop to meet timing goals.

          Further Reading

          1. MSP430-gcc Documentation: MSP430 ABI
          2. MSP430-gcc Documentation: MSP430 Function Calling Conventions

          Introduction

          In this lab, you will implement a program that flashes "SOS" with the following timing:

          • Short "dits" are 1/4 second on followed by 1/4 second of off.
          • Long "dahs" are 1/2 second on, followed by 1/2 second off.
          • Characters are separated by 1 second of off.
          • Words (such as SOS) are separated by 2 seconds of off.

          To do this, you will:

          • Write a subroutine in assembly language with the C signature:
            void millisleep(unsigned short delay); // no sense in negative delays!
            
            That loops for delay milliseconds, and then returns. For example:
            millisleep(500);
            
            Should delay for 1/2 second.
          • Write a main() (and additional functions if desired) in C that constitute a well designed program that flashes SOS repeatedly as specified above. Observe that to accomplish this lab, you will demonstrate competency in the following areas:
            • Callling an assembly langauge subroutine from C
            • Creating an accurate delay loop (say, 1ms) in assembly language
            • Creating nested loops in assembly langugae
            • Passing a 16 bit parameter
            • Sending simple morse code characters from C (say in a subroutine)
            • Sending the word SOS from C (say in a subroutine)
            • Doing this forever

          Prior to building this lab, you should assess the complexity and inter-dependency of each of these tasks and concisely document (in your README) the sequence that you will implement features and manner that you will test them. You MUST have a TA agree to this sequence prior to implementation. Your final README should also indicate the effectiveness of this strategy.

          2-3: Serial communication

          Objectives:

          1. Understanding serial communication.
          2. Construction of serial i/o infrastructure for future lab projects.
          3. Project design and testing skills.

          Assignment:

          In this assignment, students will construct a subroutine and all needed helper-functions to implement

          char print_serial(char c)
          

          That

          • transmits its argument c in RS-232, and
          • returns c

          This function may be written in C or assembly language, though it probably would be simpler to write in C with helper functions in assembly.

          It is acceptable to transmit characters at any common RS-232 format and baud rate, but we recommend:

          • 300 baud (bits per second)
          • 7 data bits
          • No parity
          • one stop bit

          A frame of an rs232 transmission is as follows:

          Each frame consists of the start bit 1, followed by 7 bits of the messages, which are inverted, followed by the stop bit of 0.

          Like the previous lab, students will be required to prepare and submit a design and test plan. We encourage a strategy where components are tested before integration. You may want to test the function that converts characters to RS-232 bit sequences using a native development environment so that you have access to console I/O. For example, you may want to print diagnostics:

          printing character 'A' = 0x41
          bits:
          (start) 1 
          0
          1
          0
          0
          0
          0
          0
          1
          (stop) 0
          

          If you follow this approach, you may want to put a copy of this test environment in a subdirectory with its own Makefile and document it in your README.

          Useful advice: The serial port line that goes from the MSP430 to the computer's serial port is at P1.7 on the MSP430. (The LED is connected to P1.0.)

          Note: You must test your routine using a serial terminal emulator. On the lab systems, we use minicom. You can find instructions for using minicom in .

          What you must hand in for grading

          1. design and test plan annotated with lessons-learned. This should be tuned outside of class.
          2. File(s) that contain well commented assembly (and C routines) that together implement print_serial().
          3. A demonstration "main" program in its own file that repeatedly
            1. transmits the characters "abcd" to the serial port followed by a carriage return ('\r') and a newline ('\n').
            2. waits 2 seconds
          4. A README.txt describing your implementation including all RS-232 parameters.

          Grading Criteria:

          1. Quality of development & test plan
          2. Quality of program including comments
          3. Program works.correctly

          2-4: Serial communication 2

          Objectives:

          1. Understanding serial communication.
          2. Realworld application of state machines.
          3. Understanding of interrupt driven code.
          4. Project design and testing skills.

          Assignment:

          In this assignment, we will provide you with interrupt-driven code to send enqueued characters to the serial port. As shown below, the provided program sets up the serial subsystem, and then slowly (and repeatedly) enqueues "hello" followed by a carriage-return & newline.

          The sample program contains:

          1. q.h, q.c: q circular queue subsystem. You may want to use the same subsystem for your recieve queue.
          2. clk0_handler.o: an assembly-language handler for the clock interrupt that calls the c function periodic_clock() in serial.c
          3. a serial subsystem (serial.c, serial.h) that transmits characters "put" into the "transmitQ".
          4. a "main" program that initializes the seral subsystem and then slowly sends "hello..."

          As shown below, your program must add additional functionality to:

          1. In an interrupt-driven manner, recieve characters and put them on a receive queue.
          2. In a main program, remove characters from the input queue, and then insert them twice into the transmit queue.

          This function may be written in C or assembly language, though it probably would be simpler to write in C, possibly with helper functions in assembly.

          Like previous labs, students will be required to prepare and submit a design and test plan. We encourage a strategy where components are tested before integration.

          If you follow this approach, you may want to put a copy of this test environment in a subdirectory with its own Makefile and document it in your README.

          What you must hand in for grading

          1. design and test plan annotated with lessons-learned. This should be tuned outside of class.
          2. File(s) that contain well commented assembly (and C routines) that implement the above functionality.
        • A README.txt describing your implementation including all RS-232 parameters.

            Grading Criteria:

            1. Quality of development & test plan
            2. Quality of program including comments
            3. Program works correctly
          1. 2-5: Using C Arrays and Pointers in Assembly

            This lab is due on Sunday, November 11, by 23:59:59.

            Objectives

            1. Understand how a program in C calls an assembly routine.
            2. Understand the relationship between arrays and pointers in C. Learn how to reference elements of C arrays in assembly.

            Assignment

            1. Modify the program you wrote in the previous lab so that it accepts a pointer to an array as a single argument instead of the two arguments currently being passed. The array (called x here) will contain two elements:
              1. x[0]: frequency
              2. x[1]: count
            2. The elements of this array are the same as the arguments to the function in the previous lab with one major exception: both elements will be of type unsigned char. This will limit the frequency of LED blinking to a range of 0 to 255 Hz.

            Grading Criteria

            1. Observe a demonstration of the running program presented to the TA.
            2. The TA will verify that the program works as expected and that changing the elements of the array being passed to the assembly routine has the expected effect on the blinking of the LEDs.
            3. The TA will verify syntactic correctness in the assembly and C code.

            Using struct in C

            This lab is due on Sunday, November 18, by 23:59:59.

            Objectives

            1. Introduce structs in C and learn how to use them.

            Assignment

            1. Modify the program you wrote in the previous lab so that it accepts a pointer to a C struct instead of a pointer to an array. The struct will contain two members:
              1. unsigned char brightness
              2. unsigned char count
            2. brightness will be a value between 0 and 255 indicating the brightness of the LED. For a value of 0, the LED should be off 100% of the time. For a value of 255, the LED should be on 100% of the time. Therefore, for a value of 127, for example, the LED should be on 50% of the time. This should allow the LED to operate at 50% brightness.
            3. It is up to you to determine a blink frequency that works well. count, as in previous labs, determines the number of blink cycles the LED will experience.

            Grading Criteria

            1. Observe a demonstration of the running program presented to the TA.
            2. Verification that the program works as expected and that changing the values of the members of the struct being passed to the assembly routine has the expected effect on the blinking of the LEDs.
            3. Verification that varying values of brightness changes the brightness of the LED as expected.

            2-7: Linked Lists and Queues in C

            This lab is due on Sunday, November 18, by 23:59:59.

            Objectives

            1. Learn how to create a struct in C that is suitable for use as a node in a linked list.
            2. Learn how to form a linked list out of multiple nodes.
            3. Learn how to create a stack using a linked list of struct nodes.

            Given

            1. A simple example program in C illustrating the use of a linked list:
              #include 
              
              /* A struct representing a node in a linked list */
              struct node {
                      char value;     
                      struct node* next;      
              };                              
              
              /* Our main function */
              int main (int argc, char** argv) {
                      int i;
              
                      struct node* node;
              
                      /* Allocate memory for our six nodes */
              	char memory[6][sizeof (struct node)];
              
              	/* Create pointers to six nodes */
              	struct node* list[6];
              
              	/* Map the regions of memory we created above to our linked list */
              	for (i = 0; i < 6; i++) {
              		/* Assign a chunk of memory to the struct pointer */
              		list[i] = (struct node*) memory[i];
              	}
              
                      /* Set values for each of our nodes */
                      list[0]->value = 'a';
                      list[1]->value = 'b';
                      list[2]->value = 'c';
                      list[3]->value = 'd';
                      list[4]->value = 'e';
                      list[5]->value = 'f';
              
                      /* Make each node point to the node after it */
              	for (i = 0; i < 5; i++) {
              		/* Make this node point to the one after it */
              		list[i]->next = list[i + 1];
              	}
              
                      /* Null-terminate our linked list */
                      list[5]->next = (void*) 0;
              
                      /* Begin at a, our head node */
                      node = list[0];
              
                      /* Loop through each node until we've reached the end of the list */
                      for (i = 0; node != (void*) 0; i++) {
                              /* Print this node's value */
                              putchar (node->value);
                              putchar ('\n');
              
                              /* Go to the next node */
                              node = node->next;
                      }
              
                      return 0;
              }
              

              Sample code demonstrating how to use a linked list with structs in C. (Lab 2-5)

            Assignment

            1. Implement a queue using a linked list in C. To do this, it is suggested that you create some helper functions:
              1. void enqueue (struct node* head, struct node* item): adds the node item to the end of the queue head.
              2. struct node* dequeue (struct node* head): removes the last node from the queue head and returns it. (What do you think this function should do if head is null?)
            2. Modify the program you wrote in the previous lab so that it accepts a pointer to the head node of a linked list as an argument. The program will operate exactly like the one you wrote in the previous lab except that it will operate the LED for each node of the passed linked list and will terminate when the end of the list is reached.
            3. After you're finished with everything else, for extra credit: If you implement your queue using the helper methods suggested, the entire queue must be traversed every time you enqueue or dequeue an item (i.e., O(n)). Can you think of a more efficient way to implement a queue (e.g., O(1))?

            Grading Criteria

            1. The TA will verify that your program works as expected and that modifying the passed linked list changes program operation as expected.
            2. The TA will verify syntactic correctness in your assembly and C code.
            3. Observe a demonstration of the running program presented by the TA

            2-8: Using Linked Lists, Queues, and struct in Assembly

            This lab is due on Sunday, November 18, by 23:59:59.

            Objectives

            1. Learn how to access regions of memory created in C from within HC11 assembly.
            2. Develop a further understanding of linked lists and structs.

            Assignment

            1. Write an assembly routine to traverse a linked list created in C code.
            2. The nodes in the linked list you create will contain two fields:
              1. unsigned char value, the number of times to blink the robot's LED
              2. struct node* next, a pointer to the next node in the linked list
            3. The assembly routine will traverse each node, starting from the head node, and continue until it reaches a null terminator.
            4. For each node the routine traverses, it will blink the robot's LED the specified number of times (in the value field of the struct).

            Grading Criteria

            1. Observe a demonstration of the running program presented by to the TA.
            2. The TA will verify that the program works as expected and that changing the values of the members of the struct being passed to the assembly routine has the expected effect on the blinking of the LEDs.
            3. The TA will verify syntactic correctness in the your assembly and C code.

            2.9: Resets and the Bouncing Switch

            Objectives:

            1. Understand the problem of the switch bounce and how to deal with it.
            2. Learn how the reset switch works on the robots.

            Suggest waiting 1s after reset for bounce to stop. Good solutions should work multiple times (clearing the count) from a single load of the program.

            Given:

            1. Every time you press the reset button, it bounces several times for a few milliseconds per bounce. This means that for every press of the reset button, the first few instructions of your program get executed with each bounce.
            2. The student may find it helpful to refer to previous labs.

            Assignment:

            1. The student will write a program in HC11 assembly to determine how many times the reset switch bounces with each press.
            2. The assembly routine should be called main so that it will be executed immediately after pressing the reset button.
            3. The robot will blink the LED a number of times that indicates the number of bounces.

            Grading Criteria:

            1. Observe a demonstration of the running program presented by the student.
            2. The TA willl verify that the program works as expected.
            3. The Ta will verify syntactic correctness in the your code.

            2.10: TODO: The section I wrote that's too long. I'm keeping this for other people's reference, but remove it before final release.

            Objectives

            • Have an understanding of how to use memory-mapped IO on PORTA by turning on the robot's LED in C.
            • Learn how to compile C routines into object files and how to link object files into an ELF file.
            • Learn how to convert ELF files into S19 files for the HC11 and how to load the S19 files to the HC11.

            Further Reading

            • man gcc
            • man ld
            • info gcc
            • info ld
            • man m6811load
            • The scripts in /opt/m68hc1x-gcc
            • This lab, again ;-)

            Memory on x86 Systems vs. Memory on the HC11

            In this lab, you will learn how to compile C programs for the HC11 microcontroller (often called an MCU, or a microcontroller unit) in the robots. But before we start with the robots, let's take a few minutes to look at this example program:

            int main (void) {
            	char* porta;
            
            	/* Get a reference to PORTA */
            	porta = (char*) 0x1000;
            
            	/* Turn on the LED.  On my robot, the LED is connected to the pins at
            	 * 0x10 on PORTA, but yours is probably different. */
            	*porta = 0x10;
            
            	return 0;
            }
            

            simple.c, a simple program to turn the LED on on the robot's HC11 MCU. (Lab 2.10)

            What does this program do, exactly? As you can see, it simply stores the value 0x10 at address 0x1000 in memory. On my robot, this is how you turn the LED on. But first, what would happen if we tried to run this program on a standard x86 computer? Let's try it:

            $ gcc simple.c -o simple
            $ ./simple
            Segmentation fault (core dumped)
            

            That's interesting. Why did we get Segmentation fault (core dumped) instead of no output, like we expected? The answer is relatively simple. On x86 systems running an operating system (in this case, Cygwin emulates a Linux-like operating system), when a program is executed, it is assigned a memory space by the operating system. When the program attempts to access memory locations outside of the range assigned to it by the operating system, the result, on UNIX systems, is to generate a signal called SIGSEGV. Signals are commonly used on UNIX systems to handle external conditions affecting the execution of programs. In this case, the program receives SIGSEGV from the operating system (Cygwin) and must terminate immediately. The shell (bash) detects that the process terminated with the SIGSEGV signal and prints the message Segmentation fault (core dumped).

            On an x86 system, of course, it is extremely unlikely that the operating system would assign a process a range of memory that includes the address 0x1000. This is why running our program under Cygwin gives us a segmentation fault.

            So what exactly is different between the way the HC11 microcontroller and standard x86 systems manage memory? Simple: the operating system. The simple robots do not run any operating system at all. When we compile and run programs on the robots, we have full access to the entire 65,536-byte memory addressing space of the HC11. On the x86 systems, on the other hand, the operating system prevents us from accessing any regions of memory that aren't strictly ours.

            Step One: Compiling

            You may not know it, but there are actually many steps required to turn your C source code into an executable. The first step is, no doubt, the one that you're most familiar with: compiling. Let's dig a little deeper into the stages of compiling.

            When you run gcc, the first step is running the code through the C preprocessor. Are you familiar with the commands that start with # in C, like #define, #include, and so on? These are called preprocessor directives. Let's run a simple demonstration program through the preprocessor to see what comes out; to do this, we use the -E switch with gcc:

            #include 
            
            int main (int argc, char** argv) {
            	puts ("Hello, world!");
            
            	return 0;
            }
            

            hello.c, a simple program that just prints a message to the screen. (Lab 2.10)

            $ gcc -E hello.c
            # 1 "hello.c"
            # 1 ""
            # 1 ""
            # 1 "hello.c"
            # 1 "/usr/include/stdio.h" 1 3 4
            # 28 "/usr/include/stdio.h" 3 4
            # 1 "/usr/include/features.h" 1 3 4
            
            ...
            
            # 842 "/usr/include/stdio.h" 3 4
            
            # 2 "hello.c" 2
            
            int main (int argc, char** argv) {
             puts ("Hello, world!");
            
             return 0;
            }
            

            As you can see, the preprocessor added quite a few lines to the simple hello.c. What do all of these lines do? That's beyond the scope of this course. Just know that they've been added because of the #include statement at the top of the code.

            For now, though, let's take a look at the second stage of compiling: the conversion from C to assembly, which is actually called compiling. Let's use the -S switch with gcc on our hello.c to see what kind of assembly output gets produced (we use the -o - switch to write the output to the standard output):

            $ gcc -S hello.c -o -
            	.file	"hello.c"
            	.section	.rodata
            .LC0:
            	.string	"Hello, world!"
            	.text
            .globl main
            	.type	main, @function
            main:
            	leal	4(%esp), %ecx
            	andl	$-16, %esp
            	pushl	-4(%ecx)
            	pushl	%ebp
            	movl	%esp, %ebp
            	pushl	%ecx
            	subl	$4, %esp
            	movl	$.LC0, (%esp)
            	call	puts
            	movl	$0, %eax
            	addl	$4, %esp
            	popl	%ecx
            	popl	%ebp
            	leal	-4(%ecx), %esp
            	ret
            	.size	main, .-main
            	.ident	"GCC: (GNU) 4.1.2 (Gentoo 4.1.2)"
            	.section	.note.GNU-stack,"",@progbits
            

            As you can see, gcc produced some assembly code. Of course, this is x86 assembly code, which you are not expected to understand. Just know that this small fragment of assembly code, when converted to machine code and executed on an x86 CPU, will print Hello, world! to the screen.

            This leads us to the final stage of compiling: assembly. To execute anything on a CPU, we first need to convert our program to machine code. This is exactly what the assembly stage of compiling does. To generate a file containing the binary machine code produced by assembling the assembly code above, let's use the -c switch with gcc, followed by running a utility called objcopy:

            $ gcc -c hello.c -o hello.o
            $ objcopy -j .text -O binary hello.o hello.bin
            $ hexdump -C hello.bin
            00000000  8d 4c 24 04 83 e4 f0 ff  71 fc 55 89 e5 51 83 ec  |.L$.....q.U..Q..|
            00000000  8d 4c 24 04 83 e4 f0 ff  71 fc 55 89 e5 51 83 ec  |.L$.....q.U..Q..|
            00000010  04 c7 04 24 00 00 00 00  e8 fc ff ff ff b8 00 00  |...$............|
            00000010  04 c7 04 24 00 00 00 00  e8 fc ff ff ff b8 00 00  |...$............|
            00000020  00 00 83 c4 04 59 5d 8d  61 fc c3                 |.....Y].a..|
            00000020  00 00 83 c4 04 59 5d 8d  61 fc c3                 |.....Y].a..|
            0000002b
            

            And there you have it, the exact binary code that the x86 CPU can understand. Feel free to take a look at hello.bin with a text editor if you're curious. You'll just see control characters that are interpreted as opcodes by the CPU. Of course, we have no way to actually execute this program in its binary format because we're running in an operating system. Because the operating system needs a format for executables that it can understand, we have the ELF format. On Linux systems, this is the standard executable format. (An in-depth description of the ELF format is well beyond the scope of this course. However, if you're interested in learning more about it, you are encouraged to research it yourself.) In fact, the hello.o file you created in the last step, called an object file, is in ELF format. Let's take a look at what it contains using the objdump utility:

            $ objdump -s hello.o
            
            hello.o:     file format elf32-i386
            
            Contents of section .text:
             0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
             0010 04c70424 00000000 e8fcffff ffb80000  ...$............
             0020 000083c4 04595d8d 61fcc3             .....Y].a..     
            Contents of section .rodata:
             0000 48656c6c 6f2c2077 6f726c64 2100      Hello, world!.  
            Contents of section .comment:
             0000 00474343 3a202847 4e552920 342e312e  .GCC: (GNU) 4.1.
             0010 32202847 656e746f 6f20342e 312e3229  2 (Gentoo 4.1.2)
             0020 00                                   .               
            

            That probably doesn't look like much to you. However, there are a couple of things you can see immediately. First of all, you can see that hello.o is divided into three sections: .text, .rodata, and .comment. You can also see that each of these sections contains some data. One thing to note, however, is that the HC11 will not use the .rodata section ("read-only data"); instead, it simply uses the .data section. Here is a brief summary of each of the sections you will encounter and what they are used for:

            • The .page0 section. This section is used to store the first 256 bytes of memory for the HC11. These first 256 bytes are somewhat special, as you will see later.
            • The .softregs section. This section is used to store software registers for the HC11. You'll learn more about these later.
            • The .data section. This section is used to store data used by your program. This essentially means that variables used in your programs will be stored in the .data section.
            • The .bss section. This section is used to store uninitialized variables.
            • The .text section. This section is used to store the executable code portion of your programs. Generally speaking, the vast majority of output ends up in this section.
            • The .vectors section. This section is used to store the special reset vector value. The reset vector tells the HC11 where to begin executing your program. We'll talk more about the reset vector later on.

            Now suppose we'd like to see a list of the routines contained in a compiled object file. Of course, we know that we wrote a single function, main (), in our hello.c routine. But let's see a list of the symbols defined in hello.o using the nm utility:

            $ nm hello.o
            00000000 T main
                     U puts
            

            We can see a couple of things here. First, we can see that our main () routine is defined in the .text section (which is why it has T next to it) and that it resides at address 0x00000000 in our hello.o file. Next, we see that somewhere in our hello.o file, there is a reference to something called puts. Since it has a U next to it, we know that it is an undefined symbol. This means that hello.o has no idea what puts is, just that it needs to look for puts during our next build stage, linking.

            Step Two: Linking

            Now you know how to create object files. But what do we do with all these object files? How do we put them together and make a single program? The answer, logically enough, is that we must link the object files into an executable. This process is typically called separate compilation, or more commonly, linking. Let's write a simple program to calculate the factorial of a number:

            #include 
            
            int main (void) {
            	long f;
            
            	/* Determine 12! */
            	f = factorial (12);
            
            	/* Print the result */
            	printf ("12! = %ld\n", f);
            
            	return 0;
            }
            

            main.c, a simple program that determines 12!, the factorial of 12. (Lab 2.10)

            long factorial (int n) {
            	if (n == 0) {
            		return 1;
            	} else {
            		return n * factorial (n - 1);
            	}
            }
            

            factorial.c, a simple routine to determine the factorial of a number. (Lab 2.10)

            Now, let's compile these two files into object files and take a look at the symbols they use with nm:

            $ gcc -c main.c -o main.o
            $ gcc -c factorial.c -o factorial.o
            $ nm main.o
                     U factorial
            00000000 T main
                     U printf
            $ nm factorial.o
            00000000 T factorial
            

            From the output of nm, we can see that main.o has only the main () routine defined and that it contains references to two other routines (these are more generically known as symbols): factorial, and printf. For now, don't worry about the function of the printf () function. It is beyond the scope of this course. However, we can see that the factorial () routine is defined in our factorial.o object file. As you would probably guess, this is the same factorial () function we are calling from main (). So, are you ready to link the two object files together to get an executable? Let's use gcc to do our linking:

            $ gcc -o factorial.elf main.o factorial.o
            $ ./factorial.elf
            12! = 479001600
            

            And there you have it, 12! = 479,001,600. At this point, hopefully you have a basic understanding of how we can link together multiple object files into a single executable. However, until now, everything we have been doing is for the x86 architecture, not for the HC11. The process is essentially the same for the HC11, but it does vary slightly. All the utilities you have been using (gcc, nm, objcopy, objdump, and so on) are also available for the HC11. The only difference is that you should prepend "m6811-elf-" to every command's name. That means you should use m6811-elf-gcc instead of gcc, for example.

            Let's compile our LED program again, but this time for the HC11; we'll also take a look at the object file with nm, and then objdump:

            int main (void) {
            	char* porta;
            
            	/* Get a reference to PORTA */
            	porta = (char*) 0x1000;
            
            	/* Turn on the LED.  On my robot, the LED is connected to the pins at
            	 * 0x10 on PORTA, but yours is probably different. */
            	*porta = 0x10;
            
            	return 0;
            }
            

            simple.c, a simple program to turn the LED on on the robot's HC11 MCU. (Lab 2.10)

            $ m6811-elf-gcc -c simple.c -o simple.o
            $ m6811-elf-nm simple.o
                     U _.frame
            00000000 T main
            $ m6811-elf-objdump -s simple.o
            
            simple.o:     file format elf32-m68hc11
            
            Contents of section .text:
             0000 de003c3c 9f00cc10 00de00ed 0118de00  ..<<............
             0010 cdee01c6 10e7004f 5fce0000 18381838  .......O_....8.8
             0020 18df0039                             ...9            
            Contents of section .comment:
             0000 00474343 3a202847 4e552920 332e332e  .GCC: (GNU) 3.3.
             0010 362d6d36 38686331 782d3230 30363031  6-m68hc1x-200601
             0020 323200                               22.             
            

            The next step, then, is to link our object file. But how will we link it? How does the linker know what it needs to do? To answer that, we need to take a look at a file called memory.x:

            /* We want to generate code for the HC11 */
            OUTPUT_ARCH(m68hc11)
            
            /* We want to generate an ELF binary for the HC11 */
            OUTPUT_FORMAT(elf32-m68hc11)
            
            /* Our stack will begin at 0xffbf, just below the vectors.  Remember, the stack
             * works upside down on the HC11 for some strange reason.  That means it "grows
             * down," not "up," the traditional way.  We want the stack to take up 0x100
             * bytes, from 0xfebf to 0xffbf. */
            _stack = 0xffbf;
            
            /* Begin executing at _start */
            ENTRY(_start)
            
            /* Define regions of memory */
            MEMORY
            {
            	ram	(rwx) : ORIGIN = 0x0000, LENGTH = 0x0100
            	mem     (rx)  : ORIGIN = 0x8000, LENGTH = 0x8000
            	stack   (rw)  : ORIGIN = 0xfebf, LENGTH = 0x0100
            	reset   (rw)  : ORIGIN = 0xfffe, LENGTH = 0x0002
            }
            
            /* Define the sections of our output object file */
            SECTIONS
            {
            	/* Put page0 in RAM */
            	.page0 : { *(.page0) } > ram
            
            	/* Set up virtual software registers in RAM */
            	.softregs : { *(.softregs) } > ram
            
            	/* Save our data section */
            	.data : { *(.data) } > mem
            
            	/* Next, our bss */
            	.bss    : { *(.bss) } > mem
            
            	/* And finally, code goes in the memory */
            	.text   : { *(.text) } > mem
            
            	/* Insert our reset vector at 0xfffe */
            	.vectors : { *(.vectors) } > reset
            }
            

            memory.x, the file used to tell the linker what to do. (Lab 2.10)

            You're not expected to understand anything in memory.x. The important thing is for you to understand that it tells the linker the following important things:

            • Which architecture to link for.
            • Where, in memory, the stack is located.
            • Where the HC11 should begin executing. This is the symbol called _start.
            • Which regions of the HC11's memory space are to be used for what purpose.
            • Where, in memory, to put the data contained in each section of the object files begin linked.

            As mentioned above, the linker needs to know where to begin executing. This is the symbol called _start. But, as you may have noticed, we did not define any symbol with that name in our simple.c program. If we had, it would have showed up when we ran nm. To deal with this, we actually need to link in another object file, crt0.o. The purpose of this object file is simple: it defines the _start symbol, sets up the HC11 to prepare to execute your program, and then begins executing your program. crt0.o performs some other functions, as well. However, these aren't important for the time being. (In case you're curious, "crt" stands for "C runtime.")

            Let's take this opportunity to explain something about how we linked the x86 sample program earlier. If you remember, we used gcc to perform the linking, providing the names of our object files as arguments. However, there is a special program called ld that is often used to link object files and is better suited to our purposes. The reason we didn't use ld in the previous x86 example is because for the x86 architecture, the dependencies for linking far outnumber those needed for the HC11 architecture. Therefore, we used gcc instead of ld in the example because it takes care of many of these problems automatically. However, for the HC11, since we have everything we need, specifically memory.x and crt0.o, we will be using ld to link object files.

            So, to recap, we know we need to do two things before we can link object files for the HC11. First, we need to specify the correct memory.x file, so the linker knows how to link the object files. Second, we need to link in crt0.o so the _start symbol is defined. Just so you get a feel for how crt0.o works, let's try linking our simple.o, first without crt0.o, then with it; note that we must define the path to memory.x using the -T switch:

            $ m6811-elf-ld -T /opt/m68hc1x-gcc/m6811-elf/lib/ldscripts/cs3432.x \
              -o simple.elf simple.o
            m6811-elf-ld: warning: cannot find entry symbol _start; defaulting to 00008000
            simple.o(.text+0x1): In function `main':
            : undefined reference to `_.frame'
            simple.o(.text+0x5): In function `main':
            : undefined reference to `_.frame'
            simple.o(.text+0xa): In function `main':
            : undefined reference to `_.frame'
            simple.o(.text+0xf): In function `main':
            : undefined reference to `_.frame'
            simple.o(.text+0x22): In function `main':
            : undefined reference to `_.frame'
            $ m6811-elf-ld -T /opt/m68hc1x-gcc/m6811-elf/lib/ldscripts/cs3432.x \
              -o simple.elf \
              /opt/m68hc1x-gcc/lib/gcc-lib/m6811-elf/3.3.6-m68hc1x-20060122/crt0.o \
              simple.o
            

            There are a couple of important things to note about this. Most obviously, these are extremely long and difficult-to-remember commands. Don't worry, though, because as you'll soon see, we don't expect you to remember these long commands.

            The other important thing to note is the error output from the first command, without linking in crt0.o. You can see that _start is not defined. Without defining _start, programs will never execute properly on the HC11. The other error we see is that _.frame is not defined. _.frame is one of the soft registers that crt0.o defines (namely, the frame pointer). The function of _.frame isn't really important right now; just know that it's something that crt0.o provides so we can link properly.

            You'll see now, if you execute ls, that there's a simple.elf executable. This executable, however, is built to run on the HC11, not the x86 architecture. Let's try to execute it, just to see the error that gets produced:

            $ ./simple.elf
            ./simple.elf: ./simple.elf: cannot execute binary file
            

            If you recall, as mentioned earlier, the ELF format is used by the operating system (Linux, for example) to execute programs from within the operating system. Obviously, since the HC11 has no operating system, the ELF format isn't very well suited to run on the HC11. Instead, we have to convert the ELF file to a format better suited to the HC11's minimal system constraints.

            Step Three: Conversion from ELF Format to S19 Format

            Motorola has developed a special format, called S19 format, that can be used to store executable code. It is comparable to the ELF format, but is a highly specialized format for use on the HC11. The specifics of the S19 format are beyond the scope of this course, but you are encouraged to research it yourself if you are interested.

            Before we can try out our simple.c program, we need to convert the ELF file to an S19 file. To do this, we can use the objcopy utility:

            $ m6811-elf-objcopy -O srec -j .text -j .data -j .bss -j .vectors simple.elf \
              simple.s19
            $ cat simple.s19
            S00D000073696D706C652E7331395D
            S11380008EFFBFBF00004F06BD800D20FEDE003C8A
            S11380103C9F00CC1000DE00ED0118DE00CDEE0127
            S1138020C610E7004F5FCE00001838183818DF007C
            S10480303912
            S105FFFE80007D
            S90380007C
            

            This command instructs the objcopy utility to convert the data contained in the ELF file to S19 format (called S records), keeping only the sections of the executable that are important. Now, the next step, of course, is to get the S19 file to the robot.

            Step Four: Loading S19 Files

            The robots, as you may know, can be connected to a computer. The robots have a serial communications interface (SCI) that allows the robot to be connected to a computer's serial port. The HC11 has a small program built into its ROM that allows you to upload a program to execute on the HC11. This sounds like a great way to load our program, but there is a major problem: this program in the HC11's ROM will only allow the uploading of programs up to 256 bytes in size.

            Of course, this is a problem. Since the robots have 32 kilobytes of memory, it doesn't make very much sense that our program size is limited to 256 bytes. So, we found a clever way around this. We simply created a loader program (which is far below 256 bytes in size) that reads in your program, in S19 format, over the serial port, and stores it in the full 32-kilobyte memory space of the robot.

            To get ready to load a program to the robot, there are a couple of steps you need to take:

            • The robot needs to have power. Make sure that the batteries are adequately charged.
            • Confirm that the left switch is in the "Download" position, not the "Run" position.
            • Connect the serial cable to the computer. If there is a good connection between the serial board and the computer, an LED will turn on on the serial board.
            • Connect the serial cable to the robot. To make sure you have the serial ribbon cable connected to the robot in the correct direction, hold down the reset button. If the other LED lights up while you are holding down the reset button, then you have the cable connected the right direction. Otherwise, the cable is backwards; reverse it and try again.
            • Determine the COM port number that the robot is connected to. If you're using a "real" serial port on the computer (that is, a built-in serial port), then it is probably COM1 or COM2. If you're using a USB-to-serial adapter, then you'll need to determine the COM port number assigned to the adapter. Use the USB-to-serial guide for details on that (see "Further Reading" above).

            Let's load our S19 file to the robot now (of course, replace /dev/com1 in the example with your proper COM port):

            $ m6811load -p /dev/com1 simple.s19
            m6811load version 1.0.1
            Loaded 256-byte loader program /usr/share/m6811load/loader.bin
            Read 206 bytes from simple.s19
            Found 51 bytes of executable code in S19
            Converted simple.s19 to HC11 machine code
            Opened serial port /dev/com1 at 1200 baud
            Gave the download command to the HC11
            Bootstrapping the loader program
            Error: Handshake failure: Sent CE but got 00 back
            

            You can see that, for me, it didn't work this time. It's no problem, we just need to press the reset button on the robot and try again:

            $ m6811load -p /dev/com1 simple.s19
            m6811load version 1.0.1
            Loaded 256-byte loader program /usr/share/m6811load/loader.bin
            Read 206 bytes from simple.s19
            Found 51 bytes of executable code in S19
            Converted simple.s19 to HC11 machine code
            Opened serial port /dev/com1 at 1200 baud
            Gave the download command to the HC11
            Bootstrapping the loader program
            Bootstrapping finished successfully
            Serial port closed
            Opened serial port /dev/com1 at 9600 baud
            Gave the download command to the loader program
            Loader program is accepting data
            Loading finished successfully
            Serial port closed
            Finished.  Now reset the HC11 in expanded mode to run your program.
            

            There we go. After a brief delay, the program was loaded with no problem. Now, let's run the program. Move the left switch to the "Run" position and press the reset button.

            Nothing happened, right? Well, this is your assignment for Lab 2.10. You need to figure out which pins on PORTA your robot's LED is connected to. On my robot, the LED is connected to 0x10, but it's probably different on your robot, so you'll need to figure which value (instead of 0x10) to use to get your LED to light up. But before you get started, there are certainly some more utilities you'll want to learn about.

            Making Life a Little Easier

            You have probably noticed that most of the commands you've learned about in this lab are extremely long. Don't worry, you're not expected to memorize these commands. Instead, we just expect you to know what the commands do. To make your life quite a bit easier, we've provided a few scripts you can use. Let's try them out. First, clean out the files in your temporary directory so you can start fresh. Make sure you keep a copy of simple.c, though. Now, follow along:

            $ gel-compile simple.c
            $ ls
            simple.c  simple.o
            $ gel-link simple.elf simple.o
            $ ls
            simple.c  simple.elf  simple.o
            $ gel-elf2s19 simple.elf
            $ ls
            simple.c  simple.elf  simple.o  simple.s19
            $ gel-dump simple.elf
            
            simple.elf:     file format elf32-m68hc11
            
            Disassembly of section .text:
            
            00008000 <_start>:
                8000:	8e ff bf    	lds	#ffbf <_stack>
                8003:	bf 00 00    	sts	0 <_.frame>
                8006:	4f          	clra
                8007:	06          	tap
                8008:	bd 80 0d    	jsr	800d 
            800b: 20 fe bra 800b <_start+0xb> 0000800d
            : 800d: de 00 ldx *0 <_.frame> 800f: 3c pshx 8010: 9f 00 sts *0 <_.frame> 8012: ce 10 00 ldx #1000 8015: c6 10 ldab #16 8017: e7 00 stab 0,x 8019: 4f clra 801a: 5f clrb 801b: 38 pulx 801c: df 00 stx *0 <_.frame> 801e: 39 rts

            As you can see, the gel-compile script compiled simple.c into simple.o, the gel-link script linked simple.o into simple.elf, and the gel-elf2s19 script created simple.s19 from simple.elf. You can also see that running gel-dump on the ELF file produces a disassembly of the code in the ELF file. Perhaps this doesn't seem very useful to you right now, but it will definitely be useful later on.

            These scripts should make it significantly easier for you to write HC11 programs. For a brief summary of how to use each script, run it with no arguments (for example, just run gel-link for usage information). You are encouraged to view the contents of these scripts to learn more about how they work. They're located in /opt/m68hc1x-gcc.

            Assignment

            Your assignment is to familiarize yourself with the commands discussed in this lab and to modify simple.c to turn on your robot's LED.

            While this lab is very long, we hope that you found it very useful and informative.

            Grading Criteria

            • Your program should turn the LED on when it's run. This means that the value you store in PORTA needs to actually turn on the LED.
            • Your code should be syntactically correct.
            • The TA will need to watch you compile, link, convert, and upload your program.
            • The TA will need to observe a demonstration of your running program.
            阅读(1421) | 评论(0) | 转发(0) |
            给主人留下些什么吧!~~