Blog Page 3

[Electric Skateboard]#3. Wi-Fi Receiver and Controller

0

Hardware of Wi-Fi Receiver

Wi-Fi 수신기는 스마트폰에서 데이터를 수신하여 스케이트 보드의 속도를 조절하는 장치이며, 하드웨어 구성은 아래와 같다.

20151029_143029아래 사진은 완성된 하드웨어 사진이다. 배터리나 다른 보드들과의 circuit short를 방지하기 위해 절연 테이트를 부착하여 조금 부족해 보이지만 동작에는 문제가 없다. 오른쪽에 3핀, 2핀이 나와 있는데, 위에 3핀은 PWM,3.3V VCC,GND 핀이고, 아래 2핀은 배터리에서 받아 올 5V VCC, GND 핀이다. 20151030_082315

Wi-Fi Receiver Firmware

Source Code

Wi-Fi Receiver firmware는 아래 그림과 같은 구조로 동작된다. 스마트폰에서 Speed Up 명령을 주면 Wi-Fi Receiver는 속도를 5% 올리고 Speed Down 명령을 주면 속도를 5% 내린다.

20151030_084225예외 상황에서는 아래 그림과 같이 Motor를 정지 한다. 단 Motor가 정지 되어도 가속도 때문에 스케이트 보드는 계속 움직일 수는 있다. 20151030_085505

Wi-Fi 수신기에 대한 소스코드는 아래 경로에서 다운로드 할 수 있다. https://developer.mbed.org/users/kaizen/code/Wifi_Electric_Skateboard/

#include "mbed.h"
#include "Servo.h"
#include "WizFi250Interface.h"

#define SECURE WizFi250::SEC_WPA2_MIXED
#define SSID "Kaizen_Skate"
#define PASS "qwertyuiop"

#define SERVER_PORT    5000
#define STOP_MOTOR     50

#if defined(TARGET_WIZwiki_W7500P)
    WizFi250Interface wizfi250(PC_2,PC_3,PC_13,PC_14,PC_12,NC,115200);
    Serial pc(USBTX, USBRX);
    Servo motor_speed(PC_8);      // it used to percentage value ( 0 ~ 1 )
    DigitalOut green_led(LED2);    
    DigitalOut red_led(LED1);
    DigitalOut blue_led(LED3);
#endif

#define START_MSG       "START"
#define END_MSG         "END\n"
#define CONTROL_UP      "UPXX"
#define CONTROL_DOWN    "DOWN"
#define CONTROL_CRUISER "CRUI"
#define CONTROL_STOP    "STOP"

#define STATUS_CMD_OK   "OKXX"
#define STATUS_CMD_FAIL "FAIL"

#define CONTROL_MSG_SIZE    19

// Control Pkt Format : START,CONTROL,SPEED_VALUE,END
//                      START,UPXX,500,END
// Status Pkt Format : START,CONTROL,SPEED_VALUE,STATUS,END

struct control_pkt{
    char start_msg[6];
    char control_msg[5];
    char speed_msg[4];
    char end_msg[4];
};
void parse(char buffer[], int *j, char *string);

int main() {    
    int recv_cnt,j;
    volatile float speed_value = 0.5;
    char recv_control_msg[100];
    char status_msg[100];
    control_pkt control;

    pc.baud(115200);
    green_led = 1; red_led = 1; blue_led = 1;

    wizfi250.init();
    wizfi250.setAntMode(WizFi250::PCB);
    wizfi250.setAddress("192.168.100.2","255.255.255.0","192.168.100.1");
    if ( wizfi250.connect(SECURE, SSID, PASS, WizFi250::WM_AP))
    {
        red_led = 0;
        return -1;
    }
    printf("IP Address is %s\r\n", wizfi250.getIPAddress());
    green_led = 0; red_led = 0; blue_led = 0;

    TCPSocketServer server;
    TCPSocketConnection client;

    while(true)
    {
        if( client.is_connected() == false )
        {
            green_led = 1; red_led = 1; blue_led = 0;            
            client.close();
            server.close();

            if(speed_value == 0.5)
            {
                server.bind(SERVER_PORT);
                server.listen();

                printf("\nWait for new connection...\r\n");
                server.accept(client);
                client.set_blocking(false, 1500);
            }
            else if(speed_value <= 0.4)
            {
                printf("Speed decrease +\r\n");

                speed_value = 0.5;
                motor_speed = speed_value;
//                wait(0.5);
//                speed_value = 0.57;
//                motor_speed = speed_value;
//                wait(5);
//                speed_value = 0.5;
//                motor_speed = speed_value;
            }
            else
            {
                printf("Speed decrease -\r\n");
                speed_value = 0.5;  motor_speed = speed_value;
//                wait(0.5);
//                speed_value = 0.41; motor_speed = speed_value;
//                wait(2);
//                speed_value = 0.35; motor_speed = speed_value;
//                wait(2);
//                speed_value = 0.41; motor_speed = speed_value;
//                wait(2);
//                speed_value = 0.5;  motor_speed = speed_value;
//                wait(2);
            }
        }
        else
        {
            motor_speed = speed_value;

            green_led = 0; red_led = 1; blue_led = 1;
            //recv_cnt = client.receive_all((char*)recv_control_msg, sizeof(control)-1);
            recv_cnt = client.receive_all((char*)recv_control_msg, sizeof(control));

            j=0;
            parse(recv_control_msg, &j, control.start_msg);
            parse(recv_control_msg, &j, control.control_msg);
            parse(recv_control_msg, &j, control.speed_msg);
            parse(recv_control_msg, &j, control.end_msg);

            if(recv_cnt > 0)
            {
                if( (strncmp(control.start_msg,START_MSG,sizeof(control.start_msg)) != 0) || 
                    (strncmp(control.end_msg,END_MSG,sizeof(control.end_msg)) != 0) )
                {
                    printf("TEST Error\r\n");
                    sprintf(status_msg,"%s,%03d,%s,%s\n",START_MSG,int(speed_value*100),STATUS_CMD_FAIL,END_MSG);
                    client.send_all((char*)status_msg,strlen(status_msg));
                    continue;
                }

                if( strncmp(control.control_msg,CONTROL_UP,sizeof(control.control_msg)) == 0 )
                {
                    speed_value += 0.05; 
                    motor_speed = speed_value;
                    printf("TEST UP %f\r\n",speed_value); 
                }
                else if( strncmp(control.control_msg,CONTROL_DOWN,sizeof(control.control_msg)) == 0)
                {
                    speed_value -= 0.05; 
                    motor_speed = speed_value;
                    printf("TEST DOWN %f\r\n",speed_value);
                }
                else if( strncmp(control.control_msg,CONTROL_CRUISER,sizeof(control.control_msg)) == 0)
                {
                    printf("TEST CRUISER\r\n"); 
                    speed_value = (float)(atoi(control.speed_msg)) / 100;
                    motor_speed = speed_value;
                }
                else if( strncmp(control.control_msg,CONTROL_STOP,sizeof(control.control_msg)) == 0)
                {
                    printf("TEST STOP\r\n"); 
                    speed_value = 0.5;
                    motor_speed = speed_value;
                }
                else
                {
                    printf("TEST Error 1-2\r\n");
                    sprintf(status_msg,"%s,%03d,%s,%s\n",START_MSG,int(speed_value*100),STATUS_CMD_FAIL,END_MSG);
                    client.send_all((char*)status_msg,strlen(status_msg));
                    continue;
                }

                sprintf(status_msg,"%s,%03d,%s,%s\n",START_MSG,int(speed_value*100),STATUS_CMD_OK,END_MSG);
                client.send_all((char*)status_msg,strlen(status_msg));
            }
            else
            {
                sprintf(status_msg,"%s,%03d,%s,%s\n",START_MSG,int(speed_value*100),STATUS_CMD_OK,END_MSG);
                client.send_all((char*)status_msg,strlen(status_msg));
            }
        }
    }// while
}

void parse(char buffer[], int *j, char *string) {
//extracts next location string data item from buffer
    int i=0;
    for (i=0; i<=strlen(buffer); i++) {  //TOTAL SIZE OF RETURNED DATA
        if ((buffer[*j+i] == ',')||(buffer[*j+i] == '\0' )) { //IF comma or end of string
            //comma is the string field delimiter
            string[i]=0; //SETS END OF SRTRING TO 0
            *j=*j+i+1; //UPDATES to 1 after comma seperated value
            break;
        } else string[i]=buffer[*j+i]; //Keep adding to the string
    }
}

Android Application

Screenshot_2015-10-29-16-20-25

