单片机只会调库和复制别人的代码是什么水平?

news2024/11/26 12:25:46

单片机只会调库和复制别人的代码是什么水平?

    • 前言
    • 什么是调库?
    • 如何不调库点亮一个LED
    • 调库与不调库的区别
    • 为什么要操作寄存器
    • 结语

前言

在这里插入图片描述

相信对于学习过单片机的同学对于调库这个操作都不陌生,大家都是从调别人的库阶段过来的,今天看到一个评论说如果只会调库到了公司后会发现自己啥都不是,其实这话说的一点也不假,如果你只会调库的话你的单片机水平还停留在C语言阶段,并不能称为真正的单片机开发。

但是我们要有这么一个概念,调库是自己编写的开始,如果上来就给你讲寄存器这些我相信很多初学者都接收不了,理解不了这写寄存器到底在干啥,但是如果我们从调被人库开始学习单片机我们就会对单片机有个初始概念,对于后面的学习非常有帮助,今天我们就看一下我们如何从调库工程师成为真正的开发工程师。

什么是调库?

如果你通过机构的培训视频,比如野火的STM32单片机开发视频,相信你对于调库并不陌生,调库其实就是通过调用别人封装好的库函数,来实现自己的某些功能,不同的机构封装出来的库函数也有所不同,但是基本操作都大同小异,下面我们就以STM32调用固件库实现点灯为例给大家进行讲解。

我们先来看一个我们非常熟悉的结构体:

void LED_GPIO_Config(void)//初始化相关的GPIO 第2个灯
{
	GPIO_InitTypeDef GPIO_InitStruct;
	/*第一步:打开外设的时钟(RCC寄存器控制)*/
	RCC_APB2PeriphClockCmd(LED1_GPIO_CLK|LED2_GPIO_CLK,ENABLE);
	
	/*第二步:配置外设初始化结构体*/
	GPIO_InitStruct.GPIO_Pin = LED1_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	
	/*第三步:调用外设初始化函数,把配置好的结构体成员写到寄存器里面*/
	GPIO_Init(LED1_GPIO_PORT,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Pin = LED2_GPIO_PIN;
	GPIO_Init(LED2_GPIO_PORT,&GPIO_InitStruct);
}

相信对于学习过STM32单片机的同学对于这个函数都不陌生,这个函数其实就是实现了对于一个GPIO的初始化,相信初学者并没有思考过我们为什么要这么初始化呢?这里面的一些函数都有什么作用呢?他们是在哪个地方被封装的呢?我们可不可以不按照这个函数的结构来写呢?

带着这些一文我们继续往更深的层次去探索一下这些东西都是什么意思:

这里面用到了很多的宏定义,我们可以使用右键-->go to来向前查询该宏定义在哪个地方进行定义的,例如我们对时钟的宏定义LED1_GPIO_CLK 具体如下:

#define LED1_GPIO_CLK         RCC_APB2Periph_GPIOC//时钟
#define LED1_GPIO_PORT        GPIOC               //端口
#define LED1_GPIO_PIN         GPIO_Pin_2//pin 引脚
 
#define LED2_GPIO_CLK         RCC_APB2Periph_GPIOC//时钟
#define LED2_GPIO_PORT        GPIOC               //端口
#define LED2_GPIO_PIN         GPIO_Pin_3//pin

我们可以看到一些宏定义,例如LED1_GPIO_CLK被宏定义为RCC_APB2Periph_GPIOC,这里的RCC_APB2Periph_GPIOC就是官方固件库中定义的时钟,如果你想继续研究RCC_APB2Periph_GPIOC代表什么意思,我们可以继续右键-->go to

在这里插入图片描述
我们发现依然是宏定义,这里将RCC_APB2Periph_GPIOC宏定义成了((uint32_t)0x00000010),如果你想继续了解((uint32_t)0x00000010)代表什么的话那就需要查看STM32的芯片手册了,我们这里做一下简单的讲解。

关于GPIO的需要用到的寄存器如下:
在这里插入图片描述
我们将0x10转换为2进制为:1 0000我们可以看到第四位为1,其他位为0,查看芯片手册可以发现第四位解释如下:

在这里插入图片描述
发现这句话其实就是在使能I/O端时钟C,和我们的使用是相同的。到这里我们就知道了从封装的库到底层寄存器中间经过了什么,当然这只是一个简单的例子,实际会比此复杂很多。

如何不调库点亮一个LED

通过固件库我们可以看到如果想要控制一个GPIO大概需要以下几步操作:

  1. 打开GOIO端口的时钟
  2. .配置IO口为输出(控制CRL寄存器)
  3. 配置ODR寄存器

知道了我们需要进行的操作下一步我们就可以开始通过寄存器操作来控制一个LED了,具体代码我直接给大家贴出来大家可以自己进行分析。

#define rRCCAHB1CLKEN   *((volatile unsigned long *)0x40023830) 
	
#define rGPIOF_MODER  *((volatile unsigned long *)0x40021400)   
#define rGPIOF_OTYPER *((volatile unsigned long *)0x40021404) 
#define rGPIOF_OSPEEDR  *((volatile unsigned long *)0x40021408) 
#define rGPIOF_IDR  *((volatile unsigned long *)0x40021410) 
#define rGPIOF_ODR  *((volatile unsigned long *)0x40021414) 
 
 
#define rGPIOE_MODER  *((volatile unsigned long *)0x40021000)
#define rGPIOE_OTYPER *((volatile unsigned long *)0x40021004)
#define rGPIOE_OSPEEDR  *((volatile unsigned long *)0x40021008)
#define rGPIOE_IDR  *((volatile unsigned long *)0x40021010)
#define rGPIOE_ODR  *((volatile unsigned long *)0x40021014)
 
 
#define rGPIOA_MODER  *((volatile unsigned long *)0x40020000)
#define rGPIOA_OTYPER *((volatile unsigned long *)0x40020004)
#define rGPIOA_OSPEEDR  *((volatile unsigned long *)0x40020008)
#define rGPIOA_IDR  *((volatile unsigned long *)0x40020010)
#define rGPIOA_ODR  *((volatile unsigned long *)0x40020014)
void key_init()
{
	
	
	
	rRCCAHB1CLKEN |= 1 | (1 << 1);
	
	
	
	rGPIOA_MODER&=~(1|(1<<1));
	
	rGPIOF_OSPEEDR &= ~(1 | (1 << 1) );
	
	
	
	rGPIOE_MODER&= ~(0x3f<<4);
	
	 rGPIOE_MODER &= ~(0x3f<<4);
}
void led_init()
{
	
	rRCCAHB1CLKEN |= (1 << 5) | (1 << 4);
 
	
	rGPIOF_MODER &= ~((0x3 << 18) | (0x3 << 20));
	rGPIOF_MODER |= (1 << 18) | (1 << 20);  
 
	 
	rGPIOF_OTYPER &= ~( (1 << 9) | (1 << 10));
	
 
 
	rGPIOF_OSPEEDR &= ~((0x3 << 18) | (0x3 << 20) );
	
	rGPIOF_ODR  |=  (1 << 9 | 1 << 10) ;
 
 
 
	rGPIOE_MODER &= ~((0X3 << 26) | (0X3 << 28) );
	rGPIOE_MODER |= (1 << 26) | (1 << 28);
 
	rGPIOE_OTYPER &= ~( (1 << 13) | (1 << 14));
 
	rGPIOE_OSPEEDR &= ~((0x3 << 26) | (0x3 << 28) );
 
	rGPIOE_ODR  |=  (1 << 13 | 1 << 14) ;
 
	
}
 
 
void delay(int i)
{
	int v = i;
	while(v--);
}
 
void led_on(int i)
{
	if (i == 0)
	{
		rGPIOF_ODR &= ~(1 << 9);
		rGPIOF_ODR |= 1 << 10;
 
		rGPIOE_ODR |= (1 << 13) | (1 << 14);
	}
	else if (i == 1)
	{
		rGPIOF_ODR |= (1 << 9);
		rGPIOF_ODR &= ~(1 << 10);
 
		rGPIOE_ODR |= (1 << 13) | (1 << 14);
		
	}
	else if (i == 2)
	{
		rGPIOF_ODR |= (1 << 9) | (1 << 10);
 
		rGPIOE_ODR &= ~(1 << 13);
		rGPIOE_ODR |= 1 << 14;
	}
	else if (i == 3)
	{
		rGPIOF_ODR |= (1 << 9) | (1 << 10);
 
		rGPIOE_ODR &= ~(1 << 14);
		rGPIOE_ODR |= 1 << 13;
	}
}
 
int main()
{
	int i = 0;
	led_init();
	key_init();
	while(1)
	{
		
			 if(!(rGPIOA_IDR&1))
       {
				 delay(50);//消抖
				 if(!(rGPIOA_IDR&1))
				 {
						led_on(0);
				 }
			 }
			 else
			 {
				rGPIOF_ODR |= 1 << 9;//µÆÃð
			 }
			if(!(rGPIOE_IDR&(1<<2)))
       {
				 delay(50);
				 if(!(rGPIOE_IDR&(1<<2)))
				 {
					led_on(1);
				 }
			 }
			 else
			 {
				rGPIOF_ODR |= 1 << 10;
			 }
			 if(!(rGPIOE_IDR&(1<<3)))
       {
				 delay(50);
				 if(!(rGPIOE_IDR&(1<<3)))
				 {
					led_on(2);
				 }
			 }
			 else
			 {
				rGPIOE_ODR |= 1 << 13;
			 }
			 if(!(rGPIOE_IDR&(1<<4)))
       {
				 delay(50);
				 if(!(rGPIOE_IDR&(1<<4)))
				 {
					led_on(3);
				 }
			 }
			 else
			 {
				rGPIOE_ODR |= 1 << 14;
			 }
			
	}
}

上面的代码实现的功能是通过循环扫描判断按键是否被按下,如果按键被按下则对LED引脚输出低电平从而点亮LED灯,这里用了四个按键和四个LED,方便大家理解之间的不同,引脚的定义如下:

LED的引脚定义为:
LED0 ->PF9
LED1 -> PF10
LED2-> PE13
LED3 -> PE14

按键引脚定义为:
KEY0--> PA0
KEY1--> PE2
KEY2--> PE3
KEY3--> PE4

具体每个寄存器代表什么意思大家可以查看STM32的官方手册,里面有详细的介绍。没有手册的话可以看下面这篇文章,里面有常用的寄存器:https://www.cnblogs.com/jzcn/p/15775328.html

调库与不调库的区别

说到这两者的区别也是我写这篇文章的主要意图,相信你打开这篇文章绝对不是来看不调库是如何开发的,而是来看调库开发和不调库开发具体有哪些区别,为什么有现成的库不用,非要自己去查寄存器,自己进行开发。

从应用角度讲,寄存器相对来说是属于更底层的,类似于驱动层,而固件库则类似通过将寄存器封装之后的应用层。相比之下,固件库更像是包装好给用户的产品一样,只需要我们使用就行了,让封装自己和寄存器打交道,而使用寄存器在使用时必须要清楚自己要操作那个一个寄存器,就很复杂,需要了解清楚寄存器的底层配置。

如果你学习过Linux的话想必你对分层的思想是有所了解的,虽然在单片机中分层思想的应用和Linux中的分层不太一样,但也都是大同小异的。

STM32标准外设库之前的版本也称固件函数库或简称固件库,是⼀个固件函数包,它由程序、数据结构和宏组成,包括了微控制器所有外设的性能特征。

该函数库还包括每⼀个外设的驱动描述和应用实例,为开发者访问底层硬件提供了⼀个中间API,通过使用固件函数库,无需深入掌握底层硬件细节,开发者就可以轻松应⽤每⼀个外设。

因此,使⽤固态函数库可以大大减少用户的程序编写时间,进而降低开发成本。每个外设驱动都由⼀组函数组成,这组函数覆盖了该外设所有功能。每个器件的开发都由⼀个通⽤API驱动,API对该驱动程序的结构,函数和参数名称都进⾏了标准化。

这样的操作既有好处又有坏处,对于毫无基础的人来说它可以使我们的控制更加简单,上手更容易,但是他也会造成我们接触不到单片机的底层操作,可能你使用单片机干过很多的事,做过很多的项目,但是对于单片机的运行逻辑依然不清楚。

从专业角度来讲,由于寄存器更底层,更需要用户了解基本构成以及底层配置,所以说操作寄存器相对于固件库显得更加专业,相比之下,直接操作固件库不需要了解那么多甚至不了解就可以直接开发,并不需要太多专业知识。

通过上面的分析我们可以总结出他们的优缺点:
固件库优点: 可以直接应用,操作更方便,开发迅速,适合新手入门。
固件库缺点: 因为操作固件库,本质上也会对寄存器的操作,因为要通过封装这一中间商,所以执行速度要比直接操作寄存器更慢,但是没有寄存器移植那么方便。

所以我们可以从固件库入门,之后再慢慢深入了解寄存器,了解相关知识,在我看来,了解更多底层的东西是有利无害的,更利于提升自己,可以懒,但是不能不会。

为什么要操作寄存器

回归我们的中心,讲了这么多我们到底该如何学习单片机呢?详细这个问题在互联网上都已经被谈烂了。对于初学者应该如何入门应该学习哪些东西今天这篇文章我就不再讨论了,今天要讨论的内容是如果你已经入门了,也已经通过操作固件库做了很多的东西,下一步你应该学习哪些东西。

如果你已经使用单片机做了很多的实验,比如什么ADC采集、PWM波输出这些操作你都用过了,并且感觉单片机你已经玩的炉火纯青了,那么下面的东西对你应该很有用。

还有一点需要强调一下,如果未来你并不打算做单片机相关的工作的话那下面的东西你可以量力而行,可以作为了解的内容,并不用深入的了解。

大家学习51单片机的时候是不是常常进行一些寄存器操作,那为什么我们在32中就很少见到这些直接对寄存器进行操作呢?那是因为32的寄存器相比于51单片机要复杂很多,比如一个GPIO的操作可能就和很多的寄存器有关,我们很难通过一句话就可以控制一个GPIO,当然不这么干不代表不能这么干。

如果你接触到单片机的高级开发(当然没有这么一说,你可以理解成用单片机做一些产品)那么你的开发就会遇到瓶颈,从而限制你的开发,这也是很多单片机开发要求你一定要会寄存器操作。

对于经过系统培训的开发者,单片机(MCU)或SoC的驱动开发,不管是使用各种库还是直接上寄存器,都不成问题。

HAL库函数或者固件库都是ST开发的,也是人写出来的代码,既然是代码,那就有存在BUG的可能,而且像这些经过ST调试过的代码,更可能隐藏深层问题,这些都需要通过修改寄存器配置来调试定位。

所以这就可能在你的代码里埋下了更深的炸弹,而且这些炸弹是埋藏的非常深的(一般的小bug是不会有的,毕竟那么多人使用)而这些bug一旦复现,你就会不知所措,完全不知道从何查起。

而且一般公司的MCU都不是你平时学的这些单片机,而是一些工业级的MCU,所以你可以想一想如果你一直使用的都是STM32的固件库进行的开发,从来没接触过寄存器操作,或者根本都不知道怎么看芯片手册,怎么操作寄存器,那么你怎么保证
你们公司使用的MCU你就一定会操作呢?

所以对于STM32或者51这类单片机的定位你就把它当成学习使用的,你要通过这类简单的、有丰富资料的单片机去入门,去学习。当你的学习内容达到一定程度之后就一定会接触到寄存器这些操作,说实话寄存器操作也只是工作的基础,最重要的是举一反三,通过一个单片机学习到所有MCU操作的本质,这样才能更好的在工作中使用,而不受单片机(MCU)型号的限制。

结语

对于单片机的学习我们可以使用单片机的固件库入门,初步了解单片机操作的步骤,可以先不接触寄存器,等到固件库使用的非常熟悉之后可以转战寄存器了。

对于寄存器操作绝不是点个小灯就完了,你需要做的是知道如何查看芯片手册,知道固件库里的每个宏定义或者函数这么写的依据是什么?如果让你来写一个固件库你会怎么写?

当你的水平能够达到对STM32的寄存器操作已经非常6的话你可以尝试几款工业级的MCU,例如工业非常常用的TC397,这个MCU在车载行业用的非常多,可以尝试一下,不过治疗可能不太好找,如果遇到问题的话就需要自己琢磨了,这也是一种进步。

👇点击下方公众号卡片获取资料👇

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

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

相关文章

便捷记账本小程序+后台管理系统-JAVA【数据库设计、源码、开题报告】

第一章 绪 论 1.1选题背景 互联网是人类的基本需求&#xff0c;特别是在现代社会&#xff0c;个人压力增大&#xff0c;社会运作节奏高&#xff0c;随着互联网的快速发展&#xff0c;用户的需求也越来越高&#xff0c;用户也将越来越多依靠互联网而不是自己获取信息&#xff…

Go:进度条工具库 vbauerster/mpb 简介

文章目录简介核心能力示例单进度条渲染多进度条渲染小结简介 mpb是一个在终端进行进度条渲染的工具库 核心能力 支持多进度条 Multiple Bars: Multiple progress bars are supported支持动态设置进度条总值 Dynamic Total: Set total while bar is running支持动态增加 / 删…

【Unity Shader】屏幕后处理4.0:基于高斯模糊的Bloom

原本打算写高斯模糊和双重模糊两个实现Bloom方法的对比&#xff0c;但两个加在一起篇幅过长&#xff0c;于是拆成两篇文章来进行。 学习前建议应先搞清楚的几个概念 HDRLDRToneMapping几种模糊算法1 高斯模糊实现Bloom 最近一直在学习Unity Shader实现各种后处理效果&#x…

c++ - 第13节 - c++中的继承

1.继承的概念及定义 面向对象三大特性&#xff1a;封装、继承、多态注&#xff1a;面向对象不止这三个特性&#xff0c;还有其他特性&#xff0c;比如反射&#xff08;Java中的概念&#xff09;、抽象等封装的理解&#xff1a;&#xff08;1&#xff09;将c设计的stack类&#…

记一次艰难的上班历程

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 以下事件均为卢松松真实经历&#xff1a; 早上7点&#xff0c;小区又被静默了&#xff0c;几百号人堵在小区门口。 我不顾病毒传染的风险挤到了小区门口&#xff0c;问原因。 看门的说到&#x…

pumping lemma

正规语言版本 LLL是正规语言,则存在整数p≥1p\ge 1p≥1 对于任意长度大于等于ppp的字符串w∈Lw\in Lw∈L&#xff0c;wxyzwxyzwxyz,满足下面3个条件 ∣y∣≥1\left|y\right|\ge 1∣y∣≥1 ∣xy∣≤p\left|xy\right|\le p∣xy∣≤p ∀n≥0,xynz∈L\forall n\ge 0,xy^nz\in L∀n≥…

1997-2020年各省三废排放量和熵值法计算的环境规制综合指数(无缺失值)

1997-2020年各省三废排放量和环境规制综合指数 1、包括&#xff1a;30个省份 2、指标包括&#xff1a;工业二氧化硫排放量、工业烟尘排放量和工业废水排放量 环境规制综合指数是由工业废水排放量、工业 SO2 排放量以及工业烟尘排放量计算而来 &#xff08;表格中有详细的三…

网络流量监测与调度技术研究

网络流量监测与调度技术研究网络流量监测与调度技术研究学习目标&#xff1a;流量监测学习内容&#xff1a;流量监测流量监测的设计框架框架一框架二框架三申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计…

零基础上手unity VR开发【Oculus账号体系准备】

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;Unity VR 开发成神之路 --【着重解决VR项目开发难&#xff0c;国内资料匮乏的问题。从零入门&#xff0c;一步一个脚印&#xff0c;带你搞定VR开发! &#x1f63b;&#x1f63b;】 &#x1f4d…

大数的乘法

输入一个大正整数和一个非负整数&#xff0c;求它们的积。 输入格式: 测试数据有多组&#xff0c;处理到文件尾。每组测试输入1个大正整数A&#xff08;位数不会超过1000&#xff09;和一个非负整数B&#xff08;int范围&#xff09;。 输出格式: 对于每组测试&#xff0c;…

[Leetcode刷题] - LC003 Longest Substring without repating character

题目链接 Leetcode 003Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.https://leetcode.com/problems/longest-substring-without-repeating-characters/ 题目描述 给定…

数字先锋 | 随时随地云端阅片,“云胶片”时代来啦!

作为现代医疗必不可少的诊断方法&#xff0c;医学影像数据在医疗数据中的占比高达90%且正以每年30%的速度递增&#xff0c;而影像医生就业人数年增长率仅4%。这意味着&#xff0c;全国总人数不到20万的放射科医生&#xff0c;要处理每年75.4亿人次影像相关诊断需求&#xff0c;…

代理模式-P19

静态代理&#xff1a; 创建项目&#xff1a; Rent package com.Li.demo01;//租房 public interface Rent {public void rent(); }Proxy&#xff1a; package com.Li.demo01; //中介&#xff08;负责找房东&#xff09; public class Proxy implements Rent{//private Host h…

Redis安装及使用(WindowsLinux)

Windows 1.下载 下载地址&#xff1a;Releases tporadowski/redis GitHub。 目前最新5.0.14 2.解压 3.先后打开redis-server.exe和redis-cli.exe两个文件&#xff08;一定要按顺序&#xff09; 4.使用 正常使用在redis-cli.exe里面输入各种命令&#xff08;使用期间redis…

7、系统管理

文章目录7、系统管理7.1 Linux 中的进程和服务7.2 service 服务管理&#xff08;CentOS 6 版本-了解&#xff09;7.2.1 基本语法7.2.2 经验技巧7.2.3 案例实操&#xff08;1&#xff09;查看网络服务的状态&#xff08;2&#xff09;停止网络服务&#xff08;3&#xff09;启动…

基于JAVA的个人博客论坛系统的设计与实现参考【数据库设计、源码、开题报告】

在学校开发搭建一个什么项目最有成就感&#xff0c;那肯定就是搭建「个人博客」呀&#xff0c;然后把自己平时的学习笔记写到博客里&#xff0c;这时你的笔记就是**云笔记**&#xff0c;就再也不会出现因为本地文件丢失而感到痛心的事情。 而且&#xff0c;还可以把你的个人博客…

艾美捷小鼠肿瘤坏死因子α-ELISpot试剂盒使用说明

肿瘤坏死因子-α 肿瘤坏死因子-α&#xff08;TNF-α&#xff0c;也称为TNF-α、TNF-a、TNF-a和肿瘤坏死因子α&#xff09;由许多不同的细胞类型产生&#xff0c;例如单核细胞、巨噬细胞、T细胞和B细胞。TNF-α的许多作用包括保护细菌感染、细胞生长调节、免疫系统调节和感染…

Kotlin 使用vararg可变参数

文章目录背景Kotlin中使用可变参数对Kotlin可变参数反编译资料背景 一般在项目开发中&#xff0c;我们经常会在关键节点上埋点&#xff0c;而且埋点中会增加一些额外参数&#xff0c;这些参数通常是成对出现且参数个数是不固定的。如下&#xff1a; //定义事件EVENT_ID const…

云原生|kubernetes |使用Prometheus监控k8s cAdvisor篇(一)(centos)

前言: 为了能够提前发现kubernetes集群的问题以及方便快捷的查询容器的各类参数&#xff0c;比如&#xff0c;某个pod的内存使用异常高企 等等这样的异常状态&#xff08;虽然kubernetes有自动重启或者驱逐等等保护措施&#xff0c;但万一没有配置或者失效了呢&#xff09;&am…

PAM exec模块监控服务器ECS登录用户及IP,触发登录后,发送至钉钉

PAM 模块pam_exec &#xff0c;用于运行外部命令 PAM 项作为环境变量导出&#xff1a;* PAM_RHOST &#xff0c; PAM_RUSER &#xff0c; PAM_SERVICE &#xff0c; PAM_TTY &#xff0c; PAM_USER 和 PAM_TYPE *&#xff0c;其中包含以下模块类型之一&#xff1a;account&…