智能割草机软件方案介绍:

功能需求如下:

接下来逐步和大家介绍以上功能的实现:

一、产品创建

  • 首先登录涂鸦智能IoT平台创建产品,点击创建产品,在标准类目栏的最下方找到“找不到品类”,点击进入自定义产品创建页面。

  • 输入产品名称和描述,通讯协议选择WIFI-蓝牙,点击创建产品。

  • 在功能定义一栏添加DP点,如下图所示,本demo添加了标准功能:“方向控制”、以及自定义功能“刀片位置”、“刀片转速”、“范围长度设置”、“范围宽度设置”、“弓字除草”等;功能点可以根据需求自行增减,功能点名称以及属性也可根据需求自行修改。

  • 进入设备面板,可以选择自己喜欢的模板或者自己自定义面板,调试阶段推荐选择开发调试面板,便于测试。

  • 点开硬件开发一栏,对接方式选择“涂鸦标准模组MCU SDK开发”,模组选择CBU Wi-Fi&Bluetooth模组(在实际开发过程中可选择手上已有的涂鸦模组即可)

  • 在下方开发资料处点击下载全部,其中就包含了涂鸦 MCU-SDK 文件。

  • 至此,产品创建阶段已经基本完成,此时可以通过“涂鸦智能”app和虚拟设备体验dp数据的接收和发送。

二、MCU固件开发

在进行我们的割草机应用开发之前,我们需要在我们的MCU工程中移植涂鸦 MCU-SDK。移植过程可参考文档:GD32F4系列单片机移植涂鸦MCU-SDK

移植了MCU-SDK后,再搭配一块烧录并授权了通用对接固件的CBU模组,我们的MCU就具备了连接涂鸦云和上报下发dp点的功能。

同时,本demo工程代码还移植了FreeRTOS系统以便于开发。

完成准备工作后,正式开始割草机的应用功能开发。

本demo例程地址:github

1.电机、舵机、电调驱动

本demo最主要的器件就是电机舵机和电调了,4个电机各控制了一个小轮,负责实现小车的运动;两个舵机组合在一起用来控制电调和电调上面的刀片的高低位置;电调则是控制刀片旋转,同时使转速可调控。

首先为上述器件编写初始化和设置接口,方便后续调用。相关的接口都实现在 servo_motor.c文件中。

  • 输出IO口宏定义,方便后续修改:
#define MOTOR_PORT_1                        GPIOA
#define MOTOR_PIN_1                         GPIO_PIN_15
#define MOTOR_PORT_1_P                      GPIOD
#define MOTOR_PIN_1_P                       GPIO_PIN_0
#define MOTOR_PORT_1_N                      GPIOD
#define MOTOR_PIN_1_N                       GPIO_PIN_7#define MOTOR_PORT_2                        GPIOB
#define MOTOR_PIN_2                         GPIO_PIN_9
#define MOTOR_PORT_2_P                      GPIOD
#define MOTOR_PIN_2_P                       GPIO_PIN_1
#define MOTOR_PORT_2_N                      GPIOD
#define MOTOR_PIN_2_N                       GPIO_PIN_2#define MOTOR_PORT_3                        GPIOB
#define MOTOR_PIN_3                         GPIO_PIN_10
#define MOTOR_PORT_3_P                      GPIOD
#define MOTOR_PIN_3_P                       GPIO_PIN_3
#define MOTOR_PORT_3_N                      GPIOD
#define MOTOR_PIN_3_N                       GPIO_PIN_4#define MOTOR_PORT_4                        GPIOB
#define MOTOR_PIN_4                         GPIO_PIN_11
#define MOTOR_PORT_4_P                      GPIOD
#define MOTOR_PIN_4_P                       GPIO_PIN_8
#define MOTOR_PORT_4_N                      GPIOD
#define MOTOR_PIN_4_N                       GPIO_PIN_9 #define MOTOR_CHANNEL_1                     TIMER_CH_0
#define MOTOR_CHANNEL_2                     TIMER_CH_1
#define MOTOR_CHANNEL_3                     TIMER_CH_2
#define MOTOR_CHANNEL_4                     TIMER_CH_3#define SERVO_PORT_1                        GPIOC
#define SERVO_PIN_1                         GPIO_PIN_6#define SERVO_PORT_2                        GPIOC
#define SERVO_PIN_2                         GPIO_PIN_7#define BLADE_MOTOR_PORT                    GPIOC
#define BLADE_MOTOR_PIN                     GPIO_PIN_8
  • 初始化接口,调用后即打开GPIO时钟,同时设置输出IO的普通输出模式和PWM模式,然后调用timer_config去设置PWM的具体参数:
void servo_motor_init(void)
{rcu_periph_clock_enable(RCU_GPIOA);rcu_periph_clock_enable(RCU_GPIOB);rcu_periph_clock_enable(RCU_GPIOC);rcu_periph_clock_enable(RCU_GPIOD);/*Configure PD1~8 Output motor Positive and Negative pin to drive wheels_1~4*/gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, MOTOR_PIN_1_P | MOTOR_PIN_1_N | MOTOR_PIN_2_P | MOTOR_PIN_2_N | MOTOR_PIN_3_P | MOTOR_PIN_3_N | MOTOR_PIN_4_P | MOTOR_PIN_4_N);gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_1_P | MOTOR_PIN_1_N | MOTOR_PIN_2_P | MOTOR_PIN_2_N | MOTOR_PIN_3_P | MOTOR_PIN_3_N | MOTOR_PIN_4_P | MOTOR_PIN_4_N);gpio_bit_reset(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_1_N | MOTOR_PIN_2_P | MOTOR_PIN_2_N | MOTOR_PIN_3_P | MOTOR_PIN_3_N | MOTOR_PIN_4_P | MOTOR_PIN_4_N);/*Configure PA15(TIMER1_CH0) Output PWM pulse to drive wheels_1*/gpio_mode_set(MOTOR_PORT_1, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_1);gpio_output_options_set(MOTOR_PORT_1, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_1);gpio_af_set(MOTOR_PORT_1, GPIO_AF_1, MOTOR_PIN_1);/*Configure PB9(TIMER1_CH1) Output PWM pulse to drive wheels_2*/gpio_mode_set(MOTOR_PORT_2, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_2);gpio_output_options_set(MOTOR_PORT_2, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_2);gpio_af_set(MOTOR_PORT_2, GPIO_AF_1, MOTOR_PIN_2);/*Configure PB10(TIMER1_CH2) Output PWM pulse to drive wheels_3*/gpio_mode_set(MOTOR_PORT_3, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_3);gpio_output_options_set(MOTOR_PORT_3, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_3);gpio_af_set(MOTOR_PORT_3, GPIO_AF_1, MOTOR_PIN_3);/*Configure PB11(TIMER1_CH3) Output PWM pulse to drive wheels_4*/gpio_mode_set(MOTOR_PORT_4, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_4);gpio_output_options_set(MOTOR_PORT_4, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_4);gpio_af_set(MOTOR_PORT_4, GPIO_AF_1, MOTOR_PIN_4);/*Configure PC6(TIMER2_CH0) Output PWM pulse to drive servo no.1*/gpio_mode_set(SERVO_PORT_1, GPIO_MODE_AF, GPIO_PUPD_NONE, SERVO_PIN_1);gpio_output_options_set(SERVO_PORT_1, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,SERVO_PIN_1);gpio_af_set(SERVO_PORT_1, GPIO_AF_2, SERVO_PIN_1);/*Configure PC7(TIMER2_CH1) Output PWM pulse to drive servo no.2*/gpio_mode_set(SERVO_PORT_2, GPIO_MODE_AF, GPIO_PUPD_NONE, SERVO_PIN_2);gpio_output_options_set(SERVO_PORT_2, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,SERVO_PIN_2);gpio_af_set(SERVO_PORT_2, GPIO_AF_2, SERVO_PIN_2);/*Configure PC8(TIMER2_CH2) Output PWM pulse to drive blade motor*/gpio_mode_set(BLADE_MOTOR_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, BLADE_MOTOR_PIN);gpio_output_options_set(BLADE_MOTOR_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,BLADE_MOTOR_PIN);gpio_af_set(BLADE_MOTOR_PORT, GPIO_AF_2, BLADE_MOTOR_PIN);timer_config();}void timer_config(void)
{uint16_t i = 0;/* TIMER1 configuration: generate PWM signals with different duty cycles:TIMER1CLK = SystemCoreClock / 120 = 1MHz */timer_oc_parameter_struct timer_ocintpara;timer_parameter_struct timer_initpara;rcu_periph_clock_enable(RCU_TIMER1);rcu_periph_clock_enable(RCU_TIMER2);rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);timer_struct_para_init(&timer_initpara);timer_deinit(TIMER1);timer_deinit(TIMER2);/* TIMER1 configuration */timer_initpara.prescaler         = 119;timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;timer_initpara.counterdirection  = TIMER_COUNTER_UP;timer_initpara.period            = 500; /* 500*(1/1MHZ) = 500us */timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;timer_initpara.repetitioncounter = 0;timer_init(TIMER1,&timer_initpara);/* TIMER2 configuration */timer_initpara.prescaler         = 119;timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;timer_initpara.counterdirection  = TIMER_COUNTER_UP;timer_initpara.period            = 20000; /* 20000*(1/1MHZ) = 20ms */timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;timer_initpara.repetitioncounter = 0;timer_init(TIMER2,&timer_initpara);timer_channel_output_struct_para_init(&timer_ocintpara);timer_ocintpara.ocpolarity  = TIMER_OC_POLARITY_HIGH;timer_ocintpara.outputstate = TIMER_CCX_ENABLE;timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;for(i = 0; i < 4; i++) {timer_channel_output_config(TIMER1,i,&timer_ocintpara);timer_channel_output_pulse_value_config(TIMER1,i,0);timer_channel_output_mode_config(TIMER1,i,TIMER_OC_MODE_PWM0);timer_channel_output_shadow_config(TIMER1,i,TIMER_OC_SHADOW_DISABLE);}for(i = 0; i < 3; i++) {timer_channel_output_config(TIMER2,i,&timer_ocintpara);timer_channel_output_pulse_value_config(TIMER2,i,0);timer_channel_output_mode_config(TIMER2,i,TIMER_OC_MODE_PWM0);timer_channel_output_shadow_config(TIMER2,i,TIMER_OC_SHADOW_DISABLE);}/* auto-reload preload enable */timer_auto_reload_shadow_enable(TIMER1);timer_auto_reload_shadow_enable(TIMER2);/* TIMER enable */timer_enable(TIMER1);timer_enable(TIMER2);
}
  • 完成初始化接口后,剩下的内容就是对输出特定电机或舵机的pwm进行占空比调节,将调节占空比这个操作封装成通用的接口。以四轮的电机为例,car_moving这一接口实现的操作就是根据传入的方向和速度参数去控制电机的正负极电平和占空比,继而控制车轮转动的方向和转速:
void car_moving(MOVING_DIRECTION direction, uint16_t speed_value)
{uint8_t i;switch(direction) {case forward:gpio_bit_set(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);for(i = 0; i < 4; i++) {timer_channel_output_pulse_value_config(TIMER1,i,speed_value);}break;case right:gpio_bit_set(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);       //Since the two sets of wheels on the right are always faster than the left in the actual commissioning process, 60 is added to compensatetimer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_1,speed_value + 60);timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_3,speed_value + 60);timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_2,speed_value);timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_4,speed_value);  break;case left:gpio_bit_set(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);    timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_1,speed_value);timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_3,speed_value);timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_2,speed_value + 50);timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_4,speed_value + 50);break;case backward:gpio_bit_reset(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);gpio_bit_set(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);for(i = 0; i < 4; i++) {timer_channel_output_pulse_value_config(TIMER1,i,speed_value);}   break;case stop:gpio_bit_reset(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);for(i = 0; i < 4; i++) {timer_channel_output_pulse_value_config(TIMER1,i,speed_value);}break;default:break;}
}

2.PID控制实现直线行驶

由于本demo方案的四个轮子都是由独立电机控制的,在给定相同占空比的pwm输出时,电机与电机之间的误差以及其他因素都会导致小车并不能按照预想的那样驶出一条直线,因此我们有必要引入PID算法来动态调整各个轮子的实际转速。
理想中的直线行驶,即代表四个轮子转动了同样的距离,同时因为这四个轮子的直径都是相同的,也就相当于要求转速是一致的。而电机的转速又直接影响了电机的编码器输出脉冲数,因此一个简单的PID控制思路就产生了:实时采集单位时间下电机编码器输出的脉冲数作为PID算法的输入,占空比增量作为被控项,通过不断的反馈调整最终使四个电机的单位时间脉冲数都收敛至同一期望值。

  • 首先需要采集四路电机编码器单位时间脉冲增量,这里是简单的通过外部中断计数脉冲的方式来实现(有关外部中断的配置都放在movement.c文件中的movement_system_init()函数内):
void movement_system_init(void)
{rcu_periph_clock_enable(RCU_SYSCFG);gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);nvic_irq_enable(EXTI0_IRQn, 2U, 0U);syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN0);exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_RISING);exti_interrupt_flag_clear(EXTI_0);gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_1);nvic_irq_enable(EXTI1_IRQn, 2U, 0U);syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN1);exti_init(EXTI_1, EXTI_INTERRUPT, EXTI_TRIG_RISING);exti_interrupt_flag_clear(EXTI_1);gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_2);nvic_irq_enable(EXTI2_IRQn, 2U, 0U);syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN2);exti_init(EXTI_2, EXTI_INTERRUPT, EXTI_TRIG_RISING);exti_interrupt_flag_clear(EXTI_2);gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_3);nvic_irq_enable(EXTI3_IRQn, 2U, 0U);syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN3);exti_init(EXTI_3, EXTI_INTERRUPT, EXTI_TRIG_RISING);exti_interrupt_flag_clear(EXTI_3);
}void EXTI0_IRQHandler(void)
{if(RESET != exti_interrupt_flag_get(EXTI_0)){move_state.encoder_pulse[0]++;     }exti_interrupt_flag_clear(EXTI_0);
}void EXTI1_IRQHandler(void)
{if(RESET != exti_interrupt_flag_get(EXTI_1)){move_state.encoder_pulse[1]++;     }exti_interrupt_flag_clear(EXTI_1);
}void EXTI2_IRQHandler(void)
{if(RESET != exti_interrupt_flag_get(EXTI_2)){move_state.encoder_pulse[2]++;     }exti_interrupt_flag_clear(EXTI_2);
}void EXTI3_IRQHandler(void)
{if(RESET != exti_interrupt_flag_get(EXTI_3)){move_state.encoder_pulse[3]++;     }exti_interrupt_flag_clear(EXTI_3);
}
  • 实现forward_correction()接口,该接口将采集到的脉冲数pulse_num、上一次采集的脉冲数pulse_num_old和期望的脉冲增量standard_increment作为参数传入PID控制函数,根据计算结果调用single_motor_speed_set()调整对应单个电机的PWM占空比:
int e[4]={0},e1[4]={0},e2[4]={0}; //pid 偏差
float uk[4]={0.0},uk1[4]={0.0},duk[4]={0.0}; //pid输出
float Kp=0.8,Ki=0.3,Kd=0.9; //pid控制系数
int out[4] = {0};static void forward_correction(uint32_t *pulse_num, uint32_t *pulse_num_old, uint32_t standard_increment)
{uint8_t i = 0;for(i = 0;i < 4;i++) {e[i] = standard_increment - (pulse_num[i] - pulse_num_old[i]);duk[i] = (Kp*(e[i]-e1[i])+Ki*e[i])/100;uk[i] = uk1[i] + duk[i];out[i] = (int)uk[i];uk1[i] = uk[i];e2[i] = e1[i];e1[i] = e[i];single_motor_speed_set(i, (uint16_t)(200+(out[i]*5)));}return;
}
  • 在运动逻辑轮询处理函数中,当进入直线状态的switch分支时,调用上面提到的forward_correction()接口来调整电机转速,然后再把当前采集到的脉冲数encoder_pulse赋值给encoder_pulse_old(todo_judge()将进行转弯需求判断,会在后文讲到):
void movement_logic_handle(void)
{   MOVE_STATE_T *p = NULL;p = &move_state;uint8_t i = 0;MOVING_DIRECTION turning_state;switch(p->todo_type) {......case on_the_move:if(forward_sampling_time_flag == 20) { //20*20ms = 400msfor(i = 0;i < 4;i++) {pulse_test[i] = p->encoder_pulse[i]-p->encoder_pulse_old[i];                           }forward_correction(p->encoder_pulse,p->encoder_pulse_old,390);for(i = 0;i < 4;i++) {p->encoder_pulse_old[i] = p->encoder_pulse[i];                          }               forward_sampling_time_flag = 0;}forward_sampling_time_flag++;todo_judge();break; ......default:break;        }}

3.实现直角转弯

以弓字模式为例,当小车直线行驶到规定面积长度时就需要进行直角转弯动作。在上一节的例程中出现的todo_judge函数就是用于转弯判断的,判断依据也是根据电机编码器的脉冲数来转换的,将长度换算成编码器脉冲数,当脉冲数达到规定长度转换出的脉冲数时就开始转弯动作。同时,小车也需要区分当前是在长边转弯还是在宽边转弯,是往左转还是往右转。todo_judge()函数中有一个swich判断,case分支就是代表小车当前是将要在长边左右转还是在宽边左右转。case内的代码主要是判断当前直线行驶距离是否达到转弯条件,若达到则改变小车的直行转弯状态(change_to_do(turning);),然后改变下一个行驶阶段进入该函数时的case分支(p->run_step_flag = width_right;),最后通过宽度是否达到设定值来判断整个弓字行驶是否结束:

static void todo_judge(void)
{MOVE_STATE_T *p = NULL;p = &move_state;switch(p->run_step_flag) {case length_right:if(pulse_to_distance(p->encoder_pulse[0]) >= (p->range_length_mm - 10)) {if((p->current_angle + 900) > 3600) {p->target_angle = p->current_angle + 900 - 3600;}else{p->target_angle = p->current_angle + 900;}change_to_do(turning);p->run_step_flag = width_right;}break;case width_right:if(pulse_to_distance(p->encoder_pulse[0]) >= 100) {if((p->current_angle + 900) > 3600) {p->target_angle = p->current_angle + 900 - 3600;}else{p->target_angle = p->current_angle + 900;}change_to_do(turning);p->run_step_flag = length_left;width_remain_mm -= 100;}break;case length_left:if(pulse_to_distance(p->encoder_pulse[0]) >= (p->range_length_mm - 10)) {if(p->current_angle < 900) {p->target_angle = 3600 - (900 - p->current_angle);}else{p->target_angle = p->current_angle - 900;}change_to_do(turning);p->run_step_flag = width_left;}break;case width_left:if(pulse_to_distance(p->encoder_pulse[0]) >= 100) {if(p->current_angle < 900) {p->target_angle = 3600 - (900 - p->current_angle);}else{p->target_angle = p->current_angle - 900;}change_to_do(turning);p->run_step_flag = length_right;width_remain_mm -= 100;}break;default:break;}if(width_remain_mm <= 0) {change_to_do(to_stop);move_state.bow_mode_switch = pdFALSE;}}

上面例程中的p->target_angle为目标方位角,p->current_angle为当前方位角,用于控制小车的转弯角度。目标方位角是由即将转弯时的小车当前方位角加减90度得来的,而当前方位角则是由地磁传感器数据计算得出。传感器型号为QMC5883L,是IIC驱动的,具体驱动代码都在QMC5883L.c文件里,这里就不在赘述。主要用到的传感器接口就是QMC5883L_GetAngle(),获取当前方位角:

void move_control_task(void *pvParameters)
{   float_xyz Mag_angle;uint8_t test_flag = 50;vTaskDelay(200);QMC_Init();QMC5883L_GetAngle(&Mag_angle);move_state.current_angle = (int16_t)Mag_angle.x;vTaskDelay(500);while(1) {    if(test_flag){vTaskDelay(20);test_flag--;}else if(test_flag == 0) {vTaskDelay(20);movement_logic_handle();}QMC5883L_GetAngle(&Mag_angle);move_state.current_angle = (int16_t)Mag_angle.x;}
}

得到了方位角,就可以用在转弯过程中的判断了。这里实现了一个angle_correction()接口,用于得出当前小车是需要继续左转或者右转还是已经转到目标角度可以直行了。和直行一样,angle_correction()接口在运动逻辑轮询处理函数中的case分支turning中调用:

static MOVING_DIRECTION angle_correction(void)
{int16_t target,current = 0;target = move_state.target_angle;current = move_state.current_angle;if(target < current) {if(current - target <= 20) {return forward;}if(current - target <= 1800) {return left;}else{return right;}}else if(target > current) {if(target - current <= 20) {return forward;}if(target - current <= 1800) {return right;}else{return left;}}else if(current == target) {return forward;}
}void movement_logic_handle(void)
{   MOVE_STATE_T *p = NULL;p = &move_state;uint8_t i = 0;MOVING_DIRECTION turning_state;switch(p->todo_type) {......case on_the_move:if(forward_sampling_time_flag == 20) { //20*20ms = 400msfor(i = 0;i < 4;i++) {pulse_test[i] = p->encoder_pulse[i]-p->encoder_pulse_old[i];                           }forward_correction(p->encoder_pulse,p->encoder_pulse_old,390);for(i = 0;i < 4;i++) {p->encoder_pulse_old[i] = p->encoder_pulse[i];                          }               forward_sampling_time_flag = 0;}forward_sampling_time_flag++;todo_judge();break;case turning: turning_state = angle_correction();if(turning_state == right) {car_full_turn(right,150);turning_sampling_time_flag = 0;}else if(turning_state == left) {car_full_turn(left,150);turning_sampling_time_flag = 0;}else if(turning_state == forward) {car_moving(stop,0);turning_sampling_time_flag++;}                         if(turning_sampling_time_flag >= 25) {p->todo_type = on_the_move;car_moving(forward,200);forward_sampling_time_flag = 0;turning_sampling_time_flag = 0;for(i = 0;i < 4;i++) {p->encoder_pulse[i] = 0;p->encoder_pulse_old[i] = 0;                          }}break;......default:break;        }}

4.刀片位置与刀片速度设置

刀片位置是由两个舵机一起控制的,在位置上下变动的同时还需要保持刀片的水平姿态不变,这就需要两个舵机在相反的方向变动相同的角度。这里直接封装出一个刀片位置设置接口,入参为位置枚举值:

void blade_position_set(BLADE_POSITION value)
{switch(value) {case low:  timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,3100);timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,3000);         break;case medium:timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,2800);timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,2700);  break;case high:timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,2600);timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,2500);break;default:break;            }
}