Source Code

https://github.com/kaizen8501/WiFi_Skateboard

Demo Video

[Electric Skateboard]#1. Material

0

Skateboard Material

기본적인 스케이트보드 구동을 위해서는 아래와 같은 재료들이 필수적으로 필요 하다. 보드에 대해서 잘 알지는 못하지만, 라이딩용으로 만들기 위해 90mm Wheel과 34″ Longboard Deck를 구매 하였다. 아래 링크를 이용하면 각 부품에 대한 자세한 설명과 구매 정보를 확인 할 수 있다.

34″ Cruiser Longboard Deck

30-inch-Electric-Longboard-325x127

Trucks

20151029_091514

90mm Wheels

90mm-longboard-wheels-epower-neon-green

Bearings 8mm

longboard-bearings-322x215

Electric Material

Motor

Electric-Skateboard-Motor250mm RC Brushless Motor, 2200 Watts / 2.95HP Motor, 200KV, 80Amp, 50mm x 65mm, 8mm shaft

Motor Mount

50mm-motor-mount

44T Drive Wheel Pulley Adapter

44T-Electric-Skateboard-Drive-Wheel-Pulley16t-htd5-motor-pulley-322x215

12S 120A ESC

TorqueBoards-12S-120A-ESC-OPTO1

TorqueBoards ESC Programming Card

torqueboards-esc-programming-card

12S UBEC

12s-ubec-322x215

On/Off Anti-Spark Power Switch

antispark-highvoltage-blue-led-switch

Assembly

위 부품들은 전동 스케이트 보드를 만들기 위해 필요한 부품들이다. RC를 많이 해본 사람이라면 친숙한 부품일 수 있지만, RC에 입문하는 입장에서 처음 접하는 부품들이 많이 있었고 각 부품들을 받았을 때는 어떻게 해야 할지 막막하기도 했었다. 아래 사진들을 보면 조립 방법을 유추 할 수 있다.

Wheel Adapter 조립

20151023_11572020151023_115744

Wheel & Trucks 조립

20151021_20493120151021_204910

20151021_20492420151029_134552

How to implement CLI (Command Line Interpreter) in your Cortex M0 using FreeRTOS

0

This post shows how to implement CLI ( Command Line Interpreter ) in your Cortex M0 using FreeRTOS.

Meterials

I used thease meterials in this project.

FreeRTOS

FreeRTOS is a popular[1] real-time operating system kernel[2][3] for embedded devices, that has been ported to 35 microcontrollers. It is distributed under the GPL with an optional exception. The exception permits users’ proprietary code to remain closed source while maintaining the kernel itself as open source, thereby facilitating the use of FreeRTOS in proprietary applications.[4]

Key Features

  • Very small memory footprint, low overhead, and very fast execution.
  • Tick-less option for low power applications.
  • Equally good for hobbyists who are new to OSes, and professional developers working on commercial products.
  • Scheduler can be configured for both preemptive or cooperative operation.
  • Coroutine support (Coroutine in FreeRTOS is a very simple and lightweight task that has very limited use of stack)

Reference : https://en.wikipedia.org/wiki/FreeRTOS

CLI(Command Line Interpreter)

A command-line interface or command language interpreter (CLI), also known as command-line user interface, console user interface,[1] and character user interface (CUI), is a means of interacting with a computer program where the user (or client) issues commands to the program in the form of successive lines of text (command lines).

Reference :https://en.wikipedia.org/wiki/Command-line_interface

FreeRTOS Porting Guide on W7500

If you’d like to know how to use freeRTOS on your Cortex M0, refer to this Link

Repository of Project

There is repository of my CLI project. If you are heading to CLI Project, you can download and use it.

20150922_215621

Expalin breif structure

Serial.c

This file is serial driver code for using UART interface of W7500 in FreeRTOS. Default UART interface of WIZwiki-W7500ECO is UART2 so serial.c set UART2 and manage serial data using Queue.

  • xCharsForTx : Queue for Tx data
  • xRxedChars : Queue for Rx data
xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
xComPortHandle xReturn;
	/* Create the queues used to hold Rx/Tx characters. */
	xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
	xCharsForTx = xQueueCreate( uxQueueLength + 1, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
	
	/* If the queue/semaphore was created correctly then setup the serial port
	hardware. */
	if( ( xRxedChars != serINVALID_QUEUE ) && ( xCharsForTx != serINVALID_QUEUE ) )
	{
        S_UART_Init(115200);

        S_UART_ITConfig(S_UART_CTRL_RXI,ENABLE);
        NVIC_EnableIRQ(UART2_IRQn);
        
        xReturn = (xComPortHandle)UART2;
	}
	else
	{
		xReturn = ( xComPortHandle ) 0;
	}

	/* This demo file only supports a single port but we have to return
	something to comply with the standard demo header file. */
	return xReturn;
}

void UART2_Handler( void )
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
char cChar;

    if( S_UART_GetITStatus(S_UART_INTSTATUS_RXI) != RESET ) {
        S_UART_ClearITPendingBit(S_UART_INTSTATUS_RXI);
        cChar = S_UART_ReceiveData();
        xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
    }    
    portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}

 

signed portBASE_TYPE xSerialGetChar( xComPortHandle pxPort, signed char *pcRxedChar, TickType_t xBlockTime )
{
	/* The port handle is not required as this driver only supports one port. */
	( void ) pxPort;

	/* Get the next character from the buffer.  Return false if no characters
	are available, or arrive before xBlockTime expires. */
	if( xQueueReceive( xRxedChars, pcRxedChar, xBlockTime ) )
	{
		return pdTRUE;
	}
	else
	{
		return pdFALSE;
	}
}
/*-----------------------------------------------------------*/

void vSerialPutString( xComPortHandle pxPort, const signed char * const pcString, unsigned short usStringLength )
{
signed char *pxNext;

	/* A couple of parameters that this port does not use. */
	( void ) usStringLength;
	( void ) pxPort;

	/* NOTE: This implementation does not handle the queue being full as no
	block time is used! */

	/* The port handle is not required as this driver only supports UART1. */
	( void ) pxPort;

	/* Send each character in the string, one at a time. */
	pxNext = ( signed char * ) pcString;
	while( *pxNext )
	{
		xSerialPutChar( pxPort, *pxNext, serNO_BLOCK );
		pxNext++;
	}
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialPutChar( xComPortHandle pxPort, signed char cOutChar, TickType_t xBlockTime )
{
signed portBASE_TYPE xReturn;
char cChar;
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

	if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) == pdPASS )
	{
		xReturn = pdPASS;
		if( xQueueReceiveFromISR( xCharsForTx, &cChar, &xHigherPriorityTaskWoken ) == pdTRUE )
		{
			/* A character was retrieved from the queue so can be sent to the
			THR now. */
            S_UART_SendData( cChar );
		}        
	}
	else
	{
		xReturn = pdFAIL;
	}

	return xReturn;
}

 

UARTCommandConsole.c, FreeRTOS_CLI.c

Feature of thease files is as below. First, it parses serial input data whether it is command or not and then if it is command, it will run task of command.

CLI-commands.c

It manages command list for using CLI. If you make CLI_Command_Definition_t as xSetNetConfig, you can add command. In this example, I used SetNetConfig and GetNetConfig command.  

static const CLI_Command_Definition_t xSetNetConfig =
{
	"set-net",
	"set-network <ip-addr> <subnet> <gateway>:\r\n",
	prvSetNetworkConfigCommand, /* The function to run. */
	3 /* Three parameters are expected, which can take any value. */
};

static const CLI_Command_Definition_t xGetNetConfig =
{
	"get-net",
	"get-network\r\n",
	prvGetNetworkConfigCommand, /* The function to run. */
	0
};

void vRegisterCLICommands(void)
{
	/* Register all the command line commands defined immediately above. */
    FreeRTOS_CLIRegisterCommand(&xSetNetConfig);
    FreeRTOS_CLIRegisterCommand(&xGetNetConfig);

}

These codes are task for each command. prvSetNetworkConfigCommand function is run when “set-net” command is input. This function can set IP address, Subnet mask and Gateway address of WIZwiki-W7500ECO. prvGetNetworkConfigCommand is run when “get-net” command is input. This function can get network information of WIZwiki-W7500ECO.

static BaseType_t prvSetNetworkConfigCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
char ip[16]={0};
char subnet[16]={0};
char gateway[16]={0};

