Sunday, October 5, 2014

Contest Buzzer System (by re-purposing Sparkfun's Arduino based 7 Segment Serial Display)

Quiz contest's are one of the most fun ways to learn things in a group. Throw in a buzzer system and you have sense of urgency, excitement and teamwork surging through the various teams.

Couple of years ago (in 2006) - before Arduino or microcontrollers - me and Ranjit assembled a parallel port based buzzer system. We had 5 buzzers for upto 5 teams - we used the 5 Status pins (nACK, Busy, Paper Out, Select and nError) of the parallel port to read the status of the switches. The project lasted us a long time - till the time they stopped equipping laptops with parallel ports!
Some of the features of this system were:

  • Use of bell switches as buzzers. the spring action, brings the switches back to off position.
  • Use of microphone coaxial cables - these sturdy yet flexible cables are designed to be stepped upon and still last long!
  • Use of RCA connector to attach and detach buzzers - essential because we were using coaxial cables
  • Pull up resistors to make sure the status pins idled at logic high when the switch weren't pressed. Pressing the switch grounded the corresponding status pin.
  • The "Always on top" Visual Basic application which changed color and showed which team pressed the buzzer first - This went well with our quiz sessions hosted on a digital projecter using Power Point slide - the audience could see on the screen who pressed the buzzer first!


The hardware - made of microphone coax cables and bell switches.

The Visual Basic application that read the status pins.

The buzzer application went well with our powerpoint slide deck - it always stayed on top.


Fast forward to 2014 - We now have Arduino and portable power banks! but I am lazier than ever before, so instead of assembling my own circuit, I picked up Sparkfun's 7 Segment Serial Display Widget which is simply an Arduino enabled AVR microcontroller (ATmega328) with a 4 digit 7 segment display. The microcontroller's firmware allows the display to be controlled via its SPI/UART/I2C interface.

And so connected bell switches to the SPI/I2C pins of the 7 segment serial display using them in GPIO mode and replaced the firmware with an Arduino sketch of my own and I had a standalone buzzer system ready. Oh and I added a hacked portable charger (a.k.a, power bank) to make the system really stand alone. The 7 segment display shows the time sequence in which the buzzers were pressed. The system can be reset using the reset bell switch which is connected to the reset pin of the Arduino microcontroller present on the 7 Segment Serial Display widget.

Sparkfun's 7-segment serial display widget

Here are the features of the system:
  • Sturdy Hardware: Bell switches, microphone coax cables and RCA connectors - all the goodness carried over from the previous parallel port based system. Used cable ties to act as cable stress relief around the bell switches.
  • Avoiding Confusion: Used printed numbered labels to mark the RCA sockets and switches. Used spray paint on the beginning and end of coax cables to help identify the RCA plugs corresponding to various bell switches in case the cables got entangled.
  • Pin Map: 4 + 1 = 5 bell switches for 4 teams and 1 for the quiz master to reset the system before every question. The pin connection
    Buzzer 1 - SDI/MOSI/PB3/PCINT3 - Digital I/O 11
    Buzzer 2 - SCK/PB5/PCINT5 - Digital I/O 13
    Buzzer 3 - SS#/PB2/PCINT2 - Digital I/O 10
    Buzzer 4 - SDO/MISO/PB4/PCINT4 - Digital I/O 12
  • Pullup resistors: 8.2 kilo ohms pull up resistors on 4 GPIOs and the RESET pin to prevent false triggering due to electrical noise. Pressing the bell switches grounds the corresponding GPIO pin which is registered by the microcontroller as a falling edge pin change interrupt. One might think that such long lengths of microphone coax cables would cause false triggering
  • Arduino Libraries: 3 arduino libraries are used:
    • TimerOne - Library to use the Timer1 in interrupt mode - used to refresh the 7-segment display.
    • SevSeg - Library used to drive the 7-segment display.
    • PinChangeInt - Use interrupts to detect when switches are pressed
  • Interrupt Driven: The software does everything in interrupts: The 7 segment display is refreshed every 0.01 seconds using Timer1 and the buzzer inputs are registered using Pin Change interrupts. The main loop lies idle.
  • No debounce required for the switches: Since the pin change interrupts are used.
  • Non standard Arduino pins used: If you refer to the schematic of 7 Segment Serial Display, you will note that pins PB6 and PB7 are used. You will need to modify C:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\standard\pins_arduino.h to access these pins from withing Arduino IDE. These pins are used to light up segment E and the decimal points. Refer the source code comments on how to make modifications to pins_arduino.h.
  • Power Source: A hacked power bank is used to power the system anywhere. The system draws just a few milliamperes of current which is about a hundred times less than what mobile phones used while being charged from the power bank. So the power bank had to be hacked lest it thinks that it is not being used for charging and automatically turns off. Here is how to hack a power bank to make sure it doesn't turn off while powering a low power DI?y project - it involves replacing the current sense resistor. The power bank supplies 5 volts to the 7-Segment Serial
  • Housing: A small easily procurable lunch box.
  • Program downloading: The 7-Segment Serial Display can be re-programmed using Sparkfun's FTDI Basic Breakout
Mounting the 7-Segment Serial Display widget and the 6 way RCA block
in the lid of the lunch box.

The hacked iBall power bank supplies 5 volts to the Arduino widget.

Wiring it all up - note the JST connector, the 8.2Kohms pullup resistors and the
blu-tack used to hold the wires in place

Nylon Cable ties used as stress relief near the ends of the bell switches.


The hacked power bank supplying power to the arduino widget.


The complete system.


The power bank lies at the bottom inside the lunch box.

Using FTDI basic breakout to download a new sketch into 7 Segment Serial Display

And finally, here is the Arduino sketch that goes into the 7-Segment Serial Display widget:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// This code uses the following libraries each of which need to be downloaded to C:\Program Files (x86)\Arduino\libraries
// and placed in a folder of its own.
// 1. TimerOne https://code.google.com/p/arduino-timerone/downloads/list
// 2. SevSeg https://github.com/sparkfun/SevSeg
// 3. PinChangeInt https://code.google.com/p/arduino-pinchangeint/downloads/list

// Add non standard arduino pins: 
// PB6 as digital pin 20 to pins_arduino.h
// PB7 as digital pin 21 to pins_arduino.h
// To do that
// 1. Open C:\Program Files (x86)\Arduino\hardware\arduino\avr\variants\standard\pins_arduino.h with admin rights
// 2. Append PB, PB to digital_pin_to_port_PGM[]
// 3. Append _BV(6), _BV(7) to digital_pin_to_bit_mask_PGM[]


//My buzzer Pinout
//RCA Socket 1 - Red - SDI or MOSI or PB3 or PCINT3 or D 11
//RCA Socket 2 - Yellow - SCK or PB5 or PCINT5 or D 13
//RCA Socket 3 - Brown - SS# or PB2 or PCINT2 or D 10
//RCA Socket 4 - Green - SDO - MISO or PB4 or PCINT4 or D 12

#include "TimerOne.h"
#include "SevSeg.h"
#include "PinChangeInt.h"

SevSeg myDisplay; //Create an instance of the object

#define RCASOCK01 11
#define RCASOCK02 13
#define RCASOCK03 10
#define RCASOCK04 12

// Struct for 4-digit, 7-segment display 
// Stores display value (digits),  decimal status (decimals) for each digit, and cursor for overall display
struct display
{
  char digits[4];
  unsigned char decimals;
  unsigned char cursor;
} 
display;  // displays be displays

void Timer1ISR() //use Timer Interrupt to refresh the display
{
  myDisplay.DisplayString(display.digits, display.decimals);
}

void buzzpressed()
{
  uint8_t latest_interrupted_pin;
  latest_interrupted_pin=PCintPort::arduinoPin;
  if(display.cursor < 4)
  {
    switch(latest_interrupted_pin) {
      case RCASOCK01: display.digits[display.cursor] = '1'; break;
      case RCASOCK02: display.digits[display.cursor] = '2'; break;
      case RCASOCK03: display.digits[display.cursor] = '3'; break;
      case RCASOCK04: display.digits[display.cursor] = '4'; break;
      default: break;
    }
    PCintPort::detachInterrupt(latest_interrupted_pin);
    display.cursor++;
  }
}


void setup()
{  
  myDisplay.SetBrightness(100); //Set the display to 100% bright

  // Set the initial state of displays and decimals 'x' =  off
  display.digits[0] = '-';
  display.digits[1] = '-';
  display.digits[2] = '-';
  display.digits[3] = '-';
  display.decimals = 0x00;  // Turn all decimals off
  display.cursor = 0;  // Set cursor to first (left-most) digit

  int digit1 = 16; // DIG1 = A2/16 (PC2)
  int digit2 = 17; // DIG2 = A3/17 (PC3)
  int digit3 = 3;  // DIG3 = D3 (PD3)
  int digit4 = 4;  // DIG4 = D4 (PD4)

  //Declare what pins are connected to the segments
  int segA = 8;  // A = D8 (PB0)
  int segB = 14; // B = A0 (PC0)
  int segC = 6;  // C = D6 (PD6), shares a pin with colon cathode
  int segD = A1; // D = A1 (PC1)
  int segE = 21; // E = PB7 (not a standard Arduino pin)
  int segF = 7;  // F = D7 (PD6), shares a pin with apostrophe cathode
  int segG = 5;  // G = D5 (PD5)
  int segDP= 20; //DP = PB6 (not a standard Arduino pin)

  int digitColon = 2; // COL-A = D2 (PD2) (anode of colon)
  int segmentColon = 6; // COL-C = D6 (PD6) (cathode of colon), shares a pin with C
  int digitApostrophe = 9; // APOS-A = D9 (PB1) (anode of apostrophe)
  int segmentApostrophe = 7; // APOS-C = D7 (PD7) (cathode of apostrophe), shares a pin with F

  int numberOfDigits = 4; //Do you have a 2 or 4 digit display?

  int displayType = COMMON_ANODE; //SparkFun 10mm height displays are common anode

  //Initialize the SevSeg library with all the pins needed for this type of display
  myDisplay.Begin(displayType, numberOfDigits, 
  digit1, digit2, digit3, digit4, 
  digitColon, digitApostrophe, 
  segA, segB, segC, segD, segE, segF, segG, 
  segDP,
  segmentColon, segmentApostrophe);
  
  Timer1.initialize(10000); // set a timer of length 10000 microseconds (or 0.01 sec)
  Timer1.attachInterrupt( Timer1ISR ); // attach the service routine here
  
  pinMode(RCASOCK01, INPUT);
  digitalWrite(RCASOCK01, HIGH);
  PCintPort::attachInterrupt(RCASOCK01, &buzzpressed, FALLING);  // add more attachInterrupt code as required

  pinMode(RCASOCK02, INPUT);
  digitalWrite(RCASOCK02, HIGH);
  PCintPort::attachInterrupt(RCASOCK02, &buzzpressed, FALLING);  // add more attachInterrupt code as required

  pinMode(RCASOCK03, INPUT);
  digitalWrite(RCASOCK03, HIGH);
  PCintPort::attachInterrupt(RCASOCK03, &buzzpressed, FALLING);  // add more attachInterrupt code as required

  pinMode(RCASOCK04, INPUT);
  digitalWrite(RCASOCK04, HIGH);
  PCintPort::attachInterrupt(RCASOCK04, &buzzpressed, FALLING);  // add more attachInterrupt code as required
  
  interrupts();  // Turn interrupts on, and les' go
}

void loop()
{
}

And the buzzer in action in my classroom! Excitement!



Saturday, October 4, 2014

Replacing the current sense resistor in Portable Chargers/Power Banks for powering low power DIY projects.

Mobile Phone/Tablet charging Power Banks can be used to power so many DIY projects. But if your project is low power and draws very little current, the power bank will auto turn off after a few seconds thinking that it isn't being used to charge gadget (because the current draw is very low). I need to figure out a way to hack one and locate and replace the current sense resistor with one of a higher value. Here is how I did it.

  1. This told me that the resistor would be located on the return path (ground path) of the output USB connector. I promptly located the R100 SMD resistor in my iBall portable charger. The fact that this resistor was fatter (higher power dissipation rating) and that it was 0.1 ohms in value confirmed that this had to be the current sense resistor
    Original 0.1 ohms current sense resistor
  2. I measured the voltage across it when nothing was connected. It was zero volts and the portable charger turned off in a few seconds when nothing was being charged.
  3. I then turned on the charger back again and this time measured the voltage across it while charging my galaxy tab. The voltage across the resistor was 0.07 volts
  4. I now disconnected the galaxy tab and tried powering my arduino circuit which I knew for sure was drawing very very less current because the charger used to keep turning off after a few seconds. The low current drawn by Arduino didn't cause appreciable voltage drop across the current sense resistor and so the charger decided to turn off. When I measured the voltage across the 0.1 ohm current sense resistor while powering the arduino circuit, the multimeter didnt even register a reading - even on the lowest range.
  5. On a hunch I replaced the 0.1 ohm SMD resistor with a 1/4 watt 10 ohm thru hole resistor. I figured, that USB ports provide upto 500 mA of current and so the phone must be drawing that much current when charging while my arduino circuit would only be drawing a few mA of current (maybe 5 mA coz it had a 7 segment display on it). So i increased the value of the current sense 100 times. Also 10 ohms was the least value I had in my resistor collection.
    Replaced the 0.1 ohm resistor with a 10 ohm one
  6. After replacing the resistor, I measured the voltage across it while powering the Arduino. It was now 0.07 volts and now the power bank wouldn't turn off!!!
  7. Awesomeness!


    OMG it's alive! and doesn't auto turn off now!

Monday, July 7, 2014

Using Raspberry Pi with a Digital Projector in a Teach For India classroom

I am a TeachForIndia fellow now and have been teaching at a low income school in Pune. The school is named Kilbil Public School (kilbil is Marathi for "chattering") and I teach Maths and Geography to 3 classes of 7th Graders. We are a team of 3 TFI Fellows, the other two teach English, Science and History/Civics. We have a facebook page here. The first thing I did is to ask all of my close friend to contribute money to help me buy a digital projector. The idea was that if we are  to use Inquiry-based learning for Geography, History/Civics and Science, we would need to hook the students attention by showing them images and youtube videos. So here's what I got for my setup:

Hardware:

  1. BenQ MS521p DLP Projector with HDMI Input and Analog Stereo output. This cost Rs. 30,000 from Flipkart. My friends contributed hard cash (and Flipkart vouchers - which cannot be bought using international credit cards) for this. Recently I have ordered another projector, so now the two new BenQ projectors together with an existing old one will allow us to deliver awesome instructions simultaneously all three section of 7th grade. The projector decodes the digital audio stream from HDMI and makes it available as analog audio for feeding it into speakers.
  2. Raspberry Pi with case - I had this one lying around. I was scared that I would end up breaking my laptop if I carried it to school everyday. So I thought of using a Raspberry Pi in the classroom instead to play MP4 videos downloaded from YouTube and to teach my kids programming using Scratch
  3. SanDisk SD Card (16Gb 45 MB/s) - I had been using this with my DSLR, I figured that I could put it to better use with my RPi since I was anyway using my Nexus 5 to take photos of my kids instead of the DSLR.
  4. Logitech MK220 Wireless Keyboard and Mouse Combo - I had bought this long ago for general use with my laptop or RPi. Available from Flipkart.
  5. Netgear Wireless USB Micro WNA1000M - USB WiFi Dongle for use with RPi. Buy from Flipkart. This was so that I could access internet using my cellphone as a WiFi hotspot.
  6. HP Stereo Speakers S3000 bought from Croma for Rs. 899, they are available for less here on Flipkart. These can be powered from USB
  7. Nextech Dual USB Charger from Croma for powering the RPi and Speakers simultaneously
  8. A really thin and short HDMI Cable to connect the RPi to the projector
  9. A Micro USB Cable for powering the RPi
  10. A powerstrip to act as an extension board with its short cable replaced with a longer one.
  11. A Square Bucket and Mug to carry everything in - the convenience of carrying things in a bucket cannot be stressed enough!


The dual port charger. This charger has a Europlug.
I needed to create an extension wire which was equal in length to
the power cable of the projector.
See image below.
I used cable ties to tie the charger to the projector power cable.
The extension wire made for the dual port charger made it convenient
to twist the two wires around each other.
Now days you get powerstrip with USB ports builtin for
charging personal communication devices.
If you can get your hands on one of these then perhaps you may
not need a separate dual port USB charger for powering the speakers and the RPi.
Just make sure that the built in charger of the powerstrip is able
to provide enough current to the Speakers and RPi.


The backside of BenQ MS521p projector.
The wires going into it are: power cable for powering the projector,
3.5mm stereo lead from speakers and the thin HDMI cable from the Raspberry Pi.

The speakers

Raspberry Pi with the Logitech wireless mouse next to it.
The wires attached to the RPi are the mircoUSB cable for powering it
and the HDMI cable which goes to the projector.

RPi with the SD card containing Raspbian OS

RPi with the wireless mouse and keyboard


The final setup. Note how I have twisted the cables around each other
so that only three bunches of cable are coming out of the projector.
And this is how I carry the hardware to school everyday.
In a square bucket.
Its makes it easy to carry all the stuff to school and back.
And it takes under a minute to setup or store (no zips to open like in a bag).


Software:
  1. Raspbian as OS for RPi - Download the SD card image from here and write the image to the SD Card
  2. Youtube Downloader HD for Windows - Help you download MP4 versions of videos from youtube. Get from here.
  3. Paragon ExtFS for Windows - When you prepare an SD card for RPi by writing the Raspbian image to it, the card cannot be read by Windows because the card uses the ext3/ext4 files system. This software will allow you to read files from and copy files to the Raspbian SD card from within Windows. Make sure to copy your MP4 files to /home/pi folder. This software is essential in two scenarios - 1) when you do not have any USB port left because the two that RPi has have been used up and so you cannot connect a FAT32 formatted USB flash drive containing the Youtube videos. 2) RPi might not be able to provide enough power via the USB port and it might hang when you do connect a USB flash drive. So its just better to copy the videos to the SD card instead of playing them from a FAT32 formatted USB flash drive. (FAT32 flash drive can be read to and written from Windows as well as Linux). You can play MP4 and videos in other format using omxplayer. Issue the command at the terminal: "omxplayer /home/pi/abc.mp4" 

