iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)

没玩过NES游戏的童年,可能不是80后的童年。我们小时候是从玩FC开始接触游戏机的,那时真的是红极一时啊,我上初中时还省吃俭用买了一台小霸王,暑假里把电视机都给打爆了!那时任天堂单是FC机的主机的发售收入就超过全美的电视台的收入的总和,在人们的心目中扎下了任天堂的这个招牌。

前言

1983年7月15日,由日本任天堂株式会社(原本是生产日式扑克即“花札”)的宫本茂先生领导开发的一种第三代家用电子游戏机:FC,全称:Family Computer,也称作:Famicom;在欧美发售时则被称为nes,全称:Nintendo Entertainment System;在中国大陆、台湾和香港等地,因其外壳为红白两色,所以人们俗称其为“红白机”,正式进入市场销售,并于后来取得了巨大成功,由此揭开了家用电子游戏机遍布世界任何角落,电子游戏全球大普及的序幕。

什么是InfNES?

一款NES游戏模拟器。InfoNES可以很容易地被移植到各个平台,作者是Martin Freij。他是一位瑞典的程序员和游戏爱好者,于2002年开发了infoNES模拟器。infoNES是一个基于NES(任天堂娱乐系统)的模拟器,旨在让人们能够在计算机上玩经典的NES游戏。

InfoNES具备良好的可移植性,它将与环境有关的内容都清出了软件内核,并且单独集合于一个InfoNES_System.h中,我们要做的就是实现这里提到的各种函数,再把InfoNES加入到我们的工程中一起编译。

最近成功实现了USB接口的FC手柄驱动,使得在imx6ull开发板玩游戏具有可玩性,这里将这个移植过程记录下来。如果对NES模拟器的源码实现感兴趣,infoNES也是个不错的研究对象,代码结构清晰,可以让你了解到如何模拟实现k6502这款经典cpu的,加深对计算机体系结构的理解。

接下来让我们重温下经典,缅怀下童年吧!

池塘外的迷路书上,知鸟在声声叫着夏天......,伴随着优美的歌声,仿佛穿越回来了,少年。

完成以下操作,让你即刻拥有款移动游戏机,实现童年时的梦想。

很早之前我在imax283平台上移植过infoNES,那时我的github仓地址是:

https://github.com/yongzhena/infoNES

这次直接拉取下来用,只是修改下joypad手柄驱动的代码就可以完美运行啦。

移植过程

整个移植过程主要涉及三部分,显示、声音输出和usb手柄支持。前两个直接拉取上面的我的仓直接就具备了,这里着重介绍下USB手柄驱动支持。

基于fb0的LCD显示

在InfoNES_System_Linux.cpp文件中修改。显示这块儿实现两个函数,一个是lcd_fb_init,一个是lcd_fb_display_px。

static int lcd_fb_init()
{//如果使用 mmap 打开方式 必须是 读定方式fb_fd = open("/dev/fb0", O_RDWR);if(-1 == fb_fd){printf("cat't open /dev/fb0 \n");return -1;}//获取屏幕参数if(-1 == ioctl(fb_fd, FBIOGET_VSCREENINFO, &var)){close(fb_fd);printf("cat't ioctl /dev/fb0 \n");return -1;}//计算参数px_width     = var.bits_per_pixel /8;line_width   = var.xres * px_width;screen_width = var.yres * line_width;lcd_width    = var.xres;lcd_height   = var.yres;printf("fb width:%d height:%d pixel:%d \n", lcd_width, lcd_height,px_width*8);fb_mem = (unsigned char *)mmap(NULL, screen_width, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);if(fb_mem == (void *)-1){close(fb_fd);printf("cat't mmap /dev/fb0 \n");return -1;}//清屏memset(fb_mem, 0 , screen_width);return 0;
}
static int lcd_fb_display_px(WORD color, int x, int y)
{unsigned char  *pen8;unsigned short *pen16;pen8 = (unsigned char *)(fb_mem + y*line_width + x*px_width);pen16 = (unsigned short *)pen8;*pen16 = color;return 0;
}

以下的实现注意zoom_x_tab,zoom_y_tab这两项。它的作用是对像素做了全屏和放大处理。 源码里的make_zoom_tab()就是干这个用。如果觉得屏幕很大,放大后颗粒感很重,能否再优化?这里是个可能的优化方向。

