2020年10月30日 星期五

STM32_USB_DIY(二) --- Custom HID (二) :系統開發前置作業及USB中斷

 這個寫韌體程式大家都會,如果你是去上班的新進菜鳥工程師的話,

這些系統開發的平台與環境根本不需要你操心的,尤其是大公司。

但不是每個公司或團隊都這麼好的資源,或是你只是學生想DIY 一下,

那這些不起眼的前置工作是每個工程師是否能夠獨立作業的重要指標與依據。

俗話說:靠山山倒,靠人人跑,靠自己最好。一生靠自己才是王道啊。

所以你不要以為我只是寫軟體或寫韌體的,但如果你要過自己輕鬆的日子,

我是覺得這些東西,你還是有空多摸摸吧,至少也要拿來說明一下:

"雖然不一定能夠證明別人是錯的,但至少也要保護自己說:我也沒錯啊"

我以前說過:工程技術討論,最好還是以"描述現象為主,不要動不動指著對方

說:這是你的問題啊。除非你以後你可以自行處理問題,否則,你還是得靠人家的啦。

所以當我們在第一篇文章中:STM32_USB_DIY(一) --- Custom HID (一)Project 建立

將原廠的開發環境簡化成我們要的平台之後,我們就可以進行系統開發了。

但我們在寫韌體程式時,幾乎都沒有任何除錯(Debug) 工具時,要怎麼辦?

你就不要老是想用所謂的 ICE 功能了啦,一步一步執行?那是碰到真正難解的

問題才難得使用一次的,平常寫程式是劈哩啪啦的一路往下寫了,誰還在用ICE ?

一般這一種系統除錯的方法就是在程式中塞進 printf 來看程式進度與相關數值內容,

所以大部分的程式都會幫你預留這個介面:就是我們一般的 UART 介面,

原廠的這個程式也有,程式中他稱為:COM1/COM2 。我們只要挑一個就可以了。

但原廠這個程式只有設定基本的COM (UART)的硬體IO 設定而已,你自己還要

把它再加一些上層的函數:譬如就是 printf 及相關的 Baudrate 的設定:

(因為這塊小片的 STM32 學習版的是有幫你預留一個UART 接口,他對應的就是COM1

是以PA9(TX)/PA10(RX) 為主,所以就只選COM1 而已。)


另外當然還有其他類似 stdio.h 及基本的 putc ,因為printf 的底層就是putc 。

怎麼做?這個東西就是凡事問Google 大神了。stm32 的網路資源一堆,

自己找答案,我文章不會花太多心思講解這些可以容易找得到答案的東西。


所以我們就可以很快的利用超級終端機的來看到我們系統程式跑起來的內容了。

當然也包括我們的按鍵反應結果,這樣子不是很方便嗎?

那一定有人問我:那你為什麼不用對岸的 sscom32 或相關串口除錯軟件呢?

不習慣,太複雜了...如果還要透過這些軟體下UART 指令或內容時,那不就又失去

我們之所以要搞USB HID 介面的精神的嗎?軟體夠用就好。

好,上面講到一個按鍵問題,所以我們就要準備按鍵了。因為我們不是用原廠標準的

硬體平台,所以我們要自己弄個按鍵了:


原廠所附的USB HID 程式是用到兩個按鍵:

TAMPER Button :在 PC13 I/O 上。使用中斷是:EXTI_15_10。

User Key Button : 在 PB9 I/O 上。使用中斷是:EXTI_9_5。

幸好我們這片小板子都有這兩根 I/O 。所以不用額外還要調整程式就可以了。 

不過,這裡還有一個題外話:那就我這片滿街都是的LED 按鍵板子,(這想也知道

都是來自對岸的便宜貨啊),竟然這四個獨立按鍵是壞了三顆:


你也不要笑,你要便宜又簡單的東西,就是有這個風險,幸好我們自己太有經驗了,

簡單的用三用電表就可以馬上找到問題了,跟買家吵,還不如趕快換掉吧。

你說:誰跟你說寫韌體不用搞硬體啊?"靠自己才是王道啊"

然後既然要動手焊板子,那就順便再加個東西吧,這個偷料也都偷得太兇了吧。


然後我們就可以整理一下開發硬體平台,專心的寫韌體了。


雖然我們買不起原廠專業的開發板子,但我們也不能讓我們的開發板子"二二六六"的。

所以我們就找一片壓克力板子,把我們的電路板整理一下,這樣子不是看起來很清爽啊?

-----

雖然我們這次是用 32 bits MCU 來講USB DIY,但這個東西早在八位元時,一樣可以

用的,所以這種USB 的東西說實在的,道理都是一樣的啦。所以有些小細節我也就

不會特地的說明講解,如果有不清楚的地方,就麻煩到我以前那些USB DIY 系列

文章翻查一下吧。當然我也不會像對岸網路那樣:講 STM32 USB 的東西,就只是把

原廠的資料用中文翻譯交代一下,看了老半天,根本不知道你在講甚麼?

一個好好簡單容易使用的東西,被你搞得像網路論文文章似的....最重要的還是USB 的

基本理論與相關的系統應用關係與使用方式。

USB 基本上跟一般UART 也是一樣的:無非就是TX/RX 而已。只是他為了系統擴充

與兼容性的問題,定義了許多相關的通訊協定與基本傳輸架構而已。但這些大部分

也都是硬體幫你處理了,你也只要盯著TX/RX 的資料做就可以了。

因為USB Host 是PC 端,所以USB Tx 指的就是 USB Out ;USB Rx 指的就是USB In。

(當然對USB 裝置,我們的Stm32 來說:剛好反過來。只要方向搞清楚就可以了)

而USB Tx 其實有兩個:就是Setup Token 及Out Token 。Setup 是用來發動一開始

USB 傳輸的基本命令組。然後就是資料(也可能是命令)內容而已,然後裝置如果

正確收到再回覆訊息,完成基本溝通程序而已。

很簡單吧。網路上講得落落長有甚麼用?

或是可以參考我之前的文章:Hinet 網頁系列 --- USB DIY 系列(五)---USB DIY 講座 (三)

我說了:那怕是 32 bits MCU ,USB 的這些基本架構是不會變的啦。

(上圖中的那個 Status Stage 所對應的就是零長度的資料內容,我自己一般都稱為

是 Null-IN 或 Null-OUT 的 Token,在MCU 中他也是會發中斷要求的,以下有說明)

我們就來看 STM32 中的USB 中斷處理方式吧。


(附註:這一部分你也可以參考我以前在八位元USB MCU 所做的實驗做一個比較,

看看有甚麼不一樣的地方,或是可以參考我以前做的實驗細節說明:


)

其實大家在USB 的中斷處理方式都是一樣的啦:上圖左邊藍色代表USB 總中斷,

然後再依序細分為:Setup Token (Tx) 中斷, Out Token (Tx) 中斷及 In Token (Rx)中斷。

不過,都必須是正確回ACK 才會產生中斷的。因為這些中斷都會依序發生的,

所以在系統上是不可能發生訊號打架的問題的。

原廠這隻示範程式是有附一個PC 端的軟體,你也可以依照這個軟體操作檢視結果:

首先我們先試一下簡單的LED 燈號控制:


這個命令在USB上看到的是: 


裡面的有兩個 01 01。第一個 01 代表的是 Report ID = 0x01。

而第二個就是LED 燈號控制值:0x01 開燈,0x00 為關燈。

他是用 ENDPoint 1 來下命令的,但我覺得他軟體上的標示名詞,不是很恰當,

因為在USB HID Class 規格書裡,人家真的有一個 Set Report Request。

那是要包著完整的 Setup - Out - In 命令組的:


他會這麼稱呼,可能是被微軟的軟體所誤導的吧。:


人家明明 SetReport 有分: SetReport_Interrupt 及 SetReport_Control。兩種,

上面這個傳輸架構應該就是  SetReport_Interrupt。這個以後會再解釋。

所以他另一個控制方式為:


在 USB 上看到的是:


這個應該就是 SetReport_Control 發出的。有沒有看到他 Setup 命令中那個:

0x21 0x09 0x01 0x03 0x00 0x00 0x02 0x00 的內容,是不是跟我們上圖SetReport 的

規格書內容一樣啊?

反正你自己就要清楚明白這些軟體與韌體之間的關係就好了。

所以USB 的東西不難,你只要看著規格書,一步一步地跟著做就可以了。

我以前說過:學USB 要不要買甚麼書?不用,只要有規格書就可以了。

所以你一定要看得懂規格書跟你的說的內容就可以了。

--------

但是 USB Custom HID 的系統應用,跟你USB 宣告定義息息相關,別人標準的 Class 

宣告你可不用看得懂,而且也輪不到你去改甚麼,反正就跟做就對了。

但USB Cutsom HID 就不是了,這些宣告可能跟著你系統需求而會有所調整,

所以你就得自己看得懂,而且還要跟著修改定義的,不過,我們就先來看原廠