BaseType_t xParameterStringLength, xReturn;

	/* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );

    pcParameter = FreeRTOS_CLIGetParameter(pcCommandString, 1, &xParameterStringLength);
    strncat(ip, pcParameter, xParameterStringLength);
    setSIPR( str_to_ip_array(ip) );

    pcParameter = FreeRTOS_CLIGetParameter(pcCommandString, 2, &xParameterStringLength);
    strncat(subnet, pcParameter, xParameterStringLength);
    setSUBR( str_to_ip_array(subnet) ) ;

    pcParameter = FreeRTOS_CLIGetParameter(pcCommandString, 3, &xParameterStringLength);
    strncat(gateway, pcParameter, xParameterStringLength);
    setGAR( str_to_ip_array(gateway) );

    sprintf(pcWriteBuffer,SUCCESS_MESSAGE); 

    xReturn = pdFALSE;
    return xReturn;
}

static BaseType_t prvGetNetworkConfigCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
BaseType_t xReturn;
uint8_t tmp[8];
static int paramCount = 0;

    /* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );


    switch(paramCount)
    {
        case 0:
            getSHAR(tmp);
            sprintf(pcWriteBuffer,"MAC ADDRESS : %.2X:%.2X:%.2X:%.2X:%.2X:%.2X\r\n",tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        case 1:
            getSIPR(tmp);
            sprintf(pcWriteBuffer,"IP ADDRESS  : %d.%d.%d.%d\r\n",tmp[0],tmp[1],tmp[2],tmp[3]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        case 2:
            getGAR(tmp);
            sprintf(pcWriteBuffer,"GW ADDRESS  : %d.%d.%d.%d\r\n",tmp[0],tmp[1],tmp[2],tmp[3]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        case 3:
            getSUBR(tmp);
            sprintf(pcWriteBuffer,"SN MASK    : %d.%d.%d.%d\r\n",tmp[0],tmp[1],tmp[2],tmp[3]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        default:
            paramCount=0;
            xReturn = pdFALSE;
            break;
    }

    return xReturn;
}

 

Demo

To check my example,I did test as below. First, I checked default network information of WIZwiki-W7500ECO using get-net command and then I send ping data on my PC. Second, I changed IP address to 192.168.77.50 using set-net command and then I send ping again. 20150922_223157

Future Work

This project is prototype for using FreeRTOS on WIZwiki-W7500ECO so It needs to add more commands. And, In current version, It can’t use network libraries of W7500 because It handles socket event using polling method. So I have to change network libraries for using interrupt method.

FreeRTOS기반의 W7500 UART CLI (Command Line Interpreter) 구현하기

0

CLI(Command Line Interpreter)

CLI, Command line interface) 또는 명령어 인터페이스는 텍스트 터미널을 통해 사용자와 컴퓨터가 상호 작용하는 방식을 뜻한다. 즉, 작업 명령은 사용자가 컴퓨터 키보드 등을 통해 문자열의 형태로 입력하며, 컴퓨터로부터의 출력 역시 문자열의 형태로 주어진다.

참고 : https://ko.wikipedia.org/wiki/%EB%AA%85%EB%A0%B9%EC%A4%84%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4

Repository of Project

Repository에 접속한 후, Download ZIP혹은 Clone in Desktop 버튼을 클릭하면 해당 프로젝트를 사용 할 수 있다.

20150922_215621

Explain key point function of this project

serial.c

serial.c는 W7500에서 FreeRTOS 기반에서 UART를 사용하기 위한 Driver Code이다.

Serial 초기화(xSerialPortInitMinimal())과정에서 Rx Data를 관리하기 위한 xRxedChars Queue와 Tx Data를 관리하기 위한 xCharsForTx Queue를 생성한다.

UART Rx Interrupt가 발생하면, xRxedChars Queue에 보관하고 xSerialGetChar() 시에 Queue에서 Data를 꺼내서 사용한다.

xComPortHandle xSerialPortInitMinimal( unsigned long ulWantedBaud, unsigned portBASE_TYPE uxQueueLength )
{
xComPortHandle xReturn;
	/* Create the queues used to hold Rx/Tx characters. */
	xRxedChars = xQueueCreate( uxQueueLength, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
	xCharsForTx = xQueueCreate( uxQueueLength + 1, ( unsigned portBASE_TYPE ) sizeof( signed char ) );
	
	/* If the queue/semaphore was created correctly then setup the serial port
	hardware. */
	if( ( xRxedChars != serINVALID_QUEUE ) && ( xCharsForTx != serINVALID_QUEUE ) )
	{
        S_UART_Init(115200);

        S_UART_ITConfig(S_UART_CTRL_RXI,ENABLE);
        NVIC_EnableIRQ(UART2_IRQn);
        
        xReturn = (xComPortHandle)UART2;
	}
	else
	{
		xReturn = ( xComPortHandle ) 0;
	}

	/* This demo file only supports a single port but we have to return
	something to comply with the standard demo header file. */
	return xReturn;
}

void UART2_Handler( void )
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
char cChar;

    if( S_UART_GetITStatus(S_UART_INTSTATUS_RXI) != RESET ) {
        S_UART_ClearITPendingBit(S_UART_INTSTATUS_RXI);
        cChar = S_UART_ReceiveData();
        xQueueSendFromISR( xRxedChars, &cChar, &xHigherPriorityTaskWoken );
    }    
    portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
signed portBASE_TYPE xSerialGetChar( xComPortHandle pxPort, signed char *pcRxedChar, TickType_t xBlockTime )
{
	/* The port handle is not required as this driver only supports one port. */
	( void ) pxPort;

	/* Get the next character from the buffer.  Return false if no characters
	are available, or arrive before xBlockTime expires. */
	if( xQueueReceive( xRxedChars, pcRxedChar, xBlockTime ) )
	{
		return pdTRUE;
	}
	else
	{
		return pdFALSE;
	}
}
/*-----------------------------------------------------------*/

void vSerialPutString( xComPortHandle pxPort, const signed char * const pcString, unsigned short usStringLength )
{
signed char *pxNext;

	/* A couple of parameters that this port does not use. */
	( void ) usStringLength;
	( void ) pxPort;

	/* NOTE: This implementation does not handle the queue being full as no
	block time is used! */

	/* The port handle is not required as this driver only supports UART1. */
	( void ) pxPort;

	/* Send each character in the string, one at a time. */
	pxNext = ( signed char * ) pcString;
	while( *pxNext )
	{
		xSerialPutChar( pxPort, *pxNext, serNO_BLOCK );
		pxNext++;
	}
}
/*-----------------------------------------------------------*/

signed portBASE_TYPE xSerialPutChar( xComPortHandle pxPort, signed char cOutChar, TickType_t xBlockTime )
{
signed portBASE_TYPE xReturn;
char cChar;
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

	if( xQueueSend( xCharsForTx, &cOutChar, xBlockTime ) == pdPASS )
	{
		xReturn = pdPASS;
		if( xQueueReceiveFromISR( xCharsForTx, &cChar, &xHigherPriorityTaskWoken ) == pdTRUE )
		{
			/* A character was retrieved from the queue so can be sent to the
			THR now. */
            S_UART_SendData( cChar );
		}        
	}
	else
	{
		xReturn = pdFAIL;
	}

	return xReturn;
}

 

UARTCommandConsole.c, FreeRTOS_CLI.c

UART로 입력된 명령을 Parsing 해서 지정된 Task를 호출하는 기능을 수행한다. prvUARTCommandConsoleTask() 함수는 Uart Data가 “\r\n” 개행 문자가 나올 때 까지 문자열을 보관한 후, FreeRTOS_CLIProcessCommand()함수를 실행시켜 등록되어 있는 Command인지 확인하고 해당 Task를 호출하는 기능을 수행한다.

CLI-commands.c

Command 이름과 수행해야 하는 Task, Parameter 수를 CLI_Command_definition_t 타입에 저장 한후, FreeRTOS_CLIRegisterCommand()를 이용하여 Command들을 등록한다. 본 예제에서는 SetNetConfig와 GetNetConfig를 등록해서 사용한다.

static const CLI_Command_Definition_t xSetNetConfig =
{
	"set-net",
	"set-network <ip-addr> <subnet> <gateway>:\r\n",
	prvSetNetworkConfigCommand, /* The function to run. */
	3 /* Three parameters are expected, which can take any value. */
};

static const CLI_Command_Definition_t xGetNetConfig =
{
	"get-net",
	"get-network\r\n",
	prvGetNetworkConfigCommand, /* The function to run. */
	0
};

void vRegisterCLICommands(void)
{
	/* Register all the command line commands defined immediately above. */
    FreeRTOS_CLIRegisterCommand(&xSetNetConfig);
    FreeRTOS_CLIRegisterCommand(&xGetNetConfig);

}

