模块1:蓝牙模块
蓝牙模块广泛应用在各种电子器件,比如手机、蓝牙耳机/音箱、蓝牙手环、扫地机器人,等等。大家在学嵌入式的时候,玩过的智能家居、智能小车、无人机,都有蓝牙模块的应用。
所以,蓝牙模块的学习势在必行。 蓝牙模块的学习其实也没大家想的那么难,只需要你玩好串口就行了,再加上会一些 AT 指令,你就可以称得上蓝牙高手了。但关于蓝牙协议栈,那学起来估计没一年半载下不来。
蓝牙模块介绍
现在市面上流行的蓝牙模块有很多,例如广州汇承(HC)公司的蓝牙模块应用非常的广泛,它们公司生产的 HC 系列的蓝牙模块如下图所示:
型号 | 主/从机 | 通信协议 | 工作频段 | 通信距离 | 嵌入方式 | 空中速率 | 尺寸 |
---|---|---|---|---|---|---|---|
HC-02 | 从机 | 蓝牙2.0 / 蓝牙4.0 | 2.4G | 10米 | 贴片 | 2Mbps | 26.9 * 13 * 2 mm |
HC-04 | 从机 | 蓝牙2.0 / 蓝牙4.0 | 2.4G | 10米 | 贴片 | 60KB/s | 18.5 * 13 * 2 mm |
HC-05 | 主从机一体 | 蓝牙2.0 | 2.4G | 10米 | 贴片 | 2Mbps | 27.0 * 13 * 2 mm |
HC-06 | 主从机一体 | 蓝牙2.0 | 2.4G | 10米 | 贴片 | 2Mbps | 27.0 * 13 * 2 mm |
HC-08 | 主从机一体 | 蓝牙4.0 | 2.4G | 80米 | 贴片 / 焊接 | 1Mbps | 26.9 * 13 * 2 mm |
HC-09 | 主从机一体 | 蓝牙4.0 | 2.4G | 60米 | 贴片 / 焊接 | 3KB/s | 18.5 * 13 * 2 mm |
HC-42 | 主从机一体 | 蓝牙5.0 | 2.4G | 40米 | 贴片 / 焊接 | 1Mbps / 2Mbps | 26.9 * 13 * 2 mm |
这些蓝牙模块,在主/机、工作频段、通信距离、空中速率等方面都存在差异,大家可以根据自己的业务需求进行选择。HC-08 是一款非常适合入门的蓝牙模块,本文就是以 HC-08 作为对象,介绍蓝牙模块的玩法。
HC-08 采用 TI CC25540 芯片方案,蓝牙 BLE4.0 主从一体,通过 BLE 的软件连接,传输速率 1Mbps ,传输距离 80m ,低功耗,详细参数如下:
主/从机模式
-
主机模式
当蓝牙模块处于主机模式的时候,可以与一个从机进行连接。在此模式下可以对周围设备进行搜索并选择需要连接的从机进行连接。理论上,一个蓝牙主端设备,可同时与 7 个蓝牙从端设备进行通讯。
一个具备蓝牙通讯功能的设备,可以在两个角色之间进行切换。比如:平时工作在从机模式,等待其它主机来连接;在需要时,可转换为主机模式,向其它设备发起连接。一个蓝牙设备以主机模式发起连接时,需要知道对方的蓝牙地址,配对密码等信息,配对完成之后,可直接发起连接。
-
从机模式
当蓝牙模块处于从机模式的时候,只能被主机搜索,不能主动搜索。从机与主机连接以后,也可以和主机进行发送和接收数据。
两种工作模式有什么区别呢?
主机是指能够搜索别人并主动建立连接的一方,从机则不能主动建立连接,只能等待主机连接自己。
- 如何进入从机模式?
HC-08 上电之后,默认情况下就是从机模式。如果需要手动配置,可以使用 AT+ROLE=S
指令。
- 如何进入主机模式?
我们需要通过 AT 指令 AT+ROLE=M
来设置蓝牙模块为主机模式。
引脚介绍
HC-08 蓝牙模块是通过串口与单片机进行通信,这个模块既可以作为主机也可以作为从机(通过 AT 指令配置)。有些蓝牙模块不支持主机(如 HC-02 、HC-04),所以在使用时需要注意区分。HC-08 蓝牙模块实物图如下所示:
STATE | 状态输出引脚。未连接时,则为低电平。连接成功时,则为高电平。可以在程序中作指示引脚使用; |
RXD | 串口接收引脚。接单片机的 TX 引脚(如果是5V MCU,需串联一个 220R 电阻); |
TXD | 串口发送引脚。接单片机的 RX 引脚; |
GND | 接地电源; |
VCC | 输入 3.2~6V 的电源(注意,上面一层邮票口的模块不能接 5V 的电源,需要底板降压至 3.3V) |
KEY | 主机用于清除配对的从机地址记忆(需要拉高电平 200ms 以上)。 |
- 引脚接线
正常通信下,只需接 RXD、TXD、GND、VCC 四条线就够了。在硬件接线的时候蓝牙模块的 TXD 要和单片机的 RXD 相连接,蓝牙模块的 RXD 要和单片机的 TXD 相连接,也就是所谓的「交叉接线」。
蓝牙模块上还有一个 LED灯和一个小按键 (按键控制着引脚 KEY )。默认情况下,当 LED灯闪烁时表示蓝牙模块当前为从机,正在等待连接。而长亮的时候就代表已经有主机连接上该模块,可以正常进行透传通讯了。
当按键按下后,主机将清除已被记录的从机地址。另外,也可使用 AT+CLEAR
指令,实现「主机清除已记录的从机地址」的功能。
蓝牙模块的AT指令
- 什么是AT指令?
答:在嵌入式开发中,经常是使用 AT 命令去控制各种通讯模块,比如 WiFi 模块、蓝牙模块、GPRS 模块等等。一般就是主芯片通过硬件接口(比如串口、SPI)发送 AT 指令给通讯模块,模块接收到数据之后回应响应的数据。
- 常用的AT指令
类型 | 格式 | 功能 |
---|---|---|
测试指令 | AT + < X > = ? | 测试终端设备或调制解调器的某些功能是否正常工作 |
查询指令 | AT + < X > ? | 返回参数的当前值 |
设置指令 | AT + < X > = < ... > | 设置用户自定义的参数值 |
执行指令 | AT + < X > | 用于控制终端设备的具体操作 |
序号 | AT指令(小写 x 表示参数) | 作用 | 默认状态 | 主/从生效 |
---|---|---|---|---|
1 | AT | 检测串口是否正常工作 | - | M/S |
2 | AT+RX | 查看模块基本参数 | - | M/S |
3 | AT+DEFAULT | 恢复出厂设置 | - | M/S |
4 | AT+RESET | 模块重启 | - | M/S |
5 | AT+VERSION | 获取模块版本、 日期 | - | M/S |
6 | AT+ROLE=x | 主/从角色切换 | S | M/S |
7 | AT+NAME=xxx | 修改蓝牙名称 | HC-08 | M/S |
8 | AT+ADDR=xxxxxxxxxxxx | 修改蓝牙地址 | 硬件地址 | M/S |
9 | AT+RFPM=x | 更改无线射频功率 | 0(4dBm) | M/S |
10 | AT+BAUD=xx,y | 修改串口波特率 | 9600,N | M/S |
11 | AT+CONT=x | 是否可连接 | 0(可连) | M/S |
12 | AT+AVDA=xxx | 更改广播数据 | - | S |
13 | AT+MODE=x | 更改功耗模式 | 0 | S |
14 | AT+AINT=xx | 更改广播间隔 | 320 | M/S |
15 | AT+CINT=xx,yy | 更改连接间隔 | 6,12 | M/S |
16 | AT+CTOUT=xx | 更改连接超时时间 | 200 | M/S |
17 | AT+CLEAR | 主机清除已记录的从机地址 | - | M |
18 | AT+LED=x | LED 开/关 | 1 | M/S |
19 | AT+LUUID=xxxx | 搜索 UUID | FFF0 | M/S |
20 | AT+SUUID=xxxx | 服务 UUID | FFE0 | M/S |
21 | AT+TUUID=xxxx | 透传数据 UUID | FFE1 | M/S |
22 | AT+AUST=x | 设置自动进入睡眠的时间 | 20 | S |
常用AT指令说明
请注意,只有当蓝牙模块未连接上主/从机,通过串口发送的数据才会被识别为
AT
指令。否则一旦连接上主/从机,则发送的字符串则被视为普通数据,直接透传给对方。
- 测试指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT | OK | 测试指令 | 最基础的测试指令 |
当模块连接上 MCU 之后,我们不知道模块是否连接到位、是否有虚连、模块是否正常工作,我们可以发送 AT
这条指令进行测试,如果接收到 OK
响应,则代表模块一切正常,可以进行后续的操作了。
- 模块重启指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+RESET | OK | 重启蓝牙模块 | 蓝牙模块会自动重启,重启 200ms 后可执行新的操作 |
- 恢复出厂设置指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+DEFAULT | OK | 恢复出厂设置 | 注:不会清除主机已记录的从机地址!若要清除,请在未连线状态下使用 AT+CLEAR 指令进行清除。 蓝牙模块会自动重启,重启 200ms 后可进行新的操作。 |
- 更改功耗模式指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+MODE=? | 0/1/2 | 获取当前功耗模式 | 获取当前功耗模式 |
AT+MODE=0 | OK | 更改功耗模式(仅限从机) | 全速功耗模式(出厂默认) |
AT+MODE=1 | OK | 更改功耗模式(仅限从机) | 一级节能模式 |
AT+MODE=2 | OK | 更改功耗模式(仅限从机) | 二级节能模式(睡眠模式) |
节能模式说明:
- 一级节能模式是模块最主要的低功耗模式,可为透传提供低功耗待机,也可以作为低功耗的广播数据;
- 二级节能模式是睡眠模式,在睡眠下时不可发现、不可连接,串口唤醒后可发现、可连接。
两种节能模式都可以通过串口发送 1 个字节以上的数据来唤醒,但唤醒后前面几个字节的数据可能会乱码。
- 修改模块角色指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+ROLE=? | Master/Slave | 获取当前模块的主从机状态 | 获取当前的蓝牙模块为主机/从机模式 |
AT+ROLE=M | Master | 设置为主机模式 | 设置后模块将自动重启,重启 200ms 后可进行新的操作 |
AT+ROLE=S | Slave | 设置为从机模式 | 设置后模块将自动重启,重启 200ms 后可进行新的操作 |
- 设置 LED 开/关指令
指令 |
响应 |
功能 |
说明 |
---|---|---|---|
AT+LED=? |
OK+LED=? |
查询LED工作模式 |
查询LED当前的工作模式 |
AT+LED=0 |
OK+LED=0 |
设置LED工作模式 |
设置LED的工作模式为关闭 |
AT+LED=1 |
OK+LED=1 |
设置LED工作模式 |
设置LED的工作模式为打开 |
使用指令关闭LED后再打开,需要重启蓝牙模块才能生效
- 修改蓝牙地址指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+ADDR=? | (当前蓝牙模块MAC地址) | 获取蓝牙模块MAC地址 | 地址必须为 12 位的 0~F 数字或大写字符,即 16 进制字符。 |
AT+ADDR=xxx | OKsetADDR | 修改蓝牙模块的MAC地址 | 建议不要修改模块的 MAC 地址,避免冲突 |
- 查看软件版本指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+VERSION | HC-08V3.1, 2017-07-07 | 获取软件版本和发布日期 | 获取软件版本和发布日期 |
- 查看当前基本参数
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+RX | Name:HC-08 | 查询模块的基本参数 | 蓝牙名是用户设定的名字 |
Role:Slave | 模块角色(主/从) | ||
Baud:9600,NONE | 串口波特率,校验位 | ||
Addr:xx,xx,xx,xx,xx,xx | 蓝牙地址 | ||
PIN :000000 | 蓝牙密码(密码无效) |
- 修改蓝牙名称指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+NAME=? | OK+NAME=HC-08 | 获取蓝牙当前名称 | 获取蓝牙模块的当前名称 |
AT+NAME=xxx | OKsetNAME | 设置蓝牙名称 | 设置蓝牙模块的名称 |
- 设置模块是否可连接指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+CONT=? | Connectable/Non-Connectable | 获取蓝牙模块当前是否可连接 | 设置可连接性,不可连接时主要用于广播数据 |
AT+CONT=0 | OK | 设置“可连接”成功 | 设置“可连接”成功 |
AT+CONT=1 | OK | 设置“不可连接”成功 | 设置“不可连接”成功 |
模式 | 主机 | 从机 |
---|---|---|
可连接 | 中心(Central)可连接,连线后进入普通透传模式 | 外设(Peripheral)可连接,连线后进入普通透传模式 |
不可连接 | 观察者(Observer)当前模块不能连接到其它模块或者设备,但是会自动扫描 HC-08 从机的广播数据包,固定 2s 刷新一次 | 广播者(Broadcaster)不会和主机连接,但可以结合低功耗模式 1,实现广播数据包发送 |
- 设置连接超时指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+CTOUT=? | OK+CTOUT=200(默认) | 查询连接超时时间 | 查询连接超时时间,单位 10ms,范围 10~3200(100ms~32s)。默认为200 |
AT+CTOUT=100 | OK+CTOUT=100 | 设置连接超时时间 | 设置连接超时时间为100ms |
- 设置自动进入睡眠时间指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+AUST=? | OK+AUST=20 | 查询自动睡眠定时 | (默认 20s,该时间越小则越省电)设置的时间范围为:1s~300s 。在低功耗模式下,激活模块后如无操作,x 秒后将返回用户原先所设置的低功耗模式 |
AT+AUST=100 | OK+AUST=100 | 设置自动睡眠时间 | 设置自动睡眠时间为100s |
- 修改串口波特率指令
指令 | 响应 | 功能 | 说明 |
---|---|---|---|
AT+BAUD=xx,y | xx, y | 设置串口 | 设置串口,参数如下表格 |
AT+BAUD=? | 9600,NONE | 查询串口设置 | 查询串口设置 |
xx 为串口波特率,y 为校验位
参数 | 串口波特率(xx) | 参数 | 校验位(y) |
---|---|---|---|
1200 | 1200bps | N | 无校验NONE |
2400 | 2400bps | E | 偶校验EVEN |
4800 | 4800bps | O | 奇校验ODD |
9600 | 9600bps(默认波特率) | ||
19200 | 19200bps | ||
38400 | 38400bps | ||
57600 | 57600bps | ||
115200 | 115200bps |
通信示意图
小实验:蓝牙模块收发数据
实验目的
蓝牙模块连接到STM32板子上的UART2口上,将其与手机上的CH蓝牙助手连接,实现二者之间的收发数据。将蓝牙模块接收到的数据通过USB转TTL模块的串口通信打印到PC端。
硬件清单
HC-08蓝牙模块、STM32开发板、USB转TTL
硬件接线
HC-08 | STM32 | USB转TTL |
---|---|---|
VCC | 3.3V | VCC |
RXD | A2 | |
TXD | A3 | |
GND | GND | GND |
A10 | TXD | |
A9 | RXD |
文件代码
- bluetooth.c文件代码
#include "sys.h"
#include "bluetooth.h"
#include "string.h"
#include "stdarg.h"
UART_HandleTypeDef uart2_handle = {0}; /* UART2句柄 */
uint8_t uart2_rx_buf[UART2_RX_BUF_SIZE]; /* UART2接收缓冲区 */
uint16_t uart2_rx_len = 0; /* UART2接收字符长度 */
//**
// * @brief 重定义fputc函数
// * @note printf函数最终会通过调用fputc输出字符串到串口
// */
//int fputc(int ch, FILE *f)
//{
// while ((USART2->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
// USART2->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
// return ch;
//}
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void bluetooth_init(uint32_t baudrate)
{
/*UART2 初始化设置*/
uart2_handle.Instance = USART2; /* USART1 */
uart2_handle.Init.BaudRate = baudrate; /* 波特率 */
uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
uart2_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
uart2_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
uart2_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&uart2_handle); /* HAL_UART_Init()会使能UART1 */
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
//void HAL_UART_MspInit(UART_HandleTypeDef *huart)
//{
//}
/**
* @brief UART2接收缓冲区清除
* @param 无
* @retval 无
*/
void uart2_rx_clear(void)
{
memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf)); /* 清空接收缓冲区 */
uart2_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口2中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(uart2_rx_len >= sizeof(uart2_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
uart2_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000); /* 接收一个字符 */
uart2_rx_buf[uart2_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
printf("btrecv: %s\r\n", uart2_rx_buf); /* 将接收到的数据打印出来 */
uart2_rx_clear();
__HAL_UART_CLEAR_IDLEFLAG(&uart2_handle); /* 清除UART总线空闲中断 */
}
}
/**
* @brief 蓝牙模块发送消息
* @note 蓝牙通过透传,将数据发送到手机上
* @param 传输的数据数据,和数组长度。
* @retval 无
*/
//void bt_send(char *send_buf, uint8_t size){
//
// HAL_UART_Transmit(&uart2_handle,(uint8_t *)send_buf,size,100);
//}
/**
* @brief 蓝牙模块发送消息
* @note 蓝牙通过透传,将数据发送到手机上
* @param 传入不定长的数据长度。
* @retval 无
*/
void bt_send(char * format, ...){
uint8_t send_buf[128] = {0};
va_list arg;
va_start(arg,format);
vsprintf((char*)send_buf,format,arg);
va_end(arg);
HAL_UART_Transmit(&uart2_handle,send_buf,sizeof(send_buf),100);
}
- bluetooth.h文件代码
#ifndef __BLUETOOTH_H__
#define __BLUETOOTH_H__
#include "stdio.h"
#include "sys.h"
/* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE 128
#define UART2_TX_BUF_SIZE 64
void bluetooth_init(uint32_t bound); /* 串口初始化函数 */
//void bt_send(char *sendbuf, uint8_t size); /* 蓝牙模块向手机端发送数据 */
void bt_send(char * format, ...); /* 蓝牙模块向手机端发送不定长数据 */
#endif
- uart1.c文件代码
#include "sys.h"
#include "uart1.h"
#include "string.h"
UART_HandleTypeDef uart1_handle; /* UART1句柄 */
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; /* UART1接收缓冲区 */
uint16_t uart1_rx_len = 0; /* UART1接收字符长度 */
/**
* @brief 重定义fputc函数
* @note printf函数最终会通过调用fputc输出字符串到串口
*/
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void uart1_init(uint32_t baudrate)
{
/*UART1 初始化设置*/
uart1_handle.Instance = USART1; /* USART1 */
uart1_handle.Init.BaudRate = baudrate; /* 波特率 */
uart1_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
uart1_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
uart1_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
uart1_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&uart1_handle); /* HAL_UART_Init()会使能UART1 */
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef gpio_init_struct;
if (huart->Instance == USART1) /* 如果是串口1,进行串口1 MSP初始化 */
{
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */
__HAL_RCC_USART1_CLK_ENABLE(); /* 使能串口时钟 */
gpio_init_struct.Pin = GPIO_PIN_9; /* 串口发送引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_10; /* 串口RX脚 模式设置 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */
HAL_NVIC_EnableIRQ(USART1_IRQn); /* 使能USART1中断通道 */
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART1接收中断 */
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART1总线空闲中断 */
}
else if (huart->Instance == USART2){ /* 如果是串口1,进行串口1 MSP初始化 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */
__HAL_RCC_USART2_CLK_ENABLE(); /* 使能串口时钟 */
gpio_init_struct.Pin = GPIO_PIN_2; /* 串口发送引脚号 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
gpio_init_struct.Pin = GPIO_PIN_3; /* 串口RX脚 模式设置 */
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */
HAL_NVIC_EnableIRQ(USART2_IRQn); /* 使能USART1中断通道 */
HAL_NVIC_SetPriority(USART2_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART2接收中断 */
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART2总线空闲中断 */
}
}
/**
* @brief UART1接收缓冲区清除
* @param 无
* @retval 无
*/
void uart1_rx_clear(void)
{
memset(uart1_rx_buf, 0, sizeof(uart1_rx_buf)); /* 清空接收缓冲区 */
uart1_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口1中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(uart1_rx_len >= sizeof(uart1_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
uart1_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&uart1_handle, &receive_data, 1, 1000); /* 接收一个字符 */
uart1_rx_buf[uart1_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&uart1_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
printf("recv: %s\r\n", uart1_rx_buf); /* 将接收到的数据打印出来 */
uart1_rx_clear();
__HAL_UART_CLEAR_IDLEFLAG(&uart1_handle); /* 清除UART总线空闲中断 */
}
}
- uart1.h文件代码
#ifndef __USART_H__
#define __USART_H__
#include "stdio.h"
#include "sys.h"
/* 错误代码 */
#define UART_EOK 0 /* 没有错误 */
#define UART_ERROR 1 /* 通用错误 */
#define UART_ETIMEOUT 2 /* 超时错误 */
#define UART_EINVAL 3 /* 参数错误 */
/* UART收发缓冲大小 */
#define UART1_RX_BUF_SIZE 128
#define UART1_TX_BUF_SIZE 64
void uart1_init(uint32_t bound); /* 串口初始化函数 */
#endif
- mian.c文件代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "bluetooth.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* LED初始化 */
uart1_init(115200);
printf("hello,world");
bluetooth_init(115200);
uint8_t i = 0;
while(1)
{
bt_send("hello,world %d\n",i++);
delay_ms(2000);
led1_on();
led2_off();
delay_ms(500);
led1_off();
led2_on();
delay_ms(500);
}
}
问题和注意事项:
usart中Msp函数只能声明一次;
关于函数传入不定长的的字符数组;
只能重写一次fput函数;
项目:蓝牙遥控插座
项目需求
硬件清单
蓝牙模块、继电器、杜邦线、开发板、ST-Link、USB转TTL
硬件接线
STM32
|
蓝牙模块
|
继电器
|
PA2
|
RX
| |
PA3
|
TX
| |
PB6
|
I/O
| |
3V3
|
VCC
| |
5V
|
VCC
| |
GND
|
GND
|
GND
|
项目框图
文件代码
- bluetooth.c文件代码
#include "sys.h"
#include "bluetooth.h"
#include "string.h"
#include "stdarg.h"
#include "pugle.h"
uint8_t pugle_flag = False;
UART_HandleTypeDef uart2_handle = {0}; /* UART2句柄 */
uint8_t uart2_rx_buf[UART2_RX_BUF_SIZE]; /* UART2接收缓冲区 */
uint16_t uart2_rx_len = 0; /* UART2接收字符长度 */
///**
// * @brief 重定义fputc函数
// * @note printf函数最终会通过调用fputc输出字符串到串口
// */
//int fputc(int ch, FILE *f)
//{
// while ((USART2->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
// USART2->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
// return ch;
//}
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void bluetooth_init(uint32_t baudrate)
{
/*UART2 初始化设置*/
uart2_handle.Instance = USART2; /* USART1 */
uart2_handle.Init.BaudRate = baudrate; /* 波特率 */
uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
uart2_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
uart2_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
uart2_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&uart2_handle); /* HAL_UART_Init()会使能UART1 */
}
/**
* @brief UART底层初始化函数
* @param huart: UART句柄类型指针
* @note 此函数会被HAL_UART_Init()调用
* 完成时钟使能,引脚配置,中断配置
* @retval 无
*/
//void HAL_UART_MspInit(UART_HandleTypeDef *huart)
//{
//}
/**
* @brief UART2接收缓冲区清除
* @param 无
* @retval 无
*/
void uart2_rx_clear(void)
{
memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf)); /* 清空接收缓冲区 */
uart2_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口2中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(uart2_rx_len >= sizeof(uart2_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
uart2_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000); /* 接收一个字符 */
uart2_rx_buf[uart2_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
printf("btrecv: %s\r\n", uart2_rx_buf); /* 将接收到的数据通过usart1串口打印出来 */
if(strstr((char *)uart2_rx_buf,"on") != NULL)
// pugle_flag = True;
pugle_on();
else if(strstr((char*)uart2_rx_buf,"off") != NULL)
// pugle_flag = False;
pugle_off();
uart2_rx_clear();
__HAL_UART_CLEAR_IDLEFLAG(&uart2_handle); /* 清除UART总线空闲中断 */
}
}
/**
* @brief 蓝牙模块发送消息
* @note 蓝牙通过透传,将数据发送到手机上
* @param 传输的数据数据,和数组长度。
* @retval 无
*/
//void bt_send(char *send_buf, uint8_t size){
//
// HAL_UART_Transmit(&uart2_handle,(uint8_t *)send_buf,size,100);
//}
/**
* @brief 蓝牙模块发送消息
* @note 蓝牙通过透传,将数据发送到手机上
* @param 传入不定长的数据长度。
* @retval 无
*/
void bt_send(char * format, ...){
uint8_t send_buf[128] = {0};
va_list arg;
va_start(arg,format);
vsprintf((char*)send_buf,format,arg);
va_end(arg);
HAL_UART_Transmit(&uart2_handle,send_buf,sizeof(send_buf),100);
}
//uint8_t pugle_flag_get(void){
// uint8_t temp = pugle_flag;
// pugle_flag = False;
// return temp;
//}
//void pugle_flag_set(uint8_t val){
// pugle_flag = val;
//}
- bluetooth.h文件代码
#ifndef __BLUETOOTH_H__
#define __BLUETOOTH_H__
#include "stdio.h"
#include "sys.h"
#define False 0
#define True 1
/* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE 128
#define UART2_TX_BUF_SIZE 64
void bluetooth_init(uint32_t bound); /* 串口初始化函数 */
//void bt_send(char *sendbuf, uint8_t size); /* 蓝牙模块向手机端发送数据 */
void bt_send(char * format, ...); /* 蓝牙模块向手机端发送不定长数据 */
uint8_t pugle_flag_get(void);
void pugle_flag_set(uint8_t val);
#endif
- 其中关于打印串口代码uart1.c文件代码和uart1.h文件代码,参考串口那篇文章。
- 继电器的代码,参考之前电动车报警项目文章代码。
- main.c文件代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "bluetooth.h"
#include "pugle.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* LED初始化 */
uart1_init(115200);
printf("hello,world");
bluetooth_init(115200);
pugle_init();
// uint8_t i = 0;
while(1)
{
// bt_send("hello,world %d\n",i++);
// delay_ms(2000);
//
// led1_on();
// led2_off();
// delay_ms(500);
// led1_off();
// led2_on();
// delay_ms(500);
}
}
遇到的问题和注意事项
- 如何在主函数中利用中断标志位来进行控制开关的通断,而不是在中断服务函数中直接控制开关的通断
- 传入不定长形参的格式。
模块2:ESP8266
在嵌入式开发中,无线通信的方式有很多,其中 WIFI 是绕不开的话题。说到 WIFI 通信,就不得不提 ESP8266了。
ESP8266 是一款高性能的 WIFI 串口模块,实现透明传输。只要有一定的串口知识,不需要知道 WIFI 原理就可以上手,在业内应用广泛。
ESP8266介绍
ESP8266 是一个非常强大的 WIFI 模块,可以利用串口与单片机进行通讯,从而编程实现控制 ESP8266。利用 ESP8266 可以访问一些 API,获取天气信息或者完成网络授时,也可以连接云平台进行开发。不过因为是串口传输,速度较慢,不能用来传输图像、视频这些大容量的数据,但是传些传感器数据还是绰绰有余的。
我们常说的 ESP8266 是指 ESP8266 WIFI 模块,它是物联网和嵌入式开发常用的模块,其中 ESP8266 是 WIFI 模块的芯片型号。
ESP8266 有 ESP-01/01S/07/07S/12E/12F/12S 等规格,还有正点原子自研的 ATK-ESP8266 (修改了固件及模组引脚)。
本文用到的是 ESP-01S,长下面这样。这种模组非常便宜,某宝几块钱一个,建议可以买几个囤着。
三种调试方式
- USB 转 TTL 工具
USB转TTL模块 | ESP8266 |
---|---|
3.3V | 3.3V |
TXD | RX |
RXD | TX |
GND | GND |
注意:
3.3V 相接后可能无法启动 ESP8266,这是因为 USB 转 TTL 模组的 3.3V 并没有真的达到 3.3V,莫慌,直接将 ESP8266 的3.3V 引脚接入 USB 转 TTL 的 5V 引脚,如果ESP8266 会发烫到无法触摸就拔掉,如果温度你手指还能接受,那就没问题,ESP8266 还是没那么脆弱的。
- USB 转 ESP8266WIFI 模块
- 单片机调试
这种就是在项目中最常用的方式了。需要将 ESP8266 接到单片机的任意一个串口,然后再编写代码驱动 ESP8266 ,实现各种业务逻辑。
接好线之后,然后打开串口调试助手发送 AT(并且还要敲一个回车),ESP8266 回复 OK,就是正常启动了。
ESP8266工作模式
ESP8266 支持 STA、AP、AP+STA 三种工作模式。
- STA 模式(Station)——类似手机、平板连接的设备
一般用于远距离传输。ESP8266 通过路由器连接互联网,终端设备通过互联网实现对设备的远程控制。简单来说,此时的 ESP8266 可以当作是一个客户端,可以向服务端进行数据的下载与传输。这就类似于,手机/平板/笔记本(客户端)可以通过 WIFI 连接到路由器进行上网。
- AP 模式(Access Point)——相当于路由器
一般用于近距离传输。ESP8266 作为热点,提供无线接入服务、数据访问,一般的无线路由/网桥工作在 AP 模式下,最多支持 4 台设备接入。简单来说,此时的 ESP8266 可以当作是一个服务端。这就类似于,ESP8266 变身为一个路由器,然后手机/平板/笔记本可以通过 WIFI 连接到 ESP8266 进行上网。
- AP+STA 模式——既能当路器也可以当连接设备
两种模式的共存模式,可以通过互联网控制可实现无缝切换,方便操作。简单来说,此时的 ESP8266 可以当作是一个路由器既可以做服务端接收也可以当客户端连接路由器,进行联网传输和控制。
常用的AT指令和使用
AT指令 | 功能 |
---|---|
AT | 测试是否正常启动 |
AT+CWMODE=1 | 设置 STA 模式 |
AT+CWMODE=2 | 设置 AP 模式 |
AT+CWMODE=3 | 设置 AP+STA 模式 |
AT+RST | 重启生效 |
AT+CWSAP=”SSID”,”password”,1,4 | 设置 AP 参数:账号为SSID ,密码为password,通道号为 1,加密方式为:WPA_WPA2_PSK |
AT+CIPMUX=0 | 开启单连接(使用透传模式开启这个模式) |
AT+CIPMUX=1 | 开启多连接 |
AT+CIPSERVER=1,8080 | 开启 SERVER 模式,设置端口为 8080 |
AT+CIPSTART=“TCP”,"192.168.X.XXX”,8080 | 建立 TCP 连接到”192.168.X.XXX”,8080 |
AT+CIPSTART=“UDP”,“192.168.X.XXX”,8080 | 建立 UDP 连接到”192.168.X.XXX”,8080 |
AT+CIPCLOSE | 断开 TCP 连接 |
AT+CWQAP | 断开热点 |
AT+CIPSEND=n | 开始传输,n表示需要传输的字节数 |
AT+CIPSEND=0,n | 向 ID0 发送 n 字节数据包,n的值自己定 |
AT+CIPMODE=1 | 开启透传模式 |
AT+CIPSEND | 开始发送数据 |
AT+CIPMODE=0 | 退出透传(或发送+++) |
AT+CWJAP="SSID,“password” | 加入 WIFI 热点:SSID ,密码为:password |
AT+CIFSR | 查询 ESP8266 的 IP 地址 |
AT+CIPSTA? | 查询 ESP8266 的 IP 、网关地址和子网掩码 |
- 将USB转TTL和ESP8266的模块相接:将USB转TTL插入电脑
USB转TTL模块 | ESP8266 |
---|---|
3.3V | 3.3V |
TXD | RX |
RXD | TX |
GND | GND |
- 测试模块是否正常(AT)
发送
AT
,ESP8266 回复 OK,就是正常启动了。
1. 使用ESP8266模块作为客户端和TCP服务器建立连接
入网设置
- 设置工作模式---STA模式
AT+CWMODE=1 // 1. 是station(设备)模式 2.是AP(路由)模式 3.是双模OK //结果
- 连接路由器
AT+CWJAP="OPPO Find X7","xys123456" //指令WIFI CONNECTED //结果WIFI GOT IP //结果
- 设置单路链接模式(透传只能使用此模式)
AT+CIPMUX=0OK
- 查询IP地址
AT+CIFSR //指令
配置TCP服务端
- 使用网络助手,设立TCP服务器
- 连接服务器
AT+CIPSTART="TCP","192.168.1.18",8080 //指令,注意双引号逗号都要半角(英文)输入
CONNECT //结果:成功OK //结果:成功
发送数据
AT+CIPSEND=4 // 设置即将发送数据的长度 (这里是4个字节)
>abcd // 看到大于号后,输入消息,abcd,不要带回车Response :SEND OK //结果:成功//注意,这种情况下,每次发送前都要先发送AT+CIPSEND=长度 的指令,再发数据!
透传发送数据
AT+CIPMODE=1 //开启透传模式
Response :OKAT+CIPSEND //带回车Response: > //这个时候随意发送接收数据咯
//在透传发送数据过程中,若识别到单独的⼀包数据 “+++”,则退出透传发送
//1 配置成AP模式
AT+CWMODE=2
Response :OK
//2 使能多链接
AT+CIPMUX=1
Response :OK
//3 建立TCPServer
AT+CIPSERVER=1 // default port = 333
Response :OK
//4 发送数据
AT+CIPSEND=0,4 // 发送4个字节在连接0通道上
>abcd //输入数据,不带回车
Response :SEND OK
//5 接收数据
+IPD, 0, n: xxxxxxxxxx //+IPD是固定字符串 0是通道,n是数据长度,xxx是数据
//断开连接
AT+CIPCLOSE=0
Response :0, CLOSED OK
小实验1:编程实现ESP8266串口通讯功能
实验目的
利用两个USB转TTL的模块,一个接入作为串口1与电脑进行通讯,另一个模拟ESP8266接入的串口2,调试两个串口的收发功能是否正常。
硬件清单
2个USB转TTL的模块、ST-Link、开发板
硬件接线
STM32 | 串口1 | 串口2 |
3.3v | VCC | VCC |
GND | GND | GND |
PA9(TX1) | RX | |
PA10(RX1) | TX | |
PA2(TX2) | RX | |
PA3(RX2) | TX |
文件代码
esp8266.c文件代码
- 方式1:利用IDNE IE接收完成的中断服务函数实现
#include "sys.h"
#include "esp8266.h"
#include "string.h"
#include "delay.h"
#define ESP8266_OK 1
#define ESP8266_ERROR 0
#define rx_complete 3
#define rx_erro 4
uint8_t rx_flag = rx_erro;
UART_HandleTypeDef esp8266_handle; /* UART2句柄 */
uint8_t esp8266_rx_buf[UART2_RX_BUF_SIZE]; /* UART1接收缓冲区 */
uint16_t esp8266_rx_len = 0; /* esp8266接收字符长度 */
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void esp8266_uart_init(uint32_t baudrate)
{
/*UART2 初始化设置*/
esp8266_handle.Instance = USART2; /* USART1 */
esp8266_handle.Init.BaudRate = baudrate; /* 波特率 */
esp8266_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
esp8266_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
esp8266_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
esp8266_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
esp8266_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&esp8266_handle); /* HAL_UART_Init()会使能esp8266 */
}
/**
* @brief UART2接收缓冲区清除
* @param 无
* @retval 无
*/
void esp8266_rx_clear(void)
{
memset(esp8266_rx_buf, 0, sizeof(esp8266_rx_buf)); /* 清空接收缓冲区 */
esp8266_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口1中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(esp8266_rx_len >= sizeof(esp8266_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
esp8266_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&esp8266_handle, &receive_data, 1, 1000); /* 接收一个字符 */
esp8266_rx_buf[esp8266_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
// printf("esp_recv: %s\r\n", esp8266_rx_buf); /* 将接收到的数据打印出来 */
rx_flag = rx_complete;
// esp8266_rx_clear();
__HAL_UART_CLEAR_IDLEFLAG(&esp8266_handle); /* 清除UART总线空闲中断 */
}
}
//
void esp8266_init(uint32_t baudrate){
esp8266_uart_init(baudrate);
//esp8266的其他初始化
}
uint8_t esp8266_rx_flag(void){
uint8_t temp = rx_flag;
rx_flag = rx_erro;
return temp;
}
//定义一个函数,让ESP8266发送一个函数并返回一个数
uint8_t esp8266_send(char *cmd,char *res){
uint32_t time_out = 120;
HAL_UART_Transmit(&esp8266_handle,(uint8_t *)cmd,strlen(cmd),100);
while(time_out--)
{
if(esp8266_rx_flag() == rx_complete){
if(strstr((char *)esp8266_rx_buf,res) !=NULL)
return ESP8266_OK;
}
delay_ms(10);
}
esp8266_rx_clear();
return ESP8266_ERROR;
}
void esp8266_send_test(void){
if(esp8266_send("AT","OK") == ESP8266_OK)
printf("esp8266:%s\n",esp8266_rx_buf);
}
- 方式2:利用接收中断+延时时间来实现ESP8266接收和发送的功能
esp8266.h文件代码
#ifndef __ESP8266_H__
#define __ESP8266_H__
#include "stdio.h"
#include "sys.h"
/* 错误代码 */
#define UART_EOK 0 /* 没有错误 */
#define UART_ERROR 1 /* 通用错误 */
#define UART_ETIMEOUT 2 /* 超时错误 */
#define UART_EINVAL 3 /* 参数错误 */
/* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE 128
#define UART2_TX_BUF_SIZE 64
void esp8266_uart_init(uint32_t bound); /* 串口初始化函数 */
void esp8266_init(uint32_t baudrate);
void esp8266_send_test(void);
#endif
mian.c文件代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "esp8266.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* LED初始化 */
uart1_init(115200);
printf("hello,world\n");
esp8266_init(115200);
printf("你好、\n");
while(1)
{
esp8266_send_test();
delay_ms(50); //要进行延时操作
}
}
小实验2:利用代码实现ESP8266入网的模式
实验目的
编码的形式实现ESP8266联网的功能
硬件清单
ESP8266、ST-Link、开发板、杜邦线、USB转TTL
硬件接线
和实验1的接线一样
配置流程
文件代码
- esp8266.c文件代码
#include "sys.h"
#include "esp8266.h"
#include "string.h"
#include "delay.h"
#define ESP8266_OK 1
#define ESP8266_ERROR 0
#define rx_complete 3
#define rx_erro 4
uint8_t rx_flag = rx_erro;
UART_HandleTypeDef esp8266_handle; /* UART2句柄 */
uint8_t esp8266_rx_buf[UART2_RX_BUF_SIZE]; /* UART1接收缓冲区 */
uint16_t esp8266_rx_len = 0; /* esp8266接收字符长度 */
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void esp8266_uart_init(uint32_t baudrate)
{
/*UART2 初始化设置*/
esp8266_handle.Instance = USART2; /* USART1 */
esp8266_handle.Init.BaudRate = baudrate; /* 波特率 */
esp8266_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
esp8266_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
esp8266_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
esp8266_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
esp8266_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&esp8266_handle); /* HAL_UART_Init()会使能esp8266 */
}
/**
* @brief UART2接收缓冲区清除
* @param 无
* @retval 无
*/
void esp8266_rx_clear(void)
{
memset(esp8266_rx_buf, 0, sizeof(esp8266_rx_buf)); /* 清空接收缓冲区 */
esp8266_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口1中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(esp8266_rx_len >= sizeof(esp8266_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
esp8266_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&esp8266_handle, &receive_data, 1, 1000); /* 接收一个字符 */
esp8266_rx_buf[esp8266_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
// printf("esp_recv: %s\r\n", esp8266_rx_buf); /* 将接收到的数据打印出来 */
rx_flag = rx_complete;
__HAL_UART_CLEAR_IDLEFLAG(&esp8266_handle); /* 清除UART总线空闲中断 */
// esp8266_rx_clear();
}
}
/**
* @brief 接收缓冲区接收完成后的中断标志位
* @note
* @param 无
* @retval rx_erro 或 rx_complete
*/
uint8_t esp8266_rx_flag(void){
uint8_t temp = rx_flag;
rx_flag = rx_erro;
return temp;
}
/**
* @brief 声明函数ESP8266传入两个字符串,
* @note 函数里将第一个参数发送到接收缓冲区,判断接收完成后,与第二个参数进行比较是否存在。
* @param char *cmd,char *res
* @retval ESP8266_OK或ESP8266_ERROR
*/
uint8_t esp8266_send(char *cmd,char *res){
uint32_t time_out = 120;
HAL_UART_Transmit(&esp8266_handle,(uint8_t *)cmd,strlen(cmd),100);
while(time_out--)
{
if(esp8266_rx_flag() == rx_complete){
if(strstr((char *)esp8266_rx_buf,res) !=NULL)
return ESP8266_OK;
}
delay_ms(10);
}
esp8266_rx_clear();
return ESP8266_ERROR;
}
/**
* @brief 配置ESP8266入网步骤的四个函数
*
* @note 有四个步骤:1.测试模块是否正常AT; 2:设置工作模式:sta、ap、sta——ap
* 3:连接路由器; 4:设置单链接模式
* @param 不同函数的传入参数不同
* @retval ESP8266_OK
*/
uint8_t esp8266_at_test(void){
return esp8266_send("AT\r\n","OK");
}
uint8_t esp8266_set_mode(uint8_t mode){
switch (mode){
case ESP8266_STA_MODE:
return esp8266_send("AT+CWMODE=1\r\n","OK");
case ESP8266_AP_MODE:
return esp8266_send("AT+CWMODE=2\r\n","OK");
case ESP8266_STA_AP_MODE:
return esp8266_send("AT+CWMODE=3\r\n","OK");
default:
return UART_EINVAL;
}
}
uint8_t esp8266_jion_ip(char *ip ,char *key){
char cmd[68];
sprintf(cmd,"AT+CWJAP=\"%s\",\"%s\"\r\n",ip,key);
return esp8266_send(cmd,"WIFI GOT IP");
}
uint8_t esp8266_connection_mode(uint8_t mode){
char cmd[68];
sprintf(cmd,"AT+CIPMUX=%d\r\n",mode);
return esp8266_send(cmd,"OK");
}
/**
* @brief 初始化ESP8266函数和配置入网的步骤
* @note 一定要注意在while 循环中添加判断语句,否则会一直处于循环的状态
* @param baudrate
* @retval 无
*/
void esp8266_init(uint32_t baudrate){
printf("ESP8266初始化开始。。。\r\n");
esp8266_uart_init(baudrate);
//esp8266的其他初始化
printf("1.测试ESP8266是否正常。。。\r\n");
while(esp8266_at_test()!= ESP8266_OK)
delay_ms(500);
printf("2.ESP8266设置工作模式为STA。。。\r\n");
while(esp8266_set_mode(ESP8266_STA_MODE)!= ESP8266_OK)
delay_ms(500);
printf("3.ESP8266单路连接模式。。。\r\n");
while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION)!= ESP8266_OK)
delay_ms(500);
printf("ESP8266连接路由器无线。。。SSID : %s ,KEY : %s \r\n",WIFI_SSID,WIFI_KEY);
while(esp8266_jion_ip(WIFI_SSID ,WIFI_KEY)!= ESP8266_OK)
delay_ms(1500);
printf("4.ESP8266初始化完成。。。\r\n");
}
/**
* @brief 测试函数,在主函数中的while语句中调用,测试模块的收发是否正常
* @note 无
* @param 无
* @retval 无
*/
void esp8266_send_test(void){
if(esp8266_send("AT","OK") == ESP8266_OK)
printf("esp8266:%s\n",esp8266_rx_buf);
}
- esp8266.h文件代码
#ifndef __ESP8266_H__
#define __ESP8266_H__
#include "stdio.h"
#include "sys.h"
/* 错误代码 */
#define UART_EOK 0 /* 没有错误 */
#define UART_ERROR 1 /* 通用错误 */
#define UART_ETIMEOUT 2 /* 超时错误 */
#define UART_EINVAL 3 /* 参数错误 */
/* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE 128
#define UART2_TX_BUF_SIZE 64
/* 工作模式 */
#define ESP8266_STA_MODE 1
#define ESP8266_AP_MODE 2
#define ESP8266_STA_AP_MODE 3
/* 单链接和双链接的模式 */
#define ESP8266_SINGLE_CONNECTION 0
#define ESP8266_MULTI_CONNECTION 1
/* 无线名称和无线密码 */
#define WIFI_SSID "OPPO Find X7"
#define WIFI_KEY "xys123456"
void esp8266_uart_init(uint32_t bound); /* 串口初始化函数 */
void esp8266_init(uint32_t baudrate);
void esp8266_send_test(void);
#endif
- mian.c文件代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "esp8266.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* LED初始化 */
uart1_init(115200);
printf("hello,world\n");
esp8266_init(115200);
printf("你好、\n");
while(1)
{
// esp8266_send_test();
// delay_ms(50); //要进行延时操作
}
}
注意事项和遇到的问题:
- 将初始化串口2和配置esp8266入网流程的函数进行封装到esp8266的初始化函数中;
- 关于fprintf()函数的书写格式;
- 利用宏定时声明AT指令的模式;
- 在写AT指令时,一定要在后面加上\r\n;
- 在while循环函数中要进行条件判断,否则会一直处于循环的状态。
小实验3:编码实现ESP8266连接TCP服务器
实验目的
在实验2的基础上,进一步的配置,ESP8266联网之后,进行与TCP服务器连接
硬件清单
和上面实验保持一样
硬件接线
和上面实验保持一样
配置流程
1.2.1在pc端搭建TCP服务器(具体流程参考上面AT指令的使用)
文件代码
- esp8266.c文件代码
方式1:使用空闲中断函数来实现----这个代码有点问题
#include "sys.h"
#include "esp8266.h"
#include "string.h"
#include "delay.h"
#include "stdarg.h"
#define ESP8266_OK 1
#define ESP8266_ERROR 0
#define rx_complete 3
#define rx_erro 4
uint8_t rx_flag = rx_erro;
UART_HandleTypeDef esp8266_handle; /* UART2句柄 */
uint8_t esp8266_rx_buf[UART2_RX_BUF_SIZE]; /* UART1接收缓冲区 */
uint16_t esp8266_rx_len = 0; /* esp8266接收字符长度 */
uint8_t esp8266_tx_buf[UART2_TX_BUF_SIZE];
/**
* @brief 串口1初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void esp8266_uart_init(uint32_t baudrate)
{
/*UART2 初始化设置*/
esp8266_handle.Instance = USART2; /* USART1 */
esp8266_handle.Init.BaudRate = baudrate; /* 波特率 */
esp8266_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
esp8266_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
esp8266_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
esp8266_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
esp8266_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&esp8266_handle); /* HAL_UART_Init()会使能esp8266 */
}
/**
* @brief UART2接收缓冲区清除
* @param 无
* @retval 无
*/
void esp8266_rx_clear(void)
{
memset(esp8266_rx_buf, 0, sizeof(esp8266_rx_buf)); /* 清空接收缓冲区 */
esp8266_rx_len = 0; /* 接收计数器清零 */
}
/**
* @brief 串口1中断服务函数
* @note 在此使用接收中断及空闲中断,实现不定长数据收发
* @param 无
* @retval 无
*/
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */
if(esp8266_rx_len >= sizeof(esp8266_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */
esp8266_rx_len = 0; /* 则将接收计数器清零 */
HAL_UART_Receive(&esp8266_handle, &receive_data, 1, 1000); /* 接收一个字符 */
esp8266_rx_buf[esp8266_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */
}
if (__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */
{
// printf("esp_recv: %s\r\n", esp8266_rx_buf); /* 将接收到的数据打印出来 */
rx_flag = rx_complete;
__HAL_UART_CLEAR_IDLEFLAG(&esp8266_handle); /* 清除UART总线空闲中断 */
// esp8266_rx_clear();
}
}
/**
* @brief 接收缓冲区接收完成后的中断标志位
* @note
* @param 无
* @retval rx_erro 或 rx_complete
*/
uint8_t esp8266_rx_flag(void){
uint8_t temp = rx_flag;
rx_flag = rx_erro;
return temp;
}
void esp8266_send_data(char *fmt, ...)
{
va_list ap;
uint16_t len;
va_start(ap, fmt);
vsprintf((char *)esp8266_tx_buf, fmt, ap);
va_end(ap);
len = strlen((const char *)esp8266_tx_buf);
HAL_UART_Transmit(&esp8266_handle, esp8266_tx_buf, len, 100);
}
/**
* @brief 声明函数ESP8266传入两个字符串,
* @note 函数里将第一个参数发送到接收缓冲区,判断接收完成后,与第二个参数进行比较是否存在。
* @param char *cmd,char *res
* @retval ESP8266_OK或ESP8266_ERROR
*/
uint8_t esp8266_send(char *cmd,char *res){
uint32_t time_out = 120;
HAL_UART_Transmit(&esp8266_handle,(uint8_t *)cmd,strlen(cmd),100);
while(time_out--)
{
if(esp8266_rx_flag() == rx_complete){
if(strstr((char *)esp8266_rx_buf,res) !=NULL)
return ESP8266_OK;
}
delay_ms(10);
}
esp8266_rx_clear();
return ESP8266_ERROR;
}
/**
* @brief 配置ESP8266入网步骤的四个函数
*
* @note 有四个步骤:1.测试模块是否正常AT; 2:设置工作模式:sta、ap、sta——ap
* 3:连接路由器; 4:设置单链接模式
* @param 不同函数的传入参数不同
* @retval ESP8266_OK
*/
uint8_t esp8266_at_test(void){
return esp8266_send("AT\r\n","OK");
}
uint8_t esp8266_set_mode(uint8_t mode){
switch (mode){
case ESP8266_STA_MODE:
return esp8266_send("AT+CWMODE=1\r\n","OK");
case ESP8266_AP_MODE:
return esp8266_send("AT+CWMODE=2\r\n","OK");
case ESP8266_STA_AP_MODE:
return esp8266_send("AT+CWMODE=3\r\n","OK");
default:
return UART_EINVAL;
}
}
uint8_t esp8266_jion_ip(char *ip ,char *key){
char cmd[68];
sprintf(cmd,"AT+CWJAP=\"%s\",\"%s\"\r\n",ip,key);
return esp8266_send(cmd,"WIFI GOT IP");
}
uint8_t esp8266_connection_mode(uint8_t mode){
char cmd[68];
sprintf(cmd,"AT+CIPMUX=%d\r\n",mode);
return esp8266_send(cmd,"OK");
}
uint8_t esp8266_connect_tcp_server(char *server_ip,char *server_port){
char cmd[100];
sprintf(cmd,"AT+CIPSTART=\"TCP\",\"%s\",%s\r\n",server_ip,server_port);
return esp8266_send(cmd,"OK");
}
/* 透传模式 */
uint8_t esp8266_enter_unvarnished(void) {
if (esp8266_send("AT+CIPMODE=1\r\n", "OK") != ESP8266_OK)
return ESP8266_ERROR;
// // 发送前清空接收缓冲区
// esp8266_rx_clear();
if (esp8266_send("AT+CIPSEND\r\n", ">") != ESP8266_OK)
return ESP8266_ERROR;
return ESP8266_OK;
}
/**
* @brief 初始化ESP8266函数和配置入网的步骤
* @note 一定要注意在while 循环中添加判断语句,否则会一直处于循环的状态
* @param baudrate
* @retval 无
*/
void esp8266_init(uint32_t baudrate){
printf("------ESP8266初始化开始------\r\n");
esp8266_uart_init(baudrate);
//esp8266的其他初始化
printf("1.测试ESP8266是否正常。。。\r\n");
while(esp8266_at_test()!= ESP8266_OK)
delay_ms(500);
printf("2.ESP8266设置工作模式为STA。。。\r\n");
while(esp8266_set_mode(ESP8266_STA_MODE)!= ESP8266_OK)
delay_ms(500);
printf("3.ESP8266单路连接模式。。。\r\n");
while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION)!= ESP8266_OK)
delay_ms(500);
printf("4,ESP8266连接路由器无线。。。SSID : %s ,KEY : %s \r\n",WIFI_SSID,WIFI_KEY);
while(esp8266_jion_ip(WIFI_SSID ,WIFI_KEY)!= ESP8266_OK)
delay_ms(5500);
printf("5,ESP8266连接TCP服务器 server_ip : %s ,server_port : %s \r\n",SERVER_IP,SERVER_PORT);
while(esp8266_connect_tcp_server(SERVER_IP,SERVER_PORT)!= ESP8266_OK)
delay_ms(500);
printf("6,ESP8266 进入透传模式\r\n ");
while(esp8266_enter_unvarnished()!= ESP8266_OK)
delay_ms(5500);
printf("------ESP8266已连接TCP服务器,进入透传模式------\r\n");
printf("------ESP8266初始化完成------\r\n");
}
/**
* @brief 测试函数,在主函数中的while语句中调用,测试模块的收发是否正常
* @note 无
* @param 无
* @retval 无
*/
void esp8266_send_test(void){
esp8266_send_data("你好帅!!!\n");
printf("esp8266:%s\n",esp8266_rx_buf);
}
方式2:利用接收中断+时间延时来完成
#include "esp8266.h"
#include "stdio.h"
#include "string.h"
#include "delay.h"
#include "stdarg.h"
uint8_t esp8266_rx_buf[ESP8266_RX_BUF_SIZE];
uint8_t esp8266_tx_buf[ESP8266_TX_BUF_SIZE];
uint16_t esp8266_cnt = 0, esp8266_cntPre = 0;
UART_HandleTypeDef esp8266_handle = {0};
void esp8266_uart_init(uint32_t baudrate)
{
esp8266_handle.Instance = USART2;
esp8266_handle.Init.BaudRate = baudrate;
esp8266_handle.Init.WordLength = UART_WORDLENGTH_8B;
esp8266_handle.Init.StopBits = UART_STOPBITS_1;
esp8266_handle.Init.Parity = UART_PARITY_NONE;
esp8266_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
esp8266_handle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&esp8266_handle);
}
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&esp8266_handle, UART_FLAG_RXNE) != RESET)
{
if(esp8266_cnt >= sizeof(esp8266_rx_buf))
esp8266_cnt = 0;
HAL_UART_Receive(&esp8266_handle, &receive_data, 1, 1000);
esp8266_rx_buf[esp8266_cnt++] = receive_data;
//uart1_cnt++;
//HAL_UART_Transmit(&uart1_handle, &receive_data, 1, 1000);
}
}
uint8_t esp8266_wait_receive(void)
{
if(esp8266_cnt == 0)
return ESP8266_ERROR;
if(esp8266_cnt == esp8266_cntPre)
{
esp8266_cnt = 0;
return ESP8266_EOK;
}
esp8266_cntPre = esp8266_cnt;
return ESP8266_ERROR;
}
void esp8266_rx_clear(void)
{
memset(esp8266_rx_buf, 0, sizeof(esp8266_rx_buf));
esp8266_cnt = 0;
}
void esp8266_receive_data(void)
{
if(esp8266_wait_receive() == ESP8266_EOK)
{
printf("esp8266 recv: %s\r\n", esp8266_rx_buf);
esp8266_rx_clear();
}
}
void esp8266_send_data(char *fmt, ...)
{
va_list ap;
uint16_t len;
va_start(ap, fmt);
vsprintf((char *)esp8266_tx_buf, fmt, ap);
va_end(ap);
len = strlen((const char *)esp8266_tx_buf);
HAL_UART_Transmit(&esp8266_handle, esp8266_tx_buf, len, 100);
}
uint8_t esp8266_send_command(char *cmd, char *res)
{
uint8_t time_out = 250;
esp8266_rx_clear();
HAL_UART_Transmit(&esp8266_handle, (uint8_t *)cmd, strlen(cmd), 100);
while(time_out--)
{
if(esp8266_wait_receive() == ESP8266_EOK)
{
if(strstr((const char*)esp8266_rx_buf, res) != NULL)
return ESP8266_EOK;
}
delay_ms(10);
}
return ESP8266_ERROR;
}
uint8_t esp8266_at_test(void)
{
return esp8266_send_command("AT\r\n", "OK");
}
uint8_t esp8266_set_mode(uint8_t mode)
{
switch(mode)
{
case ESP8266_STA_MODE:
return esp8266_send_command("AT+CWMODE=1\r\n", "OK");
case ESP8266_AP_MODE:
return esp8266_send_command("AT+CWMODE=2\r\n", "OK");
case ESP8266_STA_AP_MODE:
return esp8266_send_command("AT+CWMODE=3\r\n", "OK");
default:
return ESP8266_EINVAL;
}
}
uint8_t esp8266_join_ap(char *ssid, char *pwd)
{
char cmd[64];
sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);
return esp8266_send_command(cmd, "WIFI GOT IP");
}
uint8_t esp8266_connection_mode(uint8_t mode)
{
char cmd[64];
sprintf(cmd, "AT+CIPMUX=%d\r\n", mode);
return esp8266_send_command(cmd, "OK");
}
uint8_t esp8266_connect_tcp_server(char *server_ip, char *server_port)
{
char cmd[64];
sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s\r\n", server_ip, server_port);
return esp8266_send_command(cmd, "CONNECT");
}
uint8_t esp8266_enter_unvarnished(void)
{
uint8_t ret;
ret = esp8266_send_command("AT+CIPMODE=1\r\n", "OK");
ret += esp8266_send_command("AT+CIPSEND\r\n", ">");
if (ret == ESP8266_EOK)
return ESP8266_EOK;
else
return ESP8266_ERROR;
}
void esp8266_init(uint32_t baudrate)
{
printf("esp8266初始化开始...\r\n");
esp8266_uart_init(baudrate);
//esp8266的其它初始化
printf("1. 测试esp8266是否存在...\r\n");
while(esp8266_at_test())
delay_ms(500);
printf("2. 设置工作模式为STA...\r\n");
while(esp8266_set_mode(ESP8266_STA_MODE))
delay_ms(500);
printf("3. 设置单路链接模式...\r\n");
while(esp8266_connection_mode(ESP8266_SINGLE_CONNECTION))
delay_ms(500);
printf("4. 连接wifi,SSID: %s, PWD: %s\r\n", WIFI_SSID, WIFI_PWD);
while(esp8266_join_ap(WIFI_SSID, WIFI_PWD))
delay_ms(1500);
printf("5. 连接TCP服务器,server_ip:%s, server_port:%s\r\n", TCP_SERVER_IP, TCP_SERVER_PORT);
while(esp8266_connect_tcp_server(TCP_SERVER_IP, TCP_SERVER_PORT))
delay_ms(500);
printf("6. 进入到透传模式...\r\n");
while(esp8266_enter_unvarnished())
delay_ms(500);
printf("ESP8266已连接上TCP服务器并进入透传模式\r\n");
printf("ESP8266初始化完成!\r\n");
}
void esp8266_test(void)
{
esp8266_send_data("this is from esp8266\r\n");
esp8266_receive_data();
}
- esp8266.h文件代码
#ifndef __ESP8266_H__
#define __ESP8266_H__
#include "stdio.h"
#include "sys.h"
/* 错误代码 */
#define UART_EOK 0 /* 没有错误 */
#define UART_ERROR 1 /* 通用错误 */
#define UART_ETIMEOUT 2 /* 超时错误 */
#define UART_EINVAL 3 /* 参数错误 */
/* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE 128
#define UART2_TX_BUF_SIZE 64
/* 工作模式 */
#define ESP8266_STA_MODE 1
#define ESP8266_AP_MODE 2
#define ESP8266_STA_AP_MODE 3
/* 单链接和双链接的模式 */
#define ESP8266_SINGLE_CONNECTION 0
#define ESP8266_MULTI_CONNECTION 1
/* 无线名称和无线密码 */
#define WIFI_SSID "OPPO Find X7"
#define WIFI_KEY "xys123456"
/* 无线名称和无线密码 */
#define SERVER_IP "192.168.101.254"
#define SERVER_PORT "8080"
void esp8266_uart_init(uint32_t bound); /* 串口初始化函数 */
void esp8266_init(uint32_t baudrate);
void esp8266_send_test(void);
#endif
- mian.c文件代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "esp8266.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* LED初始化 */
uart1_init(115200);
printf("hello,world\n");
esp8266_init(115200);
printf("你好、\n");
while(1)
{
// esp8266_send_test();
// delay_ms(500); //要进行延时操作
}
}
小实验4 :编码实现ESP8266作为服务器
实验目的
利用代码的形式,实现ESP8266作为服务器的功能,并用客户端进行连接,实现数据通讯
硬件清单
esp8266、USB转TTL、开发板、ST-LINK
硬件接线
和上面一样
配置流程
文件代码
- esp8266.c文件代码和上面一样,主要改动AT指令相关的代码
- esp8266.h文件代码 和上面一样,无需改动
- main.c文件代码
项目:无线控制继电器开关
项目需求
电脑通过esp8266模块远程遥控继电器开关风扇。
硬件清单
esp8266模块、继电器 、杜邦线 、开发板 、ST-Link 、USB转TTL
硬件接线
STM32
|
esp8266模块
|
继电器
|
PA2
|
RX
| |
PA3
|
TX
| |
PB6
|
I/O
| |
3V
|
VCC
| |
5V
|
VCC
| |
GND
|
GND
|
GND
|
项目框图
文件代码
- esp8266.c文件代码
在上面小实验3中连接TCP服务端的代码中进行修改:receive_data()函数如下所示。
- mian.c文件代码
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "esp8266.h"
#include "fan.h"
#include "string.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
fan_init();
uart1_init(115200);
esp8266_init(115200);
printf("hello world!\r\n");
char recv_data[ESP8266_RX_BUF_SIZE];
while(1)
{
esp8266_receive_data(recv_data);
if(strstr(recv_data,"ON") != NULL)
fan_on();
else if(strstr(recv_data,"OFF") != NULL)
fan_off();
delay_ms(10);
}
}
实验现象
遇到的问题和注意事项
- 在接收函数中写入一个复制接收缓冲区的函数memcpy,将复制的函数在主函中进行strstr函数比较,是否接收到想用的指令。控制继电器的开关。
内网穿透
内网穿透的原理就像在内网和外网之间搭建了一座桥梁,使得外部网络可以穿过内网的障碍,直接访问内部的设备。
内网穿透的原理
实现步骤:
花生壳配置
花生壳的作用:将内网搭建的TCP服务端的IP地址进行映射,使外部的设配可以进行访问。
配置花生壳:
- 下载贝锐花生壳
- 创建内网映射
- 在网络调试助手上将映射的地址和端口号进行连接
- 花生壳实验的效果
- 通过内网的方式进行通讯
模块3:4G模块
将SIM卡插入模组,注意为Nano SIM卡(小卡),缺口向内,芯片向上;
引脚介绍
工作参数
上位机配置
- 选择端口号(根据自己的端口选择)
- 选择波特率(默认115200)
- 打开串口
- 点击进入配置,上位机会自动发送AT指令进行配置
- 读取参数,此命令可以读取4g模块的详细信息,并打印带基本信息栏(这一步很重要,否则在第九步的时候会出现问题)
- 选择链路1
- 默认是连接亿佰特的服务器 这边我们需要修改成我们自己的,具体从花生壳获取(是一串域名,或者IP地址,他都支持,根据自己获取到的填写就可以)
- 输入从花生壳获取的端口号
- 点击保存配置(对应第五步,我们很多的配置需要获取到模块默认配置,保存时才会不改变)
- 点击重启(重启后默认进入透传模式,无需在做其他操作,直接链接到串口助手上即可发送与接收信息)
心跳包
心跳包支持网络心跳包与串口心跳包两种,网络心跳包向服务端发送,串口心跳包向串口端发送。
作用:定时的向服务端或者单片机发送数据(字符串),确保4G模块未发生错误,正常连接和工作。
上位机软件配置步骤:
1、选择心跳包开关开启。
2、选择心跳包内容。
3、自定义心跳包需要编写心跳包内容(如没有则省略,选择IMEI和ICCID后,自定义数据不生效)。
4、设置心跳包时间(单位:秒)
5、如果采用16进制发送需要先勾选Hex框再输出内容。
AT指令
- 详细的AT指令参考 (4G模块 AT指令集),本节主要介绍如何关闭透传模式,开启AT指令模式。
利用串口进入 AT 指令:
- 关闭4G模块上位机的端口;
- 打开串口调试助手的串口,配置好波特率、端口等;
发送+++不用换行,收到\r\n+OK\r\n 后 5S 内发送一条正确的 AT 指令即可进入配置模式。- 退出 AT 指令:发送:AT+EXAT\r\n;收到:\r\n+OK\r\n。
项目:4G模块控制灯
项目需求
服务器通过4G模块远程遥控开关灯。
硬件清单
4G模块 、继电器 、杜邦线 、开发板 、ST-Link 、USB转TTL
硬件接线
STM32
|
4G模块
|
PA2
|
RX
|
PA3
|
TX
|
PB6
| |
3V3
| |
5V
|
VCC
|
GND
|
GND
|
项目框图
文件代码
- 4g.c文件代码
#include "4g.h"
#include "stdio.h"
#include "string.h"
#include "delay.h"
#include "stdarg.h"
uint8_t e4g_rx_buf[e4g_RX_BUF_SIZE];
uint8_t e4g_tx_buf[e4g_TX_BUF_SIZE];
uint16_t e4g_cnt = 0, e4g_cntPre = 0;
/**
* @brief
* @note
* @param
* @retval
*/
UART_HandleTypeDef e4g_handle = {0};
void e4g_uart_init(uint32_t baudrate)
{
e4g_handle.Instance = USART2;
e4g_handle.Init.BaudRate = baudrate;
e4g_handle.Init.WordLength = UART_WORDLENGTH_8B;
e4g_handle.Init.StopBits = UART_STOPBITS_1;
e4g_handle.Init.Parity = UART_PARITY_NONE;
e4g_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
e4g_handle.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&e4g_handle);
}
/**
* @brief
* @note
* @param
* @retval
*/
void USART2_IRQHandler(void)
{
uint8_t receive_data = 0;
if(__HAL_UART_GET_FLAG(&e4g_handle, UART_FLAG_RXNE) != RESET)
{
if(e4g_cnt >= sizeof(e4g_rx_buf))
e4g_cnt = 0;
HAL_UART_Receive(&e4g_handle, &receive_data, 1, 1000);
e4g_rx_buf[e4g_cnt++] = receive_data;
//uart1_cnt++;
//HAL_UART_Transmit(&uart1_handle, &receive_data, 1, 1000);
}
}
/**
* @brief
* @note
* @param
* @retval
*/
uint8_t e4g_wait_receive(void)
{
if(e4g_cnt == 0)
return e4g_ERROR;
if(e4g_cnt == e4g_cntPre)
{
e4g_cnt = 0;
return e4g_EOK;
}
e4g_cntPre = e4g_cnt;
return e4g_ERROR;
}
/**
* @brief
* @note
* @param
* @retval
*/
void e4g_rx_clear(void)
{
memset(e4g_rx_buf, 0, sizeof(e4g_rx_buf));
e4g_cnt = 0;
}
/**
* @brief
* @note
* @param
* @retval
*/
uint16_t e4g_receive_data(char *recv_data)
{
if(e4g_wait_receive() == e4g_EOK)
{
printf("e4g recv: %s\r\n", e4g_rx_buf);
memcpy(recv_data,e4g_rx_buf,strlen((const char*)e4g_rx_buf));
//sizeof(也可以)。strlen若传入数字则不能用。
e4g_rx_clear();
return strlen((const char*)recv_data);
}
return 0; //如果没有接收到数据时,返回0.
}
/**
* @brief
* @note
* @param
* @retval
*/
void e4g_send_data(char *fmt, ...)
{
va_list ap;
uint16_t len;
va_start(ap, fmt);
vsprintf((char *)e4g_tx_buf, fmt, ap);
va_end(ap);
len = strlen((const char *)e4g_tx_buf);
HAL_UART_Transmit(&e4g_handle, e4g_tx_buf, len, 100);
}
/**
* @brief
* @note
* @param
* @retval
*/
void e4g_init(uint32_t baudrate)
{
printf("e4g初始化开始...\r\n");
e4g_uart_init(baudrate);
}
/**
* @brief
* @note
* @param
* @retval
*/
//void e4g_test(void)
//{
// e4g_send_data("this is from e4g\r\n");
// e4g_receive_data();
//}
- 4g.h文件代码
#ifndef __4G_H__
#define __4G_H__
#include "sys.h"
#define e4g_RX_BUF_SIZE 128
#define e4g_TX_BUF_SIZE 64
#define e4g_EOK 0
#define e4g_ERROR 1
#define e4g_ETIMEOUT 2
#define e4g_EINVAL 3
void e4g_init(uint32_t baudrate);
uint16_t e4g_receive_data(char *recv_data);
#endif
- mian.c文件代码
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "4g.h"
#include "string.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
e4g_init(115200);
printf("hello world!\r\n");
char recv_data[e4g_RX_BUF_SIZE];
while(1)
{
e4g_receive_data(recv_data);
if(strstr(recv_data,"ON") != NULL)
led1_on();
if(strstr(recv_data,"OFF") != NULL)
led1_off();
}
}
遇到的问题和注意事项
- 再写关于变量名时,注意不要以数字开头。