机器谱
【U037】如何使用Controller V1.0软件

作者:机器谱

图文展示3264(1)

图文展示3264(1)

副标题

MCU-F4
Arduino
MCU-F4

一、支持平台

Win10及以上系统直接可用,Win7需要先安装.net框架组件。


二、电子硬件

三、操作步骤

上位机:Controller 1.0

下位机编程环境:keil5

① 使用MCU-F4主控板调试舵机之前,需要将STM32-controller下位机程序\servo_ctrl中的程序文件(MDK-ARM\project.uvprojx)下载到MCU-F4主控板中。

主控板MCU-F4 v2.0
扩展板SH-F4TT扩展板、SH-F4ST扩展板


硬件类型型号 / 规格
主控板MCU-F4 v2.0
扩展板SH-F4TT 扩展板


①STM32-controller下位机程序
②Controller 1.0.exe
③例程-4自由度机械臂搬运
④4自由度机械臂3D文件
⑤CH340芯片驱动
⑥六自由度机械臂资料包(例程、3D文件)


/**

* @file main.c

* @brief 主程序文件

*

* 此代码基于MCU-F4开发板、SH-F4TT扩展板和4自由度舵机机械臂(4个舵机+1个夹爪),使用了以下外设模块:

* - 舵机:连接在SH-F4TT扩展板的舵机接口。

*

* 程序功能:

* 1. 初始化延时功能和舵机控制引脚PB14、PB15、PE5、PE6、PE9。

* 2. 先让舵机回归到初始位置,然后按照move_buf数组内运动。

* 3. 整个程序只完成一次完整动作。

*

* 外设连接:

* - 舵机连接在 SH-F4TT扩展板 的指定引脚上,控制舵机的运动。

* - 按照从末端夹爪到基座的顺序,舵机分别对应连接引脚PB14、PB15、PE5、PE6、PE9。

* - 舵机通过 PWM 信号控制,角度从0度到180度变化。

*

* @author wjw

* @date 2025/1/10

* @author Aaron.Liu

* @date 2025/12/30

* @author lzl

* @date 2026/2/21

*/

 

#include "delay.h"

#include "servo.h"

#include "stm32f4xx.h"

#include "uart.h"

#include <stdlib.h>

#include <string.h>




//上位机获得的数组放这里

int move_buf[]={

1486,1455,959,1193,1500,   //初始姿态

1486,664,1822,1193,1500,   //姿态1

1054,664,1822,1193,1500,   //姿态2

1054,1599,1391,1193,1500,   //姿态3

1054,1599,1391,1193,925,   //姿态4

1054,664,1714,1193,925,    //姿态5

1539,664,1714,1193,925,    //姿态6

1539,1455,959,1193,925,    //姿态7

1539,1455,959,1193,1500    //回到初始姿态

};




#define SERVO_MIN_PWM 500

#define SERVO_MAX_PWM 2500

#define SERVO_NUM5       //舵机的数量

#define SERVO_BUF_SIZE sizeof(move_buf)/sizeof(move_buf[0])

#define UPDATE_FREQUENCY 125   // 每秒更新的次数


GPIO_TypeDef* servo_gpio[SERVO_NUM]={GPIOB,GPIOB,GPIOE,GPIOE,GPIOE};


uint32_tservo_pin[SERVO_NUM]={GPIO_Pin_14,GPIO_Pin_15,GPIO_Pin_5,GPIO_Pin_6,GPIO_Pin_9};


void pwm_interpolation_sync(int pwm_start[], int pwm_end[], int steps,GPIO_TypeDef* GPIOs[], uint32_t Pins[], int num_servos) {

float pwm_delta[5];

    for (int i = 0; i < num_servos; i++) {

        pwm_delta[i] = (float)(pwm_end[i] - pwm_start[i]) / steps;

    }


    for (int i = 0; i < steps; i++) {

        int pwm_current[5];

        for (int j = 0; j < num_servos; j++) {

            pwm_current[j] = (int)(pwm_start[j] + pwm_delta[j] * (i + 1));

           

            if (pwm_current[j] >= SERVO_MAX_PWM) pwm_current[j] = SERVO_MAX_PWM;

            if (pwm_current[j] <= SERVO_MIN_PWM) pwm_current[j] = SERVO_MIN_PWM;

        }


        for (int j = 0; j < num_servos; j++) {

            Servo_SetAngle_Pulse(GPIOs[j], Pins[j], pwm_current[j]);

        }

       

        Delay_ms(1000/UPDATE_FREQUENCY); // 保持原有延时,但所有舵机同步更新

    }

  for (int j = 0; j < num_servos; j++) {

        Servo_SetAngle_Pulse(GPIOs[j], Pins[j], pwm_end[j]);

    }

Delay_ms(50); //让舵机稳定稳定一段时间

}