위에서 등록한 명령에 대한 Task의 구현부분이다. prvSetNetworkConfigCommand는 set-net 이라는 명령이 들어오면 수행되며, W7500의 IP주소, Subnet Mask, Gateway 주소를 변경하는 기능을 수행한다. prvGetNetworkConfigCommand는 get-net 이라는 명령이 들어오면 수행되며, W7500의 Network 정보를 Serial로 출력하는 기능을 한다.

static BaseType_t prvSetNetworkConfigCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
const char *pcParameter;
char ip[16]={0};
char subnet[16]={0};
char gateway[16]={0};

BaseType_t xParameterStringLength, xReturn;

	/* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );

    pcParameter = FreeRTOS_CLIGetParameter(pcCommandString, 1, &xParameterStringLength);
    strncat(ip, pcParameter, xParameterStringLength);
    setSIPR( str_to_ip_array(ip) );

    pcParameter = FreeRTOS_CLIGetParameter(pcCommandString, 2, &xParameterStringLength);
    strncat(subnet, pcParameter, xParameterStringLength);
    setSUBR( str_to_ip_array(subnet) ) ;

    pcParameter = FreeRTOS_CLIGetParameter(pcCommandString, 3, &xParameterStringLength);
    strncat(gateway, pcParameter, xParameterStringLength);
    setGAR( str_to_ip_array(gateway) );

    sprintf(pcWriteBuffer,SUCCESS_MESSAGE); 

    xReturn = pdFALSE;
    return xReturn;
}

static BaseType_t prvGetNetworkConfigCommand( char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString )
{
BaseType_t xReturn;
uint8_t tmp[8];
static int paramCount = 0;

    /* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	( void ) pcCommandString;
	( void ) xWriteBufferLen;
	configASSERT( pcWriteBuffer );


    switch(paramCount)
    {
        case 0:
            getSHAR(tmp);
            sprintf(pcWriteBuffer,"MAC ADDRESS : %.2X:%.2X:%.2X:%.2X:%.2X:%.2X\r\n",tmp[0],tmp[1],tmp[2],tmp[3],tmp[4],tmp[5]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        case 1:
            getSIPR(tmp);
            sprintf(pcWriteBuffer,"IP ADDRESS  : %d.%d.%d.%d\r\n",tmp[0],tmp[1],tmp[2],tmp[3]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        case 2:
            getGAR(tmp);
            sprintf(pcWriteBuffer,"GW ADDRESS  : %d.%d.%d.%d\r\n",tmp[0],tmp[1],tmp[2],tmp[3]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        case 3:
            getSUBR(tmp);
            sprintf(pcWriteBuffer,"SN MASK    : %d.%d.%d.%d\r\n",tmp[0],tmp[1],tmp[2],tmp[3]); 
            xReturn = pdPASS;
            paramCount++;
            break;
        default:
            paramCount=0;
            xReturn = pdFALSE;
            break;
    }

    return xReturn;
}

Demo

Demo를 위한 절차는 아래와 같다.

  1. get-net 명령을 이용하여 WIZwiki-W7500ECO Board의 Default Network 정보를 출력한다. PC에서 Ping을 보내서 IP 주소가 유효한지 확인한다.
  2. set-net 명령을 이용하여 IP주소를 192.168.77.50으로 변경한다. 변경 후 동일하게 PC에서 Ping을 보내, IP 주소가 유효한지 확인한다.

20150922_223157

Future Work

  1. W7500을 제어하기 위한 Command 보강
  2. FreeRTOS에서 W7500의 Network 기능을 사용하기 위한 Socket Porting

FreeRTOS Porting Guide to W7500(Cortex M0)

0

FreeRTOS

Refer to Link

Download FreeRTOS

FreeRTOS사이트에 접속하면, FreeRTOS에 관한 정보와 문서, 소스 코드를 다운 받을 수 있다.

Folder Structure

FreeRTOS를 다운로드 받으면, 크게 Free-RTOS-Plus 폴더와 FreeRTOS 폴더를 확인 할 수 있다. 자세한 내용은 http://www.freertos.org/a00017.html를 참고 하기 바란다.

20150922_193631

먼저 FreeRTOS 폴더를 확인해 보면 Demo 폴더와 Source 폴더로 구성되어 있으며, Demo 폴더에는 FreeRTOS 커널 기반의 예제 데모 프로젝트가 존재 한다.

20150922_193652

Source 폴더에는 FreeRTOS Kenel 구현 파일들이 존재하며, Portable 폴더에는 프로세스별, IDE별 FreeRTOS Port를 정의한 파일이 존재 한다.

20150922_193732

본 장에서는 Cortex M0계열의 W7500 프로세서를 이용하여 FreeRTOS를 구동하므로, portable/RVDS/ARM_CM0 폴더에 있는 port.c와 portmacro.h를 사용하려고 한다. 추가로 FreeRTOS의 Memory Management를 위해 portable/MemMang 폴더에 있는 파일들을 프로젝트에 추가 해야 한다. Memory Management에 대한 자세한 설명은 http://www.freertos.org/a00111.html를 참고 하기 바란다.

WIZwiki-W7500ECO FreeRTOS Porting

본 장에서는 WIZwiki-W7500ECO Board에 FreeRTOS를 Porting 하는 방법에 대해 설명한다. 본 장에서 구동하는 예제는 WIZwiki-W7500ECO Board의 LED1을 20ms 마다 Blink 하는 프로그램이다.

Step 1 : Download W7500 Library and Example

https://github.com/Wiznet/W7500에 접속한 후, W7500 Library Code를 다운로드 한다.

20150922_201106

Step 2 : Create Keil Project for W7500

W7500-master\W7500x_Library_Examples\Projects\Peripheral_Examples 에 프로젝트를 위한 폴더를 생성한 후, 적당한 프로젝트 파일 하나는 복사한다. 나는 OS\FreeRTOS 폴더를 생성 한 후, GPIO\Blink_LED 안에 있는 파일들을 OS\FreeRTOS 폴더에 복사해서 사용하였다. 원래는 새로운 프로젝트를 만들어서 사용하는 것이 정석이나, WIZnet에서 이미 만들어 놓은 프로젝트의 설정 값들을 그대로 사용하기 위해 위와 같은 방법으로 프로젝트를 생성 하였다.

OS\FreeRTOS\MDK 폴더의 W7500x_GPIO_Blink_LED.uvproj를 열면 아래 그림과 같이 W7500의 GPIO를 On/Off 하는 예제를 확인 할 수 있다.

Include Path 설정법

본 예제는 GPIO\Blink_LED와 폴더 Depth가 동일하기 때문에 Keil 프로젝트의 설정 변경 없이 그대로 사용할 수 있다. 하지만 폴더 Depth를 기존 프로젝트들과 다르게 만들었을 경우, 컴파일이 안되는 문제가 발생 할 수 있다. 이런 경우, 아래 방법을 이용하여 Include Path를 설정하면 문제를 해결 할 수 있다.

[Project]-[Options for Target] 선택 후, [C/C++]탭의 Include Paths에 아래 폴더들의 경로를 지정한다.

  • \Libraries\CMSIS\Device\WIZnet\W7500\Include
  • \Libraries\W7500x_stdPeriph_Driver\inc
  • \Libraries\CMSIS\Include
  • ..\

20150922_202318

20150922_202539

Step 3 : Porting FreeRTOS

Step1에서 다운로드 받은 FreeRTOS 파일을 Step 2의 OS\FreeRTOS\ 폴더에 아래 그림과 같이 복사한다. FreeRTOS 폴더에는 아래 그림에 있는 내용만 남겨 놓고 다른 폴더들은 다 삭제 하기 바란다.

20150922_203751

위 과정을 완료 후, Keil 프로젝트에 FreeRTOS 그룹을 만들고 아래 파일들은 추가 한다.

  • OS\FreeRTOS\FreeRTOS\croutine.c
  • OS\FreeRTOS\FreeRTOS\list.c
  • OS\FreeRTOS\FreeRTOS\queue.c
  • OS\FreeRTOS\FreeRTOS\tasks.c
  • OS\FreeRTOS\FreeRTOS\timers.c
  • OS\FreeRTOS\FreeRTOS\portable\MemMang\heap_2.c
  • OS\FreeRTOS\FreeRTOS\portable\RVDS\ARM_CM0\port.c

20150922_204021

Create FreeRTOSConfig.h

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html.
 *----------------------------------------------------------*/

