STM32F103+FreeRTOS的使用ESP8266与手机APP实现TCP连接通信控制

前言

本人初学FreeRTOS,来自不知名普通院校,大二物联网专业,简单看完百问网韦东山老师FreeRTOS就想随便找个小项目试试看,手头里没什么元器件,只有一块ESP8266wifi模块以及温湿度模块显示屏模块,所以用到的模块不多,这俩个模块可能不太适用于FreeRTOS,但主要目的想着以最少的资源练练手,文中有缺陷或需补全的地方欢迎指导,大佬误喷。

需要主要的细节有:ESP8266模块与手机APP通信时候必须处于一个局域网,在手机APP打开之前需确保连上与ESP8266模块相同连接一个wifi,否则将导致连接不上。系统中断优先级需设计为分组四,在编写代码时触及的中断设置抢占优先级的时候必须要设置≥5,因为FreeRTOS中系统能控制的优先级为(5-15)。
其中代码有些许初始化部分是从江科大及网上搜寻而来,核心部分还是自己手敲的。

完整工程代码及手机app软件

百度网盘链接:百度网盘 请输入提取码

提取码:IOT1

系统流程图

STM32F103+FreeRTOS的使用ESP8266与手机APP实现TCP连接通信控制_第1张图片

一、stm32f103vet6移植使用FreeRTOS

百问网网址为:百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发 

移植的细节不多介绍,在百问网里边包含有FreeRTOS移植模板教程及开发手册等。

二、LED初始化

LED1 ------->PC5

LED2 ------->PB2

 LED.c

#include "led.h"

/*******************************************************************************
* 函 数 名         : LED_Init
* 函数功能		   : LED初始化函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
	
	RCC_APB2PeriphClockCmd(LED1_PORT_RCC|LED2_PORT_RCC,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=LED1_PIN | LED2_PIN ;  //选择你要设置的IO口
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;	 //设置推挽输出模式
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;	  //设置传输速率
	GPIO_Init(LED1_PORT,&GPIO_InitStructure); 	   /* 初始化GPIO */
	GPIO_Init(LED2_PORT,&GPIO_InitStructure); 	   /* 初始化GPIO */	
	GPIO_SetBits(LED1_PORT,LED1_PIN);   //将LED端口拉高,熄灭所有LED
	GPIO_SetBits(LED2_PORT,LED2_PIN);  	
//	GPIO_ResetBits(LED1_PORT,LED1_PIN); 
//	GPIO_ResetBits(LED2_PORT,LED2_PIN); 	
}



 LED.h

#ifndef _led_H
#define _led_H

#include "system.h"

/*  LED时钟端口、引脚定义 */
#define LED1_PORT 			GPIOC   
#define LED1_PIN 				GPIO_Pin_5
#define LED1_PORT_RCC		RCC_APB2Periph_GPIOC

#define LED2_PORT 			GPIOB   
#define LED2_PIN 				GPIO_Pin_2
#define LED2_PORT_RCC		RCC_APB2Periph_GPIOB


#define LED1 PCout(5)  	
#define LED2 PBout(2)  	


void LED_Init(void);


#endif

 三、USART1及USART3初始化

USART1---CH340

PA9------>RX

PA10------>TX

USART3---ESP8266

PB10------>RX

PB11------>TX

 bsp_usart.c

#include "bsp_usart.h"
#include "FreeRTOS.h"
#include "semphr.h"	
#include "task.h"
#include "queue.h"
#include "led.h"
#include "OLED.h"

extern SemaphoreHandle_t  uartSemaphore;
extern QueueHandle_t  uartQueue;
uint32_t rx_cnt=0;
xUSATR_TypeDef  xUSART;         // 声明为全局变量,方便记录信息、状态


/******************************************************************************
 * 函  数: vUSART1_Init
 * 功  能: 初始化USART1的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void USART1_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;                           // 使能USART1时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;                             // 使能GPIOA时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 6 ;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    USART_DeInit(USART1);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(USART1, &USART_InitStructure);                       // 初始化串口

    USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                  // 使能接受中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);                  // 使能空闲中断

    USART_Cmd(USART1, ENABLE);                                      // 使能串口, 开始工作

    USART1->SR = ~(0x00F0);                                         // 清理中断

    xUSART.USART1InitFlag = 1;                                      // 标记初始化标志
    xUSART.USART1ReceivedNum = 0;                                   // 接收字节数清零

    printf("USART1初始化配置成功\r");
}

/******************************************************************************
 * 函  数: USART1_IRQHandler
 * 功  能: USART1的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 *
******************************************************************************/
static uint8_t U1TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U1TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U1TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void USART1_IRQHandler(void)
{
    static uint16_t cnt = 0;                                         // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U1_RX_BUF_SIZE];                          // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;

    // 接收中断
    if (USART1->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U1_RX_BUF_SIZE))//||(xUSART.USART1ReceivedFlag==1// 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            USART1->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = USART1->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (USART1->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        xUSART.USART1ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.USART1ReceivedBuffer, RxTemp, U1_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.USART1ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
        cnt = 0;                                                     // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U1_RX_BUF_SIZE);                           // 接收数据缓存数组,清零; 准备下一次的接收
        USART1 ->SR;
        USART1 ->DR;                                 // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((USART1->SR & 1 << 7) && (USART1->CR1 & 1 << 7))             // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART1->DR = U1TxBuffer[U1TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U1TxCounter == U1TxCount)
            USART1->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}

