用STM32的高速AD和USB2.0做简易示波器

来源:本站
导读:目前正在解读《用STM32的高速AD和USB2.0做简易示波器》的相关信息,《用STM32的高速AD和USB2.0做简易示波器》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《用STM32的高速AD和USB2.0做简易示波器》的详细说明。
简介:总结下我的毕业设计,算是把自己的大学画个句号吧。
毕业设计的题目是“基于STM32的简易示波器设备驱动的研究”,就是利用STM32的AD采集波形数据,然后通过它内置的USB2.0把数据传到PC上面显示出来。

题目一共是两个人做,我和我搭档耗子。他做信号处理,采集部分。我做USB在PC下的驱动开发,以及上位机界面的显示。USB固件那部分我们是一起完成的。

这个课题从08年10月开始,到12月圣诞前夕基本完成。最后基本实现了波形的显示,实测最大可以测量的频率是200KHZ,

效果图如下:

用STM32的高速AD和USB2.0做简易示波器

30KHZ,1V正弦波(原文件名:效果图1.JPG)用STM32的高速AD和USB2.0做简易示波器

100KHZ,1V正弦波(原文件名:效果图2.JPG)用STM32的高速AD和USB2.0做简易示波器

方波的图形好像不是很漂亮会有上升沿老是有尖刺,还需要作软件做处理。没截出来,

最后能出数据离不开很多前辈的经验。有些地方是借鉴了他们的东西。现在先列出来。

第一个是21IC上面alien2006原帖地址。他也是做了一个简易示波器,但是用的是以太网传输。采集部分我很多借鉴了他的方案。

第二个是电脑圈圈,他对USB的理解令我钦佩。我能搞出驱动,他提供的源码包非常重要,有些代码也是直接在他的基础上修改的。

2整体方案

先说下一次完整的采集,比如外面进来的波形是正弦波,波形电压有正负,STM32单片机的AD只能采集0~3.6V的电压,所以要对信号进行处理。也就是需要一个模拟前端电路,把电压抬上去。接着就是采集了,STM32的AD可以用外设进行触发,这里用定时器进行触发。每过一个单位时间AD开启一次,采集一个点,这样采集的频率只要调整这个单位时间也就是定时器就可以控制了。采集了一桢数据,比如200个点。DMA中断被触发,开启USB,把数据发送到上位机,然后显示出来。

整个过程大体就是这样了,还有一个很重要的环节补充下。熟悉示波器的人都知道示波器有个触发概念。像刚才这样显示的话,比如前一帧数据是波峰开始显示,后一帧是波谷开始。这样显示出来的波形就是乱的,于是为了解决这个问题,就需要做触发,也就是保证每次采集的起始电位相同。我们的采用的是用外中断的形式,外面波形数据先不采集,先让它通过一个比较器,比如比较器的基准电压是1V,也就是每次都和1V比较,低于1V输出低电平,高于1V输出高电平。当数据电压大于1V的时候,比较器输出高电平,高电平接到单片机外中断口,这样外中断就被触发。然后开始采集,这样就能保证每帧数据的起始点都相同。

这个方案是大体方案,后来做了下修改。就是让AD一直在采集。外中断触发了后开启的是DMA,AD一直开着,只是控制DMA什么时候去取。从哪里开始取,取多少个点。下位机部分不再详说,贴下主要的程序,大侠们随便看看,多多指教。

定时器设置代码如下:

voidTIM2_Configuration(void)

{

TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;

TIM_OCInitTypeDefTIM_OCInitStructure;

TIM_DeInit(TIM2);//TIM2初始化

TIM_TimeBaseStructure.TIM_Period=18;//设置了下一个更新事件装入活动的自动重装载寄存器周期的值

TIM_TimeBaseStructure.TIM_Prescaler=0;//设置了用来作为TIMx时钟频率除数的预分频值

TIM_TimeBaseStructure.TIM_ClockDivision=0;//设置了时钟分割

TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//选择了计数器模式向上计数

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx

//TIM_PrescalerConfig(TIM2,1,TIM_PSCReloadMode_Update);//设置TIMx重载次数预分频值在更新事件装入

TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_Toggle;

TIM_OCInitStructure.TIM_Channel=TIM_Channel_2;

TIM_OCInitStructure.TIM_Pulse=9;

TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//TIM输出比较极性低

TIM_OCInit(TIM2,&TIM_OCInitStructure);

TIM_ARRPreloadConfig(TIM2,ENABLE);//使能或者失能TIMx在ARR上的自动装载寄存器

TIM_Cmd(TIM2,ENABLE);//使能TIMx外设

}