/*===================================================================*/
/*                                                                   */
/*      InfoNES_LoadFrame() :                                        */
/*           Transfer the contents of work frame on the screen       */
/*                                                                   */
/*===================================================================*/
unsigned short ChColor(unsigned short color)
{return (color>>3)<<4|(color&0x001f);
}void InfoNES_LoadFrame()
{int x,y;int line_width;WORD wColor,R,G,B,Gr;//修正 if(0 < fb_fd){for (y = 0; y < lcd_height; y++ ){line_width = zoom_y_tab[y] * NES_DISP_WIDTH;for (x = 0; x < lcd_width; x++ ){wColor = ChColor(WorkFrame[line_width  + zoom_x_tab[x]]);lcd_fb_display_px(wColor, x, y);}}}/*16 bit per pixel*//* Exchange 16-bit to 256 gray *//*for (y = 0; y < NES_DISP_HEIGHT; y++ ){for (x = 0; x < NES_DISP_WIDTH; x++ ){//wColor = WorkFrame[y * lcd_width  + x ];wColor = WorkFrame[ ( y << 8 ) + x ];R = ( ( wColor & 0x7c00 ) >>7 );G = ( ( wColor & 0x03e0 ) >>2 );B = ( ( wColor & 0x001f ) <<3 );            //Gr= ( ( 9798*R + 19235*G + 3735*B)>>15);wColor=(WORD)((B<<16)|(G<<8)|R);lcd_fb_display_px(wColor, x, y);}}*/  
}

基于Alsa的声音支持

实现这个声音支持的前提是,板子上得有基于alsa框架的音频驱动且功能正常。否则以下这些实现里需要全部留空,不用实现。

/*===================================================================*/
/*                                                                   */
/*        InfoNES_SoundInit() : Sound Emulation Initialize           */
/*                                                                   */
/*===================================================================*/
void InfoNES_SoundInit( void )
{}/*===================================================================*/
/*                                                                   */
/*        InfoNES_SoundOpen() : Sound Open                           */
/*                                                                   */
/*===================================================================*/
int InfoNES_SoundOpen( int samples_per_sync, int sample_rate )
{// sample_rate 采样率 44100// samples_per_sync  735// 采样率 / 8 * 声道数 = 44100 / 8 * 1 = 5512.5// 8位 声音/*声道数 1采样率 44100采样位数 8每次播放块大小(NES  APU 每次生成一块)735*/unsigned int rate      = sample_rate;snd_pcm_hw_params_t *hw_params;if(0 > snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) {printf("snd_pcm_open err\n");return -1;}printf("snd_pcm_open ok!\nsamples_per_sync=%d,sample_rate=%d\n",samples_per_sync,sample_rate);if(0 > snd_pcm_hw_params_malloc(&hw_params)){printf("snd_pcm_hw_params_malloc err\n");return -1;}if(0 > snd_pcm_hw_params_any(playback_handle, hw_params)){printf("snd_pcm_hw_params_any err\n");return -1;}if(0 > snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) {printf("snd_pcm_hw_params_any err\n");return -1;}//16bit PCM 数据if(0 > snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8)){printf("snd_pcm_hw_params_set_format err\n");return -1;}if(0 > snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &rate, 0)) {printf("snd_pcm_hw_params_set_rate_near err\n");return -1;}//单声道 非立体声if(0 > snd_pcm_hw_params_set_channels(playback_handle, hw_params, 1)){printf("snd_pcm_hw_params_set_channels err\n");return -1;}if(0 > snd_pcm_hw_params(playback_handle, hw_params)) {printf("snd_pcm_hw_params err\n");return -1;}snd_pcm_hw_params_free(hw_params);if(0 > snd_pcm_prepare(playback_handle)) {printf("snd_pcm_prepare err\n");return -1;}return 1;
}/*===================================================================*/
/*                                                                   */
/*        InfoNES_SoundClose() : Sound Close                         */
/*                                                                   */
/*===================================================================*/
void InfoNES_SoundClose( void )
{snd_pcm_close(playback_handle);
}/*===================================================================*/
/*                                                                   */
/*            InfoNES_SoundOutput() : Sound Output 5 Waves           */
/*                                                                   */
/*===================================================================*/
void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 )
{int i;int ret;unsigned char wav;unsigned char *pcmBuf = (unsigned char *)malloc(samples);//printf("InfoNES_SoundOutput,samples=%d\n",samples);//printf("\n");for (i=0; i <samples; i++){wav = (wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i]) / 5;//单声道 8位数据pcmBuf[i] = wav;//printf("%02x",wav);}//printf("\n");ret = snd_pcm_writei(playback_handle, pcmBuf, samples);if(-EPIPE == ret){snd_pcm_prepare(playback_handle);}free(pcmBuf);return ;
}

