【STM32】HAL库Host MSC读写外部U盘及FatFS文件系统的USB Disk模式

【STM32】HAL库Host MSC读写外部U盘及FatFS文件系统的USB Disk模式

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

在上面 USB还是工作在Device模式 而不是Host模式 如果配置成Host模式则可以解锁FatFS中的USB Disk功能 实现方式不同 原理、功能也不一样 请勿混淆
另外在Host模式下 MCU作为主机使用 可以读写外部插入进来的USB硬盘系统
在硬件上需要多加一根线 另外FatFS的USB Disk功能需要连上USB以后才能挂载硬盘
其相关配置可以由CubeMX完全生成 用户只需要调用文件操作函数API即可 不需要修改代码

该FatFS模式可以同其他文件系统一起使用 互相独立不冲突(如普通模式User-defined模式)

文章目录

  • USB Host MSC
  • FatFS USB Disk
  • 修改代码
  • 测试
  • 附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作
    • SysTick系统定时器精准延时
      • 延时函数
        • 阻塞延时
        • 非阻塞延时
    • 位带操作
      • 位带代码
        • 位带宏定义
        • 总线函数
      • 一、位带操作理论及实践
      • 二、如何判断MCU的外设是否支持位带

USB Host MSC

在Host主机模式下 可以配置主机MSC
USB配置为:
在这里插入图片描述
在该模式下 USB的中断被强制打开
MSC配置为:
在这里插入图片描述
这里需要多加一根输出引脚作为Host控制引脚
在这里插入图片描述
在工程上 导入Host下的.c文件:
在这里插入图片描述
另外添加头文件路径:
在这里插入图片描述

.\RTE\Device\STM32L496RGTx\STCubeGenerated\Middlewares\ST\STM32_USB_Host_Library\Class\MSC\Inc
.\RTE\Device\STM32L496RGTx\STCubeGenerated\Middlewares\ST\STM32_USB_Host_Library\Core\Inc

并添加src文件
在这里插入图片描述
其中 usbh_diskio.c会在配置FatFS后生成

FatFS USB Disk

与其他文件系统配置方式一致
注意支持中文
在这里插入图片描述
在MAX_SS中 可以根据实际 填入可以支持的硬盘分配单元大小(最大为4096 所以如果硬盘只支持4096 那么这一项必须要为最大 如果硬盘最小超过4096 则MCU无法支持)
同样导入FatFS相关.c文件
在这里插入图片描述
以及fatfs.cusbh_diskio.c

在这里插入图片描述

修改代码

usbh_diskio.c中 生成的代码已经完成了文件系统的底层 所以不用我们去修改了
我们需要建立自己的文件操作API函数
如下:

#include "file_operate.h"
#include <string.h>//定义用于格式化的工作区缓存
BYTE work[_MAX_SS];
#define USERFile USBHFile
#define USERFatFS USBHFatFS
#define USERPath USBHPath
#define retUSER retUSBHvoid 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);
}

实际上就是将之前的文件系统介绍的操作函数中的变量给替换了
替换的变量在fatfs.c中可以找到

uint8_t retUSBH;    /* Return value for USBH */
char USBHPath[4];   /* USBH logical drive path */
FATFS USBHFatFS;    /* File system object for USBH logical drive */
FIL USBHFile;       /* File object for USBH */#define USERFile USBHFile
#define USERFatFS USBHFatFS
#define USERPath USBHPath
#define retUSER retUSBH

注意 工作区和堆栈也要与其对应 相应增大 格式化的f_mkfs函数参数也要改变

另外 在USB设备插入之前 会循环调用MX_USB_HOST_Process函数
以轮询USB设备状态
在这里插入图片描述
声明外部变量Appli_state以随时可以读取状态
(具体在哪定义都可 只要全局包含就行)

extern ApplicationTypeDef Appli_state;

在主循环中可以改成这样:

while (1){/* USER CODE END WHILE */MX_USB_HOST_Process();/* USER CODE BEGIN 3 */if (Appli_state == APPLICATION_READY){break;}}FatFS_Init();while (1){}

