Linux学习笔记之小目标一:用QT界面操作板子LED
一、目标:用QT绘制一个界面,点击开按钮,板子LED点亮,点击关按钮,LED熄灭
二、设计知识点:Linux底层IO驱动,内核编程,QT编程
三、代码部分
1、驱动代码 qt-led.c
/**************************************
版本记录: chen :2017-11-11 V1.0
linux内核:2.6
硬件接法:
LED1 –> GPX1_0
LED2 –> GPK1_1
高电平点亮
驱动用法:
设备名称:qt-led
点亮一个灯:LED_ON
熄灭一个灯:LED_OFF
点亮所有灯:ALL_LED_ON
熄灭所有灯:ALL_LED_OFF
说明:
***************************************/
//Linux通用的头文件
#include
#include
#include
#include
#include
#include
//针对板子的头文件
#include
#include
#include
#define DEVICE_NAME "qt-led" /* 设备名称 */
static int LED_Major = 0; /* 主设备号 */
#define LED_ON 1
#define LED_OFF 0
#define FIRST_LED 0
#define SECOND_LED 1
#define ALL_LED 2
/* 打开设备函数,打开的时候仅仅做了GPIO申请 */
static int qt_led_open(struct inode *inode,struct file *file) {
int ret;
//打印KERN_EMERG是为了区分打印信息
printk(KERN_EMERG"KERN_EMERG:qt_led_open_success!\r\n");
//先释放GPIO
gpio_free(EXYNOS4_GPL2(0));
//申请GPIO
ret = gpio_request(EXYNOS4_GPL2(0),"LED1");
if(ret < 0){
printk(KERN_EMERG"KERN_EMERG:gpio_request EXYNOS4_GPX1_0 fail!\r\n");
return -1;
}
//设置GPIO模式,为output
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_OUTPUT);
//给GPIO赋初值,相当于是LED默认关
gpio_set_value(EXYNOS4_GPL2(0),0);
//第二个LED GPIO,跟第一个操作一模一样
gpio_free(EXYNOS4_GPK1(1));
ret = gpio_request(EXYNOS4_GPK1(1),"LED2");
if(ret < 0){
printk(KERN_EMERG"KERN_EMERG:gpio_request EXYNOS4_GPK1_1 fail!\r\n");
return -1;
}
s3c_gpio_cfgpin(EXYNOS4_GPK1(1),S3C_GPIO_OUTPUT);
gpio_set_value(EXYNOS4_GPK1(1),0);
return 0;
}
/* 关闭设备函数,关闭的时候没做啥,释放GPIO */
static int qt_led_release(struct inode *inode,struct file *file){
gpio_free(EXYNOS4_GPL2(0));
gpio_free(EXYNOS4_GPK1(1));
printk(KERN_EMERG"KERN_EMERG:qt_led_release_success!\r\n");
return 0;
}
/* GPIO控制函数 */
static long qt_led_ioctl(struct file *file,unsigned int cmd,unsigned long arg){
printk(KERN_EMERG"KERN_EMERG:qt_led_ioctl!\r\n");
//由于CMD组合比较复杂,达到目标我只用0.1就可以了,所以没有组合
//arg可以是很多值,所以用cmd表示开还是关,arg表示哪一路
switch(arg){
case FIRST_LED:{
gpio_set_value(EXYNOS4_GPL2(0),cmd);
printk(KERN_EMERG"KERN_EMERG:cmd is :%d,arg is :%ld\r\n",cmd,arg);
return 0;
break;
}
case SECOND_LED:{
gpio_set_value(EXYNOS4_GPK1(1),cmd);
printk(KERN_EMERG"KERN_EMERG:cmd is :%d,arg is :%ld\r\n",cmd,arg);
return 0;
break;
}
case ALL_LED:{
gpio_set_value(EXYNOS4_GPL2(0),cmd);
gpio_set_value(EXYNOS4_GPK1(1),cmd);
printk(KERN_EMERG"KERN_EMERG:cmd is :%d,arg is :%ld\r\n",cmd,arg);
return 0;
break;
}
default:
return -EINVAL;
break;
}
}
//由于用的是字符设备,不是平台设备,需要class结构体
static struct class *qt_led_class;
//file_operations结构体,无论字符设备还是平台设备都需要
static struct file_operations qt_led_fs = {
.owner = THIS_MODULE,
.open = qt_led_open,
.release = qt_led_release,
.unlocked_ioctl = qt_led_ioctl,
};
static int qt_led_init(void){
printk(KERN_EMERG"Led_Drv_Module_Init!\r\n");
//平台设备,要注册主设备号
LED_Major = register_chrdev(0,DEVICE_NAME,&qt_led_fs);
if(LED_Major < 0){
printk(KERN_EMERG"Led_Drv_Module_Init_Major_error!\r\n");
return LED_Major;
}
printk(KERN_EMERG"Led_Drv_Module_Init_Major_Success!\r\n");
//创建class
qt_led_class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(qt_led_class)){
printk(KERN_EMERG"Err: failed in qt_led_class.!\r\n");
return -1;
}
//创建device
device_create(qt_led_class,NULL,MKDEV(LED_Major,0),NULL,DEVICE_NAME);
printk(DEVICE_NAME"initialized.!\r\n");
return 0;
}
static void qt_led_exit(void){
//卸载设备,与注册要相反的操作
printk(KERN_EMERG"Led_Drv_Module_exit!\r\n");
unregister_chrdev(LED_Major,DEVICE_NAME);
device_destroy(qt_led_class,MKDEV(LED_Major,0));
class_destroy(qt_led_class);
}
MODULE_AUTHOR("Chenxw");
MODULE_DESCRIPTION("qt LED Driver");
MODULE_LICENSE("Dual BSD/GPL");
module_init(qt_led_init);
module_exit(qt_led_exit);
2、驱动代码Makefile
#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译qt-led.c这个文件编译成中间文件qt-led.o
obj-m += qt-led.o
#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/workstation下并解压的
KDIR := /home/topeet/workstation/iTop4412_Kernel_3.0
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/workstation/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
make -C $(KDIR) M=$(PWD) modules
#make clean执行的操作是删除后缀为o的文件
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.order
3、由于驱动C语言写的,QT是C++写的,需要写一个给QT的C语言接口文件,给C++提供操作驱动的接口函数
4、内核接口文件led-c.c
#include "led-c.h"
#include
#include
#include
#include
#include
//文件句柄申明和初始化
int qt_led_fd = 0;
//qt调用的文件打开函数
int qt_led_open(const char *devname){
//devname 是传进来的文件路径,O_RDWR是可读可写
qt_led_fd = open(devname,O_RDWR);
if(qt_led_fd < 0){
printf("Open device %s failed!\r\n",devname);
return -1;
}
printf("Qt-led driver is ok,open success!\r\n");
return 0;
}
//qt调用的文件操作函数
int qt_led_ioctl(unsigned int cmd, unsigned long led_num){
//cmd是控制开还是关,led_num就是arg,控制哪一个灯
ioctl(qt_led_fd,cmd,led_num);
return 0;
}
int qt_led_close(void){
//如果有文件打开,就去关闭打开的那个文件
if(qt_led_fd){
close(qt_led_fd);
}
printf("Dev qt led closed!\r\n");
return 0;
}
5、内核接口文件的头文件led-c.h
#ifndef LEDC_H
#define LEDC_H
//这是一段cpp的代码,加入extern "C"{和}处理其中的代码
//C++和C对产生的函数名字的处理是不一样的,C++为了兼容C
#ifdef __cplusplus
extern "C"{
#endif
extern int qt_led_open(const char *devname);
int qt_led_ioctl(unsigned int cmd,unsigned long led_num);
extern int qt_led_close();
extern int qt_led_fd;
#ifdef __cplusplus
}
#endif
#endif // LEDC_H
6、窗体文件mainwindow.cpp
//窗体设计可以自己一行代码一行代码敲
#include "mainwindow.h"
//窗体设计还可以用QT自带的UI设计,区别在于自动生成代码,文件前面自动添加ui标识
#include "ui_mainwindow.h"
//add by chen
#include "led-c.h"
//cmd取值
#define LED_ON 1
#define LED_OFF 0
//arg取值
#define FIRST_LED 0
#define SECOND_LED 1
#define ALL_LED 2
//设备文件位置,名字要跟驱动里面保持一致
#define LED_DEVICE "/dev/qt-led"
//add by chen end
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//add by chen
//设置窗口名字,还可以设置图片,这里就不加了
this->setWindowTitle("Chen qt led test!");
//实例化一个按钮
button_led_on = new QPushButton(this);
//设置按钮位置,大小
button_led_on->setGeometry(QRect(20,20,100,100));
//设置按钮显示文字
button_led_on->setText("led_on");
//设置点击事件,信号是released,槽是灯亮函数
connect(button_led_on,SIGNAL(released()),this,SLOT(ctl_led_on()));
/*下面代码跟上面一样了*/
button_led_off = new QPushButton(this);
button_led_off->setGeometry(QRect(140,20,100,100));
button_led_off->setText("led_off");
connect(button_led_off,SIGNAL(released()),this,SLOT(ctl_led_off()));
button_led_all_on = new QPushButton(this);
button_led_all_on->setGeometry(QRect(20,140,100,100));
button_led_all_on->setText("led_all_on");
connect(button_led_all_on,SIGNAL(released()),this,SLOT(ctl_led_all_on()));
button_led_all_off = new QPushButton(this);
button_led_all_off->setGeometry(QRect(140,140,100,100));
button_led_all_off->setText("led_all_off");
connect(button_led_all_off,SIGNAL(released()),this,SLOT(ctl_led_all_off()));
button_close_led_dev = new QPushButton(this);
button_close_led_dev->setGeometry(QRect(260,140,100,100));
button_close_led_dev->setText("close_dev");
/*由于要调试,在手动退出后文件未被关闭,增加一个按钮主动关闭文件*/
connect(button_close_led_dev,SIGNAL(released()),this,SLOT(ctl_close_led_dev()));
qt_led_open(LED_DEVICE);
printf("C++ Open %s \r\n",LED_DEVICE);
//add by chen finished
}
//add by chen
//控制灯亮函数
void MainWindow::ctl_led_on(){
qt_led_ioctl(LED_ON,FIRST_LED);
printf("Qt ctl_led_on IO here!\r\n");
printf("Qt cmd is:%d,arg is :%d\r\n",LED_ON,FIRST_LED);
}
void MainWindow::ctl_led_off(){
qt_led_ioctl(LED_OFF,FIRST_LED);
printf("Qt ctl_led_off IO here!\r\n");
printf("Qt cmd is:%d,arg is :%d\r\n",LED_OFF,FIRST_LED);
}
void MainWindow::ctl_led_all_on(){
qt_led_ioctl(LED_ON,ALL_LED);
printf("Qt ctl_led_all_on IO here!\r\n");
printf("Qt cmd is:%d,arg is :%d\r\n",LED_ON,ALL_LED);
}
void MainWindow::ctl_led_all_off(){
qt_led_ioctl(LED_OFF,ALL_LED);
printf("Qt ctl_led_all_off IO here!\r\n");
printf("Qt cmd is:%d,arg is :%d\r\n",LED_OFF,ALL_LED);
}
void MainWindow::ctl_close_led_dev(){
qt_led_close();
}
//add by chen finished
MainWindow::~MainWindow()
{
delete ui;
/*关闭窗体后要记得关闭文件*/
qt_led_close();//add by chen
}
7、窗体文件头文件mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
//add by chen
#include
#include
#include
#include
#include
#include
#include
//add by chen end
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
//add by chen
//私有成员,声明按钮
private:
QPushButton *button_led_on;
QPushButton *button_led_off;
QPushButton *button_led_all_on;
QPushButton *button_led_all_off;
QPushButton *button_close_led_dev;
//公有成员,声明事件实现槽函数
public slots:
void ctl_led_on();
void ctl_led_off();
void ctl_led_all_on();
void ctl_led_all_off();
void ctl_close_led_dev();
//add by chen end
};
#endif // MAINWINDOW_H
五、实际效果
先加载模块 insmod qt-led.ko
再运行Qt程序 qt-led -qws &
我的板子是有触摸,可以接鼠标。程序运行后两个灯都是出于灭的状态,点击led_on,第一个灯亮了,点击led_off第一个灯灭了。点击led_all_on,两个灯都亮了,点击led_all_off,两个灯都灭了。点击close_dev,然后再重复上面的操作,灯无变化,达到效果。
六、总结
买了书也两个礼拜了,本人从13年毕业到现在一直从事于硬件工作,从方案选型到原理图设计和PCB layout乃至量产等经验倒是一大堆。一直想学习linux部分内容,自己倒腾了这么久总算是完成第一个小目标。通过界面操作LED,Android部分还没去看。不得不说,linux学起来还有点吃力,理解和要记忆的一大堆,自我感觉还没入门,再接再厉。留下记录博客,以供自勉,如有同道之人愿意一起交流,无比乐意。