Issues:
  1. HDMI settings - To make the Raspberry Pi's audio behave properly with BenQ MS521p projector, you will need to make a minor change in \boot\config.txt. You need to uncomment (remove the #) the line hdmi_drive=2. More details here. The BenQ projector will decode the audio from HDMI and make the analog stream available via the 3.5mm stereo jack marked "Audio Out" on its back side. Edit the config.txt file on Raspberry Pi itself using the nano editor. Issue the command 
  2. USB Extension for Logitech Wireless Receiver - Raspberry Pi has two USB port. If you stick a WiFi USB dongle in one and the keyboard receiver dongle in another (these are very close to each other), the WiFi dongle's signals will interfere with the keyboard receiver and the keyboard and mouse will no longer work. To prevent this, just use a USB extension cable with the keyboard receiver to create some distance between the two dongles.
What doesn't work: inserting the Keyboard receiver dongle next to the
WiFi dongle will prevent it from receiving signals

What works: use a short extension cable to move the keyboard
receiver dongle away from the WiFi Dongle




How the setup looks like in the class.
The image is being projected onto a blackboard.
You actually do not need a white screen.

So I realized that I could optimize the setup time even further if
mounted everything on a piece of plywood and carry it as it is to school.
This would also prevent breakage because now all the cables are tucked
away safely and there is no way any kid would trip on it.
And the kids would be more than happy to carry the whole thing
between their class and my car - helps build ownership towards their own classroom.

Wednesday, April 23, 2014

Using USB car charger for powering your projects

Most car chargers available sport a MC34063 based DC-DC converter to step down 12 volts to 5 volts.


MC34063 based circuit within USB Car chargers
Such chargers can be used to derive 5 volts from 12 volt power supplies/lead acid batteries while constructing projects.


Using the charger to get 5V from 12V power supplies in projects.

Monday, March 17, 2014

Using Netlink to post events from kernelspace to userspace

I have been working on an older version of Linux on which I wanted to use netlink as a way to post events from kernelspace to userspace. I wanted to avoid using Netlink Protocol Library Suite (libnl) so that I could delve deeper into netlink and have better control of what I am doing. I was already successful in using Generic Netlink Sockets to perform two way request-response communication between kernelspace and userspace. This time I referred ACPI daemon 1.0.10's source and used genlmsg_multicast() to post events from the kernel side. The implementation of netlink has evolved over time and the following code ought to work only on kernels versioned around 2.6.35 (I tested my code on Ubuntu 10.10)

Source Code Organization:


kern/genl_event.c:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#include <net/genetlink.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include "genl_event.h"

//Code based on http://people.ee.ethz.ch/~arkeller/linux/multi/kernel_user_space_howto-3.html

static struct genl_family genl_exmpl_event_family = {
 .id = GENL_ID_GENERATE,         //Genetlink should generate an id
 .hdrsize = 0,
 .name = GENL_EXMPL_EVENT_FAMILYNAME,        //The name of this family, used by userspace application
 .version = GENL_EXMPL_EVENT_VERSION,          //Version number  
 .maxattr = GENL_EXMPL_EVENT_A_MAX,
};