AD以及DMA设置代码:

voidADC_Configuration(void)

{

DMA_DeInit(DMA_Channel1);//复位DMA_Channel1

DMA_InitStructure.DMA_PeripheralBaseAddr=ADC1_DR_Address;//外围设备地址

DMA_InitStructure.DMA_MemoryBaseAddr=(u32)&ADC_Data[0];//memory地址

DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//外围设备做为源

DMA_InitStructure.DMA_BufferSize=1024;//数据单元尺寸

DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外围地址是否自动增长disable不增长

DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//memory是否自动增长

DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外围设备寄存器尺寸16位

DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//memory尺寸16位

DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//DMA循环模式

DMA_InitStructure.DMA_Priority=DMA_Priority_High;//DMA通道优先级

DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//memory-to-memory转换

DMA_Init(DMA_Channel1,&DMA_InitStructure);//初始化DMA通道1

DMA_ITConfig(DMA_Channel1,DMA_IT_TC,ENABLE);//使能DMA传输完成中断

ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//ADC工作模式ADC1ADC2单独工作

ADC_InitStructure.ADC_ScanConvMode=DISABLE;//多通道扫描模式

ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//是否启用连续转换模式

ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T2_CC2;//触发方式

ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐

ADC_InitStructure.ADC_NbrOfChannel=1;//ADC规则转换通道数量

ADC_Init(ADC1,&ADC_InitStructure);

//配置转换规则

ADC_RegularChannelConfig(ADC1,ADC_Channel_10,1,ADC_SampleTime_1Cycles5);

ADC_DMACmd(ADC1,ENABLE);

ADC_Cmd(ADC1,ENABLE);

ADC_ResetCalibration(ADC1);//复位ADC1校准

while(ADC_GetResetCalibrationStatus(ADC1));//等待复位完成

ADC_StartCalibration(ADC1);//开始ADC1校准转换

while(ADC_GetCalibrationStatus(ADC1));//等待转换完成

ADC_ExternalTrigConvCmd(ADC1,ENABLE);//使能或者失能ADCx的经外部触发启动转换功能

}

DMA中断服务程序:

voidDMAChannel1_IRQHandler(void)

{

DMA_Cmd(DMA_Channel1,DISABLE);//关闭DMA通道1

for(count=64;count<1024;count++)//AD数据转换处理

{

AD_Data[count]=(ADC_Data[count]*330)/4096;

}

flag_usb=1;//usb标志位置1,使能usb发送

DMA_ClearITPendingBit(DMA_IT_TC1);

}

下面就开始说我做的部分,USB部分。一共有3个地方,

(1)USB固件程序的开发

(2)WindowsXP下USB设备驱动的开发

(3)对应的PC上的应用程序开发。

下面来详细论述。

3USB固件程序的开发

这部分主要设计USB的协议。一共花了两个星期时间,是我们做的最开心的两个星期。因为是两个人一起来研究,导师崔也经常跑过来讨论。最后测出来的传输速度是700-800K,依据USB2.0全速理论最大速度12Mbps,除以八大概是1M多,基本把速度都开发出来了。期间参考了ouravr里面的一个叫极速狂飙的帖子,也成功开启了双缓冲。

固件程序主要是根据万利板子提供的4个USB程序中的那个USB转串程序修改的,主要是修改两个文件。第一个是usb_desc.c,这个文件里面配置了全部的描述符。这些描述符在USB2.0协议里面都有严格的定义,跟着配置就好了。

constu8Virtual_Com_Port_DeviceDescriptor[]=

{

0x12,

USB_DEVICE_DESCRIPTOR_TYPE,

0x00,

0x02,

0xFF,

0xFF,

0x00,

0x40,

0x44,

0x44,

0x33,

0x33,

0x00,

0x02,

1,

2,

3,

0x01

};

constu8Virtual_Com_Port_ConfigDescriptor[]=

