STM32与ESP01/ESP01S等WIFI模块进行无线通信调试
一、ESP01/ESP01S WIFI模块介绍
ESP01/ESP01S模块的核心是ESP8266,ESP8266是一款超低功耗的UART-WiFi 透传模块,集成模块采用的就是贴片式的封装,该芯片上清晰地印着乐鑫科技的logo,说明其不仅简单的可作为一个单独的模块使用,还可作为用户可编程的单片机使用,使用Arduino等IED即可对其进行内部程序的更改,以及实现特定的功能,由于厂商已经考虑到大部分用户需求,该芯片在出厂时已经烧录好了程序,该程序将由一系列的AT指令驱动并实现相应的功能,我们不需要对其再次进行更改了,当然在某些情况下我们通常会使用厂家提供的固件进行烧录或升级固件实现更多的功能。(如MQTT链接)
二、使用步骤
1.模块引脚功能
这里以ESP01S,为例,esp01同理。
2.硬件连接
首先,你得拥有一个USB转TTL的模块
接线如下(示例):
接好线后我们将USB转串口接入电脑,使用串口通信对芯片进行功能配置。
模块默认是 AT 指令状态, 模块波特率: 115200 ( 8 位数据位, 1 位停止位), 这里我们仅介绍几个用户常用指令, 详细的指令集请参考《 ESP8266_AT 指令集 V2.1.0.pdf 》 这个文档。 ESP8266 模块支持 STA/AP/STA+AP 三种工作模式。 ⧫ STA 模式: ESP8266 模块通过路由器连接互联网, 手机或电脑通过互联网实 现对设备的远程控制。 ⧫ AP 模式: 默认模式 ATK_ESP8266 模块作为热点, 实现手机或电脑直接与模 块通信, 实现局域网无线控制。 ⧫ STA+AP 模式: 两种模式的共存模式,( STA 模式) 即可以通过路由器连接到 互联网,并通过互联网控制设备;( AP 模式)也可作为 wifi 热点, 其他 wifi 设备连接到模块。 这样实现局域网和广域网的无缝切换, 方便操作。
—STA 模式: ESP8266 模块通过路由器连接互联网, 手机或电脑通过互联网实 现对设备的远程控制。 ⧫ AP 模式: 默认模式 ATK_ESP8266 模块作为热点, 实现手机或电脑直接与模 块通信, 实现局域网无线控制。 ⧫ STA+AP 模式: 两种模式的共存模式,( STA 模式) 即可以通过路由器连接到 互联网,并通过互联网控制设备;( AP 模式)也可作为 wifi 热点, 其他 wifi 设备连接到模块。 这样实现局域网和广域网的无缝切换, 方便操作。 透传模式 ❉ ATK_ESP8266 模块仅在 TCP Client 和 UDP , 支持透传模式。
3.写入AT指令进行功能调用
打开正点原子官方的串口助手XCOM,注意WIFI模块的TX,RX和USB转串口的是交错连接的。
先测试模块工作正不正常,串口先发送AT,返回OK说明模块工作正常。
大家也可以像我一样在多条发送里写好要发送的指令,具体可参考官方文档。
4.配置服务端ESP01_Server
打开串口助手,依次发动以下AT指令:
AT+CWMODE=3
AT+CWSAP=“ESP01_Server”,“12345678”,1,4
AT+CIPMUX=1
AT+CIPSERVER=1,8080
上述指令的作用如下:
同时启用STA和AP模式(但这里主要用AP创建热点)。
WiFi名称 ,WiFi密码,信道1,加密方式4(WPA2_PSK)。
开启多连接。
开启TCP服务器,端口8080。
这里特别注意: 上述4条配置热点的指令中,第3句和第4句有时效性(实测到的小bug),即
AT+CIPMUX=1
AT+CIPSERVER=1,8080
开启多连接是为了让多个客户端接入,但是注意,我们的模块配置好WiFi热点后,断电时再次上电WiFi仍然存在,可以连接,但是配置好的服务器失效了,我们无法连接服务端,所以得串口重发
AT+CIPMUX=1
AT+CIPSERVER=1,8080
才能连接的上。
手机网络助手进行调试:
随便在应用商店下载个网络调试助手,下载好后,打开手机的WIFI,在列表中找到并连接你刚才配置好的热点ESP01_Server,密码12345678,链接即可,当然WiFi名称和密码可以自己改。
然后打开网络调试助手
我们以TCP客户端的身份接入服务器,远程IP:192.168.4.1(这个作为热点是默认不变的),端口号8080,名称自己起。
我们点击链接,如果提示链接失败,这时候不用慌,这是之前提到的小bug,如果你第一次配置成功不断电的情况下一般能直接连上。这是我给同学们做的示范拔掉了模块再接上而已,失败的情况,我们打开串口助手,发送
AT+CIPMUX=1
AT+CIPSERVER=1,8080
再次链接就可以连接上了。
手机发送:
我们尝试发送几句消息给服务端,服务端(WiFi模块)的串口与电脑相连接,我们可以从串口助手上接收到手机发来的信息。
串口收到:
如果想让服务端给手机下发消息,我们可以在串口助手发送以下指令:
AT+CIPSEND=0,5
出现箭头的标识,然后就可以发送字符了
0是已接入设备的ID,这个由服务器随机分配,5是你要发送的数据长度,当然你可以改长一些,这里作为测试我只发送了12345,hello两个字符串给手机。
手机收到的消息(绿色字体):
说明模块配置成功,数据可以与手机互传了。
讲到这里,相信你应该明白单片机的作用了,我们将使用单片机编写好的程序通过串口与WIFI模块相连接,和电脑串口的功能一样,我们可以让单片机发送AT指令。
5.配置客户端ESP01_Client(附加历程,两个ESP01S模块点对点通信需要)
这里我们需要另一块ESP01S,同样是连接好线,接上电脑,串口发送AT指令如下:
AT+CWMODE=1
AT+CWJAP=“ESP01_Server”,“12345678”
AT+CIPSTART=“TCP”,“192.168.4.1”,8080
功能如下:
仅STA模式
连接A组创建的热点
连接A组的服务器
192.168.4.1是SoftAP的默认IP
这个也是让这个模块作为客户端接入我们先前配置好的热点和TCP服务器,跟我们手机上手动添加IP和端口号作为客户端,接入服务器的过程一样的。由于我没有过多的ESP01S和USB转串口模块,单片机,这里我就不再演示了。
6.服务端ESP01_Server与STM32数据互传演示
接线如下:
这张图是缘自B站江科大的串口收发教程演示,我们把USB转串口改为ESP01_Server模块即可,3.3v记得接上,OLED模块进行串口接收到的信息显示,我在其代码的基础上做了如下更改
上图是手机没连接WIFI的情况,显示Error,跟我们在串口助手调试时出现的情况一样。连接WiFi后就可以与手机进行通信了,代码中我只做了个简单的测试,效果较为简陋,我将自己的姓名简称发给了手机同时手机发送的信息也能发到WiFi服务端,在屏幕上显示出来,但是发送姓名的操作我只写了一次,发送姓名每次按下复位键才可触发,大家啊可自行外接按键进行服务端的数据发送,修改代码,这样方便些。
主要代码如下:serial.c
#include \"stm32f10x.h\" // Device header#include #include #include #define RX_BUFFER_SIZE 128uint8_t Serial_RxData;//定义串口接收的数据变量uint8_t Serial_RxFlag;//定义串口接收的标志位变量char Serial_RxBuffer[128];uint8_t Serial_RxIndex = 0;uint8_t Serial_RxReady = 0;/** * 函 数:串口初始化 * 参 数:无 * 返 回 值:无 */void Serial_Init(void){/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//将PA9引脚初始化为复用推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//将PA10引脚初始化为上拉输入/*USART初始化*/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_Init,配置USART1/*中断输出配置*/USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收数据的中断/*NVIC中断分组*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC为分组2/*NVIC配置*/NVIC_InitTypeDef NVIC_InitStructure;//定义结构体变量NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//选择配置NVIC的USART1线NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定NVIC线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定NVIC线路的响应优先级为1NVIC_Init(&NVIC_InitStructure);//将结构体变量交给NVIC_Init,配置NVIC外设/*USART使能*/USART_Cmd(USART1, ENABLE);//使能USART1,串口开始运行}/** * 函 数:串口发送一个字节 * 参 数:Byte 要发送的一个字节 * 返 回 值:无 */void Serial_SendByte(uint8_t Byte){USART_SendData(USART1, Byte);//将字节数据写入数据寄存器,写入后USART自动生成时序波形while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//等待发送完成/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/}/** * 函 数:串口发送一个数组 * 参 数:Array 要发送数组的首地址 * 参 数:Length 要发送数组的长度 * 返 回 值:无 */void Serial_SendArray(uint8_t *Array, uint16_t Length){uint16_t i;for (i = 0; i < Length; i ++)//遍历数组{Serial_SendByte(Array[i]);//依次调用Serial_SendByte发送每个字节数据}}/** * 函 数:串口发送一个字符串 * 参 数:String 要发送字符串的首地址 * 返 回 值:无 */void Serial_SendString(char *String){uint8_t i;for (i = 0; String[i] != \'\\0\'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止{Serial_SendByte(String[i]);//依次调用Serial_SendByte发送每个字节数据}}/** * 函 数:次方函数(内部使用) * 返 回 值:返回值等于X的Y次方 */uint32_t Serial_Pow(uint32_t X, uint32_t Y){uint32_t Result = 1;//设置结果初值为1while (Y --)//执行Y次{Result *= X;//将X累乘到结果}return Result;}/** * 函 数:串口发送数字 * 参 数:Number 要发送的数字,范围:0~4294967295 * 参 数:Length 要发送数字的长度,范围:0~10 * 返 回 值:无 */void Serial_SendNumber(uint32_t Number, uint8_t Length){uint8_t i;for (i = 0; i < Length; i ++)//根据数字长度遍历数字的每一位{Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + \'0\');//依次调用Serial_SendByte发送每位数字}}/** * 函 数:使用printf需要重定向的底层函数 * 参 数:保持原始格式即可,无需变动 * 返 回 值:保持原始格式即可,无需变动 */int fputc(int ch, FILE *f){Serial_SendByte(ch);//将printf的底层重定向到自己的发送字节函数return ch;}/** * 函 数:自己封装的prinf函数 * 参 数:format 格式化字符串 * 参 数:... 可变的参数列表 * 返 回 值:无 */void Serial_Printf(char *format, ...){char String[100];//定义字符数组va_list arg;//定义可变参数列表数据类型的变量argva_start(arg, format);//从format开始,接收参数列表到arg变量vsprintf(String, format, arg);//使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);//结束变量argSerial_SendString(String);//串口发送字符数组(字符串)}/** * 函 数:获取串口接收标志位 * 参 数:无 * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零 */uint8_t Serial_GetRxFlag(void){if (Serial_RxFlag == 1)//如果标志位为1{Serial_RxFlag = 0;return 1;//则返回1,并自动清零标志位}return 0;//如果标志位为0,则返回0}/** * 函 数:获取串口接收的数据 * 参 数:无 * 返 回 值:接收的数据,范围:0~255 */uint8_t Serial_GetRxData(void){return Serial_RxData;//返回接收的数据变量}/** * 函 数:USART1中断函数 * 参 数:无 * 返 回 值:无 * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行 * 函数名为预留的指定名称,可以从启动文件复制 * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入 *///void USART1_IRQHandler(void)//{//if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//判断是否是USART1的接收事件触发的中断//{//Serial_RxData = USART_ReceiveData(USART1);//读取数据寄存器,存放在接收的数据变量//Serial_RxFlag = 1;//置接收标志位变量为1//USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除USART1的RXNE标志位////读取数据寄存器会自动清除此标志位////如果已经读取了数据寄存器,也可以不执行此代码//}//}// 串口中断处理函数void USART1_IRQHandler(void){ if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { uint8_t data = USART_ReceiveData(USART1); // 存储数据到缓冲区 if (Serial_RxIndex < RX_BUFFER_SIZE - 1) { Serial_RxBuffer[Serial_RxIndex] = data; Serial_RxIndex++; // 检查是否收到换行符(表示字符串结束) if (data == \'\\n\') { Serial_RxBuffer[Serial_RxIndex] = \'\\0\'; // 添加结束符 Serial_RxReady = 1; Serial_RxIndex = 0; } } else { // 缓冲区满,强制结束 Serial_RxBuffer[RX_BUFFER_SIZE - 1] = \'\\0\'; Serial_RxReady = 1; Serial_RxIndex = 0; } USART_ClearITPendingBit(USART1, USART_IT_RXNE); }}// 获取接收到的字符串uint8_t Serial_GetRxString(char* buffer, uint16_t size){ if (Serial_RxReady) { // 复制字符串并过滤掉回车换行符 uint16_t i = 0, j = 0; while (Serial_RxBuffer[i] != \'\\0\' && j < size - 1) { if (Serial_RxBuffer[i] != \'\\r\' && Serial_RxBuffer[i] != \'\\n\') { buffer[j] = Serial_RxBuffer[i]; j++; } i++; } buffer[j] = \'\\0\'; // 确保结束符 Serial_RxReady = 0; return 1; } return 0;}// 清空接收缓冲区void Serial_ClearBuffer(void){ Serial_RxIndex = 0; Serial_RxReady = 0; memset(Serial_RxBuffer, 0, RX_BUFFER_SIZE);}
OLED.c
#include \"stm32f10x.h\"#include \"OLED_Font.h\"#include \"Delay.h\"#include \"string.h\"/*引脚配置*/#define OLED_W_SCL(x)GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))#define OLED_W_SDA(x)GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))/*引脚初始化*/void OLED_I2C_Init(void){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOB, &GPIO_InitStructure);OLED_W_SCL(1);OLED_W_SDA(1);}/** * @brief I2C开始 * @param 无 * @retval 无 */void OLED_I2C_Start(void){OLED_W_SDA(1);OLED_W_SCL(1);OLED_W_SDA(0);OLED_W_SCL(0);}/** * @brief I2C停止 * @param 无 * @retval 无 */void OLED_I2C_Stop(void){OLED_W_SDA(0);OLED_W_SCL(1);OLED_W_SDA(1);}/** * @brief I2C发送一个字节 * @param Byte 要发送的一个字节 * @retval 无 */void OLED_I2C_SendByte(uint8_t Byte){uint8_t i;for (i = 0; i < 8; i++){OLED_W_SDA(Byte & (0x80 >> i));OLED_W_SCL(1);OLED_W_SCL(0);}OLED_W_SCL(1);//额外的一个时钟,不处理应答信号OLED_W_SCL(0);}/** * @brief OLED写命令 * @param Command 要写入的命令 * @retval 无 */void OLED_WriteCommand(uint8_t Command){OLED_I2C_Start();OLED_I2C_SendByte(0x78);//从机地址OLED_I2C_SendByte(0x00);//写命令OLED_I2C_SendByte(Command); OLED_I2C_Stop();}/** * @brief OLED写数据 * @param Data 要写入的数据 * @retval 无 */void OLED_WriteData(uint8_t Data){OLED_I2C_Start();OLED_I2C_SendByte(0x78);//从机地址OLED_I2C_SendByte(0x40);//写数据OLED_I2C_SendByte(Data);OLED_I2C_Stop();}/** * @brief OLED设置光标位置 * @param Y 以左上角为原点,向下方向的坐标,范围:0~7 * @param X 以左上角为原点,向右方向的坐标,范围:0~127 * @retval 无 */void OLED_SetCursor(uint8_t Y, uint8_t X){OLED_WriteCommand(0xB0 | Y);//设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));//设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F));//设置X位置低4位}/** * @brief OLED清屏 * @param 无 * @retval 无 */void OLED_Clear(void){ uint8_t i, j;for (j = 0; j < 8; j++){OLED_SetCursor(j, 0);for(i = 0; i < 128; i++){OLED_WriteData(0x00);}}}/** * @brief OLED显示一个字符 * @param Line 行位置,范围:1~4 * @param Column 列位置,范围:1~16 * @param Char 要显示的一个字符,范围:ASCII可见字符 * @retval 无 */void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char){ uint8_t i;OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);//设置光标位置在上半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - \' \'][i]);//显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);//设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(OLED_F8x16[Char - \' \'][i + 8]);//显示下半部分内容}}/** * @brief OLED显示字符串 * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串,范围:ASCII可见字符 * @retval 无 */void OLED_ShowString(uint8_t Line, uint8_t Column, char *String){uint8_t i;for (i = 0; String[i] != \'\\0\'; i++){OLED_ShowChar(Line, Column + i, String[i]);}}/** * @brief OLED次方函数 * @retval 返回值等于X的Y次方 */uint32_t OLED_Pow(uint32_t X, uint32_t Y){uint32_t Result = 1;while (Y--){Result *= X;}return Result;}/** * @brief OLED显示数字(十进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~4294967295 * @param Length 要显示数字的长度,范围:1~10 * @retval 无 */void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length){uint8_t i;for (i = 0; i < Length; i++){OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + \'0\');}}/** * @brief OLED显示数字(十进制,带符号数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-2147483648~2147483647 * @param Length 要显示数字的长度,范围:1~10 * @retval 无 */void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length){uint8_t i;uint32_t Number1;if (Number >= 0){OLED_ShowChar(Line, Column, \'+\');Number1 = Number;}else{OLED_ShowChar(Line, Column, \'-\');Number1 = -Number;}for (i = 0; i < Length; i++){OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + \'0\');}}/** * @brief OLED显示数字(十六进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFFFFFF * @param Length 要显示数字的长度,范围:1~8 * @retval 无 */void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length){uint8_t i, SingleNumber;for (i = 0; i < Length; i++){SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;if (SingleNumber < 10){OLED_ShowChar(Line, Column + i, SingleNumber + \'0\');}else{OLED_ShowChar(Line, Column + i, SingleNumber - 10 + \'A\');}}}/** * @brief OLED显示数字(二进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length){uint8_t i;for (i = 0; i < Length; i++){OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + \'0\');}}/** * @brief OLED初始化 * @param 无 * @retval 无 */// OLED清除单行void OLED_ClearLine(uint8_t Line){ if (Line > 4) return; // 根据实际OLED行数调整 uint8_t i; for (i = 0; i < 17; i++) { // 假设每行16字符 OLED_ShowChar(Line, i, \' \'); }}// 滚动显示函数 (在OLED.c中添加)void OLED_ScrollDisplay(char* str){ int len = strlen(str); // 如果字符串很短,直接显示 if (len <= 16) { OLED_ShowString(2, 1, str); OLED_ClearLine(3); return; } // 滚动显示长字符串 for (int start = 0; start <= len - 16; start++) { char temp[17]; strncpy(temp, &str[start], 16); temp[16] = \'\\0\'; OLED_ClearLine(2); OLED_ShowString(2, 1, temp); // 如果字符串很长,在第三行显示后续内容 if (len - start > 16) { int nextStart = start + 16; int remain = len - nextStart; int displayLen = remain > 16 ? 16 : remain; strncpy(temp, &str[nextStart], displayLen); temp[displayLen] = \'\\0\'; OLED_ShowString(3, 1, temp); } else { OLED_ClearLine(3); } Delay_ms(150); // 滚动速度 }}void OLED_Init(void){uint32_t i, j;for (i = 0; i < 1000; i++)//上电延时{for (j = 0; j < 1000; j++);}OLED_I2C_Init();//端口初始化OLED_WriteCommand(0xAE);//关闭显示OLED_WriteCommand(0xD5);//设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8);//设置多路复用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3);//设置显示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40);//设置显示开始行OLED_WriteCommand(0xA1);//设置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8);//设置上下方向,0xC8正常 0xC0上下反置OLED_WriteCommand(0xDA);//设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81);//设置对比度控制OLED_WriteCommand(0xCF);OLED_WriteCommand(0xD9);//设置预充电周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB);//设置VCOMH取消选择级别OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4);//设置整个显示打开/关闭OLED_WriteCommand(0xA6);//设置正常/倒转显示OLED_WriteCommand(0x8D);//设置充电泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF);//开启显示OLED_Clear();//OLED清屏}
main.c
#include \"stm32f10x.h\" // Device header#include \"Delay.h\"#include \"OLED.h\"#include \"Serial.h\"#include \"Delay.h\"#include \"string.h\"uint8_t RxData;//定义用于接收串口数据的变量,OLED相当于串口屏作用int main(void){/*模块初始化*/OLED_Init();//OLED初始化/*显示静态字符串*/OLED_ShowString(1, 1, \"RxData:\");/*串口初始化*/Serial_Init();//串口初始化 Serial_SendString(\"AT+CIPMUX=1\\r\\n\"); Delay_ms(1000); //Serial_SendString(\"\"); Serial_SendString(\"AT+CIPSERVER=1,8080\\r\\n\"); Delay_ms(1000); Serial_SendString(\"AT+CIPSEND=0,3\\r\\n\"); Delay_ms(1000); Serial_SendString(\"HZY\\r\\n\"); char displayStr[64];//存储收到的字符串 char lenStr[64];//实际收到的字符串长度 Serial_ClearBuffer(); char displayLine[17]={0};//分段存储的字符串while (1){if (Serial_GetRxString(displayStr, sizeof(displayStr))) { // 收到新消息清空显示区域 OLED_ClearLine(2); OLED_ClearLine(3); OLED_ClearLine(4); uint8_t len = strlen(displayStr); sprintf(lenStr, \"%d\", len);//测试WiFi模块返回的字符串长度 OLED_ShowString(1, 10, lenStr); // 逐行显示字符串 for (int i = 0; i < 3; i++) { // 最多显示3行(第2-4行) int start = i * 16; // 每行16字符 if (start < len) { // 计算本行要显示的字符数 int copyLen = (len - start) > 16 ? 16 : (len - start); strncpy(displayLine, &displayStr[start], copyLen);// 复制本行内容到对应段 displayLine[copyLen] = \'\\0\'; // 确保结束符 // 在OLED上显示 OLED_ShowString(i + 2, 1, displayLine); } } } }}
连接上WiFi,我们可以看到屏幕上显示连接成功,如果没成功则按下单片机的复位键,打开手机上的网络调试工具,链接加入TCP服务端。按下复位键,我们可以收到自己的姓名了(当然,代码里发送部分写的我的姓名)。
连接成功:
再次按下复位键,姓名就发送过来了:
手机尝试发送信息给单片机(要发两次才接收得到,至于为什么大家自己想想):
发送hello,stm32,和较长的一个字符串,比如学号3232052052421(我的学号)作为测试。
效果如下: