【RL-TCPnet網絡教程】第34章 RL-TCPnet之SMTP客戶端

第34章      RL-TCPnet之SMTP客戶端

本章節爲大家講解RL-TCPnet的SMTP應用,學習本章節前,務必要優先學習第33章的SMTP基礎知識。有了這些基礎知識之後,再搞本章節會有事半功倍的效果。

本章教程含STM32F407開發板和STM32F429開發板。

34.1  初學者重要提示

34.2  SMTP函數

34.3  SMTP配置說明(Net_Config.c)

34.4  SMTP調試說明(Net_Debug.c)

34.5  SMTP訪問方法和板子的操作步驟

34.6  實驗例程說明(裸機)

34.7  實驗例程說明(RTX)

34.8  總結

34.1  初學者重要提示

  1. 學習本章節前,務必保證已經學習了第33章的基礎知識。
  2. 利用SMTP實現郵件發送的具體使用方法,務必看本章34.5小節的說明。另外,測試本章節的例子,一定要將開發板接到能夠聯網的路由器或者交換機上。
  3. 我們這裏實現郵件的自收發,也就是開發板的SMTP客戶端登錄郵箱,然後自己給自己發,郵件發送後,大家可以在電腦端或者手機端登錄郵箱,並查看郵箱的內容。通過這種功能可以方便地實現遠程狀態監控。
  4. 由於126,163和QQ郵箱的通信都做了加密,所以RL-TCPnet的SMTP無法進行郵箱通信,當前測試新浪郵箱還可以使用,所以大家使用前務必要註冊新浪郵箱。而且開發板登錄新浪郵箱後,無法給126郵箱,163郵箱和QQ郵箱發送郵件,去年(2016年)的時候還可以的,今年已經不支持了,郵件會被拒收。爲了更進一步驗證這個問題,使用新浪郵箱給QQ郵箱發送郵件,收到“系統退信”郵件:

34.2  SMTP函數

使用如下3個函數可以實現RL-TCPnet的SMTP:

  • smtp_cbfunc
  • smtp_connect
  • smtp_accept_auth

關於這3個函數的講解及其使用方法可以看教程第 3 章 3.4 小節裏面說的參考資料 rlarm.chm 文件:

關於這3個函數注意以下一點:

  1. SMTP的所有函數都不支持重入,也就是不支持多任務調用。

34.2.1    函數smtp_accept_auth

函數原型:

BOOL smtp_accept_auth (

    U8* srv_ip );      /* SMTP服務器的IP地址 */

函數描述:

SMTP客戶端發送電子郵件需要登錄到SMTP服務器,RL-TCPnet就會調用此函數,並詢問用戶如果SMTP服務器發佈了用戶身份驗證,是否接受認證,簡單的說,就是是否登錄SMTP服務器。

  1. 第1個參數是SMTP服務器的IP地址。
  2. 返回值,返回__TRUE表示接受認證,返回__FALSE表示不接受認證。

使用這個函數要注意以下問題:

  1. 另外一個函數smtp_cbfunc形參有三種類型:SMTP_EVT_SUCCESS,SMTP_EVT_TIMEOUT和SMTP_EVT_ERROR。如果函數smtp_accept_auth返回__FALSE的話,那麼RL-TCPnet調用函數smtp_cbfunc時的形參值只能是SMTP_EVT_ERROR。

使用舉例:

BOOL smtp_accept_auth (U8 *srv_ip)

{

  if (srv_ip[0] == 192  &&

      srv_ip[1] == 168  &&

      srv_ip[2] == 1    &&

      srv_ip[3] == 253)

{

     /* 如果SMTP服務器是此IP地址,禁止登陸 */

     return (__FALSE);

   }

 

   /* 允許登陸SMTP服務器 */

   return (__TRUE);

}

34.2.2   函數smtp_connect

函數原型:

BOOL smtp_connect (

    U8*   ipadr,                   /* SMTP服務器IP地址 */

    U16   port,                    /* SMTP服務器端口號 */

    void (*cbfunc)(U8 event) );    /* 回調函數 */

函數描述:

函數smtp_connect用於啓動RL-TCPnet的SMTP客戶端登錄SMTP服務器進行郵件發送,比如登錄新浪郵箱發送郵件。

  1. 第1個參數填寫SMTP服務器的IP地址。
  2. 第2個參數填寫SMTP服務器的端口號。
  3. 第3個參數填此函數的回調函數,當SMTP會話即將結束時,會調用這個函數。此回調函數只有一個形參,形參類型如下:
  1. 返回值,返回__TRUE表示SMTP客戶端啓動成功(注意,僅僅是客戶端啓動成功,並不是郵件發送成功),返回__FALSE表示啓動失敗。

使用這個函數要注意以下問題:

  1. 標準SMTP的端口號是用的TCP端口25。
  2. 用戶是通過此函數啓動RL-TCPnet的SMTP客戶端登錄SMTP服務器進行郵件發送。

使用舉例:

/*

*********************************************************************************************************

*                                          變量

*********************************************************************************************************

*/

char const *hosts[4] =

{

     "smtp.sina.cn",

     "smtp.126.com",

     "smtp.qq.com",

     "smtp.163.com",

};

 

U8 srv_ip[4];

U8 dns_flag = 0;

 

 

/*

*********************************************************************************************************

*    函 數 名: dns_cbfunc

*    功能說明: 函數get_host_by_name的回調函數。

*    形    參: event  事件類型

*             ip     如果事件類型是DNS_EVT_SUCCESS,此指針變量指向返回的IP地址緩衝區。

*    返 回 值: 無

*********************************************************************************************************

*/

static void dns_cbfunc (unsigned char event, unsigned char *ip)