刀片速度则是由电调控制的,封装接口为blade_speed_set();

void blade_speed_set(BLADE_SPEED speed)
{switch(speed) {case init:timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,1500);                  break;case off:timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0);break;case low_speed:  timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,1800); break;case medium_speed:timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,1900);        break;case high_speed:timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,2000);  break;default:break;            }
}

5.GNSS数据接收

本demo采用的涂鸦GUC300模组是通过串口通信的方式给MCU发送GNSS数据的,这里我们使用MCU的usart1来接收数据,usart0已经被作为与wifi模组通信的串口。

  • 首先是串口初始化:
void uart_init(void)
{   /* USART interrupt configuration */nvic_irq_enable(USART0_IRQn, 0, 0);nvic_irq_enable(USART1_IRQn, 1, 1);/* enable USART clock */rcu_periph_clock_enable(RCU_USART0);/* connect port to USART0_Tx */gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);/* connect port to USART0_Rx */gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);/* configure USART Tx as alternate function push-pull */gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_9);gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_9);/* configure USART Rx as alternate function push-pull */gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_10);gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_10);/* USART configure */usart_deinit(USART0);usart_baudrate_set(USART0,115200U);usart_receive_config(USART0, USART_RECEIVE_ENABLE);usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);usart_enable(USART0);/* enable USART clock */rcu_periph_clock_enable(RCU_USART1);/* connect port to USART0_Tx */gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_5);/* connect port to USART0_Rx */gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_6);/* configure USART Tx as alternate function push-pull */gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_5);gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_5);/* configure USART Rx as alternate function push-pull */gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_6);gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_6);/* USART configure */usart_deinit(USART1);usart_baudrate_set(USART1,9600U);usart_receive_config(USART1, USART_RECEIVE_ENABLE);usart_transmit_config(USART1, USART_TRANSMIT_ENABLE);usart_enable(USART1);/* enable USART0 receive interrupt */usart_interrupt_enable(USART0, USART_INT_RBNE);/* enable USART1 receive interrupt */usart_interrupt_enable(USART1, USART_INT_RBNE);
}
  • 然后是串口接收中断服务函数。涂鸦GPS模组会每隔1秒输出GNRMC GNGGA GPGSV 等语句,我们只需要解析其中的GPGGA即可。GPGGA即可。GPGGA即可。GPGGA 语句包括17个字段:语句标识头,世界时间,纬度,纬度半球,经度,经度半球,定位质量指示,使用卫星数量,HDOP-水平精度因子,椭球高,高度单位,大地水准面高度异常差值,高度单位,差分GPS数据期限,差分参考基站标号,校验和结束标记(用回车符CR和换行符LF),分别用14个逗号进行分隔。
    格式示例:$GPGGA,014434.70,3817.13334637,N,12139.72994196,E,4,07,1.5,6.571,M,8.942,M,0.7,0016*79
    按照上述格式对串口数据进行处理:
#define USART_FH_len 6   //帧头长度
char USART_FH[USART_FH_len]={'$','G','N','G','G','A'}; //帧头
#define USART_FT_len 2  //帧尾长度
uint8_t USART_FT[USART_FT_len]={0X0D,0X0A}; //帧尾uint8_t data_buf[128] = {0};
uint8_t rx_buf[128] = {0};
uint8_t buff_len = 0;void USART1_IRQHandler(void)
{if((RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_RBNE)) && (RESET != usart_flag_get(USART1, USART_FLAG_RBNE))){  rx_buf[buff_len++] = (uint8_t)usart_data_receive(USART1);if(rx_buf[0] != USART_FH[0]) {rx_buf[0] = 0;buff_len = 0;}if(buff_len >= USART_FH_len) {if(strncmp((char*)rx_buf,USART_FH,USART_FH_len) == 0) {if(strncmp(&rx_buf[buff_len-2],(char*)USART_FT,USART_FT_len) == 0) {memcpy(data_buf,rx_buf,buff_len);memset(rx_buf,0,buff_len);buff_len = 0;}}else {memset(rx_buf,0,USART_FH_len);buff_len = 0;}}}
}void gps_data_task(void *pvParameters)
{MAP_POINT_t *p;p = &map_point[point_1];uint8_t data_len = 0;while(1) {vTaskDelay(100);data_len = strlen((char*)data_buf);if(data_len != 0){gps_data_pick(point_1, data_buf, data_len);memset(data_buf,0,data_len);}}
}

