Skip to content

예제 enet_thermometer

irmusy edited this page Oct 8, 2013 · 2 revisions

개요

myCortex-LM8962 보드는 이더넷 PHY/MAC 장치를 내장하고 있어 임베디드 이더넷 시스템을 간단하게 구성할 수 있습니다. 이더넷은 구성 요소가 복잡하고 구현해야 할 양이 많은 통신 수단이며 UART나 I2C 처럼 간단한 예제로 설명하기에는 어려움이 있습니다. 하지만 일반적으로 스텍이라 불리우는 소프트웨어 라이브러리를 활용하면 내부 구성을 잘 이해하지 못하더라도 비교적 어렵지 않게 구현할 수 있습니다.

enet_thermometer 예제는 uIP 스텍을 사용하여 간단한 임베디드 웹서버를 구현하고 있습니다. uIP 스텍에는 여러가지 예제 소스가 포함되어 있으며, 본 예제는 uIP에 있는 httpd 예제를 변형하여 myCortex-LM8962 보드에 맞게끔 수정한 것입니다.

본 예제에서는 사용자의 http GET 요청에 하나의 html 문서를 반환하는 예를 구현하고 있습니다. html 문서는 본 예제에 관한 간략한 설명과 함께 현재 MCU 코어 내부의 온도와 시스템 런타임을 담고 있습니다. PC나 스마트폰의 웹브라우저에서 접속하여 html 페이지를 확인할 수 있습니다.

본 예제와 동일한 펌웨어를 이용하여 서버를 만들어 상시 운영중입니다. http://enet_thermometer.withrobot.com 으로 접속하면 위드로봇에서 운영중인 임베디드 웹서버에 접속하여 html 파일을 보실 수 있습니다.

관련 Peripheral

  • SysCtl
  • GPIO
  • ETH

소스 살펴보기

enet_thermometer.c

84 line:

//*****************************************************************************
//
// Default TCP/IP Settings for this application.
//
// Default to Link Local address ... (169.254.1.0 to 169.254.254.255).  Note:
// This application does not implement the Zeroconf protocol.  No ARP query is
// issued to determine if this static IP address is already in use.
//
//*****************************************************************************
#define DEFAULT_IPADDR0         192
#define DEFAULT_IPADDR1         168
#define DEFAULT_IPADDR2         10
#define DEFAULT_IPADDR3         175

#define DEFAULT_NETMASK0        255
#define DEFAULT_NETMASK1        255
#define DEFAULT_NETMASK2        255
#define DEFAULT_NETMASK3        0

myCortex-LM8962 보드가 사용할 IP 주소와 Net mask 값을 지정합니다. 본 예제 소스에서는 192.168.10.175 주소를 사용합니다. 사용자 환경에 따라 이 값을 적절히 변경해야 합니다.

148 line:

    //
    // Enable and Reset the Ethernet Controller.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_ETH);
    SysCtlPeripheralReset(SYSCTL_PERIPH_ETH);

ETH 장치를 enable합니다.

155 line:

    //
    // Enable Port F for Ethernet LEDs.
    //  LED0        Bit 3   Output
    //  LED1        Bit 2   Output
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    GPIODirModeSet(GPIO_PORTF_BASE, GPIO_PIN_2 | GPIO_PIN_3, GPIO_DIR_MODE_HW);
    GPIOPadConfigSet(GPIO_PORTF_BASE, GPIO_PIN_2 | GPIO_PIN_3,
                     GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD);

myCortex-LM8962 보드의 이더넷 커넥터에는 두 개의 LED가 내장되어 있습니다. 위 코드는 이 LED를 제어하는 용도로 PF2와 PF3 핀을 사용하도록 설정하는 코드입니다.

165 line:

    //
    // Intialize the Ethernet Controller and disable all Ethernet Controller
    // interrupt sources.
    //
    EthernetIntDisable(ETH_BASE, (ETH_INT_PHY | ETH_INT_MDIO | ETH_INT_RXER |
                       ETH_INT_RXOF | ETH_INT_TX | ETH_INT_TXER | ETH_INT_RX));
    ulTemp = EthernetIntStatus(ETH_BASE, false);
    EthernetIntClear(ETH_BASE, ulTemp);

이더넷 장치 설정을 들어가기 앞서 우선 인터럽트를 disable 시키고 현재 set 되어 있는 인터럽트 flag들을 모두 clear 해서 깨끗한 상태에서 설정을 시작합니다.

