【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会话即将结束时,会调用这个函数。此回调函数只有一个形参,形参类型如下:
  4. 返回值,返回__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
",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.
");

              break;

 

         /* 允许的DNS解析重试次数已经用完,仍无法解析,时间超时 */

         case DNS_EVT_TIMEOUT:

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

              break;

 

         /* DNS协议错误,收到无效或者被损坏的回复 */

         case DNS_EVT_ERROR:

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

              return;

     }

}

 

/*

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

*    函 数 名: smtp_cback

*    功能说明: 函数smtp_connect的回调函数。

*    形    参: event  事件类型

*    返 回 值: 无

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

*/

static void smtp_cback(U8 event)

{

     switch (event)

     {

         /* 邮件发送成功 */

         case SMTP_EVT_SUCCESS:

              printf_debug ("Email successfully sent
");

              break;

        

         /* 超时,邮件未发送成功 */

         case SMTP_EVT_TIMEOUT:

              printf_debug ("Mail Server timeout.
");

              break;

        

         /* 邮件发送失败 */

         case SMTP_EVT_ERROR:

              printf_debug ("Error sending email.
");

              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客户端需要的电子邮件数据类型,比如发送邮箱、接收邮箱、用户名、用户密码、邮件内容等。具体支持的类型如下:
  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,大家好!

"                                               

  "这是一封来自STM32-V6开发板发出的邮件,运行的RL-TCPnet小型协议栈
"  

  "在这里祝福大家身体健康,万事如意

"                             

  "--------------------------------------------------------------
"

 

#define MSG_FOOTER                                                    

  "--------------------------------------------------------------
"

  "此致敬礼

"                                  

  "安富莱电子"

 

 

/* 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, "用户可以在这里添加数据
");

           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并全编译工程下载到板子里面,让开发板登录邮箱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  : 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: <baiyongbin_2006@sina.cn>

005.9 SMTP:*** Processing frame ***

005.9 SMTP: Received length: 8 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: 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文件开头的邮箱信息,比如我们刚刚注册的邮箱是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的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配置向导详情如下:

 

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键按下 
");       

                       break;  

 

                   /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置
");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                  

                   /* K3键按下 */

                   case KEY_DOWN_K3:

                       printf("K3键按下 
");

                       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
",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.
");

              break;

 

         /* 允许的DNS解析重试次数已经用完,仍无法解析,时间超时 */

         case DNS_EVT_TIMEOUT:

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

              break;

 

         /* DNS协议错误,收到无效或者被损坏的回复 */

         case DNS_EVT_ERROR:

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

              return;

     }

}

 

/*

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

*    函 数 名: smtp_cback

*    功能说明: 函数smtp_connect的回调函数。

*    形    参: event  事件类型

*    返 回 值: 无

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

*/

static void smtp_cback(U8 event)

{

     switch (event)

     {

         /* 邮件发送成功 */

         case SMTP_EVT_SUCCESS:

              printf_debug ("Email successfully sent
");

              break;

        

         /* 超时,邮件未发送成功 */

         case SMTP_EVT_TIMEOUT:

              printf_debug ("Mail Server timeout.
");

              break;

        

         /* 邮件发送失败 */

         case SMTP_EVT_ERROR:

              printf_debug ("Error sending email.
");

              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       "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,大家好!

"                                               

  "这是一封来自STM32-V6开发板发出的邮件,运行的RL-TCPnet小型协议栈
"  

  "在这里祝福大家身体健康,万事如意

"                             

  "--------------------------------------------------------------
"

 

#define MSG_FOOTER                                                    

  "--------------------------------------------------------------
"

  "此致敬礼

"                                  

  "安富莱电子"

 

 

/* 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, "用户可以在这里添加数据
");

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

 

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键按下 
");       

                       break;  

 

/* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置
");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                  

                   /* K3键按下 */

                   case KEY_DOWN_K3:

                       printf("K3键按下 
");

                       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
",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.
");

              break;

 

         /* 允许的DNS解析重试次数已经用完,仍无法解析,时间超时 */

         case DNS_EVT_TIMEOUT:

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

              break;

 

         /* DNS协议错误,收到无效或者被损坏的回复 */

         case DNS_EVT_ERROR:

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

              return;

     }

}

 

/*

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

*    函 数 名: smtp_cback

*    功能说明: 函数smtp_connect的回调函数。

*    形    参: event  事件类型

*    返 回 值: 无

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

*/

static void smtp_cback(U8 event)

{

     switch (event)

     {

         /* 邮件发送成功 */

         case SMTP_EVT_SUCCESS:

              printf_debug ("Email successfully sent
");

              break;

        

         /* 超时,邮件未发送成功 */

         case SMTP_EVT_TIMEOUT:

              printf_debug ("Mail Server timeout.
");

              break;

        

         /* 邮件发送失败 */

         case SMTP_EVT_ERROR:

              printf_debug ("Error sending email.
");

              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       "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,大家好!

"                                               

  "这是一封来自STM32-V6开发板发出的邮件,运行的RL-TCPnet小型协议栈
"  

  "在这里祝福大家身体健康,万事如意

"                             

  "--------------------------------------------------------------
"

 

#define MSG_FOOTER                                                    

  "--------------------------------------------------------------
"

  "此致敬礼

"                                  

  "安富莱电子"

 

 

/* 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, "用户可以在这里添加数据
");

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

原文地址:https://www.cnblogs.com/armfly/p/9585055.html