/* Prevent C code being included by the IAR assembler. */
#ifndef __IASMARM__
    #include <stdint.h>
    extern uint32_t SystemCoreClock;
#endif

#define configUSE_PREEMPTION            1
#define configUSE_IDLE_HOOK                0
#define configUSE_TICK_HOOK                0
#define configCPU_CLOCK_HZ                ( SystemCoreClock )
#define configTICK_RATE_HZ                ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES            ( 5 )
#define configMINIMAL_STACK_SIZE        ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE            ( ( size_t ) ( 10 * 1024 ) )
#define configMAX_TASK_NAME_LEN            ( 16 )

#define configUSE_TRACE_FACILITY    0
#define configUSE_16_BIT_TICKS        0
#define configIDLE_SHOULD_YIELD        1
#define configUSE_MUTEXES                1

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES             0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS                1
#define configTIMER_TASK_PRIORITY        ( 2 )
#define configTIMER_QUEUE_LENGTH        5
#define configTIMER_TASK_STACK_DEPTH    ( 80 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet        1
#define INCLUDE_uxTaskPriorityGet        1
#define INCLUDE_vTaskDelete                1
#define INCLUDE_vTaskCleanUpResources    1
#define INCLUDE_vTaskSuspend            1
#define INCLUDE_vTaskDelayUntil            1
#define INCLUDE_vTaskDelay                1
#define INCLUDE_eTaskGetState            1

/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names - or at least those used in the unmodified vector table. */
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

/* Bump up the priority of recmuCONTROLLING_TASK_PRIORITY to prevent false
positive errors being reported considering the priority of other tasks in the
system. */
#define recmuCONTROLLING_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )

#endif

 

 

Create blink_led_task.c

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "W7500x_gpio.h"


/* Priorities at which the tasks are created. */
#define mainQUEUE_RECEIVE_TASK_PRIORITY	( tskIDLE_PRIORITY + 2 )
#define mainQUEUE_SEND_TASK_PRIORITY		( tskIDLE_PRIORITY + 1 )

/* The rate at which data is sent to the queue.  The 200ms value is converted
to ticks using the portTICK_PERIOD_MS constant. */
#define mainQUEUE_SEND_FREQUENCY_MS			( 200 / portTICK_PERIOD_MS )

/* The number of items the queue can hold.  This is 1 as the receive task
will remove items as they are added, meaning the send task should always find
the queue empty. */
#define mainQUEUE_LENGTH					( 1 )

/* Values passed to the two tasks just to check the task parameter
functionality. */
#define mainQUEUE_SEND_PARAMETER			( 0x1111UL )
#define mainQUEUE_RECEIVE_PARAMETER			( 0x22UL )

/* The number of the LED that is toggled. */
#define mainLED_TO_TOGGLE					( 0 )

/*
 * The tasks as described in the comments at the top of this file.
 */
static void prvQueueReceiveTask( void *pvParameters );
static void prvQueueSendTask( void *pvParameters );

/*
 * Called by main() to create the simply blinky style application if
 * mainCREATE_SIMPLE_BLINKY_DEMO_ONLY is set to 1.
 */
void main_blinky( void );

void vParTestToggleLED( unsigned long ulLED );

/*
 * The hardware only has a single LED.  Simply toggle it.
 */
extern void vMainToggleLED( void );

/* The queue used by both tasks. */
static QueueHandle_t xQueue = NULL;


void main_blinky( void )
{
	/* Create the queue. */
	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( unsigned long ) );

	if( xQueue != NULL )
	{
		/* Start the two tasks as described in the comments at the top of this
		file. */
		xTaskCreate( prvQueueReceiveTask,					/* The function that implements the task. */
					"Rx", 									/* The text name assigned to the task - for debug only as it is not used by the kernel. */
					configMINIMAL_STACK_SIZE, 				/* The size of the stack to allocate to the task. */
					( void * ) mainQUEUE_RECEIVE_PARAMETER, /* The parameter passed to the task - just to check the functionality. */
					mainQUEUE_RECEIVE_TASK_PRIORITY, 		/* The priority assigned to the task. */
					NULL );									/* The task handle is not required, so NULL is passed. */

		xTaskCreate( prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE, ( void * ) mainQUEUE_SEND_PARAMETER, mainQUEUE_SEND_TASK_PRIORITY, NULL );

		/* Start the tasks and timer running. */
		vTaskStartScheduler();
	}

	/* If all is well, the scheduler will now be running, and the following
	line will never be reached.  If the following line does execute, then
	there was insufficient FreeRTOS heap memory available for the idle and/or
	timer tasks	to be created.  See the memory management section on the
	FreeRTOS web site for more details. */
	for( ;; );
}

static void prvQueueSendTask( void *pvParameters )
{
    TickType_t xNextWakeTime;
    const unsigned long ulValueToSend = 100UL;

	/* Check the task parameter is as expected. */
	configASSERT( ( ( unsigned long ) pvParameters ) == mainQUEUE_SEND_PARAMETER );

	/* Initialise xNextWakeTime - this only needs to be done once. */
	xNextWakeTime = xTaskGetTickCount();

	for( ;; )
	{
		/* Place this task in the blocked state until it is time to run again.
		The block time is specified in ticks, the constant used converts ticks
		to ms.  While in the Blocked state this task will not consume any CPU
		time. */
		vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );

		/* Send to the queue - causing the queue receive task to unblock and
		toggle the LED.  0 is used as the block time so the sending operation
		will not block - it shouldn't need to block as the queue should always
		be empty at this point in the code. */
		xQueueSend( xQueue, &ulValueToSend, 0U );
	}
}

static void prvQueueReceiveTask( void *pvParameters )
{
    unsigned long ulReceivedValue;

	/* Check the task parameter is as expected. */
	configASSERT( ( ( unsigned long ) pvParameters ) == mainQUEUE_RECEIVE_PARAMETER );

	for( ;; )
	{
		/* Wait until something arrives in the queue - this task will block
		indefinitely provided INCLUDE_vTaskSuspend is set to 1 in
		FreeRTOSConfig.h. */
		xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY );

		/*  To get here something must have been received from the queue, but
		is it the expected value?  If it is, toggle the LED. */
		if( ulReceivedValue == 100UL )
		{
			vParTestToggleLED( mainLED_TO_TOGGLE );
			ulReceivedValue = 0U;
		}
	}
}

void vParTestToggleLED( unsigned long ulLED )
{
    if( GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == Bit_SET )
        GPIO_ResetBits(GPIOA, GPIO_Pin_1);
    else
        GPIO_SetBits(GPIOA, GPIO_Pin_1);
}

 

Modified main.c

/**
  ******************************************************************************
  * @file    Uart/Printf/main.c 
  * @author  IOP Team
  * @version V1.0.0
  * @date    01-May-2015
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, WIZnet SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2015 WIZnet Co.,Ltd.</center></h2>
  ******************************************************************************
  */ 

/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
#include "W7500x_uart.h"
#include "W7500x_gpio.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"

extern void main_blinky( void );
/*
 * Initialise the LED ports, and create a timer that periodically toggles an LED
 * just to provide a visual indication that the program is running.
 */


int main()
{
    GPIO_InitTypeDef GPIO_InitDef;
    SystemInit();
    SystemCoreClockUpdate();
    
    GPIO_InitDef.GPIO_Pin = ( GPIO_Pin_1 | GPIO_Pin_2) ; // Set to Pin_1 (LED1) & Pin_2 (LED2)
    GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT; // Set to Mode Output
    GPIO_Init(GPIOA, &GPIO_InitDef);
    PAD_AFConfig(PAD_PA,(GPIO_Pin_1|GPIO_Pin_2), PAD_AF1); // PAD Config - LED used 2nd Function    
  
    main_blinky();
    
    for(;;);
}


void vApplicationMallocFailedHook( void )
{
	/* vApplicationMallocFailedHook() will only be called if
	configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h.  It is a hook
	function that will get called if a call to pvPortMalloc() fails.
	pvPortMalloc() is called internally by the kernel whenever a task, queue,
	timer or semaphore is created.  It is also called by various parts of the
	demo application.  If heap_1.c or heap_2.c are used, then the size of the
	heap available to pvPortMalloc() is defined by configTOTAL_HEAP_SIZE in
	FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used
	to query the size of free heap space that remains (although it does not
	provide information on how the remaining heap might be fragmented). */
	taskDISABLE_INTERRUPTS();
	for( ;; );
}
/*-----------------------------------------------------------*/

