通通

[Training Camp_Advanced Class] Roller Curtain General

 
Overview

roller blind general

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:

HD photosHD photosHD photos


Preface

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.

    • -

Instructions

1. Distribution network

  1. At any time after powering on, press the two buttons simultaneously for two seconds (it can also be recognized by pressing the two buttons one after another).
  2. The device plays the music "Bi~~~ Pu" and starts the network configuration process. The blue and green indicator lights flash quickly.
  3. Open "Cloud Intelligence" on your mobile phone, scan the code, and follow the prompts to start network configuration. (Scan the QR code to download the APP).
  4. After waiting for 5 seconds, the device plays three beeps, and the device turns on the AP hotspot.
  5. On the mobile phone, enter the name and password of the home router to which the device will be connected, and click "Start Connection" to start network configuration. (Lower version Android systems can directly access the adh_xxx hotspot, while higher version Android systems need to manually connect to the hotspot. After the hotspot connection is successful, it will be disconnected and return to the APP interface).
  6. Wait patiently for the device to be uploaded to the cloud. If the device plays "di~~~~" during the process, indicating an error, you can power off and restart and try again from step 2.
  7. When the device is successfully configured, the music "1234567" will play and the blue and green indicators will flash slowly. Note: An unexpected power outage at any time during the network distribution process causes the device to fail to configure the network. It will automatically enter the network distribution mode the next time it is powered on. Start from step 2 and perform network configuration operations in sequence until the network configuration is completed.

2. Network control

  • After the equipment is equipped with a network, it can be controlled via the Internet.
  • After the device is successfully connected to the Internet, the music "123" will play and the blue indicator light will start to flash slowly.
  • Press the up or down button, and the curtains will move up or down at a constant speed. After letting go, the APP will automatically refresh the position of the curtains.
  • Slide the curtain position slider on the APP, and the curtain will stop and lock when it reaches the specified position.
  • Click "Quick Operation" on the APP to directly control the curtains to the preset position.
  • Click "Status Information" on the APP to view device temperature, voltage, light intensity and other data.
  • Distribution network QR code: (If the APP is not installed, you can scan the code to download)

Distribution network QR code


Table of contents

One device selection

  • power supply
  • MCU
  • WIFI
  • motor driven
  • peripherals

Second schematic design

  • power supply
  • MCU
  • EMW3080
  • motor driven
  • peripherals

Three PCB design

  • Power supply & motor drive
  • MCU
  • EMW3080
  • peripherals

Four welding

Five APP design

Six program debugging

  • Code migration & data upload
  • ADC&DMA sampling
  • buzzer driver
  • PID and parameter tuning
  • Button control
  • Task/message scheduler rewrite
  • Distribution network mode

in conclusion


One device selection

This activity requires the design of an IoT device. In order to control costs, device selection should be as cost-effective as possible.

1.Power supply

  • The power input socket adopts DC005 socket and is designed to withstand 30V@3A.

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.

  • DCDC uses LP6498AB6F chip and is designed to output 5.12V@1A.

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.

  • The LDO uses HX9193-33GB and is designed to output 3.3V@600mA.

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.


2. MCU

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.


3.WIFI

The WIFI selected is EMW3080V2 (Alibaba Cloud Feiyan firmware). I have no experience in WIFI selection, so I just follow the course.


4. Motor drive

  • The motor drive part uses RZ7899 driver chip.

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.

  • The current sensor uses CC6900SO-5A chip.

Datasheet: CC6900SO-5A

Package: SOP-8

Gain: 400mV/A

Current: 5A


5. Peripherals

The peripheral design includes: a passive buzzer, two buttons, and two LED indicators. Encoder interface, limit switch interface.


Second schematic design

According to personal design habits, the schematic diagram is divided by function and designed on 5 A4 drawings. Introduced below.

1.Power supply

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.

Schematic - Power Supply

                                        原理图-电源

2. MCU

MCU部分主要设计 晶振电路,复位电路,SWD下载接口

晶振 采用SMD-3225封装的8MHz无源晶振,该封装对烙铁焊接不友好。晶振电路主要由晶振和两个22pF无极性陶瓷电容构成。

复位电路 由10k上拉电阻和0.1uF电容构成,主要完成上电复位功能。

SWD接口 用于调试和下载程序,引出了SWCLK、SWDIO、NRST,采用XH2.5-4P端子接口。引出NRST引脚,即使程序中未使能SWD调试接口仍能下载、调试程序。

Schematic-MCU

                                        原理图-MCU

3. EMW3080

EMW3080电路主要包括 电源滤波、串口、BOOT、测试点

电源滤波 采用0.1uF和10uF组合的形式;根据手册要求,电源采用3.3V。

串口 通过0R电阻交叉连接到MCU串口;GPIO23根据手册要求通过10k上拉;

BOOT 引脚预留0R电阻接地,但不焊接;EN引脚通过0R电阻连接到MCU和按键,主要完成WIFI的复位工作。

测试点 包括串口的TXD和RXD接口。调试时连接串口,可监视MCU和WIFI间交换的所有数据。

