위즈네트 아카데미

TUTORIAL

튜토리얼

Home  > 튜토리얼

W5500 W5500-EVB활용 Chat Client 구현하기 - 5

WIZnet Academy 2015.04.09 13:04 조회 수 : 21

[W5500-EVB 활용 Chat Client 구현하기 1~4]를 통해 Chat Client 구현하는 방법에 대해서 설명을 드렸습니다. W5500의 드라이버 소스를 이용하는 것에서는 큰 문제는 없었지만, UART 데이터를 받아 처리하는 부분에서 데이터가 유실 되는 문제가 있었습니다. 관련 문제를 해결하기 위해 구글링을 통해 링버퍼를 소스를 찾아 적용, UART 데이터 처리하는 분이 해결하게 되었으며, 이번 포스팅에는 그와 관련된 설명 입니다.

아래 소스는 링버퍼 소스입니다. ring_buffer.h와 ring_buffer.c에 링버퍼를 사용하기 위한 소스가 있습니다. 여기서는 링버퍼의 간단한 함수 설명만 진행하겠습니다.

rb_put 함수 : 링버퍼에 데이터를 저장하는 함수

rb_get 함수 : 링퍼버의 데이터를 읽어오는 함수

finder 함수 : 링버퍼의 데이터에서 특정 값을 찾아내는 함수
/******************************** ring_buffer.h ********************************/
#ifndef __RING_BUFFER_H__
#define __RING_BUFFER_H__
#include <stdint.h>
//UART 버퍼사이즈를 설정한다.
#define SERIAL_BUF_SIZE 2048

//링버퍼 구조체 선언
typedef struct { // creation ring buffer
int tail; // read data pointer
int head; // write data pointer
char data[SERIAL_BUF_SIZE]; // receive buffer
} t_rb;
uint16_t rb_put( t_rb *rb , uint8_t d );
uint8_t rb_get( t_rb *rb);
uint8_t finder ( t_rb *rb, uint8_t ch );
#endif
</stdint.h>

 
/******************************** ring_buffer.c ********************************/
#include "ring_buffer.h"
//rb_put함수는 링버퍼에 데이터를 저장하는 함수이다.
uint16_t rb_put( t_rb *rb , uint8_t d )
{
uint16_t nhead = (rb->head+1) % (SERIAL_BUF_SIZE); //
if(rb->tail == nhead){
return 0;
}
rb->data[rb->head] = d;
rb->head = nhead;
return 1;
}
//rb_get함수는 링퍼버의 데이터를 읽어오는 함수이다.
uint8_t rb_get( t_rb *rb)//, uint16_t *err) // pop -> send Ethernet
{
uint8_t d;
uint16_t ntail = (rb->tail+1) % (SERIAL_BUF_SIZE);
if(rb->head == rb->tail){
return 0; // buffer non-write -> empty
}
d = rb->data[rb->tail];
rb->tail = ntail;
return d; // buffer read
}
//finder함수는 링버퍼의 데이터에서 특정 값을 찾아내는 함수이다.
uint8_t finder ( t_rb *rb, uint8_t ch ) // new line finder
{
uint16_t i;
uint16_t cnt;
if(rb->head == rb->tail){
return 0;
}
else if(rb->head > rb->tail){
for(i=rb->tail; i < rb->head; i++){
if(rb->data[i] == ch){
return (i+1);
}
}
}
else{
for(i=rb->tail; i < SERIAL_BUF_SIZE; i++){
if(rb->data[i] == ch){
return (i+1);
}
}
cnt = i;
for(i=0; i<rb->head; i++){
if(rb->data[i] == ch){
return (cnt+i+1);
}
}
}
return 0;
}
</rb->

메인 소스는 W5500-EVB.c 로 구체적인 설명은 소스에 주석으로 대체합니다.
/******************************** W5500-EVB.c ********************************/
#if defined (__USE_LPCOPEN)
#if defined(NO_BOARD_LIB)
#include "chip.h"
#else
#include "board.h"
#endif
#endif

#include "socket.h"
#include "spi_handler.h"
#include "w5500_init.h"
#include "ring_buffer.h"