void vApplicationIdleHook( void )
{
	/* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set
	to 1 in FreeRTOSConfig.h.  It will be called on each iteration of the idle
	task.  It is essential that code added to this hook function never attempts
	to block in any way (for example, call xQueueReceive() with a block time
	specified, or call vTaskDelay()).  If the application makes use of the
	vTaskDelete() API function (as this demo application does) then it is also
	important that vApplicationIdleHook() is permitted to return to its calling
	function, because it is the responsibility of the idle task to clean up
	memory allocated by the kernel to any task that has since been deleted. */
}
/*-----------------------------------------------------------*/

void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
{
	( void ) pcTaskName;
	( void ) pxTask;

	/* Run time stack overflow checking is performed if
	configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2.  This hook
	function is called if a stack overflow is detected. */
	taskDISABLE_INTERRUPTS();
	for( ;; );
}
/*-----------------------------------------------------------*/

void vApplicationTickHook( void )
{
	/* This function will be called by each tick interrupt if
	configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h.  User code can be
	added here, but the tick hook is called from an interrupt context, so
	code must not attempt to block, and only the interrupt safe FreeRTOS API
	functions can be used (those that end in FromISR()).  The code in this
	tick hook implementation is for demonstration only - it has no real
	purpose.  It just gives a semaphore every 50ms.  The semaphore unblocks a
	task that then toggles an LED.  Additionally, the call to
	vQueueSetAccessQueueSetFromISR() is part of the "standard demo tasks"
	functionality. */

	/* The semaphore and associated task are not created when the simple blinky
	demo is used. */

}
/*-----------------------------------------------------------*/

 

Fixed Error

Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and w7500x_it.o).
Error: L6200E: Symbol SysTick_Handler multiply defined (by port.o and w7500x_it.o).

위 Error는 PendSV_Handler와 SysTick_Handler 함수가 중복되어 있어서 발생한 Error이다. 이를 해결하기 위해서는 W7500x_it.c를 아래와 같이 수정하면 된다.

__weak void PendSV_Handler(void)
{}
__weak void SysTick_Handler(void)
{}

Step 4 : Execute Example

FreeRTOS

0

RTOS

실시간 운영 체제(문화어: 실시간조작체계) 또는 RTOS(←Real Time Operating System) 는 실시간 응용 프로그램을 위해 개발된 운영 체제이다. 운영 체제의 기능 중 CPU 시간 관리 부분에 초점을 맞추어 설계되었다. 실시간 운영 체제는 프로그래머가 프로세스 우선 순위에 더 많은 제어를 할 수 있게 한다. 응용 프로그램의 우선 순위가 시스템 프로그램의 우선 순위를 넘어설 수도 있다. 시스템 코드의 임계 구역을 최소화하였으며, 이를 통하여 응용 프로그램의 처리 요청을 정해진 시간 안에 처리해 줄 수 있다.

실시간 운영 체제의 핵심은 응용 프로그램의 테스크를 처리에 걸리는 시간을 일관되게 유지할 수 있는 정도에 있다. 처리 시간의 변동폭은 지터(jitter)라 부른다. 경성(hard) 실시간 운영 체제와 연성(soft) 실시간 운영 체제로 구분할 수 있으며, 전자가 후자에 비해 지터가 적다.

참고 : https://ko.wikipedia.org/wiki/%EC%8B%A4%EC%8B%9C%EA%B0%84%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C

Ranking of RTOSs

Embedded_OS_List_Android_Linux_RTOS_2013

참고 : http://www.cnx-software.com/2013/05/21/2013-embedded-market-study-software-development-processors/

FreeRTOS

FreeRTOS는 오픈소스 실시간 내장형 운영체제 중 하나로, 단순함, 뛰어난 이식성, 간결함을 목표로 설계되었습니다. 실제로 운영체제 core 소스 코드가 4,000라인을 넘지 않으며, 불가피한 몇몇 코드를 제외한 거의 모든 code가 C 언어로 작성되어 이식성이 뛰어납니다. 현재 8051, AVR, PIC18, H8, MSP430, HCS12, PIC24, ARM Cortex-M3, ARM7, ARM9, AVR32, ColdFire, x86 등의 다양한 8bit, 16 bit, 32 bit 프로세서에 이식되어 있습니다.

FreeRTOS core는 아래와 같은 기능들을 제공합니다.

  • Mutlitasking
    • 선점형, 비선점형 선택 가능
    • Task 수 제한 없음
    • 동적으로 task 생성 가능
    • Priority 수 제한 없음
    • 두개 이상의 task에 같은 priority 할당 가능 (이 경우 라운드 로빈 스케쥴링)
  • Co-routine
  • Message queue
  • Semaphore
  • Memory Management

FreeRTOS는 변형된 GPL license 정책을 사용합니다. GPL license와 주된 차이점은 FreeRTOS와 link되어 실행되는 저작물을 공개할 의무가 없다는 것입니다. 이점은 보안이나 상업적인 목적으로 저작물에 대한 독점적 권리를 확보할 필요가 있을 때 이를 가능하게 합니다.

참고 : http://www.ubinos.org/mediawiki/index.php/FreeRTOS_%EC%86%8C%EA%B0%9C

TCP speed report of W7500

0

W7500

The IOP4IoT W7500 chip is the one-chip solution which integrates an ARM Cortex-M0, 128KB Flash and hardwired TCP/IP core for various embedded application platform, especially internet of things. The TCP/IP core is a market-proven hardwired TCP/IP stack with an integrated Ethernet MAC. The Hardwired TCP/IP stack supports the TCP, UDP, IPv4, ICMP, ARP, IGMP and PPPoE which has been used in various applications for more than 15 years. W7500 suits users who need Internet connectivity best.

WIZwiki-W7500ECO

WIZnet WIZwiki Platform based on WIZnet’s MCU. WIZnet WIZwiki-W7500eco is a WIZwiki platform board based on W7500. The IOP4IoT W7500 chip is one-chip solution which integrates an ARM Cortex-M0, 128KB Flash and hardwired TCP/IP core for various embedded application platform especially internet of things. If you use WIZwiki-W7500eco, you will be able to easily develop a prototype.

wizwiki-w7500eco3dtop

Test Scenario

WIZwiki-W7500ECO run TCP Echo Server which receive data and then send to tcp client. PC run TCP client using my test tool. It can be used for simple tcp test and check tcp speed. To check TCP speed of WIZwiki-W7500, I upload TCP echo server binary to WIZwiki-W7500ECO. I send data which is 1Mbytes to WIZwiki-W7500ECO using my test tool and then test tool will receive data from WIZwiki-W7500ECO for checking TCP communication speed.         20150826_220148

You can download “Test tool” as this URL.

https://github.com/kaizen8501/IPV6_TestTool

 

Test Result

Test Condition 1

There are change points for this test.

  • Change System Clock ( 20MHz, 48MHz )
  • Change RAM buffer ( 2048, 4096, 8192 )

20150826_184249 

Comment about Test Condition 1

WIZwiki-W7500ECO TCP Speed depended upon its system clock.After test, I got result as below.

  • Buffer size of varialbe in the code didn’t make much difference to the speed

 

  • When using 20MHz of WIZwiki-W7500ECO’s system clock, average speed is 4.7Mbps

 

 

  • When using 48MHz of WIZwiki-W7500ECO’s system clock, average speed is 8.5Mbps

 

 

 

Test Condition 2

W7500 has one 16KB TX memory for Socket n TX Buffer Blocks and one 16KB RX memory for Socket n RX buffer Blocks. 16KB TX memory is initially allocated in 2KB size for each Socket TX Buffer Block (2KB X 8 = 16KB). The initial allocated 2KB size of Socket n TX Buffer can be re-allocated by using ‘Socket n TX Buffer Size Register (Sn_TXBUF_SIZE)’. Once all Sn_TXBUF_SIZE registers have been configured, Socket TX Buffer is allocated with the configured size of 16KB TX Memory and is assigned sequentially from Socket 0 to Socket 7. Its physical memory address is automatically determined in 16KB TX memory. Therefore, the total sum of Sn_TXBUF_SIZE should not exceed 16 in case of error in data transmission.   wztoe_memory_map

There is chage point for second test.

  • Change Socket RX/TX Buffer of W7500 ( 2048, 8192, 16,384 )

20150827_083215

Comment about Test Condition 2

  • Socket TX/RX buffer size didn’t make much difference to the speed.
  • When using 2Kbytes of Socket buffer, average speed is 8.116Mbps
  • When using 8Kbytes of Socket buffer, average speed is 9.099Mbps
  • When using 16Bytes of Socket buffer, average speed is 9.142Mbps

 

