本帖最后由 1223657347 于 2017-11-25 13:46 编辑
2.1固件
固件核心结构如下图
固件设计难点和要点主要有以下几个方面: (1)USB枚举和通信。USB设备在接入主机时,主机通过总线枚举(Bus Enumeration)获取设备描述、设备配置。USB端点(Endpoint)是USB通信的管道,有输入事务(IN Transaction)和输出事务(OUT Transaction)。数据采集设备就通过IN端点向主机传输数据。 (2)定时采样和存储。输入采样是数据采集设备的基本功能,定时采样的间隔即为采样率。采样结果需要缓冲暂存,再通过USB集中发送。 (3)采样控制。数据采集设备的开始、停止采样需要可控,同时可设定的采样率也是必须的。 USB枚举和通信是固件最难的部分,但ST公司提供的STM32Cube USB库可以使这部分的设计变得非常容易。用户只需要对设备描述符(Device Descriptor)、配置描述符(Configuration Descriptor)和接口描述符(Interface Descriptor)修改,USB枚举过程完全由HAL库和STM32CubeUSB库完成。由于数据采集设备不是标准USB设备,因此设备描述符中设备类字段为自定义USB设备;接口描述符中向USB主机报告了所使用的端点及端点配置,本数据采集设备使用批量(Bulk)端点传输数据。 模拟/数字输入定时采样和存储需要STM32的多个外设进行配合,包含了ADC,定时器、DMA(Direct Memory Access,直接内存存取)。定时器溢出事件触发ADC转换,ADC转换完成的结果由DMA进行搬运,该流程中不消耗CPU资源,减弱对CPU搬运数据的依赖。4K点的环形缓冲区使用了片上SRAM(实际占用64Kb),通过修改DMA目标地址(写指针),可以依次放置ADC序列采样结果到缓冲区去。在读指针和写指针的指示下,USB端点读取缓冲区数据不会发生阻塞和覆盖。 数据采集设备的控制采用多级指令的方式,将端点2传输数据解码/编码后得到指令包,指令包分为命令包和响应包。命令包又细分为控制类、改写类、读取类,响应包细分为无数据响应和有数据响应。为了优化采样启动和停止速度,相应的命令包取消了响应机制。USB主机在启动采样之后,还需要对缓冲区进行预读,防止非法的指针访问和批量输入事务阻塞。 下面展示并说明部分核心固件代码:
static uint8_t DAQ_Itf_DataRxCallBack(uint8_t* pbuff, uint16_t length)
{
USBD_DAQ_HandleTypedef *hdaq = (USBD_DAQ_HandleTypedef*)hdaq_temp->pClassData;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_SET);
/* Recall Functions Here */
if(daq_cfg.SampleIsEN == DAQ_SAMPLE_ON)
{
if(daq_cfg.Mode == DAQ_MODE_CONTI)
{
//Out Pack Write Pointer Manage
hdaq_io.Out_wPointer += length / DAQ_DATA_OUT_PACK_SIZE;
if(hdaq_io.Out_wPointer >= DAQ_DATA_OUT_DEPTH)
{
hdaq_io.Out_wPointer = 0;
}
}
else if(daq_cfg.Mode == DAQ_MODE_BURST)
{
hdaq_io.Out_wPointer = 0;
if(*(hdaq_io.Out_Buff + hdaq_io.Out_wPointer * DAQ_DATA_OUT_PACK_SIZE) == DAQ_DATA_OUT_SOH)
{
if(daq_cfg.AO_IsEN == DAQ_ENABLE)
{
//AO0 Updata
DAC->DHR12R1 = *(uint16_t*)(hdaq_io.Out_Buff + hdaq_io.Out_wPointer * DAQ_DATA_OUT_PACK_SIZE + 2);
}
if(daq_cfg.DO_IsEN == DAQ_ENABLE)
{
//DO Channel Updata
DO0_GPIO_Port->ODR = (DO0_GPIO_Port->ODR & 0x00FF) |
((*(hdaq_io.Out_Buff + hdaq_io.Out_wPointer * DAQ_DATA_OUT_PACK_SIZE + 1)) << 8);
}
}
DAQ_Itf_DataTxPack(hdaq_io.In_Buff, DAQ_DATA_IN_PACK_SIZE);
}
}
USBD_DAQ_SetDataRxBuffer(hdaq_temp, hdaq_io.Out_Buff + hdaq_io.Out_wPointer * DAQ_DATA_OUT_PACK_SIZE);
USBD_LL_PrepareReceive(hdaq_temp, DAQ_DATA_EPOUT_ADDR, hdaq->DataRxBuffer, DAQ_DATA_EPOUT_SIZE);
// HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET);
return USBD_OK;
}
static uint8_t DAQ_Itf_CtlRxCallBack(uint8_t* pbuff, uint16_t length)
{
USBD_DAQ_HandleTypedef *hdaq = (USBD_DAQ_HandleTypedef*)hdaq_temp->pClassData;
uint8_t ack_rs = DAQ_ACK_FAIL;
/* Recall Functions Here */
if(*pbuff == length)
{
hdaq_ctl.Out_Buff = pbuff;
DAQ_CTL_PackDeCode(&hdaq_ctl);
if(hdaq_ctl.Out_Pack.bType == DAQ_CMD)
{
switch(hdaq_ctl.Out_Pack.data[0])
{
case DAQ_CMD_CTL: //*(data) -> level 0 Command
switch(hdaq_ctl.Out_Pack.data[1])
{
case DAQ_CFG: //NID ACK
switch(hdaq_ctl.Out_Pack.data[2])
{
case DAQ_AI:
daq_cfg.AI_IsEN = hdaq_ctl.Out_Pack.data[3];
break;
case DAQ_DI:
daq_cfg.DI_IsEN = hdaq_ctl.Out_Pack.data[3];
break;
case DAQ_AO:
daq_cfg.AO_IsEN = hdaq_ctl.Out_Pack.data[3];
break;
case DAQ_DO:
daq_cfg.DO_IsEN = hdaq_ctl.Out_Pack.data[3];
break;
default:
goto ACK_NONE;
}
/* DAQ Parts Manage Callback here */
DAQ_CMD_PartsEN(&daq_cfg);
/* ACK */
ack_rs = DAQ_ACK_OK;
goto ACK_MNG;
case DAQ_MODE: //NID ACK
/*************DAQ Running Mode************
* DAQ_MODE_BURST
* DAQ_MODE_CONTI
**/
daq_cfg.Mode = hdaq_ctl.Out_Pack.data[2];
/* DAQ running mode Callback here */
if((daq_cfg.Mode == DAQ_MODE_BURST) || (daq_cfg.Mode == DAQ_MODE_CONTI))
{
ack_rs = DAQ_ACK_OK;
}
else
{
ack_rs = DAQ_ACK_FAIL;
}
/* ACK */
goto ACK_MNG;
case DAQ_SAMPLE: //NO ACK
daq_cfg.SampleIsEN = hdaq_ctl.Out_Pack.data[2];
/* DAQ Sample Start or Stop Callback here */
ack_rs = DAQ_CMD_RunStop(&daq_cfg, &hdaq_io);
goto ACK_MNG;
default:
goto ACK_NONE;
}
case DAQ_CMD_ASIGN:
switch(hdaq_ctl.Out_Pack.data[1])
{
case DAQ_AI_CH_CFG:
/***********AI Channel CFG Msg*************
* Channelx = hdaq_ctl.Out_Pack.data[2]
* State = hdaq_ctl.Out_Pack.data[3]
* Index = hdaq_ctl.Out_Pack.data[4]
**/
/* DAQ AI Channel Config Callback here */
ack_rs = DAQ_CMD_AI_CH_CFG_Callback(&hdaq_ctl, &daq_cfg);
/* ACK */
goto ACK_MNG;
case DAQ_IO_SR:
/* DAQ Sample Rate Config Callback here */
//sr = (hdaq_ctl.Out_Pack.data[4] << 16) | (hdaq_ctl.Out_Pack.data[3] << 8) | (hdaq_ctl.Out_Pack.data[2]);
daq_cfg.SampleRate = (hdaq_ctl.Out_Pack.data[4] << 16) | (hdaq_ctl.Out_Pack.data[3] << 8) | (hdaq_ctl.Out_Pack.data[2]);
/* DAQ SampleRate Setting Callback here */
ack_rs = DAQ_CMD_SampleRate(&daq_cfg);
/* ACK */
goto ACK_MNG;
default:
goto ACK_NONE;
}
case DAQ_CMD_FETCH: //NID Data
switch(hdaq_ctl.Out_Pack.data[1])
{
case DAQ_CFG:
ack_rs = DAQ_CMD_Cfg(&hdaq_ctl, &daq_cfg);
break;
case DAQ_MODE:
ack_rs = DAQ_CMD_Mode(&hdaq_ctl, &daq_cfg);
break;
case DAQ_SAMPLE:
ack_rs = DAQ_CMD_Sample(&hdaq_ctl, &daq_cfg);
break;
case DAQ_AI_CH_CFG:
ack_rs = DAQ_CMD_AI_CH_Cfg(&hdaq_ctl);
break;
case DAQ_IO_SR:
ack_rs = DAQ_CMD_IO_SR(&hdaq_ctl, &daq_cfg);
break;
case DAQ_IN_BUFF:
ack_rs = DAQ_CMD_IN_Buff(&hdaq_ctl, DAQ_EPIN_State(DAQ_DATA_EPIN_ADDR));
break;
default:
goto ACK_NONE;
}
if(ack_rs == DAQ_ACK_OK)
{
goto ACK_DATA;
}
else
{
goto ACK_MNG;
}
default:
goto ACK_NONE;
}
}
}
else
{
goto ACK_NONE;
}
ACK_MNG:
DAQ_CTL_ACK_Result(&hdaq_ctl, ack_rs);
ACK_DATA:
DAQ_Itf_CtlTxPack(hdaq_ctl.In_Buff, hdaq_ctl.In_Pack.bLength);
ACK_NONE:
USBD_LL_PrepareReceive(hdaq_temp, DAQ_CTL_EPOUT_ADDR, hdaq->CtlRxBuffer, DAQ_CTL_EPOUT_SIZE);
return USBD_OK;
}
uint8_t DAQ_Itf_DataTxPack(uint8_t* pbuff, uint16_t length)
{
USBD_DAQ_HandleTypedef *hdaq = (USBD_DAQ_HandleTypedef*)hdaq_temp->pClassData;
if((hdaq_temp->pClassData != NULL)||(pbuff != NULL))
{
if(hdaq->DataTxState == 0)
{
USBD_DAQ_SetDataTxBuffer(hdaq_temp, pbuff, length);
hdaq->DataTxState = 1;
USBD_LL_Transmit(hdaq_temp, DAQ_DATA_EPIN_ADDR, hdaq->DataTxBuffer, hdaq->DataTxLength);
return USBD_OK;
}
else
{
return USBD_BUSY;
}
}
else
{
return USBD_FAIL;
}
}
uint8_t DAQ_Itf_CtlTxPack(uint8_t* pbuff, uint16_t length)
{
USBD_DAQ_HandleTypedef *hdaq = (USBD_DAQ_HandleTypedef*)hdaq_temp->pClassData;
if((hdaq_temp->pClassData != NULL)||(pbuff != NULL))
{
if(hdaq->CtlTxState == 0)
{
USBD_DAQ_SetCtlTxBuffer(hdaq_temp, pbuff, length);
hdaq->CtlTxState = 1;
USBD_LL_Transmit(hdaq_temp, DAQ_CTL_EPIN_ADDR, hdaq->CtlTxBuffer, hdaq->CtlTxLength);
return USBD_OK;
}
else
{
return USBD_BUSY;
}
}
else
{
return USBD_FAIL;
}
}
这4个函数对主机命令进行响应,控制4个端点的数据/控制指令
2.2 驱动
驱动这部分实际上没有太多可以发挥的内容,必须按照巨硬的规范来,inf文件部分参考书可看《Windows驱动开发技术详解》—张帆。具体inf文件如下
; Ksp_DAQ_v20.inf
; Copyright (c) 2010-2016 Pete Batard <pete@akeo.ie> (GNU LGPL)
[Strings]
DeviceName = "Ksp DAQ v2.0" ;设备名
VendorName = "Ksp" ;制造商名
SourceName = "Ksp DAQ v2.0Install Disk"
DeviceID = "VID_0308&PID_0200" ;硬件ID
DeviceGUID = "{44B53307-B39F-43D1-B7F0-69D647B12660}" ;设备接口GUID
[Version]
Signature = "$Windows NT$"
Class = "USBDevice"
ClassGuid = {88bae032-5a81-49f0-bc3d-a4ff138216d6}
Provider = %VendorName%
CatalogFile = Ksp_DAQ_v20.cat
DriverVer = 04/10/2016, 15.27.20.931
[ClassInstall32]
Addreg = WinUSBDeviceClassReg
[WinUSBDeviceClassReg]
HKR,,,0,"Universal Serial Bus devices"
HKR,,Icon,,-20
[Manufacturer]
%VendorName% = libusbDevice_WinUSB,NTx86,NTamd64,NTarm
[libusbDevice_WinUSB.NTx86]
%DeviceName% = USB_Install, USB\%DeviceID%
[libusbDevice_WinUSB.NTamd64]
%DeviceName% = USB_Install, USB\%DeviceID%
[libusbDevice_WinUSB.NTarm]
%DeviceName% = USB_Install, USB\%DeviceID%
[USB_Install]
Include = winusb.inf
Needs = WINUSB.NT
[USB_Install.Services]
Include = winusb.inf
AddService = WinUSB,0x00000002,WinUSB_ServiceInstall
[WinUSB_ServiceInstall]
DisplayName = "WinUSB - Kernel Driver 03/31/2015 6.1.7600.16385"
ServiceType = 1
StartType = 3
ErrorControl = 1
ServiceBinary = %12%\WinUSB.sys
[USB_Install.Wdf]
KmdfService = WINUSB, WinUsb_Install
[WinUSB_Install]
KmdfLibraryVersion = 1.11
[USB_Install.HW]
AddReg = AddDeviceInterfaceGUID
[NoDeviceInterfaceGUID]
; Avoids adding a DeviceInterfaceGUID for generic driver
[AddDeviceInterfaceGUID]
HKR,,DeviceInterfaceGUIDs,0x10000,%DeviceGUID%
[USB_Install.CoInstallers]
AddReg = CoInstallers_AddReg
CopyFiles = CoInstallers_CopyFiles
[CoInstallers_AddReg]
HKR,,CoInstallers32,0x00010000,"WdfCoInstaller01011.dll,WdfCoInstaller","WinUSBCoInstaller2.dll"
[CoInstallers_CopyFiles]
WinUSBCoInstaller2.dll
WdfCoInstaller01011.dll
[DestinationDirs]
CoInstallers_CopyFiles = 11
[SourceDisksNames]
1 = %SourceName%
[SourceDisksFiles.x86]
WinUSBCoInstaller2.dll = 1,x86
WdfCoInstaller01011.dll = 1,x86
[SourceDisksFiles.amd64]
WinUSBCoInstaller2.dll = 1,amd64
WdfCoInstaller01011.dll = 1,amd64
[SourceDisksFiles.arm]
WinUSBCoInstaller2.dll = 1,arm
WdfCoInstaller01011.dll = 1,arm
对于自定义USB设备驱动,目前常见的教程都是在使用LibUSB(1.0版本)等,我们这里使用的是Windows自带的WinUSB,开发者不需要关心内核,详细可阅读WinUSB官网(Website:https://msdn.microsoft.com/en-us/library/windows/hardware/ff540196%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396)。这里给出DAQ的模/数数据读写代码,
KSP_WinUSB_StatusTypeDef KSP_DAQ_BURST_WR(uint8_t* w_buff, uint8_t* r_buff)
{
uint32_t rx_len;
if (KSP_WinUSB_USBD_BulkWrite(DAQ_DATA_EPOUT_ADDR, w_buff, DAQ_DATA_OUT_PACK_SIZE) != USBD_OK)
{
return USBD_FAIL;
}
if (KSP_WinUSB_USBD_BulkRead(DAQ_DATA_EPIN_ADDR, r_buff, DAQ_DATA_IN_PACK_SIZE, &rx_len) != USBD_OK)
{
return USBD_FAIL;
}
return USBD_OK;
}
KSP_WinUSB_StatusTypeDef KSP_DAQ_CONTI_W(uint8_t* w_buff, uint32_t w_buff_len)
{
return KSP_WinUSB_USBD_BulkWrite(DAQ_DATA_EPOUT_ADDR, w_buff, w_buff_len);
}
KSP_WinUSB_StatusTypeDef KSP_DAQ_CONTI_R(uint8_t* r_buff, uint32_t* r_len)
{
return KSP_WinUSB_USBD_BulkRead(DAQ_DATA_EPIN_ADDR, r_buff, DAQ_DATA_EPIN_SIZE, r_len);
}
而其中核心的块读写代码如下
/**
* @brief 通过指定端点写入数据
* @param ep_addr:端点号
pbuff:待发送的数据缓冲
txsize:数据缓冲长度
* @retval 操作结果
@arg @KSP_WinUSB_StatusTypeDef
*/
KSP_WinUSB_StatusTypeDef KSP_WinUSB_USBD_BulkWrite(uint8_t ep_addr, uint8_t* pbuff, uint32_t txsize)
{
KSP_WinUSB_StatusTypeDef status = USBD_OK;
uint32_t bytessend;
if (KSP_WinUSB_USBD_IsOpen() != 1)
{
status = USBD_FAIL;
goto DONE;
}
if (WinUsb_WritePipe(h_ksp_winusb.WinusbHandle, ep_addr, pbuff, txsize, (PULONG)&bytessend, 0) == FALSE)
{
status = USBD_FAIL;
}
DONE:
return status;
}
/**·
* @brief 通过指定端点读出数据
* @param ep_addr:端点号
pbuff:读出数据的缓冲区
rxsize:接收到的数据长度(按字节计)
* @retval 操作结果
@arg @KSP_WinUSB_StatusTypeDef
*/
KSP_WinUSB_StatusTypeDef KSP_WinUSB_USBD_BulkRead(uint8_t ep_addr, uint8_t* pbuff, uint32_t buflen, uint32_t* rxsize)
{
KSP_WinUSB_StatusTypeDef status = USBD_OK;
if (KSP_WinUSB_USBD_IsOpen() != 1)
{
status = USBD_FAIL;
goto DONE;
}
if (WinUsb_ReadPipe(h_ksp_winusb.WinusbHandle, ep_addr, pbuff, buflen, (PULONG)rxsize, 0) == FALSE)
{
status = USBD_FAIL;
}
DONE:
return status;
}
在Visual Studio平台中,inf的编写编译,DAQ设备的读写测试,封装成动态链接库(dll)一气呵成。对外提供的函数接口如下
LIBRARY Ksp_daq_v21_dll
EXPORTS
KSP_DAQ_Open [url=home.php?mod=space&uid=72445]@[/url] 1
KSP_DAQ_Close @ 2
KSP_DAQ_CTL_PartsEN @ 3
KSP_DAQ_CTL_Mode @ 4
KSP_DAQ_CTL_SampleEN @ 5
KSP_DAQ_ASIGN_AI_CH_Config @ 6
KSP_DAQ_ASIGN_SampleRate @ 7
KSP_DAQ_FETCH_PartsEN @ 8
KSP_DAQ_FETCH_Mode @ 9
KSP_DAQ_FETCH_SampleEN @ 10
KSP_DAQ_FETCH_AI_CH_Config @ 11
KSP_DAQ_FETCH_SampleRate @ 12
KSP_DAQ_FETCH_BUFF_State @ 13
KSP_DAQ_BURST_WR @ 14
KSP_DAQ_CONTI_W @ 15
KSP_DAQ_CONTI_R @ 16
2.3 应用软件
对于Windows下设备访问只要把接口写好,其他都能由系统与环境自动完成。MATLAB中如何使用前一节Release的dll文件呢,请看如下测试DAQ设备的m文件
%%测试DLL文件
[~,~]=loadlibrary('Ksp_daq_v21_dll','ksp_daq_vxx.h');
SampleDepth = 400;
SampleRate = 900000;
if strcmp(calllib('Ksp_daq_v21_dll','KSP_DAQ_Open'),'USBD_OK') == 1
disp('设备打开成功')
%设置采样率为100KHz
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_SampleRate',SampleRate);
disp(strcat('当前采样率为',int2str(SampleRate),'KHz'));
%设定采样模式
calllib('Ksp_daq_v21_dll', 'KSP_DAQ_CTL_Mode', 2);
%预读缓冲区
IN_BUFF_State = zeros(1, 1, 'uint8');
rx_len = zeros(1, 1, 'uint32');
rx_buff = zeros(1, (16*SampleDepth + 512), 'uint8');
tx_buff = uint8(hex2dec({'bb','01','99','09'}))';
calllib('Ksp_daq_v21_dll','KSP_DAQ_FETCH_BUFF_State', IN_BUFF_State);
if (IN_BUFF_State == 2)
calllib('Ksp_daq_v21_dll','KSP_DAQ_CONTI_R',rx_buff, rx_len);
end
%通道配置
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 0, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 1, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 2, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 3, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 4, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 5, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 6, 2, 8);
calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 0, 1, 1);
%calllib('Ksp_daq_v21_dll','KSP_DAQ_ASIGN_AI_CH_Config', 1, 1, 2);
total_len = 0;
%启动采样
calllib('Ksp_daq_v21_dll','KSP_DAQ_CTL_SampleEN', 1);
calllib('Ksp_daq_v21_dll', 'KSP_DAQ_CONTI_W', tx_buff, 4);
while (total_len<(16*SampleDepth))
[rs, rx_buff((total_len+1):( total_len+512)), rx_len]=calllib('Ksp_daq_v21_dll','KSP_DAQ_CONTI_R', rx_buff((total_len+1):( total_len+512)), rx_len);
total_len = total_len + rx_len;
end
%停止采样
calllib('Ksp_daq_v21_dll','KSP_DAQ_CTL_SampleEN', 2);
if strcmp(calllib('Ksp_daq_v21_dll','KSP_DAQ_Close'),'USBD_OK') == 1
disp('设备关闭成功')
else
disp('设备关闭失败')
end
AI0 = [];
for i=1:1:SampleDepth
adc_value = (double(rx_buff((i -1 )*16 + 3)) + 256*double(rx_buff((i -1 )*16 + 4)))*2.5/4095;
AI0(i) = adc_value;
end
Ts = 1 / SampleRate;
plot([0:Ts:(Ts*(SampleDepth-1))], AI0);
xlabel('t/s')
ylabel('AI0/v')
else
disp('设备打开失败')
end
unloadlibrary('Ksp_daq_v21_dll')
运行成功就能看到模拟通道0输入的电压波形图啦,下图是MATLAB下GUIDE一个简单的测试结果,硬件上DO0接到了DI0。
|