SPI Sistemas Embebidos II Semestre de Verão de 2012/2013 Segunda atividade prática ecos - Ethernet Device Driver Configurar a biblioteca ecos com o driver SPI. A configuração por omissão não inclui este driver, é necessário acrescentá-lo no ficheiro ecos.db. Ensaiar em ligação loopback entre MOSI e MISO. #include <pkgconf/hal.h> #include <cyg/infra/cyg_type.h> #include <cyg/hal/hal_io.h> #include <cyg/hal/var_io.h> #include <cyg/infra/diag.h> #include <cyg/kernel/kapi.h> #include <cyg/io/spi_lpc2xxx.h> #include "lpc2106.h" #define CS_PIN 9 // base types // low level i/o // common registers static void cs_init(void) { GPIO->IOSET = 1 << CS_PIN; GPIO->IODIR = 1 << CS_PIN; static void cs_control(int select) { if (select) GPIO->IOCLR = 1 << CS_PIN; GPIO->IOSET = 1 << CS_PIN; cyg_spi_lpc2xxx_dev_t echo_device CYG_SPI_DEVICE_ON_BUS(0) = {.spi_device.spi_bus = &cyg_spi_lpc2xxx_bus0.spi_bus,.spi_cpha = 0, // Clock phase (0 or 1).spi_cpol = 0, // Clock polarity (0 or 1).spi_lsbf = 0, // MSBF.spi_baud = 500000, // Clock baud rate.spi_cs = cs_control ; int main(void) { cyg_uint8 tx_buf[] = { 'U', 'w'; cyg_uint8 rx_buf[sizeof(tx_buf)]; int len = 2; cyg_spi_transfer((cyg_spi_device *)&echo_device, 0, len, tx_buf, rx_buf); int result = memcmp(tx_buf, rx_buf); ENC28J60 Ligar o controlador ENC28J60 ao LPC2106. Recomenda-se a ligação do sinal de RESET do controlador a uma saída do GPIO para permitir atuação direta. Com o programa seguinte, que efetua a leitura de alguns registos de estado do controlador, pode-se verificar se as ligações e as funções de acesso a registos estão corretas. #include <pkgconf/hal.h> #include <cyg/infra/cyg_type.h> #include <cyg/hal/hal_io.h> #include <cyg/hal/var_io.h> // base types // low level i/o // common registers 26-04-13 SE2 - segunda atividade prática 1
#include <cyg/infra/diag.h> #include <cyg/kernel/kapi.h> #include <cyg/io/spi_lpc2xxx.h> #include "lpc2106.h" #include "enc28j60.h" #define CS_PIN 9 #define RESET_PIN 14 static void gpio_init(void) { GPIO->IOSET = (1 << CS_PIN) (1 << RESET_PIN); /* desactivar os sinais */ GPIO->IODIR = (1 << CS_PIN) (1 << RESET_PIN); static void cs_enc28j60(int select) { if (select) GPIO->IOCLR = 1 << CS_PIN; GPIO->IOSET = 1 << CS_PIN; cyg_spi_lpc2xxx_dev_t spi_enc28j60_dev CYG_SPI_DEVICE_ON_BUS(0) = {.spi_device.spi_bus = &cyg_spi_lpc2xxx_bus0.spi_bus,.spi_cpha = 0, // Clock phase (0 or 1).spi_cpol = 0, // Clock polarity (0 or 1).spi_lsbf = 0, // MSBF.spi_baud = 1000000, // Clock baud rate.spi_cs = cs_enc28j60 ; #define ENC28J60 (cyg_spi_device *)&spi_enc28j60_dev static cyg_uint8 read_control_register(int reg) { int len = (reg & SPRD_MASK)? 3 : 2; // dummy byte? cyg_uint8 tx_buf[3] = { 0x00 (reg & 0x1f); cyg_uint8 rx_buf[3]; cyg_spi_transfer(enc28j60, true, len, tx_buf, rx_buf); return rx_buf[len - 1]; static void write_control_register(int reg, cyg_uint8 data) { cyg_uint8 tx_buf[] = { 0x40 (reg & 0x1f), data ; cyg_uint8 rx_buf[sizeof(tx_buf)]; cyg_spi_transfer(enc28j60, true, sizeof(tx_buf), tx_buf, rx_buf); static void read_buffer_memory(cyg_uint8 * buffer, size_t size) { cyg_uint8 tx_buf[] = { 0x3a ; cyg_spi_transaction_begin(enc28j60); cyg_spi_transaction_transfer(enc28j60, true, 1, tx_buf, NULL, false); cyg_spi_transaction_transfer(enc28j60, true, size, buffer, buffer, true); cyg_spi_transaction_end(enc28j60); static void write_buffer_memory(cyg_uint8 * data, size_t size) { cyg_uint8 tx_buf[] = { 0x7a ; cyg_spi_transaction_begin(enc28j60); cyg_spi_transaction_transfer(enc28j60, true, 1, tx_buf, NULL, false); cyg_spi_transaction_transfer(enc28j60, true, size, data, NULL, true); cyg_spi_transaction_end(enc28j60); static void bit_field_set(int reg, cyg_uint8 data) { cyg_uint8 tx_buf[] = { 0x80 (reg & 0x1f), data ; cyg_spi_transfer(enc28j60, true, sizeof(tx_buf), tx_buf, NULL); 26-04-13 SE2 - segunda atividade prática 2
static void bit_field_clear(int reg, cyg_uint8 data) { cyg_uint8 tx_buf[] = { 0xa0 (reg & 0x1f), data ; cyg_spi_transfer(enc28j60, true, sizeof(tx_buf), tx_buf, NULL); static void soft_reset(void) { cyg_uint8 tx_buf[] = { 0xff ; cyg_spi_transfer(enc28j60, true, sizeof(tx_buf), tx_buf, NULL); static void bank_select(int address) { if (address >= EIE && address <= ECON1) return; cyg_uint8 aux = read_control_register(econ1); aux &= ~ECON1_BSEL; aux = ((address >> 5) & ECON1_BSEL); write_control_register(econ1, aux); /*------------------------------------------------------------------------------ */ static cyg_uint8 reg_read8(int addr) { return read_control_register(addr); static cyg_uint16 reg_read16(int addr) { cyg_uint16 aux = read_control_register(addr); return aux (read_control_register(addr + 1) << 8); static void reg_write8(int addr, cyg_uint8 data) { write_control_register(addr, data); static void reg_write16(int addr, cyg_uint16 data) { write_control_register(addr, data); write_control_register(addr + 1, data >> 8); static void reg_bit_field_set(int addr, cyg_uint8 data) { bit_field_set(addr, data); static void reg_bit_field_clear(int addr, cyg_uint8 data) { bit_field_clear(addr, data); static void write_physical_register(int addr, cyg_uint16 data) { reg_write8(miregadr, addr); reg_write16(miwrl, data); static cyg_uint16 read_physical_register(int addr) { cyg_uint8 aux; reg_write8(miregadr, addr); reg_bit_field_set(micmd, MICMD_MIIRD); do { aux = reg_read8(mistat); while ((aux & MISTAT_BUSY)!= 0); reg_bit_field_clear(micmd, MICMD_MIIRD); return reg_read16(mirdl); 26-04-13 SE2 - segunda atividade prática 3
int main(void) { cyg_uint32 aux; gpio_init(); GPIO->IOCLR = 1 << RESET_PIN; /* activar o sinal reset */ GPIO->IOSET = 1 << RESET_PIN; /* desactivar o sinal reset */ /* ENC28J60 reset */ soft_reset(); aux = reg_read8(econ2); if (aux!= 0x80) { diag_printf("didn't find ENC28J60\n"); return -1; cyg_uint32 delay; for (delay = 0; delay < 1000; ++delay) { aux = reg_read8(erevid); if (aux == 5) break; diag_printf("(%d)enc28j60 Revision: %d\n\r", delay, aux); aux = read_physical_register(phid1); cyg_uint32 aux2 = read_physical_register(phid2); diag_printf("physical Identifier: 0x%04x\n\r", (aux << 2) ((aux2 & 0xfc00) << 18)); for (delay = 0; delay < 1000; ++delay) { aux = read_physical_register(phstat2); if (aux & PHSTAT2_LSTAT) break; if (aux & PHSTAT2_DPXSTAT) diag_printf("full duplex operation, "); diag_printf("half duplex operation, "); if (aux & PHSTAT2_LSTAT) diag_printf("link is up, "); diag_printf("link is down, "); if (aux & PHSTAT2_PLRITY) diag_printf("tpin+/tpin- polarity is reversed\n\r"); diag_printf("tpin+/tpin- polarity is correct\n\r"); Device driver por polling Tomando como ponto de partida o código anterior e os slides sobre Ethernet, desenvolver um device driver para o controlador ENC28J60. A interface de utilização deve ser a seguinte: void ethernet_init(cyg_uint8 * mac); int ethernet_send(cyg_uint8 * packet, size_t packet_size, cyg_tick_count_t timeout); size_t ethernet_recv(cyg_uint8 * buffer, size_t buffer_size, cyg_tick_count_t timeout); A função ethernet_init aloca e inicia os recursos necessários. O parâmetro mac é um ponteiro 26-04-13 SE2 - segunda atividade prática 4
para uma sequência de 6 bytes que representa o endereço MAC. A função ethernet_send deposita no controlador o pacote definido pelos parâmetros packet e packet_size, dá ordem de transmissão e aguarda o final da transmissão. Se, ao fim do tempo estipulado no parâmetro timeout a confirmação da transmissão não suceder, o função retorna, assinalando esse facto. A função ethernet_recv recolhe da memória interna do controlador, um pacote recebido por este. Se não existirem pacotes recebidos na altura da chamada, a função vai aguardar pela receção de um, durante o tempo definido por timeout. Ensaio da receção de pacotes Utilize o programa abaixo para testar a receção de pacotes Ethernet. Este programa, depois de iniciar o controlador Ethernet invoca sucessivamente a função ethernet_recv. Em redes locais como as do ISEL há sempre muitos pacotes em broadcast, se ligar a uma destas redes não demorará muito até o controlador receber um pacote. Se ligar ao computador de desenvolvimento execute o comando ping para um endereço da mesma rede local. Irá receber logo um pacote ARP request que é transmitido em broadcast. #include <cyg/infra/cyg_type.h> #include <cyg/infra/diag.h> #include <cyg/kernel/kapi.h> #include <string.h> // base types void ethernet_init(cyg_uint8 * mac); void ethernet_send(cyg_uint8 * packet, size_t packet_size, cyg_tick_count_t timeout); size_t ethernet_recv(cyg_uint8 * buffer, size_t buffer_size, cyg_tick_count_t timeout); static cyg_uint8 ether_addr[] = {0x02, 0x65, 0x7A, 0x65, 0x71, 00; static cyg_uint8 packet[2000]; int main(void) { diag_printf("ecos, enc28j60, test\n\r"); ethernet_init(ether_addr); while (1) { size_t size; while ((size = ethernet_recv(packet, sizeof(packet), 0))) { diag_printf("packet received\n\r"); diag_dump_buf(packet, size); Ensaio da transmissão de pacotes Para testar o envio de pacotes, através da função ethernet_send, acrescente o seguinte troço de código no ciclo while (1) do programa anterior. Este código envia um pacote Ethernet em broadcast, de meio em meio segundo. Para verificar o correto envio pode usar um programa analisador de protocolos como o WireShark. { cyg_thread_delay(100); diag_printf("tx\n\r"); cyg_uint8 frame[78]; memcpy(&frame[0], "\xff\xff\xff\xff\xff\xff", 6); memcpy(&frame[6], ether_addr, 6); frame[12] = (sizeof(frame) - 14) >> 8; 26-04-13 SE2 - segunda atividade prática 5
frame[13] = (sizeof(frame) - 14); memset(&frame[14], '2', sizeof(frame) - 14); ethernet_send(frame, sizeof(frame), 0); Driver por interrupção Neste ponto pretende-se substituir a natureza das esperas espera por fim de transmissão e receção de pacote de ativas para passivas e introduzir capacidade de armazenamento de pacotes em trânsito, quer em transmissão quer em receção. A interface de utilizador do device driver deve manter-se. Na transmissão, a função ethernet_send deposita o pacote a transmitir no buffer de transmissão. Se este buffer estiver cheio, deve aguardar, em espera passiva, o tempo definido por timeout. A transferência dos pacotes, do buffer de transmissão para o controlador, é efetuada na sequência de uma interrupção por fim de transmissão. Na recepção, a função ethernet_recv retira os pacotes do buffer de receção. Se este buffer estiver vazio, deve aguardar, em espera passiva, o tempo definido por timeout. A transferência dos pacotes, do controlador para o buffer de receção, é efetuada na sequência de uma interrupção por receção de pacote. 26-04-13 SE2 - segunda atividade prática 6