.小结

​ 至此智能割草机Demo就完成了。在这款智能割草机的基础上还有很多功能可以深入开发,使体验更加人性化,智能化。同时您可以基于涂鸦 IoT 平台丰富它的功能,也可以更加方便的搭建更多智能产品原型,加速智能产品的开发流程。

一款简易低成本智能割草机制作——嵌入式功能实现篇相关推荐

  1. 一款简易低成本智能割草机的制作——硬件篇

    概述 近年来,随着大家环保意识和生活水平的提高,不少人开始在庭院里种植了小花小草.但是由于没有定期管理,很多庭院变得杂草丛生,不仅不美观,还引来一群蚊子昆虫在这安居乐业.尤其天热的时候,室外温度三四十 ...

  2. 基于涂鸦智能开发的墨水屏座位管理器——2.嵌入式功能实现篇

    随着互联网连接技术的日益普及,以及大众环保意识增强,电子纸显示市场不断发展,墨水屏的应用场景也越来越多.墨水屏座位管理器方案具体功耗低,多节点管控,信息实时同步等特点,可应用于智慧办公,智慧零售,智慧 ...

  3. 制作一款可以【记录运动历史数据】的智能呼啦圈——嵌入式功能实现

    简介:本文将从智能呼啦圈软件整体方案,外设驱动以及功能实现几个维度来带大家一起了解如何实现呼啦圈智能计算.切换运动模式以及运动历史曲线. 开发环境搭建 智能呼啦圈方案是基于涂鸦 BLE SDK 和 T ...

  4. 改造一台可以计算滤芯使用寿命的智能空气净化器——嵌入式功能实现篇

    接上篇改造一台可以计算滤芯使用寿命的智能空气净化器- 给大家介绍智能净化器的硬件改造方案.本篇带大家了解如何实现智能空气净化器的各种净化模式以及滤芯检测和滤芯寿命计算功能. 一. 功能需求 功能简述: ...

  5. 科技不总是冷冰冰,智能便携打印机让文字更有温度!——嵌入式功能实现篇

    简介:利用Wi-Fi&BLE云模组让普通打印机实现App端输入文字即可实时&远程打印. 1.功能需求 基本打印流程: (1)用户通过涂鸦App完成设备配网. (2)将需要打印的文字或者 ...

  6. Unity小地图Minimap制作全面功能介绍篇

    本系列文章将讲述如何制作小地图. 功能如下: 小地图制作和美化       https://blog.csdn.net/alayeshi/article/details/115914212 小地图展示 ...

  7. 自己动手完成一款简易P2P共享文件软件的制作(一)

    文章目录 1. 前言 2. 系统总体框架 3. 服务器设计 本文实验测试部分可参考基于QT的一款P2P共享文件系统 源码包下载地址基于QT的一款P2P共享文件系统下载,想要免费获取可以私信我 Gith ...

  8. 连载:涂鸦智能动手制作一款智能宠物喂食器(一)

    涂鸦智能动手制作一款智能宠物喂食器(一) 简介 制作过程中测试了很多次,也算是边学选练.遇到了一些问题,本着理顺的思路,做一些记录.一些过程中的图片没注意截图,幸好另一个大佬用小熊派STM32把这个项 ...

  9. 简易智能手环制作教程

    前言: 为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长. 转载地址:简易智能手环制作教程 1.智能手环简介 智能手环是一种穿戴式智能 ...

