From: "Eric Smith" <esmithmail@gmail.com>
To: "Fans of the OS Plan 9 from Bell Labs" <9fans@cse.psu.edu>
Subject: Re: [9fans] Writing device drivers
Date: Fri, 14 Apr 2006 10:36:02 -0500 [thread overview]
Message-ID: <820bc1260604140836p4ba0baby9b9ef3d081d2fb51@mail.gmail.com> (raw)
In-Reply-To: <820bc1260604140834v2a773ev90ff46b4e6b2427f@mail.gmail.com>
[-- Attachment #1.1: Type: text/plain, Size: 4772 bytes --]
Thank you SO much -- this was just the sort of thing I needed. Pointing out
the ethernet interface, /sys/src/9/pc/devether.c, and small, easily
understood, sample drivers like etherec2t.c and ether2000.c is just what I
needed.
And you guessed it -- I have spent zero time in kernel mode. But I'm
anxious to try :)
Use of pointers in C has always been a sticky point for me although it
shouldn't be. Because I have a background with low-level languages like
assembly I thoroughly understand doing things in memory, registers, etc, but
for some reason, understanding the _why_ of using pointers in C has eluded
me -- like what is to be gained by accessing functions via a table of
pointers. But that is just what I hope to learn by working on this project.
Truly, though, my C sucks. I do hope to improve it through this ... I do
hope to succeed.
But to give you an idea of how horrible my C is I've attached a piece of
code I wrote for a (hobby) microcontroller project. This is for an 8-bit
Atmel RISC. I know this bears little relation to a device driver living in
kernel space -- and no one would write non-blocking code like this as a
device driver. But it's probably the best example I have (attached).
Please pardon the heavy comments -- that is the one thing I have learned
about C: I can go back and look at old, say, Pascal code of mine, and read
it. But 6 months after writing a little piece of C it's tough for me to
read it and figure out what in the world I was thinking. So I go overboard
with the comments.
I realize I'm submitting my sorry C code to a group of really strong C
programmers -- if nothing else it should provde some amusement :)
Thank you for your help!!!!
Regards,
Eric
On 4/14/06, Brantley Coile <brantley@coraid.com> wrote:
>
>
> Interesting question. I just spend a few moments looking around and
> thinking about suggestions for helping you up the learning curve. The
> more I thought about it, the more I wondered where you were already on
> that curve. Recommendations depend on that sort of thing.
>
> Suggestions break into three parts: C skills, general device driver
> skills, and Plan 9 specific knowledge. The C skills fall into three
> categories: the syntax and semantics of the language, common
> techniques and paradigm, and skill in software design, constructing
> solution from small functions and that sort of thing. The syntax and
> semantics problem can most easily be fixed by a quick re-read of Brian
> and Dennis' book, the famaous K&R. Most likely you've read that
> before, but I re-read it every few years or so. Amazing that a
> language so small can take a life time to fully learn. (And I'm not
> talking about C99.)
>
> Common techniques and paradigms, since you want to write a Plan 9
> device driver, is best learned by reading Plan 9 code. Lots of nice
> small things in /sys/src/cmd are great for this. As with natural
> languages, the best way to learn to write is to read. I owe
> everything to this principle.
>
> The skills in design are much harder to obtain. You may already be
> strong in this department since it's not unique to C. The two
> attributes of cohesion and coupling are the key ideas here. Good code
> in Oberon, Java, C, PL/I or COBOL are all the same.
>
> Brian and Rob's book, `The Practice of Programming' is the best
> I know of for this kind of stuff.
>
> I assume from your comment that you time spend writing stuff in kernel
> mode is limited. Sorry if this isn't the case. Things you should
> learn include, the restricted context of kernel mode, the issues
> regarding locking in the multiprocessor Plan 9 kernel, how interrupt
> handlers get registered and called, limitations on what you can do in
> the interrupt handler, how processes sleep and wakeup, and more.
> Interfacing to the under part of Plan 9 is easy. It's just a 9P
> server with function calls instead of messages. Simple device drivers
> in like /sys/src/9/pc/devrtc.c show how the device driver interfaces
> with the system.
>
> But you want to write a driver for the WPN311, so you'll need to study
> the ethernet interface in /sys/src/9/pc/devether.c, and look at an
> ethernet sample driver like etherec2t.c and ether2000.c . These are
> pretty small and show how to glue into the devether.c code.
> Devether.c factors out all the common work of ethernet drivers and
> uses a table of pointers to functions to get the specific work done by
> a given driver.
>
> Hope that helps some. I've never written a wireless driver so I don't
> know about any specific gotchas with that. Good luck on it. It's a
> great feeling to get a driver working.
>
> Brantley
>
>
>
[-- Attachment #1.2: Type: text/html, Size: 5682 bytes --]
[-- Attachment #2: acq.c --]
[-- Type: text/x-csrc, Size: 10800 bytes --]
/* vim: set sw=8 ts=8 si :
* Author : Eric Smith
* Date : July 2005
* Chip type : ATMEGA8
* Clock frequency : 400 kHz (external ceramic resonator)
* Program : acq.c
* Number : 0x0A
* Data Format Version : 0x1A
*
* This program acquires data from three sensors and stores in EEPROM.
* Data from one of the three sensors (jumper selectable) is displayed on
* PORTD's 8 LED display. EEPROM writes occur once per write interval
* which can be once per second, minute, five minutes, or hour. After EEPROM
* is full, or SS_WRITE_ENABLE jumper is unset, writes will cease, however,
* the display will continue to update constantly.
*
* Data Format (ver 0x1A):
* EEPROM @ 0x000 = Program Number
* EEPROM @ 0x001 = Data Format Version Number
* EEPROM @ 0x002 - 0x1FF = 170 3-byte records
* Record Format (3 bytes):
* Byte 0 = thermistor
* Byte 1 = photocell
* Byte 2 = magnetic
******************************************************************************/
#include <avr/io.h>
#include <inttypes.h>
/* GLOBALS */
// JIFFIES_CONSTANT should be set to the number of times the program
// can pass through the main loop in one second. This will be the basis for
// setting the write interval (1 per sec, 1 per min, 1 per 5mins, or 1 per hour)
// While this processor is capable of running on a 16 MHz clock we are loping
// along at 400 kHz to save power ... which is ok: there's no rush.
const unsigned long int JIFFIES_CONSTANT = 850;
const unsigned long int led_lit_limit = 85; // should be JIFFIES_CONSTANT/10
char flags = 0; // clear all flags
// sense switch: 1=flash PC5 LED on writes, 0=quiet
const int SS_LED_ENABLE = (1<<0);
// sense switch: 1=allow display on PORTD, 0=quiet
const int SS_DISPLAY_ENABLE = (1<<1);
// sense switch: 1=write enabled, 0=write disabled
const int SS_WRITE_ENABLE = (1<<2);
unsigned long int loop_counter;
uint8_t record_number;
unsigned short int PC = 0x000; // this is the EEPROM address pointer
uint8_t SAMPLE_INTERVAL; /* How often to record data:
0 = 1 per second
1 = 1 per minute
2 = 1 every 5 minutes
3 = 1 per hour */
uint8_t DISPLAY_SELECTION; /* What to display on PORTD LEDs:
0 = record number
1 = thermistor
2 = photocell
3 = magnetic */
struct {
uint8_t thermistor;
uint8_t photocell;
uint8_t magnetic;
} sensor;
/* FUNCTION PROTOTYPES */
void delay_ms(unsigned short);
void eeprom_write_byte(unsigned char, unsigned int);
void init(void);
void read_sense_switches(void);
void acquire_data(void);
void display_data(uint8_t);
void write_data(void);
/* FUNCTIONS */
void delay_ms(unsigned short ms)
// delay for a minimum of <ms>
// with a 1Mhz clock, the resolution is 1 ms
{
uint8_t i,j;
while (ms) {
i = 40; // 40 for 400 kHz clock, 100 for 1 MHz, etc
while (i) {
i--;
j = 70;
while (j) {
j--;
}
}
ms--;
}
}
void eeprom_write_byte(unsigned char data_byte, unsigned int eepromAddr)
// Write a byte of data to eeprom at eepromAddr
{
while (EECR & (1<<EEWE)) // wait for completion of last write
;
EEAR = eepromAddr; // Setup address
EEDR = data_byte; // and data registers
EECR |= (1<<EEMWE); // Write logical 1 to master write enable
EECR |= (1<<EEWE); // start EEPROM write
}
void init() // init routine run once only at start of main()
{
delay_ms(500); // let's just wait half a sec to make sure we are ON
PORTB = (1<<PB2) | (1<<PB1) | (1<<PB0); // enable PB0..2 as inputs
DDRB = 0x00;
PORTC = (1<<PC4) | (1<<PC3); // enable PC3..4 as inputs
DDRC = 0x00;
DDRC |= _BV(PC5); // enable PC5 as output
PORTC |= _BV(PC5); // Set PC5 output to 5V, LED off (active low)
DDRD = 0xFF; // enable port D
PORTD = 0xFF; // set all bits to one = all LEDs off (active low)
ADCSR = 0xC2; /* 11000010, left to right: ADC Enable,
Start Conversion, Single Conversion Mode,
write zero to ADC Int Flag, disable int,
prescaler: 010 for XTAL/4 */
ADMUX = (1<<REFS0) | (1<<ADLAR); // ADLAR = JUSTIFY
ADMUX &= ~_BV(MUX3); // This bit always zero; see acquire_data
/* ZERO of record_number below is candidate for removal.
record_number = 0; // zero record number
*/
eeprom_write_byte(0x0a, PC++); // 0A = program identifier
eeprom_write_byte(0x1a, PC++); // 1A = data format identifier
} // PC = 0x002 on exit
void read_sense_switches()
// read jumper settings and store in flags structure
{
flags |= SS_LED_ENABLE; // LED flash always enabled for now
flags |= SS_DISPLAY_ENABLE; // Display always enabled for now
// DISPLAY_SELECTION:
// 0x00 = display record_number
// 0x01 = display thermistor data
// 0x02 = display photocell data
// 0x03 = display magnetic data
//
// PINB0 = LSB
// PINB1 = MSB
if ((PINB & _BV(PINB1)) == 0) // if this pin is LO then jumper IS set
DISPLAY_SELECTION = 2;
else
DISPLAY_SELECTION = 0;
if ((PINB & _BV(PINB0)) == 0) // if this pin is LO then jumper IS set
DISPLAY_SELECTION++;
// SAMPLE_INTERVAL:
// 0x00 = write EEPROM once per second
// 0x01 = write EEPROM once per minute
// 0x02 = write EEPROM once every five minutes
// 0x03 = write EEPROM once per hour
//
// PINB2 = LSB
// PINC3 = MSB
if ((PINC & _BV(PINC3)) == 0) // if this pin is LO then jumper IS set
SAMPLE_INTERVAL = 2;
else
SAMPLE_INTERVAL = 0;
if ((PINB & _BV(PINB2)) == 0)
SAMPLE_INTERVAL++;
// SS_WRITE_ENABLE:
// 0x00 = write disabled
// 0x01 = write enabled
//
// PINC4 = this bit ... AND, as with all, JUMPER IN takes pin LOW
if ((PINC & _BV(PINC4)) == 0) // pin LOW means JUMPER is IN, so:
flags |= SS_WRITE_ENABLE; // set SS_WRITE_ENABLE bit
else
flags &= ~SS_WRITE_ENABLE; // JUMPER OUT, clear SS_WRITE_ENABLE
}
void acquire_data()
// digitize sensor data and store in sensor structure
// ADC0 = magnetic (Hall effect device, expected ouput 0.5-4.5V, AREF = Vcc
// ADC1 = thermistor, expected output 0-2.5V, AREF = 2.56V
// ADC2 = photocell, expected output 0-2.5V, AREF = 2.56V
{
// Let's do ADC0 (magnetic sensor) first:
ADMUX &= ~_BV(MUX0);// Next three lines sets MUX to ADC0. Already set
ADMUX &= ~_BV(MUX1); // in init() are LEFT JUSTIFY, VREF=Vcc & REFS0
ADMUX &= ~_BV(MUX2);
while (ADCSR & (1<<ADSC)) // wait for last conversion to complete
;
ADCSR |= (1<<ADSC); // start conversion (throw last one away)
while (ADCSR & (1<<ADSC)) // wait for it to finish
;
sensor.magnetic = ADCH; // get high 8 bits of magnetic data
// Now let's do ADC2 (photocell):
ADMUX |= _BV(MUX1); // MUX bits now 0010 = ADC2
ADCSR |= (1<<ADSC); // start conversion, another keeper
while (ADCSR & (1<<ADSC)) //wait for it to finish
;
sensor.photocell = ADCH; // get high 8 bits of photocell data
// Now let's do ADC1 (thermistor):
ADMUX |= _BV(REFS1); // set 2.56V AREF, REFS0 already set in init()
ADMUX &= ~_BV(MUX1);
ADMUX |= _BV(MUX0); // MUX bits now 001 = ADC1
ADCSR |= (1<<ADSC); // start conversion (will throw this one away)
while (ADCSR & (1<<ADSC)) // wait for it to finish
;
ADCSR |= (1<<ADSC); // start a new conversion (this one's a keeper)
while (ADCSR & (1<<ADSC)) //wait for it to finish
;
sensor.thermistor = ADCH; // get high 8 bits of thermistor data
// Now let's set up Vcc AREF and start a conversion to throw away on
// next time through here
ADMUX &= ~_BV(REFS1); // clear REFS1, REFS0 still set, so AREF = Vcc
ADCSR |= (1<<ADSC); // start a conversion to throw away on re-entry
}
void display_data(uint8_t selection)
// Display data selected by sense switches if display is enabled and
// turn off PC5 LED if LED lit time is expired -- intent is to turn on
// the LED at write, then off after about 1/10 second or so while
// PORTD LED updates continue to occur even during the 1/10 second
{
if ((flags & SS_LED_ENABLE) != 0) { // if LED is enabled and
if (loop_counter > led_lit_limit) // lit limit exceeded
PORTC |= _BV(PC5); // then turn LED off
}
if ((flags & SS_DISPLAY_ENABLE) !=0) { // if DISPLAY is enabled
// invert bits and display data on Port D LEDs
// (must be inverted 'cause LEDs are active low)
switch (selection) {
case 0:
PORTD = ~record_number;
break;
case 1:
PORTD = ~sensor.thermistor;
break;
case 2:
PORTD = ~sensor.photocell;
break;
case 3:
PORTD = ~sensor.magnetic;
break;
}
}
}
void write_data()
// Write data in sensor structure to EEPROM at eepromAddr
// Light PC5 LED if enabled through sense switches (SS_LED_ENABLE)
{
if ((flags & SS_LED_ENABLE) != 0) // if LED is enabled
PORTC &= ~_BV(PC5); // then turn it ON! (active low)
eeprom_write_byte(sensor.thermistor, PC++);
eeprom_write_byte(sensor.photocell, PC++);
eeprom_write_byte(sensor.magnetic, PC++);
}
int main(void)
{
init(); // initialization routine to be run once only
unsigned long int loop_limit = JIFFIES_CONSTANT; // for starters
uint8_t record_limit = 170; // 170 records = 2 bytes + (170 *3)
// first byte is program number
// second byte is record type
// record type describes data structure
// of 170 records of 3 bytes each
while (1) { // THIS IS THE MAIN LOOP
loop_counter = 0;
while (loop_counter < loop_limit) {
read_sense_switches();
switch (SAMPLE_INTERVAL) {
case 0: // write ONCE PER SECOND
loop_limit = JIFFIES_CONSTANT;
break;
case 1: // write ONCE PER MINUTE
loop_limit = JIFFIES_CONSTANT * 60;
break;
case 2: // write ONCE PER FIVE MINUTES
loop_limit = JIFFIES_CONSTANT * 300;
break;
case 3: // write ONCE PER HOUR
loop_limit = JIFFIES_CONSTANT * 3600;
break;
}
acquire_data();
display_data(DISPLAY_SELECTION);
loop_counter++;
}
// loop_counter has exceeded loop_limit, time to write data
// BUT, write EEPROM only if record limit not exceeded.
// (will continue to loop and display even if EEPROM is full)
if (record_number < record_limit) {
if ((flags & SS_WRITE_ENABLE) !=0) {
write_data();
record_number++;
}
}
} // close of main loop -- will loop here forever, always displaying
// data, and writing to EEPROM only if WRITE_ENABLE is set and
// record_limit is not exceeded
}
next parent reply other threads:[~2006-04-14 15:36 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <8a5bd8ccbc2e53557663e2a9020fb26c@coraid.com>
[not found] ` <820bc1260604140834v2a773ev90ff46b4e6b2427f@mail.gmail.com>
2006-04-14 15:36 ` Eric Smith [this message]
2006-04-14 19:33 ` David Leimbach
2006-04-15 4:58 ` jmk
2006-04-15 13:24 ` Eric Smith
2006-04-15 14:16 ` Anthony Sorace
2006-04-16 23:00 ` Paweł Lasek
2006-04-19 3:10 erik quanstrom
2006-04-19 4:02 ` jmk
2006-04-19 4:57 ` lucio
2006-04-19 6:59 ` Nigel Roles
2006-04-19 21:12 ` quanstro
-- strict thread matches above, loose matches on Subject: below --
2006-04-17 0:31 erik quanstrom
2006-04-17 1:44 ` Russ Cox
2006-04-17 10:01 ` Nigel Roles
2006-04-18 1:34 ` erik quanstrom
2006-04-18 14:49 ` Nigel Roles
2006-04-18 15:42 ` jmk
2006-04-18 15:59 ` Moritz Kiese
2006-04-18 16:03 ` Nigel Roles
2006-04-18 19:15 ` Paweł Lasek
2006-04-17 0:11 erik quanstrom
2006-04-17 1:24 ` Russ Cox
2006-04-16 1:02 erik quanstrom
2006-04-16 2:36 ` Anthony Sorace
2006-04-16 8:06 ` Bruce Ellis
2006-04-15 23:34 erik quanstrom
2006-04-15 23:55 ` Skip Tavakkolian
2006-04-15 15:26 erik quanstrom
2006-04-15 23:04 ` Federico Benavento
2006-04-15 23:18 ` Skip Tavakkolian
2006-04-16 0:43 ` Federico G. Benavento
2006-04-16 18:03 ` Charles Forsyth
2006-04-16 18:14 ` Bruce Ellis
2006-04-16 21:31 ` Charles Forsyth
2006-04-14 3:20 Eric Smith
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=820bc1260604140836p4ba0baby9b9ef3d081d2fb51@mail.gmail.com \
--to=esmithmail@gmail.com \
--cc=9fans@cse.psu.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).