STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器

只用STM32单片机+SD卡+耳机插座,实现播放MP3播放器!

看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labmad占用的RAM更少。但是大多数参考的方案还是用了外接IIS接口WM98xx之类的音频DAC芯片播放音频,稍显复杂繁琐。STM32F407Vx本身就自带了2路12位DAC输出,最高刷新速度333kHz,除了分辨率差点意思,速度上对于MP3通常44.1kHz采样率来说,用来播放音频绰绰有余了。本文给的方案和源码,直接用STM32软解码MP3并使用自带的2个DAC输出引脚输出音频左右声道。

原理:STM32从SD读取MP3文件原始数据,发送给Helix库解码,Helix解码后输出PCM数据流,将此数据进一步处理转换后,按照左右声道分别存入DAC输出1和2缓存,通过定时器以MP3文件的采样率的频率提供DAC触发节拍,通过DMA取缓存中高12位数据给DAC,在DAC1和2引脚产生音频波形,通过电容耦合到耳机的左右声道上。

MP3源文件是一种经过若干算法,将原始音频数据压缩得来的,软件解码的过程是逆过程,将压缩的音频反向转换为记录了左右声道、幅值的数据流,通常是PCM格式。

PCM:是模拟信号以固定的采样频率转换成数字信号后的表现形式。记录了音频采样的数据,双通道、16bit的PCM数据格式是以0轴为中心,范围为-32768~32767的数值,每个数据占用2字节,左声道和右声道交替存储,如图。

 软解码得到的PCM数据到STM32的DAC缓存需要进一步处理。STM32的DAC是12位的,其输入范围0~4095,而双通道16位的PCM音频数据是左右声道交替存储,且数据范围-32768~32767,因此PCM到STM32的DAC缓存要按照顺序一拆为二,分为左右声道,每个数据再加上32768,使其由short int的范围转换为unsigned short int,即0~65535。由于PCM数据是对音频的采样,因此调节音量(幅值)可以在此步骤一并处理,即音频数据 x 音量 /最大音量。至于DAC是12位,只需将DAC模式设置为左对齐12位,舍弃低4位即可。

到此,STM32的DAC输出引脚上应该已经有音频信号了,通常DAC引脚上串联一个1~10uF的电容用来耦合音频信号,电容越大音质越好,电容另一端接耳机插座的左声道/右声道,插上耳机就可以欣赏音乐啦!音质嘛,反正我是听不出来好不好,跟商品MP3播放器差不多。如果不串联电容,DAC引脚直连耳机插座左右声道也能听到声音,就是有些数字信号噪声也会传进来。如果希望噪声小一些,DAC引脚输出端加一个下图的低通滤波电路也是可以的。

 

  

Helix移植:

Helix源码的官网我没找到,直接用了野火的例程里面的代码,移植也很简单,不用改任何代码,只需要将Helix文件夹拷贝到工程目录里,然后在Keil中添加好文件,以及添加头文件途径,编译即可。工程目录如图。

源码:dac配置

dac.c

/********************************************************************************* @file    dac.c* @author  ZL* @version V0.0.1* @date    September-20-2019* @brief   DAC configuration.******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "dac.h"/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define   CNT_FREQ          84000000      // TIM6 counter clock (prescaled APB1)/* DHR registers offsets */
#define DHR12R1_OFFSET             ((uint32_t)0x00000008)
#define DHR12R2_OFFSET             ((uint32_t)0x00000014)
#define DHR12RD_OFFSET             ((uint32_t)0x00000020)/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint32_t DAC_DHR12R1_ADDR = (uint32_t)DAC_BASE + DHR12R1_OFFSET + DAC_Align_12b_L;
uint32_t DAC_DHR12R2_ADDR = (uint32_t)DAC_BASE + DHR12R2_OFFSET + DAC_Align_12b_L;uint16_t DAC_buff[2][DAC_BUF_LEN]; //DAC1、DAC2输出缓冲/* Private function prototypes -----------------------------------------------*/
static void TIM6_Config(void);/* Private functions ---------------------------------------------------------*/
/*** @brief  DAC初始化* @param  none* @retval none
*/
void DAC_Config(void)
{GPIO_InitTypeDef  GPIO_InitStructure;DAC_InitTypeDef  DAC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;DAC_Init(DAC_Channel_1, &DAC_InitStructure);DAC_Init(DAC_Channel_2, &DAC_InitStructure);//配置DMADMA_InitTypeDef DMA_InitStruct;DMA_StructInit(&DMA_InitStruct);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R1_ADDR;DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[0];//DAC1DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;DMA_InitStruct.DMA_BufferSize = DAC_BUF_LEN;DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;DMA_InitStruct.DMA_Priority = DMA_Priority_High;DMA_InitStruct.DMA_Channel = DMA_Channel_7;DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;DMA_InitStruct.DMA_MemoryBurst   = DMA_MemoryBurst_Single;DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA1_Stream5, &DMA_InitStruct);DMA_InitStruct.DMA_PeripheralBaseAddr = (u32)DAC_DHR12R2_ADDR;DMA_InitStruct.DMA_Memory0BaseAddr = (u32)&DAC_buff[1];//DAC2DMA_Init(DMA1_Stream6, &DMA_InitStruct);//开启DMA传输完成中断NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream6_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);DMA_ITConfig(DMA1_Stream6, DMA_IT_HT, ENABLE);//	DMA_Cmd(DMA1_Stream5, ENABLE);
//	DMA_Cmd(DMA1_Stream6, ENABLE);DAC_Cmd(DAC_Channel_1, ENABLE);DAC_Cmd(DAC_Channel_2, ENABLE);DAC_DMACmd(DAC_Channel_1, ENABLE);DAC_DMACmd(DAC_Channel_2, ENABLE);TIM6_Config();
}//配置DAC采样率和DMA数据长度,并启动DMA DAC
void DAC_DMA_Start(uint32_t freq, uint16_t len)
{//设置DMA缓冲长度需要停止DMADAC_DMA_Stop();//设置DMA DAC缓冲长度DMA_SetCurrDataCounter(DMA1_Stream5, len);DMA_SetCurrDataCounter(DMA1_Stream6, len);//设置定时器TIM_SetAutoreload(TIM6, (uint16_t)((CNT_FREQ)/freq));//启动DMA_Cmd(DMA1_Stream5, ENABLE);DMA_Cmd(DMA1_Stream6, ENABLE);
}//停止DMA DAC
void DAC_DMA_Stop(void)
{DMA_Cmd(DMA1_Stream5, DISABLE);DMA_Cmd(DMA1_Stream6, DISABLE);
}//定时器6用于设置DAC刷新率
static void TIM6_Config(void)
{TIM_TimeBaseInitTypeDef TIM6_TimeBase;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);TIM_TimeBaseStructInit(&TIM6_TimeBase); TIM6_TimeBase.TIM_Period        = (uint16_t)((CNT_FREQ)/44100);TIM6_TimeBase.TIM_Prescaler     = 0;TIM6_TimeBase.TIM_ClockDivision = 0;TIM6_TimeBase.TIM_CounterMode   = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM6, &TIM6_TimeBase);TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);TIM_Cmd(TIM6, ENABLE);
}/*** @brief  DAC out1 PA4输出电压* @param  dat:dac数值:,0~4095* @retval none
*/
void DAC_Out1(uint16_t dat)
{DAC_SetChannel1Data(DAC_Align_12b_R,  dat);DAC_SoftwareTriggerCmd(DAC_Channel_1, ENABLE);
}/*** @brief  DAC out2 PA5输出电压* @param  dat:dac数值:,0~4095* @retval none
*/
void DAC_Out2(uint16_t dat)
{DAC_SetChannel2Data(DAC_Align_12b_R,  dat);DAC_SoftwareTriggerCmd(DAC_Channel_2, ENABLE);
}/********************************************* *****END OF FILE****/

源码:MP3播放流程 (原创野火,参考了野火的例程,本人进行整理和修改)