最新文章

  1. C++ 模板中定义友元
  2. STM32 之九 HAL 库串口(USART/UART)驱动 BUG 及解决方法
  3. history linux 日志服务器_编译bash实现history的syslog日志记录
  4. html编辑器不支持自定义样式,百度编辑器自定义按钮样式问题(写在cssRules不起做用)?...
  5. Symfony2博客应用程序教程:第四部分(续)-测试安全页
  6. linux python指向python3_linux下切换python2和python3(转)
  7. Python机器学习(基础篇---监督学习(k近邻))
  8. mysql 触发器 insert new_mysql触发器实例 插入前更新数据
  9. 如何利用jqGrid表格方法重新设置caption属性值
  10. 使用U盘制作启动盘重装mac系统
  11. vscode取消斜体注释
  12. 哈佛大学砸场子:DALL-E 2只是「粘合怪」,生成正确率只有22%
  13. 日语中电脑发出奇怪的声音是如何表达的
  14. 随机变量序列的两种收敛性
  15. echarts中渐变色的使用
  16. 关于AES:加密方式,让你的APK无懈可击,字节跳动Android面试全套真题解析在互联网火了
  17. 大数据测试--转载自开源优测
  18. 互联网时代内容分发四宗罪
  19. 单片机c语言编程 时钟加日历转换,单片机C语言电子时钟加日历显示编程
  20. go项目打包交给k8s发布并管理步骤

热门文章

  1. 三维动画与交互技术复习笔记
  2. [RK3288][Android6.0] 调试笔记 --- AndroidTool低格无效问题
  3. ZYNQ裸板:中断篇
  4. A/D与D/A转换芯片PCF8591
  5. macOS 系统安装方法/系统重装/降级
  6. Unite Beijing 2015大型活动
  7. 找出google字符流中第一个只出现一次的字符(map/queue)
  8. 数字图像处理——第八章 图像压缩
  9. 使用ABP vNext在5分钟内开发地址簿应用程序
  10. CentOS7 完全卸载 php