热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:4ESP8266+onenet+STM32定时器的PWM应用(onenet云平台远程控制LED灯的亮度)

篇首语:本文由编程笔记#小编为大家整理,主要介绍了4-ESP8266+onenet+STM32定时器的PWM应用(onenet云平台远程控制LED灯的亮度)相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了4-ESP8266+onenet+STM32定时器的PWM应用(onenet云平台远程控制LED灯的亮度)相关的知识,希望对你有一定的参考价值。






一、小插曲

在博客2-STM32+ESP8266连接onenet并上传数据(HTTP)中突然有一个想法,那就是利用onenet云平台实现远程调节灯光的亮度,虽然临时感觉没啥应用意义,但还是尝试做了一下,借助于正点原子的官方例程(实验9,PWM输出实验)还是比较顺利的。

1、正点原子官方例程实现的功能:
用 TIM3 的通道 2,把通道 2 重映射到 PB5, 产生 PWM波来控制 DS0 (LED0)的亮度,DS0亮度由亮变暗,再由暗变亮,如此不断循环下去。
正点原子官方例程实验9-pwm输出实验–提取码pbdz

2、结合云端修改后的功能:通过云端可视化的旋转按钮下发控制灯光亮度的数值,旋转开关旋转到不同的位置,表示下发不同的数值来设置占空比,即可使LED灯具有不同的亮度显示。


二、部分基础知识介绍

1、pwm简介

脉冲宽度调制简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。

2、pwm输出
STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。

3、使用到的寄存器

(1)捕获/比较模式寄存器(TIMx_CCMR1/2)
在这里插入图片描述

(2)捕获/比较使能寄存器(TIMx_CCER)
在这里插入图片描述

该寄存器控制着各个输入输出通道的开关,

(3)捕获/比较寄存器(TIMx_CCR1~4)
该寄存器总共有 4 个,对应 4 个输通道 CH1~4
在这里插入图片描述

TIM3的重映射表
在这里插入图片描述让 TIM3_CH2 映射到 PB5 上, 则需要设置部分重映射


三、PWM初始化配置过程

1、要使用 TIM3,我们必须先使能 TIM3 的时钟

(1)使用的函数为:
RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
(2)函数配置为:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器3时钟

2、同时使能PB5的引脚时钟和复用功能时钟

(1)使用函数为:
RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
(2)函数配置为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE);//使能引脚时钟和复用时钟

3、接下来对IO口模式进行设置(设置为复用推挽输出)

(1)使用的函数为:
GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
(2)函数配置为:
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//复用推挽
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);

4、重映射(定时器3的通道2重映射到PB5)–部分重映射(映射关系查看上表)

(1)使用的函数:
GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState)
(2)函数配置为:
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3 , ENABLE);//部分重映射(PB5)

5、定时器初始化

(1)使用的函数
TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
(2)函数配置为:
TIM_TimeBaseInitStruct.TIM_Period=arr;//设置自动重装载值
TIM_TimeBaseInitStruct.TIM_Prescaler=psc;//设置预分频值
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up ;//模式为向上计数
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit( TIM3, &TIM_TimeBaseInitStruct);

6、初始化定时器3通道2pwm模式

(1)使用的函数
TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct)
(2)配置函数为:
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;//极性(有效状态为高电平)
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM2;//模式2(count值比ccr小的时候无效,当CNT>CCR时输出高电平)
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//输出状态使能
TIM_OC2Init( TIM3, &TIM_OCInitStruct);

7、使能预装载

(1)使用的函数
TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload)
(2)函数配置:
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//使能预装载

8、使能定时器

(1)使用函数为:
TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
(2)函数配置为:
TIM_Cmd(TIM3, ENABLE);

**


四、PWM初始化函数汇总

**

1、PWM初始化函数内容

void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStruct;

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;

TIM_OCInitTypeDef TIM_OCInitStruct;

//1、使能定时器3的时钟和
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器3时钟
//2、对应引脚的时钟和复用的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB, ENABLE);//使能引脚时钟和复用时钟
//3、IO口模式设置
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//复用推挽
GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//4、重映射(定时器3的通道2重映射到PB5
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3 , ENABLE);//部分重映射(PB5)
//45、定时器初始化
TIM_TimeBaseInitStruct.TIM_Period=arr;//设置自动重装载值
TIM_TimeBaseInitStruct.TIM_Prescaler=psc;//设置预分频值
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up ;//模式为向上计数
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit( TIM3, &TIM_TimeBaseInitStruct);
//6、初始化定时器3通道2pwm模式
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;//极性(有效状态为高电平)
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM2;//模式2(count值比ccr小的时候无效,当CNT>CCR时输出高电平)
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//输出状态使能
TIM_OC2Init( TIM3, &TIM_OCInitStruct);
//7、使能预装载
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//使能预装载

