Computer Labs: The i8254 Timer/Counter
2o MIEIC
       Pedro F. Souto (pfs@fe.up.pt)
            September 27, 2018
Lab 2: The PC’s Timer/Counter - Part I
 I Write a set of functions:
     int timer_test_read_config(uint8_t timer,
                                enum timer_status_field field)
     int timer_test_time_base(uint8_t timer, uint32_t freq)
   that require programming the PC’s Timer/Counter
 I These functions are at a high level for pedagogical reasons
     I The idea is that you design the lower level functions (with the final
       project in mind)
     I In this lab we have also defined the lower level functions
 I What’s new?
     I Program an I/O controller: the PC’s timer counter (i8254)
     I Use interrupts (Part II)
The i8254
   I It is a programmable timer/counter
        I Each PC has a functionally equivalent circuit, nowadays it is
          integrated in the so-called south-bridge
        I Allows to measure time in a precise way, independently of
          the processor speed
    I It has 3 16-bit counters, each of
      which
        I May count either in binary or
          BCD
        I Has 6 counting modes
i8254 Counting Modes (4 of 6)
  Mode 0 Interrupt on terminal count – for counting events
     I OUT goes high and remains high when count reaches 0
  Mode 1 Hardware retriggerable one-shot
     I OUT goes low and remains low until count reaches 0, the
       counter is reloaded on a rising edge of the ENABLE input
  Mode 2 Rate Generator (divide-by-N counter)
     I OUT goes low for one clock cycle when count reaches 0,
       the counter is reloaded with its initial count afterwards,
       and ...
  Mode 3 Square Wave Generator – for Lab 2
     I Similar to mode 2, except for the duty-cycle: OUT will be
       high for half of the cycle and low for the remaining half of
       the cycle
i8254 Block Diagram
                      I Three independent 16-bit
                        counters
                          I Ports 40h, 41h and 42h
                          I MSB and LSB addressable
                            separately
                          I independent counting modes
                      I An 8 bit-control register
                          I Port 43h
                          I Programming of each counter
                            independently
i8254 Control Word
    I Used to program the timers
    I Written to the Control Register (0x43)
                                            Example
   Bit     Value        Function
   7,6              Counter selection        I Timer 2 in mode 3
             00              0
             01
             10
                             1
                             2
                                             I Couting value: 1234 = 0x04D2
   5,4             Counter Initialization
             01             LSB
             10            MSB              Control Register: 10110110
             11    LSB followed by MSB
   3,2,1             Counting Mode             I "NOTE: Don’t care bits (X)
            000              0
            001              1                    should be 0 to insure
            x10              2
            x11              3                    compatibility with future Intel
            100              4
            101              5                    products."
   0                        BCD
              0
              1
                     Binary (16 bits)
                      BCD (4 digits)
                                            Timer2 LSB 0xD2
                                            Timer2 MSB 0x04
  How to assemble the control word?
How to assemble the control word?
 Use bitwise operations
 Use the macros defined in i8254.h
i8254: Read-Back Command
  The command                                     Read-Back Command Format
                                            Bit    Value           Function
  I Allows to retrieve                      7,6              Read-Back Command
                                                      11
       I the programmed configuration       5                       COUNT
                                                       0      Read counter value
       I and/or the current counting        4                       STATUS
         value                                         0    Read programmed mode
                                            3                   Select Timer 2
     of one or more timers                             1              Yes
                                            2                   Select Timer 1
  I Written to the Control Register         1
                                                       1              Yes
                                                                Select Timer 0
    (0x43)                                  0
                                                       1              Yes
                                                                  Reserved
  Reading of the status/count
                                                    Read-Back Status Format
  I The configuration (status) is read      Bit      Value          Function
                                            7                        Output
    from the timer’s data register          6                      Null Count
                                            5,4               Counter Initialization
      I The 6 LSBs match those of the       3,2,1              Programmed Mode
                                            0                         BCD
        Control Word
  I The counting value is also read from the timer’s data register
  I If both status and count are requested, the status is the first
    value returned
How to parse the the status word?
 Use bitwise operations
 Use the macros defined in i8254.h
i8254: Use in the PC (1/2)
    I Timer 0 is used to provide a time base.
    I Timer 1 is used for DRAM refresh
        I Via DMA channel 0
      (Not sure this is still true.)
    I Timer 2 is used for tone generation
i8254: Use in the PC (2/2)
    I The i8254 is mapped in the I/0 address space:
      Timer 0:              0x40
      Timer 1:              0x41
      Timer 2:              0x42
      Control Register:     0x43
    I Need to use IN/OUT assembly instructions
        I Minix 3 provides the SYS_DEVIO kernel call for doing I/O
           #include <minix/syslib.h>
           int sys_inb(port_t port, u8_t *byte);
           int sys_outb(port_t port, u8_t byte);
    I Need to write to the control register before accessing any
      of the timers
        I Both to program (control word) a timer, or to read its
          configuration (read-back command)