// TODO: insert other include files here

// TODO: insert other definitions and declarations here
#define SOCK_TCPS0 0
#define DATA_BUF_SIZE 2048
uint8_t gDATABUF[DATA_BUF_SIZE];
uint8_t SERIAL_BUF[SERIAL_BUF_SIZE];
#define CR 0x0D
#define LF 0x0A

//링버퍼 초기화
t_rb ringbuffer = {0, 0, }; // tail = 0, head = 0
uint8_t AutoSend_flag=0; // global variable
uint8_t chat_close=1;

// Ethernet =>> Serial
uint8_t EtherToSerial(uint8_t* buf,uint16_t size)
{
uint8_t i;
for(i=0 ; i < size ; i++){
putchar(buf[i]); //uart 출력
}
putchar('r');
putchar('n');
return 0 ;
}
// Serial => Ethernet
uint8_t SerialToEther(uint8_t sn)
{
//finder함수를 이용해 UART로 들어온 데이터 중에 LF(linefeed) 찾아 LF가 저장된 인덱스를 len 변수에 저장한다.
//len 변수가 UART로 받은 데이터의 사이즈 이다.
int len = finder(&ringbuffer, LF);
//int err;
int i;
//len이 0보다 크다면
if(len > 0){
//링버퍼에 저장된 데이터를 len 만큼 읽어 SERIAL_BUF에 저장한다.
for(i=0 ; i < size ; i++){
SERIAL_BUF[i] = rb_get(&ringbuffer);
}
//send 함수를 이용해 SERIAL_BUF의 데이터를 TCP로 전송한다.
send(sn, SERIAL_BUF, len);
return len; // data send to len
}
//len이 0보다 크지 않다면
else{
if(AutoSend_flag == 1){
for(i=0 ; i < SERIAL_BUF_SIZE ; i++){
SERIAL_BUF[i] = rb_get(&ringbuffer);
}
send(sn, SERIAL_BUF, SERIAL_BUF_SIZE);
AutoSend_flag = 0;
return SERIAL_BUF_SIZE; // data send to max size
}
else{
//UART로 '@'입력되면 disconnect 를 진행한다.
if(chat_close == 1){
int len2 = finder(&ringbuffer, '@'); // find '@'
if(len2 > 0){
for(i=0 ; i < len2 ; i++){
SERIAL_BUF[i] = rb_get(&ringbuffer); // send to buffer to result
}
send(sn, SERIAL_BUF, len2); // send data
disconnect(sn); // disconnect
return len2;
}
}
}
}
return 0 ;
}

//UART interrupt function
void UART_IRQHandler(void)
{

if((Chip_UART_ReadLineStatus(LPC_USART) & UART_LSR_RDR) == 1){
//UART로 받은 1byte를 rb_put함수로 전달한다.
if(rb_put(&ringbuffer, Chip_UART_ReadByte(LPC_USART)) == 1){
//printf("Return1 data output%s");
}
else{
AutoSend_flag = 1;
//printf("Return0 data no output%s");
}
}
}

