【RL-TCPnet网络教程】第30章 RL-TCPnet之SNTP网络时间获取

第30章      RL-TCPnet之SNTP网络时间获取

本章节为大家讲解RL-TCPnet的SNTP应用,学习本章节前,务必要优先学习第29章的NTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

本章教程含STM32F407开发板和STM32F429开发板。

30.1  初学者重要提示

30.2  可用的NTP服务器

30.3  SNTP函数

30.4  SNTP配置说明(Net_Config.c)

30.5  SNTP调试说明(Net_Debug.c)

30.6  网络调试助手和板子的操作步骤

30.7  实验例程说明(裸机)

30.8  实验例程说明(RTX)

30.9  总结

30.1  初学者重要提示

  1. 学习本章节前,务必保证已经学习了第29章的基础知识。
  2. 本章相对比较简单,测试本章节的例子,务必将其接到能够联网的路由器或者交换机上

30.2  可用的NTP服务器

国内可用的NTP服务器已经在帖子:http://bbs.armfly.com/read.php?tid=31397 里面进行了简单的总结,我们这里使用IP地址为182.16.3.162的NTP服务器。

30.3  SNTP函数

涉及到SNTP的,仅有如下一个函数:

  • sntp_get_time

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

注意,这个函数不支持重入,也就是不支持多任务调用。

30.3.1   函数sntp_get_time

函数原型:

BOOL sntp_get_time (

          U8*   ipadr,        /* NTP/SNTP服务器IP地址 */

          void (*cbfunc)(     /* 回调函数,接收到NTP消息会触发 */

           U32 utc_time) );   /* 接收到的UNIX时间戳,从1970.1.1开始所经历的秒数 */

函数描述:

函数sntp_get_time用于从NTP服务器获得UNIX时间戳,这个函数支持单播和广播两种模式。单播方式下,通过此函数给远程NTP服务器发送获取时间消息。广播模式下,将打开UDP Socket接收NTP广播消息,如果局域网内有NTP服务器,可以采用这种模式。(广播和单播模式是在Net_Config.c文件中设置的,如果没有选择广播Broadcast模式,就表示单播,否则表示广播。

  1. 第1个参数是NTP服务器的IP地址。

    • 单播模式,这个参数就是远程NTP服务器的IP地址。
    • 广播模式,这个参数是局域网内NTP服务器的IP地址,如果用户设置了指定的IP地址,那么将仅接收此服务器的消息,其它服务器的消息忽略。如果此IP地址被设置为0.0.0.0,那么将接收局域网内任何NTP服务器的消息。
  2. 第2个参数是回调函数,回调函数有一个参数,这个参数utc_time代表的含义如下:
    • 调用此函数返回的UNIX时间戳,从1970.1.1开始所经历的秒数,如果此数值是0的话(数值0被保留用于表示返回失败),表示获取失败。
  3. 返回值有以下两种,返回__TURE,单播模式下表示SNTP消息发送成功,广播模式下表示UDP Socket打开成功。返回__FALSE,单播模式下表示发送失败,广播模式下表示UDP Socket打开失败。

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

  1. 如果用户将第1个参数设置为NULL的话,那么将使用Net_Congfig.c文件中设置的NTP服务器地址。

使用举例:

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

                                                   变量

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

*/

U8 ntp_server[4] = {182,16,3,162};  /* SNTP服务器地址 */

/*

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

*    函 数 名: time_cback

*    功能说明: SNTP获取时间回到函数

*    形    参: 无

*    返 回 值: 无

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

*/

static void time_cback (uint32_t time)

{

     struct tm *t_tm;

     if (time == 0)

     {

         printf_debug ("错误, 服务器未响应或者网络状态比较差\r\n");

     }

     else

     {

         /* 由于是格林尼治时间,所以北京时间要加8个小时 */

         time += 8*60*60;

         /* 将获取的UNIX时间戳转换成年月日时分秒的格式 */

         t_tm = localtime((unsigned int *)&time);

         /* 格林尼治时间是从1900年开始的,这要加上 */

         t_tm->tm_year += 1900;

         /* 获取的月份范围是0-11,这里要加1 */

         t_tm->tm_mon += 1;

         printf_debug ("UNIX时间戳:%d 日期:%02d/%02d/%02d  时间:%02d:%02d:%02d\r\n",

                        time,

                        t_tm->tm_year,

                          t_tm->tm_mon,

                          t_tm->tm_mday,

                          t_tm->tm_hour,

                          t_tm->tm_min,

                          t_tm->tm_sec);

     }

}                                   

/*

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

*    函 数 名: get_time

*    功能说明: 从SNTP服务器获取当前时间

*    形    参: 无

*    返 回 值: 无

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

*/                      

static void get_time (void)

{

     if (sntp_get_time((U8 *)&ntp_server[0], &time_cback) == __TRUE)

     {

         printf_debug ("SNTP请求已经发送成功\r\n");

     }

     else

     {

         printf_debug ("失败, SNTP未就绪或者参数错误\r\n");

     }

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer(0))

     {

         bsp_LedToggle(2);

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(0, 100);

     /* 再创建一个周期是1000ms的软定时器 */

     bsp_StartAutoTimer(1, 1000);

     while (1)

     {

         /* TCP轮询 */

         tcpnet_poll();

          /* 每秒从NTP服务器获取一次时间 */

         if(bsp_CheckTimer(1))

         {

              get_time();

         }

     }

}

30.4 SNTP配置说明(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接收窗口大小。

SNTP Client

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

(1) Broadcast Mode

打上对勾表示使能广播模式,不选择表示单播模式。

(2)NTP Server

这里是NTP服务器的IP地址。

实际应用中,这两个选项的作用看本章30.3.1小节的函数sntp_get_time即可。

30.5 SNTP调试说明(Net_Debug.c)

(重要说明,RL-TCPnet的调试是通过串口打印出来的)

RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到如下图所示的工程配置向导:

Print Time Stamp

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

其它所有的选项

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

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

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

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

下面是对SNTP Debug配置为Full debug时,打印出来的消息(NTP服务器采用的182.16.3.162):

30.6 板子的操作步骤

本章的操作相对比较简单,用户务必将板子连接到能够联网的路由器或者交换机上。串口会每秒打印一次获取的UNIX时间戳(波特率115200,数据位8,奇偶校验位无,停止位1):

30.7 实验例程说明(裸机)

30.7.1 STM32F407开发板实验

配套例子:

V5-1040_RL-TCPnet实验SNTP应用(裸机)

实验目的:

  1. 学习RL-TCPnet的SNTP使用。

实验内容:

  1. 务必将网线接到能够联网的路由器或者交换机上面测试,因为本实验要用到外网。
  2. 本实验串口每秒打印一次从NTP服务器获取的时间。
  3. 国内免费稳定的SNTP服务器很少,当前使用的这个时好时坏,如果大家测试不成功的话也是正常的。

实验操作:

详见本章节30.6小节。

配置向导文件设置(Net_Config.c):

详见本章节30.4小节。

调试文件设置(Net_Debug.c):

详见本章节30.5小节。

程序设计:

主函数初始化

在main.c文件实现:

/*

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

*    函 数 名: main

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

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 进入RL-TCPnet测试函数 */

     TCPnetTest();

}

硬件外设初始化

硬件外设的初始化是在 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_InitUart();    /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

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

     bsp_InitTimer();   /* 初始化滴答定时器 */

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现从远程NTP服务器获取当前日期和时间,精度到秒。

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

                                                   变量

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

*/

U8 ntp_server[4] = {182,16,3,162};  /* SNTP服务器地址 */

/*

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

*    函 数 名: time_cback

*    功能说明: SNTP获取时间回到函数

*    形    参: 无

*    返 回 值: 无

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

*/

static void time_cback (uint32_t time)

{

     struct tm *t_tm;

     if (time == 0)

     {

         printf_debug ("错误, 服务器未响应或者网络状态比较差\r\n");

     }

     else

     {

         /* 由于是格林尼治时间,所以北京时间要加8个小时 */

         time += 8*60*60;

         /* 将获取的UNIX时间戳转换成年月日时分秒的格式 */

         t_tm = localtime((unsigned int *)&time);

         /* 格林尼治时间是从1900年开始的,这要加上 */

         t_tm->tm_year += 1900;

         /* 获取的月份范围是0-11,这里要加1 */

         t_tm->tm_mon += 1;

         printf_debug ("UNIX时间戳:%d 日期:%02d/%02d/%02d  时间:%02d:%02d:%02d\r\n",

                        time,

                        t_tm->tm_year,

                          t_tm->tm_mon,

                          t_tm->tm_mday,

                          t_tm->tm_hour,

                          t_tm->tm_min,

                          t_tm->tm_sec);

     }

}                                   

/*

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

*    函 数 名: get_time

*    功能说明: 从SNTP服务器获取当前时间

*    形    参: 无

*    返 回 值: 无

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

*/                      

static void get_time (void)

{

     if (sntp_get_time((U8 *)&ntp_server[0], &time_cback) == __TRUE)

     {

         printf_debug ("SNTP请求已经发送成功\r\n");

     }

     else

     {

         printf_debug ("失败, SNTP未就绪或者参数错误\r\n");

     }

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer(0))

     {

         bsp_LedToggle(2);

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(0, 100);

     /* 再创建一个周期是1000ms的软定时器 */

     bsp_StartAutoTimer(1, 1000);

     while (1)

     {

         /* TCP轮询 */

         tcpnet_poll();

          /* 每秒从NTP服务器获取一次时间 */

         if(bsp_CheckTimer(1))

         {

              get_time();

         }

     }

}

30.7.2 STM32F429开发板实验

配套例子:

V6-1040_RL-TCPnet实验SNTP应用(裸机)

实验目的:

  1. 学习RL-TCPnet的SNTP使用。

实验内容:

  1. 务必将网线接到能够联网的路由器或者交换机上面测试,因为本实验要用到外网。
  2. 本实验串口每秒打印一次从NTP服务器获取的时间。
  3. 国内免费稳定的SNTP服务器很少,当前使用的这个时好时坏,如果大家测试不成功的话也是正常的。

实验操作:

详见本章节30.6小节。

配置向导文件设置(Net_Config.c):

详见本章节30.4小节。

调试文件设置(Net_Debug.c):

详见本章节30.5小节

程序设计:

主函数初始化

在main.c文件实现:

/*

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

*    函 数 名: main

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

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 进入RL-TCPnet测试函数 */

     TCPnetTest();

}

