目前共有4篇帖子。 內容轉換:不轉換▼
 
點擊 回復
993 3
【教程】使用lwip自带的sys_check_timeouts函数简化主循环中的程序,并用STM32F103的RTC时钟实现sys_now函数
一派護法 十九級
1樓 發表于:2017-4-5 22:51
原工程:https://zh.arslanbar.net/post.php?t=24589
【1】首先按照下面的五角星处的提示修改main.c文件中的代码:
#include <stdio.h>
#include <stm32f10x.h>
#include "lwip/apps/netbiosns.h"
#include "lwip/dhcp.h"
#include "lwip/dns.h"
#include "lwip/init.h" // lwip_init函数所在的头文件
#include "lwip/timeouts.h" // ★sys_check_timeouts函数所在的头文件
#include "netif/ethernet.h" // ethernet_input函数所在头文件
#include "ENC28J60.h"

err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);
void init_http(void);

int fputc(int ch, FILE *fp)
{
    if (fp == &__stdout)
    {
        if (ch == '\n')
        {
            USART1->DR = '\r';
            while ((USART1->SR & USART_SR_TXE) == 0);
        }
        USART1->DR = ch;
        while ((USART1->SR & USART_SR_TXE) == 0);
    }
    return ch;
}

void dnsfound(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
{
    printf("DNS Found IP: %s\n", ip4addr_ntoa(ipaddr));
}

void test_dns(struct netif *netif)
{
    struct ip4_addr dnsip;
    err_t err = dns_gethostbyname("zh.arslanbar.net", &dnsip, dnsfound, NULL);
    if (err == ERR_OK)
        printf("In cache! IP: %s\n", ip4addr_ntoa(&dnsip));
    else
        printf("Not in cache! err=%d\n", err);
}

void show_addr(struct netif *netif)
{
    struct dhcp *dhcp = netif_dhcp_data(netif);
    static uint8_t displayed = 0;
    if (dhcp_supplied_address(netif))
    {
        if (displayed == 0)
        {
            printf("DHCP分配成功!\n");
            printf("IP地址: %s\n", ip4addr_ntoa(&dhcp->offered_ip_addr));
            printf("子网掩码: %s\n", ip4addr_ntoa(&dhcp->offered_sn_mask));
            printf("网关: %s\n", ip4addr_ntoa(&dhcp->offered_gw_addr));
            printf("DNS服务器: %s\n", ip4addr_ntoa(dns_getserver(0)));
            displayed = 1;
            test_dns(netif);
        }
    }
    else
        displayed = 0;
}

// ★RTC时间转化为毫秒数
uint32_t sys_now(void)
{
    uint32_t tmp = (RTC->CNTH << 16) | RTC->CNTL; // 秒
    tmp = tmp * 1000 + (32767 - RTC->DIVL) * 1000 / 32768; // 毫秒
    return tmp;
}

int main(void)
{
    struct netif enc28j60;
    uint16_t last_check = 0; // ★
   
    // ★配置RTC时钟
    RCC->APB1ENR = RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN;
    PWR->CR = PWR_CR_DBP; // 允许写入后备寄存器
    if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0)
    {
        // 若RTC还没有打开, 则初始化RTC
        if ((RCC->BDCR & RCC_BDCR_LSEON) == 0)
            RCC->BDCR |= RCC_BDCR_LSEON; // 开外部低速晶振LSE
        RCC->BDCR |= RCC_BDCR_RTCEN; // 打开RTC外设, 但暂不开始走时
       
        RTC->CRL |= RTC_CRL_CNF; // 进入RTC配置模式
        RTC->PRLH = 0;
        RTC->PRLL = 32767; // 定时1s (PRLH和PRLL寄存器只能写不能读)
        //RTC->CNTH = 0;
        //RTC->CNTL = 50; // 初始时间
        RTC->CRL &= ~RTC_CRL_CNF; // 保存设置
       
        RCC->BDCR |= RCC_BDCR_RTCSEL_0; // 选LSE作为RTC时钟, 准备走时
        while ((RTC->CRL & RTC_CRL_RTOFF) == 0); // 等待设置生效
        // RTC从此处开始走时
    }
   
    // 配置PA口
    RCC->APB2ENR = RCC_APB2ENR_IOPAEN;
    GPIOA->CRH = 0x000004bb; // ★PA8为开发板上的一个LED灯(设为复用推挽输出, CRH=b)
    GPIOA->CRL = 0xb4bb0080;
    GPIOA->BSRR = GPIO_BSRR_BS1; // PA1为网卡中断输出
   
    // 配置SPI
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1; // 主机模式, 时钟至少需要8分频(BR=010), 也就是72MHz/8=9MHz
    SPI1->CR2 = SPI_CR2_SSOE; // 开CS片选输出
   
    // 配置串口
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
    USART1->BRR = 0x271; // 波特率: 115200
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
   
    // ★用定时器1的通道1来自动控制LED指示灯PA8
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    TIM1->ARR = 9999; // 10000*0.5ms=5s
    TIM1->PSC = 35999; // 定时基准0.5ms
    TIM1->CCR1 = 0; // 输出比较值
    TIM1->CCMR1 = TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0; // 匹配时翻转PA8的电平
    TIM1->CCER = TIM_CCER_CC1P | TIM_CCER_CC1E; // 开通道1的输出比较, 使用相反的电平
    TIM1->BDTR = TIM_BDTR_MOE; // 开总输出
    TIM1->EGR = TIM_EGR_UG; // 保存设置
   
    lwip_init(); // 这里面会自动调用dns_init和sys_timeouts_init函数
    // 千万不能再在这里调用sys_timeouts_init函数, 否则会出现内存不足的错误!
    netif_add(&enc28j60, IP_ADDR_ANY, IP_ADDR_ANY, IP_ADDR_ANY, NULL, ethernetif_init, ethernet_input);
    netif_set_default(&enc28j60); // 设为默认网卡
    netif_set_up(&enc28j60);
   
    netbiosns_set_name("ENC28J60"); // 设置计算机名
    netbiosns_init();
    enc28j60.hostname = "ENC28J60_Device"; // 路由器DHCP页面中显示的设备名
   
    dhcp_start(&enc28j60); // 启动DHCP
    init_http(); // 初始化网页服务器
   
    TIM1->CR1 |= TIM_CR1_CEN; // 开定时器1, 指示灯开始自动闪烁
    while (1)
    {
        if (ENC28J60_GetPacketNum() != 0)
        {
            TIM1->EGR |= TIM_EGR_UG; // 收到数据包时改变LED灯的状态(定时器清零)
            ethernetif_input(&enc28j60);
        }
        show_addr(&enc28j60); // 如果DHCP分配地址成功就显示IP地址
       
        // ★sys_check_timeouts函数千万不能调用的太频繁, 否则会出错!(后果就是开机后要等1~2分钟DHCP才能分配到IP地址)
        if ((uint16_t)(TIM1->CNT - last_check) >= 200) // 如果距离上次检测超过200*0.5ms=100ms
        {
            last_check = TIM1->CNT;
            sys_check_timeouts(); // 则再检测一次
            // 当TIM1->CNT < last_check时也能正常判断, 因为两者都是无符号数
            // 比如12-9985的结果是55563, 这相当于计算65536+(12-9985)
        }
    }
}
一派護法 十九級
2樓 發表于:2017-4-5 22:52
【2】按五角星提示修改lwipopts.h函数:
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