174 line:

    //
    // Initialize the Ethernet Controller for operation.
    //
    EthernetInitExpClk(ETH_BASE, SysCtlClockGet());

    //
    // Configure the Ethernet Controller for normal operation.
    // - Full Duplex
    // - TX CRC Auto Generation
    // - TX Padding Enabled
    //
    EthernetConfigSet(ETH_BASE, (ETH_CFG_TX_DPLXEN | ETH_CFG_TX_CRCEN |
                                 ETH_CFG_TX_PADEN));

ETH 장치를 설정합니다.

188 line:

    //
    // Enable the Ethernet Controller.
    //
    EthernetEnable(ETH_BASE);

    //
    // Enable the Ethernet interrupt.
    //
    IntEnable(INT_ETH);

    //
    // Enable the Ethernet RX Packet interrupt source.
    //
    EthernetIntRegister(ETH_BASE, EthernetIntHandler);
    EthernetIntEnable(ETH_BASE, ETH_INT_RX);

    //
    // Enable all processor interrupts.
    //
    IntMasterEnable();

ETH 장치를 enable 시키고, ETH 인터럽트를 설정합니다. 마지막으로 glabal 인터럽트를 enable 시켜줍니다.

209 line:

    //
    // Initialize the uIP TCP/IP stack.
    //
    uip_init();
    uip_ipaddr(ipaddr, DEFAULT_IPADDR0, DEFAULT_IPADDR1, DEFAULT_IPADDR2,
               DEFAULT_IPADDR3);
    uip_sethostaddr(ipaddr);
    uip_ipaddr(ipaddr, DEFAULT_NETMASK0, DEFAULT_NETMASK1, DEFAULT_NETMASK2,
               DEFAULT_NETMASK3);
    uip_setnetmask(ipaddr);

이제 uIP를 설정할 차례입니다. uip_init()부터 시작하여 사용할 IP 주소와 subnet mask를 설정합니다.

220 line:

    // 실험목적으로 사용하는 MAC 주소.
    // 이 주소는 정식으로 IEEE로 부터 할당받은 주소가 아니다.
    // 상용 제품을 출시하는 경우에는 정식 MAC 주소를 할당받아 사용해야 한다.
    sTempAddr.addr[0] = 0xb6;
    sTempAddr.addr[1] = 0x1a;
    sTempAddr.addr[2] = 0x00;
    sTempAddr.addr[3] = 0x67;
    sTempAddr.addr[4] = 0x45;
    sTempAddr.addr[5] = 0x23;

myCortex-LM8962 보드의 이더넷 MAC 주소를 설정합니다. 이 주소는 정식으로 할당받은 주소가 아니므로 상용품을 제작할 때에 사용해서는 안됩니다.

231 line:

    //
    // Program the hardware with it's MAC address (for filtering).
    //
    EthernetMACAddrSet(ETH_BASE, (unsigned char *)&sTempAddr);
    uip_setethaddr(sTempAddr);

ETH 장치에 MAC 주소를 설정합니다.

237 line:

    //
    // Initialize the TCP/IP Application (e.g. web server).
    //
    httpd_init();

httpd 를 초기화 합니다. 이 예제는 ETH -> uIP -> httpd 형태로 라이브러리 스텍(stack)이 구성되어 있습니다.

244 line:

    //
    // Configure SysTick for a periodic interrupt.
    //
    SysTickPeriodSet(SysCtlClockGet() / 100);       // 100Hz timer
    SysTickIntRegister(SysTickIntHandler);
    SysTickEnable();
    SysTickIntEnable();

주기적으로 ETH 장치에서 들어온 메시지를 확인하기 위해 SysTick 타이머를 설정합니다. 본 예제는 100Hz의 타이머를 이용하여 매 10ms 마다 ETH 메시지를 확인하고 수신된 메시지가 있다면 그에 따를 처리를 하는 형태로 구성되어 있습니다.

