项目原作者为:基于imx6ull的智能家居温湿度监控系统
如有侵权,请联系我删除
本博文以正点原子的 imx6ull 开发板为例,以一个智能家居小项目为demo说明 linux 驱动开发和应用开发的联系。并对源码作了小白式的学习和说明
目录
0. 个人简介 && 授权须知
📋 个人简介
- 💖 作者简介:大家好,我是喜欢记录零碎知识点的菜鸟打工人。😎
- 📝 个人主页:欢迎访问我的博客主页🔥…
- https://blog.csdn.net/qq_39217004?spm=1010.2135.3001.5343
- 🎉 支持我:点赞👍+收藏⭐️+留言📝
- 📣 系列专栏:嵌入式Linux开发 🍁 🍁
- 💬格言:写文档啊不是写文章,重要的还是直白!🔥
转载文章,禁止声明原创;不允许直接二次转载,转载请根据原文链接联系作者
若无需改版,在文首清楚标注作者及来源/原文链接,并删除【原创声明】,即可直接转载。
但对于未注明转载来源/原文链接的文章,我将保留追述的权利。https://blog.csdn.net/qq_39217004?spm=1010.2135.3001.5343
作者:积跬步、至千里
项目功能:
- 1.通过sht20温湿度芯片采样温湿度,可在手机APP上订阅温湿度
- 2.在手机APP上控制imx6ull开发板上的LED的亮灭。
- 3.oled上显示温湿度采样值以及实时时间
- 4.当温度超过预警值时,oled屏幕显示高温报警,打开蜂鸣器报警
项目执行之前,先手动加载驱动文件,然后运行应用程序:
1. 【应用层】项目初始化
- 读取 ini 配置文件中的 mqtt broker 信息
- 安装信号(kill crtl+c 网络重连)
- 初始化日志系统
- 初始化 mqtt 客户端
- 初始化 oled
接下来就项目中用的的知识,开始学习。
【加载驱动文件】
insmode i2c_sht20.ko
insmode chrdevled.ko
insmode spi_oled.ko
【执行应用程序】
./TEMP_HUM_APP shiyansshi
那么在成熟的项目中,开机 如何自动加载所需要的驱动,并且执行自定义的引用程序呢?
(这个暂且不探讨)
1.1 ini 文件解析器
实现将 mqtt.ini
文件中的内容解析出来,通过函数传参的形式,存储在自定义的结构体中 s_mosquitto_data mqtt_data;
1.2 signal 的初始化
对进程接收到的一些信号进行处理方式的设置。
- 进程收到
SIGTERM
或者SIGINT
信号时,会调用sign_handle
函数来进行相应处理。 - 进程收到
SIGPIPE
信号时,会直接忽略该信号。SIGPIPE
信号通常在往一个已经关闭的管道写数据等情况下产生
1.3 log 初始化
int logger_init(char *filename, int loglevel)
- 若传入的参数是 NULL 就使用 标准输出 stdout,
g_logger.fp
指针指向stderr
(通常stderr
是标准错误输出流),也就是把log打印到终端呈现出来 - 若传入的参数不空,,
g_logger.fp
指针指向打开的文件句柄,fopen(filename, “a”);a
是以追加的形式打开的,就会把 log 记录到文件中
初始化完成后,比如我要记录一个错误:
strerror(errno) 中的 errno 是一个全局变量,他记录最近一次错误的值,也就是 mosquitto_lib_init 函数的错误返回值,
通过 strerror() 函数转化为字符的形式打印出来
//mosquitto 初始化
if( mosquitto_lib_init() != MOSQ_ERR_SUCCESS ) {
log_error( "mosquitto lib init failure:%s\n", strerror(errno) );
goto Cleanup_mosq_lib;
}
下面研究一下,log_error 函数,最终调用了 log_generic 函数,该函数的作用是:
-
功能是按照特定的格式将日志信息输出到 【预先配置好的日志输出目标】 ,这取决于
g_logger.fp
的指向- (1)
g_logger.fp
指针指向打开的文件句柄,fopen(filename, “a”); 时,输出到日志文件中 - (2)
g_logger.fp
指针指向stderr
(通常stderr
是标准错误输出流)时,就将信息打印出来到终端窗口
- (1)
-
注意:
fflush
函数。调用fflush
函数强制将输出缓冲区中的数据立即写入到对应的输出目标(文件或者标准输出)。这样做可以确保日志信息能及时被输出,而不是等待缓冲区满了才写入,尤其在一些需要实时查看日志的场景下比较重要。
1.4 mqtt 初始化
// 初始化
mosquitto_lib_init
// 创建一个新的客户端
mosquitto_new
//设置连接代理账号密码
mosquitto_username_pw_set(mosq,mqtt_data.user,mqtt_data.password)
// 设置收到订阅消息后的回调函数
mosquitto_message_callback_set(mosq,dra_message_callback);
1.5 其他
OLED 初始化、温湿度传感器初始化、应该在 open 的时候就调用驱动层 完成了
2. 【应用层】项目整体逻辑
解释 README.md 文件中的流程图:
while(!p_stop)
{
// oled 刷屏
// mqtt 连接
// 时间采样,读取温湿度,oled显示 + mqtt 上报
}
关于循环条件 p_stop 的说明:
- 初始化赋值为 0,因此 while 是执行的
- 当进程收到设定的信号,
SIGTERM
和SIGINT
时,执行sign_handle
回调函数,将 p_stop 变量置 1 ,所以 while 就停止执行了
3. 【驱动层】分析
驱动层 driver 文件夹下,有 makefile 文件,make指令可以将驱动文件编译为 .ko 文件,分别是:
- spi_oled.ko
- i2c_sht20.ko
- chrdevled.ko
使用前,先加载驱动,本例子是手动加载的 需要 insmod 相应的 ko 文件
3.1 以 iic 驱动为例
当 insmod i2c_oled.ko 加载驱动文件指令之后,首先回调 i2c_driver 的 probe
函数,该函数固定的套路,向内核注册并添加一个设备:
- 注册设备号 alloc_chrdev_region
- 初始化字符设备 cdev_init
- 添加一个字符设备 cdev_add
- 创建 class class_create
- 创建设备 device_create
-
定义一个全局变量,用作和应用层数据交互
unsigned char *oled_data; //用作缓冲的全局变量
-
向内核申请分配一段空间
oled_data = kmalloc(1024,GFP_KERNEL);
-
设置页面的预留属性
SetPageReserved(virt_to_page(oled_data));
-
read 时,将数据从内核空间复制到用户空间
copy_to_user