Remote Temperature/Humidity Checker & LED controller using mbed rpc

0

앞에서 설명한 mbed RPC Library와 Python Interface를 활용한 예제를 만들어 보려고 하다 보니 제목이 거창해 진거 같다. 간단하게 말하자면, 본 예제는 아래와 같은 기능을 수행한다.

  • mbed platform의 3색 LED 제어 ( On/Off )
  • mbed platform의 현재 LED 상태 확인 ( Smartphone의 TTS 기능을 이용하여 Green,Blue,Red 인지 상태 확인 기능 )
  • mbed platform으로 부터 온도, 습도 정보를 확인 ( Smartphone의 TTS 기능을 이용하여 온/습도 정보를 확인 가능)

본 예제에서는 mbed RPC를 이용하여 스마트폰에서 mbed platform을 제어하고 있으며, 스마트폰의 Application은 python 기반의 kivy 라이브러리를 사용하였다. 20150727_132817

mbed HTTP RPC Server

본 장에서는 위에서 설명한 동작을 수행하기 위한 mbed code를 구현하는 방법에 대해 설명한다. 아래 코드는 mbed rpc와 HTTP Server 등을 구동하기 위한 코드이며, 사용된 라이브러리는 아래와 같다. mbed platform에 아래 코드를 컴파일해서 Upload 하면, 외부에서 HTTP Data를 이용하여 mbed platform을 제어 할 수 있다.

  • DHT : 온/습도 센서를 제어 하기 위한 라이브러리
  • HTTPServer : HTTP Server를 위한 라이브러리
  • mbed-rpc : mbed RPC를 위한 라이브러리
  • WIZnetInterface : 별도의 Software stack 없이 Ethernet을 사용하기 위한 라이브러리

 Example Code는 아래 경로에서 다운로드 할 수 있다.

 

#include "mbed.h"
#include "EthernetInterface.h"
#include "HTTPServer.h"
#include "mbed_rpc.h"
#include "DHT.h"
 
RpcDigitalOut led1(D9,"led1");
RpcDigitalOut led2(D10,"led2");
RpcDigitalOut led3(D11,"led3");

//RPCVarialbe<float> RPCTemperature(&GetTemperature, "Temperature");
//RPCVarialbe<float> RPCHumidity(&GetHumidity, "Humidity");
void Get_Temp_and_Humidity(Arguments * input, Reply * output);

RPCFunction Temp_and_Humidity_Finder(&Get_Temp_and_Humidity, "Temp_and_Humidity_Finder");

EthernetInterface eth;  
HTTPServer svr;

DHT sensor(D4, DHT11);

void Get_Temp_and_Humidity(Arguments * input, Reply *output){
    int error = 0;
    float h = 0.0f, c = 0.0f;
    char arg[100];

    error = sensor.readData();
    if (0 == error) {
        c   = sensor.ReadTemperature(CELCIUS);
        h   = sensor.ReadHumidity();
        sprintf(arg,"Temperature in Celcius: %4.2f, Humidity is %4.2f",c, h);
    
        output->putData(arg);
    }
}

int main() {
  //Turn the LEDs off
  uint8_t mac_addr[6] = {0x00, 0x08, 0xDC, 0x32, 0x23, 0x42}; 
  led1.write(0);
  led2.write(0);
  led3.write(0);
  
  RPC::add_rpc_class<RpcDigitalOut>();

  printf("Setting up...\n");
  eth.init(mac_addr);
  int ethErr = eth.connect();
  if(ethErr < 0)
  {
    printf("Error %d in setup.\n", ethErr);
    return -1;
  }
  svr.addHandler<HTTPRpcRequestHandler>("/rpc");
  
  //attach server to port 80
  printf("Listening...\n");
  svr.start(80, &eth);
    
  Timer tm;
  tm.start();
  //Listen indefinitely
  while(true)
  {
    svr.poll();
    if(tm.read()>.5)
    {
      tm.start();
    }
  }
}
  • 7~8 : 3색 LED를 위한 GPIO를 RPC에 등록
  • 15 : 온/습도를 얻어 오기 위한 Custom 함수를 RPC에 등록
  • 22~35 : Custom 함수의 구현 부
    • mbed RPC client로 부터 요청이 들어오면 온/습도를 추출한 후, Temperature in Celcius : … ” 데이터를 RPC client에게 송신 한다.
  • 54 : HTTPRPC Request 등록

 

 Smartphone Simple Application

본 장에서는 Smartphone에서 mbed device를 제어하기 위한 간단한 Application을 만드는 방법을 설명한다. 본 예제는 python으로 구현 하였으며, UI를 위해 kivy 라이브러리를 사용하였다. 본 예제를 구현 및 구동하기 위해서는 아래와 같은 절차를 수행 해야 한다.

Download Qpython

Android 폰에서 python을 구동하기 위해서는 Qpython 이라는 프로그램이 필요 하며, 다운로드 경로는 아래와 같다. QPython Download 20150727_140513

앞에서 설명한 것과 같이 python으로 UI를 만들기 위해서는 kivy 라이브러리가 필요하며, 스마트폰의 TTS(Text to Speach) 기능을 사용하기 위해서는 AndroidHelper 라이브러리가 있어야 한다. 각각의 라이브러리는 아래 그림과 같은 절차를 수행하면 설치 할 수 있다.

20150727_140920     20150727_141000    20150727_141026    20150727_141051

20150727_141110   20150727_141151   20150727_141247

Edit Source Code

스마트폰 앱으로 소스코드를 작성하는 일은 매우 불편하다. 다행히 QPython은 미리 작성한 코드를 FTP로 핸드폰에 저장하는 기능을 지원하고 있다. 나는 이 기능을 이용해서 PC에서 미리 구현해 놓은 프로그램을 스마트폰에 복사 한 후 실행하는 절차로 코드를 구현 및 디버깅 했다. ( 이 또한 매우 불편 하였다. )

Setting for FTP

아래 그림과 같은 절차를 수행하면 Qpython의 FTP 기능을 사용 할 수 있다.

20150727_143112  20150727_143133   20150727_143156

Upload Source Code to Smartphone

아래 그림들은 PC에서 QPython의 FTP Server에 접속해서 미리 작성한 코드를 업로드 하는 방법을 나타낸다.

20150727_143344

FTP 접속에 성공하면 아래와 같이 Qpython의 폴더 트리를 볼 수 있다. 폴더 트리에 나오는 폴더를 하나 선택하여 미리 작성한 코드를 업로드 하면 된다. 20150727_143413

나는 project3/RPC_TTS 폴더를 만들고 kivy.py와 mbedRPC.py를 복사 해서 사용한다. mbedRPC.py는 앞에서 설명한 mbed python RPC library이며, kivy.py는 Application의 UI 및 동작을 구현한 파일이다. 20150727_143443

Source code of kivy.py

#-*-coding:utf8;-*-
#qpy:2
#qpy:kivy

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from mbedRPC import *
import androidhelper