{

     switch (event)

     {

         /* 成功解析地址 */

         case DNS_EVT_SUCCESS:

              printf_debug("DNS解析出郵箱服務器IP地址 : %d.%d.%d.%d\n",ip[0],ip[1],ip[2],ip[3]);

 

              srv_ip[0] = ip[0];

              srv_ip[1] = ip[1];

              srv_ip[2] = ip[2];

              srv_ip[3] = ip[3];

              dns_flag = 1;

              break;

        

         /* DNS記錄數據庫中不存在此地址 */

         case DNS_EVT_NONAME:

              printf_debug("Host Name does not exist in DNS record database.\n");

              break;

 

         /* 允許的DNS解析重試次數已經用完,仍無法解析,時間超時 */

         case DNS_EVT_TIMEOUT:

              printf_debug("DNS Resolver Timeout expired, Host Address not resolved.\n");

              break;

 

         /* DNS協議錯誤,收到無效或者被損壞的回覆 */

         case DNS_EVT_ERROR:

              printf_debug("DNS Resolver Protocol Error, Host Address not resolved.\n");

              return;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: smtp_cback

*    功能說明: 函數smtp_connect的回調函數。

*    形    參: event  事件類型

*    返 回 值: 無

*********************************************************************************************************

*/

static void smtp_cback(U8 event)

{

     switch (event)

     {

         /* 郵件發送成功 */

         case SMTP_EVT_SUCCESS:

              printf_debug ("Email successfully sent\r\n");

              break;

        

         /* 超時,郵件未發送成功 */

         case SMTP_EVT_TIMEOUT:

              printf_debug ("Mail Server timeout.\r\n");

              break;

        

         /* 郵件發送失敗 */

         case SMTP_EVT_ERROR:

              printf_debug ("Error sending email.\r\n");

              break;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: TCPnetTest

*    功能說明: TCPent測試函數。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

    

     while (1)

     {

         os_evt_wait_or(0x0003, 0xFFFF); 

        

          /* 如果接受到按鍵K2按下的消息,解析smtp.sina.cn的IP地址 */

         xResult = os_evt_get ();

         if(xResult == KEY2_BIT1)

         {

              get_host_by_name ((U8 *)hosts[0], dns_cbfunc);

         }

        

         /* 解析出IP地址後,發送郵件 */

         if(dns_flag == 1)

         {

              smtp_connect ((U8 *)&srv_ip, 25, smtp_cback);     

              dns_flag = 0;         

         }

             

         while (main_TcpNet() == __TRUE);

     }

}

34.2.3   函數smtp_cbfunc

函數原型:

U16 smtp_cbfunc (

    U8   code,      /* SMTP客戶端的郵件數據類型請求 */

U8*  buf,       /* 輸出緩衝區地址 */

 U16  buflen,    /* 輸出緩衝區大小,單位字節 */

U32* pvar );    /* 指針變量,指向一個不會被改變的變量  */

函數描述:

函數smtp_cbfunc用於提供發送郵件所需的發送郵箱、接收郵箱、用戶名、用戶密碼、郵件內容等。SMTP客戶端通過多次調用此函數完成郵件發送。

  1. 第1個參數是SMTP客戶端需要的電子郵件數據類型,比如發送郵箱、接收郵箱、用戶名、用戶密碼、郵件內容等。具體支持的類型如下:
  1. 第2個參數是輸出緩衝區地址,用於函數smtp_cbfunc執行過程中存儲要發送的郵件內容。
  2.  第3個參數是輸出緩衝區的大小,單位字節。
  3. 第4個參數指向不會被SMTP客戶端更改的變量。用戶可以將其作爲重複計數器,或者簡單地區分smtp_cbfunc函數的不同調用,亦或者任何其它應用均可。
  4. 返回值,返回寫入到輸出緩衝區的字節數。返回值是U16類型的,其中bit15還可以作爲其它用途,而剩餘的bit0-bit14表示的最大值是32767,足夠表示TCP Socket的MSS最大報文段大小的1460字節。
  • bit15作爲函數smtp_cbfunc是否重複調用的標誌,如果此位設置爲1,表示退出函數後,依然保持第1個參數cmd和第4個參數*pvar的數值,並再次調用函數smtp_cbfunc。如果此位設置爲0,將不再重複調用。

使用這個函數要注意以下問題:

  1. 輸出緩衝區的大小是由TCP Socket的MSS最大報文段大小決定的,局域網中一般是1400字節左右,但是也可減小到500字節,甚至更小。
  2. 寫到輸出緩衝區的數據,不可以超過第三個參數buflen的大小,否則會造成內存指針鏈表的損壞,從而很容易造成系統崩潰。
  3. 對於每個SMTP會話,*pvar(注意,這裏是指的指針變量pvar所指向的存儲單元)變量都是獨立的,也就是說新創建一個會話,都會有一個獨立的*pvar變量。另外,每個會話首次調用函數smtp_cbfunc之前都會將變量*pvar清零(注意,這裏是指的指針變量pvar所指向的存儲單元清零)。

使用舉例:

/* Email definitions */

#define MAIL_FROM       "[email protected]" 

#define RCPT_TO         "[email protected]"

#define SMTP_USER       "[email protected]"

#define SMTP_PASSWORD   "xxxxxxxxx"

#define MAIL_SUBJECT    "安富萊電子"

 

 

#define MSG_HEADER                                                       \

  "Hello,大家好!\r\n\r\n"                                               \

  "這是一封來自STM32-V6開發板發出的郵件,運行的RL-TCPnet小型協議棧\r\n"  \

  "在這裏祝福大家身體健康,萬事如意\r\n\r\n"                             \

  "--------------------------------------------------------------\r\n"

 

#define MSG_FOOTER                                                    \

  "--------------------------------------------------------------\r\n"\

  "此致敬禮\r\n\r\n"                                  \

  "安富萊電子"

 

 

/* My structure of SMTP U32 storage variable. This variable is private and  */

/* is not altered by SMTP Client. It is only set to zero when smtp_cbfunc() */

/* is called for the first time.  */

typedef struct {

  U8  id;

  U16 idx;

} MY_BUF;

#define MYBUF(p)        ((MY_BUF *)p)

 

/* Net_Config.c */

extern struct sys_cfg   sys_config;

#define lhost_name      sys_config.HostName

 

/*----------------------------------------------------------------------------

 *      SMTP CallBack Functions

 *---------------------------------------------------------------------------*/

 

/*--------------------------- smtp_cbfunc -----------------------------------*/

 

U16 smtp_cbfunc (U8 code, U8 *buf, U16 buflen, U32 *pvar) {

  /* This function is called by the SMTP client to get email parameters and */

  /* data. It returns the number of bytes written to the output buffer.     */

  /* Hi-bit of return value (len is or-ed with 0x8000) is a repeat flag the */

  /* SMTP client. If this bit is set to 1, the system will call this func   */

  /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer   */

  /* can be used for storing different status variables for this function.  */

  /* It is set to 0 by SMTP client on first call and is not altered by SMTP */

  /* client for repeated calls. This function should NEVER write more than  */

  /* 'buflen' bytes to the buffer.                                          */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 - Username:   - for SMTP authentication if requested     */

  /*             1 - Password:   - for SMTP authentication if requested     */

  /*             2 - 'From   : ' - get email address of the sender          */

  /*             3 - 'To     : ' - get email address of recipient           */

  /*             4 - 'Subject: ' - get subject of email                     */

  /*             5 - 'Data   : ' - get email data in plain ascii format     */

  /*   buf    - SMTP transmit buffer                                        */

  /*   buflen - length of this buffer (500-1400 bytes - depends on MSS)     */

  /*   pvar   - pointer to local storage buffer used for repeated loops     */

  /*            This is a U32 variable - size is 4 bytes. Value is:         */

  /*            - on 1st call = 0                                           */

  /*            - 2nd call    = as set by this function on first call       */

  U32 len = 0;

 

  switch (code) {

    case 0:

      /* Enter Username for SMTP Server authentication. */

      len = str_copy (buf, SMTP_USER);

      break;

 

    case 1:

      /* Enter Password for SMTP Server authentication. */

      len = str_copy (buf, SMTP_PASSWORD);

      break;

 

    case 2:

      /* Enter email address of the sender. */

      len = str_copy (buf, MAIL_FROM);

      break;

 

    case 3:

      /* Enter email address of the recipient. */

      len = str_copy (buf, RCPT_TO);

      break;

 

    case 4:

      /* Enter email subject. */

      len = str_copy (buf, MAIL_SUBJECT);

      break;

 

    case 5:

      /* Enter email data. */

      switch (MYBUF(pvar)->id) {

        case 0:

          /* First call, enter an email header text. */

          len = sprintf ((char *)buf, MSG_HEADER);

          MYBUF(pvar)->id  = 1;

          MYBUF(pvar)->idx = 1;

          goto rep;

 

        case 1:

           /* Add email message body. */

           /* Let's use as much of the buffer as possible. */

          /* This will produce less packets and speedup the transfer. */

           len = sprintf ((char *)(buf), "%d. ",MYBUF(pvar)->idx);

          

           len += str_copy (buf+len, "用戶可以在這裏添加數據\n");

           if (++MYBUF(pvar)->idx > 5)

           {

               MYBUF(pvar)->id = 2;

           }

          /* Request a repeated call, bit 15 is a repeat flag. */

rep:      len |= 0x8000;

          break;

 

        case 2:

          /* Last one, add a footer text to this email. */

          len = str_copy (buf, MSG_FOOTER);

          break;

      }

  }

  return ((U16)len);

}

34.3 SMTP配置說明(Net_Config.c)

(本章節配套例子的配置與本小節的說明相同)

RL-TCPnet的配置工作是通過配置文件Net_Config.c實現。在MDK工程中打開文件Net_Config.c,可以看到下圖所示的工程配置嚮導:

RL-TCPnet要配置的選項非常多,我們這裏把幾個主要的配置選項簡單介紹下。

System Definitions

(1)  Local Host Name

局域網域名。

這裏起名爲armfly,使用局域網域名限制爲15個字符。

(2) Memory Pool size

參數範圍1536-262144字節。

內存池大小配置,單位字節。另外注意一點,配置嚮導這裏顯示的單位是字節,如果看原始定義,MDK會做一個自動的4字節倍數轉換,比如我們這裏配置的是8192字節,那麼原始定義是#define MEM_SIZE  2048,也就是8192/4 = 2048。

(3) Tick Timer interval

可取10,20,25,40,50,100,200,單位ms。

系統滴答時鐘間隔,也就是網絡協議棧的系統時間基準,默認情況下,取值100ms。

Ethernet Network Interface

以太網接口配置,勾選了此選項就可以配置了,如果沒有使能DHCP的話,將使用這裏配置的固定IP

(1)  MAC Address

局域網內可以隨意配置,只要不跟局域網內其它設備的MAC地址衝突即可。

(2) IP Address

IP地址。

(3) Subnet mask

子網掩碼。

(4) Default Gateway

默認網關。

Ethernet Network Interface

以太網接口配置,這個配置裏面還有如下兩項比較重要的配置要說明。

(1)  NetBIOS Name Service

NetBIOS局域網域名服務,這裏打上對勾就使能了。這樣我們就可以通過前面配置的Local Host Name局域網域名進行訪問,而不需要通過IP地址訪問了。

(2)  Dynaminc Host Configuration

即DHCP,這裏打上對勾就使能了。使能了DHCP後,RL-TCPnet就可以從外接的路由器上獲得動態IP地址。

UDP Sockets

UDP Sockets配置,打上對勾就使能了此項功能

(1)  Number of UDP Sockets

用於配置可創建的UDP Sockets數量,這裏配置了5個。

範圍1 – 20。

TCP Sockets

TCP Sockets配置,打上對勾就使能了此項功能

(1)  Number of TCP Sockets

用於配置可創建的TCP Sockets數量。

(2)Number of Retries

範圍0-20。

用於配置重試次數,TCP數據傳輸時,如果在設置的重試時間內得不到應答,算一次重試失敗,這裏就是配置的最大重試次數。

(3) Retry Timeout in seconds

範圍1-10,單位秒。

重試時間。如果發送的數據在重試時間內得不到應答,將重新發送數據。

(4) Default Connect Timeout in seconds

範圍1-600,單位秒。

用於配置默認的保持連接時間,即我們常說的Keep Alive時間,如果時間到了將斷開連接。常用於HTTP Server,Telnet Server等。

(5)Maximum Segment Size

範圍536-1460,單位字節。

MSS定義了TCP數據包能夠傳輸的最大數據分段。

(6)Receive Window Size

範圍536-65535,單位字節。

TCP接收窗口大小。

DNS Client

DNS 配置,打上對勾就使能了此項功能

(1)  Cache Table size

主機名/IP地址的緩存表大小。

範圍5-100。

SMTP Client

SMTP配置,打上對勾就使能了此項功能

(1)  Response Timeout in seconds

這個是SMTP客戶端等待SMTP服務器響應的溢出時間,超過這個時間,客戶端將終止操作。

範圍5-120秒。

34.4 SMTP調試說明(Net_Debug.c)

(重要說明,RL-TCPnet的調試是通過串口打印出來的)

RL-TCPnet的調試功能是通過配置文件Net_Debug.c實現。在MDK工程中打開文件Net_Debug.c,可以看到下圖所示的工程配置嚮導:

Print Time Stamp

勾選了此選項的話,打印消息時,前面會附帶時間信息。

其它所有的選項

默認情況下,所有的調試選項都關閉了,每個選項有三個調試級別可選擇,這裏我們以SMTP Client Debug爲例,點擊下拉列表,可以看到裏面有Off,Errors only和Full debug三個調試級別可供選擇,每個調試選項裏面都是這三個級別。

Off:表示關閉此選項的調試功能。

Errors only:表示僅在此選項出錯時,將其錯誤打印出來。

Full debug:表示此選項的全功能調試。

現在我們使能Full debug並全編譯工程下載到板子裏面,讓開發板登錄郵箱[email protected],然後給自己發一封郵件,下面就是一個完成的郵件發送成功的過程:

000.0 SMTP:Init Client

005.1 SMTP:Connect Client

005.1 SMTP: Server: 202.108.5.186

005.1 SMTP: Port  : 25

005.2 SMTP:Socket connected

005.5 SMTP:*** Processing frame ***

005.5 SMTP: Received length: 54 bytes

005.5 SMTP: Server is ready

005.5 SMTP: Sending: EHLO armfly

005.5 SMTP:*** Processing frame ***

005.5 SMTP: Received length: 120 bytes

005.5 SMTP: EHLO acked, ESMTP mode

005.5 SMTP: Start Authentication

005.5 SMTP: Sending: AUTH LOGIN

005.6 SMTP:*** Processing frame ***

005.6 SMTP: Received length: 18 bytes

005.6 SMTP: Sending: YmFpeW9uZ2Jpbl8YMDA2QHNpbmEuY24=

005.6 SMTP:*** Processing frame ***

005.6 SMTP: Received length: 18 bytes

005.6 SMTP: Sending: QkFJeW9uZ2Jpbl9fMTU=

005.8 SMTP:*** Processing frame ***

005.8 SMTP: Received length: 22 bytes

005.8 SMTP: Authentication successful

005.8 SMTP: Sending: MAIL FROM: <[email protected]>

005.9 SMTP:*** Processing frame ***

005.9 SMTP: Received length: 8 bytes

005.9 SMTP: Server acknowledge

005.9 SMTP: Sending: RCPT TO: <[email protected]>

005.9 SMTP:*** Processing frame ***

005.9 SMTP: Received length: 8 bytes

005.9 SMTP: Server acknowledge

005.9 SMTP: Sending: DATA

006.0 SMTP:*** Processing frame ***

006.0 SMTP: Received length: 37 bytes

006.0 SMTP: Server acknowledge

006.0 SMTP: Sending 'Message Body'

006.1 SMTP:*** Processing frame ***

006.1 SMTP: Received length: 30 bytes

006.1 SMTP: End of Message acknowledge

006.1 SMTP: Sending: QUIT

006.1 SMTP:*** Processing frame ***

006.1 SMTP: Received length: 48 bytes

006.1 SMTP: Server acknowledge

006.1 SMTP: Client done, close socket

34.5 註冊郵箱和板子的操作步驟

SMTP的測試稍麻煩些,需要大家先註冊新浪郵箱,然後纔可以測試。

34.5.1 註冊郵箱

由於126,163和QQ郵箱的通信都做了加密,所以RL-TCPnet的SMTP無法進行郵箱通信。當前測試新浪郵箱還可以使用,所以大家使用前務必要註冊新浪郵箱,註冊地址:https://mail.sina.com.cn/register/regmail.php

1、註冊完畢後,要設置下郵箱,使能SMTP服務功能。

登錄郵箱後,點擊左上角的設置選項:

在彈出的頁面中,看左側欄,選擇“客戶端pop/imap/smtp”。

開啓POP3/SMTP服務:

IMAP4服務/SMTP服務也開啓:

最後,切不要忘記保存:

保存後就可以進行測試了。

2、修改SMTP_uif.c文件開頭的郵箱信息

註冊並設置完畢新浪郵箱後,需要再設置下工程中SMTP_uif.c文件開頭的郵箱信息,比如我們剛剛註冊的郵箱是[email protected],密碼是amfly123456(這個密碼和賬號是隨便寫的,僅僅是舉個例子)。那麼開頭的信息就需要修改爲如下:

#define MAIL_FROM       "[email protected]"   /* 發送郵箱 */

#define RCPT_TO          "[email protected]"   /* 接收郵箱 */

#define SMTP_USER       "[email protected]"   /* 發送郵箱的用戶名 */

#define SMTP_PASSWORD "amfly123456"          /* 發送郵箱密碼*/

#define MAIL_SUBJECT     "安富萊電子"

我們這裏是做了一個自發自收的功能,即開發板登錄新浪郵箱,給自己發郵件。用戶就可以在手機端或者電腦端登錄郵箱,查看接收到的內容。

注:接收郵箱選項不支持126,163和QQ郵箱。

34.5.2 郵件發送

使用這個例子,務必要將開發板接到能夠聯網的路由器或者交換機上,因爲DNS域名解析和SMTP發送郵件需要連接網絡才行。

將本章節的程序下載到板子裏面,並將板子重新上電後,按下K2按鍵(打印完畢硬件初始化後再按),可以看到串口調試助手打印出如下信息(波特率115200,數據位8,奇偶校驗位無,停止位1):

從上面的打印信息中可以看到,郵件已經發送成功,現在就可以登錄新浪郵箱查看接收到的內容了,如果看不到的話,刷新下網頁即可:

至此就完成了RL-TCPnet的SMTP郵件發送功能。

34.6 實驗例程說明(RTX)

34.6.1 STM32F407開發板實驗

配套例子:

V5-1048_RL-TCPnet實驗_SMTP郵件發送(RTX)

實驗目的:

  1. 學習RL-TCPnet的SMTP郵件發送。

實驗內容:

  1. 用戶務必將網線接到能夠聯網的路由器或者交換機上面測試,因爲DNS域名解析和SMTP發送郵件需要連接網絡才行。
  2. 由於126,163和QQ郵箱的通信都做了加密,所以RL-TCPnet的SMTP無法進行郵箱通信。當前測試新浪郵箱還可以使用,所以大家使用前務必要註冊新浪郵箱。根據註冊的新浪郵箱,SMTP_uif.c文件開頭代碼中的發送郵箱、接收郵箱、用戶名、用戶密碼和郵件主題是需要用戶填寫的。 #define MAIL_FROM        "[email protected]"  發送郵箱 #define RCPT_TO           "[email protected]"  接收郵箱 #define SMTP_USER        "[email protected]"  發送郵箱的用戶名 #define SMTP_PASSWORD  "xxxxxxxxx"                 發送郵箱的密碼 #define MAIL_SUBJECT      "安富萊電子"                郵件主題
  3. 郵件發送的具體操作步驟,務必看本章節配套教程的使用說明。
  4. 我們這裏實現郵件的自收發,也就是開發板的SMTP Client登錄新浪郵箱,然後自己給自己發郵件。發送後,大家可以在電腦端或者手機端登錄郵箱,並查看郵箱的內容。
  5. 按下按鍵K2將啓動郵件發送。

實驗操作:

詳見本章節34.5小節。

配置嚮導文件設置(Net_Config.c):

詳見本章節34.3小節。

調試文件設置(Net_Debug.c):

詳見本章節34.4小節。

RTX配置:

RTX配置嚮導詳情如下:

Task Configuration

(1)  Number of concurrent running tasks

允許創建6個任務,實際創建瞭如下5個任務:

AppTaskUserIF任務   :按鍵消息處理。

AppTaskLED任務     :LED閃爍。

AppTaskMsgPro任務 :按鍵檢測。

AppTaskTCPMain任務:RL-TCPnet測試任務。

AppTaskStart任務  :啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。

(2) Number of tasks with user-provided stack

創建的5個任務都是採用自定義堆棧方式。

(3) Run in privileged mode

設置任務運行在非特權級模式。

RTX任務調試信息:

程序設計:

任務棧大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任務棧 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任務棧 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任務棧 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任務棧 */

static uint64_t AppTaskStartStk[1024/8];     /* 任務棧 */

將任務棧定義成uint64_t類型可以保證任務棧是8字節對齊的,8字節對齊的含義就是數組的首地址對8求餘等於0。如果不做8字節對齊的話,部分C語言庫函數、浮點運算和uint64_t類型數據運算會出問題。

系統棧大小分配:

RTX初始化:

/*

*********************************************************************************************************

*    函 數 名: main

*    功能說明: 標準c程序入口。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

int main (void)

{   

     /* 初始化外設 */

     bsp_Init();

    

     /* 創建啓動任務 */

     os_sys_init_user (AppTaskStart,              /* 任務函數 */

                       5,                         /* 任務優先級 */

                       &AppTaskStartStk,          /* 任務棧 */

                       sizeof(AppTaskStartStk));  /* 任務棧大小,單位字節數 */

     while(1);

}

硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*

*********************************************************************************************************

*    函 數 名: bsp_Init

*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次

*    形    參:無

*    返 回 值: 無

*********************************************************************************************************

*/

void bsp_Init(void)

{

     /*

         由於ST固件庫的啓動文件已經執行了CPU系統時鐘的初始化,所以不必再次重複配置系統時鐘。

         啓動文件配置了CPU主時鐘頻率、內部Flash訪問速度和可選的外部SRAM FSMC初始化。

 

         系統時鐘缺省配置爲168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 優先級分組設置爲4,可配置0-15級搶佔式優先級,0級子優先級,即不存在子優先級。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

 

     bsp_InitDWT();     /* 初始化DWT */

     bsp_InitUart();    /* 初始化串口 */

     bsp_InitKey();    /* 初始化按鍵變量(必須在 bsp_InitTimer() 之前調用) */

     bsp_InitLed();    /* 初始LED指示燈端口 */

}

RTX任務創建:

/*

*********************************************************************************************************

*    函 數 名: AppTaskCreate

*    功能說明: 創建應用任務

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任務函數 */

                                           1,                         /* 任務優先級 */

                                           &AppTaskUserIFStk,         /* 任務棧 */

                                           sizeof(AppTaskUserIFStk)); /* 任務棧大小,單位字節數 */

    

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任務函數 */

                                        2,                       /* 任務優先級 */

                                        &AppTaskLEDStk,          /* 任務棧 */

                                        sizeof(AppTaskLEDStk));  /* 任務棧大小,單位字節數 */

    

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任務函數 */

                                           3,                         /* 任務優先級 */

                                           &AppTaskMsgProStk,         /* 任務棧 */

                                           sizeof(AppTaskMsgProStk)); /* 任務棧大小,單位字節數 */

    

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任務函數 */

                                           4,                         /* 任務優先級 */

                                           &AppTaskTCPMainStk,         /* 任務棧 */

                                           sizeof(AppTaskTCPMainStk)); /* 任務棧大小,單位字節數 */

}