void back_home()

{

Servo_SetAngle_Pulse(GPIOB, GPIO_Pin_14, move_buf[0]);   // 更新 PWM 值

Servo_SetAngle_Pulse(GPIOB, GPIO_Pin_15, move_buf[1]);   // 更新 PWM 值

Servo_SetAngle_Pulse(GPIOE, GPIO_Pin_5, move_buf[2]);   // 更新 PWM 值

Servo_SetAngle_Pulse(GPIOE, GPIO_Pin_6, move_buf[3]);   // 更新 PWM 值

Servo_SetAngle_Pulse(GPIOE, GPIO_Pin_9, move_buf[4]);   // 更新 PWM 值

}


void servo_move()

{

  int total_groups = SERVO_BUF_SIZE / SERVO_NUM;

    int steps_per_group = 60; // 每组间插值步数

for (int i = 0; i < total_groups - 1; i++) {

        int start_pwm[SERVO_NUM] = {

            move_buf[i*SERVO_NUM],

            move_buf[i*SERVO_NUM+1],

            move_buf[i*SERVO_NUM+2],

            move_buf[i*SERVO_NUM+3],

            move_buf[i*SERVO_NUM+4]

        };

        int end_pwm[SERVO_NUM] = {

            move_buf[(i+1)*SERVO_NUM],

            move_buf[(i+1)*SERVO_NUM+1],

            move_buf[(i+1)*SERVO_NUM+2],

            move_buf[(i+1)*SERVO_NUM+3],

            move_buf[(i+1)*SERVO_NUM+4]

        };

pwm_interpolation_sync(start_pwm, end_pwm, steps_per_group, servo_gpio, servo_pin, SERVO_NUM);

    }

}


int main(void) {

  // 初始化外设

  Delay_Init();

  Servo_Init(GPIOB, GPIO_Pin_14);

Servo_Init(GPIOB, GPIO_Pin_15);

Servo_Init(GPIOE, GPIO_Pin_5);

Servo_Init(GPIOE, GPIO_Pin_6);

Servo_Init(GPIOE, GPIO_Pin_9);

Delay_ms(1000);


back_home();

Delay_ms(1000);

servo_move();


  while (1) {


  }

}



上位机舵机编号SH-F4TT扩展板舵机引脚
P0PB14
P1PB15
P2PE5
P3PE6
P4PE9
P5PE11
P6PE13
P7PE14


上位机舵机编号SH-F4ST扩展板舵机引脚
P0PB14
P1PB15
P2PE5
P3PE6
P4PE9
P5PE11
P6PE13
P7PE14
P8PC6
P9PC7
P10PC8
P11PC9
P12PD12
P13PD13
P14PD14
P15PD15
P16PB8
P17PB9


/**

* @file main.c

* @brief 主程序文件

*

* 此代码基于MCU-F4开发板,配合SH-F4TT扩展板或者SH-F4ST扩展板使用。此外,还使用了以下外设模块:

* - 舵机:连接在扩展板的舵机接口上。

*

* 程序功能:

* - 通过串口接收来自上位机的数据,并将数据进行分析,最后驱动对应的舵机。

*

* 外设连接:

* - 舵机连接在扩展板的指定引脚上,控制舵机的运动。

* - SH-F4TT扩展板有8个舵机接口,在本程序中表现为0~7,对应Controller 软件的P0~P7面板

* - SH-F4ST扩展板有18个舵机接口,在本程序中表现为0~17,对应Controller 软件的P0~P17面板

* - 舵机通过 PWM 信号控制,角度从0度到180度变化。

*

* @author wjw

* @date 2025/1/10

* @author Aaron.Liu

* @date 2025/12/30

* @author lzl

* @date 2026/1/20

*/

#include "delay.h"

#include "servo.h"

#include "stm32f4xx.h"