MP3player.c

/*
******************************************************************************
* @file    mp3Player.c
* @author  fire
* @version V1.0
* @date    2023-08-13
* @brief   mp3解码
******************************************************************************
*/
#include <stdio.h>
#include <string.h>
#include "ff.h" 
#include "mp3Player.h"
#include "mp3dec.h"
#include "dac.h"
#include "led.h"/* 推荐使用以下格式mp3文件:* 采样率:44100Hz* 声  道:2* 比特率:320kbps*//* 处理立体声音频数据时,输出缓冲区需要的最大大小为2304*16/8字节(16为PCM数据为16位),* 这里我们定义MP3BUFFER_SIZE为2304*/
#define MP3BUFFER_SIZE  2304
#define INPUTBUF_SIZE   3000static HMP3Decoder		Mp3Decoder;			/* mp3解码器指针	*/
static MP3FrameInfo		Mp3FrameInfo;		/* mP3帧信息  */
static MP3_TYPE mp3player;            /* mp3播放设备 */
volatile uint8_t Isread = 0;          /* DMA传输完成标志 */
volatile uint8_t dac_ht = 0;          //DAC dma 半传输标志uint32_t led_delay = 0;uint8_t inputbuf[INPUTBUF_SIZE]={0};     /* 解码输入缓冲区,1940字节为最大MP3帧大小  */
static short outbuffer[MP3BUFFER_SIZE];  /* 解码输出缓冲区*/static FIL file;			/* file objects */
static UINT bw;       /* File R/W count */
FRESULT result; //从SD卡读取MP3源文件进行解码,并传入DAC缓冲区
int MP3DataDecoder(uint8_t **read_ptr, int *bytes_left)
{int err = 0, i = 0, outputSamps = 0;//bufflag开始解码 参数:mp3解码结构体、输入流指针、输入流大小、输出流指针、数据格式err = MP3Decode(Mp3Decoder, read_ptr, bytes_left, outbuffer, 0);if (err != ERR_MP3_NONE)	//错误处理{switch (err){case ERR_MP3_INDATA_UNDERFLOW:printf("ERR_MP3_INDATA_UNDERFLOW\r\n");result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);*read_ptr = inputbuf;*bytes_left = bw;break;		case ERR_MP3_MAINDATA_UNDERFLOW:/* do nothing - next call to decode will provide more mainData */printf("ERR_MP3_MAINDATA_UNDERFLOW\r\n");break;		default:printf("UNKNOWN ERROR:%d\r\n", err);		// 跳过此帧if (*bytes_left > 0){(*bytes_left) --;read_ptr ++;}break;}return 0;}else		//解码无错误,准备把数据输出到PCM{MP3GetLastFrameInfo(Mp3Decoder, &Mp3FrameInfo);		//获取解码信息				/* 输出到DAC */outputSamps = Mp3FrameInfo.outputSamps;						//PCM数据个数if (outputSamps > 0){if (Mp3FrameInfo.nChans == 1)	//单声道{//单声道数据需要复制一份到另一个声道for (i = outputSamps - 1; i >= 0; i--){outbuffer[i * 2] = outbuffer[i];outbuffer[i * 2 + 1] = outbuffer[i];}outputSamps *= 2;}//if (Mp3FrameInfo.nChans == 1)	//单声道}//if (outputSamps > 0)//将数据传送至DMA DAC缓冲区for (i = 0; i < outputSamps/2; i++){if(dac_ht == 1){DAC_buff[0][i] = outbuffer[2*i] * mp3player.ucVolume /100 + 32768;DAC_buff[1][i] = outbuffer[2*i+1] * mp3player.ucVolume /100 + 32768;}else{DAC_buff[0][i+outputSamps/2] = outbuffer[2*i] * mp3player.ucVolume /100 + 32768;DAC_buff[1][i+outputSamps/2] = outbuffer[2*i+1] * mp3player.ucVolume /100 + 32768;}}return 1;}//else 解码正常
}//读取一段MP3数据,并把读取的指针赋值read_ptr,长度赋值bytes_left
uint8_t read_file(const char *mp3file, uint8_t **read_ptr, int *bytes_left)
{result = f_read(&file, inputbuf, INPUTBUF_SIZE, &bw);if(result != FR_OK){printf("读取%s失败 -> %d\r\n", mp3file, result);return 0;}else{*read_ptr = inputbuf;*bytes_left = bw;return 1;}
}/*** @brief  MP3格式音频播放主程序* @param  mp3file MP3文件路径* @retval 无*/
void mp3PlayerDemo(const char *mp3file)
{uint8_t *read_ptr = inputbuf;int	read_offset = 0;				/* 读偏移指针 */int	bytes_left = 0;					/* 剩余字节数 */	mp3player.ucStatus = STA_IDLE;mp3player.ucVolume = 15; //音量值,100满//尝试打开MP3文件result = f_open(&file, mp3file, FA_READ);if(result != FR_OK){printf("Open mp3file :%s fail!!!->%d\r\n", mp3file, result);result = f_close (&file);return;	/* 停止播放 */}printf("当前播放文件 -> %s\n", mp3file);//初始化MP3解码器Mp3Decoder = MP3InitDecoder();	if(Mp3Decoder == 0){printf("初始化helix解码库设备失败!\r\n");return;	/* 停止播放 */}else{printf("初始化helix解码库完成\r\n");}//尝试读取一段MP3数据,并把读取的指针赋值read_ptr,长度赋值bytes_leftif(!read_file(mp3file, &read_ptr, &bytes_left)){MP3FreeDecoder(Mp3Decoder);return;	/* 停止播放 */}//尝试解码成功if(MP3DataDecoder(&read_ptr, &bytes_left)){//打印MP3信息printf(" \r\n Bitrate       %dKbps", Mp3FrameInfo.bitrate/1000);printf(" \r\n Samprate      %dHz",   Mp3FrameInfo.samprate);printf(" \r\n BitsPerSample %db",    Mp3FrameInfo.bitsPerSample);printf(" \r\n nChans        %d",     Mp3FrameInfo.nChans);printf(" \r\n Layer         %d",     Mp3FrameInfo.layer);printf(" \r\n Version       %d",     Mp3FrameInfo.version);printf(" \r\n OutputSamps   %d",     Mp3FrameInfo.outputSamps);printf("\r\n");//启动DAC,开始发声if (Mp3FrameInfo.nChans == 1)	//单声道要将outputSamps*2{DAC_DMA_Start(Mp3FrameInfo.samprate, 2 * Mp3FrameInfo.outputSamps);}else//双声道直接用Mp3FrameInfo.outputSamps{DAC_DMA_Start(Mp3FrameInfo.samprate, Mp3FrameInfo.outputSamps);}}else //解码失败{MP3FreeDecoder(Mp3Decoder);return;}/* 放音状态 */mp3player.ucStatus = STA_PLAYING;/* 进入主程序循环体 */while(mp3player.ucStatus == STA_PLAYING){//寻找帧同步,返回第一个同步字的位置read_offset = MP3FindSyncWord(read_ptr, bytes_left);if(read_offset < 0)					//没有找到同步字{if(!read_file(mp3file, &read_ptr, &bytes_left))//重新读取一次文件再找{continue;//回到while(mp3player.ucStatus == STA_PLAYING)后面}}else//找到同步字{			read_ptr   += read_offset;	//偏移至同步字的位置bytes_left -= read_offset;	//同步字之后的数据大小	if(bytes_left < 1024)				//如果剩余的数据小于1024字节,补充数据{/* 注意这个地方因为采用的是DMA读取,所以一定要4字节对齐  */u16 i = (uint32_t)(bytes_left)&3;	//判断多余的字节if(i) i=4-i;						//需要补充的字节memcpy(inputbuf+i, read_ptr, bytes_left);	//从对齐位置开始复制read_ptr = inputbuf+i;										//指向数据对齐位置result = f_read(&file, inputbuf+bytes_left+i, INPUTBUF_SIZE-bytes_left-i, &bw);//补充数据if(result != FR_OK){printf("读取%s失败 -> %d\r\n",mp3file,result);break;}bytes_left += bw;		//有效数据流大小}}//MP3数据解码并送入DAC缓存if(!MP3DataDecoder(&read_ptr, &bytes_left)){//如果播放出错,Isread置1,避免卡住死循环Isread = 1;}//mp3文件读取完成,退出if(file.fptr == file.fsize){printf("单曲播放完毕\r\n");break;}	//等待DAC发送一半或全部中断while(Isread == 0){led_delay++;if(led_delay == 0xffffff){led_delay=0;LED1_TROG;}//Input_scan();		//等待DMA传输完成,此间可以运行按键扫描及处理事件}Isread = 0;}//运行到此处,说明单曲播放完成,收尾工作DAC_DMA_Stop();//停止喂DAC数据	mp3player.ucStatus = STA_IDLE;MP3FreeDecoder(Mp3Decoder);//清理缓存f_close(&file);	
}void DMA1_Stream6_IRQHandler(void)
{if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_HTIF6) != RESET) //半传输{	dac_ht = 1;		Isread=1;DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_HTIF6);}if(DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) != RESET) //全传输{dac_ht = 0;Isread=1;DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);}
}/***************************** (END OF FILE) *********************************/