五個RTX任務的實現:

/*

*********************************************************************************************************

*    函 數 名: AppTaskUserIF

*    功能說明: 按鍵消息處理     

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 1  (數值越小優先級越低,這個跟uCOS相反)

*********************************************************************************************************

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

 

    while(1)

    {

         ucKeyCode = bsp_GetKey();

        

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1鍵按下 */

                   case KEY_DOWN_K1:

                       printf("K1鍵按下 \r\n");       

                       break;  

 

                   /* K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit1被設置\r\n");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                  

                   /* K3鍵按下 */

                   case KEY_DOWN_K3:

                       printf("K3鍵按下 \r\n");

                       break;

 

                   /* 其他的鍵值不處理 */

                   default:                    

                       break;

              }

         }

        

         os_dly_wait(20);

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskLED

*    功能說明: LED閃爍。

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 2 

*********************************************************************************************************

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = 500; /* 延遲週期 */

    

     /* 設置延遲週期 */

     os_itv_set(usFrequency);

    

    while(1)

    {

         bsp_LedToggle(2);

 

         /* os_itv_wait是絕對延遲,os_dly_wait是相對延遲。*/

         os_itv_wait();

    }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskMsgPro

*    功能說明: 按鍵檢測

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 3 

*********************************************************************************************************

*/

