usb

利用stm32cube实现USB+FATFS的usbdisk程序--实现Fatfs对U盘文件操作

本来是要实现usb通过U盘来升级芯片flash程序的
不过实现之前,要先调通fatfs操作u盘
这里记录一下我的操作和想法实现的整个过程,
说句实话,实现之后的代码什么的没什么用,
学习过程才是根本,尤其是实现之中的种种思考方法
大家也不要一味只是拿代码
多思考下为什么这样做?
为什么这样写?
那样写为什么就不行?
我这个教程也不是一次写成的,中间也是各种曲折,各种查资料
相应的我也会在分享的过程中写出我所查的资料和思考的方向
同时,希望大家也能踊跃的分享些自己的学习过程
 
分享的过程就是再次巩固的过程!
当你写出来的时候,你会以别人的角度看待这个问题,为了描述清楚,你就要比别人更加清楚其中的原理,这也是学习的方法!
 
 
>>声明:本站原创不提倡转载,如需转载请注明出处!!!
 

 好了,言归正传,我的是stm32f407discovery板,原理图什么的想必大家看了其他帖子也都了解了.
晶振8M
首先,选外部晶振


QQ截图20160726112418.jpg


我们要用到usb的u盘功能,所以选择fs模式的host_only 主机模式

QQ截图20160726112442.jpg



回到顶部,usb_host中选择MSC大容量存储,并在fatfs中选择usb disk

QQ截图20160726112525.jpg

这个时候,我发现原理图里面Vbus 是被PC0控制的,

QQ截图20160726112702.jpg

所以PC0要选择输出,为了更好的观察,开启两个led灯,PD12/13

QQ截图20160726112807.jpg

 
去设置时钟,主频168,usb48,我比较懒,让它自动生成的时钟

QQ截图20160726112845.jpg

这时候,去configuration面板,先去设置下usb的中断优先级为5.0

QQ截图20160726112944.jpg

下面的图是gpio口的,PC0原理图里面有了上拉了,所以就NOpull了,
 

QQ截图20160726113109.jpg

打开usb_host的按钮进行设置,选择PC0作为VBUS的驱动口

QQ截图20160726113231.jpg

configuration面板中其他的都默认即可,我们再去设置下工程的Heap和Stack分别为0x2000

QQ截图20160726113452.jpg

ok,生成工程文件吧.
 
 
 
 
本帖相关完整程序可在本站百度网盘中找到.
文件名:USBDisk(FatFs+USB操作U盘).7z
已邀请:

admin

赞同来自:

上面写的过程中,我发现有个片子不熟悉,不知道做什么用的?所以我专门去查了下资料
就是控制usb vbus的那个 stmps241 芯片
这个芯片能不能删除?
是做什么用的?
能用什么替换?
QQ截图20160726114227.jpg

从中可以看出,这个是作为开关管
stmps241 支持输出500MA电流给外设
500MA更好就是笔记本电脑的usb口对应的输出电流,
支持ttl驱动
反向电流保护
拥有8KV的esd静电防护,保护CPU芯片.
ok,现在知道了,这个芯片可以去除,但是去除后可能有单片机被静电击穿的危险.
 

admin

赞同来自: 木子dong

查看整个工程之前我们先来看看官方提供的一个Fatfs的开发应用程序的文档
编号是:UM1721

QQ截图20160726115944.jpg

从图中我们知道,要操作u盘,主程序中需要定义
FATFS mynewdiskFatFs;//用户逻辑驱动的文件系统对象
FIL MyFile; //文件对象
char mynewdiskPath[4];//用户逻辑驱动路径
还需要依次使用
FATFS_LinkDriver();
f_mount();
f_open();
f_write();
f_close();
来完成一个完整的写入操作.
 
好了,我们打开刚才生成的工程的
main.c文件

QQ截图20160726115142.jpg

