11

上海航芯 · 2022年01月11日

干货分享 | 基于SPI Flash的U盘程序,从STM32F103到ACM32F403

前言

本项目是以SPI Flash(如W25Q128等)存储元件作为存储单元,MCU主控完成USB接口通信并根据SCSI协议实现U盘功能。其结构如下图所示:

 title=

SPI Flash部分移植

SPI功能部分相对简单,ACM32F403的接口引脚和STM32F103的相同,可直接对接,按照ACM32F403的说明对SPI接口进行初始化,并对底层读写函数进行更改即可。

USB部分移植

1. STM32F103代码结构

在ST的芯片上,USB的数据是由两个中断,USB\_LP\_CAN1\_RX0\_IRQHandler和USB\_HP\_CAN1\_TX\_IRQHandler来进行,其中高优先级中断(USB\_HP\_CAN1\_TX\_IRQHandler)用于处理同步(Isochronous)模式传输或双缓冲块(Bulk)传输模式下的正确传输事件,而低优先级中断(USB\_LP\_CAN1\_RX0\_IRQHandler)用于处理其他传输时间。ST的USB数据处理如下图所示:

 title=

由于USBFS协议的限制,一包数据中最多可携带64字节数据,因此,当存在大量数据需要进行传输(IN或OUT包)时,需要分批次进行传输。在ST的代码中,通过变量“Bot\_State”来进行控制,以Read10指令为例,其读数据流程可如下图所示:

 title=

需要注意的是,Read10指令解析完成之后(即上图左侧流程图)则进入数据传输阶段,此阶段是通过多次进入USB高优先级中断中,调用Read\_Memory();来实现的。Read\_Memory();函数内每次传输64字节数据。

2. ACM32F403代码移植要点

本文基于上海航芯官方USB例程进行移植,移植后的程序结构如下图所示:

 title=

ACM32F403的USB是采用一个中断来进行数据处理。在官方例程中,USB的中断函数内判定接收数据类型,包括suspend,resume,reset,EP0\_pack以及其他端点的接收数据。判定结束后,会调用USB\_Monitor();函数来处理suspend,resume,reset以及EP0\_pack数据。而其他端点数据会在usb\_transfer\_monitor();函数中进行解析,该函数由客户调用,一般在主函数的死循环中进行处理。在本文的移植中,主要需对USB的端点数据进行处理。

A. EP0\_Pack

EP0接收的setup数据会被存放在SETIP\_0\_3\_DATA和SETIP\_4\_7\_DATA寄存器中 ,数据结构如下所示:

`dev_req.bmRequestType=USBCTRL->SETIP_0_3_DATA &0xff;    

dev_req.bRequest=(USBCTRL->SETIP_0_3_DATA>>8)&0xff;

dev_req.wValue=(USBCTRL->SETIP_0_3_DATA>>16)&0xffff;

dev_req.wIndex   = USBCTRL->SETIP_4_7_DATA&0xffff;

dev_req.wLength=(USBCTRL->SETIP_4_7_DATA>>16)&0xffff;  `

该部分解析,可由用户在函数void usb\_control\_transfer(void)中添加需要的处理函数。该函数由航芯官方例程里提供。在做U Disk程序移植时,需添加GetMaxLun和Storage\_Reset处理函数,如下图所示:

 title=

B. EP1\_Pack

在本文所述的代码中,ACM32F403采用EP1完成数据的收发工作。主要是完成对SCSI协议的解析工作。移植过程中,需要文件mass\_mal.c、memory.c、scsi\_data.c、usb\_scsi.c、usb\_bot.c及其头文件。本段主要就上述文件中代码需要改动的地方进行说明,部分参数需要重新定义,读者可自行解决。下表列出了ST和Aisino的USB收发功能函数,该部分移植时需要修改的主要部分:

 title=

a. void Mass\_Storage\_In (void)

在ST的工程代码中该部分主要用于处理SCSI的读指令。由于全速USB一包数据最大支持64字节,因此,当需要传输的数据个数大于该数值时,则需要分包传输。在使用ACM32F403时,可直接传送需要的数据长度,内部会进行分包处理,因此,该函数可省略。

b. void Mass\_Storage\_Out (void)

该函数用于处理SCSI指令解析以及发送指令,需在usb\_transfer\_monitor()中调用,并将函数内部的接收数据部分更改为:

“Data_Len = HAL_FSUSB_Receive_Data(Bulk_Data_Buff, 64, out_ep_index, 1);”

c.void Transfer_Data_Request(uint8_t* Data_Pointer, uint16_t Data_Len)

将USB发送函数更改为ACM32F403对应的发送函数。在ST的工程中,该函数用于传输完数据后,进入BOT\_DATA\_IN\_LAST状态,并在下一次的Mass\_Storage\_In()函数调用时,回复CSW指令。而本文的移植代码中,省略了Mass\_Storage\_In()函数,因此,可在该函数的尾部增加CSW发送指令:

Set_CSW (CSW_CMD_PASSED, SEND_CSW_ENABLE);

d.void Set\_CSW (uint8\_t CSW\_Status, uint8\_t Send\_Permission)

将USB发送函数更改为ACM32F403对应的发送函数。

e.void Bot\_Abort(uint8\_t Direction)

该函数主要对收发端点的STALL状态进行处理,在ACM32F403的收发库函数中,对端点的STALL已做出相应控制,因此,该函数可省略。

f.void Read\_Memory(uint8\_t lun, uint32\_t Memory\_Offset, uint32\_t Transfer\_Length)

Read\_Memory函数用于收到PC端的IN包请求后将存储器中的数据读取并发送至PC端。而ACM32F403的USB发送库函数中,自行进行分包操作(一包最大数据为64字节),因此在数据缓冲区容量允许条件下,可直接发送完毕,该函数修改如下:

{

   uint32_t Offset, Length;

   Offset = Memory_Offset * Mass_Block_Size[lun];

   Length = Transfer_Length * Mass_Block_Size[lun];

   CSW.dDataResidue = CBW.dDataLength;

   while(Transfer_Length --)

   {
          MAL_Read(lun ,
                           Offset ,
                           Data_Buffer,
                           Mass_Block_Size[lun]);
          Length = min(Mass_Block_Size[lun], CSW.dDataResidue);
          Offset += Mass_Block_Size[lun];
          HAL_FSUSB_Send_Data((uint8_t *)(Data_Buffer), Length, in_ep_index);

          CSW.dDataResidue -= Length;
   }

Set_CSW (CSW_CMD_PASSED, SEND_CSW_ENABLE);

}

g.void Write\_Memory (uint8\_t lun, uint32\_t Memory\_Offset, uint32\_t Transfer\_Length)

写数据指令完成后,将Bot\_State 值更改为 BOT\_IDLE。ST的工程代码中,变量“Bot\_State”收发状态机的状态值,其值如下表所示:

 title=

而基于ACM32F403的U Disk工程,IN包可由函数HAL\_FSUSB\_Send\_Data()在其内部进行分包处理,不需要额外逻辑,因此,移植后Bot\_State仅需要在BOT\_IDLE、BOT\_DATA\_OUT、BOT\_ERROR之间转换,其他对Bot\_State的控制可省略。

如需更多咨询,请联系:marketing@aisinochip.com

推荐阅读
关注数
2851
内容数
51
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息