__task void AppTaskMsgPro(void)

{

    while(1)

    {

         bsp_KeyScan();

         os_dly_wait(10);

    }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskTCPMain

*    功能說明: RL-TCPnet測試任務

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 4 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while (1)

     {

         TCPnetTest();

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskStart

*    功能說明: 啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 5 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

    

     /* 創建任務 */

     AppTaskCreate();

    

     os_itv_set (100);

    

    while(1)

    {

         os_itv_wait ();

        

         /* RL-TCPnet時間基準更新函數 */

         timer_tick ();

         os_evt_set(0x0001, HandleTaskTCPMain);

    }

}

RL-TCPnet功能測試

這裏專門創建了一個app_tcpnet_lib.c文件用於RL-TCPnet功能的測試,此文件主要實現網絡主函數main_TcpNet的調用和郵件的發送。

#include "includes.h"

 

 

 

/*

*********************************************************************************************************

*                                      用於本文件的調試

*********************************************************************************************************

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

 

 

/*

*********************************************************************************************************

*                                          變量

*********************************************************************************************************

*/

char const *hosts[4] =

{

     "smtp.sina.cn",

     "smtp.126.com",

     "smtp.qq.com",

     "smtp.163.com",

};

 

U8 srv_ip[4];

U8 dns_flag = 0;

 

 

/*

*********************************************************************************************************

*    函 數 名: dns_cbfunc

*    功能說明: 函數get_host_by_name的回調函數。

*    形    參: event  事件類型

*             ip     如果事件類型是DNS_EVT_SUCCESS,此指針變量指向返回的IP地址緩衝區。

*    返 回 值: 無

*********************************************************************************************************

*/

static void dns_cbfunc (unsigned char event, unsigned char *ip)

{

     switch (event)

     {

         /* 成功解析地址 */

         case DNS_EVT_SUCCESS:

              printf_debug("DNS解析出郵箱服務器IP地址 : %d.%d.%d.%d\n",ip[0],ip[1],ip[2],ip[3]);

 

              srv_ip[0] = ip[0];

              srv_ip[1] = ip[1];

              srv_ip[2] = ip[2];

              srv_ip[3] = ip[3];

              dns_flag = 1;

              break;

        

         /* DNS記錄數據庫中不存在此地址 */

         case DNS_EVT_NONAME:

              printf_debug("Host Name does not exist in DNS record database.\n");

              break;

 

         /* 允許的DNS解析重試次數已經用完,仍無法解析,時間超時 */

         case DNS_EVT_TIMEOUT:

              printf_debug("DNS Resolver Timeout expired, Host Address not resolved.\n");

              break;

 

         /* DNS協議錯誤,收到無效或者被損壞的回覆 */

         case DNS_EVT_ERROR:

              printf_debug("DNS Resolver Protocol Error, Host Address not resolved.\n");

              return;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: smtp_cback

*    功能說明: 函數smtp_connect的回調函數。

*    形    參: event  事件類型

*    返 回 值: 無

*********************************************************************************************************

*/

static void smtp_cback(U8 event)

{

     switch (event)

     {

         /* 郵件發送成功 */

         case SMTP_EVT_SUCCESS:

              printf_debug ("Email successfully sent\r\n");

              break;

        

         /* 超時,郵件未發送成功 */

         case SMTP_EVT_TIMEOUT:

              printf_debug ("Mail Server timeout.\r\n");

              break;

        

         /* 郵件發送失敗 */

         case SMTP_EVT_ERROR:

              printf_debug ("Error sending email.\r\n");

              break;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: TCPnetTest

*    功能說明: TCPent測試函數。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

    

     while (1)

     {

         os_evt_wait_or(0x0003, 0xFFFF); 

        

          /* 如果接受到按鍵K2按下的消息,解析smtp.sina.cn的IP地址 */

         xResult = os_evt_get ();

         if(xResult == KEY2_BIT1)

         {

              get_host_by_name ((U8 *)hosts[0], dns_cbfunc);

         }

        

         /* 解析出IP地址後,發送郵件 */

         if(dns_flag == 1)

         {

              smtp_connect ((U8 *)&srv_ip, 25, smtp_cback);     

              dns_flag = 0;         

         }

             

         while (main_TcpNet() == __TRUE);

     }

}

SMTP用戶接口文件的實現

KEIL官網有提供SMTP的接口文件,名爲SMTP_uif.c文件。我們就是在這個文件上修改。具體修改後的代碼如下:

#include <Net_Config.h>

#include <string.h>

#include <stdio.h>

 

/* Email definitions */

#define MAIL_FROM       "[email protected]" 

#define RCPT_TO         "[email protected]"

#define SMTP_USER       "[email protected]"

#define SMTP_PASSWORD   "xxxxxxxxx"

#define MAIL_SUBJECT    "安富萊電子"

 

 

#define MSG_HEADER                                                       \

  "Hello,大家好!\r\n\r\n"                                               \

  "這是一封來自STM32-V6開發板發出的郵件,運行的RL-TCPnet小型協議棧\r\n"  \

  "在這裏祝福大家身體健康,萬事如意\r\n\r\n"                             \

  "--------------------------------------------------------------\r\n"

 

#define MSG_FOOTER                                                    \

  "--------------------------------------------------------------\r\n"\

  "此致敬禮\r\n\r\n"                                  \

  "安富萊電子"

 

 

/* My structure of SMTP U32 storage variable. This variable is private and  */

/* is not altered by SMTP Client. It is only set to zero when smtp_cbfunc() */

/* is called for the first time.  */

typedef struct {

  U8  id;

  U16 idx;

} MY_BUF;

#define MYBUF(p)        ((MY_BUF *)p)

 

/* Net_Config.c */

extern struct sys_cfg   sys_config;

#define lhost_name      sys_config.HostName

 

/*----------------------------------------------------------------------------

 *      SMTP CallBack Functions

 *---------------------------------------------------------------------------*/

 

/*--------------------------- smtp_cbfunc -----------------------------------*/

 

U16 smtp_cbfunc (U8 code, U8 *buf, U16 buflen, U32 *pvar) {

  /* This function is called by the SMTP client to get email parameters and */

  /* data. It returns the number of bytes written to the output buffer.     */

  /* Hi-bit of return value (len is or-ed with 0x8000) is a repeat flag the */

  /* SMTP client. If this bit is set to 1, the system will call this func   */

  /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer   */

  /* can be used for storing different status variables for this function.  */

  /* It is set to 0 by SMTP client on first call and is not altered by SMTP */

  /* client for repeated calls. This function should NEVER write more than  */

  /* 'buflen' bytes to the buffer.                                          */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 - Username:   - for SMTP authentication if requested     */

  /*             1 - Password:   - for SMTP authentication if requested     */

  /*             2 - 'From   : ' - get email address of the sender          */

  /*             3 - 'To     : ' - get email address of recipient           */

  /*             4 - 'Subject: ' - get subject of email                     */

  /*             5 - 'Data   : ' - get email data in plain ascii format     */

  /*   buf    - SMTP transmit buffer                                        */

  /*   buflen - length of this buffer (500-1400 bytes - depends on MSS)     */

  /*   pvar   - pointer to local storage buffer used for repeated loops     */

  /*            This is a U32 variable - size is 4 bytes. Value is:         */

  /*            - on 1st call = 0                                           */

  /*            - 2nd call    = as set by this function on first call       */

  U32 len = 0;

 

  switch (code) {

    case 0:

      /* Enter Username for SMTP Server authentication. */

      len = str_copy (buf, SMTP_USER);

      break;

 

    case 1:

      /* Enter Password for SMTP Server authentication. */

      len = str_copy (buf, SMTP_PASSWORD);

      break;

 

    case 2:

      /* Enter email address of the sender. */

      len = str_copy (buf, MAIL_FROM);

      break;

 

    case 3:

      /* Enter email address of the recipient. */

      len = str_copy (buf, RCPT_TO);

      break;

 

    case 4:

      /* Enter email subject. */

      len = str_copy (buf, MAIL_SUBJECT);

      break;

 

    case 5:

      /* Enter email data. */

      switch (MYBUF(pvar)->id) {

        case 0:

          /* First call, enter an email header text. */

          len = sprintf ((char *)buf, MSG_HEADER);

          MYBUF(pvar)->id  = 1;

          MYBUF(pvar)->idx = 1;

          goto rep;

 

        case 1:

           /* Add email message body. */

           /* Let's use as much of the buffer as possible. */

          /* This will produce less packets and speedup the transfer. */

           len = sprintf ((char *)(buf), "%d. ",MYBUF(pvar)->idx);

          

           len += str_copy (buf+len, "用戶可以在這裏添加數據\n");

           if (++MYBUF(pvar)->idx > 5)

           {

               MYBUF(pvar)->id = 2;

           }

          /* Request a repeated call, bit 15 is a repeat flag. */

rep:      len |= 0x8000;

          break;

 

        case 2:

          /* Last one, add a footer text to this email. */

          len = str_copy (buf, MSG_FOOTER);

          break;

      }

  }

  return ((U16)len);

}

 

 

/*--------------------------- smtp_accept_auth ------------------------------*/

 

BOOL smtp_accept_auth (U8 *srv_ip) {

  /* SMTP server with address 'srv_ip' is asking for user authentication. */

  /* Return value: __TRUE  = use the authentication                       */

  /*               __FALSE = do not use the authentication                */

 

  /* Accept the authentication. */

  return (__TRUE);

}

 

/*----------------------------------------------------------------------------

 * end of file

 *---------------------------------------------------------------------------*/

34.6.2 STM32F429開發板實驗

配套例子:

V6-1048_RL-TCPnet實驗_SMTP郵件發送(RTX)

實驗目的:

  1. 學習RL-TCPnet的SMTP郵件發送。

實驗內容:

  1. 用戶務必將網線接到能夠聯網的路由器或者交換機上面測試,因爲DNS域名解析和SMTP發送郵件需要連接網絡才行。
  2. 由於126,163和QQ郵箱的通信都做了加密,所以RL-TCPnet的SMTP無法進行郵箱通信。當前測試新浪郵箱還可以使用,所以大家使用前務必要註冊新浪郵箱。根據註冊的新浪郵箱,SMTP_uif.c文件開頭代碼中的發送郵箱、接收郵箱、用戶名、用戶密碼和郵件主題是需要用戶填寫的。 #define MAIL_FROM        "[email protected]"  發送郵箱 #define RCPT_TO           "[email protected]"  接收郵箱 #define SMTP_USER        "[email protected]"  發送郵箱的用戶名 #define SMTP_PASSWORD  "xxxxxxxxx"                 發送郵箱的密碼 #define MAIL_SUBJECT      "安富萊電子"                郵件主題
  3. 郵件發送的具體操作步驟,務必看本章節配套教程的使用說明。
  4. 我們這裏實現郵件的自收發,也就是開發板的SMTP Client登錄新浪郵箱,然後自己給自己發郵件。發送後,大家可以在電腦端或者手機端登錄郵箱,並查看郵箱的內容。
  5. 按下按鍵K2將啓動郵件發送。

實驗操作:

詳見本章節34.5小節。

配置嚮導文件設置(Net_Config.c):

詳見本章節34.3小節。

調試文件設置(Net_Debug.c):

詳見本章節34.4小節。

RTX配置:

RTX配置嚮導詳情如下:

Task Configuration

(1)Number of concurrent running tasks

允許創建6個任務,實際創建瞭如下5個任務:

AppTaskUserIF任務   :按鍵消息處理。

AppTaskLED任務     :LED閃爍。

AppTaskMsgPro任務 :按鍵檢測。

AppTaskTCPMain任務:RL-TCPnet測試任務。

AppTaskStart任務  :啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。

(2)Number of tasks with user-provided stack

創建的5個任務都是採用自定義堆棧方式。

(3)Run in privileged mode

設置任務運行在非特權級模式。

RTX任務調試信息:

程序設計:

任務棧大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任務棧 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任務棧 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任務棧 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任務棧 */

static uint64_t AppTaskStartStk[1024/8];     /* 任務棧 */

將任務棧定義成uint64_t類型可以保證任務棧是8字節對齊的,8字節對齊的含義就是數組的首地址對8求餘等於0。如果不做8字節對齊的話,部分C語言庫函數、浮點運算和uint64_t類型數據運算會出問題。

系統棧大小分配:

RTX初始化:

/*

*********************************************************************************************************

*    函 數 名: main

*    功能說明: 標準c程序入口。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

int main (void)

{   

     /* 初始化外設 */

     bsp_Init();

    

     /* 創建啓動任務 */

     os_sys_init_user (AppTaskStart,              /* 任務函數 */

                       5,                         /* 任務優先級 */

                       &AppTaskStartStk,          /* 任務棧 */

                       sizeof(AppTaskStartStk));  /* 任務棧大小,單位字節數 */

     while(1);

}

硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*

*********************************************************************************************************

*    函 數 名: bsp_Init

*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次

*    形    參:無

*    返 回 值: 無

*********************************************************************************************************

*/

void bsp_Init(void)

{

     /*

         由於ST固件庫的啓動文件已經執行了CPU系統時鐘的初始化,所以不必再次重複配置系統時鐘。

         啓動文件配置了CPU主時鐘頻率、內部Flash訪問速度和可選的外部SRAM FSMC初始化。

 

         系統時鐘缺省配置爲168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 優先級分組設置爲4,可配置0-15級搶佔式優先級,0級子優先級,即不存在子優先級。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

 

     SystemCoreClockUpdate();    /* 根據PLL配置更新系統時鐘頻率變量 SystemCoreClock */

 

     bsp_InitDWT();      /* 初始化DWT */

     bsp_InitUart();     /* 初始化串口 */

     bsp_InitKey();     /* 初始化按鍵變量(必須在 bsp_InitTimer() 之前調用) */

 

     bsp_InitExtIO();    /* FMC總線上擴展了32位輸出IO, 操作LED等外設必須初始化 */

     bsp_InitLed();      /* 初始LED指示燈端口 */

}

RTX任務創建:

/*

*********************************************************************************************************

*    函 數 名: AppTaskCreate

*    功能說明: 創建應用任務

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任務函數 */

                                           1,                         /* 任務優先級 */

                                           &AppTaskUserIFStk,         /* 任務棧 */

                                           sizeof(AppTaskUserIFStk)); /* 任務棧大小,單位字節數 */

    

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任務函數 */

                                        2,                       /* 任務優先級 */

                                        &AppTaskLEDStk,          /* 任務棧 */

                                        sizeof(AppTaskLEDStk));  /* 任務棧大小,單位字節數 */

    

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任務函數 */

                                           3,                         /* 任務優先級 */

                                           &AppTaskMsgProStk,         /* 任務棧 */

                                           sizeof(AppTaskMsgProStk)); /* 任務棧大小,單位字節數 */

    

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任務函數 */

                                           4,                         /* 任務優先級 */

                                           &AppTaskTCPMainStk,         /* 任務棧 */

                                           sizeof(AppTaskTCPMainStk)); /* 任務棧大小,單位字節數 */

}