#include "uart.h"

#include <stdlib.h>

#include <string.h>


unsigned char buf[256]= {0};

int rcnt= 0;

intanalyse_flag= 0;

int p_flag=0;

int b_flag=0;

int t_flag=0;

int move_flag=0;


typedef struct{

char pin[6];

char pulse[8];

GPIO_TypeDef* GPIOx;

uint32_t GPIO_Pin;

}Servo_info;


Servo_info servo_info;


void analyse_data(unsigned char *buf)

{

b_flag = p_flag = t_flag = -1; // 初始化为无效值

char len=strlen((char*)buf);

for(int i=0;i<len;i++)

{

switch(buf[i])

{

case '#' :

{

b_flag=i;

break;

}

case 'P' :

{

p_flag=i;

memset(servo_info.pin,0,sizeof(servo_info.pin));

memcpy(servo_info.pin,buf+b_flag+1,p_flag-b_flag-1);

break;

}

case 'T' :

{

t_flag=i;

memset(servo_info.pulse,0,sizeof(servo_info.pulse));

memcpy(servo_info.pulse,buf+p_flag+1,t_flag-p_flag-1);

break;

}

case '\n':

{break;}

default   :

break;

}

}

if(!strcmp(servo_info.pin,"0")){

//这是0

servo_info.GPIOx=GPIOB;

servo_info.GPIO_Pin=GPIO_Pin_14;

}else if(!strcmp(servo_info.pin,"1")){

//这是1

servo_info.GPIOx=GPIOB;

servo_info.GPIO_Pin=GPIO_Pin_15;

}else if(!strcmp(servo_info.pin,"2")){

//这是2

servo_info.GPIOx=GPIOE;

servo_info.GPIO_Pin=GPIO_Pin_5;

}else if(!strcmp(servo_info.pin,"3")){

//这是3

servo_info.GPIOx=GPIOE;

servo_info.GPIO_Pin=GPIO_Pin_6;

}else if(!strcmp(servo_info.pin,"4")){

//这是4

servo_info.GPIOx=GPIOE;

servo_info.GPIO_Pin=GPIO_Pin_9;

}else if(!strcmp(servo_info.pin,"5")){

//这是5

servo_info.GPIOx=GPIOE;

servo_info.GPIO_Pin=GPIO_Pin_11;

}else if(!strcmp(servo_info.pin,"6")){

//这是6

servo_info.GPIOx=GPIOE;

servo_info.GPIO_Pin=GPIO_Pin_13;

}else if(!strcmp(servo_info.pin,"7")){

//这是7

servo_info.GPIOx=GPIOE;

servo_info.GPIO_Pin=GPIO_Pin_14;

}else if(!strcmp(servo_info.pin,"8")){

//这是8

servo_info.GPIOx=GPIOC;

servo_info.GPIO_Pin=GPIO_Pin_6;

}

else if(!strcmp(servo_info.pin,"9")){

//这是9

servo_info.GPIOx=GPIOC;

servo_info.GPIO_Pin=GPIO_Pin_7;

}

else if(!strcmp(servo_info.pin,"10")){

//这是10

servo_info.GPIOx=GPIOC;

servo_info.GPIO_Pin=GPIO_Pin_8;

}

else if(!strcmp(servo_info.pin,"11")){

//这是11

servo_info.GPIOx=GPIOC;

servo_info.GPIO_Pin=GPIO_Pin_9;

}

else if(!strcmp(servo_info.pin,"12")){

//这是12

servo_info.GPIOx=GPIOD;

servo_info.GPIO_Pin=GPIO_Pin_12;

}

else if(!strcmp(servo_info.pin,"13")){

//这是13

servo_info.GPIOx=GPIOD;

servo_info.GPIO_Pin=GPIO_Pin_13;

}

else if(!strcmp(servo_info.pin,"14")){

//这是14

servo_info.GPIOx=GPIOD;

servo_info.GPIO_Pin=GPIO_Pin_14;

}

else if(!strcmp(servo_info.pin,"15")){

//这是15

servo_info.GPIOx=GPIOD;

servo_info.GPIO_Pin=GPIO_Pin_15;

}

else if(!strcmp(servo_info.pin,"16")){

//这是16

servo_info.GPIOx=GPIOB;

servo_info.GPIO_Pin=GPIO_Pin_8;

}

else if(!strcmp(servo_info.pin,"17")){

//这是17

servo_info.GPIOx=GPIOB;

servo_info.GPIO_Pin=GPIO_Pin_9;

}

}