static struct genl_multicast_group genl_exmpl_event_group = {
 .name = GENL_EXMPL_EVENT_GROUPNAME,
};

uint8_t genl_exmpl_event_init(void) {
 int rc;

    //Step 01: Register the new family
 rc = genl_register_family(&genl_exmpl_event_family);
 if (rc != 0) {
  goto failure;
 }

 //Step 02: Register group for the new family
 //Referred: http://lxr.free-electrons.com/source/drivers/acpi/event.c?v=2.6.33#L254
 rc = genl_register_mc_group(&genl_exmpl_event_family, &genl_exmpl_event_group);
 if (rc != 0) {
  printk("Generic Netlink register group: %i",rc);
  genl_unregister_family(&genl_exmpl_event_family);
  goto failure;
 }

 return 0; 
failure:
 return -1;
}

void genl_exmpl_event_deinit(void) {
 int ret;

    //Unregister the family
 ret = genl_unregister_family(&genl_exmpl_event_family);
 if(ret !=0) {
  printk("Generic Netlink unregister family %i\n",ret);
 }
}

int genl_exmpl_event_send(u8 *msg, int len) {
 //Referred: http://lxr.free-electrons.com/source/drivers/acpi/event.c?v=2.6.33#L183
 struct sk_buff *skb;
 int rc;
 void *msg_head;

 //Send a message back ti userspace
    //Allocate some memory, since the size is not yet known use NLMSG_GOODSIZE
 skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
 if (skb == NULL) {
  printk("Could not allocate skb\n");
  return 0;
 }

 //Create the message headers
    /* arguments of genlmsg_put: 
       struct sk_buff *, 
       int (sending) pid, 
       int sequence number, 
       struct genl_family *, 
       int flags, 
       u8 command index (why do we need this?)
    */
 msg_head = genlmsg_put(skb, 0, 0, &genl_exmpl_event_family, 0, GENL_EXMPL_EVENT_C_DO);
 if (msg_head == NULL) {
  rc = -ENOMEM;
  printk("genlmsg_put() returned error\n");
  return 0;
 }

 //Add a GENL_EXMPL_EVENT_A_MSG attribute (actual value to be sent)
 rc = nla_put(skb, GENL_EXMPL_EVENT_A_MSG, len, msg);
 if (rc != 0) {
  printk("nla_put() returned error\n");
  return 0;
 }
 
 //Finalize the message
 genlmsg_end(skb, msg_head);

    //Send the message
 rc = genlmsg_multicast(skb, 0,genl_exmpl_event_group.id,GFP_ATOMIC);
 if (rc != 0) {
  //printk("genlmsg_multicast() returned error (Group ID: %d)\n", genl_exmpl_event_group.id);
  return 0;
 } else {
  printk("genlmsg_multicast() event message sent (Group ID: %d): %s\n", genl_exmpl_event_group.id, msg);
 }
 return 0;
}

