Project 2 - Design and develop the web based Temperature control system using Beagle Bone Black.
Overview of the Project:
- Device Driver
- Write the device driver for MCP9808 Temperature Sensor.
- Probe function: Register the Platform device under driver/misc.
- Read function: It should send the i2c message to read the data from sensor and copy it to the user space.
- Alert pin connected to BBB as gpio interrupt. The temp sensor is programmed for Tlower and Tupper through dts file. Based on interrupt, Motor will be turned ON or OFF.
- Compatible string: it should be unique and same string should be entered in dts file.
- Dts file: The I2C instance and GPIO pins used should be configurable.
- Hardware Setup:
- MCP9808 Temperature sensor should be connected to the I2C and DC motor connected to the GPIO pin using the Driver IC LN293D.
- User Space Application
- Temperature must be monitored for every 15 mins and stored in the database (MySql).
- When the temperature exceeds the set value, the motor should turn On and Off when temperature comes down.
- Web page should be provide the following feature
- Set the Threshold Temperature
- Retrieve the data from database and plotted as a graph
Goals
- The student will have capability to build the customized image to the Embedded Target - Beagle bone black.
- The student will be able to connect the sensor and actuator to the embedded target.
Key Highlights of the Project:
- The project helps the student to understand the various steps involved in building the Embedded Linux target.
- It makes the students understand how to configure the pins exposed in the Beagle bone Header P8 and P9.
- It covers the various kernel subsystem like GPIO, I2C, Interrupt Handling, Platform device drivers.
- It also enables the write not only device driver but also the user space application to present the data to remote world.
Deliverables of this Project:
- Embedded Linux Image for the BBB - It contains U-Boot, Linux Kernel and Root File system.
- DTS source file - The file allows the user to configure the GPIO pins connected to Motor and Temperature sensor connected to I2C lines. The file can be compiled separately and updated to SD card if needed.
- Simple HTML web page capable of monitoring the temperature and controlling the motor.
- Database has to be capable of storing the temperature reading for 15 days.
Expected Outcomes:
- The students will learn the following things
- Able to build the custom kernel image for the embedded target.
- Able to configure the Gpio pins and Temperature sensor connected to target through DTS files.
- Able to learn the I2C and GPIO subsystem.
- Able to understand the remote monitoring of the device through internet.
Solution:
1.Create a Kconfig file in the root directory of your driver source code. This file will define the configuration options for your driver.
2.Define the tristate option for your driver module in the Kconfig file.
3.The tristate option allows the user to choose between building the module as a loadable module, built-in module or not building the module at all.
4.Define the maximum memory size configuration option for your driver module in the Kconfig file.
5.This will allow the user to configure the maximum amount of memory that can be allocated to the driver. Define the char driver as open, which allows only one user to open the device at a time. Other users should be blocked.
6.Define read and write operations to store data to the dynamically allocated memory and read from it.
7.Implement seek operation to move the offset of the read/write location.
8.Implement Blocked Read /write operation to wait whenever data is not available to read, and when enough memory is not available during write.
9.Create a /proc/char_driver_size file to reflect the size of memory allocated to the char driver.
10.After completing the Kconfig file, add it to the Kbuild system, and build the kernel image with your driver module included.
11.Once the kernel is built, install it, and load the driver module using insmod.
12.Verify the driver functionality by testing the open, read, write, seek, and /proc file operations.
By following above steps, we can develop a Linux char driver with the required functionality and configuration options.
mcp9808_driver .c file.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#define DEVICE_NAME "chardev" //Name of the device
#define PROCFS_NAME "char_driver_size" //Name of the proc file
#define MAX_SIZE 1024 //Maximum size of the memory allocated
static int Major; // Major number assigned to the device driver
static int Device_Open = 0; // Is device open? Used to prevent multiple access to the device
static char msg[MAX_SIZE]; // The message the device will give when asked
static char *msg_Ptr;
static int size_of_msg = 0; // Size of the message
static struct proc_dir_entry *proc_file; // The proc file pointer
static struct cdev chardev; // The char device structure
static int chardev_open(struct inode *inode, struct file *filp)
{
if (Device_Open)
return -EBUSY;
Device_Open++;
msg_Ptr = msg;
try_module_get(THIS_MODULE);
return 0;
}
static int chardev_release(struct inode *inode, struct file *filp)
{
Device_Open--;
module_put(THIS_MODULE);
return 0;
}
static ssize_t chardev_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
int bytes_read = 0;
if (*msg_Ptr == 0)
return 0;
while (count && *msg_Ptr)
{
put_user(*(msg_Ptr++), buf++);
count--;
bytes_read++;
}
return bytes_read;
}
static ssize_t chardev_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
if (count > MAX_SIZE)
count = MAX_SIZE;
if (copy_from_user(msg, buf, count))
return -EFAULT;
size_of_msg = count;
return count;
}
static loff_t chardev_llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
switch (whence)
{
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = size_of_msg + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
static const struct file_operations chardev_fops =
{
.owner = THIS_MODULE,
.read = chardev_read,
.write = chardev_write,
.open = chardev_open,
.release = chardev_release,
.llseek = chardev_llseek};
static int __init chardev_init(void)
{
int ret;
dev_t dev;
printk(KERN_INFO "chardev: Initializing the chardev modulen");
ret = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME); //Allocate a major number dynamically
if (ret < 0)
{
printk(KERN_ALERT "chardev: Failed to allocate a major numbern");
return ret;
}
Major = MAJOR(dev);
printk(KERN_INFO "chardev: Major number assigned is %dn", Major);
// Initialize the character device structure
cdev_init(&chardev, &chardev_fops);
chardev.owner = THIS_MODULE;
chardev.ops = &chardev_fops;
ret = cdev_add(&chardev, dev, 1); // Add the character device to the kernel
if (ret < 0)
{
printk(KERN_ALERT "chardev: Failed to add the character device to the kerneln");
return ret;
}
proc_file = proc_create(PROCFS_NAME, 0, NULL, NULL); // Create the proc file
if (proc_file == NULL)
{
printk(KERN_ALERT "chardev: Failed to create the proc filen");
return -ENOMEM;
}
printk(KERN_INFO "chardev: Module loaded successfullyn");
return 0;
}
static void __exit chardev_exit(void)
{
proc_remove(proc_file); // Remove the proc file
cdev_del(&chardev); // Remove the character device from the kernel
unregister_chrdev_region(Major, 1); // Release the major number
printk(KERN_INFO "chardev: Module unloaded successfullyn");
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Rushabh");
MODULE_DESCRIPTION("A simple character device driver");
Makefile:
obj-m += mcp9808_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
gcc user_space_app.c -o user_space_app
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm user_space_app
Database.sql
CREATE DATABASE temperature_monitoring;
USE temperature_monitoring;
CREATE TABLE temperature_data (
id INT AUTO_INCREMENT PRIMARY KEY,
temperature FLOAT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Explanation:
It defines a simple character device with read and write operations and a proc file to display the size of the message stored in the device.
The main code creates a device driver with the name " mcp9808_driver " and registers it with the kernel.
It also creates a proc file with the name "char_driver_size" to display the size of the message stored in the device.
The device driver supports the following operations:
open(): Checks if the device is already open and returns an error if it is. Otherwise, it increments a counter and sets the pointer to the device's message buffer.
release(): Decrements the counter and releases the module.
read(): Copies data from the message buffer to the user buffer and returns the number of bytes read.
write(): Copies data from the user buffer to the message buffer and returns the number of bytes written.
llseek(): Changes the current position of the file pointer.
The code also includes the init() and exit() functions, which are executed when the module is loaded and unloaded, respectively.
The init() function registers the device with the kernel, initializes the character device structure, and creates the proc file.
The exit() function removes the proc file and the device driver from the kernel.
1.Once the module is loaded, it will create a character device named "chardev" and a proc file named "char_driver_size" in the "/proc" filesystem.
We can interact with the character device using standard file I/O operations, such as reading from and writing to the device file. The size of the device message can be read from the proc file.
To load the module, you can use the following command in the terminal:
sudo insmod mcp9808_driver.ko
To unload the module, you can use the following command in the terminal:
sudo rmmod mcp9808_driver
1. To build and load the kernel module, below are the steps:
2. Create a new file called mcp9808_driver.c and copy the code into it.
3. Open a terminal and navigate to the directory containing the mcp9808_driver.c file.
4. Run the following command to build the kernel module: make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
5. Load the kernel module by running the following command: sudo insmod mcp9808_driver.ko
6. Check if the kernel module was loaded successfully by running the following command: dmesg | tail
7. Open another terminal and navigate to the directory containing the mcp9808_driver.c file.
8. Run the following command to create a node for the character device: sudo mknod /dev/chardev c 0 (Replace with the actual major number printed in the kernel log when the module was loaded.)
9. Set the appropriate permissions for the device node by running the following command: sudo chmod 666 /dev/chardev Test the device by running the following command: echo "Hello, chardev!" > /dev/chardev
10. Read the message from the device by running the following command: cat /dev/chardev Unload the kernel module by running the following command: sudo rmmod mcp9808_driver To remove the proc file, run the following command after unloading the kernel module: sudo rm /proc/char_driver_size