void Servo_move(Servo_info *s_info)

{

Servo_SetAngle_Pulse(s_info->GPIOx, s_info->GPIO_Pin, atoi(s_info->pulse));

}


int main(void) {

  // 初始化外设

  Delay_Init();

UART_Init(USART1,115200);

  Servo_Init(GPIOB, GPIO_Pin_14);

Servo_Init(GPIOB, GPIO_Pin_15);

Servo_Init(GPIOB, GPIO_Pin_8);

Servo_Init(GPIOB, GPIO_Pin_9);

Servo_Init(GPIOE, GPIO_Pin_5);

Servo_Init(GPIOE, GPIO_Pin_6);

Servo_Init(GPIOE, GPIO_Pin_9);

Servo_Init(GPIOE, GPIO_Pin_11);

Servo_Init(GPIOE, GPIO_Pin_13);

Servo_Init(GPIOE, GPIO_Pin_14);

Servo_Init(GPIOC, GPIO_Pin_6);

Servo_Init(GPIOC, GPIO_Pin_7);

Servo_Init(GPIOC, GPIO_Pin_8);

Servo_Init(GPIOC, GPIO_Pin_9);

Servo_Init(GPIOD, GPIO_Pin_12);

Servo_Init(GPIOD, GPIO_Pin_13);

Servo_Init(GPIOD, GPIO_Pin_14);

Servo_Init(GPIOD, GPIO_Pin_15);

Delay_ms(1000);

  while (1) {

if(analyse_flag==1)

{

analyse_flag=0;

analyse_data(buf);

move_flag=1;

}

if(move_flag==1)

{

move_flag=0;

Servo_move(&servo_info);

}

  }

}


void USART1_IRQHandler()

{

unsigned char data;

if(USART_GetITStatus(USART1,USART_IT_RXNE)== SET)

{

data=USART_ReceiveData(USART1);

buf[rcnt++]=data;

if(data=='\n')

{

buf[rcnt] = '\0';

analyse_flag=1;

rcnt=0;

}

}

}


     下载完成后,在电脑上安装CH340芯片驱动。然后将主控板通过Type-C线连接上电脑(要使用靠近开关的Type-C口,这个口是串口),并打开主控板电源,以便利用上位机进行调试。

② 双击打开Controller 1.0.exe:

③界面左上角选择:点击“设置→面板设置”,弹出需要显示的调试块,可通过勾选隐藏掉不需要的调试块,调试块里的数字是pwm值,范围500~2500,中值1500,分别对应舵机的0度~180度,以及中值90度。

④ 如果你用的是SH-F4TT扩展板,它有8个舵机接口。

因此最多只需要开启P0~P7(你的样机有几个舵机就开几个调试块),如图:

上位机舵机编号与SH-F4TT扩展板舵机引脚对应关系如下表所示:

⑤ 如果你用的是SH-F4ST扩展板,它有18个舵机接口。

因此最多只需要开启P0~P17(你的样机有几个舵机就开几个调试块),如图:

上位机舵机编号与SH-F4ST扩展板舵机引脚对应关系如下表所示:

⑥ 点击“联机”选择主控板对应端口号以及波特率(115200),并点击“连接”。

⑦ 用锂电池给主控板供电。拖动进度条,可以观察到对应舵机的摆动。通过拖动进度条或直接编辑舵机的PWM值,可调整样机达到所需的姿态,点击左下角的“添加”,即可获得一组舵机角度编码,再调整样机到达下一个姿态位,再点击“添加”,即可获得另一组舵机角度编码,以此类推,可以获得多组舵机编码,对应多个样机姿态位。最后点击“转化”,即可获得用于编程的舵机数组:

⑧ 该数组可直接复制到相应的MCU-F4程序中进行使用。

⑨ 示例项目:4自由度机械臂搬运

1.电子硬件:

2.参考代码如下:

main.c

3.功能说明

      该4自由度机械臂搭配 1 个夹爪(共计 5 个舵机),通过上位机 Controller 软件采集各运动姿态对应的 PWM 数值并生成数组,烧录至主控板后,机械臂可按预设的姿态序列自动完成抓取、搬运、放置的完整流程,所有舵机采用 PWM 插值同步运动,保证动作平滑稳定。


