Note:
The project description file is the same as the instruction document. You can download the attachment "Roller Blind General_Electric Curtain_Instruction Document.pdf" to view.
The project code can be viewed by downloading the attachment "Code_RollerBlindGeneral.zip".
The demonstration video of network distribution, unexpected power outage and restart during network distribution, and network control has been uploaded and can be scrolled down to the bottom of the document to view.
PDF files have built-in hyperlinks that can be directly clicked to jump to Datasheet and other files.
Author: OpticalMoe
Date: 2020/08/23
HD beautiful photos:
The project aims to develop an electric curtain equipment. The device uses an STM32 microcontroller as the main control, and realizes the electric opening and closing of the curtains by controlling the forward and reverse rotation of the encoder motor. It is connected to the Alibaba Cloud IoT platform through EMW3080 to achieve local/cloud control.
Power supply: Using LP6498 chip, designed to output 5V@1A. The LDO uses HX9193 and is designed to output 3.3V.
WIFI: Using EMW3080 (Alibaba Feiyan firmware), it can be connected to the Alibaba Cloud Life IoT platform.
Motor drive: equipped with AB phase encoder interface, which can realize position loop and speed loop PID control of the motor; the drive chip uses RZ7899, supporting 25V@3A (maximum 5A); it is also equipped with a limit switch interface to prevent the curtain from exceeding the limit. Bit operation is damaged.
Peripherals: Equipped with a passive buzzer, controlled by TIM timer, which can play music; two LED indicators are used to indicate WIFI networking status and device operating status; photoresistor and NTC thermistor can feedback light and temperature; Two buttons, a single button can manually control the up and down movement of the curtains, and a double button can enter the network distribution mode.
1. Distribution network
2. Network control
Table of contents
One device selection
Second schematic design
Three PCB design
Four welding
Five APP design
Six program debugging
in conclusion
This activity requires the design of an IoT device. In order to control costs, device selection should be as cost-effective as possible.
Datasheet: DC005-30A
Package: DC005-T25
Input: 30V (maximum)
Current: 3A (maximum)
Pay attention to choosing A-class sockets, and pay attention to whether the withstandable voltage and current are met.
Datasheet: LP6498
Package: SOT23-6
Input: 4.5 ~ 30V
Output: 4.8 ~ 12V
Current: 1200mA (maximum)
The chip has high voltage resistance, wide input and output voltage ranges, and large current. Small size, simple peripheral circuit, and adjustable output voltage. Cheap, sturdy, and cost-effective.
Datasheet: HX9193-33GB
Package: SOT-23-5
Input: 6V (maximum)
Output: 3.3V (fixed)
Current: 600mA (maximum)
Voltage drop: 480mV (max)
This chip has large current and small voltage drop. Small size and simple peripheral circuit. Cheap, sturdy, and cost-effective.
The MCU uses STM32F030K6 microcontroller.
Datasheet: STM32F030K6T6
Package: LQFP32
The main reason for choosing this microcontroller is Cheap Great value for money. At the same time, the STM32 chip can use ST-Link to connect to Keil online DEBUG, and you can also use the STM32CubeMonitor software to print the internal variable change curve to facilitate PID debugging.
This single chip has 32KB FLASH, 4KB RAM, 48MHz main frequency, LQFP-32 package, a serial port, 5 timers, a 10-channel 12bit AD, and 26 IOs. It can be said to be small and compact, with extraordinary strength.
I originally planned to install an RTOS, but running the OS on 4KB of RAM was a bit difficult. After adding a few things, it overcame the problem, and it didn’t work in the end.
The self-made ST-Link V2-1 has low cost, strong performance, and a higher sampling frequency than the pirated J-Link.
The WIFI selected is EMW3080V2 (Alibaba Cloud Feiyan firmware). I have no experience in WIFI selection, so I just follow the course.
Datasheet: RZ7899
Package: SOP-8_150mil
Input: 3 ~ 25V
Current: 3A
Peak current: 5A
Built-in braking function, built-in over-temperature protection, built-in short-circuit protection, built-in over-current protection.
Datasheet: CC6900SO-5A
Package: SOP-8
Gain: 400mV/A
Current: 5A
The peripheral design includes: a passive buzzer, two buttons, and two LED indicators. Encoder interface, limit switch interface.
According to personal design habits, the schematic diagram is divided by function and designed on 5 A4 drawings. Introduced below.
The power supply section is mainly divided into four parts. They are: power input socket, DCDC step-down, LDO step-down, and test points .
The positive terminal of the power input socket first passes through the SS54 diode and then is connected to the device.
The DCDC and LDO parts can be drawn according to the official manual.
原理图-电源
MCU部分主要设计 晶振电路,复位电路,SWD下载接口。
晶振 采用SMD-3225封装的8MHz无源晶振,该封装对烙铁焊接不友好。晶振电路主要由晶振和两个22pF无极性陶瓷电容构成。
复位电路 由10k上拉电阻和0.1uF电容构成,主要完成上电复位功能。
SWD接口 用于调试和下载程序,引出了SWCLK、SWDIO、NRST,采用XH2.5-4P端子接口。引出NRST引脚,即使程序中未使能SWD调试接口仍能下载、调试程序。
原理图-MCU
EMW3080电路主要包括 电源滤波、串口、BOOT、测试点。
电源滤波 采用0.1uF和10uF组合的形式;根据手册要求,电源采用3.3V。
串口 通过0R电阻交叉连接到MCU串口;GPIO23根据手册要求通过10k上拉;
BOOT 引脚预留0R电阻接地,但不焊接;EN引脚通过0R电阻连接到MCU和按键,主要完成WIFI的复位工作。
测试点 包括串口的TXD和RXD接口。调试时连接串口,可监视MCU和WIFI间交换的所有数据。
连接外部串口监视数据时,MCU串口需设置为开漏+上拉模式,否则会导致MCU与WIFI间数据乱码。
原理图-EMW3080
电机驱动部分主要完成 电流传感器电流采样、电源电压采样、电机驱动、测试点。
电源电压采样 采用分压电阻结构,通过100k和10k电阻获得低的采样电压送入MCU-ADC接口。
电流采样 按照CC6900SO-5A官方手册绘制即可。
电机驱动芯片 按照官方手册绘制,注意输入和输出接口走线宽度。同时在电机接口上设计四个二极管钳位。电机采用5.0-2P接口,方便拆装。
测试点 主要有电流采样点、电压采样点、电机驱动正反转信号点,便于调试时确定状态。
注意功率地与信号地分开,并连接
原理图-电机驱动
外设主要设计 无源蜂鸣器、光敏电阻、热敏电阻、编码器接口、按键、LED指示灯、测试点、机械孔。
无源蜂鸣器 需连接到TIM-PWM输出引脚,可以通过调整TIM装载值和比较值控制蜂鸣器音调和音量。
热敏电阻 和 光敏电阻 需要串联一个已知阻值的电阻接入电路,通过MCU-ADC测量中间点电压反向推算出外部温度和光强。
热敏电阻和光敏电阻测量精度非常有限,即使程序中加入修正,采样值仍可能和实际值偏差较大。对温度和光强精度要求高的场所慎用。
编码器接口 是为了电机的位置环、速度环PID设计,可连接AB相编码器。接口内已设计上拉电阻和硬件消抖电路,编码器电源通过两个0R电阻在5V和3.3V间选择,注意不可同时连接5V和3.3V电阻。
按键 用于控制窗帘的上拉、下拉动作,同时在必要时刻充当配网开关。
蓝色LED指示灯 用于指示WIFI连接状态,绿色LED 用于指示设备运行状态。
测试点 可测量光敏电阻和热敏电阻输出电压。
机械孔 是四个M3螺丝孔,方便设备通过螺丝安装在需要的地方。
原理图-外设
PCB设计经验不足,在此抛砖引玉。如有错误之处,还望大佬不惜赐教。
PCB效果图
电源 和 电机驱动 主要注意走线宽度、功率地和信号地分开、端子下面挖空防止接地短路等。
PCB-电源&电机驱动
左侧为 LDO,右侧为 DCDC。注意电感离DCDC芯片近一些,电感下面不要走信号线。(此图为错误示范)
PCB-DCDC&LDO
MCU主要注意晶振连线短一些,滤波电容靠近MCU电源引脚。
PCB-MCU
EMW3080按照官方手册要求,1、2、24、25脚不接,天线前方、左右留16mm净空区。搜索EDA中所有的封装都不完全满足官方手册要求,我自己画了一个。
PCB-EMW3080
热敏 避开发热区域;光敏 避开LED区域;编码器接口 放在电机端子旁边,方便连接;按键 和 LED指示灯 放在板子下方,方便操作。
PCB-外设
拿到PCB,准备焊接工具,开始焊接。
PCB
因为部分PCB中有一些封装对烙铁十分不友好。所以,上风枪。
工具
下面简述下焊接步骤和注意事项。
单片机可以使用针管挤焊锡膏在焊盘上,摆好单片机,烙铁走一遍就能焊好,不连锡,贼好用。
电容焊盘也较短,需要焊锡膏和烙铁配合焊接。
PCB注意有几个元件不能焊接。分别是:编码器电源5V处0R电阻,EMW3080的BOOT接地电阻。
焊接完成后,效果如下。
实物图
APP设计采用阿里云物联网平台。具体过程可参考B站课程回放
一些属性参数如下:(部分功能未使用)
属性参数
三元组信息
APP主界面
程序调试主要按功能块调试。调试日志按以下顺序依次进行:代码移植、上传数据、蜂鸣器驱动、ADC&DMA采样、PID&参数整定、按键控制、任务调度器改写、配网模式。
零妖大佬给的例程是基于51单片机。51程序和STM32不兼容,需要移植一些底层代码。代码平台 CubeMX&HAL 库,MDK-ARM 5.27 。
移植需要一定的软件操作基础,回忆的过程,不完整。
工程代码可移步附件下载查看。
移植前可通过串口先让WIFI上云,减小移植难度。
首先打开 CubeMX 软件,选择 STM32F030K6T6 ,使能外部晶振,使能SWD接口。勾选必要的外设。设置时钟48MHz。填写工程名称,保存位置。选择使用的IDE为MDK-ARM 5.27,勾选“为每一个外设生成.c/.h文件”。点击“生成代码”。
引脚配置
时钟树
代码生成后,点击“打开工程”,自动调用Keil软件。
首先,在keil左侧Project中添加一个文件夹,用来存放我们的.c文件。双击该文件夹,加入Code_User文件夹下所有文件。
然后,点击 魔术棒 ,点击“C/C++”选项卡,点击“Include Paths”后面三点,添加“Code_User”文件夹路径。
编译文件,不出意外,您会收获 error(s) 。
下面开始解决这些错误:
其他错误可双击编译结果跳转至指定位置。具体问题自行查阅资料修改,不赘述。 首次移植可只删除和移植串口代码,其他无关紧要的稍后移植。 如果一切顺利,编译没有错误,可下载到MCU。WIFI能够上云。
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC_Init();
MX_TIM3_Init();
MX_TIM14_Init();
MX_USART1_UART_Init();
MX_TIM17_Init();
MX_TIM1_Init();
MX_TIM16_Init();
/* USER CODE BEGIN 2 */
//******** Init ***************//
AdcInit();
MotorSpeedInit();
MotorPositionInit();
//******** OS Init ***************//
Init_Uart2();
Timer0_Init();
Button_Init();
WIFI_Init();
//******** PID ***************//
Timer_0_Add_Fun(10, MotorPositionLoop); //位置环
// Timer_0_Pas_Fun(MotorPositionLoop); //位置环 暂停
Timer_0_Add_Fun(10, MotorSpeedLoop); //速度环
Timer_0_Pause_Fun(MotorSpeedLoop); //速度环 暂停
//******** OS ***************//
Timer_0_Add_Fun(50, UserButton); //按键检测底层业务
Timer_0_Add_Fun(5, Uart2_CheckMessageLoop); //帧处理函数
Timer_0_Add_Fun(500, WIFI_LED_Loop); //网络状态指示灯 1Hz
Timer_0_Add_Fun(500, Mode_LED_Loop); //设备状态指示灯 1Hz
Timer_0_Add_Fun(60 * 1000, WIFI_SubTemp); //上报一次 温度 信息
Timer_0_Add_Fun(61 * 1000, WIFI_SubLlluminance);//上报一次 光强 信息
Timer_0_Add_Fun(62 * 1000, WIFI_SubVoltage); //上报一次 电压 信息
// Timer_0_Add_Fun(32 * 1000, WIFI_SubMotorMode); //上报一次 电机运行模式 信息
// Timer_0_Add_Fun(33 * 1000, WIFI_SubLimitStatus);//上报一次 限位状态 信息
// Timer_0_Add_Fun(34 * 1000, WIFI_SubAction); //上报一次 电机动作 信息
// Timer_0_Add_Fun(35 * 1000, WIFI_SubMode); //上报一次 窗帘模式 信息
// Timer_0_Add_Fun(36 * 1000, WIFI_SubPosition); //上报一次 窗帘位置 信息
//******** Message ***************//
Timer0_Add_MessageFun('A', DistributionNetwork); //上次AP配网不成功,开机会自动进 “配网模式”
Timer0_Add_MessageFun('F', DistributionNetwork); //上下按键同时长按2S 配网
Timer0_Add_MessageFun('U', MotorUp); //上键 上行
Timer0_Add_MessageFun('D', MotorDown); //下键 下行
Timer0_Add_MessageFun('S', LetGo); //松手检测
Timer0_Add_MessageFun('S', WIFI_SubPosition); //上传位置
//******** Buzzer ***************//
Timer0_Add_MessageFun('C', Buzzer_DJI); //连接网络
Timer0_Add_MessageFun('U', Buzzer_Di); //上键 上行
Timer0_Add_MessageFun('D', Buzzer_Di); //下键 下行
//******** Pause ***************//
PauseUpload(); //未联网时,所有上传动作暂停
//******** ReStart ***************//
Timer0_Add_MessageFun('C', ReStartUpload); //开机 连接网络成功
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
Timer0_SYS_APP_LOOP();
Timer0_SYS_APP_LOOP_Message();
Timer0_SYS_APP_LOOP_Once();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
上传数据基于零妖代码结构。把所有上传项目独立,可配置不同项目不同上传频率。
//******************* 上传数据 **************************//
void WIFI_SubTemp(void)
{
WIFI_SubStation(0);
}
void WIFI_SubLlluminance(void)
{
WIFI_SubStation(1);
}
void WIFI_SubMotorMode(void)
{
WIFI_SubStation(2);
}
void WIFI_SubLimitStatus(void)
{
WIFI_SubStation(3);
}
void WIFI_SubMode(void)
{
WIFI_SubStation(5);
}
void WIFI_SubPosition(void)
{
WIFI_SubStation(6);
}
void WIFI_SubVoltage(void)
{
WIFI_SubStation(7);
}
ADC采用DMA多通道不连续采集。使用二维数组缓存数据,每次获取ADC测量值时均采样10次求平均后上传。
//0:Current; 1:Voltage; 2:Temp; 3:Photo; 4:Vref
uint16_t AdcValue[10][5];
float AdcActualValue[10][5];
uint8_t AdcValuePosition = 0;
void AdcInit(void)
{
//校准ADC
HAL_ADCEx_Calibration_Start(&hadc);
//开DMA
AdcValuePosition = 0;
// HAL_ADC_Start_DMA(&hadc, (uint32_t *)AdcValue[AdcValuePosition], 5);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
HAL_ADC_Stop_DMA(&hadc);
if(++AdcValuePosition >= 10)
AdcValuePosition = 0;
HAL_ADC_Start_DMA(&hadc, (uint32_t *)AdcValue[AdcValuePosition], 5);
}
温度和光强公式根据元件手册给出的温度-阻值、光强-阻值曲线拟合而成,辅以修正因子修正。
float AdcGetOneChannel(uint8_t channel)
{
uint8_t i;
float AdcReturn;
float PowerVoltage;
//校准ADC
HAL_ADCEx_Calibration_Start(&hadc);
//开DMA
AdcValuePosition = 0;
HAL_ADC_Start_DMA(&hadc, (uint32_t *)AdcValue[AdcValuePosition], 5);
HAL_Delay(1);
HAL_ADC_Stop_DMA(&hadc);
for(i = 0; i < 10; i++)
{
PowerVoltage = 1.2 * 4096 / AdcValue[i][4]; //电源电压,内部参考电压1.2V
switch(channel)
{
case 0: //电流,中点2.5v,增益100mV/A
AdcActualValue[i][0] = ( 2.5 - ( AdcValue[i][0] * PowerVoltage / 4096 )) / 0.1;
break;
case 1: //电压,1/11
AdcActualValue[i][1] = ( AdcValue[i][1] * PowerVoltage / 4096 ) * 11;
break;
case 2: //温度
//-10~50℃: y = -33.186 * x + 80.268 (R^2 = 0.998) //修正:-5
AdcActualValue[i][2] = -33.186 * ((float)AdcValue[i][2] * PowerVoltage / 4096) + 80.268 - 5;
break;
case 3: //光强: [<400lx] y = 30.24 * x^(-3.54) (R^2 = 0.9653); [>400lx] y = -16691 * x + 8262.9 (R^2 = 0.9764);
if(AdcValue[i][3] > (0.47 * 4096 / PowerVoltage))
AdcActualValue[i][3] = 30.24 * pow(( (float)AdcValue[i][3] * PowerVoltage / 4096 ), -3.54);
else
AdcActualValue[i][3] = -16691 * ((float)AdcValue[i][3] * PowerVoltage / 4096 ) + 8262.9;
break;
default:
break;
}
if(i == 0)
AdcReturn = AdcActualValue[i][channel];
AdcReturn += AdcActualValue[i][channel];
AdcReturn /= 2;
}
return AdcReturn;
}
蜂鸣器通过TIM14-1通道驱动。内置25个环形缓存区数组实现蜂鸣器音调、音量、延时功能。
#define BuzzerParameterMax 25
//蜂鸣器参数:频率(0-65535Hz),音量(0-100),时长(0-65535ms)
uint16_t BuzzerParameter[BuzzerParameterMax][3] = {{0xFFFF, 0, 0}};
uint8_t BuzzerPositionOut = 0, BuzzerPositionIn = 0, BuzzerCount = 0;
float BuzzerReload = 0.0, BuzzerTime = -1.0, BuzzerBeat = 0.0;
uint8_t BuzzerWorking = 0;
void BuzzerInterrupt(void) //蜂鸣器中断
{
if(BuzzerTime > 0) //延时中...
{
BuzzerTime -= BuzzerBeat;
}
else //切换
{
if(BuzzerCount == 0) //OVER
{
BuzzerWorking = 0;
HAL_TIM_PWM_Stop(&htim14, TIM_CHANNEL_1);
HAL_TIM_Base_Stop_IT(&htim14);
HAL_GPIO_WritePin(Buzzer_GPIO_Port, Buzzer_Pin, GPIO_PIN_RESET);
}
else //NEXT
{
BuzzerReload = 1000000.0 / BuzzerParameter[BuzzerPositionOut][0];
__HAL_TIM_SET_AUTORELOAD(&htim14, (uint16_t)BuzzerReload - 1);
__HAL_TIM_SET_COMPARE(&htim14, TIM_CHANNEL_1, (uint16_t)(BuzzerReload * BuzzerParameter[BuzzerPositionOut][1] * 0.01 * 0.9));
BuzzerBeat = BuzzerReload / 1000; //ms
BuzzerTime = BuzzerParameter[BuzzerPositionOut][2];
HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim14);
BuzzerPositionOut = (BuzzerPositionOut + 1) % BuzzerParameterMax;
BuzzerWorking = 1;
BuzzerCount--;
}
}
}
uint8_t BuzzerSetParameter(uint16_t frequncy, uint8_t volume, uint16_t time) //设置蜂鸣器参数
{
BuzzerParameter[BuzzerPositionIn][0] = frequncy;
BuzzerParameter[BuzzerPositionIn][1] = volume;
BuzzerParameter[BuzzerPositionIn][2] = time;
BuzzerPositionIn = (BuzzerPositionIn + 1) % BuzzerParameterMax;
if((++BuzzerCount == 1) && (!BuzzerWorking)) //从停止状态启动
{
BuzzerTime = -1.0;
BuzzerInterrupt();
}
if(BuzzerCount == BuzzerParameterMax)
return 0;
else
return 1;
}
PID使用增量式和位置式,分别用于速度环和位置环。
PID参数因电机而异,需要自行耐心调整。
void MotorIncrementPID(struct PID *pid, int16_t pidInput)
{
pid->PidInput = pidInput;
//Pid
pid->PidEt = pid->PidSetPoint - pid->PidInput;
pid->PidOutput += pid->PidKp * (pid->PidEt - pid->PidLastErr)
+ pid->PidKi * pid->PidEt
+ pid->PidKd * (pid->PidEt - 2 * pid->PidLastErr + pid->PidLastTwoErr);
//Pid限幅
pid->PidOutput = pid->PidOutput > pid->PidLimitUp ? pid->PidLimitUp : pid->PidOutput;
pid->PidOutput = pid->PidOutput < pid->PidLimitDown ? pid->PidLimitDown : pid->PidOutput;
//覆写
pid->PidLastTwoErr = pid->PidLastErr;
pid->PidLastErr = pid->PidEt;
}
void MotorPositionPID(struct PID *pid, int32_t pidInput)
{
pid->PidInput = pidInput;
//Pid
pid->PidEt = pid->PidSetPoint - pid->PidInput;
pid->PidEtSum += pid->PidEt;
pid->PidOutput = pid->PidKp * pid->PidEt + pid->PidKi * pid->PidEtSum
+ pid->PidKd * (pid->PidEt - pid->PidLastErr);
//Pid限幅
pid->PidOutput = pid->PidOutput > pid->PidLimitUp ? pid->PidLimitUp : pid->PidOutput;
pid->PidOutput = pid->PidOutput < pid->PidLimitDown ? pid->PidLimitDown : pid->PidOutput;
//覆写
pid->PidLastErr = pid->PidEt;
}
因为使用的编码器电机阻尼大,大约5V电压才能启动,为了避免电机从停止状态退出过程时间过长,在PID输出和电机间添加MotorCurve函数。保证低速时呈对数变化,高速时线性变化。该函数已在我的遥控车项目验证,效果非常棒。
float MotorCurve(float inPut)
{
float outPut = 0;
outPut = (inPut < 0 ? -1 : 1);
inPut = inPut > 265 ? 265 : inPut;
inPut = inPut < -265 ? -265 : inPut;
//输出曲线。类似对数曲线。
//分界点 15:266.2; 0.002425; -0.155; use
//可快速从驻车状态退出,且低速时刹车距离变短。
//f(x) = 256.7[227.6, 285.8] * exp(0.002453[0.001927, 0.002979] * x)
// + (-256.7[-297.8, -215.6]) * exp(-0.09653[-0.1635, -0.02957] * x);
/*下面两行可合并为一行。此处改写是因为MarkDown不识别*/
outPut *= 266.2 * (exp(0.002425 * (inPut < 0 ? - inPut : inPut));
output -= exp(-0.155 * (inPut < 0 ? - inPut : inPut))) + 0.5;
return outPut;
}
速度环:
void MotorSpeedInit(void)
{
//PID: 5, 0.6, 1
MotorInit(&PidSpeed);
MotorSetPidParameter(&PidSpeed, 5, 0.6, 1);
PidSpeed.PidLimitUp = 150;
PidSpeed.PidLimitDown = -150;
}
从速度环曲线可以看出,调整时间大约0.3s。过程存在一点超调,正常现象,可以保证更快速的调整。
速度环调试曲线
位置环:
void MotorPositionInit(void)
{
//0.6, 0, 13
MotorInit(&PidPosition);
MotorSetPidParameter(&PidPosition, 0.6, 0, 13); //微调该参数
PidPosition.PidLimitUp = 200;
PidPosition.PidLimitDown = -200;
}
位置环中间线性段受限于电机输出限幅。后段波动是施加扰动和释放扰动造成。
位置环曲线
按键部分删除了原来的Button_Loop实现函数。添加了User_Button函数,用于识别单按键按下,双按键按下,按键松开等动作。并向系统发布消息。
void UserButton(void)
{
if( ((Button_ReadIO(0)) && (Button_ReadIO(1)))
&& (ButtonTwoTime || Button_Timer[0] || Button_Timer[1]) ) //松手检测
{
Timer0_SendMessage('S'); //松手
}
if((!Button_ReadIO(0)) && (!Button_ReadIO(1))) //同时按下
{
ButtonTwoTime++;
Button_Timer[0] = 0;
Button_Timer[1] = 0;
}
else
{
ButtonTwoTime = 0;
if(Button_ReadIO(0) == 0)
Button_Timer[0]++;
else
Button_Timer[0] = 0;
if(Button_ReadIO(1) == 0)
Button_Timer[1]++;
else
Button_Timer[1] = 0;
}
if(ButtonTwoTime > 65500)
ButtonTwoTime = 65500;
if(Button_Timer[0] > 65500)
Button_Timer[0] = 65500;
if(Button_Timer[1] > 65500)
Button_Timer[1] = 65500;
if(ButtonTwoTime > Button_L_Time)
{
Timer0_SendMessage('F'); //恢复出场设置
return;
}
if(ButtonTwoTime == 0) //按键未同时按下
{
if((Button_Timer[0] > Button_G_Time) && (Button_Timer[0] < Button_L_Time))
{
Timer0_SendMessage('U'); //上
Button_Timer[0] = 65500;
return;
}
if((Button_Timer[1] > Button_G_Time) && (Button_Timer[1] < Button_L_Time))
{
Timer0_SendMessage('D'); //下
Button_Timer[1] = 65500;
return;
}
}
}
为了实现一个消息可以对应多个功能函数,我们修改了Message循环部分代码。修改后的代码,可以实现:按下上键,电机速度环模式上行,同时蜂鸣器“滴”提示音。下键同理。
//******** Message ***************//
Timer0_Add_MessageFun('A', DistributionNetwork); //上次AP配网不成功,开机会自动进 “配网模式”
Timer0_Add_MessageFun('F', DistributionNetwork); //上下按键同时长按2S 配网
Timer0_Add_MessageFun('U', MotorUp); //上键 上行
Timer0_Add_MessageFun('D', MotorDown); //下键 下行
Timer0_Add_MessageFun('S', LetGo); //松手检测
Timer0_Add_MessageFun('S', WIFI_SubPosition); //上传位置
//******** Buzzer ***************//
Timer0_Add_MessageFun('C', Buzzer_DJI); //连接网络
Timer0_Add_MessageFun('U', Buzzer_Di); //上键 上行
Timer0_Add_MessageFun('D', Buzzer_Di); //下键 下行
//系统循环执行-邮箱处理
void Timer0_SYS_APP_LOOP_Message(void)
{
signed char i = 0, j = 0;
if(Timer0_Handler_Flag_Message == 0)
return;
Timer0_Handler_Flag_Message = 0;
for(i = 0; i < Timer_0_List_Count; i++) //调用消息队列中的函数
{
if(Timer0_Message_Struct.MessageQueue[i])
{
for(j = 0; j < Timer_0_List_Count; j++)
{
if(Timer0_Message_Struct.Flag[j] == 1)
{
if(Timer0_Message_Struct.MessageQueue[i] == Timer0_Message_Struct.MessageList[j])
{
Timer0_Message_Struct.MessageFun_Point_List[j]();
// j = Timer_0_List_Count + 10; //注释则可以一个消息对应多个函数
}
}
}
Timer0_Message_Struct.MessageQueue[i] = 0;
}
}
}
为了实现一些功能,我们在零妖的任务调度器基础上进行了修改。
对Flag部分重新规划。
unsigned char Flag[Timer_0_List_Count]; //0:空;1:运行;2:暂停;10:暂停所有
修改后的任务调度器添加了:删除、暂停、恢复、暂停所有、恢复所有功能。
//************** 任务 *****************//
//添加
unsigned char Timer_0_Add_Fun(unsigned long Time,void (*Fun)(void))
{
signed char i = 0;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Struct.Flag[i] == 0) //空的
{
Timer0_Struct.Flag[i] = 1;
Timer0_Struct.Counter[i] = 0;
Timer0_Struct.Fun_Point_List[i] = Fun;
Timer0_Struct.Timer[i] = Time-1;
return 1;
}
}
return 0;
}
//删除
unsigned char Timer_0_Del_Fun(void (*Fun)(void))
{
signed char i=0;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前
{
if(Fun == Timer0_Struct.Fun_Point_List[i])
{
Timer0_Struct.Flag[i] = 0;
Timer0_Struct.Counter[i] = 0;
return 1;
}
}
return 0;
}
//暂停
unsigned char Timer_0_Pause_Fun(void (*Fun)(void))
{
signed char i=0;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前
{
if((Fun == Timer0_Struct.Fun_Point_List[i])
&& (Timer0_Struct.Flag[i] == 1))
{
Timer0_Struct.Flag[i] = 2;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
//恢复
unsigned char Timer_0_ReStart_Fun(void (*Fun)(void))
{
signed char i=0;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前
{
if(Fun == Timer0_Struct.Fun_Point_List[i])
{
Timer0_Struct.Flag[i] = 1;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
//暂停所有
unsigned char Timer_0_Pause_All(void)
{
signed char i=0;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Struct.Flag[i] == 1)
{
Timer0_Struct.Flag[i] = 10;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
//恢复 通过“暂停所有”暂停的任务
unsigned char Timer_0_ReStart_All(void)
{
signed char i=0;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Struct.Flag[i] == 10)
{
Timer0_Struct.Flag[i] = 1;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
为了实现一些功能,我们添加了Flag,并对其重新规划。
unsigned char Flag[Timer_0_List_Count]; //0:空;1:运行;2:暂停;10:暂停所有
修改后的消息调度器添加了:删除、暂停、恢复、暂停所有、恢复所有功能。
//************** 消息 *****************//
//添加
unsigned char Timer0_Add_MessageFun(unsigned char Message,void (*Fun)(void))
{
signed char i;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Message_Struct.Flag[i] == 0)
{
Timer0_Message_Struct.Flag[i] = 1;
Timer0_Message_Struct.MessageList[i] = Message;
Timer0_Message_Struct.MessageFun_Point_List[i] = Fun;
return 1;
}
}
return 0;
}
//删除
unsigned char Timer0_Del_MessageFun(void (*Fun)(void))
{
signed char i;
for(i = (Timer_0_List_Count - 1); i >= 0; i--) //从后往前
{
if(Fun == Timer0_Message_Struct.MessageFun_Point_List[i])
{
Timer0_Message_Struct.Flag[i] = 0;
Timer0_Message_Struct.MessageList[i] = 0x00;
return 1;
}
}
return 0;
}
//暂停
unsigned char Timer0_Pause_MessageFun(void (*Fun)(void))
{
signed char i;
for(i = (Timer_0_List_Count - 1); i >= 0; i--) //从后往前
{
if((Fun == Timer0_Message_Struct.MessageFun_Point_List[i])
&& (Timer0_Message_Struct.Flag[i] == 1))
{
Timer0_Message_Struct.Flag[i] = 2;
return 1;
}
}
return 0;
}
//恢复
unsigned char Timer0_ReStart_MessageFun(void (*Fun)(void))
{
signed char i;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前删
{
if(Fun == Timer0_Message_Struct.MessageFun_Point_List[i])
{
Timer0_Message_Struct.Flag[i] = 1;
return 1;
}
}
return 0;
}
//暂停 所有
unsigned char Timer0_Pause_MessageAll(void)
{
signed char i;
for(i = (Timer_0_List_Count - 1); i >= 0; i--) //从后往前
{
if(Timer0_Message_Struct.Flag[i] == 1)
{
Timer0_Message_Struct.Flag[i] = 10;
}
}
return 0;
}
//恢复 所有
unsigned char Timer0_ReStart_MessageAll(void)
{
signed char i;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前删
{
if(Timer0_Message_Struct.Flag[i] == 10)
{
Timer0_Message_Struct.Flag[i] = 1;
}
}
return 0;
}
同时长按上下两按键2S,进入AP热点配网模式。
//配网模式
void DistributionNetwork(void)
{
MotorPause();
Timer_0_Pause_All(); //暂停所有任务
Timer0_Pause_MessageAll(); //暂停所有消息
Buzzer_BiPu(); //奏乐
//开始配网流程
Timer_0_Add_Fun_Once(1000, WIFI_CloseRTE); //关闭回显
Timer_0_Add_Fun_Once(1500, WIFI_ResetAuthor);//解除绑定关系,
Timer_0_Add_Fun_Once(2000, WIFI_SendAT); //AT
Timer_0_Add_Fun_Once(2500, WIFI_SetILOP); //设置三元组
Timer_0_Add_Fun_Once(3000, WIFI_StartILOP); //开启ILOP
// Timer_0_Add_Fun_Once(5000, WIFI_StartAWS); //路由器配网
Timer_0_Add_Fun_Once(5000, WIFI_StartAP); //热点配网
//其他操作
Timer_0_Add_Fun(100, WIFI_LED_Loop); //WIFI指示灯快闪 5Hz
Timer_0_Add_Fun(100, Mode_LED_Loop); //快闪 5Hz
Timer_0_ReStart_Fun(Uart2_CheckMessageLoop); //帧处理函数
// Timer0_Add_MessageFun('C', WIFI_SubPosition); //连接成功 主动上报一次位置
Timer0_Add_MessageFun('C', NetworkConnected); //连接成功
Timer0_Add_MessageFun('E', Buzzer_DiLong); //Error
Timer0_Add_MessageFun('A', Buzzer_DiDiDi); //AP已开启
}
If the network configuration is successful, execute the NetworkConnected function. Delete the tasks and messages created during the network distribution process, enable the motor, and restore all tasks and messages before network distribution. Play the music at the end and return to the original mission. No need to restart the device, saving restarting & networking time. This is quite different from Zero Demon code. Song Gong's implementation process is: entering the network distribution process and reinitializing all tasks and messages. Create the tasks and messages required for network distribution. After successful network distribution, call the software restart command to re-enter the normal working mode. This will restart the WIFI cloud process one more time.
//配网成功 已连接
void NetworkConnected(void)
{
//删除 配网 过程创建的所有任务
Timer_0_Del_Fun(WIFI_LED_Loop); //WIFI指示灯
Timer_0_Del_Fun(Mode_LED_Loop); //
Timer0_Del_MessageFun(NetworkConnected); //连接成功
Timer0_Del_MessageFun(Buzzer_DiLong); //Error
Timer0_Del_MessageFun(Buzzer_DiDiDi); //AP已开启
MotorReStart();
Timer_0_ReStart_All(); //恢复 配网前所有任务
Timer0_ReStart_MessageAll(); //恢复 配网前所有消息
Buzzer_DoToXi(); //奏乐
}
This project refers to the Zero Demon code framework, deletes some unused functions, modifies some underlying functions, and adds some customized functions.
The equipment basically realizes the local/cloud control function of electric curtains and can upload the equipment operating status at a certain frequency. When equipment fails, it has certain processing capabilities. Has a more friendly way of interaction. Proper cost control.
The program design adopts the task/message scheduler mode, which can easily add and delete functions without affecting the operation of other functions.
Due to limited time, energy, and ability, the equipment still has many problems that need to be repaired. The known problems are listed below. If you have the time, energy, and ability, you can try to repair them.
Thanks to Lichuang EDA for organizing the event and providing afree whoringOpportunities to learn.
Thanks to the Zero Yao source code, I have learned a lot about task/message scheduler, ring buffer, JSON string comparison and other knowledge, and I have benefited a lot.
Thanks to guest editor: Mr. 481978A for uploading the file, which solved the frustrating network distribution process.
All reference designs on this site are sourced from major semiconductor manufacturers or collected online for learning and research. The copyright belongs to the semiconductor manufacturer or the original author. If you believe that the reference design of this site infringes upon your relevant rights and interests, please send us a rights notice. As a neutral platform service provider, we will take measures to delete the relevant content in accordance with relevant laws after receiving the relevant notice from the rights holder. Please send relevant notifications to email: bbs_service@eeworld.com.cn.
It is your responsibility to test the circuit yourself and determine its suitability for you. EEWorld will not be liable for direct, indirect, special, incidental, consequential or punitive damages arising from any cause or anything connected to any reference design used.
Supported by EEWorld Datasheet