USB手柄支持

接下来这块儿是介绍的重点,实现usb手柄驱动的支持。这样才有可玩性啊。我买的这款USB的游戏手柄很便宜,也很容易买到。如果你的USB手柄不是这款,那么实现驱动支持的原理也是类似的,万变不离宗,只是键值对应关系跟我的可能不一样,实测改下即可。

关于USB游戏手柄的驱动支持,参见我的上篇博文:iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄_特立独行的猫a的博客-CSDN博客

不想按上文总结的重新编译内核的话,可以把驱动单独编译成模块动态加载进去。

这里介绍下让infoNES支持usb手柄需要做哪些移植。

按键键值测试小程序

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h> #define _EV_KEY         0x01    /* button pressed/released */
#define _EV_ABS         0x03    
#define _EV_MSC         0x04   int main() {printf("hello,usb hid joystick key test\n");int fd = open("/dev/input/event3", O_RDONLY);struct input_event e;while(1) {read(fd, &e, sizeof(e));switch(e.type) {case _EV_KEY:printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);break;case _EV_ABS:printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);break;case _EV_MSC:printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);break;default:if(e.type != 0){printf("type:%d, code: %d,value: %d, time: %d\n",e.type, e.code,e.value, e.time);}}}close(fd);return 0;
}

joypad_input.cpp文件修改

主要是USBjoypadGet()接口的实现,要跟FC手柄的键值对应上。

static int USBjoypadGet(void)
{/*** FC手柄 bit 键位对应关系 真实手柄中有一个定时器,处理 连A  连B * 0  1   2       3       4    5      6     7* A  B   Select  Start  Up   Down   Left  Right*///因为 USB 手柄每次只能读到一位键值 所以要有静态变量保存上一次的值static unsigned char joypad = 0;struct input_event e;if(0 < read (USBjoypad_fd, &e, sizeof(e))){if(0x3 == e.type){/*上:value:0 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(0 == e.value && 0x1 == e.code){joypad |= 1<<4;printf("Up\n");}/*下:value:255 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(255 == e.value && 0x1 == e.code){joypad |= 1<<5;printf("Down\n");}//松开if(127 == e.value && 0x1 == e.code){joypad &= ~(1<<4 | 1<<5);}/*左:value:0 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(0 == e.value && 0 == e.code){joypad |= 1<<6;printf("Left\n");}/*右:value:255 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(255 == e.value && 0 == e.code){joypad |= 1<<7;printf("Right\n");}//松开if(127 == e.value && 0 == e.code){joypad &= ~(1<<6 | 1<<7);}}if(0x1 == e.type){/*选择:value:0x1 type:0x1 code:296value:0x0 type:0x1 code:296*/if(0x1 == e.value && 296 == e.code){joypad |= 1<<2;printf("Select\n");}if(0x0 == e.value && 296 == e.code){joypad &= ~(1<<2);}/*开始:value:0x1 type:0x1 code:297value:0x0 type:0x1 code:297*/if(0x1 == e.value && 297 == e.code){joypad |= 1<<3;printf("Start\n");}if(0x0 == e.value && 297 == e.code){joypad &= ~(1<<3);}/*Avalue:0x1 type:0x1 code:288value:0x0 type:0x1 code:288*/if(0x1 == e.value && 288 == e.code){joypad |= 1<<0;printf("A\n");}if(0x0 == e.value && 288 == e.code){joypad &= ~(1<<0);}/*Bvalue:0x1 type:0x1 code:289value:0x0 type:0x1 code:289*/if(0x1 == e.value && 289 == e.code){joypad |= 1<<1;printf("B\n");}if(0x0 == e.value && 289 == e.code){joypad &= ~(1<<1);}/*Xvalue:0x1 type:0x1 code:290value:0x0 type:0x1 code:290*/if(0x1 == e.value && 290 == e.code){joypad |= 1<<0;printf("X\n");}if(0x0 == e.value && 290 == e.code){joypad &= ~(1<<0);}/*Yvalue:0x1 type:0x1 code:291value:0x0 type:0x1 code:291*/if(0x1 == e.value && 291 == e.code){joypad |= 1<<1;printf("Y\n");}if(0x0 == e.value && 291 == e.code){joypad &= ~(1<<1);}}return joypad;}return -1;
}