這一支示範程式的宣告吧:

    /******************** Descriptor of Custom HID endpoints ******************/
    /* 27 */
    0x07,          /* bLength: Endpoint Descriptor size */

    USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */

    0x81,          /* bEndpointAddress: Endpoint Address (IN) */
    0x03,          /* bmAttributes: Interrupt endpoint */
    0x02,          /* wMaxPacketSize: 2 Bytes max */
    0x00,
    0x20,          /* bInterval: Polling Interval (32 ms) */
    /* 34 */
   
    0x07, /* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
/* Endpoint descriptor type */
    0x01, /* bEndpointAddress: */
/* Endpoint Address (OUT) */
    0x03, /* bmAttributes: Interrupt endpoint */
    0x02, /* wMaxPacketSize: 2 Bytes max  */
    0x00,
    0x20, /* bInterval: Polling Interval (20 ms) */
    /* 41 */

以上是關於 EndPoint 的宣告,這也是一般HID 的基本宣告。有一個基本的

Interrupt In Token 。這是必要的條件。而至於要不要有 Interrupt Out token ?

沒有硬性規定。這在系統上也是合理的,因為基本上USB 屬於主從架構,

PC 是主,Host。所有的通訊傳輸都是由Host 發動的,所以 Interrupt Out Token 

是可以用其他方式取代,譬如上述的 SetReport 命令等。

但在USB 裝置端就只能靠 Interrupt In Token 來通知Host 一些訊息了。

所以他就有定義了一個輪詢時間間隔參數:Polling Interval 。

在我們這個範例中,是 32 mSec 。但其實也沒那麼準啦:



從上圖我們可以發現:主機Host 的微軟作業系統就會依據你的宣告,

會固定約 32 mSec (42-8 = 34, 因為每一個SOF 約 1mSec)透過Endpoint 1 來跟你要資料

,只是這一支範例程式基本上都是回 NAK ,沒資料可以給,作業系統也沒覺得怎樣?

反正 32 mSec 會再來問一次。

=======
然後接下來就是其他 HID 中 Report 型態描述了,這個也不用講得多艱深或是大道理

專有名詞似的,無非就是我們要用甚麼形式來包裝我們的資料內容?

只有一種呢?(Report ID ),還是需要很多種?

如果只有一種,那也可以不需要Report ID 。 就看你的資料量多寡與複雜程度。

然後呢?資料內容是多長?是以多少 Bits 來包裝,嫌麻煩你也可以全部用 8 bits 

定義,簡單容易看得懂。而既然是 8 bits 最大最小就是 255 ~0 。

然後你的資料傳輸 Tx 或 Rx 要不要共用同一個 Report ID ?然後是要用甚麼來

傳輸?上述的軟體已經跟你說了只有六種:

SetFeatureReport
GetFeatureReport
SetReport_Interrupt
GetReport_Interrupt
SetReport_Control
GetReport_Control

因為雖然HID 號稱免驅動程式,但在PC 的 Windows 作業系統下,底層的驅動程式

是歸微軟的作業系統管,人家說了算。

所以我們就來看一下這個範例程式的Report 描述內容吧:

  ; /* CustomHID_ConfigDescriptor */
