Linux驱动开发:platform总线驱动

news2024/10/5 23:59:40

目录

1、为什么需要platform总线

2、设备端:platform_device

2.1 platform_device结构体

2.2 注册

2.3 注销

3、驱动端:platform_driver

3.1 platform_driver结构体

3.2 注册

3.3 注销

4、总线

4.1 bus_type 

4.2 platform_bus_type

5、匹配

5.1 匹配规则,platform_match

5.2 platform_device匹配流程

5.3 platform_driver匹配流程

6、在没有设备树时,使用name进行匹配

6.1 设备端程序

6.2 驱动端程序

7、在没有设备树时,使用 idtable 进行匹配

7.1 设备端程序

7.2 驱动端程序

8、获取设备信息

8.1 获取设备信息的API

8.1.1 platform_get_resource

8.1.2 platform_get_irq

8.1.3 根据device_node获取设备信息

8.2 驱动程序

9、module_platform_driver:一键注册platform

10、MODULE_DEVICE_TABLE:实现热插拔

10.1 定义以及使用方法

10.2 如何实现热插拔的功能

11、 platform设备树匹配

11.1 修改设备树以及驱动程序的compatible属性

11.1.1 驱动端

11.1.2 设备树

11.2 驱动程序:获取设备树中的中断以及GPIO资源

11.2.1 修改设备树

11.2.2 驱动程序

11.3 应用程序


1、为什么需要platform总线

        举一个例子,对于同一个主机来说,他可以支持很多I2C设备,对于同一个I2C设备来说,他也可以给很多主机来用,如果每个主机对应每个设备都需要一段驱动代码的话,会非常的冗余,根据高内聚低耦合的原则,这样是非常不好的。所以就需要这么一个统一的接口,将二者分离开来,设备端只负责设备,驱动端只负责驱动。于是提出platform这个虚拟总线,相应的就有 platform_driver 和 platform_device。当设备或者驱动加载时,就会去对面查看是否有匹配的内容。

2、设备端:platform_device

2.1 platform_device结构体

struct platform_device {
	const char	*name;  //用于匹配的名字
	int		id;         //总线号 PLATFORM_DEVID_AUTO
	//bool		id_auto; //TRUE
	struct device	dev; //父类
	u32		num_resources;     //资源的个数
	struct resource	*resource; //设备信息结构体
    char *driver_override; 
}
struct device{
   void	(*release)(struct device *dev); //释放资源的函数
};

struct resource { //设备信息结构体
	resource_size_t start; //资源的起始值 
	resource_size_t end;   //资源的结束值 
	unsigned long flags;   //资源的类型
					IORESOURCE_IO		//GPIO类型的资源
					IORESOURCE_MEM		//内存类型的资源
					IORESOURCE_IRQ	    //中断类型的资源
					IORESOURCE_DMA	    //DMA类型的资源
};

2.2 注册

int platform_device_register(struct platform_device *);

2.3 注销

void platform_device_unregister(struct platform_device *);

3、驱动端:platform_driver

3.1 platform_driver结构体

struct platform_driver {
	int (*probe)(struct platform_device *);     //匹配成功执行的函数
	int (*remove)(struct platform_device *);    //分离的时候执行的函数
	struct device_driver driver;                //父类
	const struct platform_device_id *id_table;	
};

struct device_driver {
	const char		*name;                       
	const struct of_device_id	*of_match_table; 
}; 

3.2 注册

int platform_driver_register (struct platform_driver *);

3.3 注销

void platform_driver_unregister(struct platform_driver *);

4、总线

4.1 bus_type 

Linux 内核用 bus_type 结构体来表示总线,我们所用的 I2C、SPI、USB 都是用这个结构体来定义的。该结构体如下:

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

4.2 platform_bus_type

platform总线是 bus_type的一个具体实例,定义如下:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

5、匹配

5.1 匹配规则,platform_match

在platform_bus_type中,match函数就是用来匹配的,platform_match函数实现如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

1、platform_device.driver_override 和 platform_driver.driver.name

2、设备树中的compatible  和 platform_driver.driver.of_match_table 的 compatible

3、platform_device.name                和 platform_driver.id_table[i].name

4、platform_device.name                和 platform_driver.driver.name

5.2 platform_device匹配流程