//8、使能定时器
TIM_Cmd(TIM3, ENABLE);
}

2、主函数内容

int main(void)
{
u16 led0pwmval=0;
u8 dir=1;//方向
delay_init(); //延时函数初始化
NVIC_Configuration();//中断优先级分组
LED_Init(); //初始化与LED连接的硬件接口
TIM3_PWM_Init(899,0);//不分频内部时钟是72MHZ,pwm频率=72000000/900=80khz
while(1)
{
delay_ms(10);
if(dir)led0pwmval++;//dir=1表明正向走+1操作
else led0pwmval--;//表示反向进行-1操作

if(led0pwmval>300)dir=0;//当正向+1的数值大于300时,改变方向
if(led0pwmval==0)dir=1;//当反向-1减到0时,改变方向

// 修改 TIM3_CCR2 来控制占空比
TIM_SetCompare2(TIM3, led0pwmval);//设置比较的数值
}
}

以上为正点原子实验9所需要涉及到的内容,接下来借助这个官方例程实验进行改进,在3-STM32+ESP8266连接onenet上传数据+远程控制(MQTT)的基础上添加使用定时器3的PWM波驱动LED0,并且将设置的比较值上传至云端,同时订阅并解析云端下发的比较值进行占空比的设置


五、远程实现对灯光亮度的控制

官方例程的主函数可知,下面函数起着至关重要的作用
TIM_SetCompare2(TIM3, led0pwmval);//设置比较的数值

注:
由于是在原来基础上新增PWM的属性将数值上传和订阅解析,故加入初始化函数的前提下再对
数据封装函数
unsigned char OneNet_FillBuf(char *buf)
云平台返回数据检测函数
void OneNet_RevPro(unsigned char *cmd)

进行部分代码添加即可实现。只需要小小的添加即可实现,其实在上一篇博客中数据上传和订阅数据并解析所需要修改的部分也主要是这两个函数的内容

1、找官方例程
先借助已有的程序进行移植,复制正点原子的官方例程中的实验9中HARDWARE下的TIMER文件夹
在这里插入图片描述
2、复制到自己程序中
复制刚才的文件夹到自己已有的程序文件夹的HARDWARE中去,(上一篇博客中已经可以实现STM32+ESP8266上传数据到onenet云平台并订阅解析数据的程序)
在这里插入图片描述
3、添加c文件到工程
打开工程后将刚复制的c文件添加到工程中去,并且调用对应的头文件以及设置头文件调用的路径(否则会报错)
HARDWARE下找到timer.c(包含PWM初始化函数)文件进行添加,添加c文件步骤
在这里插入图片描述
4、添加调用头文件的路径
添加头文件需要调用的路径,找到haraware下的TIMRE文件夹即可
在这里插入图片描述
5、主函数中添加初始化函数

main.c文件记得调用timer.h文件并调用初始化函数,主函数中的内容不需要其他修改
在这里插入图片描述
6、重点来了-修改onenet.c文件中的两个函数

接下来只需要修改onenet.c文件的两个函数即可完成程序修改

注意:
onenet.c文件中别忘记调用头文件stm32f10x_tim.h,否则我们所使用的TIM_SetCompare2(TIM3, json_value2->valueint)函数会报错

(1)、在平台返回数据检测中加入代码即可(用与解析PWM属性的数值)

json2 = cJSON_Parse(req_payload);
if (!json2)
printf("Error before: [%s]\\n",cJSON_GetErrorPtr());
else
{
json_value2 = cJSON_GetObjectItem(json2 , "PWM");
PWM_value=json_value2 ->valueint;
printf("pw_value_int=%d\\r\\n",PWM_value);
TIM_SetCompare2(TIM3, json_value2->valueint);// 修改 TIM3_CCR2 来控制占空比
}

对解析出的数据直接使用来控制者占空比
在这里插入图片描述

平台返回数据检测函数整合代码如下:
在前者的基础上只是加入了对PWM属性数值的提取

