热门标签 | 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

在这里插入图片描述






推荐阅读
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
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社区 版权所有