无线通信(WIFI 蓝牙 LORA NB-IOT ZIGBEE等)
课程介绍
7天课程:
- WIFI->TCP连接 MQTT连接(阿里云)2天
- RTOS->FreeRTOS 1天
- PCB->硬件基础 PCB基础 PCB设计 智能云衣柜项目 4天
课程特点
目的:对嵌入式/单片机开发高薪就业提供帮助(在嵌入式软件开发/单片机就业方向更进一步的学习)
深入浅出、活学活用、从小白且从项目角度进行讲解
(不要担心学不会、跟着节奏走就很简单)
所需知识:电路基本知识、单片机基础知识(接口技术)、C语言(熟练运用)
单片机控制ESP8266连接TCP
1、ESP8266
官网
简介
ESP8266 系列模组是深圳市安信可科技有限公司开发的一系列基于乐鑫ESP8266EX的低功耗UART-WiFi芯片模组,可以方便地进行二次开发,接入云端服务,实现手机3/4G全球随时随地的控制,加速产品原型设计。
模块核心处理器 ESP8266 在较小尺寸封装中集成了业界领先的 Tensilica L106 超低功耗 32 位微型 MCU,带有 16 位精简模式,主频支持 80 MHz 和 160 MHz,支持 RTOS,集成 Wi-Fi MAC/ BB/RF/PA/LNA,板载天线。支持标准的 IEEE802.11 b/g/n 协议,完整的 TCP/IP 协议栈。用户可以使用该模块为现有的设备添加联网功能,也可以构建独立的网络控制器。
ESP8266 是高性能无线 SoC,以最低成本提供最大实用性,为 Wi-Fi 功能嵌入其他系统提供无限可能。
技术:从设计开始——————》产品完成
产品:专用目的的集成电路
ESP32:WiFi+蓝牙
工具地址
2. ESP8266开发方式
●固件开发(二次开发):不需要其他单片机只用8266就可以了,写8266内程序
●AT指令方式开发:需要通过串口再接一个单片机,8266和这个单片机进行串口通信,8266内有原本的固件程序,单片机串口发送指令(AT指令)控制8266
3. ESP8266的AT指令
AT指令集参考文档
4. AT指令练习
1 AT+RST
配置WiFi模式
AT+CWMODE=1 //station mode
响应:OK
连接AP(路由器)
AT+CWJAP="SSID","password" //SSID and password of router
响应:OK
查询 ESP8266 设备的 IP 地址
AT+CIFSR
响应:
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"82:64:6f:a8:75:e0"
+CIFSR:STAIP,"192.168.137.180"
+CIFSR:STAMAC,"80:64:6f:a8:75:e0"
OK
PC与 ESP8266 设备连接同一路由器,在PC端使用网络调试工具,建立一个 TCP 服务器。
假设:PC 创建的服务器IP 地址为 192.168.137.1,端⼝号为 8080
ESP8266 设备作为 TCP client 连接到上述服务器。
AT+CIPSTART="TCP","192.168.137.1",8080 //protocol,server IP and port
响应:OK
ESP8266 设备向服务器发送数据
AT+CIPSEND=16 //set date length which will be sent, such as 16 bytes
1234567890ABCDEF //enter the data, no CR
响应:Recv 16 bytes SEND OK
当 ESP8266 设备接收到服务器发来的数据,将提示如下信息:
+IPD,n:xxxxxxxxxx // received n bytes, data=xxxxxxxxxxx
AT | 测试 |
AT+RST | 复位 |
AT+CWMODE=1 | 设置wifi模式 ap/sta/ap+sta |
AT+CWJAP="rjy","12345678" | 连接WIFI热点 (名称+密码) |
AT+CIPMUX=0 | 设置单连接模式 |
AT+CIPSTART="TCP","192.168.1.1",8080 | 连接TCP服务器 |
AT+CIPSEND=5 | 发送数据 |
AT+CIPMODE=1 | 设置透传模式 |
AT+CIPSEND | 使能透传发送 |
在数据传输过程中,数据在传输链路的起点到终点之间,不经过任何修改或处理地直接传递。换句话说,透传意味着数据在传输过程中保持其原始性。
在串口通信中,透传模式通常意味着数据从串口接收后,会原封不动地通过另一个串口发送出去,不会经过任何中间处理或修改。
在网络传输中,虽然数据包在传输过程中可能会经过多个路由器或交换机,但在透明传输(与透传相似但更广泛的概念)的情况下,这些数据包的内容不会在这些中间设备上被修改或检查,除非有特定的安全或路由需求。(防火墙关掉)
TCP测试:使用了安信可透传云
- 单片机控制ESP8266连接TCP程序编写
5.1 思路梳理
程序编写思路
- 看原理图,找串口(串口5-----》WiFi,串口1-----》串口助手)
- 配置工程------》MX
- 配置串口5(115200——》WiFi使用的是这个) 使能全局中断
- 配置串口1(115200) 使能全局中断
- 配置工程————》生成工程
- 打开工程一定先编译一次
- 写中断程序
- 使能串口5的接收中断(肯定不能使用轮询)
- 使能串口5的串口空闲中断(检查是否有数据传输)
- 找到中断向量表---》中断服务程序
- 在中断服务程序写接收程序
- 判断空闲线是否为空(1)表示完成
- 清除空闲中断标志位(不能自动清除。如果不轻触——》只要有中断就会进入)
- 计算接收数据的长度
- 重新指定接受数据缓冲区的地址
- 重新指定接受数据的最大长度(1024)
- 可以加一个数据标志位,判断数据完成
- 写串口1和串口5的发送数据
- 方法一:HAL用库(不能格式化输出 阻塞形式 指定长度);
- 方法二:重定向(用的是C库,只能重定向一个)
- 方法三:自己写格式化输出
- 实现AT指令并判断返回值
5.2 MX配置
5.3 串口收发程序编写
启动文件说明
【教程】一文搞懂STM32启动文件_eclipse stm32 启动文件-CSDN博客
mian函数中使能串口空闲中断(记得声明)
HAL_UART_Receive_IT(&huart5,USART5_RxBuff,1024);
__HAL_UART_ENABLE_IT(&huart5,UART_IT_IDLE);//空闲中断
去中断向量表->找中断服务程序(目的:判断是否接收数据完毕,完毕了计算接收了多少数据,并标记已经接受完毕)
extern uint8_t USART5_RxBuff[1024];
extern uint8_t USART5_RxCounter;
void UART5_IRQHandler(void)
{
/* USER CODE BEGIN UART5_IRQn 0 */
/* USER CODE END UART5_IRQn 0 */
HAL_UART_IRQHandler(&huart5);//自带的不用管
/* USER CODE BEGIN UART5_IRQn 1 */
//以下的是咱们自己写的
if(__HAL_UART_GET_FLAG(&huart5, UART_FLAG_IDLE) == SET)
//判断是否触发串口空闲中断
{
int datalen = 0;
//用于存储接收数据长度,这里的没有用,如果有需要可以弄全局变量
__HAL_UART_CLEAR_IDLEFLAG(&huart5); //清除空闲中断标志
datalen = 1024 - huart5.RxXferCount; //接收到多少数据
//HAL_UART_Transmit(&huart1,(uint8_t*)USART5_RxBuff,datalen,10);
//验证是否收到,正式功能里注释掉
//memset(USART5_RxBuff,0,1024);
//正式功能可以哪里使用数据,使用完清除
huart5.RxXferCount = 1024;//初始化接收的最大数据量
huart5.pRxBuffPtr = USART5_RxBuff;//定义缓冲区
}
/* USER CODE END UART5_IRQn 1 */
}
5.4 串口格式化输出
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
void USART_printf(UART_HandleTypeDef *USARTx,char *format, ...)
{
const char *str;
int num;
char buf[16];
va_list args;
va_start(args, format);
while (*format != '\0')
{
if (*format == '\\') // 如果遇到反斜杠
{
format++;
if (*format == 'r')
HAL_UART_Transmit(USARTx, (uint8_t *)"\r", 1, HAL_MAX_DELAY); // 发送回车符
else if (*format == 'n')
HAL_UART_Transmit(USARTx, (uint8_t *)"\n", 1, HAL_MAX_DELAY); // 发送换行符
format++;
}
else if (*format == '%') // 如果遇到格式化字符
{
format++;
if (*format == 's') // 字符串
{
str = va_arg(args, const char *);
HAL_UART_Transmit(USARTx, (uint8_t *)str, strlen(str), HAL_MAX_DELAY); // 发送字符串
}
else if (*format == 'd') // 十进制整数
{
num = va_arg(args, int);
snprintf(buf, sizeof(buf), "%d", num);
HAL_UART_Transmit(USARTx, (uint8_t *)buf, strlen(buf), HAL_MAX_DELAY); // 发送整数的字符串表示
}
format++;
}
else
HAL_UART_Transmit(USARTx, (uint8_t *)format++, 1, HAL_MAX_DELAY); // 直接发送字符
}
va_end(args);
}
5.5 串口格式化输出使用
USART_printf(&huart1,"ruan_jiayu\r\n");
程序烧录
5.6 WIFI部分程序编写
5.7 加入工程代码
- 找到自己工程的文件夹,在文件夹中创建一个新的文件夹用于存放自己写的程序(我这里名字写的APP),并且我又在APP里建了个WIFI用于放WIFI功能程序,这样更好区分不同功能
- 打开KEIL添加自己的wifi.c
- 添加WIFI程序所在路径
- main函数中调用(记得添加WIFI的头文件)
- 烧录验证功能
仿真中可以查看缓冲区,TCP发数据TCP缓冲区会随之变化
- 单片机控制ESP8266连接阿里云物联平台
需要了解的新知识点
MQTT(一种基于TCP实现的应用层网络通信协议)
CJSON(json是一种数据格式,C语言实现的)
AT指令(使用MQTT固件的)
cJSON简介
MQTT协议简介
【腾讯文档】MQTTMQTT
订阅和发布
MQTT是基于topic来发布消息的,发布者在发布消息时需要指定该消息发布在哪个topic下。
topic最容易的理解方式是文件夹路径。如果有成千上万的不同类型的文件要保存,为了合理放置这些文件,需要创建一系列有层次性的文件夹来管理它们。发布消息就好比把文件保存到不同的文件夹下。
和文件夹类似,topic有主题级别,各级别之间是以斜杠(/)来分隔的。
例如: rensanning/home/room1/light/brightness 就有5个级别。
第1层:用户
第2层:场所
第3层:位置
第4层:物件
第5层:数据指标
/sys/k0snmQNV0Hn/${deviceName}/thing/event/property/post
1、云平台搭建
- 注册和登录
第一步,先找到阿里云平台官网。
点击右上角的注册登录完成之后,进行实名认证,任选一种认证方式。
认证完成之后找到左上角的三条横杠(或者首页左上角的产品),
- 实例的开通和创建
然后找到物联网然后右侧找到物联网平台
点进去然后点击管理控制台第一次用会让你开通物联网平台,然后点击开通
进去之后点击我已阅读那个服务协议然后点击立即开通会提示购买成功,
2021年07.30之前开通的公共实例是永久,此时间之后开通的是公共实例试用
然后点击管理控制台,会进入到实例概览界面,如果没有公共实例就先等阿里云给创建好,然后点开通公共实例点击开通的时候会有一个类似警告⚠的说明然后点我已理解以上内容然后点确认开通.
- 产品和设备的创建
后期可直接从此网址进入阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
开通好之后,点进去左侧找到设备管理,点产品
创建产品
输入产品名称,现在做项目的时候是点击自定义品类,标准品类后期看具体需求
然后节点类型:普通设备(灯开关,温湿度,二氧化碳浓度等等显示类和控制类的都是选择直连设备),网关设备就选网关设备,看后期具体需求
联网方式首选WiFi如果需要别的协议就选对应的,数据格式最常用的就是JSON,然后校验级别默认弱校验,认证方式默认设备密钥,产品描述可以自己添加对该产品的描述。
产品创建成功后,你可以选择前往添加设备,可以选择查看产品详情等。
也可以返回产品列表点查看,查看自己的产品所有信息。
添加设备
然后在左侧导航栏选择设备管理>设备,在设备列表中添加页下点击添加设备。
在弹出的对话框内选择你之前创建的产品名称,并填写DeviceName为你自己起的名字(比如智能温湿度可以写smartTemperature1,备注名称为“智能温湿度01号”(也就是你自己对设备的一个备注。
点击确定之后点击完成。确定后可以在设备详情页中看到增加的设备。如果有多个设备,也可按上述方法继续添加。
功能定义
发布上线
确认完成之后点发布上线
- MQTTFX工具使用
mqtt.fx | 一款超级好用的Mqtt客户端软件(下载、安装、使用详解)-CSDN博客
发布
- MQTT固件烧录
注意:
资料中或者官网找到固件压缩包并解压
找到烧录工具并打开
开发板上将WIFI模块切换为下载模式串口连接8266,在设备管理器中查看usb端口号
选择固件程序配置地址,点击烧录程序
亲~~~,别忘了拨回运行模式哦
- AT指令验证
打开串口调试助手,按顺序发送AT指令给WIFI模组,记得替换自己的参数
1、AT+RST //测试
2、AT+CWMODE=1 //设置模式
- AT+CWJAP="WiFi名字","WiFi密码" //连接路由器
- AT+CIPSNTPCFG=1,8,"ntp1.aliyun.com" //用于配置网络时间协议(NTP)服务器的设置。
- AT+MQTTUSERCFG=0,1,"NULL","username","passwd",0,0,"" //
- AT+MQTTCLIENTID=0,"clientId" //clientId第二个参数注意每个逗号前加分隔符“\”
- AT+MQTTCONN=0,"mqttHostUrl",1883,1 //自己的Url
- AT+MQTTSUB=0,"订阅的主题",1 //订阅的主题可在云端设备的“自定义Topic列表”复制进去
注意:订阅主题(替换自己的设备名字)
- 调试验证订阅
程序编写
思路梳理
- 程序需要改的地方
亲~~~~, 一定要改(里面信息是我的,哇呜)
三、RTOS(实时操作系统)
1、裸机开发模式
1.1 轮询方式
对于简单的应用程序,轮询(无限循环)的实现比较简单,在硬件完成初始化后,顺序的完成各种 任务。在外设的基础实验中,常采用这种方式。
int main()
{
while(1)
{
DHT11数据采集;
读取WIFI数据;
判断数据;
}
}
在实际的嵌入式系统中,存在周期性与触发型任务,每个任务的执行时间与实时响应要求不同,在采用轮询系统进行程序设计时,很难应对这些场景。
1.2 前后台(中断方式)
前后台系统是在轮询的基础上加入了中断。外部事件的记录在中断中操作,对事件的响应在轮询中 完成,中断处理过程称之为前台,main 函数中的轮询称为后台。
后台的程序顺序执行,如果产生中断,那么中断会打断后台程序的正常执行,转而去执行中断服务 程序。如果事件的处理过程比较简单,可以直接在中断服务程序中进行处理;如果事件的处理过程比较 复杂,可以在中断中对事件响应进行标记,进而返回后台程序进行处理。
int main()
{
while(1)
{
if(DHT11==1)
{
DHT11数据处理;
DHT11=0;
}
if(wifi==1)
{
处理WIFI数据;
wifi=0;
}
}
}
void DHT11_irq()
{
DHT11=1;
}
void WIFI_irq()
{
wifi=1;
}
//中断处理的速度高了
//触发之后又成为了轮询操作
//假如DHT11优先级高,且处理时间过长 wifi的处理就不及时了
采用前后台系统进行程序时,对后台的任务需要进行设计,避免单个任务长时间占有处理器资源。 当任务的逻辑比较复杂,任务的拆分难度增加,同时,随着中断事件的增加,整个程序的设计与响应的 实时性将会降低。
1.3 改进(前后台(中断))定时器
设置3S定时器中断(DHT11采集)
设置5s定时器中断(Wifi数据处理)
1.4 裸机进一步优化
void wifi(void)
{
static int key=0;
switch(key)
{
case 0:
发送AT指令();
key=1;
return;
case 1:
接收数据();
key=2;
return;
case 2:
判断数据();
key=0;
return;
}
}
void DHT11(void)
{
static int DHT11=0;
switch(DHT11)
{
case 0:
握手();
DHT11=1;
return;
case 1:
接收数据();
DHT11=2;
return;
case 2:
判断数据完整性();
DHT11=3;
return;
case 3:
数据处理();
DHT11=0;
return;
}
}
//问题解决了,但是程序的复杂度上来了
//基于裸机架构无法完美解决复杂耗时的多个函数
1.5 裸机的其他问题
while(1)
{
DHT11;
if(key)
{
delay(100);//程序在这停止了,效率被影响了
if(key)
{
wifi();
}
}
}
按键消抖:在按键中断开启定时器,在定时器中断实现按键效果
2、RTOS的概念
什么是RTOS
为什么要使用 RTOS
主要是为了满足系统在时间和资源管理上的特殊需求
实时性要求:
- 确定性响应:RTOS提供精确的任务调度和时间管理,确保关键任务能够在规定的时间内执行。
- 低延迟:实时应用程序需要快速响应外部事件,RTOS通过优先级调度和中断处理来保证低延迟。
任务管理:
- 多任务处理:RTOS支持同时运行多个任务,并通过调度器管理任务的执行顺序,确保每个任务都能及时获得CPU时间。
- 优先级调度:RTOS允许为不同任务设置优先级,确保高优先级任务可以打断低优先级任务的执行,以及时完成关键操作。
资源管理:
- 内存管理:RTOS提供有效的内存管理机制,减少内存碎片,并确保关键任务的内存分配。
- 同步和通信:RTOS提供任务间同步和通信机制,如信号量、消息队列和互斥锁,确保任务之间能够安全地共享资源和信息。
可靠性和稳定性:
- 故障隔离:通过分离不同任务,RTOS能够防止一个任务的崩溃影响整个系统,提高系统的稳定性和可靠性。
- 看门狗定时器:RTOS常包含看门狗定时器功能,确保系统在出现异常情况时能够自动复位和恢复。
硬件抽象:
- 硬件无关性:RTOS提供硬件抽象层,使应用程序可以在不同硬件平台上运行而无需修改,大大提高了系统的可移植性。
开发效率:
- 模块化设计:使用RTOS可以将系统设计为多个独立模块,每个模块专注于特定功能,提高开发和维护的效率。
- 调试工具:许多RTOS提供丰富的调试和监控工具,帮助开发者快速发现和解决问题。
RTOS的应用场景
物联网(IoT):RTOS是专门为物联网设备设计的操作系统,它提供了实时性、高效性和可靠性,以满足物联网应用的特殊需求。RTOS通过任务调度算法管理任务的执行顺序,确保高优先级任务能够及时响应,满足物联网设备对实时性的要求。
智能家居:RTOS在智能家居系统中用于控制各种智能设备,如智能灯泡、智能插座、智能门锁等。RTOS提供了设备之间的实时通信和协同工作,使用户能够方便地通过智能手机或其他设备控制家居环境。
医疗设备:RTOS在医疗设备中发挥着重要作用,如心脏起搏器、监护仪等设备需要实时处理生理参数,确保患者的生命安全。RTOS的高可靠性和实时性使得这些设备能够在关键时刻发挥关键作用。SAFERTOS等RTOS产品特别针对医疗设备的需求,提供响应迅速、稳健、确定性的嵌入式实时操作系统,降低项目风险、开发成本,并缩短上市时间。
汽车电子:在汽车领域,RTOS的应用越来越广泛。例如,高级驾驶辅助系统(ADAS)需要实时处理大量的传感器数据,以实现自动驾驶、车辆导航等功能。RTOS能够确保这些系统的实时性和稳定性,提高驾驶安全性。
工业自动化和机器人技术:RTOS在工业自动化系统中扮演关键角色,通过精确定时和控制能力,确保生产线的稳定运行,提高生产效率和质量。在机器人技术中,RTOS能够确保机器人实时响应指令,执行复杂任务,如无人机和机器人的飞行轨迹控制、任务执行等。
航空航天:在航空航天领域,RTOS的应用至关重要。由于航空电子系统的复杂性和对实时性的极高要求,RTOS能够提供高度可靠的中断处理和任务调度机制,确保飞行控制系统、导航系统和传感器数据处理的实时性和准确性。
RTOS的选择
安全性:RTOS 是否有助于设备的安全性或损害设备的安全性?容易出现用户错误吗?
性能:RTOS 能否促进应用程序代码的开发?代码是否在所需参数内执行?
可靠性:RTOS 是否会影响设备的可靠性?
功能:RTOS 是否具备完成这项工作所需的设施?
- 学习RTOS的3个步骤
学会使用API
了解API实现原理
可以优化改进API
- RTOS的工作原理
① RTOS相当于实现了后台的主循环,并能够处理ISR与主循环的交互
② 使得用户可以只考虑任务的设计
③ RTOS还提供了各种组件用于实现任务间交互及其他控制管理功能(e.g. 存储管理)
提供多个执行流,虽然实际只有一颗CPU,但通过"虚拟化",每个Task好像独占CPU
提供资源管理和通信组件
提供一些组件用于简化任务对资源的访问,事件的处理,以及任务之间的通信,有效降低任务之间的代码耦合
FreeRTOS特点
创建第一个FreeRTOS程序
搭建方法
- 移植文件(FreeRTOS相关文件)+时钟配置
- 官网源码下的DEMO(需要精简 去掉无关文件)+标准库
- CUBEMX直接生成就可以了
FreeRTOS中的动态内存管理(heap_1、heap_2、heap_3、heap_4)_freertos heap选择-CSDN博客
1、官网源码下载
(1)进入FreeRTOS官网
https://www.freertos.org/zh-cn-cmn-s/index.html
(2)点击下载FreeRTOS
2、处理工程目录
(1)下载后解压FreeRTOS文件
(2)删除多余文件(红框里的)
(3)删除"FreeRTOSv202212.01\FreeRTOS\Demo"目录下用不到的示例工程,留下common这里放了一些公共文件
(4)"FreeRTOSv202212.01\FreeRTOS\Source\portable"目录下只保留如下两个文件夹,其他全部删掉。(5)"FreeRTOSv202212.01\FreeRTOS\Source\portable\RVDS"目录下只保留如下一个文件夹,其他全部删掉
3、打开编译工程
(1)删除后文件后,进入如下图打开工程
(2)弹出如下对话框,说明该工程是用KeilMDK4创建的。点击“Migrate to Device Pack”更新为KeilMDK5。
(3)弹出对话框,点击“确定”。
- 更新后,关闭工程再重新打开,编译。
- 工程目录介绍(System里还有一个lcd也删掉)
4、去掉无关代码
(1)Demo Files文件下只保留“serial.c和main.c”文件,其他都删掉(删完之后main里去掉一些头文件)。
(2)编译
5、删除未定义报错内容
(1)在文件STM32F10x.s中,删除如下内容。
(2)删除其他未定义的相关内容,再次编译。报错的内容均删除或者注释,直到没错为止。
6、验证
在原有任务的基础上加个i++验证
串口打印实验
1、重定向
这个fputc在main中:
int fputc( int ch, FILE *f )//重定向 修改数据传输方向
{
while(!(USART1->SR & (1<<7))){}
USART1->DR =ch;
return ch;
}
2、配置串口
初始化删除多余的东西,自己写一个串口一的初始化。
void UartInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE );
//tx
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//rx
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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_InitStructure.USART_Clock = USART_Clock_Disable;
USART_InitStructure.USART_CPOL = USART_CPOL_Low;
USART_InitStructure.USART_CPHA = USART_CPHA_2Edge;
USART_InitStructure.USART_LastBit = USART_LastBit_Disable;
USART_Init( USART1, &USART_InitStructure );
USART_Cmd( USART1, ENABLE );
}
1、串口初始化
2、调用串口初始化
3、重定向
4、两个任务里写printf测试
如何找串口仿真
命名规范
三、FreeRTOS命名规范_freertos命名规则-CSDN博客
数据类型
命名规范
动态任务的创建
1、任务是什么
任务的外观:一个永远不返回的函数
说明:使用void* 类型形参,确保可以传入任意类型的参数
2、任务实验
实现
创建任务函数xTaskCreate:任务也不是很复杂的东西,任务也就是一个函数
xTaskCreate。简单得说,创建一个任务,你得提供它的执行函数,你得提供它的栈的大小,函数的执行空间,函数的优先级等重要的条件。因为任务在运行中,任务函数有调用关系,有局部变量,这些都保存在任务的栈里面;任务有可能被切换,有可能被暂停,这时候CPU寄存器中断现场数据都保存在栈里面。
函数原型
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数说明
pvTaskCode:指向任务函数的指针。该函数表示任务要执行的代码。
pcName:任务名称字符串。用于调试和跟踪,不影响任务功能。也可用于获取任务句柄
usStackDepth:任务栈大小(以单词为单位)。根据任务需求设定,过小可能导致栈溢出。
pvParameters:传递给任务函数的参数。可以是任意类型的指针。
uxPriority:任务优先级。数值越大,优先级越高。
pxCreatedTask:任务句柄指针。用于存储创建任务后的任务句柄,可选参数。可为NULL
返回值
如果任务创建成功,返回pdPASS。如果任务创建失败(例如内存不足),返回错误码。
任务创建成功后,系统会自动将其加入到调度队列。调度器会根据任务优先级选择合适的任务执行。
实验
优先级
结论
FreeRTOS中,优先级数越大优先级越高,两个任务同优先级时通过时间片轮转执行,如果有高优先级时,高优先级执行,一直到高优先级停止执行低优先级才能执行。
延时函数
void vTaskDelay( const TickType_t xTicksToDelay );
查看延时时间验证
声明个变量a只有任务一执行时才为1,其他时候为0,然后进入仿真使用虚拟逻辑分析仪查看变量a状态
查看方法(进入仿真使用):
任务状态
同优先级的任务正在运行,所以需要等待。
运行态(Running):如果一个任务得到 CPU 的使用权,即任务被实际执行时,那么这个任务处于运行态。如果运行 FreeRTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处理运行态。
就绪态:如果一个任务已经能够被执行(不处于阻塞态或挂起态),但当前还未被执行(具有相同优先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。
阻塞态(Blocked):如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情况下,处于阻塞态的任务都 有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后,即使任务等待的外部事件还没有发生, 任务的阻塞态也会被解除。要注意的是,处于阻塞态的任务是无法被运行的。
挂起态(Suspended):任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态与阻塞态一样,处于挂起态的任务也无法被运行。
挂起任务、恢复任务
挂起任务函数原型
vTaskSuspend( TaskHandle_t xTaskToSuspend )
参数
xTaskToSuspend:需要挂起任务的句柄
实验
创建任务时加上句柄
句柄声明
在任务1里挂起任务2(前提是可以执行到它)
void vTask1( void *pvParameters )
{
while(1)
{
printf("vTask1\n");
demo1=1;
demo2=0;
vTaskDelay(4);
vTaskSuspend(CreatedTask);
}
}
恢复任务函数原型
void vTaskResume( TaskHandle_t xTaskToResume )
参数
TaskToResumex:需要恢复任务的句柄
实验
//挂起后又恢复了实验中如果正常执行任务2就验证了
//也可以再创建个任务3,进入延时一段时间恢复任务2
void vTask1( void *pvParameters )
{
while(1)
{
printf("vTask1\n");
demo1=1;
demo2=0;
vTaskDelay(4);
vTaskSuspend(CreatedTask);
vTaskDelay(4);
vTaskResume(CreatedTask); }
}
总结
1、写个挂起的函数验证
2、在第二个任务中写vTaskSuspend(NULL); 把自己挂起
3、验证仿真内看效果
4、写个恢复函数的验证
5、在第一个任务中写vTaskResume(lcdTask); 其中lcdTask 为任务2 的句柄
6、声明任务2句柄,并在创建任务2的函数最后一个参数填入
任务删除
vTaskDelete()函数用于删除任务。在使用这个函数时,需要提供一个任务句柄作为参数,以便通知内核删除哪个任务。
函数原型
void vTaskDelete( TaskHandle_t xTaskToDelete )
参数:
xTaskToDelete:需要删除任务的句柄
如果写的是NULL,则自杀
1、空闲任务
创建的任务大部份时间都处于阻塞态。这种状态下所有的任务都不可运行,所以也不能被调度器选中。但处理器总是需要代码来执行——所以至少要有一个任务处于运行态。为了保证这一点,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务。空闲任务是一个非常短小的循环——和最早的示例任务十分相似,总是可以运行。空闲任务拥有最低优先级(优先级 0)以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级。运行在最低优先级可以保证一旦有更高优先级的任务进入就绪态,空闲任务就会立即切出运行态。
- 钩子函数
通过空闲任务钩子函数(或称回调,hook, or call-back),可以直接在空闲任务中添加应用程序相关的功能。空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。通常空闲任务钩子函数被用于:
- 执行低优先级,后台或需要不停处理的功能代码。
- 测试系统处理(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。
- 将处理器配置到低功耗模式——提供一种自动省电方法,使得在没有任何应用功能
- 需要处理的时候,系统自动进入省电模式。
- 空闲任务钩子函数的实现限制
- 绝不能阻塞或挂起。空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!
- 如果应用程序用到了 vTaskDelete() 函数,则空闲钩子函数必须能够尽快返回。因为在任务被删除后,空闲任务负责回收内核资源。如果空闲任务一直运行在钩子函数中,则无法进行回收工作。
- 钩子函数的使用
- main函数中找到vTaskStartScheduler()并跳转
- vTaskStartScheduler()内可以找到如图的函数
- 跳转空闲任务函数找到了vApplicationIdleHook()函数就是钩子函数,但是需要configUSE_IDLE_HOOK==1
- 右键跳转configUSE_IDLE_HOOK并将configUSE_IDLE_HOOK等于1
- 编译发现报错,内容为vApplicationIdleHook未定义
- 接下来我们声明写一个vApplicationIdleHook函数并在里面写自己的任务程序就可以了
- 使用CUBE配置FreeRTOS编写程序
1、更新安装freertos插件
- 配置FreeRTOS
创建一个新工程
如果没有FREERTOS,那就关掉MX再重新打开一次
3、配置LED灯
再次创建即可
- 最后生成工程MDK内编写程序
void StartTask02(void *argument)
{
/* USER CODE BEGIN myTask02 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
osDelay(100);
}
/* USER CODE END myTask02 */
}
5、串口输出(扩展:自己看,原理都一样,很简单)
这里需要加上串口的头文件
现象