Lecture 16 – A device driver

Copyright © 2017–2023 J. M. Spivey
Revision as of 12:39, 2 March 2022 by Mike (talk | contribs)
Jump to navigation Jump to search

The Nordic chip on the microbit has a temperature sensor that measures the temperature of the processor die in units of 1/4° Celcius. The device consists of an analog circuit that exploits some temperature-dependent property of silicon, together with a dedicated A-to-D converter that digitises a reading. The A-to-D converter takes some time to settle, so there is a protocol where the processor initiates a reading, then must wait for an event that can be signalled by interrupt before getting the result. We will aim to write a device driver for the sensor, and provide a function

int temp_reading(void)

that can be called from any process to get an up-to-date temperature reading.

Reasons for having a separate device driver process:

  • We can manage concurrent access from multiple processes and prevent a new reading being initiated before the previous one is finished.
  • We can connect to the interrupt from the device and react when a reading is complete.

The hardware

Two pages from the nRF51822 manual describe the hardware of the temperature sensor. All the device registers are addressible at offsets from the base address TEMP = 0x4000C000.

  • There is an 'task' START = 0x000 that initiates a reading when triggered with TEMP.START = 1.
  • There is an 'event' DATARDY = 0x100 that indicates when the reading is ready (with TEMP.DATARDY == 1).
  • There is an interrupt enable register INTEN = 0x300 in which bit TEMP_INT_DATARDY = 0 enables the interrupt on the DATARDY event.
  • There is a data register TEMP = 0x508 that contains the value of the reading when ready.

The driver process

Because we will deal with each request – starting the reading, waiting for it to be ready, then retrieving the value – before accepting another request, we can write the driver process in a particularly simple way, where the server loop deals with one complete request for each iteration.

The interface between the client and the device driver is a function temp_reading() that sends a request to the driver, awaits a reply, and returns the temperature reading (still in units of 1/4°). For the remote procedure call idiom of sending a request and waiting for the reply, we can use the sendrec system call.

int temp_reading(void)
{
    message m;
    sendrec(TEMP_TASK, REQUEST, &m);
    return m.int1;
}
static void temp_task(int arg)
{
    message m;
    int temp, client;

    TEMP.INTEN = BIT(TEMP_INT_DATARDY);
    connect(TEMP_IRQ);
    enable_irq(TEMP_IRQ);

    while (1) {
        receive(ANY, &m);

        switch (m.type) {
        case REQUEST:
            client = m.sender;

            TEMP.START = 1;
            receive(INTERRUPT, NULL);
            assert(TEMP.DATARDY);
            temp = TEMP.VALUE;
            TEMP.DATARDY = 0;
            clear_pending(TEMP_IRQ);
            enable_irq(TEMP_IRQ);

            m.int1 = temp;
            send(client, REPLY, &m);
            break;

        default:
            badmesg(m.type);
        }
    }
}