4.演示视频如下:

五、资料清单

文件下载
Controller1.0资料包-STM32.zip
116.24MB下载40次下载
上一页 1 下一页
四、六自由度机械臂教程视频演示
Arduino

1.支持平台

   win10、win7

   win10打开Controller 1.0.exe即可运行;win7需要先安装Controller1.0资料包\NetFarmwork文件夹中的.net框架组件。

2.电子硬件

   我们用以下硬件为例来讲解Controller 1.0软件的使用:

主控板

Basra主控板(兼容Arduino Uno)

扩展板

Bigfish2.1扩展板

SH-SR舵机扩展板


3.操作步骤

上位机:Controller 1.0

下位机编程环境:Arduino 1.8.19

① 使用 SH-SR舵机扩展板 调试舵机时,将Controller1.0资料包\Arduino\servo\servo.ino下载到主控板;

/*------------------------------------------------------------------------------------

  版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.

           Distributed under MIT license.See file LICENSE for detail or copy at

           https://opensource.org/licenses/MIT

           by 机器谱 2023-03-17 https://www.robotway.com/

  ------------------------------*/

#include <Tlc5940.h>

#include <tlc_servos.h>


#define ANGLE_VALUE_MIN 0

#define ANGLE_VALUE_MAX 180

#define PWM_VALUE_MIN 500

#define PWM_VALUE_MAX 2500


int data_array[2] = {0,0};   //servo_pin: data_array[0], servo_value: data_array[1];

String data = "";


boolean dataComplete = false;


void setup() {

  Serial.begin(9600);

  Tlc.init(0);

  tlc_initServos();

  delay(1000);

}


void loop() {

 

  while(Serial.available())

  {

    int B_flag, P_flag, T_flag;

    data = Serial.readStringUntil('\n');

    data.trim();

    for(int i=0;i<data.length();i++)

    {

      //Serial.println(data[i]);

      switch(data[i])

      {

        case '#':

          B_flag = i;  

        break;

        case 'P':

        {

          String pin = "";

          P_flag = i;

          for(int i=B_flag+1;i<P_flag;i++)

          {

            pin+=data[i];

          }

          data_array[0] = pin.toInt();

        }

        break;

        case 'T':

        {

          String angle = "";

          T_flag = i;

          for(int i=P_flag+1;i<T_flag;i++)

          {

            angle += data[i];

          }

          data_array[1] = angle.toInt();

        }

        break;

        default: break;

      }     

    }

   

    /*

    Serial.println(B_flag);

    Serial.println(P_flag);

    Serial.println(T_flag);

   

    for(int i=0;i<2;i++)

    {

      Serial.println(data_array[i]);

    }

    */

   

    dataComplete = true;

  }

 

  if(dataComplete)

  {

    if(data_array[1] >= ANGLE_VALUE_MIN && data_array[1] <= ANGLE_VALUE_MAX)

    {

      tlc_setServo(data_array[0], data_array[1]);

    }

    else if(data_array[1] >= PWM_VALUE_MIN && data_array[1] <= PWM_VALUE_MAX)

    {

      data_array[1] = map(data_array[1], 500, 2500, 0, 180);

      tlc_setServo(data_array[0], data_array[1]);

    }

    Tlc.update();

    dataComplete = false;

  }

 


}

使用 Bigfish扩展板 调试舵机时,将Controller1.0资料包\Arduino\servo_bigfish\servo_bigfish.ino下载到主控板。

/*------------------------------------------------------------------------------------

  版权说明:Copyright 2023 Robottime(Beijing) Technology Co., Ltd. All Rights Reserved.

           Distributed under MIT license.See file LICENSE for detail or copy at

           https://opensource.org/licenses/MIT

           by 机器谱 2023-03-17 https://www.robotway.com/

  ------------------------------*/

/*

* Bigfish扩展板舵机口; 4, 7, 11, 3, 8, 12, 14, 15, 16, 17, 18, 19

* 使用软件调节舵机时请拖拽对应序号的控制块

*/

#include <Servo.h>


#define ANGLE_VALUE_MIN 0

#define ANGLE_VALUE_MAX 180

