自定义USB BULK设计(一)——固件程序,LPC2378

来源:互联网 发布:nba2kol安德森数据 编辑:程序博客网 时间:2024/04/28 22:07

    前段时间买了块D12+51的开发板来玩,现在公司要求用ARM来做一个USB项目。原来用D12也就做了个LED流水灯,还是HID设备的,现在不能满足要求,你总不能显示公司的设备时,还搞个“人体工学设备”。于是把开发流程又理了一遍,这里是第一部分,讲固件的编写。

 

    使用的MCU是NXP的ARM,LPC2378,自带了USB模块,关于2378的寄存器定义什么的,大家去看手册,ZLG网上还有中文版的下。

 

    固件编写其实不用从头开始,我一般是拿官网的例子改改,就变成自己的代码了,这个USB的也是一样,X:/Keil/ARM/Boards/Keil/MCB2300 路径下有多个2300开发板的例子,我拿USBMem的例子改了一下,因为一会我要用的是BULK传输。

    1、打开/USBMem下的Memory.Uv2,查看项目的target Option ,你会发现是LPC2368,这个可以改为你想要的芯片类型,只要USB模块相同的系列都行。

    2、查看下项目文件列表,如图1所示,删除不需要的文件,增加main.c和USB_trans.c 2个文件,如图2所示

         

图1                                                                 图2

 

usbcfg.h——usb配置文件

usbcore.c——USB内核文件

usbdesc.c——USB Descriptors

usbhw.c——USB Hardware Layer Module for Philips LPC23xx/24xx

usbuser.c——USB Custom User Module

 

2.代码修改

(1)usbcfg.h :

这个文件是定义USB相关代码的宏,其他文件根据这些宏来判断某个函数或某个代码段是否需要编译,这样可以最小的减少代码量。

我只定义了以下事件(函数),也可以根据设计需要定义

#define USB_POWER_EVENT     0
#define USB_RESET_EVENT     1
#define USB_SUSPEND_EVENT   0
#define USB_RESUME_EVENT    0
#define USB_WAKEUP_EVENT    0
#define USB_SOF_EVENT       0
#define USB_ERROR_EVENT     0
#define USB_EP_EVENT        0x0005 //该宏使能了2个端口处理函数,USB_EndPoint0和USB_EndPoint2,

#define USB_CONFIGURE_EVENT 1
#define USB_INTERFACE_EVENT 0
#define USB_FEATURE_EVENT   0

 

以下宏全部设为0,因为我们是自定义的设备,不是以下类别

#define USB_CLASS          0

#define USB_HID             0
#define USB_HID_IF_NUM      0
#define USB_MSC             0
#define USB_MSC_IF_NUM      0
#define USB_AUDIO           0
#define USB_ADC_CIF_NUM     0
#define USB_ADC_SIF1_NUM    0
#define USB_ADC_SIF2_NUM    0
#define USB_CDC     0
#define USB_CDC_CIF_NUM     0
#define USB_CDC_DIF_NUM     0
#define USB_CDC_BUFSIZE     0

(2)usbcore.c——USB内核文件

这个文件不用做任何修改,它包含了一个0端点函数(控制端点)USB_EndPoint0,用于响应控制传输,其他端点的响应函数都放在usbuser.c中。

 

(3)usbdesc.c

这里放置的是各种描述符,你可以根据需要,改为你自己的设备描述,至于描述符的定义和写法,参考USB协议(1.0和2.0都可以)的第9章。

 

(4)usbhw.c

这个文件的函数都与硬件相关,就是和MCU的USB模块有关,包括寄存器初始化,读写缓冲区,中断函数等,

USB_Init配置了USB寄存器,因为这个工程本来就只使能了端口2并使用BULK传输,因此你什么都不用改,如果你想讲其他端口配置为BULK或其他,你就对着手册自己改了。

个人觉得它的ISR那里虽然通用性强但是写得有点啰嗦,自己改了一下,因为只用到端口0和端口2(这里的端口指的是逻辑端点,就是一个逻辑端点有2个物理端点),因此ISR的代码改为如下:

void USB_ISR (void) __irq {
  U32 disr, val, n, m;
  U32 episr, episrCur;

  disr = DEV_INT_STAT;                      /* Device Interrupt Status 获得设备中断状态*/

  /* Device Status Interrupt (Reset, Connect change, Suspend/Resume) */
  //如果是总线状态改变:包括设备复位,连接改变,挂起/唤醒
  if (disr & DEV_STAT_INT) {
    DEV_INT_CLR = DEV_STAT_INT;//清该中断标志
    WrCmd(CMD_GET_DEV_STAT);   //发送获得设备状态命令
    val = RdCmdDat(DAT_GET_DEV_STAT);       /* Device Status */
    if (val & DEV_RST) {                    /* Reset */
      USB_Reset();
      USB_Reset_Event();
    }
    goto isr_end;  //不允许同时2个中断置位
  }

  /* Endpoint's Slow Interrupt */
  if (disr & EP_SLOW_INT) {
    episr    = EP_INT_STAT;  //获得端点中断状态


 if(episr & EP0RX)//端口0 OUT
 {
     EP_INT_CLR = 1; //清端点中断,相当于发送了清端点中断指令
     while ((DEV_INT_STAT & CDFULL_INT) == 0); //判断命令寄存器是否可读出数据
     val = CMD_DATA;//获得选择端点寄存器的内容

        if (val & EP_SEL_STP) {         // 如果所选端点上一次接收到的包为 SETUP 包
            USB_P_EP[0](USB_EVT_SETUP); // 调用函数USB_EndPoint0
        }
  else
  {
      USB_P_EP[0](USB_EVT_OUT);
  }

 }
  else if(episr & EP2RX)//端口2 OUT
 {
     EP_INT_CLR = 0x10; //清端点中断,相当于发送了清端点中断指令
     while ((DEV_INT_STAT & CDFULL_INT) == 0); //判断命令寄存器是否可读出数据

        USB_P_EP[2](USB_EVT_OUT);
 }

 

isr_end:
  VICVectAddr = 0;                          /* Acknowledge Interrupt */
}

 

有人可能奇怪,为什么没有端口0和端口2的IN处理,呵呵,这是USB传输的关键所在,如果希望从上位机读取设备的数据,

程序设计者必须自定义帧格式或帧协议,比如自定义一个帧,它的第一个字节为命令字节,如果该字节为0x11,则表示上位机是请求接收数据,如果是其他,则表示上位机是其他操作。

 

上位机发送OUT帧(writeFile)—>USB发生端口OUT中断->固件程序检测OUT包的第1个字节是否为0x11,如果是,则将RAM缓冲区的数据写到USB硬件缓冲区,然后发送->上位机再执行一个ReadFile函数,就把在PC缓冲区的数据读出来了。

(5)usbuser.c

这个文件定义了很多端口处理函数。里面有个很酷的写法:

#define P_EP(n) ((USB_EP_EVENT & (1 << (n))) ? USB_EndPoint##n : NULL)

/* USB Endpoint Events Callback Pointers */
void (* const USB_P_EP[16]) (U32 event) = {
  P_EP(0),
  P_EP(1),
  P_EP(2),
  P_EP(3),
  P_EP(4),
  P_EP(5),
  P_EP(6),
  P_EP(7),
  P_EP(8),
  P_EP(9),
  P_EP(10),
  P_EP(11),
  P_EP(12),
  P_EP(13),
  P_EP(14),
  P_EP(15),
};

 

##符号会连接两个符号,从而产生新的符号(词法层次)。

就是说,你调用USB_P_EP[2] ,就是调用函数USB_EndPoint2,而且还可以带参数。

USB_P_EP是一个只读型指针数组,其每个元素都是一个指向函数的指针。

 

由于我只用到端口2,将USB_EndPoint2 函数改写如下

void USB_EndPoint2 (U32 event) {

      
      USB_BulkOut();
   }

 

(6)

usb_trans.h中

 

#define EP_BULK_IN       0x82
#define EP_BULK_OUT      0x02


usb_trans.c中

 

void USB_BulkOut(void)
{
     U8 BulkLen;
  BulkLen = USB_ReadEP(EP_BULK_OUT, usbWBuffer);//读端口2的OUT端点数据
  if(usbWBuffer[0]==0x11)//如果是收到上位机读请求,则将数据放到端口2的IN端点,并发送
  { USB_WriteEP(EP_BULK_IN, usbRBuffer, 64);}

   

}

(7)

main函数中,只要编写以下代码,固件就编写完了。

  USB_Init();
  USB_Connect(__TRUE);                      /* USB Connect */