连接外部串口监视数据时,MCU串口需设置为开漏+上拉模式,否则会导致MCU与WIFI间数据乱码。

Schematic-EMW3080

                                        原理图-EMW3080

4. 电机驱动

电机驱动部分主要完成 电流传感器电流采样、电源电压采样、电机驱动、测试点

电源电压采样 采用分压电阻结构,通过100k和10k电阻获得低的采样电压送入MCU-ADC接口。

电流采样 按照CC6900SO-5A官方手册绘制即可。

电机驱动芯片 按照官方手册绘制,注意输入和输出接口走线宽度。同时在电机接口上设计四个二极管钳位。电机采用5.0-2P接口,方便拆装。

测试点 主要有电流采样点、电压采样点、电机驱动正反转信号点,便于调试时确定状态。

注意功率地与信号地分开,并连接

Schematic - Motor Drive

                                        原理图-电机驱动

5. 外设

外设主要设计 无源蜂鸣器、光敏电阻、热敏电阻、编码器接口、按键、LED指示灯、测试点、机械孔

无源蜂鸣器 需连接到TIM-PWM输出引脚,可以通过调整TIM装载值和比较值控制蜂鸣器音调和音量。

热敏电阻光敏电阻 需要串联一个已知阻值的电阻接入电路,通过MCU-ADC测量中间点电压反向推算出外部温度和光强。

热敏电阻和光敏电阻测量精度非常有限,即使程序中加入修正,采样值仍可能和实际值偏差较大。对温度和光强精度要求高的场所慎用。

编码器接口 是为了电机的位置环、速度环PID设计,可连接AB相编码器。接口内已设计上拉电阻和硬件消抖电路,编码器电源通过两个0R电阻在5V和3.3V间选择,注意不可同时连接5V和3.3V电阻。

按键 用于控制窗帘的上拉、下拉动作,同时在必要时刻充当配网开关。

蓝色LED指示灯 用于指示WIFI连接状态,绿色LED 用于指示设备运行状态。

测试点 可测量光敏电阻和热敏电阻输出电压。

机械孔 是四个M3螺丝孔,方便设备通过螺丝安装在需要的地方。

Schematic - Peripherals

                                        原理图-外设

三 PCB设计

PCB设计经验不足,在此抛砖引玉。如有错误之处,还望大佬不惜赐教。

PCB renderings

                                        PCB效果图

1. 电源&电机驱动

电源电机驱动 主要注意走线宽度、功率地和信号地分开、端子下面挖空防止接地短路等。

PCB-Power & Motor Driver

            PCB-电源&电机驱动

左侧为 LDO,右侧为 DCDC。注意电感离DCDC芯片近一些,电感下面不要走信号线。(此图为错误示范)

PCB-DCDC&LDO

                            PCB-DCDC&LDO

2. MCU

MCU主要注意晶振连线短一些,滤波电容靠近MCU电源引脚。

PCB-MCU

                            PCB-MCU

3. EMW3080

EMW3080按照官方手册要求,1、2、24、25脚不接,天线前方、左右留16mm净空区。搜索EDA中所有的封装都不完全满足官方手册要求,我自己画了一个。

PCB-EMW3080

                        PCB-EMW3080

4. 外设

热敏 避开发热区域;光敏 避开LED区域;编码器接口 放在电机端子旁边,方便连接;按键LED指示灯 放在板子下方,方便操作。

PCB-Peripheral

                             PCB-外设

四 焊接

拿到PCB,准备焊接工具,开始焊接。

PCB

                        PCB

因为部分PCB中有一些封装对烙铁十分不友好。所以,上风枪。

tool

                    工具

下面简述下焊接步骤和注意事项。

  • 首先,准备0.5mm左右的焊锡丝、焊锡膏、助焊剂。清理烙铁头,烙铁温度350℃。尖嘴镊子。提前释放身上静电。
  • 第一步,焊接DCDC芯片及外围电路。电感封装问题,只能用风枪和焊锡膏焊。焊接完成后,焊接DC005接口。接入12V电源,使用万用表电压档测量5V测试点电压是否在5.12V左右。若电压不正确,需核对反馈电阻阻值是否正确。
  • 第二步,焊接LDO电路。焊完后上电测试输出电压是否在3.3V左右。
  • 第三步,电源没有问题后,焊接其他元件。顺序没有要求,一般由高度低的元件开始焊接。

单片机可以使用针管挤焊锡膏在焊盘上,摆好单片机,烙铁走一遍就能焊好,不连锡,贼好用。

电容焊盘也较短,需要焊锡膏和烙铁配合焊接。

PCB注意有几个元件不能焊接。分别是:编码器电源5V处0R电阻,EMW3080的BOOT接地电阻。

焊接完成后,效果如下。

Physical map

                                        实物图

五 APP设计

APP设计采用阿里云物联网平台。具体过程可参考B站课程回放

一些属性参数如下:(部分功能未使用)

Property parameters

                                    属性参数

Triplet information

                                    三元组信息