#define PWM_VALUE_MIN 500

#define PWM_VALUE_MAX 2500


#define SERVO_NUM 12


Servo myServo[SERVO_NUM];


int data_array[2] = {0,0};   //servo_pin: data_array[0], servo_value: data_array[1];

int servo_port[SERVO_NUM] = {4, 7, 11, 3, 8, 12, 14, 15, 16, 17, 18, 19};

int servo_value[SERVO_NUM] = {};


String data = "";


boolean dataComplete = false;


void setup() {

  Serial.begin(9600);

 

}


void loop() {

 

  while(Serial.available())

  {

    int B_flag, P_flag, T_flag;

    data = Serial.readStringUntil('\n');

    data.trim();

    for(int i=0;i<data.length();i++)

    {

      //Serial.println(data[i]);

      switch(data[i])

      {

        case '#':

          B_flag = i;  

        break;

        case 'P':

        {

          String pin = "";

          P_flag = i;

          for(int i=B_flag+1;i<P_flag;i++)

          {

            pin+=data[i];

          }

          data_array[0] = pin.toInt();

        }

        break;

        case 'T':

        {

          String angle = "";

          T_flag = i;

          for(int i=P_flag+1;i<T_flag;i++)

          {

            angle += data[i];

          }

          data_array[1] = angle.toInt();

          servo_value[pin2index(data_array[0])] = data_array[1];

        }

        break;

        default: break;

      }     

    }

   

    /*

    Serial.println(B_flag);

    Serial.println(P_flag);

    Serial.println(T_flag);

   

    for(int i=0;i<2;i++)

    {

      Serial.println(data_array[i]);

    }

    */

   

    dataComplete = true;

  }

 

  if(dataComplete)

  {

    for(int i=0;i<SERVO_NUM;i++)

    {

      ServoGo(i, servo_value[i]);

      /*********************************串口查看输出***********************************/

//      Serial.print(servo_value[i]);

//      Serial.print(" ");

    }

//    Serial.println();

      /*********************************++++++++++++***********************************/


    dataComplete = false;

  }

 


}


void ServoStart(int which){

  if(!myServo[which].attached() && (servo_value[which] != 0))myServo[which].attach(servo_port[which]);

      else return;

  pinMode(servo_port[which], OUTPUT);

}


void ServoStop(int which){

  myServo[which].detach();

  digitalWrite(servo_port[which],LOW);

}


void ServoGo(int which , int where){

  ServoStart(which);

  if(where >= ANGLE_VALUE_MIN && where <= ANGLE_VALUE_MAX)

  {

    myServo[which].write(where);

  }

  else if(where >= PWM_VALUE_MIN && where <= PWM_VALUE_MAX)

  {

    myServo[which].writeMicroseconds(where);

  }

}


int pin2index(int _pin){

  int index;

  switch(_pin)

  {

    case 4: index = 0; break;

    case 7: index = 1; break;

    case 11: index = 2; break;

    case 3: index = 3; break;

    case 8: index = 4; break;

    case 12: index = 5; break;

    case 14: index = 6; break;

    case 15: index = 7; break;

    case 16: index = 8; break;

    case 17: index = 9; break;

    case 18: index = 10; break;

    case 19: index = 11; break;

  }

  return index;

}

下载完成后,保持主控板和电脑的USB连接,并打开主控板电源,以便利用上位机进行调试。

② 双击打开Controller 1.0.exe:

③ 界面左上角选择:

设置-面板设置,弹出需要显示的调试块,可通过勾选隐藏不需要调试的舵机块:联机-选择主控板对应端口号以及波特率。

④ 拖动进度条,可以观察相应的舵机角度转动。写好对应的舵机调试角度,勾选左下角添加-转化,获得舵机调试的数组:

⑤ 该数组可直接复制到相应的arduino软件的程序中进行使用。

使用Controller 1.0软件调试舵机角度的实验案例可参考 【R111】12自由度六足-原地舞蹈 。



4.资料清单

序号

内容
1

Controller1.0资料包


文件下载
【整体打包】-【U002】如何驱动模拟舵机-Controller 1.0-资料附件.zip
66.94MB下载80次下载
上一页 1 下一页
© 2024 机器时代(北京)科技有限公司  版权所有
机器谱——机器人共享方案网站
学习  开发  设计  应用