从上到下看下去,
时钟配置
gpio初始化
然后是
MX_FATFS_Init();
我们打开这个函数,查看他的内容
void MX_FATFS_Init(void) 
{
/*## FatFS: Link the USBH driver ###########################*/
retUSBH = FATFS_LinkDriver(&USBH_Driver, USBH_Path);

/* USER CODE BEGIN Init */
/* additional user code for init */
/* USER CODE END Init */
}
看到里面已经包含了FATFS_LinkDriver();
说明我们之后的程序中不需要再去刻意写了.
然后是i
MX_USB_HOST_Init();
查看他 的内容
void MX_USB_HOST_Init(void)
{
/* Init Host Library,Add Supported Class and Start the library*/
USBH_Init(&hUsbHostFS, USBH_UserProcess, HOST_FS);

USBH_RegisterClass(&hUsbHostFS, USBH_MSC_CLASS);

USBH_Start(&hUsbHostFS);
}
里面先初始化了用户进程类
又注册了系统MSC进程类,并开启了usb模式
其中的用户进程类就类似于callback
我们查看下他的内容
/*
* user callbak definition
*/
static void USBH_UserProcess (USBH_HandleTypeDef *phost, uint8_t id)
{

/* USER CODE BEGIN 2 */
switch(id)
{
case HOST_USER_SELECT_CONFIGURATION:
break;

case HOST_USER_DISCONNECTION:
Appli_state = APPLICATION_DISCONNECT;
break;

case HOST_USER_CLASS_ACTIVE:
Appli_state = APPLICATION_READY;
break;

case HOST_USER_CONNECTION:
Appli_state = APPLICATION_START;
break;

default:
break;
}
/* USER CODE END 2 */
}
里面根据不同的usb通信状态id来改变外接flag标识,
根据上面我们提到的um1721文档介绍流程图,我们可以得到一个执行顺序:Host_USER_CONNECTION 连接I/o
HOST_USER_SELECT_CONFIGURATION 驱动配置
HOST_USER_CLASS_ACTIVE 处理过程
HOST_USER_DISCONNECTION 失去连接
 
我们在看看flag标识
typedef enum {
APPLICATION_IDLE = 0,
APPLICATION_START,
APPLICATION_READY,
APPLICATION_DISCONNECT,
}ApplicationTypeDef;

我们这里只用到ready和disconnect即可.
 
 
 

admin

赞同来自: caoenq

现在开始编写main.c文件
我们准备利用usb的各种回调状态进行不同的操作,
在while(1)大循环中添加
        switch(Appli_state)
{
case APPLICATION_READY:
MSC_Application();
Appli_state = APPLICATION_DISCONNECT;
break;

case APPLICATION_DISCONNECT:
f_mount(NULL, (TCHAR const*)"", 0);

break;
// case APPLICATION_READY:
// case APPLICATION_DISCONNECT:
default:
break;
}
上面因为用到了Appli_state这个变量,而这个变量是在usb_host.c中实现状态间的切换,所以,我们在main文件头部添加外部引用:
extern ApplicationTypeDef Appli_state;
上面的那个switch状态解释如下:
如果usb的用户可控状态(APPLICATION_READY)准备好了,那么进行
MSC_Application();//u盘的读写操作
读写完毕之后,将usb用户可控状态设置为APPLICATION_DISCONNECT;//未连接
这样的话下次循环到这里就切换到
case APPLICATION_DISCONNECT这个里面进行操作,而这个里面
f_mount(NULL, (TCHAR const*)"", 0);
要理解这个意思先看它的定义
FRESULT f_mount (
FATFS* fs, /* Pointer to the file system object (NULL:unmount)*/
const TCHAR* path, /* Logical drive number to be mounted/unmounted */
BYTE opt /* 0:Do not mount (delayed mount), 1:Mount immediately */
)
当第一个参数是NULL的时候,就是unmount 意思就是卸载这个逻辑磁盘
 