硬件外设初始化

硬件外设的初始化是在 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_InitUart();    /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

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

     bsp_InitTimer();   /* 初始化滴答定时器 */

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现从远程NTP服务器获取当前日期和时间,精度到秒。

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

                                                   变量

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

*/

U8 ntp_server[4] = {182,16,3,162};  /* SNTP服务器地址 */

/*

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

*    函 数 名: time_cback

*    功能说明: SNTP获取时间回到函数

*    形    参: 无

*    返 回 值: 无

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

*/

static void time_cback (uint32_t time)

{

     struct tm *t_tm;

     if (time == 0)

     {

         printf_debug ("错误, 服务器未响应或者网络状态比较差\r\n");

     }

     else

     {

         /* 由于是格林尼治时间,所以北京时间要加8个小时 */

         time += 8*60*60;

         /* 将获取的UNIX时间戳转换成年月日时分秒的格式 */

         t_tm = localtime((unsigned int *)&time);

         /* 格林尼治时间是从1900年开始的,这要加上 */

         t_tm->tm_year += 1900;

         /* 获取的月份范围是0-11,这里要加1 */

         t_tm->tm_mon += 1;

         printf_debug ("UNIX时间戳:%d 日期:%02d/%02d/%02d  时间:%02d:%02d:%02d\r\n",

                        time,

                        t_tm->tm_year,

                          t_tm->tm_mon,

                          t_tm->tm_mday,

                          t_tm->tm_hour,

                          t_tm->tm_min,

                          t_tm->tm_sec);

     }

}                                   