platform_device_register(&pdev){
    return platform_device_add(pdev)
}
->
pdev->dev.bus = &platform_bus_type
device_add(&pdev->dev)
->
bus_add_device(dev)  //放入链表
bus_probe_device(dev)
->
device_initial_probe(dev)
->
__device_attach(dev, true)
->
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver)
->
__device_attach_driver
->
driver_match_device(drv, dev)  //是否匹配
return driver_probe_device(drv, dev)  //调用 probe 函数

5.3 platform_driver匹配流程

#define platform_driver_register(drv)
->
__platform_driver_register(drv, THIS_MODULE)
->
drv->driver.bus = &platform_bus_type; //指定为platform bus
driver_register(&drv->driver)
->
bus_add_driver(drv)  //放入链表
->
driver_attach(drv)
->
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
->
__driver_attach
->
driver_match_device(drv, dev)
->
drv->bus->match(dev, drv) //是否匹配

6、在没有设备树时,使用name进行匹配

6.1 设备端程序

struct resource res[] = {
	[0] = {
		.start = 0x12345678,
		.end = 0x12345678+49,
		.flags = IORESOURCE_MEM,				 
	},
	[1] = {
		.start = 71,
		.end = 71,
		.flags = IORESOURCE_IRQ,
	}
};

void pdev_release(struct device *dev)
{

}

struct platform_device pdev = {
	.name = "aabbccdd",
	.id = PLATFORM_DEVID_AUTO, //自动分配
	.dev = {
		.release =  pdev_release,
	},
	.resource = res,
	.num_resources = ARRAY_SIZE(res),
};

static int __init pdev_init(void)
{
	return platform_device_register(&pdev);
}

static void __exit pdev_exit(void) 
{
	platform_device_unregister(&pdev);
}

6.2 驱动端程序

int pdrv_probe(struct platform_device*pdev)
{
    return 0;
}

int pdrv_remove(struct platform_device*pdev)
{
    return 0;
}

struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "aabbccdd",
    },
};

static int __init pdrv_init(void)
{
    return platform_driver_register(&pdrv);
}

static void __exit pdrv_exit(void)
{
    platform_driver_unregister(&pdrv);
}

7、在没有设备树时,使用 idtable 进行匹配

7.1 设备端程序

与 6.1 设备端不一样的地方

struct platform_device pdev = {
	.name = "hello1",
	.id = PLATFORM_DEVID_AUTO, //自动分配
	.dev = {
		.release =  pdev_release,
	},
	.resource = res,
	.num_resources = ARRAY_SIZE(res),
};

7.2 驱动端程序

与 6.2 驱动端不一样的地方

struct platform_device_id idtable[] = {
	{"hello1",0},
	{"hello2",1},
	{"hello3",2},
	{/*end*/}
};

struct platform_driver pdrv = {
	.probe = pdrv_probe,
	.remove = pdrv_remove,
	.driver = {
		.name = "aabbccdd", //这个name一定要填,因为要以这个名字创建文件夹 
	},
	.id_table = idtable,
};

8、获取设备信息

8.1 获取设备信息的API

8.1.1 platform_get_resource

struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int index)
/*
功能:在驱动中获取设备信息
参数:
    @dev :platform_device的结构体指针
	@type:资源的类型
	@index:同类型资源的索引号
返回值:成功返回resource的结构体指针,失败返回NULL
*/

8.1.2 platform_get_irq

int platform_get_irq(struct platform_device *dev, unsigned int index)
/*
功能:获取中断类型的资源
参数:
    @dev :platform_device的结构体指针
    @index:中断类型资源的索引号    
返回值:成功返回中断号,失败返回错误码
*/

8.1.3 根据device_node获取设备信息

Linux驱动开发:设备树节点与属性_凛冬将至__的博客-CSDN博客的7与8两节

8.2 驱动程序

完整的驱动程序就不再重写了,在 6.2 驱动程序中 probe 函数中得到设备信息

struct resource *res;
int pdrv_probe(struct platform_device*pdev)
{
    res = platform_get_resource(pdev,IORESOURCE_MEM,0);

    irqno = platform_get_irq(pdev,0);

    printk("addr = %#llx,irqno = %d\n",res->start,irqno);

    return 0;
}

9、module_platform_driver:一键注册platform

//在linux/platform_device.h中
#define module_platform_driver(__platform_driver) 
	module_driver(__platform_driver, platform_driver_register, 
			platform_driver_unregister)

