Skip to content

Latest commit

 

History

History
233 lines (199 loc) · 11.4 KB

L02.md

File metadata and controls

233 lines (199 loc) · 11.4 KB
layout title license
default
Output device driver - LED

Introduction

This lab is concerned with device driver development for the LPC4088QSB development board.

Developing a device driver can be a complicated business. Even for a device as simple as a LED. This lab will require you to look at industry-standard documentation such as user manuals and schematics. This can be intimidating. But don't worry. You are not expected to understand everything straightaway. The point is to develop an appreciation of the complexity of the process of device driver development and sufficient understanding to make progress with the development of drivers of your own for very simple devices. The lab will also help to reinforce aspects of C programming that are essential for understanding OS implementation.

We'll begin by considering the steps required to develop a driver for LED1 on the LPC4088QSB board.

  1. Identify the hardware features of the microcontroller that are used to control the device
    A LED is an output device. It is a simple device that is either on or off. We can guess that it is associated with one of the GPIO (General Purpose I/O) pins of the LPC4088QSB. But which one? We could read the schematic. Find the LED1 pin on the schematic of the microcontroller. Notice that it is associated with P1.18. That means pin 18 in GPIO port 1. Actually, the pin is associated with several functions. We'll need to make sure that the GPIO function is selected for this pin.

  2. Understand how to control the relevant hardware features from software
    So now we need to know how to program the GPIO pins. We need to read the user manual for the LPC4088. Notice by looking at page 16 that the LPC4088 communicates with its peripheral devices (I/O devices) by mapping them into its own memory space. The devices are said to be memory-mapped. This is convenient for the programmer, who can control devices simply by assigning values to the device addresses as though they were ordinary variables. Note that some other processors, e.g. the Intel range, require the programmer to control I/O devices by working with a dedicated set of I/O ports using special read and write instructions. These devices are said to be I/O mapped (or port-mapped). We won't consider this approach in this module. There's more relevant information about programming the GPIO ports in Chapter 8 of the LPC4088 user manual, see p.146 in particular.

  3. The user manual tells us that the direction register for fast GPIO port 1 (DIR1) is at memory address 0x20098000 + 0x020. This register is used to determine, for each pin, whether it's an input pin (value 0) or an output pin (value 1). To control an LED we need an output pin. The manual also mentions a register (PIN1) that controls the value of the pins. Its address is 0x20098000 + 0x034.

  4. So, in order to configure pin 18 as an output pin, we need to set bit 18 of the direction register to the value 1. Once the pin is configured as an output pin, we can control the state of the pin by setting (assigning the value 1 to) or clearing (assigning the value 0 to) bit 18 in the PIN1 register. We should modify both of these registers without disturbing the value of the other bits in them. We need to understand how to manipulate individual bits in order to do this. Read the section on 'Common Bit Manipulation Techniques' in this Wikipedia article for more information. Once, we've gathered enough information from the documentation, we might try the following program to control the LED:

    #include <stdint.h>
    #include <stdbool.h>
    
    #define IOCON_P1_18 (*(volatile uint32_t *)(0x4002C0C8UL))
    #define GPIOBASE   (0x20098000UL)
    #define GPIO1DIR   (*(volatile uint32_t *)(GPIOBASE + 0x020))
    #define GPIO1PIN   (*(volatile uint32_t *)(GPIOBASE + 0x034))
    
    #define LED1PIN    (1UL << 18)
    
    void delay(uint32_t ms);
    
    int main() {
      IOCON_P1_18 = 0UL;      // configure as GPIO pin
      GPIO1DIR |= LED1PIN;    // set P1_18 as output pin
      while (true) {
        GPIO1PIN |= LED1PIN;  // set pin HIGH
        delay(1000);
        GPIO1PIN &= ~LED1PIN; // set pin LOW
        delay(1000);
      }
    }

Ask your tutor to explain this program carefully to you. You need to become familiar with all of its details.

In the lab

  1. Use the Start menu to run the git bash shell (you can just search for git bash and then left-click on the result). Clone the git repository for this lab by executing the following command in the git bash shell:

    $ git clone https://github.com/DavidKendall/blinky
    

