【STM32】通过HAL库Flash建立FatFS文件系统并配置为USB虚拟U盘MSC

【STM32】通过HAL库Flash建立FatFS文件系统并配置为USB虚拟U盘MSC

在先前 分别介绍了FatFS文件系统和USB虚拟U盘MSC配置
前者通过MCU读写Flash建立文件系统 后者通过MSC连接电脑使其能够被操作
这两者可以合起来 就能够实现同时在MCU、USB中操作Flash的文件系统
【STM32】通过L496的HAL库Flash建立FatFS文件系统(CubeMX自动配置R0.12C版本)
【STM32】HAL库USB虚拟U盘MSC配置及采用自带的Flash作为文件系统

在这里 USB还是工作在Device模式 而不是Host模式 如果配置成Host模式则可以解锁FatFS中的USB Disk功能 实现方式不同 原理、功能也不一样 请勿混淆
(在Host模式下 MCU作为主机使用 需要多加一根线 USB Disk功能需要连上USB以后才能挂载硬盘 其相关配置可以由CubeMX完全生成 用户只需要调用文件操作函数API即可 不需要修改代码 感兴趣的可以自己尝试一下相关配置)

文章目录

  • Flash操作
    • Flash地址写
    • Flash地址读
  • FatFS文件系统配置
  • FatFS移植
    • 驱动函数
    • 时间戳函数
  • 文件操作函数
    • 工作区缓存
    • 文件挂载和格式化测试
    • 文件读写测试
    • 其他文件操作函数
  • MSC配置
  • 工程配置
  • 测试
  • 附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作
    • SysTick系统定时器精准延时
      • 延时函数
        • 阻塞延时
        • 非阻塞延时
    • 位带操作
      • 位带代码
        • 位带宏定义
        • 总线函数
      • 一、位带操作理论及实践
      • 二、如何判断MCU的外设是否支持位带

Flash操作

无论是何种Flash 都能进行读写操作
读一般可以随机地址读取 但写操作只能按某一个最小单位进行擦除后 才能写入
【STM32】HAL库Flash读写操作及配置(L4和F4系列不同操作、HAL_FLASH_ERROR_PGA报错的解决方案)
为了能够用自带的Flash进行文件系统的建立 首先空间不能太小
其次 为了方便编程 可以选择多页面、小空间的Flash进行操作
若采用F407 每次写入擦除的最小单位是一个扇区(128K) 编程起来比较麻烦
所以本文采用L496来进行操作

这里我们就用496的第二个BANK来作为硬盘操作(地址0x0808 0000 之后的数据 总共256页 每页2K大小 总大小512K)
在这里插入图片描述

操作L496的话 是双字64位操作
在这里插入图片描述
在双Bank模式下 每次擦除时还需要选择擦除的Bank序号(1或2 或两者都擦除)

/** @defgroup FLASH_Banks FLASH Banks* @{*/
#define FLASH_BANK_1              ((uint32_t)0x01)                          /*!< Bank 1   */
#if defined (STM32L471xx) || defined (STM32L475xx) || defined (STM32L476xx) || defined (STM32L485xx) || defined (STM32L486xx) || \defined (STM32L496xx) || defined (STM32L4A6xx) || defined (STM32L4P5xx) || defined (STM32L4Q5xx) || defined (STM32L4R5xx) || \defined (STM32L4R7xx) || defined (STM32L4R9xx) || defined (STM32L4S5xx) || defined (STM32L4S7xx) || defined (STM32L4S9xx)
#define FLASH_BANK_2              ((uint32_t)0x02)                          /*!< Bank 2   */
#define FLASH_BANK_BOTH           ((uint32_t)(FLASH_BANK_1 | FLASH_BANK_2)) /*!< Bank1 and Bank2  */
#else
#define FLASH_BANK_BOTH           ((uint32_t)(FLASH_BANK_1))                /*!< Bank 1   */
#endif

HAL库测试代码如下:

void Test_Flash(uint32_t add)
{uint32_t error = 0;uint64_t dat = 0x0123456776543210;//要写入的数据,必须得是双字64bituint64_t read_dat = 0 ;FLASH_EraseInitTypeDef flash_dat;          //定义一个结构体变量,里面有擦除操作需要定义的变量HAL_FLASH_Unlock();                                    //第二步:解锁                        flash_dat.TypeErase = FLASH_TYPEERASE_PAGES;         //擦除类型是“Page Erase” 仅删除页面 另外一个参数是全部删除flash_dat.Page = (uint32_t)((add-0x08000000)/2048);            //擦除地址对应的页flash_dat.NbPages = 1;                               //一次性擦除1页,可以是任意页if(flash_dat.Page>255){flash_dat.Banks=2;}else{flash_dat.Banks=1;}HAL_FLASHEx_Erase(&flash_dat,&error);            //第三步:参数写好后调用擦除函数FLASH_WaitForLastOperation(0xFFFF); HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, add, dat);//第四步:写入数据HAL_FLASH_Lock();                                      //第五步:上锁read_dat = *(__I uint64_t *)add;	   //读出flash中的数据uint32_t read_dat1=read_dat>>32;uint32_t read_dat2=read_dat&0x00000000FFFFFFFF;printf("[INFO] Flash_Test:0x%08x 0x%08x\n",read_dat1,read_dat2);
}

步骤就是:
解锁;
擦除;
写数据;
上锁。

若要在写入某个地址下的一部分数据时 需要擦除整个页面 然后再进行写入
所以如果要保留该页面下的其他数据 就应该在写入之前读取该页面数据 然后将某一部分修改的数据替换掉
之后再按页面整个写入
好在文件系统中 只要配置得当 可以帮我们实现按页擦除、写入的功能
这样我们就只需要定义好地址写、地址读函数即可

这里需要注意 由于L496的Flash是按64位对其 而我们的MCU是32位 所以不建议直接进行64位移位操作
最好是用两个32位变量 来拼接成一个64位
并且需要注意的是 32位变量左移位时 不得操作32位 最好是先赋值给64位变量 再单独对64位变量进行操作
同理 在读取函数中 64位变量也建议拆分成两个32位变量进行读取操作

Flash地址写

//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToWrite:要读取的字节数(最大65535)
void Write_Flash(const uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{if(Flag_Flash_Busy==1)return;Flag_Flash_Busy=1;uint32_t Current_ADD = ReadAddr;	uint32_t add =0;uint32_t page=(uint32_t)((Current_ADD-0x08000000)/2048);uint32_t first_add = Current_ADD;	uint32_t judg_add = (page)*0x800+0x08000000+Flash_Page_Size;uint32_t error = 0;uint64_t dat = 0;														//要写入的数据,必须得是双字64bituint32_t dat_0=0;uint32_t dat_1=0;uint16_t i =0;uint16_t j = NumByteToRead/8;FLASH_EraseInitTypeDef flash_dat;          //定义一个结构体变量,里面有擦除操作需要定义的变量HAL_FLASH_Unlock();                                    //第二步:解锁                        flash_dat.TypeErase = FLASH_TYPEERASE_PAGES;         //擦除类型是“Page Erase” 仅删除页面 另外一个参数是全部删除flash_dat.Page = (uint32_t)((Current_ADD-0x08000000)/2048);            //擦除地址对应的页flash_dat.NbPages = 1;                               //一次性擦除1页,可以是任意页if(flash_dat.Page>255){flash_dat.Banks=2;}else{flash_dat.Banks=1;}HAL_FLASHEx_Erase(&flash_dat,&error);            //第三步:参数写好后调用擦除函数FLASH_WaitForLastOperation(0xFFFF); for(i=0;i<j;i++){add = Current_ADD+i*8;if(add>=judg_add){HAL_FLASH_Lock();     //第五步:上锁Flag_Flash_Busy=0;Write_Flash(pBuffer+i*8,add-first_add,NumByteToRead-i*8);return;}dat_0 = pBuffer[i*8+0]|(pBuffer[i*8+1]<<8)|(pBuffer[i*8+2]<<16)|(pBuffer[i*8+3]<<24);		dat_1 = pBuffer[i*8+4]|(pBuffer[i*8+5]<<8)|(pBuffer[i*8+6]<<16)|(pBuffer[i*8+7]<<24);dat = dat_1;dat = (dat<<32)|dat_0;HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, add, dat);  //第四步:写入数据}HAL_FLASH_Lock();     //第五步:上锁Flag_Flash_Busy=0;
}

Flash地址读

//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void Read_Flash(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{if(Flag_Flash_Busy==1)return;Flag_Flash_Busy=1;uint32_t Current_ADD = ReadAddr;	uint32_t add =0;uint64_t dat = 0;														//要写入的数据,必须得是双字64bituint32_t dat_0=0;uint32_t dat_1=0;uint16_t i =0;uint16_t j = NumByteToRead/8;for(i=0;i<j;i++){add = Current_ADD+i*8;dat = *(__I uint64_t *)(add);dat_1=dat>>32;dat_0=dat&0x00000000FFFFFFFF;pBuffer[i*8+0]=(uint8_t)(dat_0&0xFF);pBuffer[i*8+1]=(uint8_t)((dat_0>>8)&0xFF);pBuffer[i*8+2]=(uint8_t)((dat_0>>16)&0xFF);pBuffer[i*8+3]=(uint8_t)((dat_0>>24)&0xFF);pBuffer[i*8+4]=(uint8_t)(dat_1&0xFF);pBuffer[i*8+5]=(uint8_t)((dat_1>>8)&0xFF);pBuffer[i*8+6]=(uint8_t)((dat_1>>16)&0xFF);pBuffer[i*8+7]=(uint8_t)((dat_1>>24)&0xFF);}Flag_Flash_Busy=0;
}

FatFS文件系统配置

FatFS文件系统依赖底层Flash驱动来进行文件系统配置
通过实现f_open等函数操作来进行文件的操作
这里就不讲解底层原理了 相关资料很多
可以通过CubeMX进行配置
如图:
在这里插入图片描述
修改以支持中文字符
修改MAX_SS为2048(496的一个页面是2K)
这里MAX_SS只能选择512 1024 2048 4096 其对应的就是格式化中的“分配单元大小”
也就是规定其最小操作单元为2048

另外 配置好RTC(可用可不用)
在这里插入图片描述

FatFS移植

CubeMX生成代码后 需要在工程中进行配置
导入用户文件:
在这里插入图片描述
导入外设中的FatFS库文件
在这里插入图片描述
添加头文件目录:
在这里插入图片描述

驱动函数

修改user_diskio.c中的函数:

/*** @brief  Initializes a Drive* @param  pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS USER_initialize (BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{/* USER CODE BEGIN INIT */Stat = STA_NOINIT;//获取驱动器状态Stat = USER_status(pdrv);  return Stat;/* USER CODE END INIT */
}/*** @brief  Gets Disk Status* @param  pdrv: Physical drive number (0..)* @retval DSTATUS: Operation status*/
DSTATUS USER_status (BYTE pdrv       /* Physical drive number to identify the drive */
)
{/* USER CODE BEGIN STATUS */Stat = STA_NOINIT;		  //驱动器未初始化,Stat=0x01Stat = 0 ;  //Stat=0x00return Stat;/* USER CODE END STATUS */
}/*** @brief  Reads Sector(s)* @param  pdrv: Physical drive number (0..)* @param  *buff: Data buffer to store read data* @param  sector: Sector address (LBA)* @param  count: Number of sectors to read (1..128)* @retval DRESULT: Operation result*/
DRESULT USER_read (BYTE pdrv,      /* Physical drive nmuber to identify the drive */BYTE *buff,     /* Data buffer to store read data */DWORD sector,   /* Sector address in LBA */UINT count      /* Number of sectors to read */
)
{/* USER CODE BEGIN READ */uint32_t globalAddr = (sector)*0x800+0x08080000;  uint16_t byteCount = count << 11;   //读取数据Read_Flash((uint8_t *)buff,globalAddr, byteCount);return RES_OK;/* USER CODE END READ */
}/*** @brief  Writes Sector(s)* @param  pdrv: Physical drive number (0..)* @param  *buff: Data to be written* @param  sector: Sector address (LBA)* @param  count: Number of sectors to write (1..128)* @retval DRESULT: Operation result*/
#if _USE_WRITE == 1
DRESULT USER_write (BYTE pdrv,          /* Physical drive nmuber to identify the drive */const BYTE *buff,   /* Data to be written */DWORD sector,       /* Sector address in LBA */UINT count          /* Number of sectors to write */
)
{/* USER CODE BEGIN WRITE *//* USER CODE HERE */uint32_t globalAddr = (sector)*0x800+0x08080000;  uint16_t byteCount = count << 11;   Write_Flash((uint8_t *)buff,globalAddr, byteCount);return RES_OK;/* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 *//*** @brief  I/O control operation* @param  pdrv: Physical drive number (0..)* @param  cmd: Control code* @param  *buff: Buffer to send/receive control data* @retval DRESULT: Operation result*/
#if _USE_IOCTL == 1
DRESULT USER_ioctl (BYTE pdrv,      /* Physical drive nmuber (0..) */BYTE cmd,       /* Control code */void *buff      /* Buffer to send/receive control data */
)
{/* USER CODE BEGIN IOCTL */DRESULT res = RES_OK;switch(cmd){/*以下四个命令都是按照FatFs默认参数配置时必须需要的*///完成挂起的写入过程(在_FS_READONLY == 0时需要)case CTRL_SYNC:               //确保设备已完成挂起的写入过程。如果磁盘I/O层或存储设备具有回写式缓存,则脏缓存数据必须立即提交到介质。如果对介质的每个写操作都在以下时间内完成,则此命令不执行任何操作 disk_write 功能。return RES_OK;case GET_SECTOR_COUNT:{*(DWORD *)buff = 256;     //表示扇区的个数return RES_OK;}		case GET_SECTOR_SIZE:{*(WORD *)buff = 2048;  //表示每个扇区的大小return RES_OK;}	case GET_BLOCK_SIZE:{*(WORD *)buff = 1;  //表示同时可擦除的扇区个数return RES_OK;}	default:res = RES_ERROR;}return res;/* USER CODE END IOCTL */
}

这里的读写函数需要加上地址偏移
每次操作2048个字节
扇区个数为256 对应Flash的256页
扇区大小即位页大小 2048字节
每次同时擦除1个扇区也就是1页

加入使用多页擦除的话 譬如2页擦除 则中间需要缓存的数据就为2048*2 这会大大占用系统资源 但能有效提高读写速度 不过在嵌入式系统中不建议这样做
另外配置堆栈大小 越大越好
在这里插入图片描述

时间戳函数

在文件fatfs.c中修改时间戳函数

/*** @brief  Gets Time from RTC* @param  None* @retval Time in DWORD*/
DWORD get_fattime(void)
{/* USER CODE BEGIN get_fattime */RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;//获取RTC时间if(HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK){//获取RTC日期HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);WORD date=(2000+sDate.Year-1980)<<9;date = date |(sDate.Month<<5) |sDate.Date;WORD time=sTime.Hours<<11;time = time | (sTime.Minutes<<5) | (sTime.Seconds>1);DWORD dt=(date<<16) | time;return	dt;}elsereturn 0;/* USER CODE END get_fattime */
}

文件操作函数

建立一个文件 加上文件操作等函数
头文件声明:

#ifndef FILE_OPERATE_H
#define FILE_OPERATE_H#include "main.h"
#include "FatFs.h"
#include "stdio.h"/*函数声明*/
void FatFS_Init(void);void FatFs_GetDiskInfo(void);
void FatFs_ScanDir(const TCHAR* PathName);
void FatFs_ReadTXTFile(TCHAR *filename);
void FatFs_WriteTXTFile(TCHAR *filename,uint16_t year, uint8_t month, uint8_t day);
void FatFs_GetFileInfo(TCHAR *filename);
void FatFs_DeleteFile(TCHAR *filename);
void FatFs_PrintfFileDate(WORD date, WORD time);#endif

工作区缓存

//定义用于格式化的工作区缓存
BYTE work[_MAX_SS];

由于一次只操作一个扇区 所以缓存大小即为2048

文件挂载和格式化测试

	retUSER = f_mount(&USERFatFS,USERPath,1);//挂载盘符Aif(retUSER == FR_NO_FILESYSTEM)//没有文件系统就格式化创建创建文件系统{retUSER = f_mkfs(USERPath,FM_FAT,2048,work,sizeof(work));if(retUSER == FR_OK){retUSER = f_mount(&USERFatFS,USERPath,1);//挂载printf("[FatFS] 格式化成功retUSER=%d\r\n",retUSER);}else{printf("[FatFS] 格式化失败retUSER=%d\r\n",retUSER);return;}//格式化失败}else if(retUSER == FR_OK){printf("[FatFS] 挂载成功retUSER=%d\r\n",retUSER);}else{printf("[FatFS] 挂载失败retUSER=%d\r\n",retUSER);return;}//挂载失败

第一次时首先挂载 若未成功则重新格式化再挂载

需要注意的是 格式化后 Flash内容尽量不要发生改变
若不慎改变 则很可能在挂载时会卡死 建议执行重新格式化
最好的方法就是把首个文件系统扇区进行擦除 然后让函数重新执行格式化

在格式化中 f_mkfs函数的传参除了路径、文件系统类型外 其工作区和工作区大小 以及分配单元大小都要与2048对齐

文件读写测试

若挂载成功 则可以进行文件读写测试

void SDFileTestWrite(void)
{FRESULT res_sd;UINT fnum;/* 文件成功读写数量 */char string[100];signed int ByteNum = 0;memset(string,0,sizeof(string));sprintf(string,"%s%s.xls",USERPath,"Test");res_sd = f_open(&USERFile, string,FA_CREATE_ALWAYS | FA_WRITE );if(res_sd != FR_OK){printf("[FILE] Failed to create file! %d\r\n",res_sd);}sprintf(string,"Vreal\tA1\tA2\n");ByteNum = strlen(string);res_sd=f_write(&USERFile,string,ByteNum,&fnum);res_sd = f_close(&USERFile);if(res_sd != FR_OK){printf("[FILE] Error:File closure Exception!\r\n");}else{printf("[FILE] SDFileTestWrite ok!\r\n");}
}void SDFileTestRead(void)
{FRESULT res_sd;char string[100];uint32_t line = 0;memset(string,0,sizeof(string));sprintf(string,"%s%s.xls",USERPath,"Test");res_sd = f_open(&USERFile, string, FA_OPEN_EXISTING | FA_READ);if(res_sd != FR_OK){goto LoadFail;}line = 0;while(!(f_eof(&USERFile))){memset(string,0,sizeof(string));f_gets(string,sizeof(string),&USERFile);if(strlen(string) == 0){break;}++line;printf("[FILE] line:%d %s\r\n",line,string);//sscanf(string,"%f\t%f\t%f\n",&Vreal[*pNum],&Va1[*pNum],&Va2[*pNum]);//按格式提取字符串函数}res_sd = f_close(&USERFile);if(res_sd != FR_OK){printf("[FILE] Error:Load File closure Exception!\r\n");}printf("[FILE] SDFileTestRead ok\r\n");return;LoadFail:{printf("[FILE] Load Fail:%s\r\n",string);}
}

此函数实现了创建一个xls文件 并读取

其他文件操作函数

包括但不限于 查看目录所有文件、添加/删除文件、文件信息浏览等等
其实就是C语言文件操作那一些函数罢了 对应在Linux中就是ls、mkdir、touch等等 具体的模拟CLI实现可以用串口来进行
完整代码:

#include "file_operate.h"
#include <string.h>//定义用于格式化的工作区缓存
BYTE work[_MAX_SS];void SDFileTestWrite(void)
{FRESULT res_sd;UINT fnum;/* 文件成功读写数量 */char string[100];signed int ByteNum = 0;memset(string,0,sizeof(string));sprintf(string,"%s%s.xls",USERPath,"Test");res_sd = f_open(&USERFile, string,FA_CREATE_ALWAYS | FA_WRITE );if(res_sd != FR_OK){printf("[FILE] Failed to create file! %d\r\n",res_sd);}sprintf(string,"Vreal\tA1\tA2\n");ByteNum = strlen(string);res_sd=f_write(&USERFile,string,ByteNum,&fnum);res_sd = f_close(&USERFile);if(res_sd != FR_OK){printf("[FILE] Error:File closure Exception!\r\n");}else{printf("[FILE] SDFileTestWrite ok!\r\n");}
}void SDFileTestRead(void)
{FRESULT res_sd;char string[100];uint32_t line = 0;memset(string,0,sizeof(string));sprintf(string,"%s%s.xls",USERPath,"Test");res_sd = f_open(&USERFile, string, FA_OPEN_EXISTING | FA_READ);if(res_sd != FR_OK){goto LoadFail;}line = 0;while(!(f_eof(&USERFile))){memset(string,0,sizeof(string));f_gets(string,sizeof(string),&USERFile);if(strlen(string) == 0){break;}++line;printf("[FILE] line:%d %s\r\n",line,string);//sscanf(string,"%f\t%f\t%f\n",&Vreal[*pNum],&Va1[*pNum],&Va2[*pNum]);//按格式提取字符串函数}res_sd = f_close(&USERFile);if(res_sd != FR_OK){printf("[FILE] Error:Load File closure Exception!\r\n");}printf("[FILE] SDFileTestRead ok\r\n");return;LoadFail:{printf("[FILE] Load Fail:%s\r\n",string);}
}
/*挂载FatFs文件系统*/
void FatFS_Init(void)
{	retUSER = f_mount(&USERFatFS,USERPath,1);//挂载盘符Aif(retUSER == FR_NO_FILESYSTEM)//没有文件系统就格式化创建创建文件系统{retUSER = f_mkfs(USERPath,FM_FAT,2048,work,sizeof(work));if(retUSER == FR_OK){retUSER = f_mount(&USERFatFS,USERPath,1);//挂载printf("[FatFS] 格式化成功retUSER=%d\r\n",retUSER);}else{printf("[FatFS] 格式化失败retUSER=%d\r\n",retUSER);return;}//格式化失败}else if(retUSER == FR_OK){printf("[FatFS] 挂载成功retUSER=%d\r\n",retUSER);}else{printf("[FatFS] 挂载失败retUSER=%d\r\n",retUSER);return;}//挂载失败SDFileTestWrite();SDFileTestRead();FatFs_GetDiskInfo();FatFs_ScanDir(USERPath);
}/*获取磁盘信息并在LCD上显示*/
void FatFs_GetDiskInfo(void)
{FATFS *fs;//定义剩余簇个数变量DWORD fre_clust; //获取剩余簇个数FRESULT res = f_getfree("0:", &fre_clust, &fs); //获取失败if(res != FR_OK){printf("f_getfree() error\r\n");return;}printf("\r\n*** FAT disk info ***\r\n");//总的扇区个数DWORD tot_sect = (fs->n_fatent - 2) * fs->csize;  //剩余的扇区个数 = 剩余簇个数 * 每个簇的扇区个数DWORD fre_sect = fre_clust * fs->csize;    //对于SD卡和U盘, _MIN_SS=512字节
#if  _MAX_SS == _MIN_SS  //SD卡的_MIN_SS固定为512,右移11位相当于除以2048//剩余空间大小,单位:MB,用于SD卡,U盘DWORD freespace= (fre_sect>>11); //总空间大小,单位:MB,用于SD卡,U盘		DWORD totalSpace= (tot_sect>>11);  
#else//Flash存储器,小容量//剩余空间大小,单位:KBDWORD freespace= (fre_sect*fs->ssize)>>10;   //总空间大小,单位:KBDWORD totalSpace= (tot_sect*fs->ssize)>>10;  
#endif//FAT类型printf("FAT type = %d\r\n",fs->fs_type);printf("[1=FAT12,2=FAT16,3=FAT32,4=exFAT]\r\n");//扇区大小,单位字节printf("Sector size(bytes) = ");//SD卡固定512字节
#if  _MAX_SS == _MIN_SS printf("%d\r\n", _MIN_SS);
#else//FLASH存储器printf("%d\r\n", fs->ssize);
#endifprintf("Cluster size(sectors) = %d\r\n", fs->csize);printf("Total cluster count = %ld\r\n", fs->n_fatent-2);printf("Total sector count = %ld\r\n", tot_sect);//总空间
#if  _MAX_SS == _MIN_SS printf("Total space(MB) = %ld\r\n", totalSpace);
#elseprintf("Total space(KB) = %ld\r\n", totalSpace);
#endif//空闲簇数量printf("Free cluster count = %ld\r\n",fre_clust);//空闲扇区数量printf("Free sector count = %ld\r\n", fre_sect);//空闲空间
#if  _MAX_SS == _MIN_SS printf("Free space(MB) = %ld\r\n", freespace);
#elseprintf("Free space(KB) = %ld\r\n", freespace);
#endifprintf("Get FAT disk info OK\r\n");
}/*创建文本文件*/
void FatFs_WriteTXTFile(TCHAR *filename,uint16_t year, uint8_t month, uint8_t day)
{FIL	file;printf("\r\n*** Creating TXT file: %s ***\r\n", filename);FRESULT res = f_open(&file, filename, FA_CREATE_ALWAYS | FA_WRITE);//打开/创建文件成功if(res == FR_OK){//字符串必须有换行符"\n"TCHAR str[]="Line1: Hello, FatFs***\n";  //不会写入结束符"\0"f_puts(str, &file); printf("Write file OK: %s\r\n", filename);}else{printf("Open file error,error code: %d\r\n", res);}//使用完毕关闭文件f_close(&file);
}/*读取一个文本文件的内容*/
void FatFs_ReadTXTFile(TCHAR *filename)
{printf("\r\n*** Reading TXT file: %s ***\r\n", filename);FIL	file;//以只读方式打开文件FRESULT res = f_open(&file, filename, FA_READ);  //打开成功if(res == FR_OK){//读取缓存TCHAR str[100];//没有读到文件内容末尾while(!f_eof(&file)){//读取1个字符串,自动加上结束符”\0”f_gets(str,100, &file);	printf("%s", str);}printf("\r\n");}//如果没有该文件else if(res == FR_NO_FILE)printf("File does not exist\r\n");//打开失败elseprintf("f_open() error,error code: %d\r\n", res);//关闭文件f_close(&file);
}/*扫描和显示指定目录下的文件和目录*/
void FatFs_ScanDir(const TCHAR* PathName)
{DIR dir;					//目录对象FILINFO fno;				//文件信息//打开目录FRESULT res = f_opendir(&dir, PathName);//打开失败if(res != FR_OK){//关闭目录,直接退出函数f_closedir(&dir);printf("\r\nf_opendir() error,error code: %d\r\n", res);return;}printf("\r\n*** All entries in dir: %s ***\r\n", PathName);//顺序读取目录中的文件while(1){//读取目录下的一个项res = f_readdir(&dir, &fno);    //文件名为空表示没有多的项可读了if(res != FR_OK || fno.fname[0] == 0)break;  //如果是一个目录if(fno.fattrib & AM_DIR)  		{printf("DIR: %s\r\n", fno.fname);}//如果是一个文件else  		{printf("FILE: %s\r\n",fno.fname);}}//扫描完毕,关闭目录printf("Scan dir OK\r\n");f_closedir(&dir);
}/*获取一个文件的文件信息*/
void FatFs_GetFileInfo(TCHAR *filename)
{printf("\r\n*** File info of: %s ***\r\n", filename);FILINFO fno;//检查文件或子目录是否存在FRESULT fr = f_stat(filename, &fno);//如果存在从fno中读取文件信息if(fr == FR_OK){printf("File size(bytes) = %ld\r\n", fno.fsize);printf("File attribute = 0x%x\r\n", fno.fattrib);printf("File Name = %s\r\n", fno.fname);//输出创建/修改文件时的时间戳FatFs_PrintfFileDate(fno.fdate, fno.ftime);}//如果没有该文件else if (fr == FR_NO_FILE)printf("File does not exist\r\n");//发生其他错误elseprintf("f_stat() error,error code: %d\r\n", fr);
}/*删除文件*/
void FatFs_DeleteFile(TCHAR *filename)
{printf("\r\n*** Delete File: %s ***\r\n", filename);FIL	file;//打开文件FRESULT res = f_open(&file, filename, FA_OPEN_EXISTING);  if(res == FR_OK){//关闭文件f_close(&file);printf("open successfully!\r\n");}//删除文件res = f_unlink(filename);//删除成功if(res == FR_OK){printf("The file was deleted successfully!\r\n");}//删除失败else{printf("File deletion failed, error code:%d\r\n", res);}
}/*打印输出文件日期*/
void FatFs_PrintfFileDate(WORD date, WORD time)
{printf("File data = %d/%d/%d\r\n", ((date>>9)&0x7F)+1980, (date>>5)&0xF, date&0x1F);printf("File time = %d:%d:%d\r\n", (time>>11)&0x1F, (time>>5)&0x3F, time&0x1F);
}

MSC配置

开启USB_FS即可 这里选择Device_Only
在这里插入图片描述
NVIC中开启中断 其他不用改

如果使用HS(高速) 需要物理芯片
而FS则上拉电阻即可
具体看手册

在外设中配置MSC 并配置扇区大小(最好与Flash的最小读写单元保持一致)

在这里插入图片描述
这里是用的最大值2048
在这里插入图片描述

工程配置

添加如下文件
在这里插入图片描述
在这里插入图片描述
并添加USB内核和MSC的头文件路径:
在这里插入图片描述
在这里插入图片描述
最后编译就行了

然后修改usbd_storage_if.c文件
设备初始化:
在这里插入图片描述
读写锁(判断是否繁忙):
在这里插入图片描述
读写函数:
在这里插入图片描述

另外 头部定义修改为cubemx中一致

#define STORAGE_LUN_NBR                  1
#define STORAGE_BLK_NBR                  256
#define STORAGE_BLK_SIZ                  2048

然后连上PC就能看到U盘了
在这里插入图片描述
在这里插入图片描述

测试

在格式化前 数据都是FF
在这里插入图片描述
在挂载测试时 会读取整个硬盘数据 发现没数据 就会报挂载不成功 然后开始格式化

格式化时 写入的第一个地址内容如下:
在这里插入图片描述
格式化完成后:
在这里插入图片描述
在这里插入图片描述
这些都是底层操作 我们不用考虑 只要文件系统没BUG 就肯定能跑
格式化成功测试:
在这里插入图片描述
在测试之前 我跑了一下Flash Test 其会将0x0808 0000的整个页面清空
所以 每次复位都会重新格式化
去掉Flash Test后则能直接挂载:
在这里插入图片描述
硬盘信息:
在这里插入图片描述
目录下所有文件信息:
在这里插入图片描述

附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作

SysTick系统定时器精准延时

延时函数

SysTick->LOAD中的值为计数值
计算方法为工作频率值/分频值
比如工作频率/1000 则周期为1ms

以ADuCM4050为例:

#include "ADuCM4050.h"void delay_ms(unsigned int ms)
{SysTick->LOAD = 26000000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能52MHz的系统定时器while(ms--){while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待}SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{SysTick->LOAD = 26000000/1000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能52MHz的系统定时器while(us--){while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待}SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

其中的52000000表示芯片的系统定时器频率 32系列一般为外部定时器频率的两倍

Cortex-M架构SysTick系统定时器阻塞和非阻塞延时

阻塞延时

首先是最常用的阻塞延时

void delay_ms(unsigned int ms)
{SysTick->LOAD = 50000000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器while(ms--){while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待}SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{SysTick->LOAD = 50000000/1000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器while(us--){while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待}SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

50000000表示工作频率
分频后即可得到不同的延时时间
以此类推

那么 不用两个嵌套while循环 也可以写成:

void delay_ms(unsigned int ms)
{SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

但是这种写法有个弊端
那就是输入ms后,最大定时不得超过计数值,也就是不能超过LOAD的最大值,否则溢出以后,则无法正常工作

而LOAD如果最大是32位 也就是4294967295

晶振为50M的话 50M的计数值为1s 4294967295计数值约为85s

固最大定时时间为85s

但用嵌套while的话 最大可以支持定时4294967295*85s

非阻塞延时

如果采用非阻塞的话 直接改写第二种方法就好了:

void delay_ms(unsigned int ms)
{SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

将等待和关闭定时器语句去掉
在使用时加上判断即可变为阻塞:

delay_ms(500);
while ((SysTick->CTRL & 0x00010000)==0);
SysTick->CTRL = 0;

在非阻塞状态下 可以提交定时器后 去做别的事情 然后再来等待

不过这样又有一个弊端 那就是定时器会自动重载 可能做别的事情以后 定时器跑过了 然后就要等85s才能停下

故可以通过内部定时器来进行非阻塞延时函数的编写

基本上每个mcu的内部定时器都可以配置自动重载等功能 网上资料很多 这里就不再阐述了

位带操作

位带代码

M3、M4架构的单片机 其输出口地址为端口地址+20 输入为+16
M0架构的单片机 其输出口地址为端口地址+12 输入为+8
以ADuCM4050为列:

位带宏定义
#ifndef __GPIO_H__
#define __GPIO_H__
#include "ADuCM4050.h"
#include "adi_gpio.h"#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))#define GPIO0_ODR_Addr    (ADI_GPIO0_BASE+20) //0x40020014
#define GPIO0_IDR_Addr    (ADI_GPIO0_BASE+16) //0x40020010#define GPIO1_ODR_Addr    (ADI_GPIO1_BASE+20) //0x40020054
#define GPIO1_IDR_Addr    (ADI_GPIO1_BASE+16) //0x40020050#define GPIO2_ODR_Addr    (ADI_GPIO2_BASE+20) //0x40020094
#define GPIO2_IDR_Addr    (ADI_GPIO2_BASE+16) //0x40020090#define GPIO3_ODR_Addr    (ADI_GPIO3_BASE+20) //0x400200D4
#define GPIO3_IDR_Addr    (ADI_GPIO3_BASE+16) //0x400200D0#define P0_O(n)   	BIT_ADDR(GPIO0_ODR_Addr,n)  //输出 
#define P0_I(n)    	BIT_ADDR(GPIO0_IDR_Addr,n)  //输入 #define P1_O(n)   	BIT_ADDR(GPIO1_ODR_Addr,n)  //输出 
#define P1_I(n)    	BIT_ADDR(GPIO1_IDR_Addr,n)  //输入 #define P2_O(n)   	BIT_ADDR(GPIO2_ODR_Addr,n)  //输出 
#define P2_I(n)    	BIT_ADDR(GPIO2_IDR_Addr,n)  //输入 #define P3_O(n)   	BIT_ADDR(GPIO3_ODR_Addr,n)  //输出 
#define P3_I(n)    	BIT_ADDR(GPIO3_IDR_Addr,n)  //输入 #define Port0			(ADI_GPIO_PORT0)
#define Port1			(ADI_GPIO_PORT1)
#define Port2			(ADI_GPIO_PORT2)
#define Port3			(ADI_GPIO_PORT3)#define Pin0			(ADI_GPIO_PIN_0)
#define Pin1			(ADI_GPIO_PIN_1)
#define Pin2			(ADI_GPIO_PIN_2)
#define Pin3			(ADI_GPIO_PIN_3)
#define Pin4			(ADI_GPIO_PIN_4)
#define Pin5			(ADI_GPIO_PIN_5)
#define Pin6			(ADI_GPIO_PIN_6)
#define Pin7			(ADI_GPIO_PIN_7)
#define Pin8			(ADI_GPIO_PIN_8)
#define Pin9			(ADI_GPIO_PIN_9)
#define Pin10			(ADI_GPIO_PIN_10)
#define Pin11			(ADI_GPIO_PIN_11)
#define Pin12			(ADI_GPIO_PIN_12)
#define Pin13			(ADI_GPIO_PIN_13)
#define Pin14			(ADI_GPIO_PIN_14)
#define Pin15			(ADI_GPIO_PIN_15)void GPIO_OUT(unsigned int port,unsigned int pin,unsigned int flag);
void GPIO_BUS_OUT(unsigned int port,unsigned int num);void P0_BUS_O(unsigned int num);
unsigned int P0_BUS_I(void);void P1_BUS_O(unsigned int num);
unsigned int P1_BUS_I(void);void P2_BUS_O(unsigned int num);
unsigned int P2_BUS_I(void);void P3_BUS_O(unsigned int num);
unsigned int P3_BUS_I(void);#endif
总线函数
#include "ADuCM4050.h"
#include "adi_gpio.h"
#include "GPIO.h"void GPIO_OUT(unsigned int port,unsigned int pin,unsigned int flag)
{switch(port){case 0:{switch(pin){case 0:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_0));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_0));};break;case 1:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_1));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_1));};break;case 2:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_2));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_2));};break;case 3:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_3));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_3));};break;case 4:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_4));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_4));};break;case 5:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_5));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_5));};break;case 6:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_6));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_6));};break;case 7:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_7));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_7));};break;case 8:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_8));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_8));};break;case 9:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_9));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_9));};break;case 10:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_10));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_10));};break;case 11:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_11));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_11));};break;case 12:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_12));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_12));};break;case 13:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_13));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_13));};break;case 14:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_14));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_14));};break;case 15:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_15));}else{adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_15));};break;default:pin=0;break;}}break;case 1:{switch(pin){case 0:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_0));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_0));};break;case 1:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_1));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_1));};break;case 2:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_2));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_2));};break;case 3:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_3));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_3));};break;case 4:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_4));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_4));};break;case 5:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_5));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_5));};break;case 6:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_6));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_6));};break;case 7:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_7));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_7));};break;case 8:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_8));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_8));};break;case 9:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_9));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_9));};break;case 10:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_10));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_10));};break;case 11:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_11));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_11));};break;case 12:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_12));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_12));};break;case 13:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_13));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_13));};break;case 14:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_14));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_14));};break;case 15:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_15));}else{adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_15));};break;default:pin=0;break;}}break;case 2:{switch(pin){case 0:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_0));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_0));};break;case 1:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_1));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_1));};break;case 2:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_2));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_2));};break;case 3:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_3));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_3));};break;case 4:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_4));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_4));};break;case 5:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_5));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_5));};break;case 6:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_6));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_6));};break;case 7:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_7));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_7));};break;case 8:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_8));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_8));};break;case 9:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_9));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_9));};break;case 10:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_10));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_10));};break;case 11:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_11));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_11));};break;case 12:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_12));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_12));};break;case 13:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_13));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_13));};break;case 14:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_14));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_14));};break;case 15:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_15));}else{adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_15));};break;default:pin=0;break;}}break;case 3:{switch(pin){case 0:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_0));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_0));};break;case 1:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_1));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_1));};break;case 2:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_2));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_2));};break;case 3:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_3));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_3));};break;case 4:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_4));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_4));};break;case 5:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_5));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_5));};break;case 6:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_6));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_6));};break;case 7:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_7));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_7));};break;case 8:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_8));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_8));};break;case 9:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_9));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_9));};break;case 10:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_10));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_10));};break;case 11:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_11));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_11));};break;case 12:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_12));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_12));};break;case 13:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_13));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_13));};break;case 14:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_14));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_14));};break;case 15:if(flag==1){adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_15));}else{adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_15));};break;default:pin=0;break;}}break;default:port=0;break;}	
}void GPIO_BUS_OUT(unsigned int port,unsigned int num)  //num最大为0xffff
{int i;for(i=0;i<16;i++){GPIO_OUT(port,i,(num>>i)&0x0001);}
}void P0_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){P0_O(i)=(num>>i)&0x0001;}
}
unsigned int P0_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(P0_I(i)<<i)&0xFFFF;}return num;
}void P1_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){P1_O(i)=(num>>i)&0x0001;}
}
unsigned int P1_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(P1_I(i)<<i)&0xFFFF;}return num;
}void P2_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){P2_O(i)=(num>>i)&0x0001;}
}
unsigned int P2_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(P2_I(i)<<i)&0xFFFF;}return num;
}void P3_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){P3_O(i)=(num>>i)&0x0001;}
}
unsigned int P3_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(P3_I(i)<<i)&0xFFFF;}return num;
}