kern/genl_event.h:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* attributes (variables):
 * the index in this enum is used as a reference for the type,
 * userspace application has to indicate the corresponding type
 * the policy is used for security considerations 
 */
enum {
 GENL_EXMPL_EVENT_A_UNSPEC,
 GENL_EXMPL_EVENT_A_MSG,
    __GENL_EXMPL_EVENT_A_MAX,
};
#define GENL_EXMPL_EVENT_A_MAX (__GENL_EXMPL_EVENT_A_MAX - 1)

/* commands: enumeration of all commands (functions), 
 * used by userspace application to identify command to be executed
 */
enum {
 GENL_EXMPL_EVENT_C_UNSPEC,
 GENL_EXMPL_EVENT_C_DO,
 __GENL_EXMPL_EVENT_C_MAX,
};
#define GENL_EXMPL_EVENT_C_MAX (__GENL_EXMPL_EVENT_C_MAX - 1)

#define GENL_EXMPL_EVENT_VERSION 1
#define GENL_EXMPL_EVENT_FAMILYNAME "CONTROL_EXMPL"
#define GENL_EXMPL_EVENT_GROUPNAME "EXMPL_GRP"

uint8_t genl_exmpl_event_init(void);
void genl_exmpl_event_deinit(void);
int genl_exmpl_event_send(u8 *msg, int len);