254 line:

    //
    // Configure ADC
    // 1초 간격으로 온도 센서를 ADC하여 현재 온도를 전역 변수에 저장해 둔다.
    // client에서 http request가 들어오면 이 전역 변수에 저장된 온도 값을
    // HTML로 만들어 전송한다.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC);
    SysCtlADCSpeedSet(SYSCTL_ADCSPEED_500KSPS);

    // 내장 온도 센서 채널만으로 ADC sequence를 구성한다.
    ADCSequenceDisable(ADC_BASE, 0);
    ADCSequenceConfigure(ADC_BASE, 0, ADC_TRIGGER_TIMER, 0);

    ADCSequenceStepConfigure(ADC_BASE, 0, 0, ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_END);

    ADCSequenceEnable(ADC_BASE, 0);

    ADCIntRegister(ADC_BASE, 0, ADCIntHandler);
    ADCIntEnable(ADC_BASE, 0);

    //
    // Configure Timer to trigger ADC
    // 매 1초마다 자동으로 ADC 될 수 있도록 ADC trigger timer를 설정한다.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
    TimerConfigure( TIMER0_BASE, TIMER_CFG_32_BIT_PER );
    TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
    TimerLoadSet( TIMER0_BASE, TIMER_A, SysCtlClockGet() / 1 );     // 1Hz trigger
    TimerEnable( TIMER0_BASE, TIMER_A );

ADC와 타이머를 초기화합니다. 본 예제에서는 MCU 코어 내장 온도 센서를 읽어 코어 온도를 섭씨단위로 표시하는 html 파일을 전송합니다. 이를 위해 주기적으로 ADC를 수행하고 그 결과를 전역 변수에 저장해 두었다가 클라이언트 접속이 들어오면 가장 최근 온도를 담아 html 문서를 생성합니다.

286 line:

    lPeriodicTimer = 0;
    lARPTimer = 0;

ETH 장치는 주기적으로 메시지 수신 여부를 확인하여 처리해야 합니다. 이를 위해 전역변수로 타이머 변수를 두고 사용하고 있습니다. 이 변수들은 SysTick 타이머 인터럽트 핸들러에서 시간을 관리합니다.

295 line:

        SysCtlSleep();      // SysTick/ADC/ETH 인터럽트가 발생할 때 까지 sleep

인터럽트가 발생할 때 까지 이곳에서 sleep 합니다. 본 예제에서는 3가지 인터럽트가 사용되며 이들 중 어떠한 것이라도 발생하면 sleep에서 깨어납니다.

298 line:

        //
        // Check for an RX Packet and read it.
        //
        lPacketLength = EthernetPacketGetNonBlocking(ETH_BASE, uip_buf, sizeof(uip_buf));

수신된 패킷이 있는지 확인하고 있다면 수신한 패킷을 가져옵니다.

이하의 코드들은 uIP의 main control loop에 해당하는 코드입니다.

uip main control loop

위 그림과 같이 수신된 패킷을 확인하여 처리하고, 타임아웃을 확인하여 처리하는 형태로 구성되어 있습니다. 코드와 구성에 관한 자세한 내용은 uIP 문서를 참조하시기 바랍니다. 예제 소스 중 enet_thermometer/uip-1.0/doc 폴더에 문서가 포함되어 있습니다.

        if(lPacketLength > 0)
        {
            //
            // Set uip_len for uIP stack usage.
            //
            uip_len = (unsigned short)lPacketLength;

            //
            // Renable RX Packet interrupts.
            //
            EthernetIntEnable(ETH_BASE, ETH_INT_RX);

            //
            // Process incoming IP packets here.
            //
            if(BUF->type == htons(UIP_ETHTYPE_IP))
            {
                uip_arp_ipin();
                uip_input();

                //
                // should be sent out on the network, the global variable
                // uip_len is set to a value > 0.
                //
                if(uip_len > 0)
                {
                    uip_arp_out();
                    EthernetPacketPut(ETH_BASE, uip_buf, uip_len);
                    uip_len = 0;
                }
            }

            //
            // Process incoming ARP packets here.
            //
            else if(BUF->type == htons(UIP_ETHTYPE_ARP))
            {
                uip_arp_arpin();

                //
                // If the above function invocation resulted in data that
                // should be sent out on the network, the global variable
                // uip_len is set to a value > 0.
                //
                if(uip_len > 0)
                {
                    EthernetPacketPut(ETH_BASE, uip_buf, uip_len);
                    uip_len = 0;
                }
            }
        }

        //
        // Process TCP/IP Periodic Timer here.
        //
        if(lPeriodicTimer > UIP_PERIODIC_TIMER_MS)
        {
            lPeriodicTimer = 0;
            for(ulTemp = 0; ulTemp < UIP_CONNS; ulTemp++)
            {
                uip_periodic(ulTemp);

                //
                // If the above function invocation resulted in data that
                // should be sent out on the network, the global variable
                // uip_len is set to a value > 0.
                //
                if(uip_len > 0)
                {
                    uip_arp_out();
                    EthernetPacketPut(ETH_BASE, uip_buf, uip_len);
                    uip_len = 0;
                }
            }

#if UIP_UDP
            for(ulTemp = 0; ulTemp < UIP_UDP_CONNS; ulTemp++)
            {
                uip_udp_periodic(i);

                //
                // If the above function invocation resulted in data that
                // should be sent out on the network, the global variable
                // uip_len is set to a value > 0.
                //
                if(uip_len > 0)
                {
                    uip_arp_out();
                    EthernetPacketPut(ETH_BASE, uip_buf, uip_len);
                    uip_len = 0;
                }
            }
#endif // UIP_UDP
        }

        //
        // Process ARP Timer here.
        //
        if(lARPTimer > UIP_ARP_TIMER_MS)
        {
            lARPTimer = 0;
            uip_arp_timer();
        }