const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
  {                    
    0x06, 0xFF, 0x00,      /* USAGE_PAGE (Vendor Page: 0xFF00) */                       
    0x09, 0x01,            /* USAGE (Demo Kit)               */    
    0xa1, 0x01,            /* COLLECTION (Application)       */            
    /* 6 */
    
    /* Led 1 */        
    0x85, 0x01,            /*     REPORT_ID (1)      */
    0x09, 0x01,            /*     USAGE (LED 1)              */
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */          
    0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */           
    0x75, 0x08,            /*     REPORT_SIZE (8)            */        
    0x95, 0x01,            /*     REPORT_COUNT (1)           */       
    0xB1, 0x82,             /*    FEATURE (Data,Var,Abs,Vol) */     

    0x85, 0x01,            /*     REPORT_ID (1)              */
    0x09, 0x01,            /*     USAGE (LED 1)              */
    0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
    /* 26 */
    
    /* Led 2 */
    0x85, 0x02,            /*     REPORT_ID 2      */
    0x09, 0x02,            /*     USAGE (LED 2)              */
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */          
    0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */           
    0x75, 0x08,            /*     REPORT_SIZE (8)            */        
    0x95, 0x01,            /*     REPORT_COUNT (1)           */       
    0xB1, 0x82,             /*    FEATURE (Data,Var,Abs,Vol) */     

    0x85, 0x02,            /*     REPORT_ID (2)              */
    0x09, 0x02,            /*     USAGE (LED 2)              */
    0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
    /* 46 */
    
    /* Led 3 */        
    0x85, 0x03,            /*     REPORT_ID (3)      */
    0x09, 0x03,            /*     USAGE (LED 3)              */
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */          
    0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */           
    0x75, 0x08,            /*     REPORT_SIZE (8)            */        
    0x95, 0x01,            /*     REPORT_COUNT (1)           */       
    0xB1, 0x82,             /*    FEATURE (Data,Var,Abs,Vol) */     

    0x85, 0x03,            /*     REPORT_ID (3)              */
    0x09, 0x03,            /*     USAGE (LED 3)              */
    0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
    /* 66 */
    
    /* Led 4 */
    0x85, 0x04,            /*     REPORT_ID 4)      */
    0x09, 0x04,            /*     USAGE (LED 4)              */
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */          
    0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */           
    0x75, 0x08,            /*     REPORT_SIZE (8)            */        
    0x95, 0x01,            /*     REPORT_COUNT (1)           */       
    0xB1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */     

    0x85, 0x04,            /*     REPORT_ID (4)              */
    0x09, 0x04,            /*     USAGE (LED 4)              */
    0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
    /* 86 */
    
    /* key Push Button */  
    0x85, 0x05,            /*     REPORT_ID (5)              */
    0x09, 0x05,            /*     USAGE (Push Button)        */      
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */      
    0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */      
    0x75, 0x01,            /*     REPORT_SIZE (1)            */  
    0x81, 0x82,            /*     INPUT (Data,Var,Abs,Vol)   */   
    
    0x09, 0x05,            /*     USAGE (Push Button)        */               
    0x75, 0x01,            /*     REPORT_SIZE (1)            */           
    0xb1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */  
         
    0x75, 0x07,            /*     REPORT_SIZE (7)            */           
    0x81, 0x83,            /*     INPUT (Cnst,Var,Abs,Vol)   */                    
    0x85, 0x05,            /*     REPORT_ID (2)              */         
                    
    0x75, 0x07,            /*     REPORT_SIZE (7)            */           
    0xb1, 0x83,            /*     FEATURE (Cnst,Var,Abs,Vol) */                      
    /* 114 */

    /* Tamper Push Button */  
    0x85, 0x06,            /*     REPORT_ID (6)              */
    0x09, 0x06,            /*     USAGE (Tamper Push Button) */      
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */      
    0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */      
    0x75, 0x01,            /*     REPORT_SIZE (1)            */  
    0x81, 0x82,            /*     INPUT (Data,Var,Abs,Vol)   */   
    
    0x09, 0x06,            /*     USAGE (Tamper Push Button) */               
    0x75, 0x01,            /*     REPORT_SIZE (1)            */           
    0xb1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */  
         
    0x75, 0x07,            /*     REPORT_SIZE (7)            */           
    0x81, 0x83,            /*     INPUT (Cnst,Var,Abs,Vol)   */                    
    0x85, 0x06,            /*     REPORT_ID (6)              */         
                    
    0x75, 0x07,            /*     REPORT_SIZE (7)            */           
    0xb1, 0x83,            /*     FEATURE (Cnst,Var,Abs,Vol) */  
    /* 142 */
    
    /* ADC IN */
    0x85, 0x07,            /*     REPORT_ID (7)              */         
    0x09, 0x07,            /*     USAGE (ADC IN)             */          
    0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */               
    0x26, 0xff, 0x00,      /*     LOGICAL_MAXIMUM (255)      */                 
    0x75, 0x08,            /*     REPORT_SIZE (8)            */           
    0x81, 0x82,            /*     INPUT (Data,Var,Abs,Vol)   */                    
    0x85, 0x07,            /*     REPORT_ID (7)              */                 
    0x09, 0x07,            /*     USAGE (ADC in)             */                     
    0xb1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */                                 
    /* 161 */

    0xc0           /*     END_COLLECTION              */
  }; /* CustomHID_ReportDescriptor */


所以依據我們剛剛的解說,我們很快就可以理解他硬體上有四棵LED ,

他就用四個 Report ID 來分別代表四個LED 的控制命令。而這四個也同時

支援 OUT (就是Endpoint 1 Interrupt Out Token) 及 Feature 功能(也就是一般

Setup Token 形式的 SetReport 方式)。所以我們上面的軟體實驗就可以

用兩種不同方式來控制LED 了。

另外關於按鍵部分,就分別為 Report ID 5 及Report ID 6 了,他跟LED 類似。

只是從 OUT 變成 IN 而已。(就是Endpoint 1 Interrupt IN Token) 

最後一個是ADC ,也是跟按鍵一樣,只是我們電路板上沒有支援ADC,

所以就留給各位自行研究了。

----
好像一下又講太多了,那就先休息一下。

待下回繼續分享吧。



沒有留言:

張貼留言