一、位带操作理论及实践

位带操作的概念其实30年前就有了,那还是 CM3 将此能力进化,这里的位带操作是 8051 位寻址区的威力大幅加强版

位带区: 支持位带操作的地址区

位带别名: 对别名地址的访问最终作 用到位带区的访问上(注意:这中途有一个 地址映射过程)

位带操作对于硬件 I/O 密集型的底层程序最有用处

支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM4中,有两个区中实现了位带。其中一个是SRAM区的最低1MB范围,第二个则是片内外设区的最低1MB范围。这两个区中的地址除了可以像普通的RAM一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个32位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

位操作就是可以单独的对一个比特位读和写,类似与51中sbit定义的变量,stm32中通过访问位带别名区来实现位操作的功能
STM32中有两个地方实现了位带,一个是SRAM,一个是片上外设。
在这里插入图片描述
(1)位带本质上是一块地址区(例如每一位地址位对应一个寄存器)映射到另一片地址区(实现每一位地址位对应一个寄存器中的一位),该区域就叫做位带别名区,将每一位膨胀成一个32位的字。
(2)位带区的4个字节对应实际寄存器或内存区的一个位,虽然变大到4个字节,但实际上只有最低位有效(代表0或1)

只有位带可以直接用=赋值的方式来操作寄存器 位带是把寄存器上的每一位 膨胀到32位 映射到位带区 比如0x4002 0000地址的第0个bit 映射到位带区的0地址 那么其对应的位带映射地址为0x00 - 0x04 一共32位 但只有LSB有效 采用位带的方式用=赋值时 就是把位带区对应的LSB赋值 然后MCU再转到寄存器对应的位里面 寄存器操作时 如果不改变其他位上面的值 那就只能通过&=或者|=的方式进行

