Linux驱动开发进阶(八)- GPIO子系统BSP驱动

news2025/4/19 19:42:39

文章目录

  • 1、前言
  • 2、pinctrl子系统
  • 3、pinctrl bsp驱动
  • 4、gpio子系统
  • 5、gpio bsp驱动

1、前言

  1. 学习参考书籍以及本文涉及的示例程序:李山文的《Linux驱动开发进阶》
  2. 本文属于个人学习后的总结,不太具备教学功能。

2、pinctrl子系统

在讨论gpio子系统时,一般要带上pinctrl子系统。pinctrl子系统管理着io的功能复用,gpio子系统依赖于pinctrl子系统。所以先讨论pinctrl子系统。

先看设备树,下面是瑞芯微平台的pinctrl控制器的设备树节点:

&pinctrl {
    compatible = "rockchip,rk3568-pinctrl";
    xl9535:xl9535 {
        rockchip,pins = <4 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>;
    };
};

下面是全志平台的pinctrl控制器的设备树节点:

&pio {
    compatible = "allwinner,sun8iw20-pinctrl";
    csi_mclk0_pins_a: csi_mclk0@0 {
        pins = "PE3";
        function = "csi0";
        drive-strength = <10>;
    };
}

上面展示了两个不同平台的pinctrl控制器设备树节点。我们不关心他们配置了什么内容,但值得注意的是,他们用于表示IO复用的属性是不一样的。这也说明了这方面是高度自由的,通常都由厂家来定义。

Linux内核使用一个pinctrl_desc结构体来描述pinctrl控制器:

struct pinctrl_desc {
	const char *name;	// pinctrl的名称
	const struct pinctrl_pin_desc *pins;	// 引脚描述结构体
	unsigned int npins;	// 引脚数量
	const struct pinctrl_ops *pctlops; // 引脚的控制操作集合
	const struct pinmux_ops *pmxops;   // 引脚的复用操作集合
	const struct pinconf_ops *confops; // 引脚的配置操作集合
	struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
	unsigned int num_custom_params;
	const struct pinconf_generic_params *custom_params;
	const struct pin_config_item *custom_conf_items;
#endif
	bool link_consumers; // 用于指示是否将使用该pinctrl控制器的设备链接到该控制器
};

先看引脚描述结构体:

struct pinctrl_pin_desc {
	unsigned number;	// GPIO引脚的编号
	const char *name;	// GPIO引脚名称
	void *drv_data;		// 私有数据
};

再看pinctrl_ops结构体:

struct pinctrl_ops {
	int (*get_groups_count) (struct pinctrl_dev *pctldev);
	const char *(*get_group_name) (struct pinctrl_dev *pctldev,
				       unsigned selector);
	int (*get_group_pins) (struct pinctrl_dev *pctldev,
			       unsigned selector,
			       const unsigned **pins,
			       unsigned *num_pins);
	void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
			  unsigned offset);
	int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
			       struct device_node *np_config,
			       struct pinctrl_map **map, unsigned *num_maps);
	void (*dt_free_map) (struct pinctrl_dev *pctldev,
			     struct pinctrl_map *map, unsigned num_maps);
};

该结构体的函数都要实现。get_groups_count用于获取IO分组,get_group_name用于获取IO分组名称,get_group_pins用于获取IO引脚资源,pin_dbg_show用于打印调试信息,dt_node_to_map用于从设备树获取设备节点然后映射(这个函数挺重要的),dt_free_map用于释放映射。

再看pinmux_ops结构体:

struct pinmux_ops {
	int (*request) (struct pinctrl_dev *pctldev, unsigned offset);
	int (*free) (struct pinctrl_dev *pctldev, unsigned offset);
	int (*get_functions_count) (struct pinctrl_dev *pctldev);
	const char *(*get_function_name) (struct pinctrl_dev *pctldev,
					  unsigned selector);
	int (*get_function_groups) (struct pinctrl_dev *pctldev,
				  unsigned selector,
				  const char * const **groups,
				  unsigned *num_groups);
	int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
			unsigned group_selector);
	int (*gpio_request_enable) (struct pinctrl_dev *pctldev,
				    struct pinctrl_gpio_range *range,
				    unsigned offset);
	void (*gpio_disable_free) (struct pinctrl_dev *pctldev,
				   struct pinctrl_gpio_range *range,
				   unsigned offset);
	int (*gpio_set_direction) (struct pinctrl_dev *pctldev,
				   struct pinctrl_gpio_range *range,
				   unsigned offset,
				   bool input);
	bool strict;
};

