串口調試在項目中被使用越來越多,串口資源的緊缺也變的尤為突出。很多本本人群,更是深有體會,不準備一個USB轉串口工具就沒辦法進行開發。
了解USB虛擬串口,為了在項目中用一下這個USB,調試方便一些,供電可直供。公司以后的產品開發就基于STM32這個平臺,從contex_M3到contex-M4。不管速度、功耗、價格、采購的方便性都有競爭力,不想再修改了(除非它無法滿足要求)。
STM32基本上都帶有USB串口。如果不把它用上而另外加一個USB轉串口單元,未免顯得太落后了而且也是一種資源的浪費。順便說一下,根據公司的狀況,合適的才是好的。以前一直用TI的,從430到ARM到28XX。這個ST的包含了以前的所有,TI的ARM速度比較慢,而它的普通的DSP的速度與M4相當,有些還比不過M4,耗電卻驚人,何況M4帶了浮點后比定點在某些地方要快很多。難怪TI在推M4時故意將頻率放慢,DSP功能減少,我想各個公司都有自已的考慮吧。而ST就把CMOS傳感器接口也放進去。趕明兒啥時有空了再做塊PCB試試。ST最讓人不爽的是它的開發例程做的太淺,例如超速USB2。0,CMOS接口資料幾乎沒有,TCP/IP也淺嘗即止,網頁也設計得世界上最爛,讓人找個芯片,找個資料很累。在應用上比NXP差遠了,畢竟有個ZLG幫忙,FREESCALE在網絡通訊方面的應用做得最好,看他們的TCP/IP源代碼是一種享受。看來做芯片是ST的強項,做應用還非常有待加強。
說明:
1.跳過驅動。使用低速傳輸。最大可以設為921000即100KB/S。應該也差不多了。只傳一些簡單的東西。因為我們工作的重點是工業控制,無需高速,而我們是以完成任務為主要目的。而不是非要學什么東西,完成任務才是第一重要的。
2.由于例程中沒有操作系統支持,也許以后可以用keil的操作系統。比較簡單,最重要的是ucOS不支持M4的浮點運算。當然對于M3我們可以將USB部分移到ucOS上。而對于M4,我們直接用KeilOS,不想花很大力氣去做OS移植了。
總是先從main開始
Set_System();///設置系統
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);//如果有數據將它發送到串口中去
count_out=0;/////發送完后這個清零
}
}
初看一下,還算比較好理解,但是由于這個例子好象只有發,沒有收。我想它的收大約在中斷中進行的(也就是串口向USB發的過程,估計在串口中斷中進行,后面我們可以再分析,不行可能需要自行加上這部分代碼,希望不要這樣)
我們還是一條一條的來看,首先看Set_System()這個函數,如果沒猜錯的話,應該是設置時鐘吧。
果然如此,我們下面一條一條看一下,它先是允許外部晶振---這里哆嗦一下,外部晶振我公司采用12M。而一般開發版采用8M。所以配置stm32f10x_conf.h文件中,要將外部晶振頻率從8000000改為12000000。
然后,等外部晶振起來,如果晶振沒焊接好,此時就會死在這里。如果你一運行就死,可找一下這個地方。
然后它做下列工作:
允許FLASH取指緩沖
FLASH時鐘分頻2倍-------這是否意味著FLASH的時鐘是36M呢,記得好象有ST的文章中說FLASH可工作在50M的時鐘下。
系統頻率HCLK配置成SYSCLK
APB2的時鐘配置成SYSCLK不分頻
APB1的時鐘配置成2分頻。但要注意它下面的定時器2,3,4。。頻率仍是72M因有倍頻
ADC的時鐘配置成6分頻,即它是12MHz。注意AD需13.5個時鐘完成意味著差不多1us可完成一次AD轉換。
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個函數Set_USBClock()-------兩句話:
RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);/*EnableUSBclock*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB,ENABLE);72M除1。5=48然后允許USB的時鐘。這個要看一下數據手冊。從數據手冊比較好理解。它是直接從PCLK中除一個數,可以除1也可以除1.5.。這段還是比較好理解的。
再看第3個函數USB_Interrupts_Config()-------配置USB中斷
在該函數中使能了兩個中斷,一個是USB,一個是串口。至于中斷放在RAM或FLASH當然一般是后者,所以在這也就沒有什么意義了。串口優先級為1,USB為0。中斷分組中我們將一位作為可重入的優先組,另3位作為優先級組。由于一個為0一個為1,故這兩個中斷都不會相互被另一個中斷打斷。
現在既然允許了兩個中斷,估計在中斷中將做很多事情,大多數事情都在中斷中完成的。這個中斷程序等一會馬上就來解讀。最后是USB_Init()這個函數了。在這是里先看到:
pInformation=&Device_Info;這個是設備資料部分,里面的全部變量,靜態的。pInformation-》ControlState=2;
pInformation是一個指向Device_Info的指針。不知為什么要這樣大費周折。不可以直接這樣寫嗎:
Device_Info.ControlState=2
接下來:
pProperty=&Device_Property;
pUser_Standard_Requests=&User_Standard_Requests;pProperty是一個指針,指向DEVICE_PROP這個是設備屬性部分
設備屬性部分包含一些方法,即函數。也包括兩個參數,一個是接收區的緩沖區地址,一個是最大的包的長度。都是用字節表示的。
這個屬性是一個通用的屬性,它指以相當于實例化套到USB這個頭上去。故我們在這里看上去運行一個pProperty-》Init();實際上運行的是DEVICE_PROP中的Virtual_Com_Port_init()這個函數。我們看在這個函數中做了什么:
Get_SerialNum();設置芯片序列號,將描述符中的例如STM等字符串修改沒太大意義。
PowerOn(void)先使能芯片(三極管B極變高)強迫USB復位。再將全部USB中斷都屏蔽后,將中斷清除,最后再允許以下中斷:CNTR_RESETM|CNTR_SUSPM|CNTR_WKUPM;
接下來,再一次清除中斷標志,然后使能中斷(CNTR_CTRM|CNTR_SOFM|CNTR_RESETM)這幾個中斷就是復位中斷,正確傳輸中斷,SOF中斷
配置串口至缺省狀態---在這里波特率被設為9600,并且允許了接收中斷。發送中斷沒有允許。
將當前的狀態定義為未連接狀態。bDeviceState=UNCONNECTED;什么時候連接不知道。
至此,初始化結束。我們現在要看的是中斷函數了。中斷函數不外于一個是串口的接收中斷。串口的發送中斷是沒有允許的。
串口是如何發送的呢?它直接寫串口寄存器,顯然如果有大量數據發送時就會出問題的。因為它根本不判斷是否發送緩沖區為空。因此,感覺這個程序要進行大量的數據交互不可能,最多是敲打一下鍵盤可能還差不多。
看它的庫中的函數:直接發送,也不管是否空。難道它有16字節緩沖嗎?沒有。voidUSART_SendData(USART_TypeDef*USARTx,u16Data){
/*TransmitData*/
USARTx-》DR=(Data&(u16)0x01FF);}
先從簡單的看起,串口的接收中斷,它在哪里呢?發現它在stm32f10x_it.c中有如下:
USART_To_USB_Send_Data();表示從串口向USB端發送數據。
再看這個定義如下:
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);}
輸入的數據長度++即count_in++
將收到的數據拷貝到端口1的發送緩沖區中。設置發送緩沖區的長度
發送數據,它是從ENDP1發送。
我相信,發送完后,這個count_in會被清零。
果然,在EP1_IN_Callback()函數中,它被清零。
最后,我們就來看一下USB的中斷。USB的中斷入口有一個還是多個?它有一個高優先級中斷和一個低優先級中斷。應該只用其中一個。
看程序中有下列:
voidUSB_LP_CAN_RX0_IRQHandler(void){
USB_Istr();}
這個說明,在程序中將USB設為相對低的優先級中斷。回想起我們好象在什么地方這樣設過?果然,在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);
所以所有的中斷都是進到這個中斷中去了。趕緊看一看,這個是怎么處理的?它被發現在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();得到中斷的原因,這個根本不是函數,而是得到ISTR的值。
由于我們沒有外掛復位,故外掛的復位就不進行了。我們只處理這個:voidVirtual_Com_Port_Reset(void),這個函數在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是發送口,端口3是接收口。
在.h中定義了緩沖區表的基地址為00,而端口0收為0x40,端口0的發為0x80,端口1的發地址為0xC0.端口2的發地址為0x100.端3的收地址為0x110.可以看到其緩沖區端口2的為16字節,其它的都為64字節。感覺ST公司太節省了點吧。這么短的包,如果用480M的超速的話怎么夠?
最后一句:bDeviceState=ATTACHED;表示USB進入一個新狀態。
接下來,看中斷是否響應DMA上溢下溢,錯誤處理,喚醒、掛起中斷我們都沒有用到,故全部不看它。現在主要要看的一個終于出現了,它就是我們三個要響應的中斷之一(三個中斷分別是復位中斷,幀頭SOF中斷,正確收發中斷)SOF中斷。這個表示幀的起始中斷。不過這個中斷的處理卻是異常的簡單,就是將這個SOF標志清除后再bIntPackSOF++;即可
當然最重要的總是在總后的,接下來的一個中斷可要費點周章了。它就是正確的收發到數據的中斷,它調用了CTR_LP()函數,這個函數它定義在usb_int.c中。我們重點解讀它:這個程序名為低優先級正確接收中斷
★首先這個中斷是一個循環,它一直在等ISTR_CTR==0為止。因為當它等于0時,表示里面的數據已經全部取完。沒取完它是不會罷休的。就象強盜進了金庫要將它搬空為止,結果是阿里巴巴勝出一樣。
★清除這個CTR標志位,為了這個,我們去看一下數據手冊,慶幸是中文的看得快一點(如果老是這么想,也許是不幸的開始)。發現CTR標志位只是一個只讀的位,要清除它,它能是去清除USB_EpnR中的對應位。所以為什么用一個while()的原因是中斷一個處理完后,可能還有其它的中斷未處理完,如果是這樣的話,這個CTR位就一直是高電平。可是程序中卻將ISTR的CTR位清除(在數據手冊中它被說明為只讀位)難道這是ST的一個小失誤?別人不信,反正我是信了。
★根據端點的ID號(ISTR寄存器)決定它是控制端點0的響應呢還是其它端點的響應。原來控制端點0的響應在這里,估計枚舉就在這里進行的吧。不過枚舉過程我暫不想看,因為我相信ST會把所有的過程都給搞定的。讀程序時,如果過分的分支再分支,最后就一無所有,有時要反復4~5遍才知道,如此就只好先舍去一些確定性的,象兩平行線一定不相交的證明就不要看了。先看與要達到的目的密切相關的才行。如果要看枚舉過程,“圈圈的教我玩USB”寫得非常好,完全是由淺入深,建議買這本書看一看(贊一個,盡管不很深入,談到教書育人,比清華的教授要強得多了,某些教授就是只會騙國家經費,找學生做事,大學搞出來的科研成果99%沒有價值)。
★重點看其它端點的響應中斷由于前面我們已經得知了ID號,我們就到對應的端點寄存器中去找,即臂如是端點2的響應,我們就到端點2的USB_EP2R中去找。看它是發送中斷還是接收中斷。它是B15位就是它的CTR_RX,如果不等于0說明它就是該端點的接收中斷。
★接收中斷處理的過程:
_ClearEP_CTR_RX(EPindex);///清除這個接收標志
(*pEpInt_OUT[EPindex-1])();///調用相應的接收中斷的處理函數
注意這個函數數組的用法。它的定義如下:void(*pEpInt_OUT[7])(void)={
EP1_OUT_Callback,EP2_OUT_Callback,EP3_OUT_Callback,。。。
};回憶一下,大學C語言學過的函數的定義:void*function(void)學的時候沒用功吧。其實,要是我來做的話,還不如用幾個if語句來得簡明。
★所幸,我們在這里只用到兩個回調函數,只需看2個即可,一個是EP1_IN_Callback()另一個是EP3_OUT_Callback()
而EP1這個,只是執行這么簡單的一句:count_in=0;這個是當串口向USB發時時,串口的數據,在串口中斷中已經做了處理。它就是我們前面看過的:
buffer_in[count_in]=USART_ReceiveData(USART1);count_in++;
UserToPMABufferCopy(buffer_in,ENDP1_TXADDR,count_in);SetEPTxCount(ENDP1,count_in);SetEPTxValid(ENDP1);
如果串口上我們連一個鍵盤,當敲打它時,就產生了串口中斷,這個中斷中將數據接收好,然后拷到緩沖區中(這是一個64字節的緩沖區)然后只需設置EP1的長度,開始發送就可以了。注意到這個發送字節的個數的寄存器在緩沖區中的某個地方。它在[USB_BTABLE]+n×16+4處。這點還請參考數據手冊。
SetEPTxValid(ENDP1)這個函數還有點煩。要是將它全面解剖開來,就有下列東東:(為簡化起見,中間的一些變量我用實際的數表表示了)#define_SetEPTxStatus(1,0x0030){\////我們這里是將兩位都置為11
registeru16_wRegVal;\
_wRegVal=_GetENDPOINT(1)&EPTX_DTOGMASK;\///這個數就是0x0030/*togglefirstbit?*/\if((EPTX_DTOG1&wState)!=0)\///如果原來的值不為0_wRegVal^=EPTX_DTOG1;
\///異或一下即原來如果為0則變為1而如果已經是1了就變為0---已經為1就不能再發了
/*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發送就成功了。而如果原來就是11,則置為00。意味著發送就失敗了。原來為01就變為10,原來為10就變為01。而01代表的是STALL。10代表的是NAK。具體為什么要這樣,這個STALL,NAK在USB中的含義到現在有點模糊,可參考圈圈的書。但精確的在這里的含義還有待以后再弄清楚
STM32 USB轉串口
1、適用于stm32f10xxe系列。
2、本代碼為從iNEMO? module STEVAL-MKI062V2中提取出來,使用芯片為STM32F103RE。外圍設備如下:
3、使用。IDE為IAR,在main函數里完成相應的初始化函數,就可以輸出數據到端口(詳見代碼)。前提是要裝好驅動文件。
4、輸出效果。
評論
查看更多