We'll be using git throughout this module to manage our source code. It is easy to learn to use git for the simple source code management tasks that we need to perform. There is very good official documentation for git and also a useful guide to learning resources for git.

  1. Build and run the program by following these steps:
  • Start the Keil uVision5 IDE, select Project -> Open Project and browse to the blinky directory.
  • Open the file blinky.
  • Build the program by choosing the Build option from the toolbar (or by pressing function key <F7>).
  • Load the code to the target by choosing the Download option from the toolbar (or by pressing function key <F8>).
  • Choose the option Debug -> Start/Stop Debug Session. Wait patiently for the session to start.
  • Run the program by choosing the Run option from the toolbar.
  • Observe LED1 flashing approximately once per second.
  1. Now you're going to modify the program so that it also flashes LED3.

    • Choose the Debug -> Start/Stop Debug Session option to stop the debug session and return to the editor.

    • Refer to the schematic and find out which pin controls LED3.

    • Create a new branch in your repository to work on your modification, e.g.:

      $ cd blinky
      $ git checkout -b X03
      
    • Add the code required to flash LED3 at the same time as LED1. This should take exactly six lines of code. One line for each of the following steps:

      1. Define the address of the pin connect register (IOCON_...) for the pin.
      2. Define a mask for the LED3 pin.
      3. Configure the pin for LED3 as a GPIO pin.
      4. Configure the direction register for the pin so that it is an output pin.
      5. Add a line in the loop to make the pin go HIGH.
      6. Add a line in the loop to make the pin go LOW.
    • Build, run and observe your program as before.

  2. When you're satisfied with your solution to exercise 3, commit your changes to your repository and compare your solution with the one that we propose. Use the commands below in the git bash shell to do this, then reload the files into Keil uVision5.

    $ git add .                # add changes to the staging area
    $ git commit               # commit changes - you'll be prompted to enter a commit message
    $ git checkout P03         # checkout our solution to exercise 3
    
  3. Practice your understanding of the logical operators by modifying the solution to problem 3 so that you only use a single assignment statement for each of the following: setting the DIR register so that P1.13 and P1.18 are both outputs; setting both P1.13 and P1.18 to HIGH; and setting both P.13 and P.18 to LOW. The program should behave as before.

  4. Now simplify the body of the loop so that you toggle the state of both LEDs using a single compound assignment statement involving the XOR operator (^). The program should behave as before.

  5. Add the code that is needed to flash LEDs 2 and 4, so that your program now flashes all LEDs - you'll need to refer to the schematic to find the right pins. Make sure that you follow the example of the solution to exercise 6 for the selection of the GPIO function via the IOCON registers - you'll run into difficulties if you just assign 0 to IOCON_...

  6. Occasionally, the device hardware provides features intended to make life easier for the programmer. The GPIO devices on the LPC4088 fall into this category. In this case the feature that is provided is a pair of registers that allow the programmer to manipulate the value of individual bits in the PIN register without having to worry about disturbing the value of other bits. These registers are known as the SET and CLR registers. Writing a 1 into any bit position in the SET register causes the corresponding bit in the PIN register to go HIGH but (and this is the key point) writing a 0 into any bit position in the SET register has no effect. So, for example, we can set bit 13 in the PIN register with a simple assignment statement GPIO0SET = (1UL << 13); without being concerned that we'll affect the value of any other bits in the PIN register. Similarly, we can cause a bit in the PIN register to go LOW by writing a 1 to the corresponding bit in the CLR register, e.g. GPIO0CLR = (1UL << 13); causes bit 13 in the PIN register to go LOW while leaving the other bits unchanged (contrast this with the alternative approach GPIO0PIN &= ~(1UL << 13);). Modify your solution to exercise 7 so that the program has the same behaviour but uses the SET and CLR registers rather than using the XOR (^) operator on the PIN register.

  7. The electrical configuration of the board is such that for LEDs 1 and 2, the LED is turned ON when the corresponding GPIO pin is LOW (0), but for LEDs 3 and 4 the LED is turned ON when the corresponding GPIO pin is HIGH (1). This is an important lesson. Devices don't always behave in the way that you expect! Modify your program for exercise 8, so that all LEDs are turned ON simultaneously.

  8. Now add more code to your previous program to control the RGB-LED. Your program should behave as before except that, in addition, it should cycle through the colours RED, GREEN and BLUE on the RGB-LED, changing state every time LED1 .. LED4 are turned off. The RGB-LED is on the experiment base board. You'll need to refer to the schematic for this board in order to find out which GPIO pins to use to control it.

  9. Modify the delay() function by removing the word volatile from the declarations of i and j. Rebuild and run the program and observe how the behaviour of the program changes. Can you explain what's going on? After you've thought about this, read Use volatile judiciously to help your understanding. Why is this approach to implementing a delay function so poor?

If you want to find out a bit more about GPIO configuration, you should have a look at Understanding Microcontroller Pin Input/Output Modes. Remember devices don't come any simpler than this! Be grateful for your operating system.