源码:main.c

/********************************************************************************* @file    ../User/main.c * @author  ZL* @version V1.0* @date    2015-12-26* @brief   Main program body******************************************************************************
**//* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "hw_includes.h"
#include "ff.h"  
#include "exfuns.h"  
#include "mp3Player.h"//遍历目录文件并打印输出
u8 scan_files(u8 * path)
{FRESULT res;char buf[512] = {0};	char *fn;#if _USE_LFNfileinfo.lfsize = _MAX_LFN * 2 + 1;fileinfo.lfname = buf;
#endifres = f_opendir(&dir,(const TCHAR*)path);if (res == FR_OK) {	printf("\r\n"); while(1){res = f_readdir(&dir, &fileinfo);                if (res != FR_OK || fileinfo.fname[0] == 0) break;  #if _USE_LFNfn = *fileinfo.lfname ? fileinfo.lfname : fileinfo.fname;
#else							   fn = fileinfo.fname;
#endif	    printf("%s/", path);			printf("%s\r\n", fn);			} }	  return res;	  
}/*** @brief  Main program* @param  None* @retval None*/
int main(void)
{	delay_init(168);usart1_Init(115200);LED_Init();DAC_Config();if(!SD_Init()){exfuns_init();							//为fatfs相关变量申请内存				 f_mount(fs[0],"0:",1); 					//挂载SD卡 }//打印SD目录和文件scan_files("0:");LED0_ON;while (1){mp3PlayerDemo("0:/断桥残雪.MP3");mp3PlayerDemo("0:/张国荣-玻璃之情.MP3");delay_ms(50);}
}

为方便调试测试,使用usart1打印数据。实测效果:

程序源码与原理图,测试音频:

链接:https://pan.baidu.com/s/10hYXkrqnuBQgs0DWKLUUOA?pwd=iatt 
提取码:iatt

知道这里下载要积分登录什么的麻烦得很,所以程序放到百度网盘了,假如连接失效,记得在评论区喊我更新!

理论上STM32F1或者其他系列也能用这个方案,要自己改改测试喽,本文把思路分享出来抛砖引玉。

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

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

相关文章

揭秘程序员的鄙视链,你在哪一层?看完我想哭

虽然不同的编程语言都有其优缺点&#xff0c;而且程序员之间的技能和能力更加重要&#xff0c;但是有些程序员可能会因为使用不同的编程语言而产生鄙视链。 以下是一些可能存在的不同编程语言程序员之间的鄙视链&#xff1a; 低级语言程序员鄙视高级语言程序员&#xff1a;使用…

Ubuntu常用配置

文章目录 1. 安装VMware虚拟机软件2. 下载Ubuntu镜像3. 创建Ubuntu虚拟机4. 设置屏幕分辨率5. 更改系统语言为中文6. 切换中文输入法7. 修改系统时间8. 修改锁屏时间9. 通过系统自带的应用商店安装软件10. 安装JDK11. 安装 IntelliJ IDEA12. 将左侧任务栏自动隐藏13. 安装docke…

.netcore grpc身份验证和授权

一、鉴权和授权&#xff08;grpc专栏结束后会开启鉴权授权专栏欢迎大家关注&#xff09; 权限认证这里使用IdentityServer4配合JWT进行认证通过AddAuthentication和AddAuthorization方法进行鉴权授权注入&#xff1b;通过UseAuthentication和UseAuthorization启用鉴权授权增加…

在 React+Typescript 项目环境中创建并使用组件

上文 ReactTypescript清理项目环境 我们将自己创建的项目环境 好好清理了一下 下面 我们来看组件的创建 组件化在这种数据响应式开发中肯定是非常重要的。 我们现在src下创建一个文件夹 叫 components 就用他专门来处理组件业务 然后 我们在下面创建一个 hello.tsx 注意 是t…

架构演进及常用架构

1架构演进及常用架构 1.1单体分层架构 1.2 多应用微服务架构 1.3 分布式集群部署 部署 CDN 节点&#xff1a; 用户访问量的增加意味着用户地域的分散请求&#xff0c;如果所有请求都直接发送中心服务器的话&#xff0c;距离越远&#xff0c;响应速度越差&#xff0c;这时就需…

使用CLI添加磁盘到VM

登录 https://portal.azure.com/#home&#xff0c;点击右上角的控制台图标 &#xff0c;打开CLI 在控制台中输入如下指令&#xff0c;在NetworkWatcherRG创建一个名字为TEST的虚拟机&#xff0c;使用的镜像是Win2019datacenter&#xff0c;username是aaa,password是1234567890A…

Go语言基础之基本数据类型

Go语言中有丰富的数据类型&#xff0c;除了基本的整型、浮点型、布尔型、字符串外&#xff0c;还有数组、切片、结构体、函数、map、通道&#xff08;channel&#xff09;等。Go 语言的基本类型和其他语言大同小异。 基本数据类型 整型 整型分为以下两个大类&#xff1a; 按…

javascript期末作业【三维房屋设计】

1、引入three.js库 官网下载three.js 库 放置目录并引用 引入js文件: 设置场景&#xff08;scene&#xff09; &#xff08;1&#xff09;创建场景对象 &#xff08;2&#xff09;设置透明相机 1,透明相机的优点 透明相机机制更符合于人的视角,在场景预览和游戏场景多有使用…

BIO、NIO和AIO

一.引言 何为IO 涉及计算机核心(CPU和内存)与其他设备间数据迁移的过程&#xff0c;就是I/O。数据输入到计算机内存的过程即输入&#xff0c;反之输出到外部存储&#xff08;比如数据库&#xff0c;文件&#xff0c;远程主机&#xff09;的过程即输出。 I/O 描述了计算机系统…

基于YOLOv8模型的奶牛目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型的奶牛目标检测系统可用于日常生活中检测与定位奶牛目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训练数据集…

CentOS7源码安装MySQL详细教程

&#x1f60a; 作者&#xff1a; Eric &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_47316183?typeblog &#x1f389; 主题&#xff1a;CentOS7源码安装MySQL详细教程 ⏱️ 创作时间&#xff1a; 2023年08月014日 文章目录 1、安装的四种方式2、源码安装…

时序预测 | MATLAB实现SA-ELM模拟退火算法优化极限学习机时间序列预测

时序预测 | MATLAB实现SA-ELM模拟退火算法优化极限学习机时间序列预测 目录 时序预测 | MATLAB实现SA-ELM模拟退火算法优化极限学习机时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现SA-ELM模拟退火算法优化极限学习机时间序列预测 程序设计 完整…