最近项目要求利用U盘进行在线升级,使用的是华大单片机跑的是RTthread,弄完后自己简单整理了下,希望对大家有所帮助。
一、准备工作
1.1硬件
华大HC32F460 demo评估板(方便连线调试使用)、某宝买的U盘一个(Fat32)
1.2软件
编译环境还是用了keil(5.29),操作系统用了RTthread(标准版:4.0.0.1)
RTthread下载链接(RTT各种版本可以在他们官网下载),RTthead移植到华大单片机这边就不详细说了,有兴趣可以直接上他们官网直接查看很全面的移植教程RTthread文档中心。
二、移植步骤
2.1USB驱动移植
在使用U盘前我们首先需要实现USB驱动,华大官方其实在源码驱动库中已经实现了USB驱动并封装成USB lib库,我们只需要简单修改下对应引脚配置即可。
华大驱动库下载链接(华大最新的驱动库在官网下载,这边使用的是V2.2.0版本)如图USB核心驱动代码,华大官方是以usb_lib形式提供的,我们可以直接整个拷贝到我们工程目录下面,如下图,usb_lib这里面内容基本不需要更改。
上面是usb驱动的准备工作把USB驱动库添加到我们工程,接下就是移植USB的主要步骤。
第一步:底层驱动的实现主要包括时钟和相关引脚的配置:如果第一次使用可以直接拷贝官方驱动库目录下example中usb例子.C和.h文件,因为我们这次U盘数据读取采用的是USB 作为主机模式方式,所以直接用usb_host_msc下的源文件(usb_app_conf.h,usb_bsp.c,usb_bsp.h,usb_host_user.h,usb_host_user,c)把这些文件直接拷贝到我们工程中。
根据实际需要我们需要修改这几个文件。
usb_bsp.c该文件就是usb驱动需要我们移植修改的主要文件,主要是初始化usb工作时钟和相关引脚,我这边重新定义成drv_usb.c了为保持工程其他驱动文件命名格式。这里移植的时候需要注意因为我们跑了rtthread,SysTick_Handler需要把该文件中的屏蔽掉,否则会跟RTT自带的重复定义,系统滴答时钟部分如果我们在RTT中已经定义也需要屏蔽,避免重复,时钟因为我们在跑rtt时board.c中已经初始化配置过,该文件中的我们也可以屏蔽掉,只需要在UsbClkIni中设置时钟源即可,详见下图。
/**
*******************************************************************************
** \brief Initilizes BSP configurations
** \param None
** \retval None
******************************************************************************/
void hd_usb_bsp_init(usb_core_instance *pdev)
{
stc_port_init_t stcPortInit;
// /* clock config */
// BSP_CLK_Init();
UsbClkIni(); //USB时钟:48MHZ
CLK_GetClockFreq(&stcClkFreq);
// BSP_LED_Init();//指示灯
// BSP_KEY_Init();//键盘扫描
// /* SysTick configuration */
// SysTick_Init(1000u);
// NVIC_SetPriority (SysTick_IRQn, DDL_IRQ_PRIORITY_14);
//
// DDL_PrintfInit(BSP_PRINTF_DEVICE, BSP_PRINTF_BAUDRATE, BSP_PRINTF_PortInit);
// DDL_Printf("USBFS start !!\n");
/* port config */
/* Disable digital function for DM DP */
MEM_ZERO_STRUCT(stcPortInit);
stcPortInit.enPinMode = Pin_Mode_Ana;
PORT_Init(PortA, Pin11, &stcPortInit);
PORT_Init(PortA, Pin12, &stcPortInit);
PORT_SetFunc(PortA, Pin11, Func_UsbF, Disable);
PORT_SetFunc(PortA, Pin12, Func_UsbF, Disable);
PORT_SetFunc(PortB, Pin08, Func_UsbF, Disable);
//使能USB
PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_USBFS, Enable);
}
/**
******************************************************************************
** \brief Initialize the usb clock for the sample
**
** \param None
**
** \return None
******************************************************************************/
static void UsbClkIni(void)
{
// stc_clk_upll_cfg_t stcUpllCfg;
// stcUpllCfg.pllmDiv = 2u;
// stcUpllCfg.plln = 84u;
// stcUpllCfg.PllpDiv = 7u;//48M
// stcUpllCfg.PllqDiv = 7u;
// stcUpllCfg.PllrDiv = 7u;
// CLK_UpllConfig(&stcUpllCfg);
// CLK_UpllCmd(Enable);
// /* Wait UPLL ready. */
// while(Set != CLK_GetFlagStatus(ClkFlagUPLLRdy))
// {
// ;
// }
CLK_SetUsbClkSource(ClkUsbSrcMpllr);//设置USB时钟源
// /* Set USB clock source */
// CLK_SetUsbClkSource(ClkUsbSrcUpllp);
}
下面时board.c中时钟配置以及sysTick配置,RTT系统节拍为1ms。
/*==================================================================
* Function :rt_hw_board_init
* Description :板级初始化
* Input Para :None
* Output Para :None
* Return Value :None
==================================================================*/
void rt_hw_board_init()
{
stc_clk_freq_t stcClkFreq = {0};
/*中断向量表*/
SCB->VTOR = 0x00000000;
/*开启fpu*/
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
/*开启时钟*/
ClockInit();
/*SysTick设置*/
CLK_GetClockFreq(&stcClkFreq);
SysTick_Config( stcClkFreq.sysclkFreq / RT_TICK_PER_SECOND );
PORT_DeInit();
PORT_Unlock();M4_PORT->PCCR = 0xC000;PORT_Lock();
/* 调用组件初始化函数 */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#ifdef RT_USING_CONSOLE
//指定控制台输出串口
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init((void*)HEAP_BEGIN, (void*)HEAP_END);
#endif
}
/*==================================================================
* Function :ClockInit
* Description :开启各总线时钟
* Input Para :None
* Output Para :None
* Return Value :None
==================================================================*/
static void ClockInit(void)
{
en_clk_sys_source_t enSysClkSrc;
stc_clk_sysclk_cfg_t stcSysClkCfg;
stc_clk_xtal_cfg_t stcXtalCfg;
stc_clk_mpll_cfg_t stcMpllCfg;
MEM_ZERO_STRUCT(enSysClkSrc);
MEM_ZERO_STRUCT(stcSysClkCfg);
MEM_ZERO_STRUCT(stcXtalCfg);
MEM_ZERO_STRUCT(stcMpllCfg);
/* Set bus clk div. */
stcSysClkCfg.enHclkDiv = ClkSysclkDiv1; // Max 168MHz
stcSysClkCfg.enExclkDiv = ClkSysclkDiv2; // Max 84MHz
stcSysClkCfg.enPclk0Div = ClkSysclkDiv1; // Max 168MHz
stcSysClkCfg.enPclk1Div = ClkSysclkDiv2; // Max 42MHz
stcSysClkCfg.enPclk2Div = ClkSysclkDiv4; // Max 60MHz
stcSysClkCfg.enPclk3Div = ClkSysclkDiv4; // Max 42MHz
stcSysClkCfg.enPclk4Div = ClkSysclkDiv2; // Max 84MHz
CLK_SysClkConfig(&stcSysClkCfg);
stcXtalCfg.enMode = ClkXtalModeOsc; //外部高速晶振作为时钟源
stcXtalCfg.enDrv = ClkXtalLowDrv; //外部高速晶振驱动能力选择(8~16MHZ晶振)
stcXtalCfg.enFastStartup = Enable; //允许超高速驱动
CLK_XtalConfig(&stcXtalCfg);
CLK_XtalCmd(Enable); //外部晶振使能
/* MPLL config. */
/*system clk = 168M*/
/*MPLLP输出频率 =(输入时钟/MPLL输入时钟分频)*MPLL倍频系数/MPLLP输出分频比*/
stcMpllCfg.pllmDiv = 1u; //MPLL分频系数
stcMpllCfg.plln =42u; //MPLL倍频系数
stcMpllCfg.PllpDiv = 2u; //MPLLP分频系数主要用于系统时钟
stcMpllCfg.PllqDiv = 2u;
stcMpllCfg.PllrDiv = 7u;
CLK_SetPllSource(ClkPllSrcXTAL); //选择外部XTAL为PLL源
CLK_MpllConfig(&stcMpllCfg);
/* flash read wait cycle setting */
EFM_Unlock();
EFM_SetLatency(EFM_LATENCY_5); //读等待5个周期
EFM_Lock();
/* Enable MPLL. */
CLK_MpllCmd(Enable);
/* Wait MPLL ready. */
while(Set != CLK_GetFlagStatus(ClkFlagMPLLRdy))
{
;
}
/* Switch system clock source to MPLL. */
CLK_SetSysClkSource(CLKSysSrcMPLL);
stc_sram_config_t stcSramCfg;
MEM_ZERO_STRUCT(stcSramCfg);
stcSramCfg.u8SramIdx = Sram12Idx | Sram3Idx | SramRetIdx;
stcSramCfg.enSramRC = SramCycle2;
stcSramCfg.enSramWC = SramCycle2;
SRAM_Init(&stcSramCfg);
}
其中hd_usb_udelay和hd_usb_mdelay两个延时函数我们也需要根据实际情况配置,我这边通过获取系统时钟函数获取当前时钟进行计算延时。
/**
*******************************************************************************
** \brief This function provides delay time in micro sec
** \param usec : Value of delay required in micro sec
** \retval None
******************************************************************************/
#define Fclk SystemCoreClock
void hd_usb_udelay(const uint32_t usec)
{
__IO uint32_t i;
uint32_t j = stcClkFreq.sysclkFreq / 1000000ul * usec;
for(i = 0ul; i < j; i++)
{
;
}
}
#include <rtdevice.h>
/**
*******************************************************************************
** \brief This function provides delay time in milli sec
** \param msec : Value of delay required in milli sec
** \retval None
******************************************************************************/
void hd_usb_mdelay (const uint32_t msec)
{
// SysTick_Delay(msec);
hd_usb_udelay(msec*1000);
}
usb_host_user.c
该文件主要是usb作为主机模式时应用程序部分可以根据实际功能添加实现,我这边只修改了host_user_msc_app函数让U盘初始化成功后挂载文件系统即可(USB_HOST_USER_AppState = USH_USR_FS_IDLE)
至此,基于RTthread U盘使用,USB驱动部分大概完成。
2.2 FatFs移植
fatFs是专为小型嵌入式设备开发的一个兼容微软fat的文件系统,采用ANSI C编写,采用抽象的硬件I/O层以及提供持续的维护,因此具有良好的硬件无关性以及可移植性。
FatFs官方网址 RT-Thread将FatFs整合为一个RT-Thread组件,并置于DFS层之下。因此可以非常方便的在RT-Thread中使用FatFs。
我们可以直接在RTT组件下把以上两个源文件添加到我们工程,目前RTT组件中FatFS的版本是R0.12b,如果有需要新版本可以直接去官网下载。FatFS源码说明可以参考这片内容,虽然版本有所差异但是大致一样 FatFs文件系统访问硬件主要通过disk_initialize、disk_status、disk_read、disk_write、disk_ioctl这五个函数实现,而华大官方驱动库中已帮我们实现这五个函数在USB host模式下,所以我们RTT中使用USB驱动可以直接调用usb_host_msc_fatfs.c里的这五个函数,屏蔽dfs_elm.c中对应函数。
最后,通过dfs_register注册FatFS文件系统到DFS下,这样我们就可以在控制台直接命令操作文件和直接使用,为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等,RTT虚拟文件系统详解DFS。
三、测试
在host_user_ms_app,挂载usb设备文件系统,注意USB正常连接会周期执行该轮询函数,其中`dfs_mount(0, "/", "elm", 0, 0)`第一个参数我们可以设置成 0,让RTT使用特殊驱动方式调用USB驱动而不通过USB设备框架。在串口终端内可以直接命令操作U盘,当然我们也可以统一的 POSIX 文件和目录操作接口:read、write、poll/select 等操作文件。