request和free函数指针用于请求和释放一个引脚,get_functions_count函数指针用于获取pin控制器中的function的个数,get_function_name函数指针用于获取指定function的名称,get_function_groups函数指针用于获取指定function所占用的引脚group,set_mux函数指针用于将指定的引脚group(group_selector)设置为指定的function。

再看pinconf_ops结构体:

struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
	bool is_generic;
#endif
	int (*pin_config_get) (struct pinctrl_dev *pctldev,
			       unsigned pin,
			       unsigned long *config);
	int (*pin_config_set) (struct pinctrl_dev *pctldev,
			       unsigned pin,
			       unsigned long *configs,
			       unsigned num_configs);
	int (*pin_config_group_get) (struct pinctrl_dev *pctldev,
				     unsigned selector,
				     unsigned long *config);
	int (*pin_config_group_set) (struct pinctrl_dev *pctldev,
				     unsigned selector,
				     unsigned long *configs,
				     unsigned num_configs);
	void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,
				     struct seq_file *s,
				     unsigned offset);
	void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,
					   struct seq_file *s,
					   unsigned selector);
	void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,
					    struct seq_file *s,
					    unsigned long config);
};

pin_config_get用于获取被选中引脚的配置,pin_config_set用于配置被选中的引脚,pin_config_group_get用于获取被选中的引脚分组配置,pin_config_group_set用于配置被选中的引脚分组,pin_cofig_dbg_show、pin_config_group_dbg_show用于调试信息。

3、pinctrl bsp驱动

pinctrl的3大作用:

作用1(分为两部分):

  1. 描述、获得单个引脚的信息
  2. 描述、获得某组引脚的信息

pinctrl_ops这个操作集合说是获取各组引脚的信息。但这个“组”到底体现了在哪?有些驱动里是把单个引脚归为一组,所以因此产生了名字和实现不匹配现象。

作用2:

用来把某组引脚(group)复用为某个功能(function)

作用3:

用来配置某个引脚(pin)或某组引脚(group)

关于bsp驱动示例程序可以参考韦东山的驱动大全或者李山文的《Linux驱动开发进阶》的pinctrl bsp示例程序。

如下贴出韦东山的示例程序:


#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/pinctrl/machine.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/slab.h>
#include <linux/regmap.h>

#include "core.h"


static struct pinctrl_dev *g_pinctrl_dev;

static const struct pinctrl_pin_desc pins[] = {
    {0, "pin0", NULL},
    {1, "pin1", NULL},
    {2, "pin2", NULL},
    {3, "pin3", NULL},
};

static unsigned long g_configs[4];

struct virtual_functions_desc {
    const char *func_name;
    const char **groups;
    int num_groups;
};


static const char *func0_grps[] = {"pin0", "pin1", "pin2", "pin3"};
static const char *func1_grps[] = {"pin0", "pin1"};
static const char *func2_grps[] = {"pin2", "pin3"};

static struct virtual_functions_desc g_funcs_des[] = {
    {"gpio", func0_grps, 4},
    {"i2c",  func1_grps, 2},
    {"uart", func2_grps, 2},
};


static const struct of_device_id virtual_pinctrl_of_match[] = {
    { .compatible = "100ask,virtual_pinctrl", },
    { },
};

static int virtual_get_groups_count(struct pinctrl_dev *pctldev)
{
    return pctldev->desc->npins;
}

static const char *virtual_get_group_name(struct pinctrl_dev *pctldev,
                                          unsigned selector)
{
    return pctldev->desc->pins[selector].name;
}

static int virtual_get_group_pins(struct pinctrl_dev *pctldev, unsigned selector,
                                  const unsigned **pins,
                                  unsigned *npins)
{
    if (selector >= pctldev->desc->npins)
        return -EINVAL;

    *pins = &pctldev->desc->pins[selector].number;
    *npins = 1;

    return 0;
}

static void virtual_pin_dbg_show(struct pinctrl_dev *pctldev, struct seq_file *s,
                                 unsigned offset)
{
    seq_printf(s, "%s", dev_name(pctldev->dev));
}

/*
 i2cgrp {
		 functions = "i2c", "i2c";
		 groups = "pin0", "pin1";
		 configs = <0x11223344	0x55667788>;
 };

 one pin ==> two pinctrl_map (one for mux, one for config)

 */
static int virtual_dt_node_to_map(struct pinctrl_dev *pctldev,
                                  struct device_node *np,
                                  struct pinctrl_map **map, unsigned *num_maps)
{
    int i;
    int num_pins = 0;
    const char *pin;
    const char *function;
    unsigned int config;
    struct pinctrl_map *new_map;
    unsigned long *configs;

    /* 1. 确定pin个数/分配pinctrl_map */
    while (1)
    {
        if (of_property_read_string_index(np, "groups", num_pins, &pin) == 0)
            num_pins++;
        else
            break;
    }

    new_map = kmalloc(sizeof(struct pinctrl_map) * num_pins * 2, GFP_KERNEL);


    for (i = 0; i < num_pins; i++)
    {
        /* 2. get pin/function/config */
        of_property_read_string_index(np, "groups", i, &pin);
        of_property_read_string_index(np, "functions", i, &function);
        of_property_read_u32_index(np, "configs", i, &config);


        /* 3. 存入pinctrl_map   */
        configs = kmalloc(sizeof(*configs), GFP_KERNEL);

        new_map[i*2].type = PIN_MAP_TYPE_MUX_GROUP;
        new_map[i*2].data.mux.function = function;
        new_map[i*2].data.mux.group = pin;

        new_map[i*2+1].type = PIN_MAP_TYPE_CONFIGS_PIN;
        new_map[i*2+1].data.configs.group_or_pin = pin;
 		new_map[i*2+1].data.configs.configs = configs;
		configs[0] = config;
		new_map[i*2+1].data.configs.num_configs = 1;

	}

	*map = new_map;
	*num_maps = num_pins * 2;

	return 0;
}
static void virtual_dt_free_map(struct pinctrl_dev *pctldev,
			 struct pinctrl_map *map, unsigned num_maps)
{
	while (num_maps--)
	{
		if (map->type == PIN_MAP_TYPE_CONFIGS_PIN)
			kfree(map->data.configs.configs);

		 kfree(map);
		 map++;
	}
}

static const struct pinctrl_ops virtual_pctrl_ops = {
	.get_groups_count = virtual_get_groups_count,
	.get_group_name = virtual_get_group_name,
	.get_group_pins = virtual_get_group_pins,
	.pin_dbg_show = virtual_pin_dbg_show,
	.dt_node_to_map = virtual_dt_node_to_map,
	.dt_free_map = virtual_dt_free_map,

};

static int virtual_pmx_get_funcs_count(struct pinctrl_dev *pctldev)
{
	return ARRAY_SIZE(g_funcs_des);
}

static const char *virtual_pmx_get_func_name(struct pinctrl_dev *pctldev,
					  unsigned selector)
{
	return g_funcs_des[selector].func_name;
}

static int virtual_pmx_get_groups(struct pinctrl_dev *pctldev, unsigned selector,
				 const char * const **groups,
				 unsigned * const num_groups)
{
  *groups = g_funcs_des[selector].groups;
  *num_groups = g_funcs_des[selector].num_groups;

  return 0;
}

static int virtual_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,
			unsigned group)
{
	printk("set %s as %s\n", pctldev->desc->pins[group].name, g_funcs_des[selector].func_name);
	return 0;
}

