//***************************************************************************** // // enet_io.c - I/O control via a web server. // // Copyright (c) 2007-2014 Texas Instruments Incorporated. All rights reserved. // Software License Agreement // // Texas Instruments (TI) is supplying this software for use solely and // exclusively on TI's microcontroller products. The software is owned by // TI and/or its suppliers, and is protected under applicable copyright // laws. You may not combine this software with "viral" open-source // software in order to form a larger program. // // THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS. // NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT // NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY // CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL // DAMAGES, FOR ANY REASON WHATSOEVER. // // This is part of revision 2.1.0.12573 of the DK-TM4C129X Firmware Package. // //***************************************************************************** #include #include #include #include "inc/hw_ints.h" #include "inc/hw_memmap.h" #include "inc/hw_nvic.h" #include "inc/hw_types.h" #include "driverlib/flash.h" #include "driverlib/gpio.h" #include "driverlib/interrupt.h" #include "driverlib/sysctl.h" #include "driverlib/systick.h" #include "driverlib/timer.h" #include "driverlib/rom.h" #include "driverlib/rom_map.h" #include "utils/locator.h" #include "utils/lwiplib.h" #include "utils/uartstdio.h" #include "utils/ustdlib.h" #include "httpserver_raw/httpd.h" #include "grlib/grlib.h" #include "drivers/kentec320x240x16_ssd2119.h" #include "drivers/pinout.h" #include "io.h" #include "cgifuncs.h" #include "drivers/frame.h" //***************************************************************************** // //! \addtogroup example_list //!

Ethernet-based I/O Control (enet_io)

