编写ESP8266和STM32的AT指令通信代码是一项复杂的任务,但是通过使用软件分层技术,我们可以更好地管理这个过程并提高代码质量。软件分层技术是一种编程方法,它将软件系统分解为多个独立的层,每个层都有特定的职责。这种结构可以帮助我们更好地组织代码,使其更易于理解和维护。下面我们详细讨论如何在编写ESP8266和STM32的AT指令通信代码时应用这种方法。
首先,我们需要将程序分为三个主要层次:硬件层,协议层和应用层。
硬件层
硬件层是最基础的一层,它负责与硬件直接交互。在ESP8266和STM32的AT指令通信代码中,硬件层的职责包括初始化USART,发送和接收数据等。这一层的代码通常是硬件相关的,因此在不同的硬件平台上可能需要进行修改。尽管硬件层的代码可能不具备良好的可移植性,但是通过将这部分代码单独放在一层,我们可以更容易地管理和修改它。
数据结构
#define RX_BUF_MAX_LEN 1024 //最大接受字节数
struct STRUCT_USART_DATA //数据帧结构体
{
char Data_RX_BUF[RX_BUF_MAX_LEN]; //usart的缓存
union
{
__IO u16 DataAll; //所有信息,用于清除所有信息
struct
{
__IO u16 DataLength :15; // 接收字节长度 14:0
__IO u16 DataFinishFlag :1; // 接收完成标志 15
}DataBit; //按位信息
};
};
串口处理函数
void ESP8266_USART_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //串口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用输出,外设USART1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//复用输出,外设USART1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不需要流量控制
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//发送,接收模式
USART_InitStructure.USART_Parity = USART_Parity_No; //无校验位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_Init(USART1,&USART_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //使能接受数据中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先级分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn ;//中断线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1,ENABLE);
}
void USART1_IRQHandler()
{
u8 ucCh;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
ucCh = USART_ReceiveData(USART1);
if(ESP8266_Data_Struct.DataBit.DataLength < ( RX_BUF_MAX_LEN - 1 ) ) // -1的目的:至少保留最后一位做结束位,补'\0'
{
ESP8266_Data_Struct.Data_RX_BUF[ ESP8266_Data_Struct.DataBit.DataLength ++ ] = ucCh;
}
}
else if(USART_GetFlagStatus(USART1, USART_FLAG_IDLE) != RESET)
{
USART1->SR;
USART1->DR;
ESP8266_Data_Struct.DataBit.DataFinishFlag = 1;
ESP8266_Data_Struct.Data_RX_BUF[ESP8266_Data_Struct.DataBit.DataLength] = '\0';
}
}
协议层
协议层的任务是处理所有的AT指令和响应。这一层的代码将硬件层的数据解析为更高级别的结构,例如AT指令或响应,然后处理这些数据。这一层的代码通常与硬件无关,因此可以在不同的硬件平台上重用。通过将协议相关的代码放在单独的一层,我们可以更明确地看到数据是如何从硬件层转化为更高级别的结构的,这有助于我们理解和修改代码。
uint8_t ESP8266_Send_AT_Cmd(char *cmd,char *ack,u32 time)
{
ESP8266_Data_Struct.DataAll = 0; //清空信息,重新接收新的数据包
Esp8266_SendString(cmd);
if(ack==0 ) //不需要接收数据
{
return 1;
}
Delay_ms(time); //延时
while(ESP8266_Data_Struct.DataBit.DataFinishFlag != 1);
ESP8266_Data_Struct.DataAll = 0;
printf("ESP8266_Send_AT_Cmd Receive Message :\r\n");
printf("%s\r\n",ESP8266_Data_Struct.Data_RX_BUF);
if( ack != 0 )
return ( strstr ( ESP8266_Data_Struct .Data_RX_BUF, ack ) );
}
该函数ESP8266_Send_AT_Cmd
用于向ESP8266模块发送AT指令并接收响应。函数具体流程如下:
- 首先清空数据结构
ESP8266_Data_Struct
,为接收新的数据包做准备。 - 使用
Esp8266_SendString
函数发送AT指令。 - 如果在函数调用中
ack
参数为0,即不需要接收数据,函数在此处返回,返回值为1。 - 如果需要接收数据,函数会暂停一段时间,等待ESP8266模块的响应。
- 在等待的过程中,如果接收到数据包,
DataFinishFlag
标志位会被设为1。然后函数会打印出接收到的数据包。 - 最后,函数会检查接收到的数据包中是否包含
ack
字符串。如果包含,函数返回1,否则返回0。这一步用于验证ESP8266模块是否返回了预期的响应。
这个函数的作用是,可以将发送AT指令和接收响应的过程封装起来,简化了在应用层使用ESP8266模块的复杂度。
应用层
最后,应用层使用协议层提供的功能来执行实际的操作,例如连接到网络,发送数据等。这一层的代码通常针对特定的应用进行编写,因此可能需要根据项目的需要进行修改。将应用相关的代码放在单独的一层,我们可以更容易地理解和管理这部分代码,使其更符合特定应用的需求。
uint8_t ESP8266_JoinAP( char * pSSID, char * pPassWord)
{
char cCmd [120];
sprintf ( cCmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", pSSID, pPassWord );
return ESP8266_Send_AT_Cmd( cCmd, "OK", 0, 5000 );
}
uint8_t ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode)
{
switch ( enumMode )
{
case STA:
return ESP8266_Send_AT_Cmd ( "AT+CWMODE=1\r\n", "OK", "no change", 2500 );
case AP:
return ESP8266_Send_AT_Cmd ( "AT+CWMODE=2\r\n", "OK", "no change", 2500 );
case STA_AP:
return ESP8266_Send_AT_Cmd ( "AT+CWMODE=3\r\n", "OK", "no change", 2500 );
default:
return 0;
}
}
函数 ESP8266_JoinAP( char * pSSID, char * pPassWord)
的作用是让ESP8266模块连接到指定的无线网络。该函数接收两个参数,分别是要连接的无线网络的SSID和密码。函数内部首先构造一个AT指令字符串,格式为AT+CWJAP="<SSID>","<Password>"\\\\r\\\\n
,然后调用ESP8266_Send_AT_Cmd
函数发送这个AT指令,并等待模块的响应。如果模块返回了"OK",则表示连接成功,函数会返回1,否则返回0。
函数 ESP8266_Net_Mode_Choose(ENUM_Net_ModeTypeDef enumMode)
的作用是设置ESP8266模块的工作模式。该函数接收一个枚举类型的参数,代表要设置的模式,可以是STA(站点模式),AP(接入点模式)或者STA_AP(站点+接入点模式)。函数内部根据输入的模式构造相应的AT指令字符串,然后调用ESP8266_Send_AT_Cmd
函数发送这个AT指令,并等待模块的响应。如果模块返回了"OK"或者"no change",则表示设置成功,函数会返回1,否则返回0。