Lecture 16 – A device driver
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 withTEMP.START = 1
. - There is an 'event'
DATARDY = 0x100
that indicates when the reading is ready (withTEMP.DATARDY == 1
). - There is an interrupt enable register
INTEN = 0x300
in which bitTEMP_INT_DATARDY = 0
enables the interrupt on theDATARDY
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); } } }