/**
 * SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
 * critical regions during buffer allocation, deallocation and memory
 * allocation and deallocation.
 */
#define SYS_LIGHTWEIGHT_PROT 0

/**
 * NO_SYS==1: Provides VERY minimal functionality. Otherwise,
 * use lwIP facilities.
 */
#define NO_SYS 1 // 无操作系统
/**
 * NO_SYS_NO_TIMERS==1: Drop support for sys_timeout when NO_SYS==1
 * Mainly for compatibility to old versions.
 */
//#define NO_SYS_NO_TIMERS 1 // ★将这一行注释掉, 然后自己实现sys_now函数

/* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
   lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
   byte alignment -> define MEM_ALIGNMENT to 2. */
#define MEM_ALIGNMENT 4

/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE (5 * 1024)

/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
   sends a lot of data out of ROM (or other static memory), this
   should be set high. */
#define MEMP_NUM_PBUF 10
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
   per active UDP "connection". */
#define MEMP_NUM_UDP_PCB 6
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
   connections. */
#define MEMP_NUM_TCP_PCB 10
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
   connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 6
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
   segments. */
#define MEMP_NUM_TCP_SEG 12
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
   timeouts. */
//#define MEMP_NUM_SYS_TIMEOUT 10 // ★这个数字应该改成较大的数, 否则会报编译错误
// 或者直接注释掉, 让opt.h自动计算最佳数值