kern/main.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <linux/kernel.h>
#include <linux/module.h>
#include <net/genetlink.h>
#include <linux/ioport.h>

#include <linux/wait.h>
#include <linux/kthread.h>
#include <asm/io.h>
#include <linux/sched.h>
#include <linux/delay.h>

#include "genl_event.h"

struct task_struct *thr_ts1;

static char nl_string[50] = "Hello";


int thread_func(void *data) {
 while (!kthread_should_stop ()) {
  sprintf(nl_string, "Hello from kernel at jiffies %ld",jiffies);
  genl_exmpl_event_send(nl_string,50);
  set_current_state (TASK_INTERRUPTIBLE);
        schedule_timeout (cputime_to_jiffies(secs_to_cputime (5))); //Do not schedule this thread again for the specified number of seconds
 }
 return 0;
}

static int __init gnKernel_init(void) {
 printk("Generic Netlink Example Module inserted.\n");

 genl_exmpl_event_init();
 
 thr_ts1 = kthread_run(thread_func, NULL, "kthread1");

 return 0;
}

static void __exit gnKernel_exit(void) {
 kthread_stop(thr_ts1);
 genl_exmpl_event_deinit();

 printk("Generic Netlink Example Module unloaded.\n");
}

module_init(gnKernel_init);
module_exit(gnKernel_exit);
MODULE_LICENSE("GPL");

     

user/main.c:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <signal.h>

#include <linux/genetlink.h>



/* Code based on
 * http://people.ee.ethz.ch/~arkeller/linux/multi/kernel_user_space_howto-3.html
 * and
 * http://sourcecodebrowser.com/acpid/1.0.10/acpid_2src_2drivers_2netlink_8c_source.html
 * and
 * http://sourcecodebrowser.com/acpid/1.0.10/acpid_2include_2acpid_2driver_2netlink_8h.html
 */



/* Generic macros for dealing with netlink sockets. Might be duplicated
 * elsewhere. It is recommended that commercial grade applications use
 * libnl or libnetlink and use the interfaces provided by the library
 */
#define NLMSG_TAIL(nlh) ((struct nlattr *) (((void *) (nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
#define NLA_OK(rta,len) ((len) >= (int)sizeof(struct nlattr) && \
                (rta)->nla_len >= sizeof(struct nlattr) && \
                (rta)->nla_len <= (len))
#define NLA_NEXT(rta,attrlen)      ((attrlen) -= NLA_ALIGN((rta)->nla_len), \
                               (struct nlattr*)(((char*)(rta)) + NLA_ALIGN((rta)->nla_len)))
#define NLA_LENGTH(len)     (NLA_ALIGN(sizeof(struct nlattr)) + (len))
#define NLA_SPACE(len)      NLA_ALIGN(NLA_LENGTH(len))
#define NLA_DATA(rta)   ((void*)(((char*)(rta)) + NLA_LENGTH(0)))
#define NLA_PAYLOAD(rta) ((int)((rta)->nla_len) - NLA_LENGTH(0))
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
#define GENL_MAX_FAM_OPS   256
#define GENL_MAX_FAM_GRPS   256


//Variables used for netlink
int nl_fd;  //netlink socket's file descriptor
struct sockaddr_nl nl_address;  //netlink socket address
int nl_family_id; //The family ID resolved by the netlink controller for this userspace program
int nl_group_id;
int nl_rxtx_length; //Number of bytes sent or received via send() or recv()
struct nlattr *nl_na;   //pointer to netlink attributes structure within the payload 
struct {    //memory for netlink request and response messages - headers are included
    struct nlmsghdr n;
    struct genlmsghdr g;
    char buf[256];
} nl_request_msg, nl_response_msg;
unsigned long nl_sequence_number = 0;
static const char *nl_family_name = "CONTROL_EXMPL";
static const char *nl_group_name = "EXMPL_GRP";
#define GENL_EXMPL_EVENT_VERSION 1



//Implementation of common netlink related methods: open, close, send, receive
int netlink_open(int * fd, unsigned long * seq_init, int protocol, int groups) {
    struct sockaddr_nl nladdr;  //netlink socket address
    socklen_t len;
    
    *fd = socket(AF_NETLINK, SOCK_RAW, protocol);
    if (*fd < 0) {
        return -1;
    }
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_groups = groups;


    if (bind(*fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
        goto fail;
    }
    len = sizeof(nladdr);
    if (getsockname(*fd, (struct sockaddr *)&nladdr, &len) < 0) {
        goto fail;
    }

    *seq_init = time(NULL);

    return 0;
fail:
    close(*fd);
    return -1;
}

int netlink_send(int fd, unsigned long * seq_num, struct nlmsghdr *n, pid_t peer, int groups) {
    struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK, .nl_pid = peer, .nl_groups = groups };
    n->nlmsg_seq = ++(*seq_num);
    return sendto(fd, n, n->nlmsg_len, 0, (struct sockaddr *)&nladdr, sizeof(nladdr));
}