static const struct pinmux_ops virtual_pmx_ops = {
	.get_functions_count = virtual_pmx_get_funcs_count,
	.get_function_name = virtual_pmx_get_func_name,
	.get_function_groups = virtual_pmx_get_groups,
	.set_mux = virtual_pmx_set,
};

static int virtual_pinconf_get(struct pinctrl_dev *pctldev,
			     unsigned pin_id, unsigned long *config)
{
	*config = g_configs[pin_id];
	return 0;
}

static int virtual_pinconf_set(struct pinctrl_dev *pctldev,
			  unsigned pin_id, unsigned long *configs,
			  unsigned num_configs)
{
	if (num_configs != 1)
		return -EINVAL;
	
	g_configs[pin_id] = *configs;
	
	printk("config %s as 0x%lx\n", pctldev->desc->pins[pin_id].name, *configs);
	
	return 0;
}

static void virtual_pinconf_dbg_show(struct pinctrl_dev *pctldev,
				 struct seq_file *s, unsigned pin_id)
{
	  seq_printf(s, "0x%lx", g_configs[pin_id]);
}

static void virtual_pinconf_group_dbg_show(struct pinctrl_dev *pctldev,
			  struct seq_file *s, unsigned pin_id)
{
   seq_printf(s, "0x%lx", g_configs[pin_id]);
}

static const struct pinconf_ops virtual_pinconf_ops = {
	.pin_config_get = virtual_pinconf_get,
	.pin_config_set = virtual_pinconf_set,
	.pin_config_dbg_show = virtual_pinconf_dbg_show,
	.pin_config_group_dbg_show = virtual_pinconf_group_dbg_show,
};

static int virtual_pinctrl_probe(struct platform_device *pdev)
{
	struct pinctrl_desc *pictrl;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* a. 分配pinctrl_desc */
	pictrl = devm_kzalloc(&pdev->dev, sizeof(*pictrl), GFP_KERNEL);
	
	/* b. 设置pinctrl_desc */
	pictrl->name = dev_name(&pdev->dev);
	pictrl->owner = THIS_MODULE;
	
	/* b.1 pins and group */
	pictrl->pins = pins;
	pictrl->npins = ARRAY_SIZE(pins);

	pictrl->pctlops = &virtual_pctrl_ops;
	
	/* b.2 pin mux */
	pictrl->pmxops = &virtual_pmx_ops;
	
	/* b.3 pin config */
	pictrl->confops = &virtual_pinconf_ops;
	
	/* c. 注册pinctrl_desc */
	g_pinctrl_dev = devm_pinctrl_register(&pdev->dev, pictrl, NULL);
	
	return 0;
}
static int virtual_pinctrl_remove(struct platform_device *pdev)
{

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


static struct platform_driver virtual_pinctrl_driver = {
	.probe		= virtual_pinctrl_probe,
	.remove		= virtual_pinctrl_remove,
	.driver		= {
		.name	= "100ask_virtual_pinctrl",
		.of_match_table = of_match_ptr(virtual_pinctrl_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_pinctrl_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_pinctrl_driver);
}


/* 2. 出口函数 */
static void __exit virtual_pinctrl_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_pinctrl_driver);
}

module_init(virtual_pinctrl_init);
module_exit(virtual_pinctrl_exit);

MODULE_LICENSE("GPL");

重点看virtual_dt_node_to_map,该函数主要是将设备树pinctrl节点信息转成pinctrl_map结构体。同时,内核也提供了该功能的函数,叫pinconf_generic_dt_node_to_map_all,如果这样,设备树pinctrl节点属性就得按照一定的格式来写。

4、gpio子系统

pinctrl子系统的主要作用是管理引脚的功能,即复用功能。而gpio子系统则是控制gpio功能的io口的高低电平。gpio子系统依赖于pinctrl子系统的实现,因此,在加载gpio bsp驱动时,也需要加载pinctrl的bsp驱动,这样gpio bsp驱动才能正常工作。

5、gpio bsp驱动

linux内核抽象出了一个结构体,叫gpio_chip,用来控制IO的电平及获取IO的电平:

注册一个gpio bsp驱动调用如下函数即可:

该函数将gpio_chip注册到内核中,这样用户在调用gpiod_xxx接口时就可以正常工作了。

示例程序可以参考李山文的《Linux驱动开发进阶》的gpio bsp示例程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2338217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Windows】安装或者点击OneDrive没有任何反应的解决方案

一些Windows企业版或者神州网信政府版的策略会禁止使用OneDrive&#xff0c;双击OneDrive安装程序或者点击OneDrive软件会没有任何反应。通过下面的设置可以解除相关的限制。 1、修改注册表 打开注册表管理器。依次HKEYLOCAL_MACHINE\Software\Policies\Microsoft\Windows\One…

Python爬虫第17节-动态渲染页面抓取之Selenium使用下篇

目录 引言 一、获取节点信息 1.1 获取属性 1.2 获取文本值 1.3 获取ID、位置、标签名、大小 二、切换Frame 三、延时等待 3.1 隐式等待 3.2 显式等待 四、前进后退 五、Cookies 六、选项卡管理 七、异常处理 引言 这一节我们继续讲解Selenium的使用下篇&#xff0…

HarmonyOS 第2章 Ability的开发,鸿蒙HarmonyOS 应用开发入门

第2章 Ability的开发 本章内容 本章介绍HarmonyOS的核心组件Ability的开发。 2.1 Ability概述 2.2 FA模型介绍 2.3 Stage模型介绍 2.4 Ability内页面的跳转和数据传递 2.5 Want概述 2.6 实战:显式Want启动Ability 2.7 实战:隐式Want打开应用管理 2.8 小结 2.9 习题 2.1 Abili…

day2-小白学习JAVA---java第一个程序

java第一个程序 1、新建一个文件&#xff0c;以.java为结尾2、用编辑器打开后写入代码&#xff08;本人写前端&#xff0c;所以用vscode&#xff0c;也可用其他&#xff09;3、编译文件4、运行文件5、HelloWorld代码解释6、文档注释 1、新建一个文件&#xff0c;以.java为结尾 …

Rockchip 新一代 64 位处理器 RK3562--九鼎开发板

RK3562 是 Rockchip 新一代 64 位处理器 RK3562&#xff08;Quad-core ARM Cortex-A53&#xff0c;主频 最高 2.0GHz&#xff09;&#xff0c;最大支持 8GB 内存&#xff1b;内置独立的 NPU&#xff0c;可用于轻量级人工智能应用&#xff0c;RK3562 拥有 PCIE2.1/USB3.0 OTG/…

z-library电子图书馆最新地址的查询方法

对于喜欢读书的伙伴们&#xff0c;应该都听说过z站&#xff08;z-library&#xff09;&#xff0c;优点多多&#xff0c;缺点就是地址不稳定&#xff0c;经常会变化网站地址。然后我最近发现了一个工具&#xff0c;可以不间断更新官方可用的z站地址&#xff1a;电子书最新地址

Spring Boot 3 + SpringDoc:打造接口文档

1、背景公司 新项目使用SpringBoot3.0以上构建&#xff0c;其中需要对外输出接口文档。接口文档一方面给到前端调试&#xff0c;另一方面给到测试使用。 2、SpringDoc 是什么&#xff1f; SpringDoc 是一个基于 Spring Boot 项目的库&#xff0c;能够自动根据项目中的配置、…

Json 在线格式化 - 加菲工具

Json 在线格式化 打开网站 加菲工具 选择“Json 在线格式化” 或者直接进入 https://www.orcc.top/tools/json 输入Json&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果

HarmonyOS-ArkUI V2装饰器: @Monitor装饰器:状态变量修改监听

Monitor作用 Monitor的作用就是来监听状态变量的值变化的。被Monitor修饰的函数,会在其对应监听的变量发生值的变化时,回调此函数,从而可以让您知道是什么值发生变化了,变化前是什么值,变化后是什么值。 V1版本的装饰器,有个叫@Watch的装饰器,其实也有监听变化的能力,…