{

0x09,

USB_CONFIGURATION_DESCRIPTOR_TYPE,

VIRTUAL_COM_PORT_SIZ_CONFIG_DESC,

0x00,

0x01,

0x01,

0x00,

0xC0,

0x00,

0x09,

USB_INTERFACE_DESCRIPTOR_TYPE,

0x00,

0x00,

0x02,

0xFF,

0xFF,

0x00,

0x00,

0x07,

USB_ENDPOINT_DESCRIPTOR_TYPE,

0x03,

0x02,

0x02,

0x00,

0x00,

0x07,

USB_ENDPOINT_DESCRIPTOR_TYPE,

0x81,

0x02,

0x40,

0x00,

0x00

};

constu8Virtual_Com_Port_StringLangID[VIRTUAL_COM_PORT_SIZ_STRING_LANGID]=

{

VIRTUAL_COM_PORT_SIZ_STRING_LANGID,

USB_STRING_DESCRIPTOR_TYPE,

0x09,

0x04

};

constu8Virtual_Com_Port_StringVendor[VIRTUAL_COM_PORT_SIZ_STRING_VENDOR]=

{

VIRTUAL_COM_PORT_SIZ_STRING_VENDOR,

USB_STRING_DESCRIPTOR_TYPE,

'S',0,'T',0,'M',0,'i',0,'c',0,'r',0,'o',0,'e',0,

'l',0,'e',0,'c',0,'t',0,'r',0,'o',0,'n',0,'i',0,

'c',0,'s',0

};

constu8Virtual_Com_Port_StringProduct[VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT]=

{

VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT,

USB_STRING_DESCRIPTOR_TYPE,

'N',0,'u',0,'m',0,'',0,'s',0,'e',0,'n',0,'d',0,

'',0,'t',0,'e',0,'s',0,'t',0,'',0

};

constu8Virtual_Com_Port_StringSerial[VIRTUAL_COM_PORT_SIZ_STRING_SERIAL]=

{

VIRTUAL_COM_PORT_SIZ_STRING_SERIAL,

USB_STRING_DESCRIPTOR_TYPE,

'D',0,'e',0,'m',0,'o',0,'',0,'1',0,'.',0,'0',0,

'0',0,'0',0

}

有些字符没有修改还是原来DEMO的,大家将就着看看。

还有就是Usb_prop.c里面这个函数

voidVirtual_Com_Port_Reset(void)//USBIP复位过程,当宏单元收到RESET信号时调用,

{//用户程序在此过程中设置端点

pInformation->Current_Configuration=0;

pInformation->Current_Interface=0;

SetBTABLE(BTABLE_ADDRESS);

SetEPType(ENDP0,EP_CONTROL);

SetEPTxStatus(ENDP0,EP_TX_STALL);

SetEPRxAddr(ENDP0,ENDP0_RXADDR);

SetEPTxAddr(ENDP0,ENDP0_TXADDR);

Clear_Status_Out(ENDP0);

SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);

SetEPRxValid(ENDP0);

SetEPType(ENDP1,EP_BULK);//设置端点1传输模式批量传输

SetEPTxAddr(ENDP1,ENDP1_TXADDR);//缓冲区基地址(IN发)

SetEPTxCount(ENDP1,64);//配置Tx缓冲计数器

SetEPTxStatus(ENDP1,EP_TX_VALID);//设置端点发送有效

SetEPRxStatus(ENDP1,EP_RX_DIS);//设置端点接收关闭

SetEPType(ENDP3,EP_BULK);//设置端点3传输模式批量传输

SetEPRxAddr(ENDP3,ENDP3_RXADDR);//缓冲区基地址(OUT收)

SetEPRxCount(ENDP3,2);//配置Rx缓冲计数器

SetEPRxStatus(ENDP3,EP_RX_VALID);//设置端点接收有效

SetEPTxStatus(ENDP3,EP_TX_DIS);//端点关闭发送

SetDeviceAddress(0);

}要配置下。就是你开启的那些端点了。

4WindowsXP下USB设备驱动的开发