int netlink_recv(int fd, struct nlmsghdr *n, pid_t *peer) {
    struct sockaddr_nl nladdr;
    socklen_t len = sizeof(nladdr);
    int ret = recvfrom(fd, n, n->nlmsg_len, 0, (struct sockaddr *)&nladdr, &len);
    *peer = nladdr.nl_pid;
    return ret;
}


int netlink_wait(int fd, unsigned long *seq_num, struct nlmsghdr *n, pid_t peer) {
    int len = n->nlmsg_len;

    for (;;) {
        pid_t sender;
        struct nlmsghdr * h;
        n->nlmsg_len = len;
        int ret = netlink_recv(fd, n, &sender);
        if (ret < 0)  {
            fprintf(stderr, "%s() | Error\n", __func__);
            continue;
        }
        if (sender != peer) {
            fprintf(stderr, "%s() | Error: Source PID mismatch\n", __func__);
            continue;
        }
        for (h = n; NLMSG_OK(h, ret); h = NLMSG_NEXT(h, ret)) {
            if (h->nlmsg_pid != getpid()) {
                fprintf(stderr, "%s() | Error: Destination PID mismatch\n", __func__);
                continue;
            }

            if (h->nlmsg_type == NLMSG_ERROR) {
                fprintf(stderr, "%s() | Error: Message receive error\n", __func__);
                return -1;
            }
            memcpy(n, h, h->nlmsg_len);
            return 0;
        }
    }
}

void netlink_close(int fd) {
    close(fd);
}

//Implementation of common netlink message parsing methods
int netlink_attr_attach(struct nlmsghdr *n, int max, int type, const void *data, int alen) {
    int len = NLA_LENGTH(alen);
    struct nlattr *nla;

    if (NLMSG_ALIGN(n->nlmsg_len) + NLA_ALIGN(len) > max) {
        return -1;
    }

    nla = NLMSG_TAIL(n);
    nla->nla_type = type;
    nla->nla_len = len;
    memcpy(NLA_DATA(nla), data, alen);
    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLA_ALIGN(len);
    return 0;
}

int netlink_attr_parse(struct nlattr *table[], int max, struct nlattr *src, int len) {
    memset(table, 0, sizeof(struct nlattr *) * (max + 1));
    while (NLA_OK(src, len)) {
        if (src->nla_type <= max)
        table[src->nla_type] = src;
        src = NLA_NEXT(src, len);
    }
    return 0;
}

static int netlink_verify_group(struct nlattr * attr, int * group, const char * expected_group_name) {
    const char *name;
    if (attr == NULL) {
        return -1;
    }

    struct nlattr *attrs[CTRL_ATTR_MCAST_GRP_MAX + 1];
    netlink_attr_parse(attrs, CTRL_ATTR_MCAST_GRP_MAX, NLA_DATA(attr), attr->nla_len - NLA_HDRLEN);

    name = NLA_DATA(attrs[CTRL_ATTR_MCAST_GRP_NAME]);
    if (strcmp(name, expected_group_name)) {
        return -1;
    }

    *group = *(__u32 *) (NLA_DATA(attrs[CTRL_ATTR_MCAST_GRP_ID]));
    return 0;
}

int netlink_wait_for_event(int fd, char * received_msg, int max) {
    char buffer[256];
    int i;
    int ret;
    struct nlmsghdr *n = (struct nlmsghdr *) &buffer;
    struct nlmsghdr * h;
    pid_t sender;

    n->nlmsg_len = 256;

    
    ret = netlink_recv(fd, n, &sender);

    if (ret < 0) {
        return -1;
    }

    i = 0;
    for (h = n; NLMSG_OK(h, ret) && i < max; h = NLMSG_NEXT(h, ret), ++i) {
        if (h->nlmsg_type == NLMSG_ERROR) {
            return -1;
        }
        printf("Event received from Kernel: %s\n",(char *)NLA_DATA(((struct nlattr *) GENLMSG_DATA(h))));
    }

    return i;
}