即轮询USB Disk连接成功后 执行FatFS初始化函数 即挂载、格式化、测试等等

测试

轮询USB Disk连接成功后:
在这里插入图片描述
硬盘信息:
在这里插入图片描述
文件内容:
在这里插入图片描述

附录: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.mzph.cn/news/895335.shtml

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

相关文章

本地生活服务平台开发进入发展热潮

本地生活服务平台&#xff1a;当下的发展热潮 本地生活服务平台开发模式 在当今数字化时代&#xff0c;本地生活服务平台开发已成为人们日常生活中不可或缺的一部分。只需动动手指&#xff0c;打开手机上的 APP&#xff0c;就能轻松满足各类生活需求。像某团、饿XX这样的平台&a…

LSTM变种模型

GRU GRU简介 门控循环神经网络 (Gated Recurrent Neural Network&#xff0c;GRNN) 的提出&#xff0c;旨在更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可学习的门来控制信息的流动。其中&#xff0c;门控循环单元 (Gated Recurrent Unit &#xff0c; GRU) 是…

微服务与网关

什么是网关 背景 单体项目中&#xff0c;前端只用访问指定的一个端口8080&#xff0c;就可以得到任何想要的数据 微服务项目中&#xff0c;ip是不断变化的&#xff0c;端口是多个的 解决方案&#xff1a;网关 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由、转发…

二分算法篇:二分答案法的巧妙应用

二分算法篇&#xff1a;二分答案法的巧妙应用 那么看到二分这两个字想必我们一定非常熟悉&#xff0c;那么在大学期间的c语言的教学中会专门讲解二分查找&#xff0c;那么我们来简单回顾一下二分查找算法&#xff0c;我们知道二分查找是在一个有序的序列中寻找一个数在这个序列…

C# OpenCV机器视觉:模仿Halcon各向异性扩散滤波

在一个充满创意与挑战的图像处理工作室里&#xff0c;阿强是一位热情的图像魔法师。他总是在追求更加出色的图像效果&#xff0c;然而&#xff0c;传统的图像处理方法有时候并不能满足他的需求。 有一天&#xff0c;阿强听说了 Halcon 中的各向异性扩散滤波功能&#xff0c;它…

实现:多活的基础中间件

APIRouter &#xff1a; 路由分发服务 API Router 是一个 HTTP 反向代理和负载均衡器&#xff0c;部署在公有云中作为 HTTP API 流量的入口&#xff0c;它能识别 出流量的归属 shard &#xff0c;并根据 shard 将流量转发到对应的 ezone 。 API Router 支持多种路由键&am…

DeepSeek本地化部署

DeepSeek本地化部署 本教程为一键式部署&#xff0c;适合于mac、ubuntu、windows。【开源地址】 环境要求 nodejs > 18Python > 3.10.12 步骤一&#xff1a;安装ollama客户端 官网直接安装&#xff0c;ollama官网。安装完成后使用命令&#xff1a;ollama -h&#xf…

大数据与大模型:数字时代的共生力量

引言&#xff1a;大数据与大模型的崭新时代 在数字化浪潮汹涌澎湃的当下&#xff0c;大数据与大模型无疑是最为耀眼的两颗明星 &#xff0c;深刻地改变着我们的生活、工作和思维方式。大数据&#xff0c;作为信息时代的宝藏&#xff0c;蕴含着无尽的价值。从电商平台的海量交易…

[2025年最新]2024.3版本idea无法安装插件问题解决

背景 随着大模型的持续发展&#xff0c;特别年前年后deepseek的优异表现&#xff0c;编程过程中&#xff0c;需要解决ai来辅助编程&#xff0c;因此需要安装一些大模型插件 问题描述 在线安装插件的时候会遇到以下问题&#xff1a; 1.数据一直在加载&#xff0c;加载的很满 2.点…

自动驾驶---如何打造一款属于自己的自动驾驶系统

在笔者的专栏《自动驾驶Planning决策规划》中&#xff0c;主要讲解了行车的相关知识&#xff0c;从Routing&#xff0c;到Behavior Planning&#xff0c;再到Motion Planning&#xff0c;以及最后的Control&#xff0c;笔者都做了相关介绍&#xff0c;其中主要包括算法在量产上…

三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测(Maltab)

三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测&#xff08;Maltab&#xff09; 完整代码私信回复三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测&#xff08;Maltab&#xff09; 一、引言 1、研究背景和意义 在现代数据科学领域&#xff0c;时间序列…

Jenkins+gitee 搭建自动化部署

Jenkinsgitee 搭建自动化部署 环境说明&#xff1a; 软件版本备注CentOS8.5.2111JDK1.8.0_211Maven3.8.8git2.27.0Jenkins2.319最好选稳定版本&#xff0c;不然安装插件有点麻烦 一、安装Jenkins程序 1、到官网下载相应的版本war或者直接使用yum安装 Jenkins官网下载 直接…

AI 编程开发插件codeium Windsurf(vscode、editor) 安装

1、vscode中安装&#xff1a; 2、vscode中使用 3、输入注册的账号密码&#xff0c;就可以使用。 4、或者直接下载editor 5、安装editor 下一步&#xff0c;下一步&#xff0c;直到安装成功&#xff0c;中间可以改下安装位置&#xff0c;如果C盘空间不够。 同样提示注册或者登录…

【Mac排错】ls: command not found 终端命令失效的解决办法

【TroubleShooting on Mac】ls: command not found 终端命令失效的解决办法 A Solution to Solve “Command not found” of Terminal on Mac 一直在使用心爱的MacBook Pro的Terminal&#xff0c;并且为她定制了不同的Profile。 这样&#xff0c;看起来她可以在不同季节&…

河北某石油管廊自动化监测

1. 项目简介 近年来&#xff0c;国家密集出台油气管道建设相关政策和规划引导中国油气管道加快建设&#xff0c;2017年&#xff0c;在《中长期油气管网规划》中对2025年和2030年油气管道发展目标均作出了相应的规划目标。另一方面&#xff0c;随着油气管道行业的发展&#xff…

问题:通过策略模式+工厂模式+模板方法模式实现ifelse优化

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 示例&#xff1a;商城系统有会员系统&#xff0c;不同会员有不同优惠程度&#xff0c;普通会员不优惠&#xff1b;黄金会员打8折&#xff1b;白金会员优惠50元&#xff0c;再打7折&#xff1b; 问题描…

Android ndk兼容 64bit so报错

1、报错logcat如下 2025-01-13 11:34:41.963 4687-4687 DEBUG pid-4687 A #01 pc 00000000000063b8 /system/lib64/liblog.so (__android_log_default_aborter16) (BuildId: 467c2038cdfa767245f9280e657fdb85) 2025…

centos安装Nexus Repository OSS(Maven私服)

1. 下载链接&#xff1a;https://help.sonatype.com/en/download.html 2. 注意页面下载页面中的要求&#xff1a;JDK17&#xff08;启动时提示最低JDK1.8最高JDK17&#xff0c;但是使用JDK1.8无法正常启动&#xff09; 3. mkdir /opt/nexus 将压缩包上传到该目录并解压。 tar …

b站——《【强化学习】一小时完全入门》学习笔记及代码(1-3 多臂老虎机)

问题陈述 我们有两个多臂老虎机&#xff08;Multi-Armed Bandit&#xff09;&#xff0c;分别称为左边的老虎机和右边的老虎机。每个老虎机的奖励服从不同的正态分布&#xff1a; 左边的老虎机&#xff1a;奖励服从均值为 500&#xff0c;标准差为 50 的正态分布&#xff0c;即…

Linux:安装 node 及 nvm node 版本管理工具(ubuntu )

目录 方法一&#xff1a;手动下载安装文件安装方法二&#xff1a;curl安装 方法一&#xff1a;手动下载安装文件安装 git clone 远程镜像 git clone https://gitee.com/mirrors/nvm安装 nvm bash install.sh刷新配置&#xff0c;使配置在终端生效 // 方法 1 source /root/.…