所以switch的真个含义就是: 当usb准备好了,就执行一次对u盘的写入或者读出操作,然后卸载掉这个磁盘,防止多次重复操作u盘.
完整的msc对u盘的操作程序如下:
static void MSC_Application(void)
{
FRESULT res; /* FatFs function common result code */
uint32_t byteswritten; /* File write/read counts */
uint8_t wtext[] = "The site is STM32cube.com working with FatFs"; /* File write buffer */
// uint8_t rtext[100]; /* File read buffer */

/* Register the file system object to the FatFs module */
if(f_mount(&USBDISKFatFs, (TCHAR const*)USBH_Path, 0) != FR_OK)
{
/* FatFs Initialization Error */
Error_Handler();
}
else
{
/* Create and Open a new text file object with write access */
if(f_open(&MyFile, "STM32.TXT", FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
{
/* 'STM32.TXT' file Open for write Error */
Error_Handler();
}
else
{
/* Write data to the text file */
res = f_write(&MyFile, wtext, sizeof(wtext), (void *)&byteswritten);

if((byteswritten == 0) || (res != FR_OK))
{
/* 'STM32.TXT' file Write or EOF Error */
Error_Handler();
}
else
{
/* Close the open text file */
f_close(&MyFile);
}
}
}
}

下面贴出完成的main.c文件内容
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
#include "fatfs.h"
#include "usb_host.h"
#include "gpio.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
extern ApplicationTypeDef Appli_state;
extern USBH_HandleTypeDef hUsbHostFS;
extern char USBH_Path[4]; /* USBH logical drive path */
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void Error_Handler(void);
void MX_USB_HOST_Process(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
static void MSC_Application(void);
/* USER CODE END PFP */

/* USER CODE BEGIN 0 */
FATFS USBDISKFatFs; /* File system object for USB disk logical drive */
FIL MyFile; /* File object */
/* USER CODE END 0 */

int main(void)
{

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration----------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* Configure the system clock */
SystemClock_Config();

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_HOST_Init();
MX_FATFS_Init();


/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
MX_USB_HOST_Process();

/* USER CODE BEGIN 3 */
switch(Appli_state)
{
case APPLICATION_READY:
MSC_Application();
Appli_state = APPLICATION_DISCONNECT;
break;

case APPLICATION_DISCONNECT:
f_mount(NULL, (TCHAR const*)"", 0);

break;
default:
break;
}
}
/* USER CODE END 3 */

}

/** System Clock Configuration
*/
void SystemClock_Config(void)
{

RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;

__HAL_RCC_PWR_CLK_ENABLE();

__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}

RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}

HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */
static void MSC_Application(void)
{
FRESULT res; /* FatFs function common result code */
uint32_t byteswritten; /* File write/read counts */
uint8_t wtext[] = "The site is STM32cube.com working with FatFs"; /* File write buffer */
// uint8_t rtext[100]; /* File read buffer */

/* Register the file system object to the FatFs module */
if(f_mount(&USBDISKFatFs, (TCHAR const*)USBH_Path, 0) != FR_OK)
{
/* FatFs Initialization Error */
Error_Handler();
}
else
{
/* Create and Open a new text file object with write access */
if(f_open(&MyFile, "STM32.TXT", FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
{
/* 'STM32.TXT' file Open for write Error */
Error_Handler();
}
else
{
/* Write data to the text file */
res = f_write(&MyFile, wtext, sizeof(wtext), (void *)&byteswritten);

if((byteswritten == 0) || (res != FR_OK))
{
/* 'STM32.TXT' file Write or EOF Error */
Error_Handler();
}
else
{
/* Close the open text file */
f_close(&MyFile);
}
}
}
}
/* USER CODE END 4 */

/**
* @brief This function is executed in case of error occurrence.
* @param None
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler */
/* User can add his own implementation to report the HAL error return state */
while(1)
{
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12|GPIO_PIN_13);
}
/* USER CODE END Error_Handler */
}

ok编译,烧写到discovery板,通过OTG线连接一个u盘,就可以在这个u盘上自动生成一个stm32.txt的文件
内容是:The site is STM32cube.com working with FatFs

QQ截图20160726145052.png


QQ截图20160726145120.png

 

admin

赞同来自:

上面的实验完成之后,我想将程序中的stm32.txt文件修改为stm32cube.txt,其他程序不动,但是出现了错误提示.
于是立马去查看UM1721上面关于文件名长度的地方

QQ截图20160726145950.png

上面说,如果使用长文件名,那么必须
_USE_LFN 设为1 , 2 或者 3
这三者的区别是:
/ 0: Disable LFN feature. _MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
因为我不使用UNICODE编码命名文件名所以
#define _LFN_UNICODE    0 /* 0:ANSI/OEM or 1:Unicode */
/* This option switches character encoding on the API. (0:ANSI/OEM or 1:Unicode)
/ To use Unicode string for the path name, enable LFN feature and set _LFN_UNICODE
/ to 1. This option also affects behavior of string I/O functions. */

#define _STRF_ENCODE 0
/* When _LFN_UNICODE is 1, this option selects the character encoding on the file to
/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
/
/ 0: ANSI/OEM
/ 1: UTF-16LE
/ 2: UTF-16BE
/ 3: UTF-8
/
/ When _LFN_UNICODE is 0, this option has no effect. */
这样我就可以,命名文件名类似:stm32cubeadkfdghkglfgj.txt这样长的了
 
下面说下利用stm32cubemx怎么设置为长文件名(不使用中文)
打开先前的stm32cubemx工程,只需要修改一个地方:

QQ截图20160726161704.png

 
这样保存再次生成整个工程,
只要你的代码按照书写规范,生成的时候是不会删除用户自己编写的代码的,所以,你现在可以修改main中的文件名了
例如:
        /* Create and Open a new text file object with write access */
if(f_open(&MyFile, "stm32cubeasdfgfhgjghjkytu.txt", FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
{
/* 'STM32.TXT' file Open for write Error */
Error_Handler();
}
然后,编译,拿u盘测试,

QQ截图20160726161934.png

OK,完成实验
 
注:我是不准备用中文名称作为文件名的,而且里面无论是读取还是写入都没有中文,所以上面的配置够我使用了,
要是用中文就去参考其他帖子.
 

秋枫、

赞同来自:

ASCII确实比中文编码方便。。中文有的时候是GB有的时候是别的编码,用keil 的时候给显示中文乱码经常就编译器的编码没搞好。。

zhanghl45 - STM32L4XX用户

赞同来自:

LL库就好了!

admin

赞同来自: 仲尼jony

这里贴一个官方读取的例子代码:
          uint32_t bytesread;   
uint8_t rtext[100];

/* Open the text file object with read access */
if(f_open(&MyFile, "STM32.TXT", FA_READ) != FR_OK)
{
/* 'STM32.TXT' file Open for read Error */
Error_Handler();
}
else
{
/* Read data from the text file */
res = f_read(&MyFile, rtext, sizeof(rtext), (void *)&bytesread);

if((bytesread == 0) || (res != FR_OK))
{
/* 'STM32.TXT' file Read or EOF Error */
Error_Handler();
}
else
{
/* Close the open text file */
f_close(&MyFile);
}
这样就齐活了,下面可以安心的去实现U盘更新flash来烧写程序了.

nemo1991

赞同来自:

amazing!

张良123

赞同来自: gonghuwei

我也知道用库开发比较快,我一般是用寄存器直接操作的,请问用库开发对flash浪费严重吗

孤独的行者。

赞同来自:

学习了,谢谢分享!

admin

赞同来自:

更新一下:
为了增加拔插U盘状态指示,需要在这个程序的基础上增加一点代码,
其实奏是增加一个开灯和关灯
main()函数中大循环while(1)修改如下:
    while (1)
{
/* USER CODE END WHILE */
MX_USB_HOST_Process();

/* USER CODE BEGIN 3 */
switch(Appli_state)
{
case APPLICATION_READY:
MSC_Application();
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,GPIO_PIN_SET);
Appli_state = APPLICATION_START;
break;

case APPLICATION_START:
f_mount(NULL, (TCHAR const*)"", 0);

break;
case APPLICATION_DISCONNECT:
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,GPIO_PIN_RESET);
Appli_state = APPLICATION_IDLE;
default:
break;
}
}

这样重新编译后,插入U盘写入文件完毕就会灯亮,拔掉u盘后灯灭,再插上再亮,.....

 

坂田银时

赞同来自: rzx1990

有没有试过和freeRTOS结合

好好学习0864 - IT工程师

赞同来自:

兄  能不能加你qq   我想咨询几个问题  我用的芯片是stm32L476  调试U盘读写   一直不通  能不能帮我看下

释怀过客

赞同来自: 欲予玉屿

你好,我用的是SD卡,像知道和你这个用USB的区别大吗?

bme_bright

赞同来自:

学习下。

谢谢分享。

nothing

赞同来自:

请问 U盘 容量大小怎么设置?

U盘被电脑识别出来但是 可用空间为0字节。无法格式化,怎么样设定U盘容量和MCU 内置FLASH 匹配?

欲予玉屿

赞同来自:

你好,按照楼主给的程序,能够实现U盘插上去,灯就亮,拔出就熄灭。那么这样的情况下,就意味着可以对U盘进行读写了吗?现在所遇到的问题就是在U盘中没有看到新建的.TXT的文档,程序进入到了错误处理程序中,即两个灯在交替闪烁,不知道是什么原因造成的,请楼主帮忙解答一下,所有的程序都是按照楼主给的步骤来的,不知道是否还有其他需要注意的地方

凯旋

赞同来自:

666666666

赞同来自:

很好的教程

新的生活 - 90后IT男

赞同来自: Amos

你好,和你这样教程做,老是提示blob.pngblob.pngblob.png

ennocheung - 70

赞同来自:

admi水平真高,UM1721已下载,很有用。

谢谢admi

caoenq - 硬件设计大师

赞同来自:

果然是高手啊

ZGJ20170905

赞同来自: bob3

哇塞,受教了,写的太好了

ckpcmpkun - 90后嵌入式

赞同来自:

看了好几个帖子就这个最管用,666

後知後覺

赞同来自:

怎么下载

usrrsr

赞同来自:

Nicley written

chen1233

赞同来自:

为啥我下进去,识别U盘后就死机了?

wcyingdream

赞同来自:

按照楼主的教程来,已经成功在U盘写入文件,很棒

赞同来自:

APPLICATION_READY

这个状态根本无法达到,分析代码 当USB作为复合设备时才可能达到这个状态

rgzdb

赞同来自:

编译后提示 ../Src/main.c(110): warning:  #111-D: statement is unreachable,无法建立文件

_one

赞同来自:

谢谢楼主的分享

寂静の烛光

赞同来自:

楼主的教程很好,但是我发现我现在插usb2.0U盘一切正常,但是插3.0U盘检测不到APPLICATION_READY状态。。。。



dagu - 嵌入式

赞同来自:

有尝试过检测U盘热拔插吗?

要回复问题请先登录注册