//! //! This example application demonstrates web-based I/O control using the //! Tiva Ethernet controller and the lwIP TCP/IP Stack. DHCP is used to //! obtain an Ethernet address. If DHCP times out without obtaining an //! address, a static IP address will be chosen using AutoIP. The address that //! is selected will be shown on the QVGA display allowing you to access the //! internal web pages served by the application via your normal web browser. //! //! Two different methods of controlling board peripherals via web pages are //! illustrated via pages labeled ``IO Control Demo 1'' and ``IO Control //! Demo 2'' in the navigation menu on the left of the application's home page. //! In both cases, the example allows you to toggle the state of the user LED //! on the board and set the speed of a graphical animation on the display. //! //! ``IO Control Demo 1'' uses JavaScript running in the web browser to send //! HTTP requests for particular special URLs. These special URLs are //! intercepted in the file system support layer (io_fs.c) and used to //! control the LED and animation. Responses generated by the board are //! returned to the browser and inserted into the page HTML dynamically by //! more JavaScript code. //! //! ``IO Control Demo 2'' uses standard HTML forms to pass parameters to CGI //! (Common Gateway Interface) handlers running on the board. These handlers //! process the form data and control the animation and LED as requested before //! sending a response page (in this case, the original form) back to the //! browser. The application registers the names and handlers for each of its //! CGIs with the HTTPD server during initialization and the server calls //! these handlers after parsing URL parameters each time one of the CGI URLs //! is requested. //! //! Information on the state of the various controls in the second demo is //! inserted into the served HTML using SSI (Server Side Include) tags which //! are parsed by the HTTPD server in the application. As with the CGI //! handlers, the application registers its list of SSI tags and a handler //! function with the web server during initialization and this handler is //! called whenever any registered tag is found in a .shtml, .ssi, .shtm or //! .xml file being served to the browser. //! //! In addition to LED and animation speed control, the second example also //! allows a line of text to be sent to the board for display on the LCD //! panel. This is included to illustrate the decoding of HTTP text strings. //! //! Note that the web server used by this example has been modified from the //! example shipped with the basic lwIP package. Additions include SSI and //! CGI support along with the ability to have the server automatically insert //! the HTTP headers rather than having these built in to the files in the //! file system image. //! //! Source files for the internal file system image can be found in the ``fs'' //! directory. If any of these files are changed, the file system image //! (io_fsdata.h) should be rebuilt by running the following command from the //! enet_io directory: //! //! ../../../../tools/bin/makefsfile -i fs -o io_fsdata.h -r -h -q //! //! For additional details on lwIP, refer to the lwIP web page at: //! http://savannah.nongnu.org/projects/lwip/ // //***************************************************************************** //***************************************************************************** // // Defines for setting up the system clock. // //***************************************************************************** #define SYSTICKHZ 100 #define SYSTICKMS (1000 / SYSTICKHZ) //***************************************************************************** // // Interrupt priority definitions. The top 3 bits of these values are // significant with lower values indicating higher priority interrupts. // //***************************************************************************** #define SYSTICK_INT_PRIORITY 0x80 #define ETHERNET_INT_PRIORITY 0xC0 //***************************************************************************** // // A set of flags. The flag bits are defined as follows: // // 0 -> An indicator that the animation timer interrupt has occurred. // //***************************************************************************** #define FLAG_TICK 0 static volatile unsigned long g_ulFlags; //***************************************************************************** // // The graphics context used by the application. // //***************************************************************************** tContext g_sContext; //***************************************************************************** // // The Y extent of the box used to display the text string sent from the // remote web browser. // //***************************************************************************** #define TEXT_BOX_TOP 140 #define TEXT_BOX_BOTTOM 184 //***************************************************************************** // // The position and size of the box used to display the simple animation. // //***************************************************************************** #define ANIM_TOP 210 #define ANIM_HEIGHT 20 #define ANIM_LEFT 40 #define ANIM_WIDTH (320 - (2 * ANIM_LEFT)) //***************************************************************************** // // The RGB value we use to change the color each time the animated bar cycles // back to the start. // //***************************************************************************** #define COLOR_INCREMENT 0x00306040 //***************************************************************************** // // Position and movement granularity for the status indicator shown while // the IP address is being determined. // //***************************************************************************** #define STATUS_X 40 #define STATUS_Y 50 #define MAX_STATUS_X (320 - (2 * STATUS_X)) #define ANIM_STEP_SIZE 8 //***************************************************************************** // // External Application references. // //***************************************************************************** extern void httpd_init(void); //***************************************************************************** // // SSI tag indices for each entry in the g_pcSSITags array. // //***************************************************************************** #define SSI_INDEX_LEDSTATE 0 #define SSI_INDEX_FORMVARS 1 #define SSI_INDEX_SPEED 2 //***************************************************************************** // // This array holds all the strings that are to be recognized as SSI tag // names by the HTTPD server. The server will call SSIHandler to request a // replacement string whenever the pattern (where tagname // appears in the following array) is found in ".ssi", ".shtml" or ".shtm" // files that it serves. // //***************************************************************************** static const char *g_pcConfigSSITags[] = { "LEDtxt", // SSI_INDEX_LEDSTATE "FormVars", // SSI_INDEX_FORMVARS "speed" // SSI_INDEX_SPEED }; //***************************************************************************** // // The number of individual SSI tags that the HTTPD server can expect to // find in our configuration pages. // //***************************************************************************** #define NUM_CONFIG_SSI_TAGS (sizeof(g_pcConfigSSITags) / sizeof (char *)) //***************************************************************************** // // Prototypes for the various CGI handler functions. // //***************************************************************************** static char *ControlCGIHandler(int32_t iIndex, int32_t i32NumParams, char *pcParam[], char *pcValue[]); static char *SetTextCGIHandler(int32_t iIndex, int32_t i32NumParams, char *pcParam[], char *pcValue[]); //***************************************************************************** // // Prototype for the main handler used to process server-side-includes for the // application's web-based configuration screens. // //***************************************************************************** static int32_t SSIHandler(int32_t iIndex, char *pcInsert, int32_t iInsertLen); //***************************************************************************** // // CGI URI indices for each entry in the g_psConfigCGIURIs array. // //***************************************************************************** #define CGI_INDEX_CONTROL 0 #define CGI_INDEX_TEXT 1 //***************************************************************************** // // This array is passed to the HTTPD server to inform it of special URIs // that are treated as common gateway interface (CGI) scripts. Each URI name // is defined along with a pointer to the function which is to be called to // process it. // //***************************************************************************** static const tCGI g_psConfigCGIURIs[] = { { "/iocontrol.cgi", (tCGIHandler)ControlCGIHandler }, // CGI_INDEX_CONTROL { "/settxt.cgi", (tCGIHandler)SetTextCGIHandler } // CGI_INDEX_TEXT }; //***************************************************************************** // // The number of individual CGI URIs that are configured for this system. // //***************************************************************************** #define NUM_CONFIG_CGI_URIS (sizeof(g_psConfigCGIURIs) / sizeof(tCGI)) //***************************************************************************** // // The file sent back to the browser by default following completion of any // of our CGI handlers. Each individual handler returns the URI of the page // to load in response to it being called. // //***************************************************************************** #define DEFAULT_CGI_RESPONSE "/io_cgi.ssi" //***************************************************************************** // // The file sent back to the browser in cases where a parameter error is // detected by one of the CGI handlers. This should only happen if someone // tries to access the CGI directly via the broswer command line and doesn't // enter all the required parameters alongside the URI. // //***************************************************************************** #define PARAM_ERROR_RESPONSE "/perror.htm" #define JAVASCRIPT_HEADER \ "\n" //***************************************************************************** // // Timeout for DHCP address request (in seconds). // //***************************************************************************** #ifndef DHCP_EXPIRE_TIMER_SECS #define DHCP_EXPIRE_TIMER_SECS 45 #endif //***************************************************************************** // // The positions of the circles in the animation used while waiting for an IP // address. // //***************************************************************************** const int32_t g_ppi32CirclePos[][2] = { { 12, 0 }, { 8, -9 }, { 0, -12 }, { -8, -9 }, { -12, 0 }, { -8, 9 }, { 0, 12 }, { 8, 9 } }; //***************************************************************************** // // The colors of the circles in the animation used while waiting for an IP // address. // //***************************************************************************** const uint32_t g_pui32CircleColor[] = { 0x111111, 0x333333, 0x555555, 0x777777, 0x999999, 0xbbbbbb, 0xdddddd, 0xffffff, }; //***************************************************************************** // // The current color index for the animation used while waiting for an IP // address. // //***************************************************************************** uint32_t g_ui32ColorIdx; //***************************************************************************** // // The current IP address. // //***************************************************************************** uint32_t g_ui32IPAddress; //***************************************************************************** // // The application's graphics context. // //***************************************************************************** tContext g_sContext; //***************************************************************************** // // The system clock frequency. Used by the SD card driver. // //***************************************************************************** uint32_t g_ui32SysClock; //***************************************************************************** // // The error routine that is called if the driver library encounters an error. // //***************************************************************************** #ifdef DEBUG void __error__(char *pcFilename, uint32_t ui32Line) { } #endif //***************************************************************************** // // This CGI handler is called whenever the web browser requests iocontrol.cgi. // //***************************************************************************** static char * ControlCGIHandler(int32_t iIndex, int32_t i32NumParams, char *pcParam[], char *pcValue[]) { int32_t i32LEDState, i32Speed; bool bParamError; // // We have not encountered any parameter errors yet. // bParamError = false; // // Get each of the expected parameters. // i32LEDState = FindCGIParameter("LEDOn", pcParam, i32NumParams); i32Speed = GetCGIParam("speed_percent", pcParam, pcValue, i32NumParams, &bParamError); // // Was there any error reported by the parameter parser? // if(bParamError || (i32Speed < 0) || (i32Speed > 100)) { return(PARAM_ERROR_RESPONSE); } // // We got all the parameters and the values were within the expected ranges // so go ahead and make the changes. // io_set_led((i32LEDState == -1) ? false : true); io_set_animation_speed(i32Speed); // // Send back the default response page. // return(DEFAULT_CGI_RESPONSE); } //***************************************************************************** // // This CGI handler is called whenever the web browser requests settxt.cgi. // //***************************************************************************** static char * SetTextCGIHandler(int32_t i32Index, int32_t i32NumParams, char *pcParam[], char *pcValue[]) { long lStringParam; char pcDecodedString[48]; tRectangle sRect; // // Find the parameter that has the string we need to display. // lStringParam = FindCGIParameter("DispText", pcParam, i32NumParams); // // If the parameter was not found, show the error page. // if(lStringParam == -1) { return(PARAM_ERROR_RESPONSE); } // // The parameter is present. We need to decode the text for display. // DecodeFormString(pcValue[lStringParam], pcDecodedString, 48); // // Erase the previous string and overwrite it with the new one. // sRect.i16XMin = 15; sRect.i16XMax = GrContextDpyWidthGet(&g_sContext) - 15 -1 ; sRect.i16YMin = TEXT_BOX_TOP + 6; sRect.i16YMax = TEXT_BOX_BOTTOM - 2; GrContextForegroundSet(&g_sContext, ClrBlack); GrRectFill(&g_sContext, &sRect); // // Now draw the new string after setting the clipping rectangle. // GrContextClipRegionSet(&g_sContext, &sRect); GrContextForegroundSet(&g_sContext, ClrYellow); GrStringDrawCentered(&g_sContext, pcDecodedString, -1, (sRect.i16XMin + sRect.i16XMax) / 2, (sRect.i16YMin + sRect.i16YMax) / 2, 0); // // Reset the clipping rectangle to the full screen. // sRect.i16XMin = 0; sRect.i16XMax = GrContextDpyWidthGet(&g_sContext) - 1; sRect.i16YMin = 0; sRect.i16YMax = GrContextDpyHeightGet(&g_sContext) - 1; GrContextClipRegionSet(&g_sContext, &sRect); // // Tell the HTTPD server which file to send back to the client. // return(DEFAULT_CGI_RESPONSE); } //***************************************************************************** // // This function is called by the HTTP server whenever it encounters an SSI // tag in a web page. The iIndex parameter provides the index of the tag in // the g_pcConfigSSITags array. This function writes the substitution text // into the pcInsert array, writing no more than iInsertLen characters. // //***************************************************************************** static int32_t SSIHandler(int32_t iIndex, char *pcInsert, int32_t iInsertLen) { // // Which SSI tag have we been passed? // switch(iIndex) { case SSI_INDEX_LEDSTATE: io_get_ledstate(pcInsert, iInsertLen); break; case SSI_INDEX_FORMVARS: usnprintf(pcInsert, iInsertLen, "%sls=%d;\nsp=%d;\n%s", JAVASCRIPT_HEADER, io_is_led_on(), io_get_animation_speed(), JAVASCRIPT_FOOTER); break; case SSI_INDEX_SPEED: io_get_animation_speed_string(pcInsert, iInsertLen); break; default: usnprintf(pcInsert, iInsertLen, "??"); break; } // // Tell the server how many characters our insert string contains. // return(strlen(pcInsert)); } //***************************************************************************** // // The interrupt handler for the SysTick interrupt. // //***************************************************************************** void SysTickIntHandler(void) { // // Call the lwIP timer handler. // lwIPTimer(SYSTICKMS); } //***************************************************************************** // // The interrupt handler for the timer used to pace the animation. // //***************************************************************************** void AnimTimerIntHandler(void) { // // Clear the timer interrupt. // ROM_TimerIntClear(TIMER2_BASE, TIMER_TIMA_TIMEOUT); // // Indicate that a timer interrupt has occurred. // HWREGBITW(&g_ulFlags, FLAG_TICK) = 1; } //***************************************************************************** // // Display an lwIP type IP Address. // //***************************************************************************** void DisplayIPAddress(uint32_t ui32Addr, uint32_t ui32Col, uint32_t ui32Row) { char pcBuf[16]; // // Convert the IP Address into a string. // usprintf(pcBuf, "%d.%d.%d.%d", ui32Addr & 0xff, (ui32Addr >> 8) & 0xff, (ui32Addr >> 16) & 0xff, (ui32Addr >> 24) & 0xff); // // Display the string. // GrStringDraw(&g_sContext, pcBuf, -1, ui32Col, ui32Row, false); } //***************************************************************************** // // Required by lwIP library to support any host-related timer functions. // //***************************************************************************** void lwIPHostTimerHandler(void) { uint32_t ui32Idx, ui32NewIPAddress, ui32Width; tRectangle sRect; // // Get the width and height of the display. // ui32Width = GrContextDpyWidthGet(&g_sContext); // // Get the current IP address. // ui32NewIPAddress = lwIPLocalIPAddrGet(); // // See if the IP address has changed. // if(ui32NewIPAddress != g_ui32IPAddress) { // // Clear the display. // sRect.i16XMin = STATUS_X; sRect.i16YMin = STATUS_Y - 22; sRect.i16XMax = MAX_STATUS_X; sRect.i16YMax = TEXT_BOX_TOP - 10; GrContextForegroundSet(&g_sContext, ClrBlack); GrRectFill(&g_sContext, &sRect); GrContextForegroundSet(&g_sContext, ClrWhite); // // See if there is an IP address assigned. // if(ui32NewIPAddress == 0xffffffff) { // // Indicate that there is no link. // GrStringDrawCentered(&g_sContext, "Waiting for link", -1, ui32Width / 2, STATUS_Y, false); } else if(ui32NewIPAddress == 0) { // // There is no IP address, so indicate that the DHCP process is // running. // GrStringDrawCentered(&g_sContext, "Waiting for IP address", -1, ui32Width / 2, STATUS_Y, false); } else { // // Display the new IP address information. // GrStringDraw(&g_sContext, "IP Address:", -1, 60, STATUS_Y, false); GrStringDraw(&g_sContext, "Subnet Mask:", -1, 60, STATUS_Y + 20, false); GrStringDraw(&g_sContext, "Gateway:", -1, 60, STATUS_Y + 40, false); DisplayIPAddress(ui32NewIPAddress, ui32Width / 2, STATUS_Y); DisplayIPAddress(lwIPLocalNetMaskGet(), ui32Width / 2, STATUS_Y + 20); DisplayIPAddress(lwIPLocalGWAddrGet(), ui32Width / 2, STATUS_Y + 40); } // // Save the new IP address. // g_ui32IPAddress = ui32NewIPAddress; } // // If there is not an IP address, draw the animated circle. // if((ui32NewIPAddress == 0) || (ui32NewIPAddress == 0xffffffff)) { // // Loop through the circles in the animation. // for(ui32Idx = 0; ui32Idx < 8; ui32Idx++) { // // Draw this circle. // GrContextForegroundSet(&g_sContext, g_pui32CircleColor[(g_ui32ColorIdx + ui32Idx) & 7]); GrCircleFill(&g_sContext, (ui32Width / 2) + g_ppi32CirclePos[ui32Idx][0], STATUS_Y + g_ppi32CirclePos[ui32Idx][1] + 44, 2); } // // Increment the color index. // g_ui32ColorIdx++; } } //***************************************************************************** // // This example demonstrates the use of the Ethernet Controller and lwIP // TCP/IP stack to control various peripherals on the board via a web // browser. // //***************************************************************************** int main(void) { uint32_t ui32User0, ui32User1, ui32AnimPos, ui32Color; uint8_t pui8MACArray[8]; tRectangle sRect; // // Run from the PLL at 120 MHz. // g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000); // // Configure the device pins. // PinoutSet(); // // Initialize the display driver. // Kentec320x240x16_SSD2119Init(g_ui32SysClock); // // Initialize the graphics context. // GrContextInit(&g_sContext, &g_sKentec320x240x16_SSD2119); // // Draw the application frame. // FrameDraw(&g_sContext, "enet-io"); // // Configure SysTick for a periodic interrupt. // ROM_SysTickPeriodSet(g_ui32SysClock / SYSTICKHZ); ROM_SysTickEnable(); ROM_SysTickIntEnable(); // // Configure the hardware MAC address for Ethernet Controller filtering of // incoming packets. The MAC address will be stored in the non-volatile // USER0 and USER1 registers. // ROM_FlashUserGet(&ui32User0, &ui32User1); if((ui32User0 == 0xffffffff) || (ui32User1 == 0xffffffff)) { // // We should never get here. This is an error if the MAC address has // not been programmed into the device. Exit the program. // GrContextForegroundSet(&g_sContext, ClrRed); GrStringDrawCentered(&g_sContext, "MAC Address", -1, GrContextDpyWidthGet(&g_sContext) / 2, (GrContextDpyHeightGet(&g_sContext) / 2) - 4, false); GrStringDrawCentered(&g_sContext, "Not Programmed!", -1, GrContextDpyWidthGet(&g_sContext) / 2, (GrContextDpyHeightGet(&g_sContext) / 2) + 16, false); while(1) { } } // // Tell the user what we are doing just now. // GrStringDrawCentered(&g_sContext, "Waiting for IP", -1, GrContextDpyWidthGet(&g_sContext) / 2, STATUS_Y, false); // // Draw the box inside which a text string sent from browser will be // displayed. // sRect.i16XMin = 10; sRect.i16XMax = GrContextDpyWidthGet(&g_sContext) - sRect.i16XMin - 1; sRect.i16YMin = TEXT_BOX_TOP; sRect.i16YMax = TEXT_BOX_BOTTOM - 1; GrContextForegroundSet(&g_sContext, ClrWhite); GrRectDraw(&g_sContext, &sRect); GrContextFontSet(&g_sContext, g_psFontCmss18); GrStringDraw(&g_sContext, " Browser Text: ", -1, sRect.i16XMin + 6, TEXT_BOX_TOP - 10, true); // // Draw the box inside which we will draw the animation. // sRect.i16XMin = ANIM_LEFT; sRect.i16XMax = (ANIM_LEFT + ANIM_WIDTH) - 1; sRect.i16YMin = ANIM_TOP; sRect.i16YMax = (ANIM_TOP + ANIM_HEIGHT) - 1; GrContextForegroundSet(&g_sContext, ClrWhite); GrRectDraw(&g_sContext, &sRect); // // Initialize the animation variables. // ui32AnimPos = ANIM_LEFT + 1; ui32Color = ClrRed; // // Convert the 24/24 split MAC address from NV ram into a 32/16 split // MAC address needed to program the hardware registers, then program // the MAC address into the Ethernet Controller registers. // pui8MACArray[0] = ((ui32User0 >> 0) & 0xff); pui8MACArray[1] = ((ui32User0 >> 8) & 0xff); pui8MACArray[2] = ((ui32User0 >> 16) & 0xff); pui8MACArray[3] = ((ui32User1 >> 0) & 0xff); pui8MACArray[4] = ((ui32User1 >> 8) & 0xff); pui8MACArray[5] = ((ui32User1 >> 16) & 0xff); // // Initialze the lwIP library, using DHCP. // lwIPInit(g_ui32SysClock, pui8MACArray, 0, 0, 0, IPADDR_USE_DHCP); // // Setup the device locator service. // LocatorInit(); LocatorMACAddrSet(pui8MACArray); LocatorAppTitleSet("DK-TM4C129X enet_io"); // // Initialize a sample httpd server. // httpd_init(); // // Set the interrupt priorities. We set the SysTick interrupt to a higher // priority than the Ethernet interrupt to ensure that the file system // tick is processed if SysTick occurs while the Ethernet handler is being // processed. This is very likely since all the TCP/IP and HTTP work is // done in the context of the Ethernet interrupt. // ROM_IntPrioritySet(INT_EMAC0, ETHERNET_INT_PRIORITY); ROM_IntPrioritySet(FAULT_SYSTICK, SYSTICK_INT_PRIORITY); // // Pass our tag information to the HTTP server. // http_set_ssi_handler((tSSIHandler)SSIHandler, g_pcConfigSSITags, NUM_CONFIG_SSI_TAGS); // // Pass our CGI handlers to the HTTP server. // http_set_cgi_handlers(g_psConfigCGIURIs, NUM_CONFIG_CGI_URIS); // // Initialize IO controls // io_init(); // // Loop forever, processing the on-screen animation. All other work is // done in the interrupt handlers. // while(1) { // // Wait for a new tick to occur. // while(!g_ulFlags) { // // Do nothing. // } // // Clear the flag now that we have seen it. // HWREGBITW(&g_ulFlags, FLAG_TICK) = 0; // // Update the bar position and check for wrap. // ui32AnimPos ++; if(ui32AnimPos == ((ANIM_LEFT + ANIM_WIDTH) - 1)) { // // We've reached the right edge so change color and move back // to the left. // ui32AnimPos = ANIM_LEFT + 1; ui32Color += COLOR_INCREMENT; } // // Now we draw the new bar. Note that we need to temporarily // disable the ethernet interrupt while doing this since we have // been naughty and set this app up so that it accesses the graphics // library from two different contexts. // ROM_IntDisable(INT_EMAC0); GrContextForegroundSet(&g_sContext, ui32Color); GrLineDrawV(&g_sContext, ui32AnimPos, (ANIM_TOP + 1), (ANIM_TOP + ANIM_HEIGHT) - 2); ROM_IntEnable(INT_EMAC0); } }