这部分是最麻烦的,至少对我来说是这样的,好像已经到了计算机专业范畴,而我对这些非常不熟悉。学习期间第一个方案用的是LABVIEW,它附带一个工具叫VISA,可以直接配置成USB驱动,然后再在LABVIEW里面直接调用,很方便。在研究LABVIEW的时候发现里面有很多DEMO,而且对中文的支持非常好,很多文档都是官方直接出中文文档。(好像半导体大额们对中国这块市场都越来越重视了,前几天看到AVR大部分的文档也都是直接官方翻译的中文文档。)学习了软件里面附带的入门教程,大概也能编一些简单的程序了,调试USB的时候发现里面有几个USB的demo,配合VISA生成的驱动居然直接接收到了下面传上来的数据,这对我们来说无异于是一次莫大的鼓励。后来导师推荐我试试另一种方法,就是用Driverstudio+VC来开发驱动和编写界面。为了学习一下USB的驱动开发,于是在第三个星期的时候开始windows下驱动的开发。老实说吃了很多苦头,到现在虽然功能基本实现了,但是对于这一块我还是只知道皮毛。DS里面的那些类和函数也没怎么搞透,驱动能开发出来主要还是借鉴了DS附带的example里面的USBBULK程序。

下面大概讲下我遇到过的问题给后来人一点借鉴:

(1)是安装顺序的问题,因为DriverStudio要嵌入到VC中,以后的代码要用DDK来编写所以三个软件要很好的兼容才可以。

第一步:安装MicrosoftVisualC++6.0;

第二步:安装MicrosoftWindowsXPDDK;

第三步:安装DriverStudio3.2驱动程序开发工具包。

VC6.0最好安装英文版的,可以减少不知名的错误。安装完成后DS3.2会嵌入到VC中,在上面多一个标题:

DriverStudio选项卡下面第三项DDKBuildsetting要设置成C:WINDDK2600(如果DDK安装在C盘),然后要编译DriverStudio安装目录下DriverStudioDriverWorkssourceVdwLibs.dsw,以得到vdw_wdm.lib这个库文件。编译的时候会出现错误,因为用VC打开vdwlibs.dsw工程文件后,有两个工程,要先将VdwLibs工程设为当前ActiveProject,然后在工具栏上单击右键选择“组建”,在弹出的编译工具栏中配置一下编译平台的设置:选择Win32WDMChecked平台(因为我们用的XP),然后编译就应该可以了。

(2)是利用向导生成驱动程序框架

DS有个DriverWizard可以生成驱动程序的大体框架。

第一步:选择开发环境VC6.0,命名。

第二步:选择WDMDriver不用修改。

第三步:选择WDMFunctionDriver

第四步:选择USB并设置好PID和VID要与固件里面的设置相同

第五步:设置端点,以及缓冲区。也要和固件里面相对应。

接下午几步可以根据自己来设置,也可以默认。完成后会生成一个工程目录,里面包含了两个工程,一个是驱动,是我们需要的。另一个是测试程序,好像没有用。当你编译驱动的时候,会提示有错误,这是因为DS里面的一个BUG,选择project-setting,左边两个工程选驱动那个,点右边LINK选项卡,删除Object/librarymodule项的ntstrsafe.lib。再编译就能通过了。

(3)接下去就是对刚才生成的框架进行研究,添加一些代码以使驱动完整。

首先来了解下生成的框架。

DriverEntry()该例程是当系统检测到与驱动程序支持的设备时被调用的。

AddDevice()该例程为系统添加一个设备。

Unload()该例程为系统卸载设备。

以上几个例程都存在于XXXXDriver.cpp是设备的基本操作可以直接使用生成的代码,不用修改。

在另一个文件XXXXDevice.cpp中包含了具体应用函数,比如read和write,但是这个文件也存在这一个BUG,在开头部分有m_Pipe1_in.Initialize(m_Lower,0x81,64);和m_Pipe2_out.Initialize(m_Lower,3,64);本次定义了两个端点,一个是叫m_Pipe1_in,0x81表示这个管道的属性是入(in),缓冲区是64.下面一个是m_Pipe2_out表示是出(out),缓冲区是64.向导生成的代码里面漏掉了“0x”这两个字符。

关键部分代码的添加

上位机应用程序,主要是通过两种方式来控制驱动,一个是DeviceIoControl()函数,另一个是ReadFile()函数和WriteFile()函数。本次使用的是后者。只要在Read(KIrpI),和Write(KIrpI)里面添加相应的代码就可以。这方面可以参考DS3.2附带的example里面的那个usbbulk例程。

