嵌入式驱动开发详解3(pinctrl和gpio子系统)

文章目录

  • 前言
  • pinctrl子系统
    • pin引脚配置
    • pinctrl驱动详解
  • gpio子系统
    • gpio属性配置
    • gpio子系统驱动
    • gpio子系统API函数
    • 与gpio子系统相关的of函数
  • pinctrl和gpio子系统的使用
    • 设备树配置
    • 驱动层部分
    • 用户层部分

前言

如果不用pinctrl和gpio子系统的话,我们开发驱动时需要先在设备树或者驱动文件中对pin进行配置成相应的功能引脚,然后如果是gpio功能的话就需要进行gpio初始化,如果是其他外设的话就需要进行其他外设的初始化,因为经常将引脚配置为gpio模式,因此linux内核针对pin的配置特意推出了pinctrl子系统,针对gpio的配置推出了gpio子系统,对于一些其他的外设模式有其他特定的子系统,这里不做讲解。

pinctrl子系统

大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的 GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下 拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置 方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引 入的,pinctrl 子系统主要工作内容如下:

  • ①、获取设备树中 pin 信息。
  • ②、根据获取到的 pin 信息来设置 pin 的复用功能
  • ③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始 化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。

pin引脚配置

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要 根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。
在dtsi 文件,有一个叫做 iomuxc 的节点就是配置对应的pin引脚信息,但是这个文件里面只设置了iomuxc寄存器的起始地址,剩下的具体配置是在具体的设备树dts文件中追加的,结合起来之后如下所示:

iomuxc: iomuxc@020e0000 
{compatible = "fsl,imx6ul-iomuxc";reg = <0x020e0000 0x4000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_hog_1>;imx6ul-evk {pinctrl_hog_1: hoggrp-1 {fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 >; ......};}; 
};

对于一个 PIN 的配置主要包括两方面, 一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。所以在每个子节点中类似UART1_RTS_B的定义就对应的设置好了 UART1_RTS_B 的复用功能和 UART1_RTS_B 的电气特性。例如,我们可以在pinfunc.h 这个头文件找到UART1_RTS_B的定义如下:

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0

0x0090 0x031C 0x0000 0x5 0x0,这 5 个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>

三个reg就是对应前面所提到的iomuxc的基地址的偏移值,前三个刚好对应上复用功能寄存器,电气属性寄存器和输入寄存器,第四个和第五个就是复用功能寄存器的值和输入寄存器的值,而电气属性寄存器的值放在了宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19的后面0x17059 。

pinctrl驱动详解

关于pinctrl驱动的具体实现我这里不做过多赘述,我们只需要掌握怎么把设备树的pin给配置好就行,配置好之后pinctrl驱动就会自动的把引脚配置成想要的状态。由于内核都封装好了,所以只需大概将一下实现流程。
在这里插入图片描述如上图所示,pinctrl子系统的实现也是通过platform实现的,当pinctrl驱动与对应的compatible(在刚刚提到的pin配置的节点信息的最前面设置好了)匹配上之后就会执行probe函数,probe函数就会根据节点下面的pin配置给对应的寄存器写入对应的值。

gpio子系统

如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系 统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动 开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API 函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开 发者使用 GPIO。

gpio属性配置

