单片机开发---ESP32S3移植NES模拟器(一)

news2024/11/18 17:49:53

书接上文

《单片机开发—ESP32-S3模块上手》
《单片机开发—ESP32S3移植lvgl+触摸屏》

参考内容

依旧是参考韦东山老师的作品来移植的

《ESP32|爷青回!ESP32(单片机) NES模拟器_NES游戏机掌机教程(开源+详细讲解实现代码!)》

韦老师已经将代码开源,喜欢的朋友当然是可以去支持一波。
在这里插入图片描述

另外还有github上的一份原始代码,喜欢从头来的,也可以去学习一下,核心部分是一样的,适配硬件的部分需要自己来修改。
github上的espressif/esp32-nesemu

移植效果

esp32s3模拟nes

小时候玩的第一个游戏就是超级玛丽,算是callback了。
在这里插入图片描述

移植过程

我使用的是ESP-IDF4.4的开发环境,和韦老师的不太一样,并且硬件也是ESP32S3,所以我的方法就是将代码移植过来,重新构建了一个工程。
源码
在这里插入图片描述
将menu和nofrendo代码复制过来,并且将适配层代码提出来并列目录。工程采用了原始的helloworld项目,只是重新修改了主函数的c文件。
在这里插入图片描述

漫长的编译过程

修改Cmake

首先需要添加对目录的检索,将c文件都进行编译,并且添加头文件检索路径,以便包含的时候,更加简单。

