Skip to content

Instantly share code, notes, and snippets.

@withattribution
Last active April 22, 2019 23:59
Show Gist options
  • Select an option

  • Save withattribution/ac2336a49fb48836b18e to your computer and use it in GitHub Desktop.

Select an option

Save withattribution/ac2336a49fb48836b18e to your computer and use it in GitHub Desktop.
Sample Kernel Driver for Frequency Measurement (beaglebone)
//Taken from https://www.mail-archive.com/beagleboard@googlegroups.com/msg00436.html <-- usage instructions
#include <stdio.h>
#include <sys/time.h>
#define MAXAV 10
void main(void)
{
FILE *fp;
int ret;
int frequency, periodes,rpm,i;
double dperiode;
int parameter;
unsigned char timespan = 1;
int samples=0;
struct timeval tv,tv_start;
int avval[MAXAV];
gettimeofday(&tv_start,NULL);
// configure frequency counter parameters
fp = fopen("/dev/fcounter","wb");
if(fp)
{
parameter = 0xaa370000;
parameter += timespan;
fwrite(&parameter,4,1,fp);
fclose(fp);
}
else
printf("Cannot open device \n");
// Read the counter result (exit with Ctrl-C)
fp = fopen("/dev/fcounter","rb");
if(fp)
{
while(1)
{
ret = fread(&frequency,4,1,fp);
if(ret<=0) break;
ret = fread(&periodes,4,1,fp);
if(ret<=0) break;
frequency /= timespan;
dperiode = 512.0/(double)periodes; // 1 jiffies entspricht 512 Hz
// bei Torzeit 1s und Priodenzaehler 512 Hz, ist die Periodenmessung bei 22 Hz genauer, dar?ber die Frequenzmessung
if(frequency < 22)
{
rpm = (int)(dperiode*60.0);
printf("Periodic measurement: ");
}
else
{
rpm = frequency*60;
printf("Frquency measurement: ");
}
for(i=(MAXAV-1); i>0; i--) avval[i] = avval[i-1];
avval[0] = rpm;
rpm = 0;
for(i=0; i<MAXAV; i++) rpm+=avval[i];
rpm/=MAXAV;
gettimeofday(&tv,NULL);
printf("[%d] Frequency: %d Hz. Periodes: %.1f. rpm=%d\n",tv.tv_sec - tv_start.tv_sec,frequency,dperiode,rpm);
usleep(250000);
}
}
else
printf("Cannot open device \n");
}
/*
* Frequency Counter, Kernel Module
* ================================
*
* Copyright (c) 2013 Kurt Moraw
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* this kernel module measures the frequency and periodic length at a GPIO pin
* it is fully interrupt driven
*
* DebugLED pin (60) : gpio1_28 (beaglebone p9/12)
* Counter Input pin (48) : gpio1_16 (beaglebone p9/15)
*
* Interface to user space: /dev/fcounter
*
* Supported hardware: Beaglebone Black
* */
// include files
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h> // copy_from/to_user
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/param.h>
#include <linux/timer.h>
#include <linux/ioport.h>
#include <linux/gpio.h>
#include <linux/moduleparam.h>
#include <linux/errno.h>
#include <asm/io.h>
//#define _DEBUG // print messages into message log, see: dmesg
#define DEBUG_LED // blinks a LED at the output GPIO
// kernel module information
#define DRV_NAME "km_gpiotest"
#define MAX_AVERAGE_DEPTH 20
MODULE_LICENSE("Dual BSD/GPL");
int majornummer = 60;
// function prototypes
static void counter_exit(void);
static int counter_init(void);
static int setup_pinmux(void);
static void timer_fcounter(unsigned long arg);
// Declaration of the init and exit functions
module_init(counter_init);
module_exit(counter_exit);
// this structure holds information about our GPIOs
struct myios {
u16 irq;
u16 input_pin;
u16 output_pin;
u32 fcounter; // counts pulses of the GPIO input line
u32 pcounter; // counts ptimer pulses per one periode of the input signal
};
static struct myios gpio_data;
// kernel timers for measurement purposes
struct timer_list timer_frequency_measurement;
struct timer_list timer_periodic_measurement;
// Variables for measurement counter and results
//int fcounter = 0;
int frequency=0; // holds the pulses per second (Hz)
int periodes=0; // result of periodic measurement
int timespan=2; // measurement time
int ptimespan = 1; // periodic counter timespan
// ========================================================================================================================
// File IO functions for communication with the user space program
// ========================================================================================================================
// user space program opens this device: fopen("/dev/fcounter",.....
static int counter_open(struct inode *inode, struct file *filp)
{
#ifdef _DEBUG
printk("<1>counter: Device opened by user space program\n");
#endif
return 0;
}
// user space program closes this device: fclose(...
static int counter_release(struct inode *inode, struct file *filp)
{
#ifdef _DEBUG
printk("<1>counter: Device closed by user space program\n");
#endif
return 0;
}
// user space program writes some values to this module: i.e.: fwrite(databuffer,size_of_item,number_of_items,filepointer);
// the parameter count = size_of_item * number_of_items since we are a character driver which handles byte by byte
static ssize_t counter_write( struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
int res;
int value;
#ifdef _DEBUG
printk("<1>counter: userspace program sent %d bytes\n",count);
#endif
if(count != 4) return count; // ignore all value with other length then 4
res = copy_from_user(&value,buf,count);
#ifdef _DEBUG
printk("<1>counter: userspace program sent value: %x [hex]\n",value);
#endif
// now the data are in value, we can process this data
// data format of value for this module: 0xrrssttuu
// rr = 0x55 (fixed value)
// ss = 0x37 (fixed value)
// tt = depth of averaging
// uu = timespan for measurement
if( ((value>>24)&0xff) == 0xaa &&
((value>>16)&0xff) == 0x37 &&
((value>>8)&0xff) == 0x00)
{
timespan = value & 0xff;
// security
if(timespan>20) timespan=20; // max. 20s measurement period
}
// the return value show how many bytes have been processed by this function
// if all bytes have been processed that the return value = count
return count;
}
// the user space program requests data from this module. i.e.: fread ( databuffer, size_of_item, number_of_items count, filepointer);
static ssize_t counter_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
int res;
u32 val[2];
#ifdef _DEBUG
printk("<1>counter: %d bytes requested by user. Offset:%d\n",count,*f_pos);
#endif
// copy the measured frequency to the user space program as a 4-byte integer value
// since the user space program runs on the same processor architecture as this driver, the value can be read directly by: fread(&frequency,4,1,filepointer);
val[0] = frequency;
val[1] = periodes;
res = copy_to_user(buf,val,8);
if(*f_pos == 0) return 8;
//if(*f_pos == 0) return 4;
return 0;
}
// Structure that declares the usual file access functions
struct file_operations counter_fileops = {
read: counter_read,
write: counter_write,
open: counter_open,
release: counter_release
} ;
// ========================================================================================================================
// IRQ: Interrupt routines called by the kernel in case of an interrupt
// ========================================================================================================================
// timer IRQ, will be called by the kernel if the timer expires (i.e. after one second)
static void timer_fcounter(unsigned long arg)
{
/*#ifdef DEBUG_LED
// read the source of the IRQ
static char stat=0;
// change status of the ext. LED
if(stat == 0)
{
stat = 1;
gpio_set_value(gpio_data.output_pin,1);
}
else
{
stat = 0;
gpio_set_value(gpio_data.output_pin,0);
}
#endif
*/
// store the number of detected edges of the input signal within the last time period
frequency = gpio_data.fcounter;
gpio_data.fcounter = 0; // reset counter
// restart timer for the next period
// the "-1" was required to correct the time span
mod_timer(&timer_frequency_measurement,jiffies+timespan*HZ-1);
}
// timer IRQ running at 512 Hz (if ptimespan is 1) for periodic measurement
static void timer_pcounter(unsigned long arg)
{
#ifdef DEBUG_LED
// read the source of the IRQ
static char stat=0;
// change status of the ext. LED
if(stat == 0)
{
stat = 1;
gpio_set_value(gpio_data.output_pin,1);
}
else
{
stat = 0;
gpio_set_value(gpio_data.output_pin,0);
}
#endif
// count periodic timer pulses
gpio_data.pcounter++;
mod_timer(&timer_periodic_measurement,jiffies+ptimespan);
}
// GPIO Interrupt Routine. Is called by the kernel for every edge detected on the input pin
static irqreturn_t input_irq_handler(int irq, void* dev_id)
{
struct myios* data = (struct myios*)dev_id;
// count the detected edges on the input pin
data->fcounter++;
// store periodic measurement
periodes = gpio_data.pcounter;
gpio_data.pcounter = 0;
return IRQ_HANDLED; // this IRQ was handled
}
// ========================================================================================================================
// Setup, Init and Exit functions for this kernel module
// ========================================================================================================================
// Pinmux: as each pin has several functions, we need to tell which function we need
// for this definitions see the uC data sheet
#define AM33XX_CONTROL_BASE 0x44e10000
#define OUTPIN (AM33XX_CONTROL_BASE + 0x878) // DebugLED pin (60) : gpio1_28 (beaglebone p9/12)
#define INPIN (AM33XX_CONTROL_BASE + 0x840) // Counter Input pin (48) : gpio1_16 (beaglebone p9/15)
#define OUTPIN_MODE (0x7 | (2 << 3)) // mode 7 (gpio), PULLUP, OUTPUT
#define INPIN_MODE (0x7 | (2 << 3) | (1 << 5)) // mode 7 (gpio), PULLUP, INPUT
static int setup_pinmux(void)
{
void *addr;
// ioremap returns the physical address, this depends on the CPU used
#ifdef DEBUG_LED
addr = ioremap(OUTPIN,4); // get physical port address
if(!addr) return -EBUSY;
iowrite32(OUTPIN_MODE,addr); // and set the port mode
gpio_data.output_pin = 60; // and store the pin numbers in our structure
#endif
addr = ioremap(INPIN,4); // get physical port address
if(!addr) return -EBUSY;
iowrite32(INPIN_MODE,addr); // and set the port mode
gpio_data.input_pin = 48; // and store the pin numbers in our structure
return 0;
}
// init function: will be called when driver is loaded with: insmod conter_driver.ko
static int counter_init(void)
{
int res,err;
// register dev, verlinke es mit dem /dev/complete das vorher angelegt sein muss
// register the device, link it to the major number (which was assigned when the device was
// created with: mknod /dev/fcounter c 60 0
res = register_chrdev(majornummer,"counter_module",&counter_fileops);
if(res < 0)
{
printk("<1> counter: cannot find major number, is the device created ?\n");
return res;
}
// Kernel-Timer: 1 second
// this timer is used for frequency measurement
// the pulses at the GPIO input are counted during this time, which results in pulses per second = Hz
init_timer(&timer_frequency_measurement); // init timer
timer_frequency_measurement.function = timer_fcounter; // this function is called when the timer expires
timer_frequency_measurement.expires = jiffies + timespan*HZ;// timespan*1 second (jiffies is the current system time and HZ are the ticks per second)
add_timer(&timer_frequency_measurement);
init_timer(&timer_periodic_measurement); // init timer
timer_periodic_measurement.function = timer_pcounter; // this function is called when the timer expires
timer_periodic_measurement.expires = jiffies + ptimespan;
add_timer(&timer_periodic_measurement);
// setup the GPIO ports
// in this driver we use
// P9-Pin12 as Output (ext. LED) and
// P9-Pin15 as Input which generates an interrupt for each falling edge
// this sets the function of the multiplexed GPIOs
err = setup_pinmux();
if (err < 0) {
printk("<1>counter: failed to apply pinmux settings.\n");
return err;
}
// request access to the GPIO pins. The kernel checks if these pins are already in use or not
#ifdef DEBUG_LED
// first request access to the output line for our LED
err = gpio_request_one(gpio_data.output_pin, GPIOF_OUT_INIT_HIGH, DRV_NAME " gpio");
if (err < 0) {
printk("<1> counter: failed to request GPIO output pin %d.\n",gpio_data.output_pin);
return err;
}
#endif
// and for the input line
err = gpio_request_one(gpio_data.input_pin, GPIOF_IN, DRV_NAME " irq");
if (err < 0) {
printk("<1> counter: failed to request GPIO input pin %d.\n",gpio_data.input_pin);
gpio_free(gpio_data.output_pin);
return err;
}
// now assign an IRQ for the input line
res = gpio_to_irq(gpio_data.input_pin);
if (res < 0) {
printk("<1> counter : failed to get IRQ for input pin %d.\n",gpio_data.input_pin);
gpio_free(gpio_data.input_pin);
gpio_free(gpio_data.output_pin);
return res;
}
else
{
// the kernel assigned irq number "res"
gpio_data.irq = (u16)res;
res = 0;
}
// now enable the IRQ
err = request_any_context_irq(gpio_data.irq,input_irq_handler,IRQF_TRIGGER_FALLING|IRQF_NO_SUSPEND|IRQF_FORCE_RESUME|IRQF_NO_THREAD,DRV_NAME,(void*)&gpio_data); // (void*)&gpio_data ... will be the parameter *dev_id of the IRQ routine
if (err < 0) {
printk("<1> counter: failed to enable IRQ %d for pin %d.\n",gpio_data.irq, gpio_data.input_pin);
gpio_free(gpio_data.input_pin);
gpio_free(gpio_data.output_pin);
return err;
}
else
{
// IRQ is enabled
//gpio_data.irq_enabled = 1;
}
#ifdef _DEBUG
printk("<1> counter_driver: driver loaded sucessfully\n");
#endif
return 0;
}
// exit function: will be called when driver is unloaded with: rmmod conter_driver.ko
// it is extremely important to release all reserved hardware, memory, timers ...
// or the kernel may hang up after removing the driver with rmmod !
static void counter_exit(void)
{
// remove the timers
del_timer(&timer_frequency_measurement);
del_timer(&timer_periodic_measurement);
// release GPIO interrupt
free_irq(gpio_data.irq, (void*)&gpio_data);
// release GPIO pins
gpio_free(gpio_data.input_pin);
gpio_free(gpio_data.output_pin);
// release Majornumber
unregister_chrdev(majornummer,"complete_demo_module");
#ifdef _DEBUG
printk("<1> counter_driver: driver unloaded\n");
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment