文章目录
基于荔枝派nano的纸张计数项目
配置、编译、烧录
配置编译
就不再造轮子,参考链接:
lichee nano 荔枝派入门——搭建环境_学游泳的鱼465的博客-CSDN博客
交叉编译工具链
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-musleabi-
export PATH=$PATH:/home/book/lichee/arm-buildroot-linux-musleabi_sdk-buildroot/bin
SPI FLASH 编译烧写
单个烧写
把 u-boot 烧写到第一分区
sudo sunxi-fel -p spiflash-write 0 u-boot-sunxi-with-spl.bin
把 dtb 文件烧写到第二分区
sudo sunxi-fel -p spiflash-write 0x0100000 suniv-f1c100s-licheepi-nano.dtb
把 zImage 镜像文件烧写到第三分区
sudo sunxi-fel -p spiflash-write 0x0110000 zImage
我们把 根文件系统 烧写到第四分区
sudo sunxi-fel -p spiflash-write 0x0510000 jffs2.img
整个烧写
sudo sunxi-fel -p spiflash-write 0 ./output/flashimg.bi
设备树
配置上升沿中断
要在f1c100s设备树中配置PE3上升沿中断,需要完成以下步骤:
打开设备树源文件(例如“suniv-f1c100s-licheepi-zero.dts”文件)以进行编辑。
在所需的GPIO节点中添加相关的属性,如下所示:
gpiokeys {
compatible = "licheepi,gpio_key";
button@0 {
label = "PE3 button";
gpios = <&pio 3 3 0>;
//linux,code = <KEY_POWER>;
interrupt-parent = <&pio>;
interrupts = <0 39 2>; //0 默认中断控制器 39号中断源 上升沿中断
interrupt-names = "up";
};
};
这里我们使用了"gpio-keys"设备类型,表示一个可用的GPIO按键设备。
解析上述属性,分别表示:
"label"属性表示该按键的名称。
"gpios"属性中 “&pio” 为GPIO控制器节点的名称,“3”代表端口号(PE3),“3”代表GPIO引脚,而“0”代表为输入模式。
“linux,code” 属性表示在Linux内部使用的键值代码。
"interrupt-parent"属性指定当前节点所使用的中断控制器。
"interrupts"属性指定了中断控制器号、中断号以及触发方式,即PE3所对应的GPIO中断号为39,而2表示上升沿触发。
"interrupt-names"属性用于具体明确中断的类型。
编译并烧写新的设备树文件。
挂载设备树文件并重新启动设备。
在重新启动设备后,PE3上升沿中断就能够被捕捉到,并产生相应的中断处理。
可执行文件开机自启动
Linux系统可以通过多种方式自动启动可执行文件,以下是其中的几种方式:
- 在**/etc/rc.local文件中添加启动命令**:在Linux系统启动时,会自动执行/etc/rc.local文件中的命令。因此,你可以在该文件中添加启动可执行文件的命令,例如:
/path/to/executable &
其中,/path/to/executable
是可执行文件的路径,&
表示在后台运行该程序。
-
使用systemd服务:systemd是Linux系统中的一个系统和服务管理器,可以用于管理系统启动和服务。你可以创建一个systemd服务文件,以启动可执行文件。例如,创建一个名为
myprogram.service
的服务文件,内容如下:
[Unit]
Description=My Program
[Service]
ExecStart=/path/to/executable
Restart=always
User=root
[Install]
WantedBy=multi-user.target
在该文件中,ExecStart
指定了可执行文件的路径,Restart
指定了服务在失败时自动重启,User
指定了服务运行的用户。将该文件保存到/etc/systemd/system/
目录下,然后运行以下命令启动服务:
systemctl enable myprogram.service
systemctl start myprogram.service
- 使用init.d脚本:在早期的Linux系统中,使用init.d脚本来管理系统启动和服务。你可以创建一个init.d脚本,以启动可执行文件。例如,创建一个名为
myprogram
的脚本,内容如下:
#!/bin/sh
# My Program
case "$1" in
start)
/path/to/executable &
;;
stop)
killall myprogram
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
;;
esac
exit 0
在该脚本中,/path/to/executable
是可执行文件的路径。将该脚本保存到/etc/init.d/
目录下,并运行以下命令启动服务:
chmod +x /etc/init.d/myprogram
update-rc.d myprogram defaults
/etc/init.d/myprogram start
应用程序流程图
应用程序
电容周期测量函数
long long unsigned measure(struct pollfd * fdset)
{
int gpio_value;
struct timeval start, end;
unsigned long long usec_diff;
char buf[MAX_BUF];
unsigned long long time_1 = 0;
int ret;
int d = 1;
int i = 0;
while(d)
{
ret = poll(fdset, 1, -1); //监听fdset,1个事件,-1永不超时
if (ret < 0) {
perror("poll");
break;
}
// 检查是否是GPIO上升沿事件 fdset[0].revents返回的事件结果
if (fdset[0].revents & POLLPRI) { //是否是紧急事件
// 读取GPIO当前的输入电平值
lseek(fdset[0].fd, 0, SEEK_SET);
read(fdset[0].fd, buf, MAX_BUF);
gpio_value = atoi(buf);
//printf("%d\n",)
if(gpio_value == 1) //监听到上升沿
{
//printf("get rising!\n");
i++;
if(i == 1)
{
gettimeofday(&start, NULL);
printf("开始计时...\n");
}
if(i == 500)
{
gettimeofday(&end, NULL);
usec_diff = ((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec));
start.tv_sec = 0;
start.tv_usec = 0;
i = 0;
//printf("500time : %llu\n",usec_diff);
//printf("1 time : %lf\n", usec_diff/500.0);
time_1 = usec_diff;
printf("结束计时...\n");
printf("500time : %llu\n", usec_diff);
//printf(" f : %lf\n", frequent);
d = 0;
//printf(" f : %lf\n", 500.0/usec_diff*1000000);
//printf("frequent : %lf\n", 1.0/usec_diff/500.0);
// printf("time : %f\n",usec_diff);
}
}
}
}
return time_1;
}
推理纸张数量函数
int inference(unsigned long time) //unsiged long 8个字节
{
double temp = 0;
double temp2 = 0;
temp = (double)(time/500.0) - (double)b;
temp2 = a/temp;
//对测量数据进行计算,得到页数
printf("inference:a = %f b = %f 推理里的1time = %f\n",a,b,time/500.0);
int num = (int)( temp2 + 0.5 ); //小数值四舍五入求纸张数 x = a/(y - b)
printf("num = %d\n",num);
return num;
}
求反比例拟合 函数
void inverse_fun(int *x, float *y, int num, float* a, float *b)
{
int i = 0;
//最小二乘法拟合反比函数
float sum_x = 0.0,sum_y=0,sum_xy=0.0,sum_xx=0;
for(i=0; i<num; i++)
{
sum_x += 1/x[i];
sum_y += y[i];
sum_xy += 1/x[i]*y[i];
sum_xx += pow(1/x[i], 2);
}
//计算回归方程系数
*a = (num * sum_xy - sum_x * sum_y) / (num * sum_xx - pow(sum_x, 2));
*b = (sum_y - (*a) * sum_x) / num;
printf("a = %f b = %f\n",*a,*b);
return ;
}
按键线程
void *thread_key( void * arg)
{
struct pollfd fdset[2];
int fd,fd1, ret;
char buf[MAX_BUF];
int gpio_value;
int gpio_value1;
int res = -1;
// 切换到GPIO设备文件夹
chdir("/sys/class/gpio");
// 导出GPIO引脚
system("echo 132 > export");
system("echo 133 > export");
// 设置GPIO输入
system("echo in > gpio132/direction");
system("echo in > gpio133/direction");
// 将GPIO设为中断模式,并监听上升沿中断事件
system("echo rising > gpio132/edge");
system("echo rising > gpio133/edge");
// 打开GPIO设备文件
fd = open("/sys/class/gpio/gpio132/value", O_RDONLY);
if (fd < 0) {
perror("gpio file open");
return NULL;
}
fd1 = open("/sys/class/gpio/gpio133/value", O_RDONLY);
if (fd1 < 0) {
perror("gpio file open");
return NULL;
}
// 监听GPIO的上升沿事件
fdset[0].fd = fd; // 待监听的文件描述符
fdset[0].events = POLLPRI; // 待见听的事件
fdset[1].fd = fd1; // 待监听的文件描述符
fdset[1].events = POLLPRI; // 待见听的事件
//初始化信号量,其初值为0
res = sem_init(&sem, 0, 0);
if(res == -1)
{
perror("semaphore intitialization failed\n");
exit(EXIT_FAILURE);
}
key_init = 1;
printf("key_init初始化完成\n");
//ioctl(fd, GPIO_SET_DEBOUNCE, 10);
while (1) {
// 等待GPIO上升沿事件发生
ret = poll(fdset, 2, -1); //监听fdset,1个事件,-1永不超时
if (ret < 0) {
perror("poll");
break;
}
// 检查是否是GPIO上升沿事件 fdset[0].revents返回的事件结果
if (fdset[0].revents & POLLPRI) { //是否是紧急事件
// 读取GPIO当前的输入电平值
lseek(fdset[0].fd, 0, SEEK_SET);
read(fdset[0].fd, buf, MAX_BUF);
usleep(10000); //消抖 10ms
gpio_value = atoi(buf);
//printf("%d\n",)
if(gpio_value == 1) //监听到上升沿
{
usleep(5000);
printf("pe4 get rising!\n");
key1 = 1;
key2 = 0;
if(adjust_num_button == 1) //用于修改adjust_num
{
adjust_num = adjust_num + 1;
lv_label_set_text_fmt(label_but, "%s: %d", "adjust num", adjust_num);
}
else if(adjust_paper_num_button == 1) //用于修改adjust_paper_num
{
*(adjust_paper_num_p+global_i) = *(adjust_paper_num_p+global_i) + 1;
lv_label_set_text_fmt(label_but,"%s: %d", "adjust_paper num", *(adjust_paper_num_p+global_i));
}
else
{
sem_post(&sem);//把信号量加1 通知main的wait
printf("post!\n");
}
}
}
if (fdset[1].revents & POLLPRI) { //是否是紧急事件
// 读取GPIO当前的输入电平值
lseek(fdset[1].fd, 0, SEEK_SET);
read(fdset[1].fd, buf, MAX_BUF);
usleep(10000); //消抖 10ms
gpio_value1 = atoi(buf);
//printf("%d\n",)
if(gpio_value1 == 1) //监听到上升沿
{
printf("pe5 get rising!\n");
key1 = 0;
key2 = 1;
sem_post(&sem); //把信号量加1 通知main的wait
printf("post!\n");
}
}
}
close(fd);
close(fd1);
return 0;
}
LVGL线程
void *thread_lvgl( void * arg)
{
/*LittlevGL init*/
lv_init();
/*Linux frame buffer device init*/
fbdev_init();
/*A small buffer for LittlevGL to draw the screen's content*/
static lv_color_t buf[DISP_BUF_SIZE];
/*Initialize a descriptor for the buffer*/
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);
/*Initialize and register a display driver*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = fbdev_flush;
disp_drv.hor_res = 800;
disp_drv.ver_res = 480;
lv_disp_drv_register(&disp_drv);
lvgl_init = 1;
printf("lvgl_init初始化完成\n");
//由于触摸芯片不合适,这里不适用触摸方式
/* Linux input device init */
//evdev_init();
/* Initialize and register a display input driver */
// lv_indev_drv_t indev_drv;
// lv_indev_drv_init(&indev_drv); /*Basic initialization*/
// indev_drv.type = LV_INDEV_TYPE_POINTER;
// indev_drv.read_cb = evdev_read;
//lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv);
/*Create a Demo*/
//lv_100ask_demo_course_1_3_1();
label_base = lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象
label_top = lv_label_create(lv_scr_act());
label_but = lv_label_create(lv_scr_act());
label_key1 = lv_label_create(lv_scr_act());
label_key2 = lv_label_create(lv_scr_act());
lv_label_set_text(label_top, " ");
lv_label_set_text(label_base, " ");
lv_label_set_text(label_key1, "[key1]measure/+1/default ");
lv_label_set_text(label_key2, "[key2]adjust/confirm/hand");
lv_label_set_text(label_but, "[key1]measure [key2]adjust");
lv_obj_set_style_text_font(label_top , &lv_font_montserrat_48, 0); // 为了方便,这里使用本地(私有)样式
lv_obj_set_style_text_font(label_base, &lv_font_montserrat_48, 0); // 为了方便,这里使用本地(私有)样式
lv_obj_set_style_text_font(label_but , &lv_font_montserrat_48, 0); // 为了方便,这里使用本地(私有)样式
lv_obj_set_style_text_font(label_key1 , &lv_font_montserrat_48, 0); // 为了方便,这里使用本地(私有)样式
lv_obj_set_style_text_font(label_key2 , &lv_font_montserrat_48, 0); // 为了方便,这里使用本地(私有)样式
lv_obj_align(label_top , LV_ALIGN_CENTER, 0, 0);
lv_obj_align(label_base , LV_ALIGN_CENTER, 0, 50);
lv_obj_align(label_but , LV_ALIGN_CENTER, 0, 100);
lv_obj_align(label_key2 , LV_ALIGN_DEFAULT, 0, 50);
/*Handle LitlevGL tasks (tickless mode)*/
while(1) {
lv_task_handler();
usleep(5000);
}
return 0;
}
主函数
int main()
{
pthread_t lvgl_pid;
pthread_t key_pid;
struct pollfd fdset[1];
int fd;
pthread_create( &lvgl_pid, NULL, thread_lvgl, NULL);
pthread_create( &key_pid, NULL, thread_key, NULL);
//终端ctrl+c 执行回调函数
struct sigaction sa;
sa.sa_handler = close_sig_handler;
assert(sigaction(SIGINT,&sa,NULL)!= -1);//捕获到信号sig,使用sa中规定的方法处理
// 切换到GPIO设备文件夹
chdir("/sys/class/gpio");
// 导出GPIO引脚
system("echo 131 > export");
// 设置GPIO输入
system("echo in > gpio131/direction");
// 将GPIO设为中断模式,并监听上升沿中断事件
system("echo rising > gpio131/edge");
// 打开GPIO设备文件
fd = open("/sys/class/gpio/gpio131/value", O_RDONLY);
if (fd < 0) {
perror("gpio file open");
return -1;
}
// 监听GPIO的上升沿事件
fdset[0].fd = fd; // 待监听的文件描述符
fdset[0].events = POLLPRI; // 待见听的事件
/*measure声明结束*/
while (1)
{
while(!(key_init && lvgl_init))
{
printf("初始化未完成!\n");
}
printf("初始化完成!\n");
{
printf("wait post!\n");
sem_wait(&sem);//把信号量减1,进入阻塞,收到pose此线程从阻塞转为运行
printf("recevice post!\n");
{
if(key1 == 1)
{
printf("开始纸张计数...\n");
lv_label_set_text(label_but, "measuring...");
unsigned long time = 0;
time = measure(fdset);
printf("开始推理...\n");
printf("推理前的 500time = %f\n",time);
paper_num = inference(time);
printf("纸张数为:%d\n",paper_num);
lv_label_set_text(label_but, "measure finish");
lv_label_set_text(label_top, "Measurement mode");
lv_label_set_text_fmt(label_base, "%s: %d", "Paper num", paper_num);
key1 == 0;
}
else if(key2 == 1)
{
key2 = 0;
printf("请输入选择校准方式:[1]默认 [2]手动校准\n");
lv_label_set_text(label_but, "[key1]default [key2]hand");
sem_wait(&sem);//把信号量减1,进入阻塞,收到pose此线程从阻塞转为运行
printf("校准wait\n");
{
if(key1 == 1)
{
a = 228.635208;
b = 459.343140;
lv_label_set_text(label_but, "defualt adjust");
printf("a = %f b = %f \n",a,b);
key1 == 0;
}
else if(key2 == 1)
{
adjust_num_button = 1;
lv_label_set_text(label_top, "Calibration mode");
lv_label_set_text_fmt(label_but, "%s: %d", "adjust num", adjust_num);
printf("请输入要校准多少个点\n");
sem_wait(&sem); //等待key2确定
adjust_num_button = 0;
lv_label_set_text_fmt(label_but, "%s: %d", "adjust num", adjust_num);
adjust_paper_num_p = (int *)malloc( sizeof(int)*adjust_num );
//申请了adjust_num个字节的动态内存
x_point = (int *)malloc( sizeof(int)*adjust_num );
y_point = (float *)malloc( sizeof(int)*adjust_num );
memset(adjust_paper_num_p, 0, sizeof(int)*adjust_num); //纸张数组初始化置0
memset(x_point, 0, sizeof(int)*adjust_num);
memset(y_point, 0, sizeof(int)*adjust_num);
sleep(1); //延时1秒 可视观感
for(global_i = 0; global_i < adjust_num; global_i++)
{
printf("请输入校准纸张数值\n");
adjust_paper_num_button = 1;
lv_label_set_text_fmt(label_but, "%s: %d", "adjust_paper_num",* (adjust_paper_num_p+global_i));
sem_wait(&sem); //等待key2确定
adjust_paper_num_button = 0;
*(x_point+global_i) = *(adjust_paper_num_p+global_i);
lv_label_set_text_fmt(label_but, "%s: %d", "adjust_paper num", * (x_point+global_i));
printf("开始测量...\n");
*(y_point+global_i) = measure(fdset);
printf("测量完成\n");
}
inverse_fun(x_point, y_point, adjust_num, (float *)&a, (float *)&b);
printf("校准完毕\n");
lv_label_set_text(label_but, "adjust finish");
free(adjust_paper_num_p); //清楚动态申请的内存,防内存泄漏
adjust_paper_num_p = NULL;//防止后面的程序再用到
free(x_point); //清楚动态申请的内存,防内存泄漏
x_point = NULL;//防止后面的程序再用到
free(y_point); //清楚动态申请的内存,防内存泄漏
y_point = NULL;//防止后面的程序再用到
key2 == 0;
}
}
key2 = 0;
}
}
lv_label_set_text(label_but, "[key1]measure [key2]adjust");
}
}
}
报错记录
应用
Segmentation fault
经检查,main 的while(1)里执行代码没有等其他线程初始化完后再执行
解决方案:互斥锁、条件变量 或 设置变量等变量为一再往下执行,否终一直循环
while(!(key_init && lvgl_init))
{
printf("初始化未完成!\n");
}
要实现本线程执行的代码需等待其他线程的初始化后才能执行,可以使用线程同步相关的函数来实现。具体的实现流程如下:
- 定义一个条件变量和一个互斥量
pthread_cond_t g_cond;
pthread_mutex_t g_mutex;
- 在等待的地方使用条件变量等待
pthread_mutex_lock(&g_mutex);
while (!g_ready) { // g_ready 另外线程处理完或初始化后会置为 true
pthread_cond_wait(&g_cond, &g_mutex); // 等待条件变量
}
pthread_mutex_unlock(&g_mutex);
- 在另外的线程执行完初始化后调用下面的代码通知等待的线程
pthread_mutex_lock(&g_mutex);
g_ready = true; // 设为 true 表示可以执行了
pthread_mutex_unlock(&g_mutex);
pthread_cond_signal(&g_cond); // 通知等待的线程
这样就可以实现本线程执行的代码需等待其他线程的初始化后才能执行了。当然,这只是一个基本的实现方式,具体实现要根据实际的业务场景来进行优化,例如加入超时等机制,以应对异常情况的发生。
error: variably modified ‘adjust_paper_num’ at file scope
int adjust_num_button = 0; //校正点数按键开关
int adjust_num = 5; //校正点数
int adjust_paper_num_button = 0;//校正纸张数按键开关
int adjust_paper_num[adjust_num]; //校正纸张数
adjust_num是变量所以报错
猜测用malloc 来解决
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p = (int *)malloc( sizeof(int)*10 );//申请了40个字节的动态内存
for(int i=0; i<10; i++)
{
p[i] = i;
}
for(int i=0; i<10; i++)
{
printf("%d\n",p[i]);
}
free(p);
p = NULL;//防止后面的程序再用到
return 0;
}
Arithmetic exception
.ArithmeticException:算术运算异常。 除数不能为0
驱动
Linux内核不匹配
insmod gpio_read_int__drv.ko
[ 81.209163] gpio_read_int__drv: version magic '4.15.0-rc8+ mod_unload ARMv5 p2v8 ' should be '5.2.4 ARMv5 p2v8 '
[ 81.248199] gpio_read_int__drv: version magic '4.15.0-rc8+ mod_unload ARMv5 p2v8 ' should be '5.2.4 ARMv5 p2v8 '
insmod: can't insert 'gpio_read_int__drv.ko': invalid module format
# uname -r
5.2.4
解决方案:
更换内核源码编译
国内源Linux源码:http://mirror.bjtu.edu.cn/kernel/linux/kernel/
驱动程序没有进入中断,如何检查
如果 Linux 驱动程序没有进入中断,可以按照以下步骤逐个排查问题:确认中断号是否正确:首先需要确认中断号是否正确,并确保使用了正确的中断号来初始化中断。
可以通过以下命令查看中断号及其处理程序:
cat /proc/interrupts
如果设备的中断号没有出现在中断列表中,则表示设备可能没有正确配置或初始化中断。
确认中断请求 (IRQ) 是否被成功注册:在驱动程序中,可以使用 request_irq
函数来请求中断。如果该函数返回错误,则表示中断请求失败。通常会在初始化函数中使用 request_irq
函数来注册中断,例如:ret = request_irq(irq, irq_handler, IRQF_TRIGGER_RISING, "my_device", NULL); if (ret) { printk(KERN_ERR "Failed to request IRQ %d\n", irq); return ret; }
如果返回值 ret
不为 0,则表示中断请求失败。可以通过将日志级别设置为调试级别(KERN_DEBUG
)来查看 request_irq 函数的详细信息。确认中断触发类型是否正确:中断请求时需要指定中断触发类型。如果指定的触发类型与实际情况不符,则中断可能不会被触发。在 request_irq 函数中使用 IRQF_TRIGGER_RISING
来指定上升沿触发中断,使用 IRQF_TRIGGER_FALLING
来指定下降沿触发中断。
确认中断处理函数是否正确:正确的中断处理函数应该满足以下要求:它应该是 irqreturn_t
类型的。它应该声明为 irq_handler_t
类型,或者在驱动程序中的其他代码中声明为该类型。声明中的第一个参数应该是中断号。声明中的第二个参数应该是指向私有数据的指针,例如设备结构体。它应该返回 IRQ_HANDLED
。它不能访问锁或调用内核函数,因为这可能会导致死锁或调度问题等。
确认中断请求是否与其他驱动程序或处理器资源冲突:如果该中断请求与其他驱动程序或处理器资源冲突,那么中断可能无法正常触发。可以通过查看中断分配、注册和释放的顺序来分析是否有冲突。
确认硬件是否正确连接:如果硬件没有正确连接,那么中断可能无法正常触发。可以使用示波器或信号分析工具来监视中断信号并检查硬件是否正确连接。希望这些步骤能够帮助您诊断 Linux 驱动程序未进入中断的问题。
经查看,
#cat /proc/interrupts
中断次数 中断控制器 中断号 触发方式 触发这个中断的设备/组件
16: 2260 sun4i_irq 13 Edge timer@1c20c00
17: 0 sun4i_irq 7 Edge mv64xxx_i2c
18: 240388 sun4i_irq 10 Edge sun6i-spi
19: 370 sun4i_irq 29 Edge 1c0c000.lcd-controller
20: 3677 sun4i_irq 23 Edge sunxi-mmc
24: 96 sun4i_irq 1 Edge ttyS0
25: 0 sun4i_irq 26 Edge musb-hdrc.1.auto
61: 1 sunxi_pio_edge 34 Edge usb0-id-det
Err: 0
没有39号中断线,设备可能没有正确配置或初始化中断
经检查, gpio_read_int_drv.c驱动程序中request_irq返回值为-517,可判断错误在初始化中断。再往上查发现of_gpio_count 函数返回 -2 通常表示在设备树中没有找到指定节点或者该节点下没有 GPIO 节点。
这种情况可能会发生在以下几种情况下:
指定的设备树节点不存在:如果传递给 of_gpio_count 函数的设备树节点指针为 NULL,或者该节点在设备树中不存在,那么函数会返回 -2。
指定的设备树节点下没有 GPIO 节点:如果指定的设备树节点存在,但是该节点下没有 GPIO 节点,那么函数也会返回 -2。
函数调用出错:如果在函数调用过程中出现了错误,例如内存分配失败、设备树解析错误等,那么函数也会返回 -2。
如果 of_gpio_count 函数返回 -2,可以通过以下方法进行排查和解决:
1检查设备树节点是否存在:确保传递给 of_gpio_count 函数的设备树节点指针不为 NULL,且该节点在设备树中存在。
2检查设备树节点下是否存在 GPIO 节点:使用设备树编辑器或者命令行工具查看指定节点下的子节点,确保该节点下存在 GPIO 节点。
3检查函数调用是否正确:确保函数调用的参数正确,例如设备树节点指针、设备树路径等。
4检查系统日志:查看系统日志,查找与 GPIO 相关的错误信息,例如设备树解析错误、内存分配失败等。
总之,of_gpio_count 函数返回 -2 可能是由多种原因引起的,需要根据具体情况进行排查和解决。
检查方式
查看设备树
cat /sys/firmware/devicetree/base
cat /sys/firmware/devicetree/base
设备节点存在且有GPIO节点。排除1、2
查看系统日志
- 使用 dmesg 命令:dmesg 命令可以显示内核启动信息和系统日志。在终端中输入 dmesg 命令即可查看系统日志。可以使用 grep 命令过滤关键字,例如 dmesg | grep error 可以查找系统日志中包含 error 关键字的信息。
- 查看 /var/log/syslog 文件:syslog 文件是系统日志的主要存储文件,包含了系统运行过程中的各种事件和错误信息。可以使用文本编辑器或者命令行工具查看该文件,例如使用 cat /var/log/syslog 命令可以查看 syslog 文件的内容。
- 使用 journalctl 命令:journalctl 命令是 systemd-journald 日志管理器的命令行工具,可以查看系统日志和服务日志。可以使用 journalctl 命令查看指定时间范围内的日志信息,例如 journalctl -u ssh.service 可以查看 ssh 服务的日志信息。
- 查看 /var/log/messages 文件:messages 文件是系统日志的备份文件,包含了系统运行过程中的各种事件和错误信息。可以使用文本编辑器或者命令行工具查看该文件,例如使用 cat /var/log/messages 命令可以查看 messages 文件的内容。
# dmesg | grep error
[ 1.470073] platform regulatory.0: Direct firmware load for regulatory.db failed with error -2
[ 966.714277] 100ask_gpio_key: probe of gpiokeys failed with error -1
[ 966.714277] 100ask_gpio_key: probe of gpiokeys failed with error -1:这行日志信息表示 100ask_gpio_key 驱动的探测失败,错误码为 -1。100ask_gpio_key 是一个 GPIO 按键驱动,用于检测 GPIO 端口的按键事件。这个错误可能会导致按键无法正常工作,需要检查驱动是否正确安装、GPIO 端口是否正确配置等。
检查结果
设备树和驱动均可能出问题
insmod装载警告
这个警告意味着在代码的某个地方发生了一个问题,可能会导致系统出现问题。通常这个警告是由于内存分配失败引起的,可能是由于系统没有足够的内存可用。
该警告通常会显示警告的位置,如 mm/page_alloc.c:3926
,这是发出警告的文件和行数。警告信息的其余部分提供了其他有关问题的提示和细节。
在大多数情况下,这个警告并不一定意味着出现了严重问题,但它表明了需要进一步查找并诊断问题的可能性。以下是一些可能导致这个警告的原因:
- 内存资源不足,可能是由于内存泄漏或者系统中内存分配不当引起的。
- 系统中出现了内存碎片,导致无法分配连续的内存段。
- 在内核/驱动程序中存在错误或者不当使用内存的代码,导致内存分配失败。
如果出现这个警告,建议您检查代码的相关部分,确认内存使用的方式是否合理,排除内存泄漏等问题。同时,您可以使用一些工具来对系统的内存使用情况进行分析和诊断,例如内存泄漏检测工具、内存分配跟踪工具等。
总之,这个警告是一个需要引起注意的警告,需要进行检查和诊断才能确保系统的正常运行
如果设备树能够成功匹配,并且/dev已经有相关设备,但是在驱动程序中调用of_get_gpio_flags
函数失败,可能是由于以下原因之一:
- 设备树中GPIO节点的属性设置不正确,导致无法正确解析GPIO的信息。你可以检查设备树中GPIO节点的属性设置是否正确,包括GPIO控制器的名称、GPIO编号、GPIO的方向和电平等信息。
- 设备树中GPIO节点的名称与驱动程序中使用的名称不匹配。你可以检查驱动程序中使用的GPIO节点名称是否与设备树中的名称一致。
- 设备树中GPIO节点的状态不正确,导致无法正确解析GPIO的信息。你可以检查设备树中GPIO节点的状态是否正确,包括GPIO控制器的状态、GPIO的状态等信息。
- 设备树中GPIO节点的属性设置正确,但是驱动程序中的GPIO操作不正确,导致无法正确读取或写入GPIO的状态。你可以检查驱动程序中GPIO操作的代码是否正确,包括GPIO的初始化、GPIO的方向设置、GPIO的电平设置等信息。
如果以上方法都无法解决问题,你可以尝试使用调试工具来进一步分析问题,例如使用printk
函数输出调试信息,或者使用调试器来跟踪代码执行过程。
项目实图
创作不易,此文已将大部分代码开源,觉得不错的话,请三连支持!
转发此文章还请注明出处,谢谢!