/******************************************************************************
 * 函  数: vUSART1_GetBuffer
 * 功  能: 获取UART所接收到的数据
 * 参  数: uint8_t* buffer   数据存放缓存地址
 *          uint8_t* cnt      接收到的字节数
 * 返回值: 0_没有接收到新数据, 非0_所接收到新数据的字节数
 ******************************************************************************/
uint8_t USART1_GetBuffer(uint8_t *buffer, uint8_t *cnt)
{
    if (xUSART.USART1ReceivedNum > 0)                                           // 判断是否有新数据
    {
        memcpy(buffer, xUSART.USART1ReceivedBuffer, xUSART.USART1ReceivedNum);  // 把新数据复制到指定位置
        *cnt = xUSART.USART1ReceivedNum;                                        // 把新数据的字节数,存放指定变量
        xUSART.USART1ReceivedNum = 0;                                           // 接收标记置0
        return *cnt;                                                            // 返回所接收到新数据的字节数
    }
    return 0;                                                                   // 返回0, 表示没有接收到新数据
}

/******************************************************************************
 * 函  数: vUSART1_SendData
 * 功  能: UART通过中断发送数据,适合各种数据类型
 *         【适合场景】本函数可发送各种数据,而不限于字符串,如int,char
 *         【不 适 合】注意环形缓冲区容量256字节,如果发送频率太高,注意波特率
 * 参  数: uint8_t* buffer   需发送数据的首地址
 *          uint8_t  cnt      发送的字节数 ,限于中断发送的缓存区大小,不能大于256个字节
 * 返回值:
 ******************************************************************************/
void USART1_SendData(uint8_t *buf, uint8_t cnt)
{
		uint8_t i; 
    for (i = 0; i < cnt; i++)
        U1TxBuffer[U1TxCount++] = buf[i];

    if ((USART1->CR1 & 1 << 7) == 0)       // 检查发送缓冲区空置中断(TXEIE)是否已打开
        USART1->CR1 |= 1 << 7;
}

/******************************************************************************
 * 函  数: vUSART1_SendString
 * 功  能: UART通过中断发送输出字符串,无需输入数据长度
 *         【适合场景】字符串,长度<=256字节
 *         【不 适 合】int,float等数据类型
 * 参  数: char* stringTemp   需发送数据的缓存首地址
 * 返回值: 元
 ******************************************************************************/
void USART1_SendString(char *stringTemp)
{
    u16 num = 0;                                 // 字符串长度
    char *t = stringTemp ;                       // 用于配合计算发送的数量
    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位
    USART1_SendData((u8 *)stringTemp, num);      // 注意调用函数所需要的真实数据长度; 如果目标需要以0作结尾判断,需num+1:字符串以0结尾,即多发一个:0
}

/******************************************************************************
 * 函  数: vUSART1_SendStringForDMA
 * 功  能: UART通过DMA发送数据,省了占用中断的时间
 *         【适合场景】字符串,字节数非常多,
 *         【不 适 合】1:只适合发送字符串,不适合发送可能含0的数值类数据; 2-时间间隔要足够
 * 参  数: char strintTemp  要发送的字符串首地址
 * 返回值: 无
 ******************************************************************************/
