本人初学FreeRTOS,来自不知名普通院校,大二物联网专业,简单看完百问网韦东山老师FreeRTOS就想随便找个小项目试试看,手头里没什么元器件,只有一块ESP8266wifi模块以及温湿度模块显示屏模块,所以用到的模块不多,这俩个模块可能不太适用于FreeRTOS,但主要目的想着以最少的资源练练手,文中有缺陷或需补全的地方欢迎指导,大佬误喷。
需要主要的细节有:ESP8266模块与手机APP通信时候必须处于一个局域网,在手机APP打开之前需确保连上与ESP8266模块相同连接一个wifi,否则将导致连接不上。系统中断优先级需设计为分组四,在编写代码时触及的中断设置抢占优先级的时候必须要设置≥5,因为FreeRTOS中系统能控制的优先级为(5-15)。
其中代码有些许初始化部分是从江科大及网上搜寻而来,核心部分还是自己手敲的。
完整工程代码及手机app软件
百度网盘链接:百度网盘 请输入提取码
提取码:IOT1
系统流程图
百问网网址为:百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发
移植的细节不多介绍,在百问网里边包含有FreeRTOS移植模板教程及开发手册等。
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---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(