//##代表字符串的拼接
#define module_driver(__driver, __register, __unregister, ...) 
static int __init __driver##_init(void) 
{ 
	return __register(&(__driver) , ##__VA_ARGS__); 
} 
module_init(__driver##_init); 
static void __exit __driver##_exit(void) 
{ 
	__unregister(&(__driver) , ##__VA_ARGS__); 
} 
module_exit(__driver##_exit);

使用该宏: module_platform_driver(pdrv),即被转化为:

#define module_platform_driver(pdrv) 
	module_driver(pdrv, platform_driver_register, platform_driver_unregister)

#define module_driver(pdrv, platform_driver_register, platform_driver_unregister) 

static int __init pdrv_init(void) 
{ 
	return platform_driver_register(&pdrv); 
} 

static void __exit pdrv_exit(void) 
{ 
	platform_driver_unregister(&pdrv); 
} 
module_init(pdrv_init); 
module_exit(pdrv_exit);

10、MODULE_DEVICE_TABLE:实现热插拔

10.1 定义以及使用方法

//定义在linux/module.h中
#define MODULE_DEVICE_TABLE(type, name)					
extern const typeof(name) __mod_##type##__##name##_device_table		
  __attribute__ ((unused, alias(__stringify(name))))

使用时,参数如下:

MODULE_DEVICE_TABLE(of,match_table)
of:总线类型
match_table:idtable数组首地址

10.2 如何实现热插拔的功能

1.先将 pdev.ko 和 pdrv.ko 放到下面的目录中
/lib/modules/5.4.0-148-generic/kernel/drivers/platform
2.执行depmod -a命令,让内核重新检索文件的位置
3.安装 pdev.ko , pdrv.ko 会被自动安装

11、 platform设备树匹配

11.1 修改设备树以及驱动程序的compatible属性

11.1.1 驱动端

在 5.1 中我们已经看过了匹配的流程,其中第二种方式就是用设备树匹配:设备树中的compatible  和 platform_driver.driver.of_match_table 的 compatible进行匹配

struct of_device_id oftable[] = {
    {.compatible = "aaa,aaa",},
    {.compatible = "bbb,bbb",},
    {.compatible = "ccc,ccc",},
    {/*end*/} //一定要有一个空的在
};

struct platform_driver {
    .driver = {
        .of_match_table = oftable,
    },
};

struct device_driver driver {
    struct device_driver driver;
}

struct device_driver {
    const struct of_device_id	*of_match_table;
}

struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];  //通过本选项和设备树完成匹配
	const void *data;
};

11.1.2 设备树

节点下需要有个 compatible 属性,并且该属性需要与 oftable 中的 compatible 名字相同,例如:

myplatform{
    compatible = "aaa,aaa";
};  

11.2 驱动程序:获取设备树中的中断以及GPIO资源

有关GPIO部分请看:

Linux驱动开发:gpio子系统_凛冬将至__的博客-CSDN博客

有关中断部分请看:

Linux驱动开发:中断子系统_凛冬将至__的博客-CSDN博客

有关阻塞部分请看:

Linux驱动开发 IO模型:阻塞IO_linux阻塞io_凛冬将至__的博客-CSDN博客

11.2.1 修改设备树

在根节点下添加自己的节点

myplatform{
    compatible = "aaa,aaa";
    interrupt-parent = <&gpiof>;
    interrupts = <9 0>;
    reg = <0x12345678 0x40>;
    led1 = <&gpioe 10 0>;
};

11.2.2 驱动程序

#define IRQNAME "key_irq"
int irqno, major;
struct gpio_desc* desc;
struct class* cls;
struct device* dev;
wait_queue_head_t wq;
int condition=0;
int status=0;
irqreturn_t key_irq_handle(int irq, void* dev)
{
    //1.设置status和led1
    status = gpiod_get_value(desc);
    status = !status;
    gpiod_set_value(desc,status);

    //2唤醒
    condition=1;
    wake_up_interruptible(&wq);

    return IRQ_HANDLED;
}
int pdrv_open(struct inode* inode, struct file* file)
{
    return 0;
}

ssize_t pdrv_read(struct file*file,
     char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    if(file->f_flags & O_NONBLOCK){
        return -EINVAL;
    }else{
        ret = wait_event_interruptible(wq,condition);
    }

    ret = copy_to_user(ubuf,&status,size);

    condition = 0;

    return size;
}
int pdrv_close(struct inode* inode, struct file* file)
{
    return 0;
}
struct file_operations fops = {
    .open = pdrv_open,
    .read = pdrv_read,
    .release = pdrv_close,
};
int pdrv_probe(struct platform_device* pdev)
{
    int ret;
    // 1.获取设备树中的设备信息
    irqno = platform_get_irq(pdev, 0);
    desc = gpiod_get_from_of_node(pdev->dev.of_node, "led1", 0, GPIOD_OUT_LOW, NULL);

    // 2.注册中断
    ret = request_irq(irqno, key_irq_handle, IRQF_TRIGGER_FALLING, IRQNAME, NULL);

    // 3.注册字符设备驱动
    major = register_chrdev(0, IRQNAME, &fops);
    cls = class_create(THIS_MODULE, IRQNAME);
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, IRQNAME);

    //4.初始化等待队列头
    init_waitqueue_head(&wq);
    return 0;
}
int pdrv_remove(struct platform_device* pdev)
{
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, IRQNAME);
    free_irq(irqno, NULL);
    gpiod_put(desc);
    return 0;
}
const struct of_device_id oftable[] = {
    {
        .compatible = "aaa,aaa",
    },
    { /*end*/ }
};
struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "bbb", //虽然用不到,但是一定要写
        .of_match_table = oftable,
    },

};
//一键注册
module_platform_driver(pdrv);