void USART1_SendStringForDMA(char *stringTemp)
{
    static u8 Flag_DmaTxInit = 0;                // 用于标记是否已配置DMA发送
    u32   num = 0;                               // 发送的数量,注意发送的单位不是必须8位的
    char *t = stringTemp ;                       // 用于配合计算发送的数量

    while (*t++ != 0)  num++;                    // 计算要发送的数目,这步比较耗时,测试发现每多6个字节,增加1us,单位:8位

    while (DMA1_Channel4->CNDTR > 0);            // 重要:如果DMA还在进行上次发送,就等待; 得进完成中断清标志,F4不用这么麻烦,发送完后EN自动清零
    if (Flag_DmaTxInit == 0)                     // 是否已进行过USAART_TX的DMA传输配置
    {
        Flag_DmaTxInit  = 1;                     // 设置标记,下次调用本函数就不再进行配置了
        USART1 ->CR3   |= 1 << 7;                // 使能DMA发送
        RCC->AHBENR    |= 1 << 0;                // 开启DMA1时钟  [0]DMA1   [1]DMA2

        DMA1_Channel4->CCR   = 0;                // 失能, 清0整个寄存器, DMA必须失能才能配置
        DMA1_Channel4->CNDTR = num;              // 传输数据量
        DMA1_Channel4->CMAR  = (u32)stringTemp;  // 存储器地址
        DMA1_Channel4->CPAR  = (u32)&USART1->DR; // 外设地址

        DMA1_Channel4->CCR |= 1 << 4;            // 数据传输方向   0:从外设读   1:从存储器读
        DMA1_Channel4->CCR |= 0 << 5;            // 循环模式       0:不循环     1:循环
        DMA1_Channel4->CCR |= 0 << 6;            // 外设地址非增量模式
        DMA1_Channel4->CCR |= 1 << 7;            // 存储器增量模式
        DMA1_Channel4->CCR |= 0 << 8;            // 外设数据宽度为8位
        DMA1_Channel4->CCR |= 0 << 10;           // 存储器数据宽度8位
        DMA1_Channel4->CCR |= 0 << 12;           // 中等优先级
        DMA1_Channel4->CCR |= 0 << 14;           // 非存储器到存储器模式
    }
    DMA1_Channel4->CCR  &= ~((u32)(1 << 0));     // 失能,DMA必须失能才能配置
    DMA1_Channel4->CNDTR = num;                  // 传输数据量
    DMA1_Channel4->CMAR  = (u32)stringTemp;      // 存储器地址
    DMA1_Channel4->CCR  |= 1 << 0;               // 开启DMA传输
}




//////////////////////////////////////////////////////////////   USART-3   //////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/******************************************************************************
 * 函  数: vUSART3_Init
 * 功  能: 初始化USART的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void USART3_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB1ENR |= RCC_APB1ENR_USART3EN;                           // 使能USART3时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;                             // 使能GPIOB时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 5;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 0;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    USART_DeInit(USART3);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(USART3, &USART_InitStructure);                       // 初始化串口

    USART_ITConfig(USART3, USART_IT_TXE, DISABLE);
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);                  // 使能接受中断
    USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);                  // 使能空闲中断

    USART_Cmd(USART3, ENABLE);  
    
    USART3->SR = ~(0x00F0);                                         // 清理中断

    xUSART.USART3InitFlag = 1;                                      // 标记初始化标志
    xUSART.USART3ReceivedNum = 0;                                   // 接收字节数清零

    printf("\rUSART3初始化配置\r");
}

/******************************************************************************
 * 函  数: USART3_IRQHandler
 * 功  能: USART的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
static uint8_t U3TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U3TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U3TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)
static uint16_t cnt = 0;  
char  RxTemp[U3_RX_BUF_SIZE];  

void USART3_IRQHandler(void)
{
		BaseType_t xHigherPriorityTaskWoken;
		BaseType_t QueuePriorityTaskWoken;
		char *buffer;
    // 接收中断
    if (USART3->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U3_RX_BUF_SIZE))//||xUSART.USART3ReceivedFlag==1 // 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            USART3->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = USART3->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (USART3->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
				if(uartSemaphore!=NULL)
				{					
					//释放二值信号量
					xSemaphoreGiveFromISR(uartSemaphore,&xHigherPriorityTaskWoken);	//释放二值信号量
				}
				portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换			  
        xUSART.USART3ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.USART3ReceivedBuffer, RxTemp, U3_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
				buffer = xUSART.USART3ReceivedBuffer;
				xQueueSendToBackFromISR( uartQueue, &buffer,&QueuePriorityTaskWoken); //将数据写入队列
				portYIELD_FROM_ISR(QueuePriorityTaskWoken);	//上下文切换			
				xUSART.USART3ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
				cnt = 0;                                                     // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U3_RX_BUF_SIZE);                           // 接收数据缓存数组,清零; 准备下一次的接收
        USART3 ->SR;
        USART3 ->DR;                                                 // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((USART3->SR & 1 << 7) && (USART3->CR1 & 1 << 7))             // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART3->DR = U3TxBuffer[U3TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U3TxCounter == U3TxCount)
            USART3->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}
/*
		功能:解析服务器下发数据,服务器不可使用中文下发数据,可使用数字及英文
*/
void Receive_Task(

你可能感兴趣的:(STM32F103+FreeRTOS的使用ESP8266与手机APP实现TCP连接通信控制)