五個RTX任務的實現:

/*

*********************************************************************************************************

*    函 數 名: AppTaskUserIF

*    功能說明: 按鍵消息處理     

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 1  (數值越小優先級越低,這個跟uCOS相反)

*********************************************************************************************************

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

 

    while(1)

    {

         ucKeyCode = bsp_GetKey();

        

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1鍵按下 */

                   case KEY_DOWN_K1:

                       printf("K1鍵按下 \r\n");       

                       break;  

 

/* K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit1被設置\r\n");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                  

                   /* K3鍵按下 */

                   case KEY_DOWN_K3:

                       printf("K3鍵按下 \r\n");

                       break;

 

                   /* 其他的鍵值不處理 */

                   default:                    

                       break;

              }

         }

        

         os_dly_wait(20);

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskLED

*    功能說明: LED閃爍。

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 2 

*********************************************************************************************************

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = 500; /* 延遲週期 */

    

     /* 設置延遲週期 */

     os_itv_set(usFrequency);

    

    while(1)

    {

         bsp_LedToggle(2);

 

         /* os_itv_wait是絕對延遲,os_dly_wait是相對延遲。*/

         os_itv_wait();

    }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskMsgPro

*    功能說明: 按鍵檢測

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 3 

*********************************************************************************************************

*/