11.3 应用程序

int main(int argc,const char * argv[])
{
    int fd,status;
    if((fd = open("/dev/key_irq",O_RDWR))==-1)
        PRINT_ERR("open error");

    while(1){
        read(fd,&status,sizeof(status));
        printf("status = %d\n",status);
    }

    close(fd);
    return 0;
}

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

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

相关文章

2023第二届中国汽车碳中和国际峰会

会议背景 随着世界越来越认识到气候变化的破坏性影响&#xff0c;政府、组织和个人正在采取行动减少导致全球变暖的温室气体排放。随着电动化和互联技术的发展&#xff0c;汽车产业价值链正在经历变革。 汽车价值链的转型还为汽车行业创造了许多脱碳和更具可持续性的新机会。 …

vue3-admin-template页面

vue3-admin-template 本人学习视频网址为&#xff1a;视频地址源码:github 网页采用技术框架 本管理模板采用vue3开发&#xff0c;使用vue-router来作为路由跳转&#xff0c;将登录成功后产生的菜单&#xff0c;token放入到vuex中存储&#xff0c;通过axios来进行交互&#x…

深入理解 spring-boot-starter-parent

目录 一、前言二、Maven继承三、分析spring-boot-starter-parent四、Maven单继承问题五、不继承spring-boot-starter-parent需要注意的 一、前言 在idea当中创建springboot项目的时候都会继承一个spring-boot-starter-parent作为父类&#xff0c;假如不继承我们的项目就不能使…

Hudi的介绍与安装编译

Hudi的介绍 安装Maven 编译Hudi 执行编译 Hudi的介绍 Hudi简介 Hudi&#xff08;Hadoop Upserts Delete and Incremental&#xff09;是下一代流数据湖平台。Apache Hudi将核心仓库和数据库功能直接引入数据湖。Hudi提供了表、事务、高效的upserts/delete、高级索引、流摄取…

CentOS 7(2009) 升级 GCC 版本

1. 前言 CentOS 7 默认安装的 gcc 版本为 4.8&#xff0c;但是很多时候都会需要用到更高版本的 gcc 来编译源码&#xff0c;那么本文将会介绍如何在线升级 CentOS 的 gcc 版本。 2. 升级 GCC (1). 安装 centos-release-scl&#xff1b; [imaginemiraclecentos7 ~]$ sudo yum…

docker-compose搭建skywalking

SkyWalking 架构图 架构组成 SkyWalking Agent &#xff1a;负责从应用中&#xff0c;收集链路信息&#xff0c;发送给 SkyWalking OAP 服务器。目前支持 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。而我们目前采用的是&#xff0c;SkyWalking Agent 收集 SkyWalk…

测试知识总结

1.影响ui自动化稳定性 异常弹出对话框 --异常场景库 页面控件元素属性的细微变化--模糊匹配 延迟 --- retry 数据 -- 数据已被使用 2. 移动端应用细分为三大类&#xff1a;Web App、Native App&#xff08;原生应用&#xff09; 和 Hybrid App&#xff08;混合应用&…

Yjs + quill:快速实现支持协同编辑的富文本编辑器