首先简述一下完成一次驱动调用所要做的具体工作。应用程序想对USB设备进行I/O操作,它需调用WindowsAPI函数比如readfile(),I/O管理器将此请求构造成一个合适的I/O请求包(IRP)并把它传递给USB设备驱动程序。USB设备驱动程序接收到这个IRP后,根据IPR中包含的具体操作代码构造相应USB请求块(URB),并把它放到一个新的IRP中,然后传递给USB底层驱动程序(中间层或总线驱动程序)。USB底层驱动程序根据IRP中所含的URB执行相应的操作,并把操作的结果返回给USB设备驱动程序。USB设备驱动程序接收到此返回的IRP后,将操作结果通过IRP返还给I/O管理器,最后I/O管理器将此IRP操作结果传回给应用程序,至此应用程序对设备的一次I/O操作完成。当上位机调用的ReadFile()的时候,USB设备驱动程序要根据IPR中包含的具体操作代码构造相应USB请求块(URB),这个URB的生成就在这里实现,比如例程里面的

PURBpUrb=m_Pipe1_in.BuildBulkTransfer(

Mem,//Whereisdatacomingfrom?

dwTotalSize,//Howmuchdatatoread?

TRUE,//direction(TRUE=IN)

NULL//LinktonextURB

);

主要是通过这个函数来实现。函数实现了之后会有不同的返回值,然后打印出不同的信息。这些信息可以在DS调试工具Monitor中看到。

5驱动开发的过程大概就是这样了。下面是应用程序的开发。主要两步,先打开设备,然后读写数据。

(1)打开设备

Windows下面有很多针对驱动调用的API函数,要调用一个USB设备首先就要打开这个设备,其对应的API函数为CreateFile,在本次驱动中DS自动生成了一个OpenByInterface.c文件,在该文件里面对这个CreateFile函数进行了封装,其参数如下

HANDLEOpenByInterface(

GUID*pClassGuid,//pointstotheGUIDthatidentifiestheinterfaceclass

DWORDinstance,//specifieswhichinstanceoftheenumerateddevicestoopen

PDWORDpError//addressofvariabletoreceiveerrorstatus

)

所以在主程序中只要调用OpenByInterface函数就可以了。本次中具体实现的代码如下:

if(g_hUsbDevice==INVALID_HANDLE_VALUE)

{

g_hUsbDevice=OpenByInterface(

&g_UsbGuid,//pointstotheGUIDthatidentifiestheinterfaceclass

0,//specifieswhichinstanceoftheenumerateddevicestoopen

&Error//addressofvariabletoreceiveerrorstatus

);

if(g_hUsbDevice==INVALID_HANDLE_VALUE)

{

MessageBox("打开设备失败!",NULL,MB_OK|MB_ICONHAND);

}

else

{

MessageBox("打开设备成功!",NULL,MB_OK|MB_ICONASTERISK);

}

}

其中GUID*pClassGuid是对应的设备的GUID,具体定义如下:

GUIDg_UsbGuid=GUID_DEVINTERFACE_USB;//打开设备的GUID

其中GUID_DEVINTERFACE_USB是在DS生成的Intrface.h文件中定义的,

#defineGUID_DEVINTERFACE_USB

{0x6C8CFFA6,0xCAB8,0x45B1,{0xAA,0xEE,0x1E,0xF4,0xDF,0x79,0xF8,0xDF}}

这个ID保证了每个USB驱动的唯一性,调用紊乱的情况的出现。OpenByInterface这个函数有一个返回值,通过的改变可以确认设备是否打开成功,然后通过MessageBox函数跳出不同的反馈信息。

(2)读写数据

Windows中读取数据的API函数有两种,一种是DeviceIoControl,一种是ReadFile、WriteFile函数。前者一个函数可以读也可以写,后者把读写分开来,这次使用的是后者。

ReadFile的具体实现如下:

ReadFile(

g_hUsbDevice,//我们的设备HANDLEhFile

DataBuffer,//输入缓冲,无lpBuffer

64,//输出字节数nNumberOfBytesToRead,

&BytesReturned,//实际读取到的字节数lpNumberOfBytesRead,

NULL

)

g_hUsbDevice是对应我们的设备,DataBuffer是我们定义的数组,读取到的数据就存在这个数组中,64是一次读取的字节数,BytesReturned是实际读取到的字节数,最后一个可以默认为NULL。如果成功读取到了数据,数组DataBuffer中的数据就会更新,然后可以做任意处理。

写数据基本和读取数据类似。

提醒:《用STM32的高速AD和USB2.0做简易示波器》最后刷新时间 2024-03-14 01:18:50,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《用STM32的高速AD和USB2.0做简易示波器》该内容的真实性请自行鉴别。