【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邮箱发送邮件,收到“系统退信”邮件:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

34.2  SMTP函数

使用如下3个函数可以实现RL-TCPnet的SMTP:

  • smtp_cbfunc
  • smtp_connect
  • smtp_accept_auth

关于这3个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

关于这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[] ==   &&

      srv_ip[] ==   &&

      srv_ip[] ==     &&

      srv_ip[] == )

{

     /* 如果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会话即将结束时,会调用这个函数。此回调函数只有一个形参,形参类型如下:【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端
  4. 返回值,返回__TRUE表示SMTP客户端启动成功(注意,仅仅是客户端启动成功,并不是邮件发送成功),返回__FALSE表示启动失败。

使用这个函数要注意以下问题:

  1. 标准SMTP的端口号是用的TCP端口25。
  2. 用户是通过此函数启动RL-TCPnet的SMTP客户端登录SMTP服务器进行邮件发送。

使用举例:

/*

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

*                                          变量

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

*/

char const *hosts[] =

{

     "smtp.sina.cn",

     "smtp.126.com",

     "smtp.qq.com",

     "smtp.163.com",

};

U8 srv_ip[];

U8 dns_flag = ;

/*

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

*    函 数 名: 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[],ip[],ip[],ip[]);

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              dns_flag = ;

              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 ()

     {

         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[], dns_cbfunc);

         }

         /* 解析出IP地址后,发送邮件 */

         if(dns_flag == )

         {

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

              dns_flag = ;         

         }

         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客户端需要的电子邮件数据类型,比如发送邮箱、接收邮箱、用户名、用户密码、邮件内容等。具体支持的类型如下:【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端
  2. 第2个参数是输出缓冲区地址,用于函数smtp_cbfunc执行过程中存储要发送的邮件内容。
  3. 第3个参数是输出缓冲区的大小,单位字节。
  4. 第4个参数指向不会被SMTP客户端更改的变量。用户可以将其作为重复计数器,或者简单地区分smtp_cbfunc函数的不同调用,亦或者任何其它应用均可。
  5. 返回值,返回写入到输出缓冲区的字节数。返回值是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       "baiyongbin_2006@sina.cn" 

#define RCPT_TO         "baiyongbin_2006@sina.cn"

#define SMTP_USER       "baiyongbin_2006@sina.cn"

#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 = ;

  switch (code) {

    case :

      /* Enter Username for SMTP Server authentication. */

      len = str_copy (buf, SMTP_USER);

      break;

    case :

      /* Enter Password for SMTP Server authentication. */

      len = str_copy (buf, SMTP_PASSWORD);

      break;

    case :

      /* Enter email address of the sender. */

      len = str_copy (buf, MAIL_FROM);

      break;

    case :

      /* Enter email address of the recipient. */

      len = str_copy (buf, RCPT_TO);

      break;

    case :

      /* Enter email subject. */

      len = str_copy (buf, MAIL_SUBJECT);

      break;

    case :

      /* Enter email data. */

      switch (MYBUF(pvar)->id) {

        case :

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

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

          MYBUF(pvar)->id  = ;

          MYBUF(pvar)->idx = ;

          goto rep;

        case :

           /* 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 > )

           {

               MYBUF(pvar)->id = ;

           }

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

rep:      len |= 0x8000;

          break;

        case :

          /* 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网络教程】第34章	 RL-TCPnet之SMTP客户端

RL-TCPnet要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

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。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

Ethernet Network Interface

以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP

(1)  MAC Address

局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。

(2) IP Address

IP地址。

(3) Subnet mask

子网掩码。

(4) Default Gateway

默认网关。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

Ethernet Network Interface

以太网接口配置,这个配置里面还有如下两项比较重要的配置要说明。

(1)  NetBIOS Name Service

NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。

(2)  Dynaminc Host Configuration

即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

UDP Sockets

UDP Sockets配置,打上对勾就使能了此项功能

(1)  Number of UDP Sockets

用于配置可创建的UDP Sockets数量,这里配置了5个。

范围1 – 20。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

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接收窗口大小。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

DNS Client

DNS 配置,打上对勾就使能了此项功能

(1)  Cache Table size

主机名/IP地址的缓存表大小。

范围5-100。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

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,可以看到下图所示的工程配置向导:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

Print Time Stamp

勾选了此选项的话,打印消息时,前面会附带时间信息。

其它所有的选项

默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以SMTP Client Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

Off:表示关闭此选项的调试功能。

Errors only:表示仅在此选项出错时,将其错误打印出来。

Full debug:表示此选项的全功能调试。

现在我们使能Full debug并全编译工程下载到板子里面,让开发板登录邮箱baiyongbin_2006@sina.cn,然后给自己发一封邮件,下面就是一个完成的邮件发送成功的过程:

000.0 SMTP:Init Client

005.1 SMTP:Connect Client

005.1 SMTP: Server: 202.108.5.186

005.1 SMTP: Port  : 

005.2 SMTP:Socket connected

005.5 SMTP:*** Processing frame ***

005.5 SMTP: Received length:  bytes

005.5 SMTP: Server is ready

005.5 SMTP: Sending: EHLO armfly

005.5 SMTP:*** Processing frame ***

005.5 SMTP: Received length:  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:  bytes

005.6 SMTP: Sending: YmFpeW9uZ2Jpbl8YMDA2QHNpbmEuY24=

005.6 SMTP:*** Processing frame ***

005.6 SMTP: Received length:  bytes

005.6 SMTP: Sending: QkFJeW9uZ2Jpbl9fMTU=

005.8 SMTP:*** Processing frame ***

005.8 SMTP: Received length:  bytes

005.8 SMTP: Authentication successful

005.8 SMTP: Sending: MAIL FROM: <baiyongbin_2006@sina.cn>

005.9 SMTP:*** Processing frame ***

005.9 SMTP: Received length:  bytes

005.9 SMTP: Server acknowledge

005.9 SMTP: Sending: RCPT TO: <baiyongbin_2006@sina.cn>

005.9 SMTP:*** Processing frame ***

005.9 SMTP: Received length:  bytes

005.9 SMTP: Server acknowledge

005.9 SMTP: Sending: DATA

006.0 SMTP:*** Processing frame ***

006.0 SMTP: Received length:  bytes

006.0 SMTP: Server acknowledge

006.0 SMTP: Sending 'Message Body'

006.1 SMTP:*** Processing frame ***

006.1 SMTP: Received length:  bytes

006.1 SMTP: End of Message acknowledge

006.1 SMTP: Sending: QUIT

006.1 SMTP:*** Processing frame ***

006.1 SMTP: Received length:  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服务功能。

登录邮箱后,点击左上角的设置选项:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

在弹出的页面中,看左侧栏,选择“客户端pop/imap/smtp”。

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

开启POP3/SMTP服务:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

IMAP4服务/SMTP服务也开启:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

最后,切不要忘记保存:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

保存后就可以进行测试了。

2、修改SMTP_uif.c文件开头的邮箱信息

注册并设置完毕新浪邮箱后,需要再设置下工程中SMTP_uif.c文件开头的邮箱信息,比如我们刚刚注册的邮箱是amfly_2017@sina.cn,密码是amfly123456(这个密码和账号是随便写的,仅仅是举个例子)。那么开头的信息就需要修改为如下:

#define MAIL_FROM       "amfly_2017@sina.cn"   /* 发送邮箱 */

#define RCPT_TO          "amfly_2017@sina.cn"   /* 接收邮箱 */

#define SMTP_USER       "amfly_2017@sina.cn"   /* 发送邮箱的用户名 */

#define SMTP_PASSWORD "amfly123456"          /* 发送邮箱密码*/

#define MAIL_SUBJECT     "安富莱电子"

我们这里是做了一个自发自收的功能,即开发板登录新浪邮箱,给自己发邮件。用户就可以在手机端或者电脑端登录邮箱,查看接收到的内容。

注:接收邮箱选项不支持126,163和QQ邮箱。

34.5.2 邮件发送

使用这个例子,务必要将开发板接到能够联网的路由器或者交换机上,因为DNS域名解析和SMTP发送邮件需要连接网络才行。

将本章节的程序下载到板子里面,并将板子重新上电后,按下K2按键(打印完毕硬件初始化后再按),可以看到串口调试助手打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

从上面的打印信息中可以看到,邮件已经发送成功,现在就可以登录新浪邮箱查看接收到的内容了,如果看不到的话,刷新下网页即可:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

至此就完成了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        "baiyongbin_2006@sina.cn"  发送邮箱

    #define RCPT_TO           "baiyongbin_2006@sina.cn"  接收邮箱

    #define SMTP_USER        "baiyongbin_2006@sina.cn"  发送邮箱的用户名

    #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配置向导详情如下:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

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任务调试信息:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

程序设计:

任务栈大小分配:

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类型数据运算会出问题。

系统栈大小分配:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

RTX初始化:

/*

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

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 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,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

五个RTX任务的实现:

/*

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

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

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

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         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();

     }

}

/*

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

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

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

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

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

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

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

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

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

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

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

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         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[] =

{

     "smtp.sina.cn",

     "smtp.126.com",

     "smtp.qq.com",

     "smtp.163.com",

};

U8 srv_ip[];

U8 dns_flag = ;

/*

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

*    函 数 名: 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[],ip[],ip[],ip[]);

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              dns_flag = ;

              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 ()

     {

         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[], dns_cbfunc);

         }

         /* 解析出IP地址后,发送邮件 */

         if(dns_flag == )

         {

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

              dns_flag = ;         

         }

         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       "baiyongbin_2006@sina.cn" 

#define RCPT_TO         "baiyongbin_2006@sina.cn"

#define SMTP_USER       "baiyongbin_2006@sina.cn"

#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 = ;

  switch (code) {

    case :

      /* Enter Username for SMTP Server authentication. */

      len = str_copy (buf, SMTP_USER);

      break;

    case :

      /* Enter Password for SMTP Server authentication. */

      len = str_copy (buf, SMTP_PASSWORD);

      break;

    case :

      /* Enter email address of the sender. */

      len = str_copy (buf, MAIL_FROM);

      break;

    case :

      /* Enter email address of the recipient. */

      len = str_copy (buf, RCPT_TO);

      break;

    case :

      /* Enter email subject. */

      len = str_copy (buf, MAIL_SUBJECT);

      break;

    case :

      /* Enter email data. */

      switch (MYBUF(pvar)->id) {

        case :

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

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

          MYBUF(pvar)->id  = ;

          MYBUF(pvar)->idx = ;

          goto rep;

        case :

           /* 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 > )

           {

               MYBUF(pvar)->id = ;

           }

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

rep:      len |= 0x8000;

          break;

        case :

          /* 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        "baiyongbin_2006@sina.cn"  发送邮箱

    #define RCPT_TO           "baiyongbin_2006@sina.cn"  接收邮箱

    #define SMTP_USER        "baiyongbin_2006@sina.cn"  发送邮箱的用户名

    #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配置向导详情如下:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

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任务调试信息:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

程序设计:

任务栈大小分配:

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类型数据运算会出问题。

系统栈大小分配:

【RL-TCPnet网络教程】第34章	 RL-TCPnet之SMTP客户端

RTX初始化:

/*

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

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 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,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

五个RTX任务的实现:

/*

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

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

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

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         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();

     }

}

/*

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

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

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

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

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

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

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

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

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

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

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

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         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[] =

{

     "smtp.sina.cn",

     "smtp.126.com",

     "smtp.qq.com",

     "smtp.163.com",

};

U8 srv_ip[];

U8 dns_flag = ;

/*

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

*    函 数 名: 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[],ip[],ip[],ip[]);

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              srv_ip[] = ip[];

              dns_flag = ;

              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 ()

     {

         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[], dns_cbfunc);

         }

         /* 解析出IP地址后,发送邮件 */

         if(dns_flag == )

         {

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

              dns_flag = ;         

         }

         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       "baiyongbin_2006@sina.cn" 

#define RCPT_TO         "baiyongbin_2006@sina.cn"

#define SMTP_USER       "baiyongbin_2006@sina.cn"

#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 = ;

  switch (code) {

    case :

      /* Enter Username for SMTP Server authentication. */

      len = str_copy (buf, SMTP_USER);

      break;

    case :

      /* Enter Password for SMTP Server authentication. */

      len = str_copy (buf, SMTP_PASSWORD);

      break;

    case :

      /* Enter email address of the sender. */

      len = str_copy (buf, MAIL_FROM);

      break;

    case :

      /* Enter email address of the recipient. */

      len = str_copy (buf, RCPT_TO);

      break;

    case :

      /* Enter email subject. */

      len = str_copy (buf, MAIL_SUBJECT);

      break;

    case :

      /* Enter email data. */

      switch (MYBUF(pvar)->id) {

        case :

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

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

          MYBUF(pvar)->id  = ;

          MYBUF(pvar)->idx = ;

          goto rep;

        case :

           /* 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 > )

           {

               MYBUF(pvar)->id = ;

           }

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

rep:      len |= 0x8000;

          break;

        case :

          /* 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的测试稍麻烦些,希望大家实际动手操作一遍,并将其熟练掌握。

上一篇:python argparse(参数解析)模块学习(二)


下一篇:配置java软件