412 line:

//
// SysTick tiemr interrupt handler
//
static void SysTickIntHandler(void)
{
    // SysTick timer는 100Hz 주기로 설정되어 있으므로 10ms 간격으로 인터럽트 발생.
    lPeriodicTimer += 10;
    lARPTimer += 10;

    g_tick++;

    if (g_tick % 100 == 0)      // 1초 경과
    {
        g_runtime++;
    }

}

SysTick 타이머 인터럽트 핸들러입니다. uIP의 타임아웃 이벤트를 위해 lPeriodicTimer와 lARPTimer 변수를 관리합니다. 또한 html 문서에서 보여 줄 런타입 정보를 만들기 위해 g_runtime 변수를 관리합니다.

433 line:

//
// The interrupt handler for the Ethernet interrupt.
//
static void EthernetIntHandler(void)
{
    unsigned long ulTemp;

    //
    // Read and Clear the interrupt.
    //
    ulTemp = EthernetIntStatus(ETH_BASE, false);
    EthernetIntClear(ETH_BASE, ulTemp);

    //
    // Check to see if an RX Interrupt has occured.
    //
    if(ulTemp & ETH_INT_RX)
    {
        //
        // Disable Ethernet RX Interrupt.
        //
        EthernetIntDisable(ETH_BASE, ETH_INT_RX);
    }
}

ETH 인터럽트 핸들러입니다. ETH에서는 메시지가 수신되었을 때 인터럽트가 발생합니다. 이 핸들러에서는 메시지 수신 시 더이상 인터럽트가 발생하지 않도록 disable 시켜주기만 합니다. 나머지 작업들은 uIP 스텍 내부에서 처리됩니다.

462 line:

/*
 * ADC Interrupt Handler
 */
static void ADCIntHandler(void)
{
    unsigned long adc_result[8];
    double temp;

    ADCIntClear(ADC_BASE, 0);

    ADCSequenceDataGet(ADC_BASE, 0, adc_result);

    temp = (2.7 - 3.0 * (double)adc_result[0] / 1024.0) * 75.0 - 55.0;
    g_temp = (unsigned long)(temp * 100);

}

온도를 읽기 위한 ADC 인터럽트 핸들러입니다. 자세한 내용은 [adc_temperature 예제](예제 adc_temperature)를 참조하시기 바랍니다.

uip-1.0/apps/httpd/httpd.c

이 파일은 클라이언트 접속이 들어왔을 때 html파일을 생성하는 코드입니다. httpd_appcall() 함수에서 현재 상태에 따라 html 파일을 생성하여 send 하고 있습니다.

웹서버에서 표시되는 내용을 변경하고자 할 경우 이 파일의 내용을 수정해야 합니다.

실행 방법 안내

  1. myCortex-LM8962 보드에 이더넷 케이블을 연결합니다. 많이 사용되는 유무선 공유기 환경이 적합합니다.
  2. 펌웨어를 빌드한 후 다운로드 하고, 보드의 리셋 버튼을 1회 누릅니다.
  3. 이더넷 커넥터에 있는 두 개의 LED가 깜박이는지 확인합니다.
  4. 동일한 공유기에 연결된 PC 혹은 스마트폰에서 웹브라우저를 사용하여 myCortex-LM8962 보드로 접속하여 html 문서가 표시되는지 확인합니다.
  • 동일한 공유기가 아닌 경우 공유기에서 포트포워딩을 설정해야 접속 가능합니다.