void OneNet_RevPro(unsigned char *cmd)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包
char *req_payload = NULL;
char *cmdid_topic = NULL;
unsigned short req_len = 0;
unsigned char type = 0;
short result = 0;
char *dataPtr = NULL;
char numBuf[10];
int num = 0;
cJSON *json , *json_value;
cJSON *json1, *json_value1;
cJSON *json2, *json_value2;//pwm控制led灯
type = MQTT_UnPacketRecv(cmd);
switch(type)
{
case MQTT_PKT_CMD: //命令下发

result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len); //解出topic和消息体
if(result == 0)
{
//打印收到的信息
printf( "cmdid: %s, req: %s, req_len: %d\\r\\n", cmdid_topic, req_payload, req_len);

// 对数据包req_payload进行JSON格式解析
json = cJSON_Parse(req_payload);

if (!json)//如果json内容为空,则打印错误信息
printf("Error before: [%s]\\n",cJSON_GetErrorPtr());
else
{
json_value = cJSON_GetObjectItem(json , "LED0");//提取对应属性的数值
// printf("json_value: [%s]\\r\\n",json_value->string);//转化为字符串数值
// printf("json_value: [%d]\\r\\n",json_value->valueint);//转化为数值型数值

if((json_value->valueint)==1)
//flag=1;//关灯
LED0=1;
else if((json_value->valueint)==0)
//flag=2;//开灯
LED0=0;
}
//同上
json1 = cJSON_Parse(req_payload);
if (!json1)
printf("Error before: [%s]\\n",cJSON_GetErrorPtr());
else
{
json_value1 = cJSON_GetObjectItem(json1 , "LED1");
if((json_value1->valueint)==1)//整数值
//flag=3;//关灯
LED1=1;
else if((json_value1->valueint)==0)
//flag=4;//开灯
LED1=0;

}

json2 = cJSON_Parse(req_payload);
if (!json2)
printf("Error before: [%s]\\n",cJSON_GetErrorPtr());
else
{
json_value2 = cJSON_GetObjectItem(json2 , "PWM");
PWM_value=json_value2 ->valueint;
printf("pw_value_int=%d\\r\\n",PWM_value);
TIM_SetCompare2(TIM3, json_value2->valueint);// 修改 TIM3_CCR2 来控制占空比

}

if(MQTT_PacketCmdResp(cmdid_topic, req_payload, &mqttPacket) == 0) //命令回复组包
{
printf( "Tips: Send CmdResp\\r\\n");

ESP8266_SendData(mqttPacket._data, mqttPacket._len); //回复命令
MQTT_DeleteBuffer(&mqttPacket); //删包
}
cJSON_Delete(json);//释放位于堆中cJSON结构体内存
cJSON_Delete(json1);
cJSON_Delete(json2);
}

break;

case MQTT_PKT_PUBACK: //发送Publish消息,平台回复的Ack

if(MQTT_UnPacketPublishAck(cmd) == 0)
printf( "Tips: MQTT Publish Send OK\\r\\n");

break;

default:
result = -1;
break;
}
ESP8266_Clear(); //清空缓存
if(result == -1)
return;
dataPtr = strchr(req_payload, '}'); //搜索'}'
if(dataPtr != NULL && result != -1) //如果找到了
{
dataPtr++;

while(*dataPtr >&#61; &#39;0&#39; && *dataPtr <&#61; &#39;9&#39;) //判断是否是下发的命令控制数据
{
numBuf[num&#43;&#43;] &#61; *dataPtr&#43;&#43;;
}
numBuf[num] &#61; 0;

num &#61; atoi((const char *)numBuf); //转为数值形式
}
if(type &#61;&#61; MQTT_PKT_CMD || type &#61;&#61; MQTT_PKT_PUBLISH)
{
MQTT_FreeBuffer(cmdid_topic);
MQTT_FreeBuffer(req_payload);
}
}

&#xff08;2&#xff09;、在数据封装函数中加入PWM的值并上传值云端实现数据同步

memset(text, 0, sizeof(text));
sprintf(text, "PWM,%d;", PWM_value);
strcat(buf, text);

整合后的函数如下&#xff1a;

unsigned char OneNet_FillBuf(char *buf)
{
char text[32];
LED0_FLAG&#61;GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5);//读取LED的开关状态&#xff08;即对应引脚的&#xff09;
LED1_FLAG&#61;GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_5);
printf("LED0_FLAG_TYPE&#61;%d\\n",sizeof(LED0_FLAG));
memset(text, 0, sizeof(text));
strcpy(buf, ",;");
memset(text, 0, sizeof(text));
sprintf(text, "Tempreture,%d.%d;",temperatureH,temperatureL);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "Humidity,%d.%d;", humidityH,humidityL);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "LED0,%d;", LED0_FLAG);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "LED1,%d;", LED1_FLAG);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "PWM,%d;", PWM_value);
strcat(buf, text);
printf("buf_mqtt&#61;%s\\r\\n",buf);
return strlen(buf);
}

六、效果演示

第一次旋转开关
在这里插入图片描述
第二次旋转开关

在这里插入图片描述

第三次旋转开关

在这里插入图片描述

LED0亮度效果展示

在这里插入图片描述
旋转开关数值为440
在这里插入图片描述
旋转开关数值为40

在这里插入图片描述






推荐阅读
  • C基本语法C程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。对象-对象具有状态和行为 ... [详细]
  • 本文介绍了多种将多行数据合并为单行的方法,包括使用动态SQL、函数、CTE等技术,适用于不同的SQL Server版本。 ... [详细]
  • 本文详细介绍了如何将After Effects中的动画相机数据导入到Vizrt系统中,提供了一种有效的解决方案,适用于需要在广播级图形制作中使用AE动画的专业人士。 ... [详细]
  • Activity跳转动画 无缝衔接
    Activity跳转动画 无缝衔接 ... [详细]
  • 酷家乐 Serverless FaaS 产品实践探索
    本文探讨了酷家乐在 Serverless FaaS 领域的实践与经验,重点介绍了 FaaS 平台的构建、业务收益及未来发展方向。 ... [详细]
  • 本文档提供了详细的MySQL安装步骤,包括解压安装文件、选择安装类型、配置MySQL服务以及设置管理员密码等关键环节,帮助用户顺利完成MySQL的安装。 ... [详细]
  • 本文主要解决了在编译CM10.2时出现的关于Samsung Exynos 4 HDMI HAL库中SecHdmiV4L2Utils.cpp文件的编译错误。 ... [详细]
  • 3144:[Hnoi2013]切糕TimeLimit:10SecMemoryLimit:128MBSubmit:1261Solved:700[Submit][St ... [详细]
  • 题目描述墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令ÿ ... [详细]
  • Python 中 filter、map 和 reduce 函数详解
    本文深入探讨了 Python 编程语言中 filter、map 和 reduce 函数的功能与用法,包括它们的基本语法、应用场景及代码示例,旨在帮助读者更好地理解和运用这些高阶函数。 ... [详细]
  • 本文介绍了在解决Hive表中复杂数据结构平铺化问题后,如何通过创建视图来准确计算广告日志的曝光PV,特别是针对用户对应多个标签的情况。同时,详细探讨了UDF的使用方法及其在实际项目中的应用。 ... [详细]
  • ZOJ 2760 - 最大流问题
    题目链接:How Many Shortest Paths。题目描述:给定一个包含n个节点的有向图,通过一个n*n的矩阵来表示。矩阵中的a[i][j]值为-1表示从节点i到节点j无直接路径;否则,该值表示从i到j的路径长度。输入起点vs和终点vt,计算从vs到vt的所有不共享任何边的最短路径数量。如果起点和终点相同,则输出无穷大。 ... [详细]
  • 本文探讨了如何利用数组来构建二叉树,并介绍了通过队列实现的二叉树层次遍历方法。通过具体的C++代码示例,详细说明了构建及打印二叉树的过程。 ... [详细]
  • 深入解析mt_allocator内存分配器(二):多线程与单线程场景下的实现
    本文详细介绍了mt_allocator内存分配器在多线程和单线程环境下的实现机制。该分配器以2的幂次方字节为单位分配内存,支持灵活的配置和高效的性能。文章分为内存池特性描述、内存池实现、单线程内存池实现、内存池策略类实现及多线程内存池实现等部分,深入探讨了内存池的初始化、内存分配与回收的具体实现。 ... [详细]
  • 本文详细解析 Skynet 的启动流程,包括配置文件的读取、环境变量的设置、主要线程的启动(如 timer、socket、monitor 和 worker 线程),以及消息队列的实现机制。 ... [详细]
author-avatar
多米音乐_35677591
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有