FILE(GLOB_RECURSE app_sources ./*.* ./menu/*.* ./esp32s3/*.* ./nofrendo/*.* ./nofrendo/cpu/*.* ./nofrendo/libsnss/*.* ./nofrendo/mappers/*.* ./nofrendo/nes/*.* ./nofrendo/sndhrdw/*.*)

idf_component_register(
 SRCS ${app_sources}
 INCLUDE_DIRS "."
 INCLUDE_DIRS "./menu/"
 INCLUDE_DIRS "./esp32s3/"
 INCLUDE_DIRS "./nofrendo/"
 INCLUDE_DIRS "./nofrendo/cpu/"
 INCLUDE_DIRS "./nofrendo/libsnss/"
 INCLUDE_DIRS "./nofrendo/mappers/"
 INCLUDE_DIRS "./nofrendo/nes/"
 INCLUDE_DIRS "./nofrendo/sndhrdw/"
 EMBED_FILES "./100ask_logo.jpg"
 )

这两行就达到了自动搜索对应路径的c文件,并且检索对应路径的头文件。

另外如果编译的时候,需要修改一些FLAGS或者增加一些宏定义进行配置编译,参考下面句子修改

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=char-subscripts -Wno-error=attributes -DNOFRENDO_DEBUG -DCONFIG_HW_CONTROLLER_GPIO")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=char-subscripts -Wno-error=attributes -DNOFRENDO_DEBUG -DCONFIG_HW_CONTROLLER_GPIO") 

我将对应韦老师的代码中定义的宏定义以及FLAGS放在这里了,然后才能开始第一步的编译。否则就怕有其他莫名其妙的问题。
在这里插入图片描述

宏定义与函数冲突

编译的时候遇到

expected declaration specifiers or '...' before '

原因就是模拟器中重新定义了malloc和free
在这里插入图片描述
但是和其他文件一起编译的时候,收到stdlib.h中的同名函数影响,就会报错。
尝试过用原有的malloc,但是会出现内存异常。
在这里插入图片描述

所以直接将模拟器部分的代码,重新替换了新的宏定义,
在这里插入图片描述
里面可能有一些问题,通过这个重新封装的函数,在释放空指针等操作的时候,给出提示,或者直接跳过。

不起作用的一句话

error: this 'if' clause does not guard... [-Werror=misleading-indentation]

报错的

if (!pMem)
    return XX;

修改后

if (!pMem)
{
    return XX;
}

反正我是一直看不上那些不爱加括号的代码。一块的功能,就是要用括号括起来,这样看起来工整多了。
在这里插入图片描述

移植小窍门

涉及到硬件的部分,首先把中间层的代码中,每个文件对外的接口提供出来,保证函数存在,该有返回值的,有返回值,其余代码注释掉。
保证编译通过,然后烧写,根据报错的内容,一步一步打开代码再修改,这样能够熟悉所有的流程,并且学习出代码的功能。
随后慢慢增加代码。

在这里插入图片描述

移植过程

该注释的注释掉,很快就能编译通过。然后就开始调试。

SD卡模块

源码首先是注册SD卡
在这里插入图片描述
因为是要将nes的rom放在sd卡中。
参考esp32s3的example代码。替换掉源码中的部分代码

esp_err_t init_sd_card(void)
{
	esp_err_t ret;
	
	// Options for mounting the filesystem.
	// If format_if_mount_failed is set to true, SD card will be partitioned and
	// formatted in case when mounting fails.
	esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
		.format_if_mount_failed = true,
#else
		.format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
		.max_files = 5,
		.allocation_unit_size = 16 * 1024
	};
	sdmmc_card_t *card;
	const char mount_point[] = "/sdcard";
	ESP_LOGI(TAG, "Initializing SD card");

	// Use settings defined above to initialize SD card and mount FAT filesystem.
	// Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
	// Please check its source code and implement error recovery when developing
	// production applications.
	ESP_LOGI(TAG, "Using SPI peripheral");

	sdmmc_host_t host = SDSPI_HOST_DEFAULT();
	host.slot=SD_HOST;

	spi_bus_config_t bus_cfg = {
		.mosi_io_num = SD_MOSI,
		.miso_io_num = SD_MISO,
		.sclk_io_num = SD_CLK,
		.quadwp_io_num = -1,
		.quadhd_io_num = -1,
		.max_transfer_sz = 4000,
	};
	ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CH_AUTO);
	if (ret != ESP_OK) {
		ESP_LOGE(TAG, "Failed to initialize bus.");
		return;
	}

	// This initializes the slot without card detect (CD) and write protect (WP) signals.
	// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
	sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
	slot_config.gpio_cs = SD_CS;
	slot_config.host_id = host.slot;

	ESP_LOGI(TAG, "Mounting filesystem");
	ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);

	if (ret != ESP_OK) 
	{
		if (ret == ESP_FAIL)
		{
			ESP_LOGE(TAG, "Failed to mount filesystem. "
					 "If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
		} 
		else 
		{
			ESP_LOGE(TAG, "Failed to initialize the card (%s). "
					 "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
		}
		return ret;
	}
	else
	{
		// Card has been initialized, print its properties
		sdmmc_card_print_info(stdout, card);
		return ESP_OK;
	}

}

这里注意SD的SPI通道选择,因为LCD通常用高速通道,所以这个SD卡我们用在了SPI3上。
在这里插入图片描述
一共就两个SPI,只能这样计划了。
在这里插入图片描述

输入模块

源码第二步就是输入设备初始化
在这里插入图片描述
这里的输入设备支持到了三种,包括GPIO,I2C和手柄。
这里根据不同宏定义进行了编译包含,后者我们都没有,所以只能用GPIO。

上拉和下拉的选择
这里我用的是一个GPIO按键模块,前面在w801上用过的。
输入方式下。内部上拉保证了如果没有输入,就是高电平,下拉相反,没有输入就是低电平。

由于我这里公共端是高电平,所以需要使能下拉,保证了:
无输入:0,有输入:1

static void _init_gpio(gpio_num_t gpio_num)
{
    gpio_config_t io_conf = {};

    io_conf.intr_type = GPIO_INTR_POSEDGE;
    io_conf.pin_bit_mask = (1ULL<<gpio_num);
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pull_up_en = 0;
    io_conf.pull_down_en = 1;
    gpio_config(&io_conf);
}

然后定义了部分GPIO来使用
在这里插入图片描述
只是为了验证部分功能,所以只注册了部分按键
在这里插入图片描述

显示模块

第三步就是显示菜单,然后结合前面的内容选择rom
在这里插入图片描述
这里的初始化与esp32s3基本一致,所以修改好对应的引脚和SPI通道,就可以使用了
在这里插入图片描述
然后需要修改一下这个函数

//Load Rom list from flash partition to char array(lines), init some variables for printing rom list
void initRomList()
{
	DIR *pDir = NULL;
	struct dirent * pEnt = NULL;
	pDir = opendir("/sdcard/nes");
	char fileName[FILENAME_LENGTH][FILENAME_LENGTH+1];
	int dir_count = 0;

	entryCount = 0;

	
	if (NULL == pDir)
	{
		perror("opendir");
	}
	else
	{
		while (1)
		{
			pEnt = readdir(pDir);
			if(pEnt != NULL)
			{
				ESP_LOGI(TAG,"rom name[%s]", pEnt->d_name);
				strcpy(fileName[dir_count], pEnt->d_name);
				dir_count++;
				entryCount++;
			}
			else
			{
				break;
			}
		}
		closedir(pDir);
	}
	if(entryCount > 0)
	{
		menuEntries = (MenuEntry *)malloc(entryCount * sizeof(MenuEntry));
		for (int i = 0; i < entryCount; i++)
		{
			//menuEntries[i].entryNumber = i;
			//menuEntries[i].icon = 'E';
			menuEntries[i].icon = '$';
		
			//strcpy(menuEntries[i].name, fileName[i]);
			memset(menuEntries[i].fileName,0,FILENAME_LENGTH+1);//sunjin
			strcpy(menuEntries[i].fileName, fileName[i]);
	
			for (int j = strlen(menuEntries[i].fileName); j > 0; j--) 
			{
				if (menuEntries[i].fileName[j] < ' ') 
				{
					menuEntries[i].fileName[j] = '\0';
				}
			}
		}
		ESP_LOGI(TAG,"Read %d rom entries", entryCount);
	}
	else
	{
		ESP_LOGW(TAG,"no roms!");
	}
}

里面我修改了一下获取的文件数量变量初始值以及初始化了一下数组,否则会出现内存异常以及显示乱码的问题。
在这里插入图片描述

到达这一步的时候,就可以显示开机动画以及rom选择菜单了。

在这里插入图片描述

读取ROM

接下来就是正式启动模拟器了
在这里插入图片描述
这里的需要修改的,就是将rom文件读取到内存中,源码为这个函数
在这里插入图片描述

这里涉及到了一个分区表的概念,具体可以参考
分区表

简单来说就是将数据从SD卡读取到FALSH中,然后就可以当成一个静态数组来使用,访问这里就像访问内存一样,解决了单片机内存小的问题。

这里我就不一样了,我有8M的内存,所以这里我直接修改放在内存中。
在这里插入图片描述

	char *romdata;

	// Open the file
	ESP_LOGI(TAG, "Reading rom from %s", selectedRomFilename);
	FILE *rom = fopen(selectedRomFilename, "r");
	long fileSize = -1;
	if (!rom)
	{
		ESP_LOGE(TAG, "Could not read %s", selectedRomFilename);
		exit(1);
	}

	// First figure out how large the file is
	fseek(rom, 0L, SEEK_END);
	fileSize = ftell(rom);

	rewind(rom);
	romdata=malloc(fileSize+READ_BUFFER_SIZE);
	if (!romdata)
	{
		ESP_LOGE(TAG, "Could not malloc ");
		exit(1);
	}

	// Copy the file contents into EEPROM memory
	char buffer[READ_BUFFER_SIZE];
	int offset = 0;
	while (fread(buffer, 1, READ_BUFFER_SIZE, rom) > 0)
	{
		memcpy(romdata+offset,buffer,READ_BUFFER_SIZE);
		offset += READ_BUFFER_SIZE;
	}
	fclose(rom);

	ESP_LOGI(TAG, "Loaded %d bytes into ROM memory", offset);
 
	return (char *)romdata;

就是豪横。

绘制游戏

spi_lcd.c中对外就提供了两个接口,
在这里插入图片描述
其实就是用来初始化显示屏和绘制图像的,韦老师的代码中用额的gpio模拟的方式进行驱动屏幕,与前面显示菜单用了两套软件。
在这里插入图片描述

这里我整合为一套,就用了显示菜单的方式。所以初始化中,我只保留了一些变量初始化,然后申请了2条缓存,用来更新画面
在这里插入图片描述
绘制图像的函数,就比较难了。我看了好久才找到显示的数据。

void draw_write_frame(const uint16_t xs, const uint16_t ys, const uint16_t width, const uint16_t height, const uint8_t *data[],		bool xStr, bool yStr)
{
	int x, y;
	int xx, yy;
	int i;
	uint16_t x1, y1, evenPixel, oddPixel, backgroundColor;
	int drsy = 0;
	
	uint32_t xv, yv, dc;
	uint32_t temp[16];

	if(data==NULL)
	{
		return;
	}
	if (getShowMenu() != lastShowMenu)
	{
		memset(rowCrc, 0, sizeof rowCrc);
	}
	lastShowMenu = getShowMenu();

	int lastY = -1;
	int lastYshown = 0;

	// Black background
	backgroundColor = 0;

	for (y = 0; y < height; y++)
	{
		yy = yStr ? scaleY[y] : y;
		if (lastY == yy)
		{
			if (!lastYshown && !getShowMenu())
				continue;
		}
		else
		{
			lastY = yy;
			uint16_t crc = calcCrc(data[yy]);
			if (crc == rowCrc[yy] && !getShowMenu())
			{
				lastYshown = false;
				continue;
			}
			else
			{
				lastYshown = true;
				rowCrc[yy] = crc;
			}
		}

		//start line
		x1 = xs + (width - 1);
		y1 = ys + y + (height - 1);

		xv = U16x2toU32(xs, x1);
		yv = U16x2toU32((ys + y), y1);

		drsy = 0;
		
		x = 0;
		while (x < width)
		{
			// Render 32 pixels, grouped as pairs of 16-bit pixels stored in 32-bit values
			for (i = 0; i < 16; i++)
			{
				xx = xStr ? scaleX[x] : x;
				if (xx >= 32 && !xStr)
					xx -= 32;
				evenPixel = myPalette[(unsigned char)(data[yy][xx])];
				x++;
			
				xx = xStr ? scaleX[x] : x;
				if (xx >= 32 && !xStr)
					xx -= 32;
				oddPixel = myPalette[(unsigned char)(data[yy][xx])];
				x++;
			
				if (!xStr && (x <= 32 || x >= 288))
					evenPixel = oddPixel = backgroundColor;
				if (!yStr && y >= 224)
					evenPixel = oddPixel = backgroundColor;
				if (getShowMenu())
				{
					evenPixel = oddPixel = renderInGameMenu(x, y, evenPixel, oddPixel, xStr, yStr);
				}
				fastlines[BbufIdx][drsy++]=evenPixel;
				fastlines[BbufIdx][drsy++]=oddPixel;		
			}
		}
		AbufIdx = BbufIdx;
		BbufIdx = 1 - BbufIdx;
		
		nes_100ask_send_line_finish(mylcd_spi);
		nes_100ask_send_one_line(mylcd_spi, yy, (uint16_t*)(fastlines[AbufIdx]));
		
	}

	if (nes_100ask_get_shutdown())
		setBrightness(nes_100ask_get_bright());
//#if LCD_BCKL >= 0
//	if (nes_100ask_get_bright() == -1)
//		LCD_BKG_OFF();
//#endif

}

这里有两个问题。

  1. 数据获取

一开是以为传入的data就是数据,其实后来发现,这里需要计算出每个像素,

				fastlines[BbufIdx][drsy++]=evenPixel;
				fastlines[BbufIdx][drsy++]=oddPixel;	

再将循环buf一次一次交替行绘制。

  1. 调色板

模拟器计算出每个点的颜色,结果绘制来发现,颜色不对,像极了我之前在w801上移植的时候,于是我返回去找了一下,原来是这个原因,在写入SPI总线 时候,大小端的问题,所以为了从根本上解决问题。
我直接修改了调色板!


uint16 myPalette[256];

unsigned short Convert(unsigned short s) 
{
	char right, left;
	right = s& 0XFF;//低八位
	left = s >> 8;//高八位  右移8位
	s = right * 256 + left;
	return s;
}

static void set_palette(rgb_t *pal)
{
	uint16 c;

	int i;

	for (i = 0; i < 256; i++)
	{
		c = (pal[i].b >> 3) + ((pal[i].g >> 2) << 5) + ((pal[i].r >> 3) << 11);
		myPalette[i] = Convert(c);
	}
}

因为不要在画图的时候再进行转化,会影响显示速度。

在这里插入图片描述

其他功能

剩余的问题包括了声音,手柄2的扩展,这些东西后面需要补充一下,才能像一个能用的游戏机。
所以还有续集。

结束语

以前人有两个坎,73和84,现在人也有两坎,35和65,薅羊毛也不能光可着这一代人薅吧。
在这里插入图片描述

在这里插入图片描述

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

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

相关文章

深入解读神策分析云两大闭环,助力企业全面数字化转型应用

随着市场的发展、客户的深度使用以及全新的产品认知&#xff0c;神策希望能够帮助企业客户构建拥有正向反馈、持续提升、优化经营效率的数据应用闭环。本次&#xff0c;神策在用户行为分析的基础上&#xff0c;对分析云的整体产品架构进行全面升级&#xff0c;不仅包括公私域流…

json字符带有反斜杠\处理

目录说明说明 json字符带有反斜杠\&#xff0c;需要转义处理 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-text</artifactId><version>1.6</version></dependency>String json "{\"n…

解决安装2.4版本的percona-xtrabackup和5.6版本的mysql冲突的问题

问题描述&#xff1a;在阿里云上有一个5.6版本的mysql&#xff0c;想把备份的数据恢复到本地。按照阿里云的文档描述&#xff0c;本机mysql的版本也得是5.6&#xff0c;percona-xtrabackup版本得是2.4。于是开始动手操作&#xff0c;无非是在本机安装5.6版本的mysql和2.4版本的…

【C++】C++11语法 ~ 可变参数模板

&#x1f308;欢迎来到C专栏~可变参数模板 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&…

2月3日第壹简报,星期五,农历正月十三

2月3日第壹简报&#xff0c;星期五&#xff0c;农历正月十三坚持阅读&#xff0c;静待花开1. 香港&#xff1a;将向世界各地旅客派发50万张免费机票&#xff0c;3月1日起派发&#xff0c;为期6个月&#xff0c;率先向东南亚地区送出。2. 我国新增18处国际重要湿地&#xff0c;湿…

聚观早报|网易开放暴雪游戏退款申请通道;鱼跃医疗回应被罚270万

今日要闻&#xff1a;网易开放暴雪游戏退款申请通道&#xff1b;谷歌 ChatGPT 竞品搜索设计将迎来大改&#xff1b;“鱼跃医疗”回应被罚270万元&#xff1b;大众考虑在加拿大建设新的电池工厂&#xff1b;微软将把ChatGPT整合到必应搜索中网易开放暴雪游戏退款申请通道 2 月 1…

力扣刷题|654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树

LeetCode 654.最大二叉树 题目链接&#x1f517; LeetCode 654.最大二叉树 思路 最大二叉树的构建过程如下&#xff1a; 构造树一般采用的是前序遍历&#xff0c;因为先构造中间节点&#xff0c;然后递归构造左子树和右子树。 class Solution {public TreeNode constructM…

构造http请求的几种方式(附源码)

文章目录前言一、form表单构造http请求二、ajax构造http请求三、Java socket构造http请求总结前言 博主个人社区&#xff1a;开发与算法学习社区 博主个人主页&#xff1a;Killing Vibe的博客 欢迎大家加入&#xff0c;一起交流学习~~ 一、form表单构造http请求 form (表单) 是…

SSH基础知识(一)

SSH基础知识SSH对称加密非对称加密如何生成秘钥如何管理秘钥不同版本ssh命令安装ssh启动ssh服务登录ssh退出ssh登录远程登录执行命令ssh参数端口转发本地转发远程转发动态转发最近有用到SFTP协议完成一些功能开发&#xff0c;这玩意和FTP比较像&#xff0c;当时以为是升级版&am…

FPGA和CPLD芯片选型介绍(二)

FPGA器件选型&#xff08;以Xilinx和Altera为例&#xff09;器件选型是件很严肃的事情&#xff0c;既要考虑性能又要兼顾成本&#xff0c;还要考虑长期供货的稳定性&#xff0c;因此很考验工程师的知识广度储备。一般而言&#xff0c;FPGA&#xff08;CPLD&#xff09;需要分三…

3、Maven——Maven创建java web工程,Maven search插件

目录 一、Maven创建java web工程 二、Maven创建java web项目的结构 1、添加项目目录 2、web.xml配置处理报错 三、IDEA更改XML版本 一、Maven创建java web工程 创建Empty Project空工程&#xff1b;右击Empty Project工程--->New--->Module--->Maven Archetype N…

Linux环境下安装软件合集【2】

Linux环境下安装软件合集【2】 Linux环境下安装软件合集【1】 1 安装tomcat 1.1 安装启动tomcat 下载tomcat压缩包 通过wget方式 wget --no-check-certificate https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.70/bin/apache-tomcat-9.0.70.tar.gz直接官网下载压缩包&…

23k入职腾讯测试岗那天,我哭了,这5个月付出的一切总算没有白费~

先说一下自己的个人情况&#xff0c;计算机专业&#xff0c;16年普通二本学校毕业&#xff0c;经历过一些失败的工作经历后&#xff0c;经推荐就进入了华为的测试岗&#xff0c;进去才知道是接了个外包项目&#xff0c;不太稳定的样子&#xff0c;可是刚毕业谁知道什么外包不外…

基于SSH框架的学生成绩管理系统源码+数据库,Struts2、Spring与Hibernate整合应用,实验报告

Struts2、Spring与Hibernate整合应用&#xff1a;基于SSH框架的学生成绩管理系统 最近一个实验课程&#xff0c;需要做一个基于SSH框架的学生成绩管理系统。就简简单单做了一下…… 1.1 实验要求&#xff1a; &#xff08;1&#xff09; 整合Struts2、Spring和Hibernate框架 …

​箭头函数和普通函数的区别​

一.外形不同&#xff1a;箭头函数使用箭头定义&#xff0c;普通函数中没有 代码实例如下&#xff1a; // 普通函数 function func(){// code } // 箭头函数 let func()>{// code } 二.箭头函数都是匿名函数 普通函数可以有匿名函数&#xff0c;也可以有具体名函数&#xf…

框架高级课程系列之Redis6笔记

文章目录前言NoSQL数据库简介技术发展NoSQL数据库NoSQL适用场景NoSQL不适用场景Redis概述安装配合关系型数据库做高速缓存多样的数据结构存储持久化数据Redis 安装准备工作&#xff1a;下载安装最新版的gcc编译器启动 Redis方式Redis 关闭方式Redis介绍相关知识常用五大数据类型…

如何利用python机器学习解决空间模拟与时间预测问题及经典案例分析

目录 专题一 机器学习原理与概述 专题二 Python编译工具组合安装教程 专题三 掌握Python语法及常见科学计算方法 专题四 机器学习数据清洗 专题五 机器学习与深度学习方法 专题六 机器学习空间模拟实践操作 专题七 机器学习时间预测实践操作 更多推荐 了解机器学习的发…

Web服务器TomCat快速入门(从安装到部署)

文章目录什么是Web服务器&#xff1f;相关概念基本使用下载安装卸载启动关闭配置部署&#x1f4c2;橙子精品文章学习推荐什么是Web服务器&#xff1f; Web 服务器是一个应用程序&#xff0c;对 HTTP 协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff…

【Linux 系统运维基础】Linux命令大全

Linux 命令大全立志列举出Linux中常用的所有命令(有很多命令不使用的话&#xff0c;很快会忘记哦)。 1. cd 目录前进/ 后缀 cd /表示跳转到根目录下&#xff0c;无论是哪个目录下&#xff0c;执行此命令后一定会跳转到根目录下。 cd ~ 表示跳转到主目录下。 cd . 表示跳转还…

【5.2】Nacos注册中心--服务多级存储模型

【【5.2】Nacos注册中心--服务多级存储模型1 Nacos服务多级存储模型2 服务跨集群调用问题3 配置集群属性4 总结1 Nacos服务多级存储模型 之前有服务的概念&#xff1a; 例如&#xff1a;提供用户查询的use-service和提供订单查询的order-service&#xff0c;它们都叫服务。 我…