Minix 3 and Timer 0
    I At start up, Minix 3 programs Timer 0 to generate a square
      wave with a fixed frequency
        I Timer 0 will generate an interrupt at a fixed rate:
             I Its output is connected to IRQ0
    I Minix 3 uses these interrupts to measure time
        I The interrupt handler increments a global variable on every
          interrupt
        I The value of this variable increments at a fixed, known, rate
    I Minix 3 uses this variable mainly for:
        I Keeping track of the date/time
        I Implementing SW timers
Lab 2: Part 1 - Reading Timer Configuration (1/2)
   What to do? Read timer configuration in Minix
     int timer_test_read_config(uint8_t timer,
                                enum timer_status_field field)
      1. Write read-back command to read input timer
         configuration:
           I Make sure 2 MSBs are both 1
           I Select only the status (not the counting value)
      2. Read the timer port
      3. Parse the configuration read
      4. Call the function timer_print_config() that we
          provide you
   How to design it? Try to develop an API that can be used in the
     project.
     int timer_get_conf(uint8_t timer, uint8_t *st);
     int timer_display_conf(uint8_t timer, uint8_t st,
                            enum timer_status_field status);
Lab 2: Part 1 - Reading Timer Configuration (2/2)
Stuff we provide you
  int timer_print_config(uint8_t timer,
                          union timer_status_field_val val,
                          enum timer_status_field field);
  enum timer_status_field {
      all,       // configuration in hexadecimal
      inital,    // timer initialization mode
      mode,      // timer counting mode
      base       // timer counting base
  };
  enum timer_init {
      INVAL_val,
      LSB_only,
      MSB_only,
      MSB_after_LSB
  };
  union timer_status_field_val {
      uint8_t    byte;       // status, in hexadecimal
      enum timer_init in_mode;     // initialization mode
      uint8_t    count_mode; // counting mode: 0, 1, ..., 5
      bool       bcd;        // true, if counting in BCD
C Enumerated Types
  I This is a user-defined type that can take one of a finite
    number of values
    enum timer_status_field {
        all,      // configuration in hexadecimal
        inital,   // timer initialization mode
        mode,     // timer counting mode
        base      // timer counting base
    };
    enum timer_status_field info = base;
      I The C compiler represents each possible value of an
        enumerated type by an integer value. By default:
           I The first value is represented with 0
           I Any other value, is one more than the previous value
      I However, it is possible to assign to an enumerated value an
        integer value different from the default (e.g. all = 255;)
  I The use of enumerated types makes the code more readable
C Unions
  I Syntatically, a union data type appears like a struct:
     union timer_status_field_val {
         enum timer_init in_mode;    // initialization mode
         uint8_t   count_mode; // counting mode: 0, 1, ..., 5
         bool      bcd;        // true, if counting in BCD
     };
       I Access to the members of a union is via the dot operator
  I However, semantically, there is a big difference:
    Union contains space to store any of its members, but not all
      of its members simultaneously
         I The name union stems from the fact that a variable of this
           type can take values of any of the types of its members
     Struct contains space to store all of its members
       simultaneously
 In timer_print_config() we are using it to reduce the
    number of arguments passed
     I And an enumerated type to specify the kind of information
Lab 2: Part 1 - Setting the Time-Base
   What to do? Change the rate at which Timer 0 generates
    interrupts.
     int timer_test_time_base(uint8_t timer, uint32_t freq)
      1. Write control word to configure Timer 0:
           I Do not change 4 least-significant bits
            I Mode (3)
            I BCD/Binary counting
             You need to read the Timer 0 configuration first.
           I Preferably, LSB followed by MSB
      2. Load Timer 0 with the value of the divisor to generate
         the frequency corresponding to the desired rate
           I Depends on the previous step
   How to design it? Try to develop an API that can be used in the
     project.
     int timer_set_frequency(uint8_t timer, uint32_t freq);
   How do we know it works? Use the date command.
      I Or, even better, use the test code provided.
Lab 2: Grading Criteria
   SVN (5%) Whether or not your code is in the right place (under
     lab2/, of the repository’s root)
      I Also, evidence of incremental development approach
   Execution (80%) Including automatic code grading.
   Code (15%)
     return values of function/kernel calls must be checked
     global variables only if you cannot do what you want, or if
        they can be considered fields/members of an object (if
        using object oriented design)
     symbolic constants i.e. use #define
     modularity both at the level of functions and at the level of
        files
   Self-evaluation Must submit it by filling a Google Form (check
     the handout)
   IMPORTANT Please follow exactly the instructions, otherwise
     you may be penalized
Further Reading
   I Lab 2 Handout
   I i8254 Data-sheet