/*

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

*    函 数 名: get_time

*    功能说明: 从SNTP服务器获取当前时间

*    形    参: 无

*    返 回 值: 无

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

*/                      

static void get_time (void)

{

     if (sntp_get_time((U8 *)&ntp_server[0], &time_cback) == __TRUE)

     {

         printf_debug ("SNTP请求已经发送成功\r\n");

     }

     else

     {

         printf_debug ("失败, SNTP未就绪或者参数错误\r\n");

     }

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer(0))

     {

         bsp_LedToggle(2);

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(0, 100);

     /* 再创建一个周期是1000ms的软定时器 */

     bsp_StartAutoTimer(1, 1000);

     while (1)

     {

         /* TCP轮询 */

         tcpnet_poll();

          /* 每秒从NTP服务器获取一次时间 */

         if(bsp_CheckTimer(1))

         {

              get_time();

         }

     }

}

30.8 实验例程说明(RTX)

30.8.1 STM32F407开发板实验

配套例子:

V5-1041_RL-TCPnet实验SNTP应用(RTX)

实验目的:

  1. 学习RL-TCPnet的SNTP使用。

实验内容:

  1. 务必将网线接到能够联网的路由器或者交换机上面测试,因为本实验要用到外网。
  2. 本实验串口每秒打印一次从NTP服务器获取的时间。
  3. 国内免费稳定的SNTP服务器很少,当前使用的这个时好时坏,如果大家测试不成功的话也是正常的。

实验操作:

详见本章节30.6小节。

配置向导文件设置(Net_Config.c):

详见本章节30.4小节。

调试文件设置(Net_Debug.c):

详见本章节30.5小节。

RTX配置:

RTX配置向导详情如下:

Task Configuration

(1)Number of concurrent running tasks

允许创建6个任务,实际创建了如下5个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskTCPMain任务:RL-TCPnet测试任务。

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

(2)Number of tasks with user-provided stack

创建的5个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

RTX初始化:

/*

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

*    函 数 名: main

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

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

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

                       5,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

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

     while(1);

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

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

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

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

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

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

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

     bsp_InitKey();    /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

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

}

RTX任务创建:

/*

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

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

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

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           1,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

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

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

                                        2,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

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

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

                                           3,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

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

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

                                           4,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

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

}

五个RTX任务的实现:

/*

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

*    函 数 名: AppTaskUserIF

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

*    形    参: 无

*    返 回 值: 无

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

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

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while(1)

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下 */

                   case KEY_DOWN_K1:

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

                       break;  

                   /* K2键按下 */

                   case KEY_DOWN_K2:

                       printf("K2键按下 \r\n");

                       break;

                   /* K3键按下 */

                   case KEY_DOWN_K3:

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

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait(20);

     }

}

/*

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

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

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

*/

__task void AppTaskLED(void)

{

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

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while(1)

    {

         bsp_LedToggle(2);

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

         os_itv_wait();

    }

}

/*

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

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

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

*/

__task void AppTaskMsgPro(void)

{

    while(1)

    {

         bsp_KeyScan();

         os_dly_wait(10);

    }

}

/*

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

*    函 数 名: AppTaskTCPMain

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

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while (1)

     {

         TCPnetTest();

     }

}

/*

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

*    函 数 名: AppTaskStart

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

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set (100);

    while(1)

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现从远程NTP服务器获取当前日期和时间,精度到秒。

#include "includes.h"

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

                                                   变量

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

*/

U8 ntp_server[4] = {182,16,3,162};  /* SNTP服务器地址 */

/*

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

*    函 数 名: time_cback

*    功能说明: SNTP获取时间回到函数

*    形    参: 无

*    返 回 值: 无

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

*/

static void time_cback (uint32_t time)

