Na primeira parte dessa série, eu dei uma breve explicação sobre o CC3000 e destaquei algumas de suas principais características. Nesta segunda parte, vou dar um pequeno tutorial introdutório de como fazer um projeto bem simples utilizando o CC3000.
Ferramentas Necessárias
Apesar de existirem diversas Launch Pads da Texas Instruments, eu optei por utilizar a tradicional LaunchPad da Value Line do MSP430. Além de ser a LaunchPad menos custosa, serve para provar o conceito que é possível implementar uma comunicação Wi-Fi com placas com pouquíssimos recursos e microcontroladores de baixo custo.
Como hardware, será necessário para implementar este projeto:
- Uma LaunchPad com um MSP430G2553;
- Um Boosterpack do CC3000;
- Um roteador Wi-Fi;
- Um computador com conexão Wi-FI.
Como software será necessário:
- Code Composer Studio 5 (para compilação do projeto);
- Hercules SETUP utility (ou qualquer outro programa que possa receber pacotes UDP);
- Java Smart Config desktop client (para o procedimento do SmartConfig);
- Último patch do CC3000.
ATENÇÃO: Abaixo você pode fazer download de um .zip contendo o projeto do Code Compoder 5, o Hercules SETUP utility, o Java Smart Config desktop client e o patch 1.11.1 do CC3000.
Descrição do Projeto
Conceitualmente, o projeto que vamos implementar é basicamente um termômetro Wi-Fi, com apenas um botão de interface, e nada mais. Basicamente, o termômetro vai se conectar à uma rede Wi-Fi e começar a fazer broadcast do valor da temperatura medido através de um Socket UDP em uma porta específica (no caso foi escolhida a porta 4444). O computador que estiver interessado na informação do sensor deve monitorar pacotes UDP na porta em questão. De forma a poder passar as informações para o sensor de que rede ele deve se conectar, deve-se segurar o botão por pelo menos 5 segundos para o mesmo começar o procedimento do SmartConfig.
Tendo definido o projeto conceitualmente, podemos, então, organizar como é lógica interna que rege o termômetro. Ela pode ser representada por uma máquina de estados simplificada, como abaixo.
Após alimentado, o termômetro realiza todas as rotinas de inicialização internas e tenta se conectar à alguma rede pré-configurada. Caso nenhuma rede ainda tenha sido configurada, o mesmo vai ficar tentando se conectar, porém não vai conseguir. Depois de conseguir se conectar à rede, ele abre o Socket UDP com o IP xxx.xxx.xxx.255, que normalmente é utilizado para broadcast. Com o Socket aberto, ele começa a enviar o valor da temperatura amostrado a cada 5 segundos. Depois da inicialização, o procedimento do SmartConfig pode ser utilizado em qualquer um dos estados. Depois de realizado o SmartConfig o termômetro deve tentar se conectar na rede configurada.
Descrição do Firmware
Para não lotar o projeto de arquivos de código fonte eu já estou distribuindo o projeto com as bibliotecas CC3000HostDriver e CC3000 Spi pré-compiladas. Os códigos das bibliotecas são distribuídos pela Texas. Se você tiver interesse em portar o código para outra plataforma, vai ter modificar apenas o CC3000 Spi, porém obviamente recompilar todos os códigos.
O código está razoavelmente comentado, porém irei dar uma breve descrição sobre a estrutura do mesmo. Eu tenho o costume de separar o acesso específico de hardware em arquivos separados, no caso identificados por HAL. Isto simplifica o processo de portabilidade para outras plataformas. Não vou entrar em detalhes sobre a configuração do hardware do MSP430G2553. Os três periféricos que merecerem algum destaque no caso são a SPI, o ADC e o Timer A1. O SPI foi inicializado com as rotinas da biblioteca CC3000. O Timer A1 foi configurado para gerar uma interrupção a cada ~500ms e o ADC foi configurado para fazer a leitura do sensor de temperatura interno do MSP430.
Na função main (corpo do programa) incialmente é realizada a inicialização do Watchdog (muito importante no caso do MSP430), é assinalado qual será o callback da interrupção do Timer A, configura todo o hardware do MSP430, roda a função de inicialização do servidor e finalmente habilita as interrupções. No loop principal do programa é processada a função UServer_asyncProcess, que basicamente cuida dos processos assíncronos (ao timer) da comunicação do servidor.
void main (void)
{
/*
* Inicializa o WatchDog Timer
*/
HAL_wdInit();
/*
* Assinala qual será a função de callback do ISR to Timer
*/
timer0.cb = Timer0_cb;
/*
* Configura o Hardware
*/
HAL_hardwareConfig();
/*
* Inicializar o Servidor
*/
UServer_init();
/*
* Habilita Interrupções Globais
*/
HAL_enableInterrupts();
for(;;)
{
/*
* Processa a rotina assícrona do servidor
*/
UServer_asyncProcess();
}
}
Dentro do callback do ISR do timer, temos basicamente um monitor do botão da placa, que gera um request para o processo do SmartConfig se o mesmo for pressionado por mais de 5 segundos. Fora isso temos a rotina UServer_syncProcess, que cuida dos processos síncronos (ao timer) do CC3000.
static uint8_t Timer0_cb(void)
{
/*
* Aqui é verificado se o botão está apertado.
* Se estiver, é inicializada uma contagem decrescente.
* Se o botão for segurado por pelo menos 5 segundos,
* é requisitado ao servidor realizar o SmartConfig.
* A rotina só começa o SmartConfig quando o botão é solto.
*/
static uint8_t swPressedCntr = 10;
if(sw.read())
{
if(!swPressedCntr)
{
UServer_requestSmartConfig();
}
swPressedCntr = 10;
}
else
{
if(swPressedCntr)
swPressedCntr--;
}
/*
* Processa a rotinas sícrona do servidor a cada 500ms
*/
UServer_syncProcess();
return(0);
}
Com a função main e o callback to timer, temos basicamente o esqueleto de funcionamento do programa. Agora entramos nas rotinas específicas ao servidor, que lidam com o CC3000. Dentro da rotina de inicialização do servidor acontece a configuração incial do módulo CC3000 e do CC3000HostDriver. Primeiro sempre temos que chamar a função wlan_init, que passa para o CC3000HostDriver qual será a função de callback de eventos assíncronos do CC3000, e algumas rotinas de acesso ao hardware do seu microcontrolador. O nome das rotinas é bem autoexplicativo, porém as funcionalidades que precisam ser passadas são para ler o pino de interrupção do CC3000, habilitar e desabilitar a interrupção referente a este mesmo pino e uma função para controlar o pino que habilita e desabilita o CC3000.É importante ressaltar que, apesar de essas rotinas estarem implementadas no HAL, eu não nomeei as mesmas corretamente de forma a manter compatibilidade com os exemplos fornecidos pela Texas Instruments (que utilizam os mesmos nomes para tais funções).
Depois de configurado o CC3000HostDriver, temos que chamar a rotina wlan_start, que propriamente implementa toda a pré-inicialização do CC3000. Posteriormente temos que chamar a função wlan_set_event_mask, que configura quais serão os eventos do CC3000 que serão “mascarados’ ou seja, ou seja, não irão gerar eventos para a nossa aplicação. Para finalizar é feita a configuração da rede com a função netapp_dhcp. Como vamos utilizar IP dinâmico, temos que passar o IP, Default Gateway, endereço DNS como zero. O único que é especificado no caso é a máscara de rede configurada como 255.255.255.0 (filtrar pelos bytes 3 bytes mais significativos).
void UServer_init(void)
{
/*
* Inicializa as flags de controle de estado
*/
g_CC3000DHCP = 0;
g_CC3000Connected = 0;
g_Socket = 0;
g_SmartConfigFinished=0;
/*
* Inicializa o Driver do CC3000
*/
wlan_init( UServer_cc3000UsynchCallback,
0,
0,
0,
ReadWlanInterruptPin,
WlanInterruptEnable,
WlanInterruptDisable,
WriteWlanPin);
/*
* Inicializa o CC3000
*/
wlan_start(0);
/*
* Configura quais eventos não serão necessários
*/
wlan_set_event_mask(HCI_EVNT_WLAN_KEEPALIVE|HCI_EVNT_WLAN_UNSOL_INIT|HCI_EVNT_WLAN_ASYNC_PING_REPORT);
/*
* Configura IP, Subnet Mask e Gateway
* Para IP Dinâmico devemos configurar Gateway e IP como 0.0.0.0
*/
netapp_dhcp((unsigned long *)g_IP_Addr, (unsigned long *)g_SubnetMask, (unsigned long *)g_IP_DefaultGWAddr, (unsigned long *)g_DNS);
}
A biblioteca CC3000HostDriver precisa que a aplicação do usuário forneça um callback de eventos do CC3000, no caso implementado na função UServer_cc3000USynchCallback. Quando um evento (que não mascaramos durante a inicialização) ocorrer, esta função vai ser chamada com três argumentos: o tipo do evento, um ponteiro para os dados relacionados ao evento e o comprimento destes dados. No caso implementamos um switch/case básico para lidar com os diferentes tipos de eventos.
Quando o evento HCI_EVNT_WLAN_SIMPLE_CONFIG_DONE é gerado, quer dizer que o procedimento do SmartConfig foi finalizado, logo mudamos o valor da flag g_SmartConfigFinished para 1.
Quando o evento HCI_EVNT_USOL_CONNECT é gerado, quer dizer que o CC3000 se conectou à alguma rede. Logo, neste caso modificamos a flag g_CC3000Connected para 1.
Quando o evento HCI_EVNT_USOL_DISCONNECT é gerado, quer dizer que o CC3000 se desconectou da conexão com a qual ele estava conectado. Este evento pode ser gerado tanto por uma desconexão voluntária, tanto por uma desconexão involuntária (e.g. perda de sinal). Neste caso modificamos as flag g_CC3000Connected e g_CC3000DHCP para 0.
E para finalizar, temos o evento HCI_EVNT_USOL_DHCP, que é gerado quando o IP dinãmico é assinalado ao CC3000 com sucesso. Neste caso neste caso modificamos a flag g_CC3000DHCP para 1 e armazenamos o IP em questão na variável g_IP_Addr.
static void UServer_cc3000UsynchCallback(long lEventType, char * data, unsigned char length)
{
switch(lEventType)
{
/*
* Evento gerado quando o SmartConfig é finalizado
*/
case HCI_EVNT_WLAN_ASYNC_SIMPLE_CONFIG_DONE:
g_SmartConfigFinished = 1;
break;
/*
* Evento gerado quando o CC3000 consegue se conectar à uma WAN
*/
case HCI_EVNT_WLAN_UNSOL_CONNECT:
g_CC3000Connected = 1;
break;
/*
* Evento gerado quando o CC3000 é desconectado de uma WAN
*/
case HCI_EVNT_WLAN_UNSOL_DISCONNECT:
g_CC3000Connected = 0;
g_CC3000DHCP = 0;
break;
/*
* Evento gerado quando um IP dinâmico é gerado com sucesso.
*/
case HCI_EVNT_WLAN_UNSOL_DHCP:
/*
* Seta a flag informando que o DHCP foi finalizado
*/
g_CC3000DHCP = 1;
/*
* Salva o número do IP
*/
g_IP_Addr[3] = data[0];
g_IP_Addr[2] = data[1];
g_IP_Addr[1] = data[2];
g_IP_Addr[0] = data[3];
break;
default:
break;
}
}
A função UServer_syncProcess implementa as chamadas síncronas ao timer do servidor. No caso ela implementa um contador para gerar um pedido de envio de temperatura.
void UServer_syncProcess(void)
{
static uint8_t sendTemperatureCntr = TEMPERATURE_BROADCAST_PERIOD;
sendTemperatureCntr--;
if(!sendTemperatureCntr)
{
sendTemperatureCntr = TEMPERATURE_BROADCAST_PERIOD;
UServer_requestSendTemperature();
}
}
A função Userver_startSmartConfig é utilizada para inicializar o procedimento do SmartConfig. Nela basicamente passamos qual será o prefixo utilizado nas comunicações do SmartConfig (sempre TTT) e chamamos a função que inicializar o processo propriamente dito.
static void UServer_startSmartConfig(void)
{
/*
* Informa o prefixo utilizado pelo SmartConfig
*/
wlan_smart_config_set_prefix((char*)g_CC3000_prefix);
/*
* Inicializa o SmartConfig
*/
wlan_smart_config_start(0);
}
Para finalizar, a função UServer_asyncProcess implementa a lógica básica do nosso servidor a ser processada ciclicamente, incluindo a máquina de estados projetada. Basicamente inicialmente executamos a função hci_unsolicited_event_handler para eventos do CC3000 pendentes. Depois verificamos se há alguma solicitação de efetuar o procedimento do SmartConfig feita pelo usuário. Se sim, verifica se o procedimento já está sendo executado e se estiver ignora a requisição. Caso não esteja sendo executado, configura a politica de conexão para não se auto-conectar por hora, deleta as redes armazenadas para garantir que o CC3000 sempre ira se conectar na rede desejada (no caso esse passo é dependente da aplicação) e muda o estado do sistema para pre-inicialização do SmartConfig. Depois do processamento das requisições do SmartConfig, temos a máquina de estados do sistema.
No estado USERVER_CONNECTING o servidor está tentando se conectar a alguma rede, logo as variáveis g_CC3000DHCP e g_CC3000Connected são monitoradas para avaliar quando isto acontecer. No caso de uma conexão bem sucedida, informamos o dispositivo que está realizando o SmartConfig que houve sucesso na conexão através da função mDNSAdvertiser. A função recebe como argumento algum string que queira ser informado, e no caso é o nome do nosso dispositivo “Termometro Wi-Fi”. Em seguida, seguimos nossa máquina de estados descrita anteriormente e mudamos para o estado USERVER_OPEN_SOCKET.
No estado USERVER_PRE_SMART_CONFIG o servidor espera o CC3000 se desconectar de alguma possível rede que ele estivesse conectado. Quando isto acontece, a função UServer_startSmartConfig é chamada e a máquina de estados muda para o estado USERVER_SMART_CONFIG.
No estado USERVER_SMART_CONFIG o servidor monitora a variável g_SmartConfigFinished para avaliar quando o procedimento do SmartConfig finalizou. Quando isto acontece, a função wlan_ioctl_set_connection_policy é chamada configurando o CC3000 para utilizar uma rede armazenada e se conectar na útima rede previamente conectada, se disponível. Depois desta configuração o CC3000 é reinicializado, chamando a função wlan_stop, fazendo um delay de alguns microsegundos e reiniciando o o CC3000 chamando a função wlan_start. No caso foi utilizado um delay gastando ciclos do processador. Esta prática não é recomendada pois basicamente bloqueia o processamento do seu sistema. Além disso foi utilizado um valor muito mair de tempo do que o necessário. Isto foi feito para simplificar a visualização do led na placa piscando com o CC3000 é resetado. Depois disso, novamente configuramos a máscara de eventos que não queremos ser informados com a função wlan_set_event_mask. Para finalizar o estado do servidor é modificado para USERVER_CONNECTING, esperando o CC3000 se conectar na rede configurada.
No estado USERVER_OPEN_SOCKET o servidor abre um Socket UDP chamando a função socket, e armazena um identificador do mesmo na variável g_Socket. Caso a função socket retorne um valor negativo, quer dizer que não houve sucesso na abertura do Socket por um erro de conexão. Caso o Socket tenha sido aberto com sucesso, o servidor muda para o estado USERVER_CONNECTED. Por redundância, é verificado se o servidor continua conectado, e caso negativo o mesmo volta para o estado USERVER_CONNECTING.
No estado USERVER_CONNECTED o servidor fica ciclicamente enviando um string com o valor da temperatura interna do MSP430 lida. Para tal é utilizada um estrutura de dados do tipo sockaddr, que armazena no campo sa_data a porta e o IP a serem endereçados. No caso estamos configurando com a porta 4444 e o IP xxx.xxx.xxx.255, que é o IP de broadcast da maior parte das redes. Os campos com xxx são configurados de acordo com o IP assinalado ao CC3000 pelo DHCP. Com a função sendto, a mensagem é enviada pelo Socket previamente aberto. Caso a função retorne um valor negativo, significa um erro de conexão do Socket e o mesmo tem que ser reaberto, logo fechamos o Socket e mudamos par ao estado USERVER_OPEN_SOCKET.
void UServer_asyncProcess(void)
{
static userver_state_e uServer_state = USERVER_CONNECTING;
sockaddr socketAddr;
char sendData[10];
int32_t temperature;
long dataLenght;
/*
* Gerencia eventos do CC3000 pendentes
*/
hci_unsolicited_event_handler();
/*
* Verifica se o SmartConfig foi solicitado
*/
HAL_disableInterrupts();
if(g_RequestSmartConfig)
{
g_RequestSmartConfig = 0;
HAL_enableInterrupts();
/*
* Verifica se o SmartConfig já não está sendo executado
*/
if(uServer_state != USERVER_SMART_CONFIG && uServer_state != USERVER_PRE_SMART_CONFIG)
{
/*
* Coloca o valor das flags como zero.
*/
g_SmartConfigFinished = 0;
g_CC3000DHCP = 0;
/*
* Configura política de conexão para não se auto-conectar
*/
wlan_ioctl_set_connection_policy(0, 0, 0);
/*
* Deleta todos os profiles armazenados para garantir que o servidor sempre
* irá se conectar na rede especificada
*/
wlan_ioctl_del_profile(255);
uServer_state = USERVER_PRE_SMART_CONFIG;
}
}
HAL_enableInterrupts();
switch(uServer_state)
{
case USERVER_CONNECTING:
/*
* Espera até o CC3000 se conectar e o DHCP ser finalizado
*/
if(g_CC3000DHCP && g_CC3000Connected)
{
mdnsAdvertiser(1,g_DeviceName,strlen(g_DeviceName));
mdnsAdvertiser(1,g_DeviceName,strlen(g_DeviceName));
mdnsAdvertiser(1,g_DeviceName,strlen(g_DeviceName));
uServer_state = USERVER_OPEN_SOCKET;
}
break;
case USERVER_PRE_SMART_CONFIG:
/*
* Espera até o C3000 ser desconectado e então inicializa
* o SmartConfig.
*/
if(!g_CC3000Connected)
{
UServer_startSmartConfig();
uServer_state = USERVER_SMART_CONFIG;
}
break;
case USERVER_SMART_CONFIG:
/*
* Espera até o SmartConfig ser finalizado.
*/
if(g_SmartConfigFinished == 1)
{
/*
* Configura a politica de conexão do CC3000 para utilizar
* um profile armazenado e se conectar na última rede conectada,
* se disponível
*/
wlan_ioctl_set_connection_policy(0, 1, 1);
/*
* Reseta o CC3000
*/
wlan_stop();
HAL_delayCycles(6000000);
/*
* Inicializa o CC3000
*/
wlan_start(0);
/*
* Desabilita os eventos que não estamos interessados
*/
wlan_set_event_mask(HCI_EVNT_WLAN_KEEPALIVE|HCI_EVNT_WLAN_UNSOL_INIT|HCI_EVNT_WLAN_ASYNC_PING_REPORT);
uServer_state = USERVER_CONNECTING;
}
break;
case USERVER_OPEN_SOCKET:
/*
* Abre um socket UDP
*/
g_Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(g_Socket > -1)
{
uServer_state = USERVER_CONNECTED;
}
/*
* Verifica se o CC3000 ainda está conectado na WAN
*/
if(!g_CC3000DHCP || !g_CC3000Connected)
{
uServer_state = USERVER_CONNECTING;
break;
}
break;
case USERVER_CONNECTED:
/*
* Verifica se o CC3000 ainda está conectado na WAN
*/
if(!g_CC3000DHCP || !g_CC3000Connected)
{
uServer_state = USERVER_CONNECTING;
break;
}
/*
* Se for requisitado enviar a temperatura, é realizada a
* leitura. Depois transformamos o valor em um string e
* enviamos pelo socket UDP.
*/
if(g_RequestSendTemperature)
{
int ret;
g_RequestSendTemperature = 0;
temperature = HAL_readTemperatureCelcius();
dataLenght = itoa(temperature, sendData);
sendData[dataLenght-1] = '\n';
/*
* Configura o socket para a porta 4444 e para realizar broadcast
*/
socketAddr.sa_family = AF_INET;
socketAddr.sa_data[0] = 0x11;
socketAddr.sa_data[1] = 0x5c;
socketAddr.sa_data[2] = g_IP_Addr[0];
socketAddr.sa_data[3] = g_IP_Addr[1];
socketAddr.sa_data[4] = g_IP_Addr[2];
socketAddr.sa_data[5] = 0xFF;
ret = sendto(g_Socket, sendData, dataLenght, 0, &socketAddr, sizeof(sockaddr));
if(ret < 0)
{
closesocket(g_Socket);
uServer_state = USERVER_OPEN_SOCKET;
}
}
break;
default: uServer_state = USERVER_CONNECTING;
break;
}
}
Colocando para Funcionar
Eu recomendo fortemente atualizar a versão do firmware do CC3000. Para tal, conecte o CC3000 na Launchpad e entre na pasta PatchProgrammer\MSP flashing tools\MSP430Flasher_1.1.3.
Clique em download_cc3000_patch_programmer_driver.bat e espere o procedimento terminar. Clique em download_cc3000_patch_programmer_firmware.bat e espere o procedimento terminar.
Se você não tem prática em como criar um projeto no Code Composer Studio, você pode utilizar o projeto pronto, distribuído neste tópico. Basta selecionar a pasta workspace quando for pedido o workspace, e o projeto já estará pronto para uso. Caso ainda não o tenha feito, conecte o booster pack do CC3000 na LaunchPad e conecte a mesma no computador via porta USB. No CCS clique em debug e espere o firmware ser gravado na placa. Quando o procedimento estiver finalizado, clique em run.
No pacote de arquivos, abra a pasta Java Smart Config desktop client\net.betaengine.smartconfig-2013-10-03. Dentro dela clique em repackage.jar. Depois de finalizado clique em net.betaengine.smartconfig-ui.jar. Vai abrir uma janela igual à da figura abaixo.
Em tese o Network Name e o Gateway Adress já devem vir preenchidos. Caso não seja o caso, preencha com os dados de sua rede. Preencha também o Password (se houver) e mude o Device Name para Termometro Wi-Fi. Aperte Send e espere o procedimento do SmartConfig terminar. Se tudo der certo, após o procedimento, o CC3000 irá se conectar à rede configurada, abrir o Socket e começar a fazer broadcast da temperatura.
Para visualizar os dados enviados, abra o hercules_3-2-4.exe. Dentro do programa, navegue para a aba UDP, preencha o campo Local port com 4444 e clique em Listen. Como na imagem abaixo, você deve começar a receber o valor da temperatura enviado pelo MSP430 (o valor muito provavelmente vai apresentar um offset de temperatura, que deve ser calibrado).
Conclusão
Aqui foi demonstrada uma aplicação simplória utilizando o CC3000, porém serve como uma boa referência para começar um projeto. Na minha opinião a ferramenta desenvolvida pela Texas é bastante poderosa pois permite que um microcontrolador consiga se conectar em uma rede Wi-Fi sem muito esforço e custo: a gama de aplicações é muito grande.












Olá,
Eu tenho um MSP430F5529, sabe me dizer se é possível fazer este projeto
Com esta placa?
Obrigado
Edgar dos Reis