linux内核版本:3.4.2
qt 版本:5.6.0
交叉编译工具:arm-linuxgcc 4.4.3
开发板:JZ2440V3
linux驱动编程环境:vscode (环境搭建可参考博客:嵌入式Linux驱动开发IDE - Visual Studio Code配置使用(强烈推荐!!!)-CSDN博客)
如何交叉编译qt应用程序,并在开发板上运行,可参考博客:从0开始在ubuntu18.04上搭建嵌入式Linux开发环境以及嵌入式Qt5.6开发环境_linux安装qt5.6-CSDN博客
该博客源码分享: https://download.csdn.net/download/ningjianwen/10998347(内含可执行文件,可直接运行)
目标
最终效果一个主屏幕,有一个LED控制按钮, 通过该按钮进入led控制界面,通过该界面控制led灯的亮灭,如下图所示.
1. 学会编写一个简单的嵌入式Linux字符设备驱动
2. 学会编写一个简单的linux 文件io应用程序.
3. 学会使用qt creator建立嵌入式qt工程.
4. 学会qt5.6设置背景图片.
5. 学会qt5.6进行界面切换.
6. 学会如何使用qt程序操作jz2440的GPIO.
一 驱动程序编写
1.硬件电路如下图所示,可以看出三个LED灯与GPF4,5,6相连接。且当io口输出为高电平时,led灯熄灭,当io输出低电平时,led灯点亮.
2.字符设备驱动编写思路:
1.编写模块的入口函数与出口函数.(module_init,module_exit)
2.定义file_operations结构体,确定需要实现哪些函数.最基本的有open ,read ,write函数,其他根据需要添加
3.编写open,read,write等函数.
驱动的write函数需要方便单独控制每一个LED灯,也需要方便同时控制所有LED灯.
3.代码实现:
#include <linux/module.h> //定义了THIS_MODULE宏
#include <linux/fs.h> //定义了file_operations结构体
#include <asm/uaccess.h> //定义了copy_to_user函数
#include <asm/io.h> //定义了ioremap 与iounremap函数
#include <linux/device.h> //定义了class_create/device_create/class_destory/device_destory函数
//定义了class 与 class_device结构体
#include <mach/regs-gpio.h>//包含了GPIO相关宏
#include <linux/gpio.h> //包含了s3c2410_gpio_cfgpin等io操作函数
#define LED_NUM 3
static struct class *leddrv_class;
static struct class_device *leddrv_class_dev;
/**应用程序的open函数时,最终会调用该函数,配置LED控制引脚为输出*/
static int led_drv_open(struct inode *inode, struct file *file)
{
s3c2410_gpio_cfgpin(S3C2410_GPF(4),S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(S3C2410_GPF(5),S3C2410_GPIO_OUTPUT);
s3c2410_gpio_cfgpin(S3C2410_GPF(6),S3C2410_GPIO_OUTPUT);
return 0;
}
/**
* 应用程序的read函数时,最终会调用该函数
* 返回的buf中有3个数据, 第一个数据对应led1的亮灭状态,一次类推.
*/
ssize_t led_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned char led_vals[LED_NUM];
if (size > LED_NUM || size < 0)
return -EINVAL;
led_vals[0] = s3c2410_gpio_getpin(S3C2410_GPF(4));
led_vals[1] = s3c2410_gpio_getpin(S3C2410_GPF(5));
led_vals[2] = s3c2410_gpio_getpin(S3C2410_GPF(6));
copy_to_user(buf, led_vals, size);
return size;
}
/**
* 应用程序的write函数时,最终会调用该函数,调用一次只能控制一个led灯的两灭
* buf:第一个byte表示控制第几个led灯范围0~3(0表示控制所有灯), 第2个byte取值1=亮灯,0=灭灯
* size:只能等于2
*/
ssize_t led_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned char led_vals[2];
unsigned char value = 0,led_num;
if ( size != 2)//
return -EINVAL;
copy_from_user(led_vals, buf, size);
value = led_vals[1] ? 0: 1;
led_num = led_vals[0];
if(led_num == 0){//同时控制所有led
s3c2410_gpio_setpin(S3C2410_GPF(4),value);
s3c2410_gpio_setpin(S3C2410_GPF(5),value);
s3c2410_gpio_setpin(S3C2410_GPF(6),value);
}else{//单独控制单个led
s3c2410_gpio_setpin(S3C2410_GPF(3+led_num),value);
}
return 0;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
};
int major;
dev_t led_dev;
/**
*初始化函数在加载该模块时就会调用,
*注册设备
*/
static int led_drv_init(void)
{
// alloc_chrdev_region()
major = register_chrdev(0, "led_drv", &led_drv_fops);
leddrv_class = class_create(THIS_MODULE, "led_drv");
if(IS_ERR(leddrv_class))
return PTR_ERR(leddrv_class);
/* 加载模块后,/dev目录下会新增 leds */
leddrv_class_dev = device_create(leddrv_class, NULL, MKDEV(major, 0), NULL, "leds");
if(unlikely(IS_ERR(leddrv_class_dev)))
return PTR_ERR(leddrv_class_dev);
printk("led_drv_init\n");
return 0;
}
static void led_drv_exit(void)
{
unregister_chrdev(major, "led_drv");
device_destroy(leddrv_class, MKDEV(major, 0));//卸载设备
class_destroy(leddrv_class);//删除class类
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
4. Makefile文件实现:
KERN_DIR = /home/ningjw/linux-3.4.2
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_drv.o
二:Qt5应用程序编写
先来看看这个工程最终包含了那些文件
1. 使用qt creator新建工程:
注:
- 只选取了关键的几个图片;
- 图2中的ARM-LINUX如何来的,可参考我开头提到的博客;
- 图2中选择了两个Kit,通过ARM-LINUX构建的程序可运行在jz2440开发板上,通过Desktop..构建的程序可在PC运行,方便调试.
- 新建工程后,需要将工程中的nullptr指针改为0,才可以编译通过.
2. 往现有工程添加界面
文件->新建文件或项目 (命名ledcontrol.ui)
3. 设计主界面
- 进入界面设计页面,并调整geometry属性为480 X 272 (对应JZ2440的长宽)
- 往界面放置一个Frame,同样设置geometry属性为480 X 272 (即铺满窗口),
- 添加一个图片资源文件 (文件->新建文件或项目,调出下面窗口)
- 为之前添加的Frame添加背景图片
- 添加一个50 X 50的PushButton控件,并为该按钮设置背景图片. (按钮的长宽通过设置geometry属性实现, 设置背景图片通过设置styleheet实现, 这里就不再贴图了,仿照上面frame的方式就行)
- 选中PushButton控件,"鼠标右键->转到槽->clicked()->OK"
- 添加一个label空间,显示"LED控制"放在PushButton下面.
通过以上步骤,主界面设计完成,效果图如下.
4 设计led控制界面
- 设计led控制界面,如下图所示,这里都是控件,托过来摆放就行,就不在详细说明了.
- 同时选中"LED1控制"按钮上的两个QRadioButton("开与关")控件, 通过操作 "鼠标右键->添加到按钮组->新建按钮组" 将这两个控件作为一组, 这样在同一时刻,LED1上的开关只能选择一个,且不与其他QRadioButton控件产生关系.
- 对其他几组的QRadioButton控件进行同样的操作.
- 选中"LED1控制"按钮,"鼠标右键->转到槽->clicked()->OK", 并对其他几个按钮做同样的操作.
5 实现界面跳转
在主界面点击"LED控制按钮",进入LED控制界面, 在LED控制节目点击"返回主界面"进行返回.
- 在widget.cpp文件中编写"LED控制"按钮对应的槽函数如下:
#include "ledcontrol.h" //需要包含该头文件,才能引用LedControl
/**
* 该函数通过"转到槽功能自动生成"
*/
void Widget::on_led_ctl_entrance_clicked()
{
//函数内容由自己手动编写
LedControl led_dialog;
led_dialog.exec();
}
- 在ledcontrol.cpp中编写"返回主界面"对应的槽函数如下:
#include "widget.h" //Widget在该头文件声明
/**
* 该函数通过"转到槽功能自动生成"
*/
void LedControl::on_Button_Return_clicked()
{
this->hide();//必须执行,否则不会显示主界面
Widget w;
w.show();
}
- 编写"LED1控制"按钮的槽函数如下,其他几个类似,就不贴处代码了.
/* 控制led亮灭的应用函数 */
void control_led(char num,bool value)
{
int fd;
char led_val[2];
led_val[0] = num;//控制第几个led灯
led_val[1] =value;//控制led灯亮灭:1=亮,2=灭
fd = open("/dev/leds",O_RDWR);//打开jz2440板上的文件
if (fd < 0){
qDebug() << "can't open leds\n";
return ;
}
write(fd, led_val, 2);//写文件,调用该函数,最终会调用驱动中的led_drv_write函数
}
/*"LED1控制"按钮对应的槽函数*/
void LedControl::on_button_led1_ctl_clicked()
{
bool value = this->ui->radio_led1_open->isChecked();//判断是否选中"开"
control_led(1,value);
}
六 测试
- 通过make编译led_drv.c文件,生成内核模块led_drv.ko
- 构建Qt5_JZ2440工程,生成可执行文件Qt5_JZ2440
- 配置jz2440从网络启动nfs文件系统
- 复制led_drv.ko/Qt5_JZ2440到文件系统
- insmod led_drv.ko加载led驱动
- ./Qt5_JZ2440 执行该软件.