{

     struct tm *t_tm;

     if (time == 0)

     {

         printf_debug ("错误, 服务器未响应或者网络状态比较差\r\n");

     }

     else

     {

          /* 由于是格林尼治时间,所以北京时间要加8个小时 */

         time += 8*60*60;

          /* 将获取的UNIX时间戳转换成年月日时分秒的格式 */

         t_tm = localtime((unsigned int *)&time);

          /* 格林尼治时间是从1900年开始的,这要加上 */

         t_tm->tm_year += 1900;

         /* 获取的月份范围是0-11,这里要加1 */

         t_tm->tm_mon += 1;

         printf_debug ("UNIX时间戳:%d 日期:%02d/%02d/%02d  时间:%02d:%02d:%02d\r\n",

                        time,

                        t_tm->tm_year,

                          t_tm->tm_mon,

                          t_tm->tm_mday,

                          t_tm->tm_hour,

                          t_tm->tm_min,

                          t_tm->tm_sec);

     }

}   

/*

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

*    函 数 名: get_time

*    功能说明: 从SNTP服务器获取当前时间

*    形    参: 无

*    返 回 值: 无

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

*/                       

static void get_time (void)

{

     if (sntp_get_time((U8 *)&ntp_server[0], &time_cback) == __TRUE)

     {

         printf_debug ("SNTP请求已经发送成功\r\n");

     }

     else

     {

         printf_debug ("失败, SNTP未就绪或者参数错误\r\n");

     }

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     uint32_t tstart, tend;

     /* 初始化起始时间 */  

     tstart = os_time_get();

     while (1)

     {

          /* RL-TCPnet处理函数 */

         main_TcpNet();

         /* 每秒从NTP服务器获取一次时间 */       

         tend = os_time_get() - tstart;

         if(tend >= 1000)

         {

              tstart = os_time_get();

              get_time();

         }

         os_dly_wait(2);

     }

}

30.8.2 STM32F429开发板实验

配套例子:

V6-1041_RL-TCPnet实验SNTP应用(RTX)

实验目的:

  1. 学习RL-TCPnet的SNTP使用。

实验内容:

  1. 务必将网线接到能够联网的路由器或者交换机上面测试,因为本实验要用到外网。
  2. 本实验串口每秒打印一次从NTP服务器获取的时间。
  3. 国内免费稳定的SNTP服务器很少,当前使用的这个时好时坏,如果大家测试不成功的话也是正常的。

实验操作:

详见本章节30.6小节。

配置向导文件设置(Net_Config.c):

详见本章节30.4小节。

调试文件设置(Net_Debug.c):

详见本章节30.5小节。

RTX配置:

RTX配置向导详情如下:

Task Configuration

(1)Number of concurrent running tasks

允许创建6个任务,实际创建了如下5个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskTCPMain任务:RL-TCPnet测试任务。

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

(2)Number of tasks with user-provided stack

创建的5个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

RTX初始化:

/*

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

*    函 数 名: main

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

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

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

                       5,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

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

     while(1);

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

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

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

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

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     SystemCoreClockUpdate();    /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */

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

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

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitExtIO();    /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */

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

}

RTX任务创建:

/*

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

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

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

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           1,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

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

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

                                        2,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

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

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

                                           3,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

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

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

                                           4,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

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

}

五个RTX任务的实现:

/*

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

*    函 数 名: AppTaskUserIF

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

*    形    参: 无

*    返 回 值: 无

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

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

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while(1)

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下 */

                   case KEY_DOWN_K1:

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

                       break;  

                   /* K2键按下 */

                   case KEY_DOWN_K2:

                       printf("K2键按下 \r\n");

                       break;

                   /* K3键按下 */

                   case KEY_DOWN_K3:

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

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait(20);

     }

}

/*

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

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

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

*/

__task void AppTaskLED(void)

{

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

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while(1)

    {

         bsp_LedToggle(2);

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

         os_itv_wait();

    }

}

/*

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

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

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

*/