完整实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/input.h> #define JOYPAD_DEV "/dev/joypad"
#define USB_JS_DEV "/dev/input/event3"typedef struct JoypadInput{int (*DevInit)(void);int (*DevExit)(void);int (*GetJoypad)(void);struct JoypadInput *ptNext;pthread_t tTreadID;     /* 子线程ID */
}T_JoypadInput, *PT_JoypadInput;//全局变量通过互斥体访问
static unsigned char g_InputEvent;static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;static int joypad_fd;
static int USBjoypad_fd;
static PT_JoypadInput g_ptJoypadInputHead;static void *InputEventTreadFunction(void *pVoid)
{/* 定义函数指针 */int (*GetJoypad)(void);GetJoypad = (int (*)(void))pVoid;while (1){//因为有阻塞所以没有输入时是休眠g_InputEvent = GetJoypad();//有数据时唤醒pthread_mutex_lock(&g_tMutex);/*  唤醒主线程 */pthread_cond_signal(&g_tConVar);pthread_mutex_unlock(&g_tMutex);}
}static int RegisterJoypadInput(PT_JoypadInput ptJoypadInput)
{PT_JoypadInput tmp;if(ptJoypadInput->DevInit()){return -1;}//初始化成功创建子线程 将子项的GetInputEvent 传进来pthread_create(&ptJoypadInput->tTreadID, NULL, InputEventTreadFunction, (void*)ptJoypadInput->GetJoypad);if(! g_ptJoypadInputHead){g_ptJoypadInputHead = ptJoypadInput;}else{tmp = g_ptJoypadInputHead;while(tmp->ptNext){tmp = tmp->ptNext;}tmp->ptNext = ptJoypadInput;}ptJoypadInput->ptNext = NULL;return 0;
}static int joypadGet(void)
{static unsigned char joypad = 0;//printf("joypadGet val:\n");joypad = read(joypad_fd, 0, 0);return joypad;
}static int joypadDevInit(void)
{joypad_fd = open(JOYPAD_DEV, O_RDONLY);if(-1 == joypad_fd){printf("%s dev not found \r\n", JOYPAD_DEV);return -1;}return 0;
}static int joypadDevExit(void)
{close(joypad_fd);return 0;
}static T_JoypadInput joypadInput = {joypadDevInit,joypadDevExit,joypadGet,
};static int USBjoypadGet(void)
{/*** FC手柄 bit 键位对应关系 真实手柄中有一个定时器,处理 连A  连B * 0  1   2       3       4    5      6     7* A  B   Select  Start  Up   Down   Left  Right*///因为 USB 手柄每次只能读到一位键值 所以要有静态变量保存上一次的值static unsigned char joypad = 0;struct input_event e;if(0 < read (USBjoypad_fd, &e, sizeof(e))){if(0x3 == e.type){/*上:value:0 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(0 == e.value && 0x1 == e.code){joypad |= 1<<4;printf("Up\n");}/*下:value:255 type:0x3 code:0x1value:127 type:0x3 code:0x1*/if(255 == e.value && 0x1 == e.code){joypad |= 1<<5;printf("Down\n");}//松开if(127 == e.value && 0x1 == e.code){joypad &= ~(1<<4 | 1<<5);}/*左:value:0 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(0 == e.value && 0 == e.code){joypad |= 1<<6;printf("Left\n");}/*右:value:255 type:0x3 code:0x0value:127 type:0x3 code:0x0*/if(255 == e.value && 0 == e.code){joypad |= 1<<7;printf("Right\n");}//松开if(127 == e.value && 0 == e.code){joypad &= ~(1<<6 | 1<<7);}}if(0x1 == e.type){/*选择:value:0x1 type:0x1 code:296value:0x0 type:0x1 code:296*/if(0x1 == e.value && 296 == e.code){joypad |= 1<<2;printf("Select\n");}if(0x0 == e.value && 296 == e.code){joypad &= ~(1<<2);}/*开始:value:0x1 type:0x1 code:297value:0x0 type:0x1 code:297*/if(0x1 == e.value && 297 == e.code){joypad |= 1<<3;printf("Start\n");}if(0x0 == e.value && 297 == e.code){joypad &= ~(1<<3);}/*Avalue:0x1 type:0x1 code:288value:0x0 type:0x1 code:288*/if(0x1 == e.value && 288 == e.code){joypad |= 1<<0;printf("A\n");}if(0x0 == e.value && 288 == e.code){joypad &= ~(1<<0);}/*Bvalue:0x1 type:0x1 code:289value:0x0 type:0x1 code:289*/if(0x1 == e.value && 289 == e.code){joypad |= 1<<1;printf("B\n");}if(0x0 == e.value && 289 == e.code){joypad &= ~(1<<1);}/*Xvalue:0x1 type:0x1 code:290value:0x0 type:0x1 code:290*/if(0x1 == e.value && 290 == e.code){joypad |= 1<<0;printf("X\n");}if(0x0 == e.value && 290 == e.code){joypad &= ~(1<<0);}/*Yvalue:0x1 type:0x1 code:291value:0x0 type:0x1 code:291*/if(0x1 == e.value && 291 == e.code){joypad |= 1<<1;printf("Y\n");}if(0x0 == e.value && 291 == e.code){joypad &= ~(1<<1);}}return joypad;}return -1;
}static int USBjoypadDevInit(void)
{USBjoypad_fd = open(USB_JS_DEV, O_RDONLY);if(-1 == USBjoypad_fd){printf("%s dev not found \r\n", USB_JS_DEV);return -1;}return 0;
}static int USBjoypadDevExit(void)
{close(USBjoypad_fd);return 0;
}static T_JoypadInput usbJoypadInput = {USBjoypadDevInit,USBjoypadDevExit,USBjoypadGet,
};int InitJoypadInput(void)
{int iErr = 0;//iErr = RegisterJoypadInput(&joypadInput);iErr = RegisterJoypadInput(&usbJoypadInput);return iErr;
}int GetJoypadInput(void)
{/* 休眠 */pthread_mutex_lock(&g_tMutex);pthread_cond_wait(&g_tConVar, &g_tMutex);	/* 被唤醒后,返回数据 */pthread_mutex_unlock(&g_tMutex);return g_InputEvent;
}

编译生成

最后,交叉编译生成可执行文件,放到板子上执行即可,插上USB手柄就可以玩啦,运行不错!还很流畅。需要注意的是,为了支持声音,使用了alsa的头文件并链接了libasound库。需确保你的环境里有这个库,没有的话不支持声音输出,可以去掉这个链接。文末有NES游戏的ROM资源。

makefile脚本

#根据实际路径修改工具链路径
CHAIN_ROOT=/opt/yang/imax6ul/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
CROSS_COMPILE=$(CHAIN_ROOT)/arm-linux-gnueabihf-#CHAIN_ROOT= /home/yang/b503/ctools/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
#CROSS_COMPILE=$(CHAIN_ROOT)/arm-linux-gnueabihf-
#CROSS_COMPILE = CC     := $(CROSS_COMPILE)gcc
#CC = arm-poky-linux-gnueabi-gcc
TARBALL = InfoNES08J# InfoNES
.CFILES =	./../K6502.cpp \./../InfoNES.cpp \./../InfoNES_Mapper.cpp \./../InfoNES_pAPU.cpp \./InfoNES_System_Linux.cpp joypad_input.cpp.OFILES	=	$(.CFILES:.cpp=.o)CCFLAGS =    -o2 -fsigned-char  -I../
LDFILGS = -lstdc++	-L../libs	# gcc3.x.xall: InfoNESInfoNES: $(.OFILES)$(CC) $(INCLUDES) -o $@ $(.OFILES) $(LDFILGS) -lm  -lpthread -lasound.cpp.o:$(CC) $(INCLUDES) -c $(CCFLAGS) $*.cpp  -o $@clean:rm -f $(.OFILES) ../*~ ../*/*~ corecleanall:rm -f $(.OFILES) ../*~ ../*/*~ core InfoNESrelease: clean alltar:( cd ..; \tar cvf $(TARBALL).tar ./*; \gzip $(TARBALL).tar \)install:install ./InfoNES /usr/local/bin

其他资源

NES红白机全屏显示

NES专题——NES游戏机简介_nesfc_金小庭的博客-CSDN博客

V3S移植nes游戏模拟器(附带游戏合集)_v3s编译游戏模拟器_qq_46604211的博客-CSDN博客

任天堂红白机nes游戏简介 任天堂红白机nes游戏简介

资料:内含众多NES的游戏ROM文件及运行模拟器

链接:https://pan.baidu.com/s/1uXAxLKGmKGwZFB3Yraq8gg  提取码:qxcy 

游戏合集并解压,然后改名为游戏名为英文
链接:https://pan.baidu.com/s/16hIWwYQQEX9aOBDG1dVa0A
提取码:asdf

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

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

相关文章

程序环境和预处理(含C语言程序的编译+链接)--1

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f43b;‍❄个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN…

在CentOS 7上挂载硬盘到系统的步骤及操作

目录 1&#xff1a;查询未挂载硬盘2&#xff1a;创建挂载目录3&#xff1a;检查磁盘是否被分区4&#xff1a;格式化硬盘5&#xff1a;挂载目录6&#xff1a;检查挂载状态7&#xff1a;设置开机自动挂载总结&#xff1a; 本文介绍了在CentOS 7上挂载硬盘到系统的详细步骤。通过确…

完全背包问题

题目链接 题意&#xff1a;在01背包的基础上多了每个物品都可以无限取的条件 思路&#xff1a;首先考虑在01背包的基础上的暴力枚举&#xff0c;我们可以在枚举前i件物品最多拿j的容量时再遍历当前物品拿的数量 贴一个暴力tle代码&#xff1a; #include<bits/stdc.h> #d…

【云原生K8s】初识Kubernetes的理论基础

K8S由google的Borg系统(博格系统&#xff0c;google内部使用的大规模容器编排工具)作为原型&#xff0c;后经GO语言延用Borg的思路重写并捐献给CNCF基金会开源。 云原生基金会&#xff08;CNCF&#xff09;于2015年12月成立&#xff0c;隶属于Linux基金会。CNCF孵化的第一个项目…

短视频矩阵源码

一、短视频矩阵源码搭建解析&#xff1a; 目录 一、短视频矩阵源码搭建解析&#xff1a; 二、短视频矩阵源码的开发路径分享&#xff1a; 三、短视频矩阵系统开发应具备哪些能力&#xff1f; 短视频技术开发能力&#xff1a; 开发人员应具备短视频相关技术能力&#xff0c…

iOS 后台运行

iOS后台行&#xff0c;一般有两种方式&#xff1a; 1.UIBackgroundTaskIdentifier后台任务标记时, 2.设置后台运行模式&#xff0c;需要有voip&#xff0c;location功能的才行。不然app上线审核肯定是过不了的。 下面是我学习后台运行的尝试过程。 一.首先创建一个项目功程…

python#django数据库一对一/一对多/多对多

一对一OneToOneField 用户和用户信息 搭建 # 一对一 class TestUser(models.Model): usernamemodels.CharField(max_length32) password models.CharField(max_length32) class TestInfo(models.Model): mick_namemodels.CharField(max_length32) usermode…

zookeeper --- 高级篇

一、zookeeper 事件监听机制 1.1、watcher概念 zookeeper提供了数据的发布/订阅功能&#xff0c;多个订阅者可同时监听某一特定主题对象&#xff0c;当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变等)&#xff0c;会实时、主动通知所有订阅者 …

STM32CubeMX+VSCODE+EIDE+RT-THREAD 工程创建

Eide环境搭建暂且不表&#xff0c;后续补充。主要记录下Vscode环境下 创建Rt-thread工程的过程。分别介绍STM32CubeMX添加rtt支持包的方式和手动添加rtt kernel方式。STM32CubeMX生成工程的时候有"坑"&#xff0c;防止下次忘记&#xff0c;方便渡一下有缘人&#xff…

【Linux后端服务器开发】Reactor模式实现网络计算器

目录 一、Reactor模式概述 二、日志模块&#xff1a;Log.hpp 三、TCP连接模块&#xff1a;Sock.hpp 四、非阻塞通信模块&#xff1a;Util.hpp 五、多路复用I/O模块&#xff1a;Epoller.hpp 六、协议定制模块&#xff1a;Protocol.hpp 七、服务器模块&#xff1a;Server.…

基于SpringBoot+Vue的地方美食分享网站设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Docker实战-操作Docker容器实战(一)

导语   在之前的分享中&#xff0c;我们介绍了关于如何去操作Docker镜像&#xff0c;下面我们来看看如何去操作容器。 简单来讲&#xff0c;容器是镜像运行的一个实例&#xff0c;与镜像不同的是镜像只能作为一个静态文件进行读取&#xff0c;而容器是可以在运行时进行写入操…

2023再谈前端状态管理

目录 什么是状态管理&#xff1f; 状态 常见模式 要解决的问题 心智模型 React Context Context 的问题 优点 缺点 React 外部状态管理库 概览 Class 时代 Redux 单向数据流 三大原则 如何处理异步 如何处理数据间联动 优点 缺点 Dva icestore Mobx 设…

危大工程智慧工地源码,微服务+Java+Spring Cloud +UniApp +MySql 物联网、人工智能、视频AI分析

一套智慧工地管理平台源码&#xff0c;PC端移动APP端可视货数据管理端源码 智慧工地可视化系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&a…

windows上给oracle打补丁注意事项

打补丁的过程 1、升级opatch工具&#xff0c;检查剩余空间用于存放ORACLE_HOME的备份&#xff0c;设置oracle_home环境变量,通过readme中的先决条件来检查现有补丁是否和本次补丁冲突 2、opatch apply 升级数据库软件&#xff0c;这个必须数据库文件不要被进程调用 在windows上…

美团前端研发框架Rome实践和演进趋势

本文整理自美团技术沙龙第76期《大前端研发协同效能提升与实践》&#xff0c;为大家介绍了美团到店前端研发框架Rome实践和演进趋势。 具体来讲&#xff0c;本文首先介绍了Rome整体的工程生态、演变路径、规模化升级以及工程框架外的开发辅助工具&#xff1b;第二部分&#xff…

GoogLeNet卷积神经网络-笔记

GoogLeNet卷积神经网络-笔记 GoogLeNet是2014年ImageNet比赛的冠军&#xff0c; 它的主要特点是网络不仅有深度&#xff0c; 还在横向上具有“宽度”。 由于图像信息在空间尺寸上的巨大差异&#xff0c; 如何选择合适的卷积核来提取特征就显得比较困难了。 空间分布范围更广的…

matlab智能算法程序包89套最新高清录制!matlab专题系列!

关于我为什么要做代码分享这件事&#xff1f; 助力科研旅程&#xff01; 面对茫茫多的文献&#xff0c;想复现却不知从何做起&#xff0c;我们通过打包成品代码&#xff0c;将过程完善&#xff0c;让您可以拿到一手的复现过程以及资料&#xff0c;从而在此基础上&#xff0c;照…

OpenCV基础

目录 图像基本操作数据读取——图像数据读取——视频截取部分图像数据 ROI——Region of Interest颜色通道提取边界填充数值计算图像融合 图像处理灰度图HSV图像阈值图像平滑(滤波)形态学-腐蚀操作形态学-膨胀操作开运算与闭运算梯度运算礼帽与黑帽 图像梯度——Sobel算子看看L…

Vue 2.x 项目升级到 Vue 3详细指南【总结版】

文章目录 0.前言1.升级教程1.1. 升级 Vue CLI&#xff1a;1.2. 安装 Vue 3&#xff1a;1.3. 更新 Vue 组件&#xff1a;1.4. 迁移全局 API&#xff1a;1.5. 迁移路由和状态管理器&#xff1a;1.6. 迁移 TypeScript&#xff1a;1.7. 迁移测试代码&#xff1a; 2.迁移总结2.0. 这…