/* 根节点下linux内核中的gpio1属性配置*/
/ {
…………soc {…………gpmi: gpmi-nand@01806000{…………aips1: aips-bus@02000000 {…………gpio1: gpio@0209c000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x0209c000 0x4000>;interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;};};};};
};/* 根节点下手动添加某个设备节点*/
/ {
…………beep{#address-cells = <1>;#size-cells = <1>;compatible = "hbb-beep";pinctrl-names = "default";pinctrl-0 = <&pinctrl_beep>;beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;status = "okay";};
};

如上所示, GPIO子系统配置时是根据根节点下linux内核中的gpio1属性配置的,但是还需要gpio编号才能知道去配置哪个gpio,因此我们需要自己手动在根节点下手动添加某个设备节点(这里的节点跟上一个帖子的节点不同,这里仅仅只是设备树的节点,可以通过cd /proc/device-tree命令查看设备树节点是否添加成功) ,通过在自己写的驱动从设备树中读取出设备编号,然后将此编号通过gpio子系统的API函数输入来操作配置GPIO寄存器即可。在上面自己手动添加的节点中:

pinctrl-0 = <&pinctrl_beep>; //这个是前面提到的pinctrl属性配置
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; //这个就是gpio的编号设置,因此前面的beep-gpio跟驱动有关联

很多初次接触设备树的驱动开发人员很容易因为这个小问题栽了大跟头!因为我们所使用 的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设 备树是根据自己官方开发板编写的,很多 PIN 的配置和我们所使用的开发板不一样。比如 A 这 个引脚在官方开发板接的是 I2C 的 SDA,而我们所使用的硬件可能将 A 这个引脚接到了其他 的外设,比如 LED 灯上,接不同的外设,A 这个引脚的配置就不同。一个引脚一次只能实现一 个功能,如果 A 引脚在设备树中配置为了 I2C 的 SDA 信号,那么 A 引脚就不能再配置为 GPIO, 否则的话驱动程序在申请 GPIO 的时候就会失败。
检查 PIN 有没有被其他外设使用包括两个方 面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。

gpio子系统驱动

在drivers/gpio/目录下的.c文件就是不同芯片厂商的gpio驱动文件,跟pinctrl子系统一样,通过linux驱动的分层与分离、平台设备驱动相关内容封装好了,我们做驱动开发只要能掌握GPIO子系统的API函数的使用就行,后期如果有想法深入研究内核的开发,可以仔细研究一下具体是怎么实现的。

gpio子系统API函数

对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定 的 GPIO,gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,gpio 子系统提供的常用的 API 函数有下面几个:
gpio_request :用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request 进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label)

gpio_free:如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:

void gpio_free(unsigned gpio)

gpio_direction_input:此函数用于设置某个 GPIO 为输入,函数原型如下所示:

int gpio_direction_input(unsigned gpio)

gpio_direction_output:此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

gpio_get_value:此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:

#define gpio_get_value __gpio_get_value 
int __gpio_get_value(unsigned gpio)

gpio_set_value:此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下

#define gpio_set_value __gpio_set_value 
void __gpio_set_value(unsigned gpio, int value)

以上只说明了有哪几个函数,具体函数怎么用可自行百度更加详细。

与gpio子系统相关的of函数

首先展示五个跟设备树的节点获取相关的五个OF函数:
of_find_node_by_name:过节点名字查找指定的节点;
of_find_node_by_type:通过 device_type 属性查找指定的节点
of_find_compatible_node:根据 device_type 和 compatible 这两个属性查找指定的节点
of_find_matching_node_and_match:通过 of_device_id 匹配表来查找指定的节点
of_find_node_by_path:通过路径来查找指定的节点,这个是我们经常用的方法。

前面的GPIO子系统的函数可以配置gpio的寄存器,但是在驱动程序中需要先读取 gpio 属性内容,因此Linux 内核提供了几个与 GPIO 有关 的 OF 函数,常用的几个 OF 函数如下所示:
of_get_named_gpio:此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号, 此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编 号,此函数在驱动中使用很频繁! 函数原型如下:

int of_get_named_gpio(struct device_node *np, const char *propname, int index)

np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO 的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值:正值,获取到的 GPIO 编号;负值,失败。

还有一些其他的跟gpio相关的of函数,但是不怎么常用,比如of_gpio_named_count和of_gpio_count,可以获取pio配置的数量。

pinctrl和gpio子系统的使用

下面将展示一个具体的例子来说明pinctrl子系统和gpio子系统具体怎么用的。

设备树配置

设备树的配置可以看前面pinctrl的配置和在设备树根文件下对gpio信息的配置

驱动层部分

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>  //copy
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h> 
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define BEEP_CNT 1
#define BEEP_NAME "beep"
#define BEEP_ON 1
#define BEEP_OFF 0struct beep_dev{dev_t devid; int major;int minor;struct cdev cdev;struct class *class;struct device *device;struct device_node *nd;int beep_gpio;
};struct beep_dev beep;static int beep_open(struct inode *inode, struct file *file)
{file->private_data = &beep;return 0;
}static ssize_t beep_write(struct file *file, const char __user *buf,size_t count, loff_t *off)
{unsigned char status;int ret;struct beep_dev *dev = file->private_data;ret = copy_from_user(&status,buf,count);if(ret < 0){printk("kernel write failed!!!");return -1;}printk("device write%d\r\n",status);if(status == BEEP_ON){gpio_set_value(dev->beep_gpio, 0);}else if(status == BEEP_OFF){gpio_set_value(dev->beep_gpio, 1);}return 0;
}int beep_release(struct inode *inode, struct file *file)
{return 0;}static const struct file_operations beep_fops = {.owner	= THIS_MODULE,.open	= beep_open,.write	= beep_write,.release = beep_release,
};static int __init gpioled_init(void)
{int ret;beep.nd = of_find_node_by_path("/beep");if(beep.nd == NULL){printk("beep node cant not find!!\r\n");ret = -1;goto fail_node;}else{printk("beep node found!!\r\n");}beep.beep_gpio = of_get_named_gpio(beep.nd,"beep-gpio",0);if(beep.beep_gpio < 0){printk("cant not get beep-gpio\r\n");ret = -1;goto fail_node;}printk("beep-gpio-num=%d\r\n",beep.beep_gpio);ret = gpio_direction_output(beep.beep_gpio,1);if(ret < 0){printk("can`t set gpio!!!\r\n");}if(beep.major){beep.devid = MKDEV(beep.major,beep.minor);ret = register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);}else{ret = alloc_chrdev_region(&beep.devid,0,BEEP_CNT,BEEP_NAME);beep.major = MAJOR(beep.devid);beep.minor = MINOR(beep.devid);printk("alloc_chrdev_region major=%d minor=%d\r\n",beep.major, beep.minor);}if (ret < 0) {printk("Could not register\r\n");goto fail_devid;}beep.cdev.owner = THIS_MODULE;cdev_init(&beep.cdev, &beep_fops);ret = cdev_add(&beep.cdev,beep.devid,BEEP_CNT);if(ret < 0){printk("Could not cdev\r\n");goto fail_cdev;}beep.class = class_create(THIS_MODULE,BEEP_NAME);if(IS_ERR(beep.class)){ret = PTR_ERR(beep.class);goto fail_class;}beep.device = device_create(beep.class,NULL,beep.devid,NULL,BEEP_NAME);if(IS_ERR(beep.device)){ret = PTR_ERR(beep.device);goto fail_device;}return 0;
fail_device:class_destroy(beep.class);
fail_class:cdev_del(&beep.cdev);
fail_cdev:unregister_chrdev_region(beep.devid,BEEP_CNT);
fail_devid:
fail_node:return ret;
}static void __exit gpioled_exit(void)
{gpio_set_value(beep.beep_gpio,1);printk("beep_exit\r\n");cdev_del(&beep.cdev);unregister_chrdev_region(beep.devid,BEEP_CNT);device_destroy(beep.class,beep.devid);class_destroy(beep.class);
}module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");	
MODULE_AUTHOR("hbb");

用户层部分

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "string.h"int main(int argc,char *argv[])
{int fd,ret;char *filename;unsigned char databuf;if(argc != 3){printf("Error Usage!!!\r\n");return -1;}filename = argv[1];fd = open(filename ,O_RDWR);if(fd < 0){printf("file %s open failed!\r\n",filename);return -1;}databuf = atoi(argv[2]);ret = write(fd,&databuf,sizeof(databuf));if(ret < 0){printf("BEEP control failed\r\n");close(fd);return -1;}ret = close(fd);if(ret < 0){printf("file %s close failed! \r\n",filename);return -1;}return 0;
}

到这里对pinctrl和gpio子系统的大致说明就结束了,后面有新的相关的重要的内容会继续进行更新。
对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[模版总结] - 树的基本算法4 -最近公共祖先 LCA

什么是最近公共祖先LCA LCA&#xff1a;在一个树中&#xff0c;距离两个节点p,q最近可以是其本身并且同时包含这两个子节点的节点 题目连接 Leetcode 236 - LCA Leetcode 1644 - LCA II Leetcode 1650 - LCAIII Leetcode 1123 - LCA of Deepest leaves 基本思路 Leetcode 23…

永磁同步电机末端振动抑制(输入整形)

文章目录 1、前言2、双惯量系统3、输入整形3.1 ZV整形器3.2 ZVD整形器3.3 EI整形器 4、伺服系统位置环控制模型5、仿真5.1 快速性分析5.2 鲁棒性分析 参考 1、前言 什么是振动抑制&#xff1f;对于一个需要精确定位的系统&#xff0c;比如机械臂、塔吊、码头集装箱等&#xff…

jQuery-Word-Export 使用记录及完整修正文件下载 jquery.wordexport.js

参考资料&#xff1a; jQuery-Word-Export导出word_jquery.wordexport.js下载-CSDN博客 近期又需要自己做个 Html2Doc 的解决方案&#xff0c;因为客户又不想要 Html2pdf 的下载了&#xff0c;当初还给我费尽心思解决Html转pdf时中文输出的问题&#xff08;html转pdf文件下载之…

【Redis_Day6】Hash类型

【Redis_Day6】Hash类型 Hash类型操作hash的命令hset&#xff1a;设置hash中指定的字段&#xff08;field&#xff09;的值&#xff08;value&#xff09;hsetnx&#xff1a;想hash中添加字段并设置值hget&#xff1a;获取hash中指定字段的值hexists&#xff1a;判断hash中是否…

【CSP CCF记录】201809-2第14次认证 买菜

题目 样例输入 4 1 3 5 6 9 13 14 15 2 4 5 7 10 11 13 14 样例输出 3 思路 易错点&#xff1a;仅考虑所给样例&#xff0c;会误以为H和W两人的装车时间是一一对应的&#xff0c;那么提交结果的运行错误就会让你瞬间清醒。 本题关键是认识到H和W的装车时间不一定一一对应&…

Unity清除所有的PlayerPrefs

方法1&#xff1a; 直接在你的代码中加入这句 PlayerPrefs.DeleteAll(); 方法2&#xff1a; 点击编辑窗口的这里

非交换几何与黎曼ζ函数:数学中的一场革命性对话

非交换几何与黎曼ζ函数&#xff1a;数学中的一场革命性对话 非交换几何&#xff08;Noncommutative Geometry, NCG&#xff09;是数学的一个分支领域&#xff0c;它将经典的几何概念扩展到非交换代数的框架中。非交换代数是一种结合代数&#xff0c;其中乘积不是交换性的&…

微信小程序下拉刷新与上拉触底的全面教程

微信小程序下拉刷新与上拉触底的全面教程 引言 在微信小程序的开发中,用户体验至关重要。下拉刷新和上拉触底是提高用户交互体验的重要功能,能够让用户轻松获取最新数据和内容。本文将详细介绍这两个功能的实现方式,结合实际案例、代码示例和图片展示,帮助开发者轻松掌握…

数据库的联合查询

数据库的联合查询 简介为什么要使⽤联合查询多表联合查询时MYSQL内部是如何进⾏计算的构造练习案例数据案例&#xff1a;⼀个完整的联合查询的过程 内连接语法⽰例 外连接语法 ⽰例⾃连接应⽤场景示例表连接练习 ⼦查询语法单⾏⼦查询多⾏⼦查询多列⼦查询在from⼦句中使⽤⼦查…

论文阅读:A fast, scalable and versatile tool for analysis of single-cell omics data

Zhang, K., Zemke, N.R., Armand, E.J. et al. A fast, scalable and versatile tool for analysis of single-cell omics data. Nat Methods 21, 217–227 (2024). 论文地址&#xff1a;https://doi.org/10.1038/s41592-023-02139-9 代码地址&#xff1a;https://github.com…

Hive离线数仓结构分析

Hive离线数仓结构 首先&#xff0c;在数据源部分&#xff0c;包括源业务库、用户日志、爬虫数据和系统日志&#xff0c;这些都是数据的源头。这些数据通过Sqoop、DataX或 Flume 工具进行提取和导入操作。这些工具负责将不同来源的数据传输到基于 Hive 的离线数据仓库中。 在离线…

设计模式之 模板方法模式

模板方法模式是行为型设计模式的一种。它定义了一个算法的骨架&#xff0c;并将某些步骤的实现延迟到子类中。模板方法模式允许子类在不改变算法结构的情况下重新定义算法的某些特定步骤。 模板方法模式的核心在于&#xff1a; 封装算法的骨架&#xff1a;通过父类中的模板方…

【分治】--- 快速选择算法

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey &#x1f3e0; 颜色划分 &#x1f4cc; 题目解析 颜色分类 本题要求我们原地对元数组划分0,1,2三个区域,也就是不能使用辅助数组&#xf…

万物皆可Docker,在NAS上一键部署最新苹果MacOS 15系统

万物皆可Docker&#xff0c;在NAS上一键部署最新苹果MacOS 15系统 哈喽小伙伴们还&#xff0c;我是Stark-C~ 最近苹果Mac mini 2024款在政府补贴的加持下&#xff0c;仅需3500块钱左右就能到手确实挺香的。我看很多评论区的小伙伴跃跃欲试&#xff0c;但是也有不少之前从未体…

C++设计模式行为模式———状态模式

文章目录 一、引言二、状态模式三、总结三、总结 一、引言 状态模式是一种行为设计模式&#xff0c; 让你能在一个对象的内部状态变化时改变其行为&#xff0c; 使其看上去就像改变了自身所属的类一样。其实现可完成类似有限状态机的功能。换句话说&#xff0c;一个对象可以处…

vscode自动打印日志插件

自动日志工具&#xff08;Auto Logger Log&#xff09; 概述 自动日志工具&#xff08;Auto Logger Log&#xff09; 是一款 VS Code 扩展&#xff0c;用于简化生成调试日志的过程。它可以为选中的变量自动生成打印语句&#xff0c;帮助开发者快速记录和调试代码。该扩展支持多…

优雅的不等式——Hard

上一文《Easy》末尾出现了又要我们证明的例子&#xff0c;Hard难度就是继续答题答下去 其实一样可以用那篇文章https://zhuanlan.zhihu.com/p/669285539中的式子继续算下去&#xff0c;但是有三个系数&#xff0c;实在是太费时间和人力了 翻到下面的第十九种类型&#xff0c;可…

虚拟局域网PPTP配置与验证(二)

虚拟局域网PPTP配置与验证(二) windows VPN客户端linux 客户端openwrt客户端性能验证虚拟局域网PPTP配置与验证(一)虚拟局域网PPTP配置与验证(二) : 本文介绍几种客户端连接PPTP服务端的方法,同时对linux/windows/openwrt 操作系统及x86、arm硬件平台下PPTP包转发性能进…

Move 合约部署踩坑笔记:如何解决 Sui 客户端发布错误Committing lock file

Move 共学活动&#xff1a;快速上手 Move 开发 为了帮助更多开发者快速了解和掌握 Move 编程语言&#xff0c;Move 共学活动由 HOH 社区、HackQuest、OpenBuild、KeyMap 联合发起。该活动旨在为新手小白提供一个良好的学习平台&#xff0c;带领大家一步步熟悉 Move 语言&#…

介绍一下strupr(arr);(c基础)

hi , I am 36 适合对象c语言初学者 strupr(arr)&#xff1b;函数是把arr数组变为大写字母 格式 #include<string.h> strupr(arr); 返回值为arr 链接分享一下arr的意义(c基础)(必看)(牢记)-CSDN博客 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #incl…