__task void AppTaskMsgPro(void)

{

    while(1)

    {

         bsp_KeyScan();

         os_dly_wait(10);

    }

}

/*

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

*    函 数 名: AppTaskTCPMain

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

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while (1)

     {

         TCPnetTest();

     }

}

/*

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

*    函 数 名: AppTaskStart

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

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set (100);

    while(1)

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现从远程NTP服务器获取当前日期和时间,精度到秒。

#include "includes.h"

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

                                                   变量

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

*/

U8 ntp_server[4] = {182,16,3,162};  /* SNTP服务器地址 */

/*

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

*    函 数 名: time_cback

*    功能说明: SNTP获取时间回到函数

*    形    参: 无

*    返 回 值: 无

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

*/

static void time_cback (uint32_t time)

{

     struct tm *t_tm;

     if (time == 0)

     {

         printf_debug ("错误, 服务器未响应或者网络状态比较差\r\n");

     }

     else

     {

          /* 由于是格林尼治时间,所以北京时间要加8个小时 */

         time += 8*60*60;

          /* 将获取的UNIX时间戳转换成年月日时分秒的格式 */

         t_tm = localtime((unsigned int *)&time);

          /* 格林尼治时间是从1900年开始的,这要加上 */

         t_tm->tm_year += 1900;

         /* 获取的月份范围是0-11,这里要加1 */

         t_tm->tm_mon += 1;

         printf_debug ("UNIX时间戳:%d 日期:%02d/%02d/%02d  时间:%02d:%02d:%02d\r\n",

                        time,

                        t_tm->tm_year,

                          t_tm->tm_mon,

                          t_tm->tm_mday,

                          t_tm->tm_hour,

                          t_tm->tm_min,

                          t_tm->tm_sec);

     }

}   

/*

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

*    函 数 名: get_time

*    功能说明: 从SNTP服务器获取当前时间

*    形    参: 无

*    返 回 值: 无

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

*/                       

static void get_time (void)

{

     if (sntp_get_time((U8 *)&ntp_server[0], &time_cback) == __TRUE)

     {

         printf_debug ("SNTP请求已经发送成功\r\n");

     }

     else

     {

         printf_debug ("失败, SNTP未就绪或者参数错误\r\n");

     }

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     uint32_t tstart, tend;

     /* 初始化起始时间 */  

     tstart = os_time_get();

     while (1)

     {

          /* RL-TCPnet处理函数 */

         main_TcpNet();

         /* 每秒从NTP服务器获取一次时间 */       

         tend = os_time_get() - tstart;

         if(tend >= 1000)

         {

              tstart = os_time_get();

              get_time();

         }

         os_dly_wait(2);

     }

}

30.9 总结

本章节就为大家讲解这么多,本章节的内容相对比较简单,主要是函数sntp_get_time的使用,希望大家熟练掌握。

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

时间: 2024-10-26 22:03:07

【RL-TCPnet网络教程】第30章 RL-TCPnet之SNTP网络时间获取的相关文章

笔记-《数据通信与网络教程》-第一章

1. 网络拓扑结构(Network Topology) 1. 公共总线拓扑结构( Common Bus Top ology):它们通过一根单总线(一束并行线)进行通信.每个设备通过相应的接口侦听总线,检查数据传输.如果接口判断出数据时送往它所服务的设备的,它就从总线上读取数据并传给设备.当一个设备有数据需要传送时,其接口电路检测总线是否空闲,如果是就立刻发送数据.当两个设备同时检测到总线空闲并传输数据时会产生一个冲突信号,设备传输时继续监听总线,因此能检测到冲突信号,并停止传输,等待一段随机事件

UNIX网络编程第30章客户服务器程序设计范式 草稿

Linux网络服务第六章PXE高效能批量网络装机

1.IP地址配置 2.关闭防火墙以及selinux状态如下 systemctl  stop     firewalld Iptables -F Setenforce 0 三.部署FTP服务 1.安装FTP服务,并将安装源复制到/var/ftp/centos7目录下: [[email protected] ~]# yum -y install vsftpd [[email protected] ~]# rpm -q vsftpd vsftpd-3.0.2-10.el7.x86_64 [[email