APP main interface

                        APP主界面

六 程序调试

程序调试主要按功能块调试。调试日志按以下顺序依次进行:代码移植、上传数据、蜂鸣器驱动、ADC&DMA采样、PID&参数整定、按键控制、任务调度器改写、配网模式

1. 代码移植&上传数据

零妖大佬给的例程是基于51单片机。51程序和STM32不兼容,需要移植一些底层代码。代码平台 CubeMX&HAL 库,MDK-ARM 5.27

移植需要一定的软件操作基础,回忆的过程,不完整。

工程代码可移步附件下载查看。

移植前可通过串口先让WIFI上云,减小移植难度。

首先打开 CubeMX 软件,选择 STM32F030K6T6 ,使能外部晶振,使能SWD接口。勾选必要的外设。设置时钟48MHz。填写工程名称,保存位置。选择使用的IDE为MDK-ARM 5.27,勾选“为每一个外设生成.c/.h文件”。点击“生成代码”。

Pin configuration

                            引脚配置

clock tree

                                        时钟树

代码生成后,点击“打开工程”,自动调用Keil软件。

首先,在keil左侧Project中添加一个文件夹,用来存放我们的.c文件。双击该文件夹,加入Code_User文件夹下所有文件。

然后,点击 魔术棒 ,点击“C/C++”选项卡,点击“Include Paths”后面三点,添加“Code_User”文件夹路径。

编译文件,不出意外,您会收获 error(s)

下面开始解决这些错误:

  1. 大多数错误是由于 #include 造成的,这是51的头文件,32不使用,删除 所有该语句。
  2. Button.c 文件实现按键的短按、长按识别。该设备只需识别单按键和双按键,修改 其中的Button_Loop 函数。
  3. DeviceName.c 文件操作三元组数据。我们把三元组数据移植到 usart.c 文件下,删除 该文件。
  4. DS18B20.c 文件用于读取温度传感器数据。我们温度采用热敏电阻配合ADC,不涉及该文件,删除
  5. IAP_EEPROM.c 文件操作 EEPROM。32没有,删除 文件。
  6. main.c 文件是设备主逻辑实现。复制内容到CubeMX创建的 main.c 文件中,删除 该文件。
  7. Mode.c 文件操作LED和继电器。我们没有继电器,LED灯移植到 WIFI.c 文件下,删除 该文件。
  8. Relay.c 文件操作继电器,删除
  9. Timer0.c 文件实现任务调度器,需要修改定时器底层。删除 定时器初始化结尾前代码,并添加“HAL_TIM_Base_Start_IT(&htim17);”启动定时器。
  10. Uart_1.c 文件用于转发串口2数据到电脑。STM32F030K6 只有一个串口,删除 该文件。
  11. Uart2.c 文件主要和WIFI交换数据。修改 底层代码,使用STM32的 DMA+空闲中断 接收不定长数据。
  12. WDT.c 文件实现看门狗。未使用,删除

其他错误可双击编译结果跳转至指定位置。具体问题自行查阅资料修改,不赘述。 首次移植可只删除和移植串口代码,其他无关紧要的稍后移植。 如果一切顺利,编译没有错误,可下载到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);
}

2. ADC&DMA采样

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;
}

3. 蜂鸣器驱动

蜂鸣器通过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;
}

4. PID及参数整定

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。过程存在一点超调,正常现象,可以保证更快速的调整。

Speed ​​loop debugging curve

                                    速度环调试曲线

位置环:

void MotorPositionInit(void)
{
    //0.6, 0, 13
    MotorInit(&PidPosition); 
    MotorSetPidParameter(&PidPosition, 0.6, 0, 13);     //微调该参数
    PidPosition.PidLimitUp = 200;
    PidPosition.PidLimitDown = -200;
}

位置环中间线性段受限于电机输出限幅。后段波动是施加扰动和释放扰动造成。 position loop curve

                                    位置环曲线

5. 按键控制

按键部分删除了原来的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;
        }
    }
}

6. 任务/消息调度器改写

为了实现一些功能,我们在零妖的任务调度器基础上进行了修改。

对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;
}

7. 配网模式

同时长按上下两按键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();                            //奏乐
}

in conclusion

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.

  1. The soft and hard limits are not implemented, and there is a risk of damage to the curtains due to over-limit operation. After the curtain exceeds the limit, the APP cannot display the actual position;
  2. PID parameter setting needs to be done manually.
  3. When the external power supply is a switching power supply, reverse rotation of the motor will trigger the overvoltage protection of the switching power supply. Adding SS54 diodes to prevent backflow can solve the problem. However, when the motor reverses, the device voltage will be pulled up, which may cause overvoltage protection of the motor driver chip when using a 24V power supply.
  4. Photoresistor and thermistor measurement values ​​are inaccurate.

Acknowledgments

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.

参考设计图片
×
Design Files
 
 
Search Datasheet?

Supported by EEWorld Datasheet

Forum More
Update:2025-07-17 11:24:23

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
community

Robot
development
community

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号