WIFI驱动
Day3-四、WIFI驱动
(一)、初始化串口
wifi模块需要用到接收中断用来保存命令的返回值或者数据,所以还要配置USART2的接收
使用WIFI模块的底层本质是利用串口进行收发。
主要包括对串口2相关参数的配置、初始化串口2的IO引脚以及设置其优先级配置、使能接收中断、串口2的中断回调函数等。
static void HAL_UART2_MspInit(UART_HandleTypeDef *huart);
static UART_HandleTypeDef huart2;
int Driver_Net_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
HAL_UART2_MspInit(&huart2); //初始化串口2的IO引脚以及设置其优先级
HAL_UART_Init(&huart2);
if(HAL_UART_Init(&huart2) != HAL_OK)
return -1; //初始化串口2失败
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE); //使能串口2的接收中断,串口2已接受到数据就进入接收中断,执行USART2_IRQHandler函数
return 0; //初始化串口2成功
}
static void HAL_UART2_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(huart->Instance == USART2)
{
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* USART2 TX/RX */
/* PA2 - TX
* PA3 - RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;//复用推挽输出
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;//复用输入
GPIO_InitStruct.Pull = GPIO_PULLUP; //配置上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//设置串口2的接收中断
HAL_NVIC_SetPriority(USART2_IRQn,1,0);//设置优先级
HAL_NVIC_EnableIRQ(USART2_IRQn); //使能中断
}
}
void USART2_IRQHandler(void)
{
uint8_t rx_data = 0;
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) == SET)//进入接收中断后首先判断以下RXNE标志位是不是1
{
__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE); //如果是就先清除一下标志位
rx_data = USART2->DR; //并读出接收到的数据
}
}
(二)、Net模块接口
1、发送指令函数
Driver_Net_TransmitCmd(const char * cmd, const char * reply, uint16_t timeout)
static int Driver_Net_TransmitCmd(const char * cmd, const char * reply, uint16_t timeout)
{//cmd和reply都是字符指针,不是字符
char buf[128] = {0};
uint8_t i = 0;
strcat(buf, cmd); //先把cmd拷贝到buf字符串的后面,因为buf字符串初始值为0,所以就相当于把cmd复制到buf里。
if(strstr(buf,"\r\n") == NULL) //判断命令里是否有“\r\n”
{
strcat(buf,"\r\n"); //如果没有则把"\r\n"拷贝到buf的最后
}
//以上,buf里存放的是含有结束符\r\n的的命令cmd
Driver_Buffer_Clean(&WifiCMDReturnBuffer); //再把环形缓冲区WifiCMDReturnBuffer清空
HAL_UART_Transmit(&huart2, (uint8_t *)buf, strlen(buf), 500); //再利用串口2把buf里的数据发送出去,也即把命令发送出去
memset(buf,0,128); //清空buf
while(timeout != 0)
{
if(Driver_Buffer_Read(&WifiCMDReturnBuffer, (uint8_t *)&buf[i]) == 0)//读环形缓冲区WifiCMDReturnBuffer的数据到buf,如果读成功则返回0,条件成立
{ //把&buf[i]强转成uint8_t *类型,读缓冲区数据的一个字节到buf[i]这个char字符,由于用的是&buf[i],buf[i]这个字符的地址,也是指针,符合参数类型
i = (i+1)%128; //就相当于i的自增,同时保证i不会超过128而溢出。比如说i初始值为0,那么现在i=1。
if(strstr(buf, reply) != 0) //判断buf字符串中是否包含有reply字符串
{
return 0; //如果有就返回0
}
}
else//如果读取环形缓冲区WifiCMDReturnBuffer失败
{
timeout--; //超时时间自减
HAL_Delay(1); //延时1ms
}
}
return -1;
}
while的逻辑为:
判断超时时间timeout是否减为0
不为0则:
读取环形缓冲区WifiCMDReturnBuffer的一字节数据到buf[i]的char字符
①如果读取成功:
i自增并且防止溢出128;
判断buf里是否包含reply字符串;
即是否收到且收完了命令的返回值reply
这里就是判断发出去一个命令后是否接收到命令的返回值reply,如果判断完整地接收到了命令的返回值则返回0,
此时认为相当于成功发送出去了一个命令。
②如果读取失败:
timeout减1;
延时1ms;
Driver_Net_TransmitCmd函数的逻辑:
①处理要发送的命令cmd,使其结尾总会有结束符\r\n;
②清空WifiCMDReturnBuffer,将cmd发送出去,清空buf; 。。。在串口2的接收中断会收到命令的返回值并写入环形缓冲区。。。
③while判断超时时间是否为0:
不为0则一个字节一个字节地读取环形缓冲区,读到buf[i]的字符去,并且i自增,每次读都判断buf字符串是否已经包含了命令的返回值reply,
如果发现已经包含了reply的完整字符串,说明已经完整地收到了cmd命令的返回值reply,就返回0,也即表示命令发送成功。
如果读取环形缓冲区失败,则timeout减1,并延时1ms,如果一直都读取失败,就一直减1并延时等待,直到timeout减为0 退出while循环。
2、发送数据函数
Driver_Net_TransmitSocket(const char * socket, int len, int timeout)
int Driver_Net_TransmitSocket(const char * socket, int len, int timeout)
{
char buf[64] = {0}; //64个字符的数组64Bytes
char cmd[16] = {0}; //16个字符的数组16Bytes
uint8_t i = 0;
sprintf(cmd, "AT+CIPSEND=%d\r\n", len); //将命令“AT+CIPSEND=len\r\n”赋给cmd
HAL_UART_Transmit(&huart2, (uint8_t *)cmd, strlen(cmd), 500); //发送命令
HAL_Delay(1); //延时一会
Driver_Buffer_Clean(&WifiCMDReturnBuffer); //清空环形缓冲区
HAL_UART_Transmit(&huart2, (uint8_t *)socket, len, 500); //发送数据
while(timeout != 0) //等待并读取命令的返回值
{
if(Driver_Buffer_Read(&WifiCMDReturnBuffer, (uint8_t *)&buf[i]) == 0)
{
i = (i+1)%64;
if(strstr(buf, "SEND OK") != 0)
{
return 0;
}
}
else
{
timeout--;
HAL_Delay(1);
}
}
return -1;
}
发送数据函数逻辑与发送指令函数基本一致。
3、接收数据函数
*Driver_Net_RecvSocket(char buf, int len, int timeout)
int Driver_Net_RecvSocket(char *buf, int len, int timeout)
{
static int tmp = 0;
tmp += Driver_Buffer_ReadBytes(&NetDataBuffer, (uint8_t*)&buf[tmp], len);
if(tmp == len)
{
tmp = 0;
return len;
}
return tmp;
}
4、连接WIFI热点
/*连接WIFI热点*/
int Driver_Net_ConnectWiFi(const char * ssid, const char * pwd, int timeout)
{
char buf[50] = "AT+CWJAP_CUR=\"";//转义字符把"也表示出来
strcat(buf, ssid);
strcat(buf, "\",\""); //相当于“,”
strcat(buf, pwd);
strcat(buf, "\""); //相当于"
//则buf里就是:" AT+CWJAP_CUR="ssid","pwd" "
return Driver_Net_TransmitCmd(buf, "GOT IP\r\n", timeout);//发送该命令
}
5、断开WIFI热点
Driver_Net_DisconnectWiFi(void)
/*断开WIFI热点连接*/
int Driver_Net_DisconnectWiFi(void)
{
return Driver_Net_TransmitCmd("AT+CWQAP", "OK\r\n", 500);
}
6、建立TCP连接
*Driver_Net_ConnectTCP(const char ip, int port, int timeout)
int Driver_Net_ConnectTCP(const char *ip, int port, int timeout)
{
char buf[128] = "AT+CIPSTART=\"TCP\",\""; //AT+CIPSTART="TCP"," (19)
sprintf(&buf[19], "%s\",%d", ip, port); //ip",port
return Driver_Net_TransmitCmd(buf, "OK\r\n", timeout);//buf里的数据就是AT+CIPSTART="TCP","ip",port.发送该命令
}
7、断开TCP/UDP连接
Driver_Net_Disconnect_TCP_UDP(void)
int Driver_Net_Disconnect_TCP_UDP(void)
{
return Driver_Net_TransmitCmd("AT+CIPCLOSE", "OK\r\n", 500);
}
8、解析网络数据
NetDataProcess_Callback(uint8_t data)
该函数要放到串口2的接收中断中
typedef enum AT_STATUS{
INIT_STATUS,
LEN_STATUS,
DATA_STATUS
}AT_STATUS;
static uint8_t g_DataBuff[256] = {0};
void NetDataProcess_Callback(uint8_t data)
{
uint8_t *buf = g_DataBuff;
static AT_STATUS g_status = INIT_STATUS;
static int g_DataBuffIndex = 0; //索引
static int g_DataLen = 0;
int i = g_DataBuffIndex;
int m = 0;
buf[i] = data;
g_DataBuffIndex++;
switch(g_status)
{
case INIT_STATUS:
{
if (buf[0] != '+')
{
g_DataBuffIndex = 0;
}
else if (i == 4)
{
if (strncmp((char*)buf, "+IPD,", 5) == 0)
{
g_status = LEN_STATUS;
}
g_DataBuffIndex = 0;
}
break;
}
case LEN_STATUS:
{
if (buf[i] == ':')
{
/* 计算长度 */
for (m = 0; m < i; m++)
{
g_DataLen = g_DataLen * 10 + buf[m] - '0';
}
g_status = DATA_STATUS;
g_DataBuffIndex = 0;
}
else if (i >= 9) /* ESP8266数据buffer大小是2920, 4位数: +IPD,YYYY:xxxxxx */
{
/* 都没有接收到':' */
g_status = INIT_STATUS;
g_DataBuffIndex = 0;
}
break;
}
case DATA_STATUS:
{
if (g_DataBuffIndex == g_DataLen)
{
/* 接收完数据 */
Driver_Buffer_WriteBytes(&NetDataBuffer, buf, g_DataLen);
/* 恢复初始状态 */
g_status = INIT_STATUS;
g_DataBuffIndex = 0;
g_DataLen = 0;
}
break;
}
default:break;
}
}
网络模块初始化函数
Driver_Net_Init(void)
int Driver_Net_Init(void)
{
if(Driver_Net_UART_Init() != 0)
return -1;
Driver_Buffer_Init(&WifiCMDReturnBuffer,128);//初始化一个存储wifi命令返回值的环形缓冲区,长度为128字节
Driver_Buffer_Init(&NetDataBuffer,1024);
Driver_Net_TransmitCmd("AT+RST","OK\r\n",10000);
HAL_Delay(500);
Driver_Net_TransmitCmd("AT+CWMODE_CUR=1","OK\r\n",500);//设置AT的模式为Station模式
return 0;
}
以上就完成了对网络模块驱动的编写。
下面需要对接口进行测试:
测试代码:
... ... ... ...
Driver_LED_Init();
Driver_DBG_Init();
Driver_Net_Init();
//连接WIFI热点测试:
if(Driver_Net_ConnectWiFi("ok","123456",5000) == 0)
{
printf("WIFI Connect Successsed!\r\n");
}
else
{
printf("WIFI Connect Failed!\r\n");
}
//建立TCP连接测试:
if(Driver_Net_ConnectTCP("223.104.39.172",8000,1000) == 0)
{
printf("TCP Connect Successsed!\r\n");
}
else
{
printf("TCP Connect Failed!\r\n");
}
while(1)
{
//发送数据测试
if(Driver_Net_TransmitSocket("Hello World",11,500) == 0)
{
printf("Socket Transmit Success!\r\n");
}
else
{
printf("Socket Transmit Failed!\r\n");
}
//接收数据测试
if(Driver_Net_RecvSocket((char *)buf, 5, 100) == 0)
{
printf("Receive Socket : %s\r\n",buf);
}
memset(buf, 0, 5);
HAL_Delay(1000);
}
... ... ... ...
经过测试发现,连接WIFI热点时,对于需要验证登录的网络如校园网是连接失败的,对于手机热点是可以连接的。
(三)、C知识回顾
strcat()函数用法:
描述
C 库函数 char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
声明
下面是 strcat() 函数的声明。
char *strcat(char *dest, const char *src)
参数
- dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
- src – 指向要追加的字符串,该字符串不会覆盖目标字符串。
返回值
该函数返回一个指向最终的目标字符串 dest 的指针。
实例
下面的实例演示了 strcat() 函数的用法。
#include <stdio.h>
#include <string.h>
int main ()
{
char src[50], dest[50];
strcpy(src, "This is source");
strcpy(dest, "This is destination");
strcat(dest, src);
printf("最终的目标字符串: |%s|", dest);
return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:
最终的目标字符串: |This is destinationThis is source|
strstr()函数的用法:
描述
C 库函数 char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’。
声明
下面是 strstr() 函数的声明。
char *strstr(const char *haystack, const char *needle)
参数
- haystack – 要被检索的 C 字符串。
- needle – 在 haystack 字符串内要搜索的小字符串。
返回值
该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
实例
#include <stdio.h>
#include <string.h>
int main()
{
const char haystack[20] = "RUNOOB";
const char needle[10] = "NOOB";
char *ret;
ret = strstr(haystack, needle);
printf("子字符串是: %s\n", ret);
return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:
子字符串是: NOOB
训练营导航:www.100ask.net