【RL-TCPnet网络教程】第26章 RL-TCPnet之DHCP应用

第26章     RL-TCPnet之DHCP应用 本章节为大家讲解RL-TCPnet的DHCP应用,学习本章节前,务必要优先学习第25章的DHCP基础知识.有了这些基础知识之后,再搞本章节会有事半功倍的效果. 本章教程含STM32F407开发板和STM32F429开发板. 26.1  初学者重要提示 26.2  DHCP函数 26.3  DHCP配置说明(Net_Config.c) 26.4  DHCP调试说明(Net_Debug.c) 26.5  DHCP检测过程 26.6  网络调试助手和

【RL-TCPnet网络教程】第32章 RL-TCPnet之Telnet服务器

第32章      RL-TCPnet之Telnet服务器 本章节为大家讲解RL-TCPnet的Telnet应用,学习本章节前,务必要优先学习第31章的Telnet基础知识.有了这些基础知识之后,再搞本章节会有事半功倍的效果. 本章教程含STM32F407开发板和STM32F429开发板. 32.1  初学者重要提示 32.2  Telnet函数 32.3  Telnet配置说明(Net_Config.c) 32.4  Telnet调试说明(Net_Debug.c) 32.5  Telnet访问

Unity网络多玩家游戏开发教程第1章Unity自带网络功能

Unity网络多玩家游戏开发教程第1章Unity自带网络功能 Unity拥有大量的第三方插件,专门提供了对网络功能的支持.但是,大部分开发者第一次接触到的还是Unity自带的网络功能,也就是大家经常说到的Unity Networking API.这些API是借助于组件NetworkView发挥作用的,而它可以简化开发者大量的网络功能编码任务.本文选自<Unity网络多玩家游戏开发教程(大学霸内部资料)> NetworkView组件 在Unity中,NetworkView组件用于处理游戏在网络上

【RL-TCPnet网络教程】第23章 RL-TCPnet之地址解析协议ARP

第23章      RL-TCPnet之地址解析协议ARP 本章节为大家讲解ARP(Address Resolution Protocol,地址解析协议),通过前面章节对TCP和UDP的学习,需要大家对ARP也有个基础的认识. (本章的知识点主要整理自网络) 23.1  初学者重要提示 23.2  ARP基础知识参考资料 23.3  ARP基础知识点 23.4  ARP函数 23.5  总结 23.1  初学者重要提示 通过前面几个章节对TCP和UDP的学习,需要初学者对ARP的基础知识点也有个

【RL-TCPnet网络教程】第37章 RL-TCPnet之FTP客户端

第37章      RL-TCPnet之FTP客户端 本章节为大家讲解RL-TCPnet的FTP客户端应用,学习本章节前,务必要优先学习第35章的FTP基础知识.有了这些基础知识之后,再搞本章节会有事半功倍的效果. 本章教程含STM32F407开发板和STM32F429开发板. 37.1  初学者重要提示 37.2  FTP函数 37.3  FTP配置说明(Net_Config.c) 37.4  FTP调试说明(Net_Debug.c) 37.5  FTP访问方法和板子的操作步骤 37.6  实

mySQL教程 第1章 数据库设计

第1章 数据库设计 E-R设计 很多同学在学SQL语句时,觉得非常困难,那是因为你在学一个你根本不了解的数据库,数据库中的表不是你设计的,表与表之间的关系你不明白.因此在学SQL语句之前,先介绍一下数据库设计. 下面举例说明数据库设计: 学校需要开发一个系统记录有学生.课程和成绩信息.数据库如何设计? 这里面涉及到两个实体,学生表.课程,这些表为实体表. 这些表之间有什么关系呢?.学生考试出成绩,成绩记录在成绩表. 一个学生可以参加多门课程,关系是1对多. 数据库设计实例 设计数据库和表 安装m