大家好&#xff0c;我是前端西瓜哥&#xff0c;这次来看看 Yjs 如何帮助我们实现协同编辑能力的。 Y.js 是一个支持 协同编辑 的开源库。只要我们将自己的数据转换为 Y.js 提供的 Y.Array、Y.Map 类型&#xff0c;Y.js 就会自动帮我们做数据的一致性处理和同步。 一致性问题 …

Cookie和Session的API、登录页面

目录 一、Cookie 和 Session 1、HttpServletRequest 类中的相关方法 2、HttpServletResponse 类中的相关方法 3、HttpSession 类中的相关方法 4、Cookie 类中的相关方法 二、网页登录 1、约定前后端交互接口 2、编写一个简单的登录页面 3、编写一个Servlet 来处理这个…

Springboot +Flowable,任务认领和回退(二)

一.简介 有的时候&#xff0c;一个任务节点会存在多个候选人&#xff0c;例如&#xff1a;张三提交一个任务&#xff0c;这个任务即可以由李四处理&#xff0c;又可以由王五处理&#xff0c;那么针对这种多个任务候选人的情况&#xff0c;该如何处理&#xff1f; 二.绘制流程…

SuperMap GIS基础产品组件GIS FAQ集锦(2)

SuperMap GIS基础产品组件GIS FAQ集锦&#xff08;2&#xff09; 【iObjects for Spark】读取GDB参数该如何填写&#xff1f; 【解决办法】可参考以下示例&#xff1a; val GDB_params new util.HashMapString, java.io.Serializable GDB_params.put(FeatureRDDProviderParam…

spi 应用层读值为0问题

昨天调SPI遇到读值为0x00&#xff0c;经排查是读写方向的问题。 #include <stdint.h> #include <stdio.h> #include <stdlib.h…

Consensus见闻:雷声大 却不下雨的奧斯汀

前言 由Coindesk举办的Consensus历时3天&#xff0c;于4月28日完美落幕&#xff0c;欧科云链研究院前往美国得克萨斯州奧斯汀参加&#xff0c;本文将分享我们在奧斯汀和Consensus会议中的所见所闻&#xff0c;带你们看一个不一样的奧斯汀。 出品&#xff5c;欧科云链研究院 作…

datagrip连接elasticsearch且进行查询20230506

背景&#xff1a;公司要做一个es的数据存储&#xff0c;然后通过接口进行查询&#xff0c;我在docker下完成了ELK的安装&#xff0c;但是对es还不是很了解&#xff0c;就想着用logstash加载完数据到es中后&#xff0c;在数据库中对es进行查询&#xff0c;发现datagrip是支持连接…

Boosting之Adaboost与GBDT

同质与异质 1.异质模型&#xff1a;把不同类型的算法集成在一起&#xff0c;基础模型要有足够大差异性&#xff08;可以找出最适合当前数据的模型&#xff09; 同质模型&#xff1a;通过一个基础算法生成的同类型学习器。 Boosting概念介绍 Boosting本意就是提升&#xff0…

腾讯云服务器怎么开通端口?以80端口为例轻量和CVM教程合集

腾讯云服务器怎么放通80端口&#xff1f;腾讯云服务器分为云服务器CVM和轻量应用服务器&#xff0c;CVM云服务器在安全组中配置规则开启80端口&#xff0c;轻量应用服务器在防火墙中开通80端口&#xff0c;阿腾云来详细详细说下腾讯云服务器开通80端口教程&#xff1a; 目录 …

【c语言】字符串拼接 | API仿真

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ …

linux usb gadget driver代码

本文基于linux-5.4.124 aspeed 2600(BMC)的代码实现来描述arm结构下的gadget driver. 在读之前&#xff0c;我们需要了解什么是usb gadget driver&#xff0c;以及它的作用。 从英文字面上翻译看&#xff0c;usb gadget driver是一个usb小工具驱动。这说了等于没说。实际上&a…

如何通过代码接入手机在网状态 API

引言 在许多场景下&#xff0c;手机号码是一种常用的身份验证信息。而使用手机在网状态 API 可以判断出手机号码是否有效&#xff0c;在一定程度上提高了身份验证的准确性和安全性&#xff0c;它的出现和广泛应用&#xff0c;为各行各业提供了更为便利和高效的解决方案。 本文…

城市夜景照明对于安科瑞智能照明系统的运用

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 摘要&#xff1a;文章以智能照明控制系统为切入点&#xff0c;介绍了智能照明控制系统在城市夜景照明工程中的应用价值&#xff0c;并结合具体案例分析了城市夜景照明控制管理平台的设计和具体应用。智能照明控制系统…