微信小程序文字混合、填充动画有效果图

效果图 .wxml <view class"text" style"--deg:{{deg}}deg;"><view>混合父级颜色</view> </view> <view class"fill {{status?action:}}">文字颜色填充</view> <button bind:tap"setStatus"…

【计算机网络 | 第一篇】计算机网络基础知识

网络分层模型 1.OSI七层模型国际标准化组织提出的一个网络分层模型&#xff0c;总共有七层&#xff0c;其大体功能以及每一层分工如下所示&#xff1a; 每一层都专注做一件事&#xff0c;并且每一层都需要下一层提供的功能。 OSI七层模型七层结构体系清晰&#xff0c;理论完整…

再读bert(Bidirectional Encoder Representations from Transformers)

再读 BERT&#xff0c;仿佛在数字丛林中邂逅一位古老而智慧的先知。初次相见时&#xff0c;惊叹于它以 Transformer 架构为罗盘&#xff0c;在预训练与微调的星河中精准导航&#xff0c;打破 NLP 领域长久以来的迷雾。而如今&#xff0c;书页间跃动的不再仅是 Attention 机制精…

uCOS3实时操作系统(系统架构和中断管理)

文章目录 系统架构中断管理ARM中断寄存器相关知识ucos中断机制 系统架构 ucos主要包含三个部分的源码&#xff1a; 1、OS核心源码及其配置文件&#xff08;ucos源码&#xff09; 2、LIB库文件源码及其配置文件&#xff08;库文件&#xff0c;比如字符处理、内存管理&#xff0…

图像预处理-图像噪点消除

一.基本介绍 噪声&#xff1a;指图像中的一些干扰因素&#xff0c;也可以理解为有那么一些点的像素值与周围的像素值格格不入。常见的噪声类型包括高斯噪声和椒盐噪声。 滤波器&#xff1a;也可以叫做卷积核 - 低通滤波器是模糊&#xff0c;高通滤波器是锐化 - 低通滤波器就…

6.数据手册解读—运算放大器(二)

目录 6、细节描述 6.1预览 6.2功能框图 6.3 特征描述 6.3.1输入保护 6.3.1 EMI抑制 6.3.3 温度保护 6.3.4 容性负载和稳定性 6.3.5 共模电压范围 6.3.6反相保护 6.3.7 电气过载 6.3.8 过载恢复 6.3.9 典型规格与分布 6.3.9 散热焊盘的封装 6.3.11 Shutdown 6.4…

用 Deepseek 写的uniapp油耗计算器

下面是一个基于 Uniapp 的油耗计算器实现&#xff0c;包含 Vue 组件和页面代码。 1. 创建页面文件 在 pages 目录下创建 fuel-calculator 页面&#xff1a; <!-- pages/fuel-calculator/fuel-calculator.vue --> <template><view class"container"…

thinkphp实现图像验证码

示例 服务类 app\common\lib\captcha <?php namespace app\common\lib\captcha;use think\facade\Cache; use think\facade\Config; use Exception;class Captcha {private $im null; // 验证码图片实例private $color null; // 验证码字体颜色// 默认配置protected $co…

【k8s系列4】工具介绍

1、虚拟机软件 vmware workstation 2、shell 软件 MobaXterm 3、centos7.9 下载地址 &#xff08;https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spma2c6h.25603864.0.0.374bf5adOaiFPW&#xff09; 4、上网软件

Spark-SQL核心编程2

路径问题 相对路径与绝对路径&#xff1a;建议使用绝对路径&#xff0c;避免复制粘贴导致的错误&#xff0c;必要时将斜杠改为双反斜杠。 数据处理与展示 SQL 风格语法&#xff1a;创建临时视图并使用 SQL 风格语法查询数据。 DSL 风格语法&#xff1a;使用 DSL 风格语法查询…

STM32单片机入门学习——第41节: [12-1] Unix时间戳

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.18 STM32开发板学习——第41节: [12-1] Unix时间戳 前言开发板说明引用解答和科普一…