class CommandScreen(GridLayout):
    def __init__(self, **kwargs):
        super(CommandScreen, self).__init__(**kwargs)
        self.cols = 2
        self.row = 2
        self.row_force_default=True
        self.row_default_height=200
        
        self.add_widget(Label(text='Device IP'))
        self.m_text_device_ip = TextInput(multiline=False,text="192.168.18.121")
        self.add_widget(self.m_text_device_ip)
        
        self.m_button_init = Button(text="Init")
        self.m_button_init.bind(on_press=self.mbedInit)
        self.add_widget(self.m_button_init)
        
        self.m_button_GetTemp = Button(text="Get Temp")
        self.m_button_GetTemp.bind(on_press=self.GetTemp)
        self.add_widget(self.m_button_GetTemp)
        
        self.btn_onLed1 = Button(text="Turn On",background_color=(1,0,0,1))
        self.btn_onLed1.bind(on_press=self.TurnOnLed1)
        self.add_widget(self.btn_onLed1)
        
        self.btn_offLed1 = Button(text="Turn Off",background_color=(1,0,0,1))
        self.btn_offLed1.bind(on_press=self.TurnOffLed1)
        self.add_widget(self.btn_offLed1)
        
        self.btn_onLed2 = Button(text="Turn On",background_color=(0,1,0,1))
        self.btn_onLed2.bind(on_press=self.TurnOnLed2)
        self.add_widget(self.btn_onLed2)
        
        self.btn_offLed2 = Button(text="Turn Off",background_color=(0,1,0,1))
        self.btn_offLed2.bind(on_press=self.TurnOffLed2)
        self.add_widget(self.btn_offLed2)

        self.btn_onLed3 = Button(text="Turn On",background_color=(0,0,1,1))
        self.btn_onLed3.bind(on_press=self.TurnOnLed3)
        self.add_widget(self.btn_onLed3)
        
        self.btn_offLed3 = Button(text="Turn Off",background_color=(0,0,1,1))
        self.btn_offLed3.bind(on_press=self.TurnOffLed3)
        self.add_widget(self.btn_offLed3)

        self.btn_getLedStatus = Button(text="LED Status")
        self.btn_getLedStatus.bind(on_press=self.GetLedStatus)
        self.add_widget(self.btn_getLedStatus)
        
        self.btn_getA0Status = Button(text="Get A0 Status")
        self.btn_getA0Status.bind(on_press=self.GetA0Status)
        self.add_widget(self.btn_getA0Status)
        

    def mbedInit(self,event):
        self.droid = androidhelper.Android()
        
        self.mbed = HTTPRPC(self.m_text_device_ip.text)
        self.led1 = DigitalOut(self.mbed,"led1")
        self.led2 = DigitalOut(self.mbed,"led2")
        self.led3 = DigitalOut(self.mbed,"led3")
        self.temp_humidity = RPCFunction(self.mbed,"Temp_and_Humidity_Finder")

        box = BoxLayout(orientation='vertical')
        box.add_widget(Label(text='Init Complete'))
        button_close = Button(text='Close me!')
        box.add_widget(button_close)

        popup = Popup(title='message', content=box,
                      size_hint=(None,None),size=(400,400), auto_dismiss=False)

        button_close.bind(on_press=popup.dismiss)
        popup.open()
        
        
    def GetTemp(self,event):
        resp = self.temp_humidity.run("")
        self.droid.ttsSpeak(resp)

    def TurnOnLed1(self,event):
        self.led1.write(1)
        
    def TurnOffLed1(self,event):
        self.led1.write(0)
        
    def TurnOnLed2(self,event):
        self.led2.write(1)
        
    def TurnOffLed2(self,event):
        self.led2.write(0)

    def TurnOnLed3(self,event):
        self.led3.write(1)
        
    def TurnOffLed3(self,event):
        self.led3.write(0)
         
    def GetLedStatus(self,event):
        if( self.led1.read()[0] == '1' ): self.droid.ttsSpeak("Turn on red.")
        if( self.led2.read()[0] == '1' ): self.droid.ttsSpeak("Turn on green.")
        if( self.led3.read()[0] == '1' ): self.droid.ttsSpeak("Turn on blue.")
        
    def GetA0Status(self,event):
        print ''

class MyApp(App):
    def build(self):
        return CommandScreen()        

if __name__ == '__main__':
    MyApp().run()

 

  • 15 ~ 65 : App Button 및 UI 초기화
  • 68 ~ 86 : mbed RPC library 초기화
  • 89 : mbed platform으로 부터 온/습도 데이터를 수신 후 TTS로 출력
  • 93 ~ 109 : LED1/2/3 On/Off
  • 111 ~ 114 : 현재 mbed platform의 LED 색깔을 확인 후 TTS로 출력

   Result after run

20150727_130822

Demo Video

mbed rpc interfacing with Python

0

Python RPC Library

mbed에서는 mbed RPC client가 python을 이용하여 mbed RPC server의 데이터를 read/write하거나 custom 함수를 call 하기 위해 아래와 같은 python module을 제공한다.

아래 코드는 HTTP를 사용한 python RPC 예제이며, 192.168.0.4인 RPC Server에 접속하여 GPIO와 ADC 값을 read/write 하는 예제 이다.

[code language=”python”]
#!python
python
>>> from mbedrpc import *
>>> mbed = HTTPRPC("192.168.0.4")
>>> x = DigitalOut(mbed,"LED1")#These objects should already exist on mbed
>>> z = DigitalOut(mbed,"LED2")
>>> ain = AnalogIn(mbed, "LED3")
>>> x.write(1)
>>> z.write(0.5)
>>> ain.read()
0.786757474
[/code]

Python RPC library에 대한 자세한 설명은 아래 주소를 참고 하기 바란다.

https://developer.mbed.org/cookbook/Interfacing-with-Python

앞에서 설명한 mbed 코드와 같이 사용하기 위해서는 아래 코드를 참고 하기 바란다.

[code language=”python”]
#!python
python
>>> from mbedrpc import *
>>> mbed = HTTPRPC("192.168.0.4")
>>> x = DigitalOut(mbed,"led1")#These objects should already exist on mbed
>>> x.write(1)
>>> x.write(0)
[/code]

History of mbed RPC Library for adding WIZwiki-W7500

0

Used library in this project

Modified Point

mbed rpc를 사용하기 위한 Interface는 RPC over Serial과 RPC over HTTP로 나눌 수 있다. 본 예제에서는 HTTP를 이용하여 RPC를 사용하며, mbed platform 에는 HTTPServer가 구동되고 있다. mbed rpc를 위한 Interface에 대한 자세한 정보는 Link를 참고 하기 바란다.

WIZnetInterface

HTTPServer Library에서 아래와 같은 코드를 사용하는데, WIZnetInterface에는 init()함수가 없음 –> WIZnetLibrary에 init() 함수 추가

HTTPServer/HTTPServer.cpp ( Line 50~56 )

[code language=”cpp”]
INFO("Initializing network\n");
if (m_pEthernet->init() != 0) {
ERR("Failed to initialize the ethernet interface !");
delete m_pEthernet;
m_pEthernet = NULL;
return false;
}
[/code]

mbed-rpc

공식 mbed-rpc Library에서 WIZwiki-W7500의 Pin들을 사용할 수 있도록 아래와 같이 코드를 추가 하였다.

mbed-rpc/parse_pins.cpp

[code language=”cpp”]
#elif defined(TARGET_WIZwiki_W7500)
if (str[0] == ‘P’ && str[2] == ‘_’) { // Px_n
uint32_t port = str[1] – ‘A’;
uint32_t pin = str[3] – ‘0’; // Px_n
uint32_t pin2 = str[4] – ‘0’; // Px_nn

if (pin2 <= 9) {
pin = pin * 10 + pin2;
}
return port_pin((PortName)port, pin);
#endif
[/code]

WIZwiki-W7500의 Port와 Pin들은 아래와 같은 규칙으로 정의 되어 있으므로, parse_pins.cpp 에서는 PA_0와 같은 String Data를 받아 0x000과 같은 Int Data로 변환하는 동작을 수행한다.

[code language=”cpp”]
// In PinNames.h

// W7500x PORT[5:4] + PIN[3:0])
PA_0 = 0x000,
PA_1 = 0x001,
PA_2 = 0x002,

PB_9 = 0x019,
PB_10 = 0x01A,
PB_11 = 0x01B,

PC_0 = 0x020,
PC_1 = 0x021,
PC_2 = 0x022,

[/code]

[code language=”cpp”]
// In PortNames.h

typedef enum {
PortA = 0,
PortB = 1,
PortC = 2,
PortD = 3,
} PortName;
[/code]

mbed rpc library에 WIZwiki-W7500을 위한 코드를 추가한 후, mbed official repository에 pull request를 보내서 코드 업데이트를 요청 하였다.

20150722_125431

Run Simple Example

[code language=”cpp”]
#include "mbed.h"
#include "EthernetInterface.h"
#include "HTTPServer.h"
#include "mbed_rpc.h"

RpcDigitalOut led1(LED1,"led1");

EthernetInterface eth;
HTTPServer svr;

int main() {
//Turn the LEDs off
uint8_t mac_addr[6] = {0x00, 0x08, 0xDC, 0x00, 0x00, 0x00};
led1.write(1);

RPC::add_rpc_class<RpcDigitalOut>();

printf("Setting up…\n");
eth.init(mac_addr);
int ethErr = eth.connect();
if(ethErr < 0)
{
printf("Error %d in setup.\n", ethErr);
return -1;
}

svr.addHandler<HTTPRpcRequestHandler>("/rpc");

//attach server to port 80
svr.start(80, &eth);

printf("Listening…\n");

Timer tm;
tm.start();
//Listen indefinitely
while(true)
{
svr.poll();
if(tm.read()>.5)
{
tm.start();
}
}
}
[/code]

  • 6 : 제어를 원하는 GPIO의 RPC Name 지정
  • 19~20 : ethernet interface 초기화 및 DHCP 수행
  • 27 : HTTPRpcRequestHandler 등록
  • 30 : HTTP Server Start ( TCP Server로 80포트 리슨)