__task void AppTaskMsgPro(void)

{

    while(1)

    {

         bsp_KeyScan();

         os_dly_wait(10);

    }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskTCPMain

*    功能說明: RL-TCPnet測試任務

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 4 

*********************************************************************************************************

*/

__task void AppTaskTCPMain(void)

{

     while (1)

     {

         TCPnetTest();

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: AppTaskStart

*    功能說明: 啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。

*    形    參: 無

*    返 回 值: 無

*   優 先 級: 5 

*********************************************************************************************************

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

    

     /* 創建任務 */

     AppTaskCreate();

    

     os_itv_set (100);

    

    while(1)

    {

         os_itv_wait ();

        

         /* RL-TCPnet時間基準更新函數 */

         timer_tick ();

         os_evt_set(0x0001, HandleTaskTCPMain);

    }

}

RL-TCPnet功能測試

這裏專門創建了一個app_tcpnet_lib.c文件用於RL-TCPnet功能的測試,此文件主要實現網絡主函數main_TcpNet的調用和郵件的發送。

#include "includes.h"

 

 

 

/*

*********************************************************************************************************

*                                      用於本文件的調試

*********************************************************************************************************

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

 

 

/*

*********************************************************************************************************

*                                          變量

*********************************************************************************************************

*/

char const *hosts[4] =

{

     "smtp.sina.cn",

     "smtp.126.com",

     "smtp.qq.com",

     "smtp.163.com",

};

 

U8 srv_ip[4];

U8 dns_flag = 0;

 

 

/*

*********************************************************************************************************

*    函 數 名: dns_cbfunc

*    功能說明: 函數get_host_by_name的回調函數。

*    形    參: event  事件類型

*             ip     如果事件類型是DNS_EVT_SUCCESS,此指針變量指向返回的IP地址緩衝區。

*    返 回 值: 無

*********************************************************************************************************

*/

static void dns_cbfunc (unsigned char event, unsigned char *ip)

{

     switch (event)

     {

         /* 成功解析地址 */

         case DNS_EVT_SUCCESS:

              printf_debug("DNS解析出郵箱服務器IP地址 : %d.%d.%d.%d\n",ip[0],ip[1],ip[2],ip[3]);

 

              srv_ip[0] = ip[0];

              srv_ip[1] = ip[1];

              srv_ip[2] = ip[2];

              srv_ip[3] = ip[3];

              dns_flag = 1;

              break;

        

         /* DNS記錄數據庫中不存在此地址 */

         case DNS_EVT_NONAME:

              printf_debug("Host Name does not exist in DNS record database.\n");

              break;

 

         /* 允許的DNS解析重試次數已經用完,仍無法解析,時間超時 */

         case DNS_EVT_TIMEOUT:

              printf_debug("DNS Resolver Timeout expired, Host Address not resolved.\n");

              break;

 

         /* DNS協議錯誤,收到無效或者被損壞的回覆 */

         case DNS_EVT_ERROR:

              printf_debug("DNS Resolver Protocol Error, Host Address not resolved.\n");

              return;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: smtp_cback

*    功能說明: 函數smtp_connect的回調函數。

*    形    參: event  事件類型

*    返 回 值: 無

*********************************************************************************************************

*/

static void smtp_cback(U8 event)

{

     switch (event)

     {

         /* 郵件發送成功 */

         case SMTP_EVT_SUCCESS:

              printf_debug ("Email successfully sent\r\n");

              break;

        

         /* 超時,郵件未發送成功 */

         case SMTP_EVT_TIMEOUT:

              printf_debug ("Mail Server timeout.\r\n");

              break;

        

         /* 郵件發送失敗 */

         case SMTP_EVT_ERROR:

              printf_debug ("Error sending email.\r\n");

              break;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: TCPnetTest

*    功能說明: TCPent測試函數。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

    

     while (1)

     {

         os_evt_wait_or(0x0003, 0xFFFF); 

        

          /* 如果接受到按鍵K2按下的消息,解析smtp.sina.cn的IP地址 */

         xResult = os_evt_get ();

         if(xResult == KEY2_BIT1)

         {

              get_host_by_name ((U8 *)hosts[0], dns_cbfunc);

         }

        

         /* 解析出IP地址後,發送郵件 */

         if(dns_flag == 1)

         {

              smtp_connect ((U8 *)&srv_ip, 25, smtp_cback);     

              dns_flag = 0;         

         }

             

         while (main_TcpNet() == __TRUE);

     }

}

SMTP用戶接口文件的實現

KEIL官網有提供SMTP的接口文件,名爲SMTP_uif.c文件。我們就是在這個文件上修改。具體修改後的代碼如下:

#include <Net_Config.h>

#include <string.h>

#include <stdio.h>

 

/* Email definitions */

#define MAIL_FROM       "[email protected]" 

#define RCPT_TO         "[email protected]"

#define SMTP_USER       "[email protected]"

#define SMTP_PASSWORD   "xxxxxxxxx"

#define MAIL_SUBJECT    "安富萊電子"

 

 

#define MSG_HEADER                                                       \

  "Hello,大家好!\r\n\r\n"                                               \

  "這是一封來自STM32-V6開發板發出的郵件,運行的RL-TCPnet小型協議棧\r\n"  \

  "在這裏祝福大家身體健康,萬事如意\r\n\r\n"                             \

  "--------------------------------------------------------------\r\n"

 

#define MSG_FOOTER                                                    \

  "--------------------------------------------------------------\r\n"\

  "此致敬禮\r\n\r\n"                                  \

  "安富萊電子"

 

 

/* My structure of SMTP U32 storage variable. This variable is private and  */

/* is not altered by SMTP Client. It is only set to zero when smtp_cbfunc() */

/* is called for the first time.  */

typedef struct {

  U8  id;

  U16 idx;

} MY_BUF;

#define MYBUF(p)        ((MY_BUF *)p)

 

/* Net_Config.c */

extern struct sys_cfg   sys_config;

#define lhost_name      sys_config.HostName

 

/*----------------------------------------------------------------------------

 *      SMTP CallBack Functions

 *---------------------------------------------------------------------------*/

 

/*--------------------------- smtp_cbfunc -----------------------------------*/

 

U16 smtp_cbfunc (U8 code, U8 *buf, U16 buflen, U32 *pvar) {

  /* This function is called by the SMTP client to get email parameters and */

  /* data. It returns the number of bytes written to the output buffer.     */

  /* Hi-bit of return value (len is or-ed with 0x8000) is a repeat flag the */

  /* SMTP client. If this bit is set to 1, the system will call this func   */

  /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer   */

  /* can be used for storing different status variables for this function.  */

  /* It is set to 0 by SMTP client on first call and is not altered by SMTP */

  /* client for repeated calls. This function should NEVER write more than  */

  /* 'buflen' bytes to the buffer.                                          */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 - Username:   - for SMTP authentication if requested     */

  /*             1 - Password:   - for SMTP authentication if requested     */

  /*             2 - 'From   : ' - get email address of the sender          */

  /*             3 - 'To     : ' - get email address of recipient           */

  /*             4 - 'Subject: ' - get subject of email                     */

  /*             5 - 'Data   : ' - get email data in plain ascii format     */

  /*   buf    - SMTP transmit buffer                                        */

  /*   buflen - length of this buffer (500-1400 bytes - depends on MSS)     */

  /*   pvar   - pointer to local storage buffer used for repeated loops     */

  /*            This is a U32 variable - size is 4 bytes. Value is:         */

  /*            - on 1st call = 0                                           */

  /*            - 2nd call    = as set by this function on first call       */

  U32 len = 0;

 

  switch (code) {

    case 0:

      /* Enter Username for SMTP Server authentication. */

      len = str_copy (buf, SMTP_USER);

      break;

 

    case 1:

      /* Enter Password for SMTP Server authentication. */

      len = str_copy (buf, SMTP_PASSWORD);

      break;

 

    case 2:

      /* Enter email address of the sender. */

      len = str_copy (buf, MAIL_FROM);

      break;

 

    case 3:

      /* Enter email address of the recipient. */

      len = str_copy (buf, RCPT_TO);

      break;

 

    case 4:

      /* Enter email subject. */

      len = str_copy (buf, MAIL_SUBJECT);

      break;

 

    case 5:

      /* Enter email data. */

      switch (MYBUF(pvar)->id) {

        case 0:

          /* First call, enter an email header text. */

          len = sprintf ((char *)buf, MSG_HEADER);

          MYBUF(pvar)->id  = 1;

          MYBUF(pvar)->idx = 1;

          goto rep;

 

        case 1:

           /* Add email message body. */

           /* Let's use as much of the buffer as possible. */

          /* This will produce less packets and speedup the transfer. */

           len = sprintf ((char *)(buf), "%d. ",MYBUF(pvar)->idx);

          

           len += str_copy (buf+len, "用戶可以在這裏添加數據\n");

           if (++MYBUF(pvar)->idx > 5)

           {

               MYBUF(pvar)->id = 2;

           }

          /* Request a repeated call, bit 15 is a repeat flag. */

rep:      len |= 0x8000;

          break;

 

        case 2:

          /* Last one, add a footer text to this email. */

          len = str_copy (buf, MSG_FOOTER);

          break;

      }

  }

  return ((U16)len);

}

 

 

/*--------------------------- smtp_accept_auth ------------------------------*/

 

BOOL smtp_accept_auth (U8 *srv_ip) {

  /* SMTP server with address 'srv_ip' is asking for user authentication. */

  /* Return value: __TRUE  = use the authentication                       */

  /*               __FALSE = do not use the authentication                */

 

  /* Accept the authentication. */

  return (__TRUE);

}

 

/*----------------------------------------------------------------------------

 * end of file

 *---------------------------------------------------------------------------*/

34.7 總結

本章節就爲大家講解這麼多,其中SMTP的測試稍麻煩些,希望大家實際動手操作一遍,並將其熟練掌握。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章