首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 25-02-16 16:40
  • 4180
  • 5146
blog.csdn.net

【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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

HAL库测试代码如下:

void Test_Flash(uint32_t add)
{
	uint32_t error = 0;
	uint64_t dat = 0x0123456776543210;//要写入的数据,必须得是双字64bit
	uint64_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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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

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

这里需要注意 由于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;														//要写入的数据,必须得是双字64bit
	uint32_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

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;														//要写入的数据,必须得是双字64bit
	uint32_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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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=0x01
		Stat = 0 ;  //Stat=0x00
    return 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 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126

这里的读写函数需要加上地址偏移
每次操作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;
	}
	else
		return 0;
  /* USER CODE END get_fattime */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

文件操作函数

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

#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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

工作区缓存

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

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

文件挂载和格式化测试

	retUSER = f_mount(&USERFatFS,USERPath,1);//挂载盘符A
	if(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;
	}//挂载失败
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

需要注意的是 格式化后 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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

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

其他文件操作函数

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

#include "file_operate.h"
#include 

//定义用于格式化的工作区缓存
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);//挂载盘符A
	if(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存储器,小容量
	//剩余空间大小,单位:KB
    DWORD freespace= (fre_sect*fs->ssize)>>10;   
	//总空间大小,单位:KB
    DWORD 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);
#endif
		
    printf("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);
#else
    printf("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);
#else
    printf("Free space(KB) = %ld\r\n", freespace);
#endif

    printf("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");
	//打开失败
	else
		printf("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");
	//发生其他错误
	else
		printf("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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324

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
  • 1
  • 2
  • 3

然后连上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  关闭系统定时器
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

其中的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  关闭系统定时器
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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  关闭系统定时器
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

但是这种写法有个弊端
那就是输入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  关闭系统定时器
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

delay_ms(500);
while ((SysTick->CTRL & 0x00010000)==0);
SysTick->CTRL = 0;
  • 1
  • 2
  • 3

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

不过这样又有一个弊端 那就是定时器会自动重载 可能做别的事情以后 定时器跑过了 然后就要等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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
总线函数
#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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190

一、位带操作理论及实践

位带操作的概念其实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)

  • 1
  • 2
  • 3

封装一下:

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

  • 1
  • 2

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

#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))
  • 1
  • 2
  • 3

那么 设置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)  //输入
  • 1
  • 2
  • 3
  • 4
  • 5

若使PF9输入输出则:

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

总线输入输出:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
#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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173

二、如何判断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

电子信息类交流
QQ群名片
注:本文转载自blog.csdn.net的嵌入式拳铁编曲MikeZhou的文章"https://blog.csdn.net/weixin_53403301/article/details/145591829"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top