int main(void) {
    int return_code;
//Step 1: Open & Bind the socket. Note that protocol = NETLINK_GENERIC
    return_code = netlink_open(&nl_fd, &nl_sequence_number, NETLINK_GENERIC,0);
    if (return_code < 0) {
        fprintf(stderr, "Error: Socket could not be created\n");
  return -1;
    }
    printf("%s() | Socket Opened\n", __func__);

//Step 2. Resolve the family ID corresponding to the string "EXMPL_EVENT"
    {
        char buffer[256];
        struct nlmsghdr *nlmsg = (struct nlmsghdr *)&buffer;
        struct nlattr *attrs[CTRL_ATTR_MAX + 1];

        nlmsg->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
        nlmsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
        nlmsg->nlmsg_type = GENL_ID_CTRL;
        nlmsg->nlmsg_pid = getpid();

        struct genlmsghdr *ghdr = NLMSG_DATA(nlmsg);
        ghdr->cmd = CTRL_CMD_GETFAMILY;

        netlink_attr_attach(nlmsg, 128, CTRL_ATTR_FAMILY_NAME, nl_family_name, strlen(nl_family_name) + 1);

        if (netlink_send(nl_fd, &nl_sequence_number, nlmsg, 0, 0) < 0) {
            fprintf(stderr, "%s() | Error: sending family ID request message\n", __func__);
            netlink_close(nl_fd);
            return -1;
        }
        printf("%s() | Family Request Sent\n", __func__);

        nlmsg->nlmsg_len = 256;
        //Wait for the response message
        if (netlink_wait(nl_fd, &nl_sequence_number, nlmsg, 0) < 0) {
            fprintf(stderr, "Error: receiving family ID request message\n", __func__);
            netlink_close(nl_fd);
            return -1;
        }

        //Validate response message
        if (nlmsg->nlmsg_type != GENL_ID_CTRL) {
            fprintf(stderr, "%s() | Error: family ID request - invalid message\n", __func__);
            netlink_close(nl_fd);
            return -1;
        }
        ghdr = NLMSG_DATA(nlmsg);
        if (ghdr->cmd != CTRL_CMD_NEWFAMILY) {
            fprintf(stderr, "%s() | Error: family ID request - invalid message\n", __func__);
            netlink_close(nl_fd);
            return -1;
        }
        if (nlmsg->nlmsg_len < NLMSG_LENGTH(GENL_HDRLEN)){
            fprintf(stderr, "%s() | Error: family ID request - invalid message\n", __func__);
            netlink_close(nl_fd);
            return -1;
        }


        netlink_attr_parse(attrs, CTRL_ATTR_MAX, NLMSG_DATA(nlmsg) + GENL_HDRLEN, NLMSG_PAYLOAD(nlmsg, GENL_HDRLEN));

        if (attrs[CTRL_ATTR_FAMILY_ID]) {
            nl_family_id = *(__u32 *) (NLA_DATA(attrs[CTRL_ATTR_FAMILY_ID]));
            printf("%s() | Family ID resolved for \"%s\": %d\n", __func__, nl_family_name, nl_family_id);
        }

        if (attrs[CTRL_ATTR_MCAST_GROUPS]) {
            int i;
            struct nlattr *attrs2[GENL_MAX_FAM_GRPS + 1];
            netlink_attr_parse(attrs2, GENL_MAX_FAM_GRPS, NLA_DATA(attrs[CTRL_ATTR_MCAST_GROUPS]), attrs[CTRL_ATTR_MCAST_GROUPS]->nla_len - NLA_HDRLEN);

            for (i = 0; i < GENL_MAX_FAM_GRPS; i++) {
                if (netlink_verify_group(attrs2[i], &nl_group_id, nl_group_name) == 0) {
                    printf("%s() | Group ID resolved for \"%s\": %d\n", __func__, nl_group_name, nl_group_id);
                }
            }
        }

//Step 3. Close and Reopen Socket for specified group
        netlink_close(nl_fd);
        if (netlink_open(&nl_fd, &nl_sequence_number, NETLINK_GENERIC, nl_group_id ? (1 << (nl_group_id - 1)) : 0)) {
            fprintf(stderr, "Error: Socket could not be re-created\n");
            return -1;
        }

        netlink_wait_for_event(nl_fd, NULL, 10);
    }
    netlink_close(nl_fd);
    printf("%s() | Socket Closed\n",__func__);
    return 0;
}

Makefile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
EXTRA_CFLAGS += -I./kern

obj-m += nl_kern.o
nl_kern-objs := kern/main.o kern/genl_event.o

all: kernel-module-uninstall kernel-clean-ring-buffer kernel-build kernel-clean-temporary kernel-module-install user-build
 @tput setaf 3
 @echo "    done: all"
 @tput sgr0
clean: kernel-module-uninstall kernel-clean user-clean
 @tput setaf 3
 @echo "    done: clean"
 @tput sgr0
 
 
 
kernel-build:
 @tput setaf 1
 @echo "    kernel-build"
 @tput sgr0
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
kernel-clean:
 @tput setaf 1
 @echo "    kernel-clean"
 @tput sgr0
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
kernel-clean-temporary:
 @tput setaf 1
 @echo "    kernel-clean-temporary"
 @tput sgr0
 -rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions
 -rm -rf kern/*.o kern/*~ kern/core kern/.depend kern/.*.cmd kern/*.mod.c kern/.tmp_versions
 -rm -rf Module.symvers modules.order
kernel-module-install:
 @tput setaf 1
 @echo "    kernel-module-install"
 @tput sgr0
 -sudo insmod nl_kern.ko
kernel-module-uninstall:
 @tput setaf 1
 @echo "    kernel-module-uninstall"
 @tput sgr0
 -sudo rmmod nl_kern
kernel-clean-ring-buffer:
 @tput setaf 1
 @echo "    kernel-clean-ring-buffer"
 @tput sgr0
 sudo dmesg -c > /dev/null

 
 
user-build:
 @tput setaf 1
 @echo "    user-build"
 @tput sgr0
 gcc user/main.c -o nl_user.out
user-clean:
 @tput setaf 1
 @echo "    user-clean"
 @tput sgr0
 rm -rf *.out

Execution:
The makefile will load the kernel module automatically, so just execute nl_user.out to receive the events. The kernel module posts events every 5 seconds.

Running the code