一、小插曲
在博客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重映射到PB5GPIO_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=80khzwhile(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消息,平台回复的Ackif(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 >= '0' && *dataPtr <= '9') //判断是否是下发的命令控制数据{numBuf[num++] = *dataPtr++;}numBuf[num] = 0;num = atoi((const char *)numBuf); //转为数值形式
}if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
{MQTT_FreeBuffer(cmdid_topic);MQTT_FreeBuffer(req_payload);
}}
(2)、在数据封装函数中加入PWM的值并上传值云端实现数据同步
memset(text, 0, sizeof(text));
sprintf(text, "PWM,%d;", PWM_value);
strcat(buf, text);
整合后的函数如下:
unsigned char OneNet_FillBuf(char *buf)
{char text[32];LED0_FLAG=GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5);//读取LED的开关状态(即对应引脚的)
LED1_FLAG=GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_5);printf("LED0_FLAG_TYPE=%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=%s\r\n",buf);return strlen(buf);}
六、效果演示
第一次旋转开关
第二次旋转开关
第三次旋转开关
LED0亮度效果展示
旋转开关数值为440
旋转开关数值为40