int main(void) {
uint8_t ip[4]={192,168,0,227}; //디바이스의 IP 설정
uint16_t port=23; //포트 설정
uint8_t sn=SOCK_TCPS0; //0번 소켓 설정
uint16_t size=0; //수신 버퍼 사이즈 초기화

#if defined (__USE_LPCOPEN)
#if !defined(NO_BOARD_LIB)
// Read clock settings and update SystemCoreClock variable
SystemCoreClockUpdate();
// Set up and initialize all required blocks and
// functions related to the board hardware
Board_Init();
// Set the LED to the state of "On"
Board_LED_Set(0, true);
#endif
#endif
SPI_Init(); //SPI 초기화
W5500_Init(); //W5500 초기화
Net_Conf();
// Enable UART interrupt
Chip_UART_IntEnable(LPC_USART, (UART_IER_RBRINT | UART_IER_RLSINT));
// Start UART interrupt
NVIC_EnableIRQ(UART0_IRQn);
while(1) {
//W5500의 Sn_SR 레지스터를 읽어서 W5500의 상태를 확인한다. W5500의 상태에 따라 동작 형태가 결정된다.
switch(getSn_SR(sn)) {
//W5500dl close 모드 라면 소켓을 생성한다.
case SOCK_CLOSED:
/* Socket Closed State */
//소켓을 생성한다. 소켓함수 전달 인자는 ("소켓 번호, 통시 모드, 포트 번호, 플래그)" 이다.
if(socket(sn, Sn_MR_TCP, port, 0x00)==sn){
printf("%d:Socket Openedrn",sn);
}
else{
printf("%d:Socket Errorrn",sn);
while(1);
}
break;
//소켓생성이 완료 되면, W5500 init 모드가 되며, init 모드 일 때 W5500이 server 모드로 동작할지 client 모드로 동작할지 정하게 된다.
case SOCK_INIT:
/* TCP Socket Creatation */
printf("%d:Connecting, ip[%d.%d.%d.%d] port [%d]rn", sn, ip[0], ip[1], ip[2], ip[3], port);
//connect 함수를 사용해서 TCP서버로 접속을 시도 한다. connect 함수의 전달 인자는 "(서버의 소켓번호, 서버의 IP, 서버의 포트번호)" 이다.
//connect 함수를 사용하게 되면 W5500은 client 모드가 되고, listen 함수를 사용하게 되면 W5500은 server 모드가 된다.
if(connect(sn,ip,port)==SOCK_OK);
else {
printf("%d:Connect Errorrn",sn);
while(1);
}
break;
//listen 모드는 W5500이 server로 동작할때 이고, client의 접속을 기다리고 있을 상태이다.
case SOCK_LISTEN:
/* TCP Server Mode */

break;
//TCP 연결이 완료 됬을 때, established 모드가 된다. established 일때 server와 client사이에 데이터 전송이 가능하다.
case SOCK_ESTABLISHED:
/* TCP ESTABLISHED */
//W5500의 Sn_IR 레지스터를 읽어 connect 인터럽트를 확인한다. established 모드로 변경됬을 때 한번 인터럽트가 발생한다.
if(getSn_IR(sn) & Sn_IR_CON) {
printf("%d:Connectedrn",sn);
setSn_IR(sn,Sn_IR_CON); //Sn_IR 레지스터의 connect 인터럽트를 초기화 한다.
Board_LED_Set(0, false);
Board_LED_Set(1, true);
}
//Sn_RX_RSR 레지스터를 읽어 W5500의 RX 버퍼에 저장된 데이터 사이즈를 읽어 size 변수(수신할 사이즈)에 저장한다.
if((size = getSn_RX_RSR(sn)) > 0) {
if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE; //수신할 사이즈가 DATA_BUF_SIZE 보다 크다면, 수신할 사이즈를 DATA_BUF_SIZE로 맞쳐 준다.
recv(sn,gDATABUF,size); //recv 함수를 이용해 TCP 데이터를 수신한다.
EtherToSerial(gDATABUF, size); //수신한 TCP 데이터를 UART로 전송한다.
}
SerialToEther(sn); //수시한 UART 데이터를 TCP로 전송한다.
break;

case SOCK_CLOSE_WAIT:
/* Disconnect request */

break;
}
}
return 0 ;
}

위의 소스로 정상 동작하는 모습입니다.

< TCP Server ==>> W5500-EVB ==>> PC Serial Terminal  >

chat_Client 5-1

< PC Serial Terminal ==>> W5500-EVB ==>> TCP Server >

주의할 점은 'LF(Linefeed)'로 데이터의 마지막을 체크하기 때문에, 전송하는 데이터의 마지막에 LF를 삽입합니다.

chat_Client 5-2

ring_buffer.c

ring_buffer.h

W5500-EVB.c

 

W5500-EVB 활용 Chat Client 구현하기 – 1

W5500-EVB 활용 Chat Client 구현하기 – 2

W5500-EVB 활용 Chat Client 구현하기 – 3

W5500-EVB 활용 Chat Client 구현하기 – 4

 

출처 : OSHW Alchemist