diff --git a/CMakeLists.txt b/CMakeLists.txt index 99bff52..fa6276a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ add_executable(${CMAKE_PROJECT_NAME} Core/Bsp/MultiButton-master/Multi_Button.c Core/Bsp/BSP_Device/bsp_aht30/bsp_aht30.c Core/Bsp/BSP_Device/bsp_rtc/bsp_rtc.c + Core/Bsp/bsp_motor/stepper_motor.c ) # Add STM32CubeMX generated sources add_subdirectory(cmake/stm32cubemx) @@ -74,6 +75,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE Core/Bsp/MultiButton-master Core/Bsp/BSP_Device/bsp_aht30 Core/Bsp/BSP_Device/bsp_rtc + Core/Bsp/bsp_motor ) # Add project symbols (macros) diff --git a/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.c b/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.c index ea1eb85..8167ef6 100644 --- a/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.c +++ b/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.c @@ -59,6 +59,9 @@ HAL_StatusTypeDef MP3_Init(void) HAL_StatusTypeDef ret1 = MP3_SendCommand(init_cmd1, sizeof(init_cmd1)); elog_d("MP3", "开启声音命令返回值: %d", ret1); + // 设置为低音量-测试用 + MP3_SetVolume(2); + // 延时等待模块响应 HAL_Delay(100); diff --git a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c index bacb3e9..c7746b2 100644 --- a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c +++ b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c @@ -554,6 +554,108 @@ void ST7735_InvertColors(bool invert) { ST7735_Unselect(); } +/** + * 在ST7735液晶显示屏上绘制一条直线 + * + * @param x0 直线起始点的X坐标 + * @param y0 直线起始点的Y坐标 + * @param x1 直线结束点的X坐标 + * @param y1 直线结束点的Y坐标 + * @param color 直线的颜色 + */ +void ST7735_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { + // 检查坐标是否超出显示屏边界 + if ((x0 >= ST7735_WIDTH) || (y0 >= ST7735_HEIGHT) || (x1 >= ST7735_WIDTH) || (y1 >= ST7735_HEIGHT)) + return; + + // 选择显示屏以进行操作 + ST7735_Select(); + + // 使用Bresenham算法绘制直线 + int16_t dx = abs((int16_t)x1 - (int16_t)x0); + int16_t dy = abs((int16_t)y1 - (int16_t)y0); + int16_t sx = (x0 < x1) ? 1 : -1; + int16_t sy = (y0 < y1) ? 1 : -1; + int16_t err = dx - dy; + + int16_t current_x = x0; + int16_t current_y = y0; + + while (1) { + // 绘制当前点 + ST7735_DrawPixel(current_x, current_y, color); + + // 检查是否到达终点 + if (current_x == x1 && current_y == y1) + break; + + // 计算下一步 + int16_t e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + current_x += sx; + } + if (e2 < dx) { + err += dx; + current_y += sy; + } + } + + // 取消选择显示屏,结束操作 + ST7735_Unselect(); +} + +/** + * 在ST7735液晶显示屏上绘制一个填充圆 + * + * @param x 圆心的X坐标 + * @param y 圆心的Y坐标 + * @param radius 圆的半径 + * @param color 填充颜色 + */ +void ST7735_FillCircle(uint16_t x, uint16_t y, uint16_t radius, uint16_t color) { + // 检查坐标是否超出显示屏边界 + if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT)) + return; + + // 选择显示屏以进行操作 + ST7735_Select(); + + // 使用中点圆算法绘制填充圆 + int16_t x_pos = radius; + int16_t y_pos = 0; + int16_t err = 1 - radius; + + while (x_pos >= y_pos) { + // 绘制水平扫描线 + if ((y + y_pos) < ST7735_HEIGHT) { + ST7735_FillRectangle(x - x_pos, y + y_pos, 2 * x_pos + 1, 1, color); + } + if ((y - y_pos) >= 0 && y_pos != 0) { + ST7735_FillRectangle(x - x_pos, y - y_pos, 2 * x_pos + 1, 1, color); + } + if ((y + x_pos) < ST7735_HEIGHT && x_pos != y_pos) { + ST7735_FillRectangle(x - y_pos, y + x_pos, 2 * y_pos + 1, 1, color); + } + if ((y - x_pos) >= 0 && x_pos != y_pos) { + ST7735_FillRectangle(x - y_pos, y - x_pos, 2 * y_pos + 1, 1, color); + } + + // 计算下一步 + if (err < 0) { + y_pos++; + err += 2 * y_pos + 1; + } else { + x_pos--; + err += 2 * (y_pos - x_pos) + 1; + y_pos++; + } + } + + // 取消选择显示屏,结束操作 + ST7735_Unselect(); +} + /** * @brief 设置ST7735显示器的伽马曲线 * diff --git a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h index 1c2e0a4..d09c185 100644 --- a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h +++ b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h @@ -315,6 +315,16 @@ extern "C" // 解释:此函数用于在显示屏上的指定位置绘制图像,图像数据以数组形式提供 void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data); + // 在指定位置绘制一条直线 + // 参数:x0 - 直线起始X坐标;y0 - 直线起始Y坐标;x1 - 直线结束X坐标;y1 - 直线结束Y坐标;color - 直线颜色 + // 解释:此函数用于在显示屏上绘制一条直线,使用Bresenham算法实现 + void ST7735_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color); + + // 绘制一个填充圆 + // 参数:x - 圆心X坐标;y - 圆心Y坐标;radius - 圆的半径;color - 填充颜色 + // 解释:此函数用于在显示屏上绘制一个填充的圆形 + void ST7735_FillCircle(uint16_t x, uint16_t y, uint16_t radius, uint16_t color); + // 反转显示屏颜色 // 参数:invert - 是否反转颜色(true/false) // 解释:此函数用于反转显示屏上的颜色,可用于显示反转效果 diff --git a/Core/Bsp/bsp_motor/stepper_motor.c b/Core/Bsp/bsp_motor/stepper_motor.c new file mode 100644 index 0000000..acd9db4 --- /dev/null +++ b/Core/Bsp/bsp_motor/stepper_motor.c @@ -0,0 +1,237 @@ +#include "stepper_motor.h" +#include "cmsis_os.h" + +/* USER CODE BEGIN 1 */ + +/** + * @brief 28BYJ-48步进电机驱动源文件 + * + * 28BYJ-48是5线4相永磁步进电机,采用ULN2003驱动器 + * 控制序列如下: + * + * 全步模式(4拍): + * IN1 IN2 IN3 IN4 + * 1 0 0 0 -> Step 1 + * 0 1 0 0 -> Step 2 + * 0 0 1 0 -> Step 3 + * 0 0 0 1 -> Step 4 + * + * 半步模式(8拍): + * IN1 IN2 IN3 IN4 + * 1 0 0 0 -> Step 1 + * 1 1 0 0 -> Step 2 + * 0 1 0 0 -> Step 3 + * 0 1 1 0 -> Step 4 + * 0 0 1 0 -> Step 5 + * 0 0 1 1 -> Step 6 + * 0 0 0 1 -> Step 7 + * 1 0 0 1 -> Step 8 + */ + +// 步进电机控制序列数组 - 全步模式 +static const uint8_t full_step_sequence[4][4] = { + {1, 0, 0, 0}, // Step 1 + {0, 1, 0, 0}, // Step 2 + {0, 0, 1, 0}, // Step 3 + {0, 0, 0, 1} // Step 4 +}; + +// 步进电机控制序列数组 - 半步模式 +static const uint8_t half_step_sequence[8][4] = { + {1, 0, 0, 0}, // Step 1 + {1, 1, 0, 0}, // Step 2 + {0, 1, 0, 0}, // Step 3 + {0, 1, 1, 0}, // Step 4 + {0, 0, 1, 0}, // Step 5 + {0, 0, 1, 1}, // Step 6 + {0, 0, 0, 1}, // Step 7 + {1, 0, 0, 1} // Step 8 +}; + +// 当前步序位置 +static uint8_t current_step = 0; +static StepperMode_t current_mode = STEPPER_MODE_FULL_STEP; + +/** + * @brief 设置步进电机各引脚状态 + * @param in1~in4 各相的高低电平状态 (0或1) + */ +static void Set_Stepper_Pins(uint8_t in1, uint8_t in2, uint8_t in3, uint8_t in4) +{ + // 设置IN1引脚 + if(in1) { + HAL_GPIO_WritePin(STEPPER_IN1_PORT, STEPPER_IN1_PIN, GPIO_PIN_SET); + } else { + HAL_GPIO_WritePin(STEPPER_IN1_PORT, STEPPER_IN1_PIN, GPIO_PIN_RESET); + } + + // 设置IN2引脚 + if(in2) { + HAL_GPIO_WritePin(STEPPER_IN2_PORT, STEPPER_IN2_PIN, GPIO_PIN_SET); + } else { + HAL_GPIO_WritePin(STEPPER_IN2_PORT, STEPPER_IN2_PIN, GPIO_PIN_RESET); + } + + // 设置IN3引脚 + if(in3) { + HAL_GPIO_WritePin(STEPPER_IN3_PORT, STEPPER_IN3_PIN, GPIO_PIN_SET); + } else { + HAL_GPIO_WritePin(STEPPER_IN3_PORT, STEPPER_IN3_PIN, GPIO_PIN_RESET); + } + + // 设置IN4引脚 + if(in4) { + HAL_GPIO_WritePin(STEPPER_IN4_PORT, STEPPER_IN4_PIN, GPIO_PIN_SET); + } else { + HAL_GPIO_WritePin(STEPPER_IN4_PORT, STEPPER_IN4_PIN, GPIO_PIN_RESET); + } +} + +/** + * @brief 初始化步进电机 + */ +void Stepper_Motor_Init(void) +{ + + Stepper_Motor_Stop(); // 停止 + + // 重置步序位置 + current_step = 0; + current_mode = STEPPER_MODE_FULL_STEP; +} + +/** + * @brief 单步驱动步进电机 (全步模式) + * @param direction 转动方向 (STEPPER_DIR_CW 或 STEPPER_DIR_CCW) + */ +void Stepper_Motor_OneStep_Full(StepperDir_t direction) +{ + uint8_t step_idx; + + if(direction == STEPPER_DIR_CW) { + // 顺时针:步序递增 + step_idx = current_step % 4; + current_step++; + } else { + // 逆时针:步序递减 + if(current_step == 0) { + current_step = 4; // 准备递减到3 + } + current_step--; + step_idx = current_step % 4; + } + + // 根据步序设置引脚状态 + Set_Stepper_Pins( + full_step_sequence[step_idx][0], + full_step_sequence[step_idx][1], + full_step_sequence[step_idx][2], + full_step_sequence[step_idx][3] + ); +} + +/** + * @brief 单步驱动步进电机 (半步模式) + * @param direction 转动方向 (STEPPER_DIR_CW 或 STEPPER_DIR_CCW) + */ +void Stepper_Motor_OneStep_Half(StepperDir_t direction) +{ + uint8_t step_idx; + + if(direction == STEPPER_DIR_CW) { + // 顺时针:步序递增 + step_idx = current_step % 8; + current_step++; + } else { + // 逆时针:步序递减 + if(current_step == 0) { + current_step = 8; // 准备递减到7 + } + current_step--; + step_idx = current_step % 8; + } + + // 根据步序设置引脚状态 + Set_Stepper_Pins( + half_step_sequence[step_idx][0], + half_step_sequence[step_idx][1], + half_step_sequence[step_idx][2], + half_step_sequence[step_idx][3] + ); +} + +/** + * @brief 驱动步进电机转动指定步数 + * @param steps 需要转动的步数 + * @param direction 转动方向 + * @param delay_ms 每步之间的延时 (毫秒) + * @param mode 驱动模式 + */ +void Stepper_Motor_RotateSteps(uint16_t steps, StepperDir_t direction, uint16_t delay_ms, StepperMode_t mode) +{ + for(uint16_t i = 0; i < steps; i++) { + if(mode == STEPPER_MODE_FULL_STEP) { + Stepper_Motor_OneStep_Full(direction); + } else { + Stepper_Motor_OneStep_Half(direction); + } + + // 延时,控制转速 + osDelay(delay_ms); + } +} + +/** + * @brief 驱动步进电机转动指定角度 + * @param angle 需要转动的角度 (0~360) + * @param direction 转动方向 + * @param delay_ms 每步之间的延时 (毫秒) + * @param mode 驱动模式 + */ +void Stepper_Motor_RotateAngle(float angle, StepperDir_t direction, uint16_t delay_ms, StepperMode_t mode) +{ + uint16_t steps; + + // 计算需要的步数 + // 28BYJ-48步距角为5.625°,即每步5.625度 + // 在全步模式下,每转一圈需要4096步才能达到实际输出轴一圈 + // 所以每度需要 4096 / 360 ≈ 11.38 步 + if(mode == STEPPER_MODE_FULL_STEP) { + steps = (uint16_t)(angle * (STEPPER_FULL_STEP_PER_REVOLUTION / 360.0)); + } else { + steps = (uint16_t)(angle * (STEPPER_HALF_STEP_PER_REVOLUTION / 360.0)); + } + + Stepper_Motor_RotateSteps(steps, direction, delay_ms, mode); +} + +/** + * @brief 驱动步进电机转动指定圈数 + * @param rounds 需要转动的圈数 + * @param direction 转动方向 + * @param delay_ms 每步之间的延时 (毫秒) + * @param mode 驱动模式 + */ +void Stepper_Motor_RotateRounds(uint8_t rounds, StepperDir_t direction, uint16_t delay_ms, StepperMode_t mode) +{ + uint32_t total_steps; + + if(mode == STEPPER_MODE_FULL_STEP) { + total_steps = rounds * STEPPER_FULL_STEP_PER_REVOLUTION; + } else { + total_steps = rounds * STEPPER_HALF_STEP_PER_REVOLUTION; + } + + Stepper_Motor_RotateSteps((uint16_t)total_steps, direction, delay_ms, mode); +} + +/** + * @brief 停止步进电机 + */ +void Stepper_Motor_Stop(void) +{ + // 将所有引脚设置为低电平,电机停止 + Set_Stepper_Pins(0, 0, 0, 0); +} + +/* USER CODE END 1 */ \ No newline at end of file diff --git a/Core/Bsp/bsp_motor/stepper_motor.h b/Core/Bsp/bsp_motor/stepper_motor.h new file mode 100644 index 0000000..73ba9ee --- /dev/null +++ b/Core/Bsp/bsp_motor/stepper_motor.h @@ -0,0 +1,108 @@ +#ifndef __STEPPER_MOTOR_H +#define __STEPPER_MOTOR_H + +#include "main.h" + +/* USER CODE BEGIN 0 */ + +/** + * @brief 28BYJ-48步进电机驱动头文件 + * + * 该文件提供对28BYJ-48步进电机的基本控制功能 + * 包括正转、反转、单步转动等操作 + * + * 28BYJ-48电机参数: + * - 步距角:5.625° (64步/圈,如果使用内部减速比为64:1) + * - 减速比:64:1 + * - 实际输出轴转一圈需要4096步 (64 * 64) + */ + +// 步进电机驱动引脚定义 - 可根据实际硬件连接修改 +#define STEPPER_IN1_PORT STEPPER_IN1_GPIO_Port +#define STEPPER_IN1_PIN STEPPER_IN1_Pin + +#define STEPPER_IN2_PORT STEPPER_IN2_GPIO_Port +#define STEPPER_IN2_PIN STEPPER_IN2_Pin + +#define STEPPER_IN3_PORT STEPPER_IN3_GPIO_Port +#define STEPPER_IN3_PIN STEPPER_IN3_Pin + +#define STEPPER_IN4_PORT STEPPER_IN4_GPIO_Port +#define STEPPER_IN4_PIN STEPPER_IN4_Pin + +// 步进电机相关常量定义 +#define STEPPER_FULL_STEP_PER_REVOLUTION 4096 // 一圈需要的步数 (64*64) +#define STEPPER_HALF_STEP_PER_REVOLUTION 2048 // 半步模式下一圈需要的步数 +#define STEPPER_WAVE_DRIVE_PER_REVOLUTION 4096 // 波形驱动模式下一圈需要的步数 + +// 电机状态定义 +typedef enum { + STEPPER_STATUS_OK = 0, + STEPPER_STATUS_ERROR, + STEPPER_STATUS_BUSY +} StepperStatus_t; + +// 电机转动方向定义 +typedef enum { + STEPPER_DIR_CW = 0, // 顺时针 + STEPPER_DIR_CCW // 逆时针 +} StepperDir_t; + +// 电机驱动模式定义 +typedef enum { + STEPPER_MODE_FULL_STEP = 0, // 全步模式 + STEPPER_MODE_HALF_STEP // 半步模式 +} StepperMode_t; + +/** + * @brief 初始化步进电机 + */ +void Stepper_Motor_Init(void); + +/** + * @brief 单步驱动步进电机 (全步模式) + * @param direction 转动方向 (STEPPER_DIR_CW 或 STEPPER_DIR_CCW) + */ +void Stepper_Motor_OneStep_Full(StepperDir_t direction); + +/** + * @brief 单步驱动步进电机 (半步模式) + * @param direction 转动方向 (STEPPER_DIR_CW 或 STEPPER_DIR_CCW) + */ +void Stepper_Motor_OneStep_Half(StepperDir_t direction); + +/** + * @brief 驱动步进电机转动指定步数 + * @param steps 需要转动的步数 + * @param direction 转动方向 + * @param delay_ms 每步之间的延时 (毫秒) + * @param mode 驱动模式 + */ +void Stepper_Motor_RotateSteps(uint16_t steps, StepperDir_t direction, uint16_t delay_ms, StepperMode_t mode); + +/** + * @brief 驱动步进电机转动指定角度 + * @param angle 需要转动的角度 (0~360) + * @param direction 转动方向 + * @param delay_ms 每步之间的延时 (毫秒) + * @param mode 驱动模式 + */ +void Stepper_Motor_RotateAngle(float angle, StepperDir_t direction, uint16_t delay_ms, StepperMode_t mode); + +/** + * @brief 驱动步进电机转动指定圈数 + * @param rounds 需要转动的圈数 + * @param direction 转动方向 + * @param delay_ms 每步之间的延时 (毫秒) + * @param mode 驱动模式 + */ +void Stepper_Motor_RotateRounds(uint8_t rounds, StepperDir_t direction, uint16_t delay_ms, StepperMode_t mode); + +/** + * @brief 停止步进电机 + */ +void Stepper_Motor_Stop(void); + +/* USER CODE END 0 */ + +#endif /* __STEPPER_MOTOR_H */ \ No newline at end of file diff --git a/Core/Inc/main.h b/Core/Inc/main.h index 70a8453..0adbd2a 100644 --- a/Core/Inc/main.h +++ b/Core/Inc/main.h @@ -87,6 +87,14 @@ void Error_Handler(void); #define M3_IO_GPIO_Port GPIOB #define MOTOR_Pin GPIO_PIN_6 #define MOTOR_GPIO_Port GPIOC +#define STEPPER_IN1_Pin GPIO_PIN_7 +#define STEPPER_IN1_GPIO_Port GPIOC +#define STEPPER_IN2_Pin GPIO_PIN_8 +#define STEPPER_IN2_GPIO_Port GPIOC +#define STEPPER_IN3_Pin GPIO_PIN_9 +#define STEPPER_IN3_GPIO_Port GPIOC +#define STEPPER_IN4_Pin GPIO_PIN_8 +#define STEPPER_IN4_GPIO_Port GPIOA /* USER CODE BEGIN Private defines */ diff --git a/Core/Src/freertos.c b/Core/Src/freertos.c index 0341a16..74a5006 100644 --- a/Core/Src/freertos.c +++ b/Core/Src/freertos.c @@ -36,6 +36,7 @@ #include "bsp_aht30.h" #include "bsp_rtc.h" #include "i2c.h" +#include "stepper_motor.h" // 添加步进电机驱动头文件 /* USER CODE END Includes */ @@ -52,6 +53,10 @@ #define KEY2_MANUAL_FEED // 按键2:手动喂食(仅手动模式有效) #define KEY3_DISPLAY_NEXT // 按键3:切换显示界面 #define KEY4_TIME_SET // 按键4:长按设置时间 +/***********************人体存在和水位检测作为两个单独的按键(GPIO输入)******************* */ +#define KEY_WATER_LEVEL // 水位检测 +#define KEY_BODY_EXIST // 人体存在 + /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ @@ -80,10 +85,26 @@ typedef struct { uint8_t system_mode; // 系统模式 (0=手动, 1=自动) } Sensor_Data_t; +// 喂食控制命令枚举 +typedef enum { + FEED_CMD_NONE = 0, // 无命令 + FEED_CMD_MANUAL, // 手动喂食 + FEED_CMD_AUTO, // 自动喂食 + FEED_CMD_REMOTE, // 远程喂食 + FEED_CMD_TEST // 测试喂食 +} Feed_Cmd_t; + // 全局变量 static LCD_Page_t current_page = LCD_PAGE_TIME; // 当前显示页面 static Sensor_Data_t sensor_data = {0}; // 传感器数据 static volatile uint8_t lcd_force_refresh = 0; // 强制刷新标志(RTC更新时设置) +static volatile uint8_t system_mode = 1; // 系统模式:1=自动模式,0=手动模式(全局变量) +static volatile uint8_t feeding_in_progress = 0; // 喂食进行中标志 + +// 喂食控制相关全局变量 +static volatile Feed_Cmd_t feed_command = FEED_CMD_NONE; // 喂食命令标志 +static volatile uint16_t feed_angle = 90; // 喂食角度(默认90度) +static volatile uint8_t feed_amount = 1; // 喂食份数 /* USER CODE END Variables */ /* Definitions for defaultTask */ @@ -121,6 +142,13 @@ const osThreadAttr_t sensor_attributes = { .stack_size = 1024 * 4, .priority = (osPriority_t) osPriorityNormal, }; +/* Definitions for step_motor */ +osThreadId_t step_motorHandle; +const osThreadAttr_t step_motor_attributes = { + .name = "step_motor", + .stack_size = 512 * 4, + .priority = (osPriority_t) osPriorityNormal, +}; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ @@ -140,6 +168,7 @@ extern void wifi_task_mqtt(void *argument); void LCD_Task(void *argument); void button_task(void *argument); void sensorTask(void *argument); +void step_motor_task(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ @@ -151,6 +180,9 @@ void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ ST7735_Init(); // 初始化ST7735显示屏 + + + /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ @@ -185,6 +217,9 @@ void MX_FREERTOS_Init(void) { /* creation of sensor */ sensorHandle = osThreadNew(sensorTask, NULL, &sensor_attributes); + /* creation of step_motor */ + step_motorHandle = osThreadNew(step_motor_task, NULL, &step_motor_attributes); + /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ /* USER CODE END RTOS_THREADS */ @@ -248,7 +283,6 @@ void LCD_Task(void *argument) char display_str[32]; RTC_Time_t rtc_time; - uint16_t text_color = ST7735_WHITE; uint16_t bg_color = ST7735_BLACK; // 显示区域参数 - 适配160x80横屏 @@ -272,89 +306,137 @@ void LCD_Task(void *argument) case LCD_PAGE_TIME: // 时间显示页面 ST7735_FillScreen(bg_color); - ST7735_WriteString(2, 2, "Time", &Font_7x10, text_color, bg_color); - + + // 绘制标题栏 + ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_BLUE); + ST7735_WriteString(5, 2, "Time", &Font_7x10, ST7735_WHITE, ST7735_BLUE); + // 使用RTC时间(独立于SNTP,即使网络断开也能显示) if (BSP_RTC_GetTime(&rtc_time) == 0) { // 显示时间(大字体,只显示时:分,不显示秒) snprintf(display_str, sizeof(display_str), "%02d:%02d", rtc_time.hour, rtc_time.minute); - ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color); + ST7735_WriteString(40, 30, display_str, &Font_16x26, ST7735_WHITE, bg_color); // 显示年月日(小字体,向下移动并居中) snprintf(display_str, sizeof(display_str), "%04d-%02d-%02d", rtc_time.year, rtc_time.month, rtc_time.day); - ST7735_WriteString(20, 60, display_str, &Font_7x10, ST7735_CYAN, bg_color); + ST7735_WriteString(30, 65, display_str, &Font_7x10, ST7735_CYAN, bg_color); } else { - ST7735_WriteString(25, 20, "--:--", &Font_16x26, text_color, bg_color); - ST7735_WriteString(20, 60, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color); + ST7735_WriteString(40, 30, "--:--", &Font_16x26, ST7735_WHITE, bg_color); + ST7735_WriteString(30, 65, "----/--/--", &Font_7x10, ST7735_CYAN, bg_color); } break; case LCD_PAGE_TEMP_HUMI: // 温湿度页面 ST7735_FillScreen(bg_color); - ST7735_WriteString(2, 2, "Temp & Humi", &Font_7x10, text_color, bg_color); + + // 绘制标题栏 + ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_GREEN); + ST7735_WriteString(5, 2, "Temp & Humi", &Font_7x10, ST7735_WHITE, ST7735_GREEN); + + // 绘制分隔线 + ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 显示温度(橙色 - 国际标准温度色) - snprintf(display_str, sizeof(display_str), "T: %.2f C", sensor_data.temperature); - ST7735_WriteString(5, 20, display_str, &Font_11x18, ST7735_ORANGE, bg_color); + snprintf(display_str, sizeof(display_str), "Temp:"); + ST7735_WriteString(5, 25, display_str, &Font_7x10, ST7735_WHITE, bg_color); + snprintf(display_str, sizeof(display_str), "%.3fC", sensor_data.temperature); + ST7735_WriteString(40, 25, display_str, &Font_16x26, ST7735_ORANGE, bg_color); // 显示湿度(青色 - 国际标准湿度色) - snprintf(display_str, sizeof(display_str), "H: %.2f %%", sensor_data.humidity); - ST7735_WriteString(5, 45, display_str, &Font_11x18, ST7735_CYAN, bg_color); + snprintf(display_str, sizeof(display_str), "Humi:"); + ST7735_WriteString(5, 55, display_str, &Font_7x10, ST7735_WHITE, bg_color); + snprintf(display_str, sizeof(display_str), "%.3f%%", sensor_data.humidity); + ST7735_WriteString(40, 55, display_str, &Font_16x26, ST7735_CYAN, bg_color); break; case LCD_PAGE_FOOD_WEIGHT: // 食物重量页面 ST7735_FillScreen(bg_color); - ST7735_WriteString(2, 2, "Food Weight", &Font_7x10, text_color, bg_color); + + // 绘制标题栏 + ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_YELLOW); + ST7735_WriteString(5, 2, "Food Weight", &Font_7x10, ST7735_BLACK, ST7735_YELLOW); + + // 绘制分隔线 + ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 显示食物重量 - snprintf(display_str, sizeof(display_str), "%.1f g", sensor_data.food_weight); - ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color); + snprintf(display_str, sizeof(display_str), "%.2f g", sensor_data.food_weight); + ST7735_WriteString(15, 25, display_str, &Font_16x26, ST7735_WHITE, bg_color); // 显示状态 if (sensor_data.food_weight < 50.0f) { - ST7735_WriteString(20, 55, "Low", &Font_11x18, ST7735_RED, bg_color); + ST7735_WriteString(20, 65, "LOW", &Font_11x18, ST7735_RED, bg_color); } else if (sensor_data.food_weight < 100.0f) { - ST7735_WriteString(15, 55, "Medium", &Font_11x18, ST7735_YELLOW, bg_color); + ST7735_WriteString(15, 65, "MEDIUM", &Font_11x18, ST7735_YELLOW, bg_color); } else { - ST7735_WriteString(20, 55, "Good", &Font_11x18, ST7735_GREEN, bg_color); + ST7735_WriteString(20, 65, "GOOD", &Font_11x18, ST7735_GREEN, bg_color); } break; case LCD_PAGE_WATER_LEVEL: // 水位页面 ST7735_FillScreen(bg_color); - ST7735_WriteString(2, 2, "Water Level", &Font_7x10, text_color, bg_color); + + // 绘制标题栏 + ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_CYAN); + ST7735_WriteString(5, 2, "Water Level", &Font_7x10, ST7735_BLACK, ST7735_CYAN); + + // 绘制分隔线 + ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 根据水位传感器状态显示 if (sensor_data.water_level == 0) { - ST7735_WriteString(20, 20, "NO WATER", &Font_16x26, ST7735_RED, bg_color); - ST7735_WriteString(35, 55, "Add water", &Font_11x18, ST7735_YELLOW, bg_color); + // 绘制警告背景 + ST7735_FillRectangle(0, 20, ST7735_WIDTH, 30, ST7735_RED); + ST7735_WriteString(20, 20, "NO WATER", &Font_16x26, ST7735_WHITE, ST7735_RED); + ST7735_WriteString(10, 60, "Add water", &Font_16x26, ST7735_YELLOW, bg_color); } else { - ST7735_WriteString(50, 25, "OK", &Font_16x26, ST7735_GREEN, bg_color); - ST7735_WriteString(35, 55, "Water OK", &Font_11x18, text_color, bg_color); + // 绘制正常背景 + ST7735_FillRectangle(0, 20, ST7735_WIDTH, 30, ST7735_GREEN); + ST7735_WriteString(5, 20, "WATER OK", &Font_16x26, ST7735_WHITE, ST7735_GREEN); + ST7735_WriteString(10, 60, "Not add", &Font_16x26, ST7735_WHITE, bg_color); } break; case LCD_PAGE_SYSTEM_STATUS: // 系统状态页面 ST7735_FillScreen(bg_color); - ST7735_WriteString(2, 2, "System Status", &Font_7x10, text_color, bg_color); + + // 绘制标题栏 + ST7735_FillRectangle(0, 0, ST7735_WIDTH, 15, ST7735_MAGENTA); + ST7735_WriteString(5, 2, "System", &Font_7x10, ST7735_WHITE, ST7735_MAGENTA); + + // 绘制分隔线 + ST7735_DrawLine(0, 15, ST7735_WIDTH, 15, ST7735_WHITE); // 显示MQTT连接状态 + snprintf(display_str, sizeof(display_str), "MQTT:"); + ST7735_WriteString(5, 25, display_str, &Font_7x10, ST7735_WHITE, bg_color); if (WIFI_Is_MQTT_Connected()) { - ST7735_WriteString(5, 20, "MQTT: OK", &Font_11x18, ST7735_GREEN, bg_color); + ST7735_WriteString(40, 25, "OK", &Font_7x10, ST7735_GREEN, bg_color); } else { - ST7735_WriteString(5, 20, "MQTT: OFF", &Font_11x18, ST7735_RED, bg_color); + ST7735_WriteString(40, 25, "OFF", &Font_7x10, ST7735_RED, bg_color); } // 显示系统模式 + snprintf(display_str, sizeof(display_str), "Mode:"); + ST7735_WriteString(5, 45, display_str, &Font_7x10, ST7735_WHITE, bg_color); if (sensor_data.system_mode) { - ST7735_WriteString(5, 45, "Mode: AUTO", &Font_11x18, text_color, bg_color); + ST7735_WriteString(40, 45, "AUTO", &Font_7x10, ST7735_GREEN, bg_color); } else { - ST7735_WriteString(5, 45, "Mode: MANUAL", &Font_11x18, text_color, bg_color); + ST7735_WriteString(40, 45, "MANUAL", &Font_7x10, ST7735_YELLOW, bg_color); + } + + // 显示喂食状态 + snprintf(display_str, sizeof(display_str), "Feed:"); + ST7735_WriteString(5, 65, display_str, &Font_7x10, ST7735_WHITE, bg_color); + if (feeding_in_progress) { + ST7735_WriteString(40, 65, "Busy", &Font_7x10, ST7735_ORANGE, bg_color); + } else { + ST7735_WriteString(40, 65, "Idle", &Font_7x10, ST7735_WHITE, bg_color); } break; } @@ -464,6 +546,131 @@ void sensorTask(void *argument) /* USER CODE END sensorTask */ } +/** + * @brief 请求喂食操作 + * @param cmd: 喂食命令类型 + * @param angle: 转动角度 + * @param amount: 喂食份数 + * @retval 1=请求成功, 0=请求失败(正在喂食中) + */ +uint8_t Request_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) { + if (feeding_in_progress) { + elog_w("FEED", "喂食进行中,无法接受新命令"); + return 0; + } + + feed_command = cmd; + feed_angle = angle; + feed_amount = amount; + + elog_i("FEED", "喂食请求已提交: cmd=%d, angle=%d, amount=%d", cmd, angle, amount); + return 1; +} + +/** + * @brief 获取当前喂食命令 + * @retval 当前喂食命令 + */ +Feed_Cmd_t Get_Feed_Command(void) { + return feed_command; +} + +/** + * @brief 清除喂食命令 + */ +void Clear_Feed_Command(void) { + feed_command = FEED_CMD_NONE; +} + +/** + * @brief 执行喂食操作 + * @param cmd: 喂食命令类型 + * @param angle: 转动角度 + * @param amount: 喂食份数 + */ +static void Execute_Feed(Feed_Cmd_t cmd, uint16_t angle, uint8_t amount) { + if (feeding_in_progress) { + return; // 防止重复执行 + } + + feeding_in_progress = 1; + + // 根据命令类型播放不同音频 + switch (cmd) { + case FEED_CMD_MANUAL: + MP3_Play(FEED_MANUAL_TRIGGER); + elog_i("FEED", "执行手动喂食: 角度%d度, %d份", angle, amount); + break; + case FEED_CMD_AUTO: + MP3_Play(FEED_AUTO_START); + elog_i("FEED", "执行自动喂食: 角度%d度, %d份", angle, amount); + break; + case FEED_CMD_REMOTE: + MP3_Play(REMOTE_CMD_RECEIVED); + elog_i("FEED", "执行远程喂食: 角度%d度, %d份", angle, amount); + break; + case FEED_CMD_TEST: + elog_i("FEED", "执行测试喂食: 角度%d度", angle); + break; + default: + break; + } + + // 执行实际的喂食动作 + for (uint8_t i = 0; i < amount; i++) { + Stepper_Motor_RotateAngle(angle, STEPPER_DIR_CW, 2, STEPPER_MODE_FULL_STEP); + if (i < amount - 1) { + osDelay(1000); // 多份之间间隔1秒 + } + } + + // 等待食物落下 + osDelay(3000); + + // 停止电机 + Stepper_Motor_Stop(); + + // 播放完成音效 + MP3_Play(FEED_COMPLETE); + elog_i("FEED", "喂食完成"); + + feeding_in_progress = 0; +} + +/* USER CODE BEGIN Header_step_motor_task */ +/** +* @brief Function implementing the step_motor thread. +* @param argument: Not used +* @retval None +*/ +/* USER CODE END Header_step_motor_task */ +void step_motor_task(void *argument) +{ + /* USER CODE BEGIN step_motor_task */ + // 初始化步进电机 + Stepper_Motor_Init(); + + /* Infinite loop */ + for(;;) + { + // 检查是否有喂食命令需要执行 + if (feed_command != FEED_CMD_NONE && !feeding_in_progress) { + Feed_Cmd_t current_cmd = feed_command; + uint16_t current_angle = feed_angle; + uint8_t current_amount = feed_amount; + + // 清除命令标志 + Clear_Feed_Command(); + + // 执行喂食 + Execute_Feed(current_cmd, current_angle, current_amount); + } + + osDelay(100); // 短暂延时,保持任务响应性 + } + /* USER CODE END step_motor_task */ +} + /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ @@ -475,6 +682,8 @@ static Button KEY1; // 按键1 static Button KEY2; // 按键2 static Button KEY3; // 按键3 static Button KEY4; // 按键4 +static Button M3_IO; // M3 IO +static Button HC_SR505; //HC-SR505 uint8_t read_button_gpio(uint8_t button_id) { switch (button_id) { @@ -490,14 +699,58 @@ uint8_t read_button_gpio(uint8_t button_id) { case 4: return HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin); break; + case 5: + return HAL_GPIO_ReadPin(M3_IO_GPIO_Port, M3_IO_Pin); + break; + case 6: + return HAL_GPIO_ReadPin(HC_SR505_IO_GPIO_Port, HC_SR505_IO_Pin); + break; default: return 0; } } -void key1_single_click_handler(Button *btn) { elog_i("KEY", "按键1单击"); } +/** + * @brief 按键1处理函数:模式切换(自动/手动) + * @param btn: 按键句柄 + */ +void key1_single_click_handler(Button *btn) { + // 切换系统模式 + system_mode = !system_mode; // 0和1之间切换 + + // 更新传感器数据中的模式状态 + sensor_data.system_mode = system_mode; + + // 记录日志 + elog_i("KEY", "按键1单击 - 模式切换: %s", system_mode ? "自动模式" : "手动模式"); + + // 播放模式切换音效 + MP3_Play(system_mode ? MODE_AUTO : MODE_MANUAL); + + // 强制刷新LCD显示以更新模式状态 + lcd_force_refresh = 1; +} -void key2_single_click_handler(Button *btn) { elog_i("KEY", "按键2单击"); } +/** + * @brief 按键2处理函数:手动喂食(仅手动模式有效) + * @param btn: 按键句柄 + */ +void key2_single_click_handler(Button *btn) { + // 检查是否为手动模式 + if (!system_mode) { + // 手动模式下才允许手动喂食 + if (Request_Feed(FEED_CMD_MANUAL, 90, 1)) { + elog_i("KEY", "按键2单击 - 手动喂食请求已提交"); + } else { + elog_w("KEY", "喂食进行中,请稍后再试"); + MP3_Play(SYS_ERROR_ALARM); + } + } else { + // 自动模式下按键无效 + elog_w("KEY", "当前为自动模式,按键2无效"); + MP3_Play(SYS_ERROR_ALARM); + } +} void key3_single_click_handler(Button *btn) { elog_i("KEY", "按键3单击"); @@ -506,13 +759,28 @@ void key3_single_click_handler(Button *btn) { void key4_single_click_handler(Button *btn) { elog_i("KEY", "按键4单击"); } +void M3_long_press_start_handler(Button *btn) { + elog_i("KEY", "M3水位传感器检测到有水(低电平)"); + sensor_data.water_level = 1; +} + +void M3_press_up_handler(Button *btn) { + elog_i("KEY", "M3水位传感器检测到无水(高电平)"); + sensor_data.water_level = 0; +} +void HC_SR055_long_click_handler(Button *btn) { elog_i("KEY", "HC-SR505触发"); } + + + + void user_button_init(void) { // 初始化按键 (active_level: 0=低电平有效, 1=高电平有效) button_init(&KEY1, read_button_gpio, 0, 1); button_init(&KEY2, read_button_gpio, 0, 2); button_init(&KEY3, read_button_gpio, 0, 3); button_init(&KEY4, read_button_gpio, 0, 4); - + button_init(&M3_IO, read_button_gpio, 0, 5); + button_init(&HC_SR505, read_button_gpio, 1, 6); elog_i("BUTTON", "按键初始化完成"); // 设置按键回调函数 @@ -520,6 +788,9 @@ void user_button_init(void) { button_attach(&KEY2, BTN_SINGLE_CLICK, key2_single_click_handler); button_attach(&KEY3, BTN_SINGLE_CLICK, key3_single_click_handler); button_attach(&KEY4, BTN_SINGLE_CLICK, key4_single_click_handler); + button_attach(&M3_IO, BTN_LONG_PRESS_START, M3_long_press_start_handler); + button_attach(&M3_IO, BTN_PRESS_UP, M3_press_up_handler); + button_attach(&HC_SR505, BTN_LONG_PRESS_START, HC_SR055_long_click_handler); // 长按触发 elog_i("BUTTON", "按键回调函数设置完成"); @@ -528,9 +799,41 @@ void user_button_init(void) { button_start(&KEY2); button_start(&KEY3); button_start(&KEY4); + button_start(&M3_IO); + button_start(&HC_SR505); elog_i("BUTTON", "按键任务已启动"); } + +/** + * @brief 获取当前系统模式 + * @retval 系统模式 (0=手动, 1=自动) + */ +uint8_t Get_System_Mode(void) { + return system_mode; +} + +/** + * @brief 设置系统模式 + * @param mode: 目标模式 (0=手动, 1=自动) + */ +void Set_System_Mode(uint8_t mode) { + if (mode <= 1) { + system_mode = mode; + sensor_data.system_mode = mode; + lcd_force_refresh = 1; // 强制刷新显示 + elog_i("SYSTEM", "系统模式设置为: %s", mode ? "自动" : "手动"); + } +} + +/** + * @brief 检查是否正在喂食 + * @retval 1=正在喂食, 0=空闲 + */ +uint8_t Is_Feeding_In_Progress(void) { + return feeding_in_progress; +} + /* USER CODE END KEY Prototypes */ /* USER CODE BEGIN LCD_Page_Functions */ @@ -619,5 +922,4 @@ void RTC_TimeUpdateCallback(void) { /* USER CODE END LCD_Page_Functions */ -/* USER CODE END Application */ - +/* USER CODE END Application */ \ No newline at end of file diff --git a/Core/Src/gpio.c b/Core/Src/gpio.c index 48973ef..5594d4b 100644 --- a/Core/Src/gpio.c +++ b/Core/Src/gpio.c @@ -51,11 +51,11 @@ void MX_GPIO_Init(void) __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOA, HX711_SCK_Pin|LCD_DC_Pin|LCD_RESET_Pin|GPIO_PIN_8, GPIO_PIN_RESET); + HAL_GPIO_WritePin(GPIOA, HX711_SCK_Pin|LCD_DC_Pin|LCD_RESET_Pin|STEPPER_IN4_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOC, LCD_CS_Pin|LCD_BLK_Pin|MOTOR_Pin|GPIO_PIN_7 - |GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_RESET); + HAL_GPIO_WritePin(GPIOC, LCD_CS_Pin|LCD_BLK_Pin|MOTOR_Pin|STEPPER_IN1_Pin + |STEPPER_IN2_Pin|STEPPER_IN3_Pin, GPIO_PIN_RESET); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, LED2_Pin|LED1_Pin, GPIO_PIN_SET); @@ -72,17 +72,17 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(HX711_DOUT_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pins : HX711_SCK_Pin LCD_DC_Pin LCD_RESET_Pin PA8 */ - GPIO_InitStruct.Pin = HX711_SCK_Pin|LCD_DC_Pin|LCD_RESET_Pin|GPIO_PIN_8; + /*Configure GPIO pins : HX711_SCK_Pin LCD_DC_Pin LCD_RESET_Pin STEPPER_IN4_Pin */ + GPIO_InitStruct.Pin = HX711_SCK_Pin|LCD_DC_Pin|LCD_RESET_Pin|STEPPER_IN4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - /*Configure GPIO pins : LCD_CS_Pin LCD_BLK_Pin MOTOR_Pin PC7 - PC8 PC9 */ - GPIO_InitStruct.Pin = LCD_CS_Pin|LCD_BLK_Pin|MOTOR_Pin|GPIO_PIN_7 - |GPIO_PIN_8|GPIO_PIN_9; + /*Configure GPIO pins : LCD_CS_Pin LCD_BLK_Pin MOTOR_Pin STEPPER_IN1_Pin + STEPPER_IN2_Pin STEPPER_IN3_Pin */ + GPIO_InitStruct.Pin = LCD_CS_Pin|LCD_BLK_Pin|MOTOR_Pin|STEPPER_IN1_Pin + |STEPPER_IN2_Pin|STEPPER_IN3_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; diff --git a/hahha.ioc b/hahha.ioc index a1c5f0e..51d1cc4 100644 --- a/hahha.ioc +++ b/hahha.ioc @@ -35,7 +35,7 @@ Dma.USART1_TX.0.Priority=DMA_PRIORITY_VERY_HIGH Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority FREERTOS.FootprintOK=true FREERTOS.IPParameters=Tasks01,FootprintOK,configTOTAL_HEAP_SIZE -FREERTOS.Tasks01=defaultTask,41,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL;sensor,24,1024,sensorTask,Default,NULL,Dynamic,NULL,NULL +FREERTOS.Tasks01=defaultTask,41,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL;sensor,24,1024,sensorTask,Default,NULL,Dynamic,NULL,NULL;step_motor,24,512,step_motor_task,Default,NULL,Dynamic,NULL,NULL FREERTOS.configTOTAL_HEAP_SIZE=30000 File.Version=6 GPIO.groupedBy=Group By Peripherals @@ -147,6 +147,8 @@ PA6.Locked=true PA6.Signal=GPIO_Output PA7.Mode=TX_Only_Simplex_Unidirect_Master PA7.Signal=SPI1_MOSI +PA8.GPIOParameters=GPIO_Label +PA8.GPIO_Label=STEPPER_IN4 PA8.Locked=true PA8.Signal=GPIO_Output PA9.Mode=Asynchronous @@ -215,10 +217,16 @@ PC6.GPIOParameters=GPIO_Label PC6.GPIO_Label=MOTOR PC6.Locked=true PC6.Signal=GPIO_Output +PC7.GPIOParameters=GPIO_Label +PC7.GPIO_Label=STEPPER_IN1 PC7.Locked=true PC7.Signal=GPIO_Output +PC8.GPIOParameters=GPIO_Label +PC8.GPIO_Label=STEPPER_IN2 PC8.Locked=true PC8.Signal=GPIO_Output +PC9.GPIOParameters=GPIO_Label +PC9.GPIO_Label=STEPPER_IN3 PC9.Locked=true PC9.Signal=GPIO_Output PCC.Checker=false