串口調(diào)試在項目中被使用越來越多,串口資源的緊缺也變的尤為突出。很多本本人群,更是深有體會,不準備一個USB轉(zhuǎn)串口工具就沒辦法進行開發(fā)。
了解USB虛擬串口,為了在項目中用一下這個USB,調(diào)試方便一些,供電可直供。公司以后的產(chǎn)品開發(fā)就基于STM32這個平臺,從contex_M3到contex-M4。不管速度、功耗、價格、采購的方便性都有競爭力,不想再修改了(除非它無法滿足要求)。
STM32基本上都帶有USB串口。如果不把它用上而另外加一個USB轉(zhuǎn)串口單元,未免顯得太落后了而且也是一種資源的浪費。順便說一下,根據(jù)公司的狀況,合適的才是好的。以前一直用TI的,從430到ARM到28XX。這個ST的包含了以前的所有,TI的ARM速度比較慢,而它的普通的DSP的速度與M4相當,有些還比不過M4,耗電卻驚人,何況M4帶了浮點后比定點在某些地方要快很多。難怪TI在推M4時故意將頻率放慢,DSP功能減少,我想各個公司都有自已的考慮吧。而ST就把CMOS傳感器接口也放進去。趕明兒啥時有空了再做塊PCB試試。ST最讓人不爽的是它的開發(fā)例程做的太淺,例如超速USB2。0,CMOS接口資料幾乎沒有,TCP/IP也淺嘗即止,網(wǎng)頁也設計得世界上最爛,讓人找個芯片,找個資料很累。在應用上比NXP差遠了,畢竟有個ZLG幫忙,F(xiàn)REESCALE在網(wǎng)絡通訊方面的應用做得最好,看他們的TCP/IP源代碼是一種享受??磥碜鲂酒荢T的強項,做應用還非常有待加強。
說明:
1.跳過驅(qū)動。使用低速傳輸。最大可以設為921000即100KB/S。應該也差不多了。只傳一些簡單的東西。因為我們工作的重點是工業(yè)控制,無需高速,而我們是以完成任務為主要目的。而不是非要學什么東西,完成任務才是第一重要的。
2.由于例程中沒有操作系統(tǒng)支持,也許以后可以用keil的操作系統(tǒng)。比較簡單,最重要的是ucOS不支持M4的浮點運算。當然對于M3我們可以將USB部分移到ucOS上。而對于M4,我們直接用KeilOS,不想花很大力氣去做OS移植了。
總是先從main開始
Set_System();///設置系統(tǒng)
Set_USBClock();///設置USB時鐘
USB_Interrupts_Config();///配置USB中斷
USB_Init();///USB初始化
while(1)
{
if((count_out!=0)&&(bDeviceState==CONFIGURED))
{
USB_To_USART_Send_Data(&buffer_out[0],count_out);//如果有數(shù)據(jù)將它發(fā)送到串口中去
count_out=0;/////發(fā)送完后這個清零
}
}
初看一下,還算比較好理解,但是由于這個例子好象只有發(fā),沒有收。我想它的收大約在中斷中進行的(也就是串口向USB發(fā)的過程,估計在串口中斷中進行,后面我們可以再分析,不行可能需要自行加上這部分代碼,希望不要這樣)
我們還是一條一條的來看,首先看Set_System()這個函數(shù),如果沒猜錯的話,應該是設置時鐘吧。
果然如此,我們下面一條一條看一下,它先是允許外部晶振---這里哆嗦一下,外部晶振我公司采用12M。而一般開發(fā)版采用8M。所以配置stm32f10x_conf.h文件中,要將外部晶振頻率從8000000改為12000000。
然后,等外部晶振起來,如果晶振沒焊接好,此時就會死在這里。如果你一運行就死,可找一下這個地方。
然后它做下列工作:
允許FLASH取指緩沖
FLASH時鐘分頻2倍-------這是否意味著FLASH的時鐘是36M呢,記得好象有ST的文章中說FLASH可工作在50M的時鐘下。
系統(tǒng)頻率HCLK配置成SYSCLK
APB2的時鐘配置成SYSCLK不分頻
APB1的時鐘配置成2分頻。但要注意它下面的定時器2,3,4。。頻率仍是72M因有倍頻
ADC的時鐘配置成6分頻,即它是12MHz。注意AD需13.5個時鐘完成意味著差不多1us可完成一次AD轉(zhuǎn)換。
PLL配置成9倍頻。8X9=72MHz。注意到對于12MHz就只能是6了。此處一定要注意。
允許PLL然后等PLL都OK了再做別的。--此后由PLL進行工作而不是HSI。
然后,我們要允許GPIOA,GPIOB和串口的時鐘。因為我們只用到了這幾個資源。當然USB是另外的,我想總會在某個地方允許的。下面這個就不好理解了:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT,ENABLE);這個叫允許USB斷開線。從原理圖上看好象是PE7來控制的。因為PE7連接到一個DP+的一個上拉電阻控制的三極管上面。請參考原理圖。但是比較奇怪的是在platform_config.h中有下列定義:
#defineRCC_APB2Periph_GPIO_DISCONNECTRCC_APB2Periph_GPIOD
它將這個設為端口D。所以這個USB斷開引腳到底是由誰來控制的還未確定。這里留下一個問號,等以后再解決。
接下來,將USB的斷開腳配置成上拉的。這也就意味著在開始時,這個上拉電阻使得三極管導通,從而使這個DP腳被加了一個1.5K的電阻,可以開始枚舉的。再看這個斷開腳指的是哪一個腳,它不是PD9就是PB14。怎么也沒有PE7的說法(原理圖)。所以這里就有點不知所以然了。難道這個原理圖與程序有沖突?
接下來配置PA10為輸入浮空,配置PA9為PP輸出。這兩個腳是串口。這個是正確的。我們幾孚所有的板子都用UART0,它就是這兩個腳的。至此這個部分結束了。
下面再來看第2個函數(shù)Set_USBClock()-------兩句話:
RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);/*EnableUSBclock*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB,ENABLE);72M除1。5=48然后允許USB的時鐘。這個要看一下數(shù)據(jù)手冊。從數(shù)據(jù)手冊比較好理解。它是直接從PCLK中除一個數(shù),可以除1也可以除1.5.。這段還是比較好理解的。
再看第3個函數(shù)USB_Interrupts_Config()-------配置USB中斷
在該函數(shù)中使能了兩個中斷,一個是USB,一個是串口。至于中斷放在RAM或FLASH當然一般是后者,所以在這也就沒有什么意義了。串口優(yōu)先級為1,USB為0。中斷分組中我們將一位作為可重入的優(yōu)先組,另3位作為優(yōu)先級組。由于一個為0一個為1,故這兩個中斷都不會相互被另一個中斷打斷。
現(xiàn)在既然允許了兩個中斷,估計在中斷中將做很多事情,大多數(shù)事情都在中斷中完成的。這個中斷程序等一會馬上就來解讀。最后是USB_Init()這個函數(shù)了。在這是里先看到:
pInformation=&Device_Info;這個是設備資料部分,里面的全部變量,靜態(tài)的。pInformation-》ControlState=2;
pInformation是一個指向Device_Info的指針。不知為什么要這樣大費周折。不可以直接這樣寫嗎:
Device_Info.ControlState=2
接下來:
pProperty=&Device_Property;
pUser_Standard_Requests=&User_Standard_Requests;pProperty是一個指針,指向DEVICE_PROP這個是設備屬性部分
設備屬性部分包含一些方法,即函數(shù)。也包括兩個參數(shù),一個是接收區(qū)的緩沖區(qū)地址,一個是最大的包的長度。都是用字節(jié)表示的。
這個屬性是一個通用的屬性,它指以相當于實例化套到USB這個頭上去。故我們在這里看上去運行一個pProperty-》Init();實際上運行的是DEVICE_PROP中的Virtual_Com_Port_init()這個函數(shù)。我們看在這個函數(shù)中做了什么:
Get_SerialNum();設置芯片序列號,將描述符中的例如STM等字符串修改沒太大意義。
PowerOn(void)先使能芯片(三極管B極變高)強迫USB復位。再將全部USB中斷都屏蔽后,將中斷清除,最后再允許以下中斷:CNTR_RESETM|CNTR_SUSPM|CNTR_WKUPM;
接下來,再一次清除中斷標志,然后使能中斷(CNTR_CTRM|CNTR_SOFM|CNTR_RESETM)這幾個中斷就是復位中斷,正確傳輸中斷,SOF中斷
配置串口至缺省狀態(tài)---在這里波特率被設為9600,并且允許了接收中斷。發(fā)送中斷沒有允許。
將當前的狀態(tài)定義為未連接狀態(tài)。bDeviceState=UNCONNECTED;什么時候連接不知道。
至此,初始化結束。我們現(xiàn)在要看的是中斷函數(shù)了。中斷函數(shù)不外于一個是串口的接收中斷。串口的發(fā)送中斷是沒有允許的。
串口是如何發(fā)送的呢?它直接寫串口寄存器,顯然如果有大量數(shù)據(jù)發(fā)送時就會出問題的。因為它根本不判斷是否發(fā)送緩沖區(qū)為空。因此,感覺這個程序要進行大量的數(shù)據(jù)交互不可能,最多是敲打一下鍵盤可能還差不多。
看它的庫中的函數(shù):直接發(fā)送,也不管是否空。難道它有16字節(jié)緩沖嗎?沒有。voidUSART_SendData(USART_TypeDef*USARTx,u16Data){
/*TransmitData*/
USARTx-》DR=(Data&(u16)0x01FF);}
先從簡單的看起,串口的接收中斷,它在哪里呢?發(fā)現(xiàn)它在stm32f10x_it.c中有如下:
USART_To_USB_Send_Data();表示從串口向USB端發(fā)送數(shù)據(jù)。
再看這個定義如下:
voidUSART_To_USB_Send_Data(void){
if(USART_InitStructure.USART_WordLength==USART_WordLength_8b){
buffer_in[count_in]=USART_ReceiveData(USART1)&0x7F;}
elseif(USART_InitStructure.USART_WordLength==USART_WordLength_9b){
buffer_in[count_in]=USART_ReceiveData(USART1);}
count_in++;
UserToPMABufferCopy(buffer_in,ENDP1_TXADDR,count_in);SetEPTxCount(ENDP1,count_in);SetEPTxValid(ENDP1);}
輸入的數(shù)據(jù)長度++即count_in++
將收到的數(shù)據(jù)拷貝到端口1的發(fā)送緩沖區(qū)中。設置發(fā)送緩沖區(qū)的長度
發(fā)送數(shù)據(jù),它是從ENDP1發(fā)送。
我相信,發(fā)送完后,這個count_in會被清零。
果然,在EP1_IN_Callback()函數(shù)中,它被清零。
最后,我們就來看一下USB的中斷。USB的中斷入口有一個還是多個?它有一個高優(yōu)先級中斷和一個低優(yōu)先級中斷。應該只用其中一個。
看程序中有下列:
voidUSB_LP_CAN_RX0_IRQHandler(void){
USB_Istr();}
這個說明,在程序中將USB設為相對低的優(yōu)先級中斷?;叵肫鹞覀兒孟笤谑裁吹胤竭@樣設過?果然,在USB_Interrupts_Config()中,有這么一段:
NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN_RX0_IRQChannel;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);
所以所有的中斷都是進到這個中斷中去了。趕緊看一看,這個是怎么處理的?它被發(fā)現(xiàn)在usb_istr.c中。我想所有的最麻煩的部分就是比較精彩的部分應該就在這里了(對初學者)下面我們就來分析這個部分,分析完之后就可以回去了,做完今天的工作。因為精彩,所以拷貝在下面了:
voidUSB_Istr(void){
wIstr=_GetISTR();
#if(IMR_MSK&ISTR_RESET)
if(wIstr&ISTR_RESET&wInterrupt_Mask){
_SetISTR((u16)CLR_RESET);Device_Property.Reset();#ifdefRESET_CALLBACK
RESET_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if(IMR_MSK&ISTR_DOVR)
if(wIstr&ISTR_DOVR&wInterrupt_Mask){
_SetISTR((u16)CLR_DOVR);#ifdefDOVR_CALLBACK
DOVR_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if(IMR_MSK&ISTR_ERR)
if(wIstr&ISTR_ERR&wInterrupt_Mask){
SetISTR((u16)CLR_ERR);#ifdefERR_CALLBACK
ERR_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/#if(IMR_MSK&ISTR_WKUP)
if(wIstr&ISTR_WKUP&wInterrupt_Mask){
_SetISTR((u16)CLR_WKUP);Resume(RESUME_EXTERNAL);#ifdefWKUP_CALLBACK
WKUP_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/#if(IMR_MSK&ISTR_SUSP)
if(wIstr&ISTR_SUSP&wInterrupt_Mask){
/*checkifSUSPENDispossible*/if(fSuspendEnabled){
Suspend();}else{
/*ifnotpossiblethenresumeafterxxms*/Resume(RESUME_LATER);}
/*clearoftheISTRbitmustbedoneaftersettingofCNTR_FSUSP*/_SetISTR((u16)CLR_SUSP);#ifdefSUSP_CALLBACK
SUSP_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/#if(IMR_MSK&ISTR_SOF)
if(wIstr&ISTR_SOF&wInterrupt_Mask){
_SetISTR((u16)CLR_SOF);
bIntPackSOF++;
#ifdefSOF_CALLBACK
SOF_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/#if(IMR_MSK&ISTR_ESOF)
if(wIstr&ISTR_ESOF&wInterrupt_Mask){
_SetISTR((u16)CLR_ESOF);
/*resumehandlingtimingismadewithESOFs*/
Resume(RESUME_ESOF);/*requestwithoutchangeofthemachinestate*/#ifdefESOF_CALLBACK
ESOF_Callback();#endif}#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/#if(IMR_MSK&ISTR_CTR)
if(wIstr&ISTR_CTR&wInterrupt_Mask){
/*servicingoftheendpointcorrecttransferinterrupt*//*clearoftheCTRflagintothesub*/CTR_LP();
#ifdefCTR_CALLBACK
CTR_Callback();#endif}#endif
}/*USB_Istr*/
下面象城管一樣,逐一分拆每戶。
wIstr=_GetISTR();得到中斷的原因,這個根本不是函數(shù),而是得到ISTR的值。
由于我們沒有外掛復位,故外掛的復位就不進行了。我們只處理這個:voidVirtual_Com_Port_Reset(void),這個函數(shù)在usb_prop.c這個文件中。它的目的是恢復上電時的缺省設置。這個我們就不去深究了。因為好象也沒有必要。
首先將全局變量pInformation(它定義在初始化中usb_init.c)中的配置值置為0表示設備還沒配置過。(這個變量猜想應該在枚舉之類的地方用于判斷是否已枚舉過)其次將當前的特征值賦值Virtual_Com_Port_ConfigDescriptor[7]。(含義先不管它)然后再將當前的通訊口設為端口0即pInformation-》Current_Interface=0;端口0大約就是控制口吧。
接下來設置緩沖表的地址或寄存器為00。這個我們暫不管它含義是什么放一邊去。
再接下來,初始化三個端口,它們是端口0,1和2。其中端口0是控制口,端口1是發(fā)送口,端口3是接收口。
在.h中定義了緩沖區(qū)表的基地址為00,而端口0收為0x40,端口0的發(fā)為0x80,端口1的發(fā)地址為0xC0.端口2的發(fā)地址為0x100.端3的收地址為0x110.可以看到其緩沖區(qū)端口2的為16字節(jié),其它的都為64字節(jié)。感覺ST公司太節(jié)省了點吧。這么短的包,如果用480M的超速的話怎么夠?
最后一句:bDeviceState=ATTACHED;表示USB進入一個新狀態(tài)。
接下來,看中斷是否響應DMA上溢下溢,錯誤處理,喚醒、掛起中斷我們都沒有用到,故全部不看它。現(xiàn)在主要要看的一個終于出現(xiàn)了,它就是我們?nèi)齻€要響應的中斷之一(三個中斷分別是復位中斷,幀頭SOF中斷,正確收發(fā)中斷)SOF中斷。這個表示幀的起始中斷。不過這個中斷的處理卻是異常的簡單,就是將這個SOF標志清除后再bIntPackSOF++;即可
當然最重要的總是在總后的,接下來的一個中斷可要費點周章了。它就是正確的收發(fā)到數(shù)據(jù)的中斷,它調(diào)用了CTR_LP()函數(shù),這個函數(shù)它定義在usb_int.c中。我們重點解讀它:這個程序名為低優(yōu)先級正確接收中斷
★首先這個中斷是一個循環(huán),它一直在等ISTR_CTR==0為止。因為當它等于0時,表示里面的數(shù)據(jù)已經(jīng)全部取完。沒取完它是不會罷休的。就象強盜進了金庫要將它搬空為止,結果是阿里巴巴勝出一樣。
★清除這個CTR標志位,為了這個,我們?nèi)タ匆幌聰?shù)據(jù)手冊,慶幸是中文的看得快一點(如果老是這么想,也許是不幸的開始)。發(fā)現(xiàn)CTR標志位只是一個只讀的位,要清除它,它能是去清除USB_EpnR中的對應位。所以為什么用一個while()的原因是中斷一個處理完后,可能還有其它的中斷未處理完,如果是這樣的話,這個CTR位就一直是高電平??墒浅绦蛑袇s將ISTR的CTR位清除(在數(shù)據(jù)手冊中它被說明為只讀位)難道這是ST的一個小失誤?別人不信,反正我是信了。
★根據(jù)端點的ID號(ISTR寄存器)決定它是控制端點0的響應呢還是其它端點的響應。原來控制端點0的響應在這里,估計枚舉就在這里進行的吧。不過枚舉過程我暫不想看,因為我相信ST會把所有的過程都給搞定的。讀程序時,如果過分的分支再分支,最后就一無所有,有時要反復4~5遍才知道,如此就只好先舍去一些確定性的,象兩平行線一定不相交的證明就不要看了。先看與要達到的目的密切相關的才行。如果要看枚舉過程,“圈圈的教我玩USB”寫得非常好,完全是由淺入深,建議買這本書看一看(贊一個,盡管不很深入,談到教書育人,比清華的教授要強得多了,某些教授就是只會騙國家經(jīng)費,找學生做事,大學搞出來的科研成果99%沒有價值)。
★重點看其它端點的響應中斷由于前面我們已經(jīng)得知了ID號,我們就到對應的端點寄存器中去找,即臂如是端點2的響應,我們就到端點2的USB_EP2R中去找。看它是發(fā)送中斷還是接收中斷。它是B15位就是它的CTR_RX,如果不等于0說明它就是該端點的接收中斷。
★接收中斷處理的過程:
_ClearEP_CTR_RX(EPindex);///清除這個接收標志
(*pEpInt_OUT[EPindex-1])();///調(diào)用相應的接收中斷的處理函數(shù)
注意這個函數(shù)數(shù)組的用法。它的定義如下:void(*pEpInt_OUT[7])(void)={
EP1_OUT_Callback,EP2_OUT_Callback,EP3_OUT_Callback,。。。
};回憶一下,大學C語言學過的函數(shù)的定義:void*function(void)學的時候沒用功吧。其實,要是我來做的話,還不如用幾個if語句來得簡明。
★所幸,我們在這里只用到兩個回調(diào)函數(shù),只需看2個即可,一個是EP1_IN_Callback()另一個是EP3_OUT_Callback()
而EP1這個,只是執(zhí)行這么簡單的一句:count_in=0;這個是當串口向USB發(fā)時時,串口的數(shù)據(jù),在串口中斷中已經(jīng)做了處理。它就是我們前面看過的:
buffer_in[count_in]=USART_ReceiveData(USART1);count_in++;
UserToPMABufferCopy(buffer_in,ENDP1_TXADDR,count_in);SetEPTxCount(ENDP1,count_in);SetEPTxValid(ENDP1);
如果串口上我們連一個鍵盤,當敲打它時,就產(chǎn)生了串口中斷,這個中斷中將數(shù)據(jù)接收好,然后拷到緩沖區(qū)中(這是一個64字節(jié)的緩沖區(qū))然后只需設置EP1的長度,開始發(fā)送就可以了。注意到這個發(fā)送字節(jié)的個數(shù)的寄存器在緩沖區(qū)中的某個地方。它在[USB_BTABLE]+n×16+4處。這點還請參考數(shù)據(jù)手冊。
SetEPTxValid(ENDP1)這個函數(shù)還有點煩。要是將它全面解剖開來,就有下列東東:(為簡化起見,中間的一些變量我用實際的數(shù)表表示了)#define_SetEPTxStatus(1,0x0030){\////我們這里是將兩位都置為11
registeru16_wRegVal;\
_wRegVal=_GetENDPOINT(1)&EPTX_DTOGMASK;\///這個數(shù)就是0x0030/*togglefirstbit?*/\if((EPTX_DTOG1&wState)!=0)\///如果原來的值不為0_wRegVal^=EPTX_DTOG1;
\///異或一下即原來如果為0則變?yōu)?而如果已經(jīng)是1了就變?yōu)?---已經(jīng)為1就不能再發(fā)了
/*togglesecondbit?*/\if((EPTX_DTOG2&wState)!=0)\///第5位也是如此做_wRegVal^=EPTX_DTOG2;\_SetENDPOINT(bEpNum,_wRegVal);\}
于我我們就知道,如果原來的STAT_TX[1:0](位于第5,4位就是0x0030處)就是為00的,則我們就置這個STAT_TX[1:0]=11發(fā)送就成功了。而如果原來就是11,則置為00。意味著發(fā)送就失敗了。原來為01就變?yōu)?0,原來為10就變?yōu)?1。而01代表的是STALL。10代表的是NAK。具體為什么要這樣,這個STALL,NAK在USB中的含義到現(xiàn)在有點模糊,可參考圈圈的書。但精確的在這里的含義還有待以后再弄清楚
STM32 USB轉(zhuǎn)串口
1、適用于stm32f10xxe系列。
2、本代碼為從iNEMO? module STEVAL-MKI062V2中提取出來,使用芯片為STM32F103RE。外圍設備如下:
3、使用。IDE為IAR,在main函數(shù)里完成相應的初始化函數(shù),就可以輸出數(shù)據(jù)到端口(詳見代碼)。前提是要裝好驅(qū)動文件。
4、輸出效果。
評論