在这里插入图片描述

要设置0x2000 0000这个字节的第二个位bit2为1,使用位带操作的步骤有:
1、将1写入位 带别名区对应的映射地址(即0x22000008,因为1bit对应4个byte);
2、将0x2000 0000的值 读取到内部的缓冲区(这一步骤是内核完成的,属于原子操作,不需要用户操作);
3、将bit2置1,再把值写 回到0x2000 0000(属于原子操作,不需要用户操作)。

关于GPIO引脚对应的访问地址,可以参考以下公式
寄存器位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

如:端口F访问的起始地址GPIOF_BASE

#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)

在这里插入图片描述

但好在官方库里面都帮我们定义好了 只需要在BASE地址加上便宜即可

例如:

GPIOF的ODR寄存器的地址 = GPIOF_BASE + 0x14

寄存器位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

设置PF9引脚的话:

uint32_t *PF9_BitBand =
*(uint32_t *)(0x42000000 + ((uint32_t )&GPIOF->ODR– 0x40000000) *32 + 9*4)

封装一下:

#define PFout(x) *(volatile uint32_t *)(0x42000000 + ((uint32_t )&GPIOF->ODR – 0x40000000) *32 + x*4)

现在 可以把通用部分封装成一个小定义:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

那么 设置PF引脚的函数可以定义:

#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414   
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 #define PF_O(n)   	BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PF_I(n)    	BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

若使PF9输入输出则:

PF_O(9)=1;  //输出高电平
uint8_t dat = PF_I(9);  //获取PF9引脚的值

总线输入输出:

void PF_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PF_O(i)=(num>>i)&0x0001;}
}
unsigned int PF_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PF_I(i)<<i)&0xFFFF;}return num;
}

STM32的可用下面的函数:

#ifndef __GPIO_H__
#define __GPIO_H__
#include "stm32l496xx.h"#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     #define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 #define PA_O(n)   	BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PA_I(n)    	BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 #define PB_O(n)   	BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PB_I(n)    	BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 #define PC_O(n)   	BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PC_I(n)    	BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 #define PD_O(n)   	BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PD_I(n)    	BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 #define PE_O(n)   	BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PE_I(n)    	BIT_ADDR(GPIOE_IDR_Addr,n)  //输入#define PF_O(n)   	BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PF_I(n)    	BIT_ADDR(GPIOF_IDR_Addr,n)  //输入#define PG_O(n)   	BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PG_I(n)    	BIT_ADDR(GPIOG_IDR_Addr,n)  //输入#define PH_O(n)   	BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PH_I(n)    	BIT_ADDR(GPIOH_IDR_Addr,n)  //输入#define PI_O(n)			BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PI_I(n)   	BIT_ADDR(GPIOI_IDR_Addr,n)  //输入void PA_BUS_O(unsigned int num);
unsigned int PA_BUS_I(void);void PB_BUS_O(unsigned int num);
unsigned int PB_BUS_I(void);void PC_BUS_O(unsigned int num);
unsigned int PC_BUS_I(void);void PD_BUS_O(unsigned int num);
unsigned int PD_BUS_I(void);void PE_BUS_O(unsigned int num);
unsigned int PE_BUS_I(void);void PF_BUS_O(unsigned int num);
unsigned int PF_BUS_I(void);void PG_BUS_O(unsigned int num);
unsigned int PG_BUS_I(void);void PH_BUS_O(unsigned int num);
unsigned int PH_BUS_I(void);void PI_BUS_O(unsigned int num);
unsigned int PI_BUS_I(void);#endif
#include "GPIO.h"void PA_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PA_O(i)=(num>>i)&0x0001;}
}
unsigned int PA_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PA_I(i)<<i)&0xFFFF;}return num;
}void PB_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PB_O(i)=(num>>i)&0x0001;}
}
unsigned int PB_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PB_I(i)<<i)&0xFFFF;}return num;
}void PC_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PC_O(i)=(num>>i)&0x0001;}
}
unsigned int PC_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PC_I(i)<<i)&0xFFFF;}return num;
}void PD_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PD_O(i)=(num>>i)&0x0001;}
}
unsigned int PD_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PD_I(i)<<i)&0xFFFF;}return num;
}void PE_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PE_O(i)=(num>>i)&0x0001;}
}
unsigned int PE_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PE_I(i)<<i)&0xFFFF;}return num;
}void PF_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PF_O(i)=(num>>i)&0x0001;}
}
unsigned int PF_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PF_I(i)<<i)&0xFFFF;}return num;
}void PG_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PG_O(i)=(num>>i)&0x0001;}
}
unsigned int PG_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PG_I(i)<<i)&0xFFFF;}return num;
}void PH_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PH_O(i)=(num>>i)&0x0001;}
}
unsigned int PH_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PH_I(i)<<i)&0xFFFF;}return num;
}void PI_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{int i;for(i=0;i<16;i++){PI_O(i)=(num>>i)&0x0001;}
}
unsigned int PI_BUS_I(void)  //输出值num最大为0xFFFF
{unsigned int num;int i;for(i=0;i<16;i++){num=num+(PI_I(i)<<i)&0xFFFF;}return num;
}

二、如何判断MCU的外设是否支持位带

根据《ARM Cortex-M3与Cortex-M4权威指南(第3版)》中第6章第7节描述
在这里插入图片描述
也就是说 要实现对GPIO的位带操作 必须保证GPIO位于外设区域的第一个1MB中
第一个1MB应该是0x4010 0000之前 位带不是直接操作地址 而是操作地址映射 地址映射被操作以后 MCU自动会修改对应寄存器的值

位带区只有1MB 所以只能改0x4000 0000 - 0x400F FFFF的寄存器
像F4系列 GPIO的首地址为0x4002 0000 就可以用位带来更改

STM32L476的GPIO就不行:
在这里插入图片描述
AHB2的都不能用位带
ABP 还有AHB1都可以用
在这里插入图片描述
但是L476的寄存器里面 GPIO和ADC都是AHB2

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

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

相关文章

用语言模型探索语音风格空间:无需情感标签的情 感TTS

用语言模型探索语音风格空间&#xff1a;无需情感标签的情感TTS 原文&#xff1a;Exploring speech style spaces with language models: Emotional TTS without emotion labels 今天我们要说的是 一种无需情感标签的情感TTS。提出了一个基于FastSpeech2的E-TTS框架&#xff0…

基于Ubuntu2404搭建k8s-1.31集群

k8s 1.31 环境初始化安装Container安装runc安装CNI插件部署k8s集群安装crictl使用kubeadm部署集群节点加入集群部署Calico网络配置dashboard 本实验基于VMware创建的Ubuntu2404虚拟机搭建k8s 1.31版本集群&#xff0c;架构为一主一从&#xff0c;容器运行时使用Container&#…

linux的三剑客和进程处理

Linux三剑客&#xff1a; grep&#xff1a;查找 sed&#xff1a;编辑 awk&#xff1a;分析 grep - 正则表达式 [rootlocalhost ~]# grep ^a hello.txt abc grep - 忽略大小写&#xff0c;还有一些场景需要查询出来对应字符串所在的行号&#xff0c;方便我们快速在文件中定位字…

渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)

Burp Suite 联动 XRAY 图形化工具.&#xff08;主动扫描被动扫描&#xff09; Burp Suite 和 Xray 联合使用&#xff0c;能够将 Burp 的强大流量拦截与修改功能&#xff0c;与 Xray 的高效漏洞检测能力相结合&#xff0c;实现更全面、高效的网络安全测试&#xff0c;同时提升漏…

时间序列分析(三)——白噪声检验

此前篇章&#xff1a; 时间序列分析&#xff08;一&#xff09;——基础概念篇 时间序列分析&#xff08;二&#xff09;——平稳性检验 一、相关知识点 白噪声的定义&#xff1a;白噪声序列是一种在统计学和信号处理中常见的随机过程&#xff0c;由一系列相互独立、具有相同…

CEF132编译指南 MacOS 篇 - 构建 CEF (六)

1. 引言 经过前面一系列的精心准备&#xff0c;我们已经完成了所有必要的环境配置和源码获取工作。本篇作为 CEF132 编译指南系列的第六篇&#xff0c;将详细介绍如何在 macOS 系统上构建 CEF132。通过配置正确的编译命令和参数&#xff0c;我们将完成 CEF 的构建工作&#xf…

deepseek + kimi 高效生成PPT

1.在deepseek中生成ppt大纲 2.将大纲复制到kimi中生成PPT kimi&#xff1a;https://kimi.moonshot.cn/

CSS 属性选择器详解与实战示例

CSS 属性选择器是 CSS 中非常强大且灵活的一类选择器&#xff0c;它能够根据 HTML 元素的属性和值来进行精准选中。在实际开发过程中&#xff0c;属性选择器不仅可以提高代码的可维护性&#xff0c;而且能够大大优化页面的样式控制。本文将结合菜鸟教程的示例&#xff0c;从基础…

【嵌入式Linux应用开发基础】read函数与write函数

目录 一、read 函数 1.1. 函数原型 1.2. 参数说明 1.3. 返回值 1.4. 示例代码 二、write 函数 2.1. 函数原型 2.2. 参数说明 2.3. 返回值 2.4. 示例代码 三、关键注意事项 3.1 部分读写 3.2 错误处理 3.3 阻塞与非阻塞模式 3.4 数据持久化 3.5 线程安全 四、嵌…

进程状态

目录 1.进程排队 硬件的队列 进程排队 2.进程的三大状态 什么是状态 运行状态 阻塞状态 挂起状态 3.Linux系统中的进程状态 4.僵尸状态 5.孤儿进程 1.进程排队 硬件的队列 计算机是由很多硬件组成的&#xff0c;操作系统为了管理这些硬件&#xff0c;通常需要为这…

项目复盘:提炼项目成功与失败的经验

项目复盘&#xff0c;顾名思义&#xff0c;就是在项目结束后&#xff0c;对整个项目过程进行全面、系统、深入的回顾与总结。它不仅仅是对项目成果的简单评价&#xff0c;更是对项目执行过程中所有细节、决策、挑战与解决方案的深入剖析。通过复盘&#xff0c;我们可以清晰地看…

Rhel Centos环境开关机自动脚本

Rhel Centos环境开关机自动脚本 1. 业务需求2. 解决方法2.1 rc.local2.2 rc.d2.3 systemd2.4 systemd附着的方法2.5 tuned 3. 测试 1. 业务需求 一台较老的服务器上面业务比较简单,提供一个简单的网站,但已经没有业务的运维人员. 想达到的效果: 由于是非标准的apache或者nginx…

网络安全威胁是什么

1.网络安全威胁的概念 网络安全威胁指网络中对存在缺陷的潜在利用&#xff0c;这些缺陷可能导致信息泄露、系统资源耗尽、非法访问、资源被盗、系统或数据被破坏等。 2.网络安全威胁的类型 物理威胁系统漏洞威胁身份鉴别威胁线缆连接威胁有害程序危险 &#xff08;1&#x…

网络工程师 (30)以太网技术

一、起源与发展 以太网技术起源于20世纪70年代&#xff0c;最初由Xerox公司的帕洛阿尔托研究中心&#xff08;PARC&#xff09;开发。最初的以太网采用同轴电缆作为传输介质&#xff0c;数据传输速率为2.94Mbps&#xff08;后发展为10Mbps&#xff09;&#xff0c;主要用于解决…

Java 循环结构进阶

二重循环 1.一个循环体内又包含另一个完整的循环结构 2.外城循环变量变化一次&#xff0c;内层循环变量要变化一遍。 二重循环-冒泡排序

SSL域名证书怎么申请?

在数字化时代&#xff0c;网络安全已成为企业和个人不可忽视的重要议题。SSL&#xff08;Secure Sockets Layer&#xff0c;安全套接层&#xff09;域名证书&#xff0c;作为保障网站数据传输安全的关键工具&#xff0c;其重要性日益凸显。 一、SSL域名证书&#xff1a;网络安…

玩转观察者模式

文章目录 什么是观察者模式解决方案结构适用场景实现方式观察者模式优缺点优点:缺点:什么是观察者模式 观察者模式通俗点解释就是你在观察别人,别人有什么变化,你就做出什么调整。观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个“观察…

使用mermaid画流程图

本文介绍使用mermaid画流程图&#xff0c;并给出几个示例。 背景 目前&#xff0c;除有明确格式要求的文档外&#xff0c;笔者一般使用markdown写文档、笔记。当文档有图片时&#xff0c;使用Typora等软件可实时渲染&#xff0c;所见即所得。但如果文档接收方没有安装相关工具…

【JVM详解四】执行引擎

一、概述 Java程序运行时&#xff0c;JVM会加载.class字节码文件&#xff0c;但是字节码并不能直接运行在操作系统之上&#xff0c;而JVM中的执行引擎就是负责将字节码转化为对应平台的机器码让CPU运行的组件。 执行引擎是JVM核心的组成部分之一。可以把JVM架构分成三部分&am…

Vim操作笔记

注&#xff1a;本篇文章是追加笔记&#xff0c;用于记录自己的常用操作。 将文本中A字符串替换成B字符串 基本语法&#xff1a; :{范围}s/{目标}/{替换}/{标志} 作用范围 分为前行(:s)、全文(:%s)、选区(:start,ends)等。选区可以在Visual模式下选择区域后输入&#xff1a…