/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE 10

/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE 1500

/* ---------- TCP options ---------- */
#define LWIP_TCP 1
#define TCP_TTL 255

/* Controls if TCP should queue segments that arrive out of
   order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ 0

/* TCP Maximum segment size. */
#define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */

/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF (2 * TCP_MSS)

/* TCP sender buffer space (pbufs). This must be at least = 2 *
   TCP_SND_BUF/TCP_MSS for things to work. */
#define TCP_SND_QUEUELEN (6 * TCP_SND_BUF) / TCP_MSS

/* TCP receive window. */
#define TCP_WND (2 * TCP_MSS)

/* ---------- ICMP options ---------- */
#define LWIP_ICMP 1

/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
   interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
   turning this on does currently not work. */
#define LWIP_DHCP 1

/* ---------- UDP options ---------- */
#define LWIP_UDP 1
#define UDP_TTL 255

/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1

// LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
#define LWIP_NETCONN 0

// LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
#define LWIP_SOCKET 0

#define LWIP_NETIF_HOSTNAME 1

#define LWIP_DNS 1
//#define LWIP_DHCP_PROVIDE_DNS_SERVERS 1 // ★需要将这个注释掉, 这个选项不再需要
#define LWIP_RAND() ((u32_t)rand())

// 以下为调试选项
//#define LWIP_DEBUG
//#define DHCP_DEBUG LWIP_DBG_ON
//#define MEMP_DEBUG LWIP_DBG_ON
//#define TIMERS_DEBUG LWIP_DBG_ON
//#define LWIP_DEBUG_TIMERNAMES 1

#endif /* __LWIPOPTS_H__ */
一派護法 十九級
3樓 發表于:2017-4-5 22:53
【3】编译并运行程序
经过大约5~8秒钟,串口输出如下:
DHCP分配成功!
IP地址: 192.168.1.116
子网掩码: 255.255.255.0
网关: 192.168.1.1
DNS服务器: 192.168.1.1
Not in cache! err=-5
DNS Found IP: 106.186.126.193
一派護法 十九級
4樓 發表于:2017-4-5 23:20
主循环里的延时不能保证每次都是100ms,因为计数值是存在上限的。可以换成自己写的delay函数。反正就是必须要延时,要不然sys_check_timeouts函数始终都在执行,那么当网卡没有数据进来的时候,算出来的时间差diff始终为0,调度程序根本就不知道要调度哪个定时器函数执行,这样就有可能导致dhcp处理超时的函数几分钟都得不到执行,而其他定时器函数却执行了很多遍,搞得开机后几分钟才能获取到IP地址。

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:993 回複數:3
評論數: ?
作者:巨大八爪鱼
最後回復:----
最後回復時間:2017-4-5 23:35
 
©2010-2024 Arslanbar Ver2.0
除非另有聲明,本站採用共享創意姓名標示-相同方式分享 3.0 Unported許可協議進行許可。