《STM32单片机开发》p5
使用接收队列来接收串口的数据
queue.h
#ifndef __QUEUE_H
#define __QUEUE_H#define QUEUE_DATA_SIZE (64) // 定义队列内存储空间struct queue {char data[QUEUE_DATA_SIZE]; // 用来保存数据unsigned short int head; // 队列头位置索引。unsigned short int tail; // 队列尾位置索引。
};
// 1. 初始化队列
void queue_init(struct queue * pq);// 2. 队列空判断, 空返回1, 否则返回0
int queue_is_empty(struct queue * pq);
// 3. 队列满判断, 满返回1, 否则返回0
int queue_is_full(struct queue * pq);// 4. 入队, 成功返回1 ,失败返回0
int queue_enqueue(struct queue *pq, int value);// 5. 出队, 成功返回1 ,失败返回0
int queue_dequeue(struct queue *pq, int *pvalue);// 7. 清空队列
void queue_clear(struct queue*pq);#endif // _QUEUE_H
queue.c
#include "queue.h"// 1. 初始化队列
void queue_init(struct queue * pq) {pq->tail = pq->head = 0;
}
// 2. 队列空判断, 空返回1, 否则返回0
int queue_is_empty(struct queue * pq) {return pq->head == pq->tail;
}
// 3. 队列满判断, 满返回1, 否则返回0
int queue_is_full(struct queue * pq) {int tail_next = (pq->tail + 1) % QUEUE_DATA_SIZE;if (tail_next == pq->head) return 1; return 0;
}
// 4. 入队, 成功返回1 ,失败返回0
int queue_enqueue(struct queue *pq, int value) {if (queue_is_full(pq))return 0;pq->data[pq->tail] = value; pq->tail = (pq->tail + 1) % QUEUE_DATA_SIZE;return 1;
}
// 5. 出队, 成功返回1 ,失败返回0
int queue_dequeue(struct queue *pq, int *pvalue) {if (queue_is_empty(pq))return 0;*pvalue = pq->data[pq->head];pq->head = (pq->head + 1) % QUEUE_DATA_SIZE;return 1;
}
// 7. 清空队列
void queue_clear(struct queue*pq) {pq->head = pq->tail = 0;
}
输入重定向(需要打开微库开关)
int fgetc(FILE *f)
{
}
ESP8266 无线通信
ESP8266 是一个WIFI芯片,他的接口是串口,可以使用USART或UART进行通信。
控制ESP8266 需要发送 AT 指令进行控制。
AT 指令
AT 指令是移动通信领域的常用操作指令,主要用于各种移动通信的模块控制。
指令说明:
- 使用串口进行控制
- 指令格式
AT+[指令代码=xxx]的格式。 - 单独发送
AT用于测试连接状态,成功返回OK。 - 返回数据指令执行的结果以文本格式串行输出,通常以
'\r\n'格式进行换行。
设备丝印: ESP-015
连接测试 AT 命令:
作用:用于测试设备连接情况。正确连接回复 OK。
发送字符串: "AT\r\n"
正确回应字符串: "AT\r\n\r\nOK\r\n"
发送内容:
AT
正确响应内容:
ATOK
系统重置 AT+RST命令
作用:用于重置ESP8266 模块。
发送字符串: "AT+RST\r\n"
正确回应字符串: "AT+RST\r\n\r\nOK\r\n"
发送内容:
AT+RST
正确响应内容:
AT+RSTOK
之后还会发送复位的相关信息如下;ets Jan 8 2013,rst cause:2, boot mode:(3,7)load 0x40100
设置连接模式 AT+CWMODE=1命令
作用:用于设置 WIFI模式,1-表示 Station模式,2-表示 AP(透传)模式,3-表示AP+Station模式。
发送字符串: "AT+CWMODE=1\r\n"
正确回应字符串: "AT+CWMODE=1\r\n\r\nOK\r\n"发送内容:
AT+CWMODE=1
正确响应内容:
AT+CWMODE=1OK
连接WIFI AT+CWJAP 命令
可用Wifi
emb3:emb3emb3 emb3emb3:emb3emb3
命令格式:
AT+CWJAP="SSID","passwd"
发送字符串: "AT+CWJAP=\"emb3\",\"emb3emb3\"\r\n"
正确回应字符串: "WIFI CONNECTED\r\nWIFI GOT IP\r\n\r\nOK\r\n"发送内容:
AT+CWJAP="emb3","emb3emb3"
正确响应内容:
WIFI CONNECTED
WIFI GOT IPOK
断开WIFI连接: AT+CWQAP 命令
发送字符串: "AT+CWQAP\r\n"
发送内容:
AT+CWQAP
正确响应内容:
AT+CWQAPOK
WIFI DISCONNECT
远程TCP连接, AT+CIPSTART 命令
IP:192.144.206.112
PORT:6666
发送字符串: "AT+CIPSTART=\"TCP\",\"192.144.206.112\",6666\r\n"
正确响应字符串: "WIFI CONNECTED\r\nWIFI GOT IP\r\n\r\nOK\r\n"发送内容:
AT+CIPSTART="TCP","192.144.206.112",6666
正确响应内容:
AT+CIPSTART="TCP","192.144.206.112",6666
CONNECTOK
错误响应内容:ERROR
查询设备IP地址和MAC地址 AT+CIFSR 命令
发送字符串: "AT+CIFSR\r\n"发送内容:
AT+CIFSR
正确响应内容:
AT+CIFSR
+CIFSR:STAIP,"192.168.1.10"
+CIFSR:STAMAC,"84:f3:eb:e2:bf:fa"OK
远程TCP断开连接,AT+CIPCLOSE命令
发送字符串: "AT+CIPCLOSE\r\n"发送内容:
AT+CIPCLOSE
正确响应内容:
AT+CIPCLOSE
CLOSEDOK
发送数据 AT+CIPSEND 命令
发送数据需要按如下步骤进行:
- 先发送 AT+CIPSEND=<字节数>
- 等待响应OK,并回应一个大于号 >
- 发送与字节数相同的字节数据(不能多也能少)
例如发送字符串hello。发送步骤如下:
发送字符串: "AT+CIPSEND=5\r\n"
发送内容:
AT+CIPSEND=5
正确响应内容:
AT+CIPSEND=5OK
>
接下来继续发送5个字节"hello"
正确响应内容:
Recv 5 bytesSEND OK
接收数据
当收到TCP或UDP数据时,他的数据格式如下:
- 以+IPD,五个字节开头
- 在逗号(,)和冒号(:)之间是接收到的字节数(ASCII表示的数字)。
- 冒号(:)之后是n个字节数据。
如:
+IPD,2:hi
或
+IPD,5:robot
或
+IPD,28:I don't know what your said!
esp8266_main.c
#include <stdio.h>
#include <string.h>
#include "led.h"
#include "beep.h"
#include "button.h"
#include "systick.h"
// #include "led_digital_tube.h"
// #include "dht11.h"
// #include "adc.h"
#include "usart1.h"
#include "esp8266.h"// 中文
char buf[512];
int main(void) {// NVIC优先级分组, 2bit 用于表示抢断优先级,2bit为子优先级NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); led_init(); // 初始化LEDbeep_init(); // 初始化 蜂鸣器// button_init();systick_init(); // 初始化计时器usart1_init(115200);esp8266_init();printf("ESP8266 Demo\r\n");led_on(0);if (0 == esp8266_connect_wifi("emb3", "emb3emb3")) {printf("link wifi error\r\n");while(1);}led_off(0);led_on(1);if (0 == esp8266_tcp_connect("192.144.206.112", 6666)){printf("tcp connect error!\r\n");while(1);}led_off(1);
#if 0while(1) { //等待接收服务器的数据并作出相应的响应。if (!esp8266_read(buf, sizeof(buf)))continue;if (0 == strcmp(buf, "BEEPON")) beep_on();else if (0 == strcmp(buf, "BEEPOFF")) beep_off();else if (0 == strcmp(buf, "LED0ON")) led_on(0);else if (0 == strcmp(buf, "LED0OFF")) led_on(0);}
#endifwhile(1) {printf("your said:");// scanf("%s", buf);gets(buf);buf[strlen(buf)-1] = 0;printf("%s\r\n", buf);if (0 == strcmp(buf, "exit"))break;if (esp8266_write(buf, strlen(buf))) {if (esp8266_read(buf, sizeof(buf)))printf("robot: %s\r\n", buf);}}#if 1 while(1) {printf("please input At Command:\r\n");scanf("%s", buf);if (strncmp("AT+CIPSEND", buf, 10) == 0) {// 发送数据esp8266_send_cmd(buf);if (esp8266_read_response(buf, sizeof(buf), 5000))printf("\r\n--------\r\n%s", buf);if (strstr(buf, "OK") != NULL && strstr(buf, ">") != NULL) {scanf("%s", buf);esp8266_send_string(buf);if (esp8266_read_response(buf, sizeof(buf), 5000))printf("\r\n--------\r\n%s", buf);}} else {// 发送命令esp8266_send_cmd(buf);if (esp8266_read_response(buf, sizeof(buf), 5000))printf("\r\n--------\r\n%s", buf);}// delay_ms(1000);}
#endif// return 0;
}
esp8266.h
#ifndef __ESP8266_H
#define __ESP8266_H#include <stm32f10x_conf.h>// ESP8266初始化
void esp8266_init(void);// ESP8266通过USART2发送字符串数据
void esp8266_send_string(const char * buf);// ESP8266通过USART2发送AT指令
void esp8266_send_cmd(const char * cmd);// 从USART2 读取相应字符串放入 buf,等待最大时长timeout毫秒ms
int esp8266_read_response(char * buf, int buf_size, int timeout_ms);// ESP8266 连接 Wifi,成功返回1,失败返回0
int esp8266_connect_wifi(const char * ssid, const char * passwd);// ESP8266 作为客户端远程 TCP 连接。成功返回1,失败返回0
int esp8266_tcp_connect(const char * ip, uint16_t port);// 关闭tcp 连接
void esp8266_tcp_close(void);// ESP8266 TCP 协议发送数据,返回写入的字节数,0表示失败
int esp8266_write(const char * data, uint16_t len);// ESP8266 读取数据,返回读取的字节数
uint16_t esp8266_read(char * buf, uint16_t buf_size);// ESP8266 清空读缓冲区
void esp8266_clear_read_buf(void);#endif // __ESP8266_H
esp8266.c
#include <stdio.h>
#include <string.h>
#include "systick.h"
#include "queue.h"#include "esp8266.h"/* USART2管脚定义TX PA2RX PA3
*/
// 创建USART2的接收队列。
static struct queue usart2_rx_queue;// 重置 Wifi 模块的传输模式为工作站模式,成功返回1,失败返回0.
int esp8266_reset_module(void) {char buf[100];esp8266_send_cmd("AT+RST"); // 重置Wifi模块delay_ms(1000);delay_ms(1000);esp8266_clear_read_buf();// 设置Wifi的工作模式为 工作站模式esp8266_send_cmd("AT+CWMODE=1");if (esp8266_read_response(buf, sizeof(buf), 1000) == 0)return 0;if (strstr(buf, "OK"))return 1;return 0;
}// ESP8266初始化
void esp8266_init(void)
{GPIO_InitTypeDef GPIO_InitStruct;USART_InitTypeDef USART_InitStruct;NVIC_InitTypeDef NVIC_InitStruct;// 初始化接收队列queue_init(&usart2_rx_queue);// 使能PA2和 PA3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 使能 USART2外设时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);// 设置PA2 为发送管脚GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);// 设置PA3 为接受管脚GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 悬空输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;GPIO_Init(GPIOA, &GPIO_InitStruct);// 初始化 USART1控制器USART_InitStruct.USART_BaudRate = 115200; // 设置波特率USART_InitStruct.USART_WordLength = USART_WordLength_8b;//字长8bit USART_InitStruct.USART_StopBits = USART_StopBits_1; // 停止位1bitUSART_InitStruct.USART_Parity = USART_Parity_No; // 校验位(无)USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发USART_InitStruct.USART_HardwareFlowControl = \USART_HardwareFlowControl_None; // 硬件流控制(无)USART_Init(USART2, &USART_InitStruct);// 使能串口2USART_Cmd(USART2, ENABLE);// 初始化接收中断USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);// 清理中断标志位USART_ClearFlag(USART2, USART_FLAG_TC);NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn; // 中断通道NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStruct);// 开启接收寄存器非空中断// USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);if (0 == esp8266_reset_module())printf("reset Wifi module Error!\r\n");
}// 编写冲断处理函数
void USART2_IRQHandler(void) {uint8_t ch;// 如果是接收寄存器非空中断,则读取数据if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {ch = USART_ReceiveData(USART2);// 将接收到的字符放入接收队列,如果队列已满会丢掉此字符queue_enqueue(&usart2_rx_queue, ch);}
}// 用于串口发送一个字节的数据
void usart2_put_char(uint8_t ch)
{USART_SendData(USART2, ch); // 发送数据// 等待发送数据完毕while(USART_GetFlagStatus(USART2, USART_FLAG_TC) != SET);
}// ESP8266通过USART2发送字符串数据
void esp8266_send_string(const char * buf)
{const char * pchar;for (pchar = buf; *pchar; ++pchar) usart2_put_char((uint8_t)*pchar);
}// ESP8266通过USART2发送AT指令
void esp8266_send_cmd(const char * cmd)
{esp8266_send_string(cmd);esp8266_send_string("\r\n");
}// 从USART2 读取相应字符串放入 buf,总等待时长timeout_ms毫秒ms,
// 直到timeout_ms耗尽为0才返回
int esp8266_read_response(char * buf, int buf_size, int timeout_ms)
{int i = 0;int ch = '\0';// 等待接收数据,最大等待 timeout_ms 微妙while(--timeout_ms) {if (queue_is_empty(&usart2_rx_queue))delay_ms(1);elsebreak;}if (timeout_ms == 0) // 超时返回return 0;// 接收数据while(i < buf_size -1 && timeout_ms > 0) {if (queue_is_empty(&usart2_rx_queue)) {delay_ms(1); // 继续等待,直至timeout_ms为零timeout_ms--;} else {queue_dequeue(&usart2_rx_queue, &ch);buf[i] = ch;i++;}}buf[i] = '\0'; // 添加尾零后返回数据return i;// for (i = 0; i < buf_size -1; i++) {
// queue_dequeue(&usart2_rx_queue, &ch);
// buf[i] = ch;
// if (queue_is_empty(&usart2_rx_queue))
// delay_ms(1);
// if (queue_is_empty(&usart2_rx_queue)) {
// i++;
// buf[i] = '\0';
// return i;
// }
// }
// return i;
}// ESP8266 连接 Wifi,成功返回1,失败返回0
int esp8266_connect_wifi(const char * ssid, const char * passwd)
{char buf[100];// 拼接命令字符串sprintf(buf, "AT+CWJAP=\"%s\",\"%s\"", ssid, passwd);esp8266_clear_read_buf(); // 清空读缓冲。esp8266_send_cmd(buf);if (0 == esp8266_read_response(buf, sizeof(buf), 5000))return 0;if (strstr(buf, "OK") != NULL)return 1;return 0;
}// ESP8266 作为客户端远程 TCP 连接。成功返回1,失败返回0
int esp8266_tcp_connect(const char * ip, uint16_t port)
{char buf[100];// 拼接命令字符串sprintf(buf, "AT+CIPSTART=\"TCP\",\"%s\",%d", ip, port);esp8266_clear_read_buf(); // 清空读缓冲。esp8266_send_cmd(buf);if (0 == esp8266_read_response(buf, sizeof(buf), 5000))return 0;if (strstr(buf, "OK") != NULL)return 1;return 0;
}// 关闭tcp 连接
void esp8266_tcp_close(void)
{esp8266_send_cmd("AT+CIPCLOSE");delay_ms(1000);esp8266_clear_read_buf(); // 清空读缓冲。
}// ESP8266 TCP 协议发送数据,返回写入的字节数,0表示失败
int esp8266_write(const char * data, uint16_t len)
{char buf[100];// 拼接命令字符串sprintf(buf, "AT+CIPSEND=%d", len);esp8266_clear_read_buf(); // 清空读缓冲。esp8266_send_cmd(buf);if (0 == esp8266_read_response(buf, sizeof(buf), 5000))return 0;if (strstr(buf, "OK") == NULL || strstr(buf, ">") == NULL)return 0;esp8266_send_string(data);return len;
}// ESP8266 读取数据,返回读取的字节数
uint16_t esp8266_read(char * data, uint16_t buf_size)
{char buf[100];char * pfind = NULL;uint16_t len = 0;if (0 == esp8266_read_response(buf, sizeof(buf), 2000))return 0;printf("esp8266_read:%s\r\n", buf);pfind = strstr(buf, "+IPD,"); // 查找是否有“+IPD,” 这5个字节if (pfind == NULL)return 0;pfind += 5; // 后移5个字节while(*pfind != ':') { // 转换后面的数字,放入len变量len *= 10;len += *pfind - '0';pfind++;}pfind++; // 定位到数据部分。len = len < buf_size-1? len : buf_size-1;data[len] = '\0';strncpy(data, pfind, len); // 复制数据return len;}// ESP8266 清空读缓冲区
void esp8266_clear_read_buf(void)
{queue_clear(&usart2_rx_queue);
}