1.添加了必要的注释和readme文件以提高代码可读性和项目文档化。2.增加了按键按下时LCD屏幕显示功能

This commit is contained in:
2026-01-25 15:42:48 +08:00
parent 373b31adfd
commit 6a861cee4e
30 changed files with 2311 additions and 50645 deletions

View File

@@ -1319,28 +1319,45 @@ void FillBoxScreen(uint8_t x, uint8_t y, uint8_t len, uint8_t high, uint8_t byte
y++; /* 移动到下一行 */ y++; /* 移动到下一行 */
} }
} }
/**
* @brief LCD 控制与背光 GPIO 初始化
* @note 初始化 UC1698U 控制器所需的 GPIO 引脚,分为两组:
* GPIOB 组(控制信号与背光):
* | 引脚 | 功能 | 说明 |
* |------|----------|----------------|
* | PB0 | 背光 | 背光开关 |
* | PB10 | RST | 复位信号 |
* | PB11 | CS | 片选 |
* | PB12 | RD | 读使能 |
* | PB13 | WR | 写使能 |
* | PB14 | CD | 命令/数据选择 |
* GPIOE 组(数据总线):
* | 引脚 | 功能 | 说明 |
* |-----------|----------|-------------------------|
* | PE8~PE15 | D0~D7 | 8 位并行数据总线 |
* 配置为推挽输出、上拉、高速模式
* @retval 无
*/
void LCD_GPIO_Init(void) void LCD_GPIO_Init(void)
{ {
GPIO_InitTypeDef gpio_init_struct; GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOB_CLK_ENABLE(); // 时钟初始化 __HAL_RCC_GPIOB_CLK_ENABLE(); /**< 使能 GPIOB 时钟 */
__HAL_RCC_GPIOE_CLK_ENABLE(); // 时钟初始化 __HAL_RCC_GPIOE_CLK_ENABLE(); /**< 使能 GPIOE 时钟 */
/* ========== GPIOB控制信号与背光 ========== */
// 配置 LCD 引脚
gpio_init_struct.Pin = GPIO_PIN_14 | GPIO_PIN_13 | GPIO_PIN_12 | GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_0; gpio_init_struct.Pin = GPIO_PIN_14 | GPIO_PIN_13 | GPIO_PIN_12 | GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_0;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /*推挽输出模式*/ gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /**< 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Pull = GPIO_PULLUP; /**< 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_HIGH; gpio_init_struct.Speed = GPIO_SPEED_HIGH; /**< 高速 */
// 初始化 LCD 选引脚
HAL_GPIO_Init(GPIOB, &gpio_init_struct); HAL_GPIO_Init(GPIOB, &gpio_init_struct);
// 配置 LCD 引脚 /* ========== GPIOE8 位数据总线 ========== */
gpio_init_struct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; gpio_init_struct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /*推挽输出模式*/ gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /**< 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Pull = GPIO_PULLUP; /**< 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_HIGH; gpio_init_struct.Speed = GPIO_SPEED_HIGH; /**< 高速 */
// 初始化 LCD 选引脚
HAL_GPIO_Init(GPIOE, &gpio_init_struct); HAL_GPIO_Init(GPIOE, &gpio_init_struct);
} }
/** /**
@@ -1422,25 +1439,30 @@ void DisplayNANRUI_LOGO(void)
} }
/******************************************************************************* /**
* FunctionName : Display_Picture() * @brief 在 LCD 上显示指定尺寸的位图
* Description : 图片显示程序 * @param Width 位图宽度(像素,不超过 160
* EntryParameter : none * @param Height 位图高度(像素,不超过 160
* ReturnValue : none * @param picture 位图数据指针(每像素 1 位,每行 Width/8 字节,行内取反显示)
*******************************************************************************/ * @note 使用 4K 色RGB444模式位图居中显示
* X 坐标按每 3 像素递增;每行末尾补 0 使点数能被 3 整除
* 若 Width 或 Height 大于 160 则直接返回
* @retval 无
*/
void Display_BMP(uint32_t Width, uint32_t Height, const uint8_t* picture) void Display_BMP(uint32_t Width, uint32_t Height, const uint8_t* picture)
{ {
uint8_t i, n; uint8_t i, n;
uint8_t disp_point[4]={0x00,0x0f,0xf0,0xff}; uint8_t disp_point[4] = {0x00, 0x0f, 0xf0, 0xff}; /**< 4K 色像素值映射 */
uint8_t x, y; uint8_t x, y;
uint32_t bytesNumOfRow; uint32_t bytesNumOfRow;
set_color_mode(COLOR_4K_444);//设置4.4.4.模式
set_color_mode(COLOR_4K_444); /**< 设置 4K 色模式 */
if (Width > 160 || Height > 160) if (Width > 160 || Height > 160)
return ; //设置4.4.4.模式 return;
x = ((160-Width)/2)/3; //x坐标为每3个像素点递增 x = ((160 - Width) / 2) / 3; /**< 居中 X每 3 像素为一单位 */
y = (160-Height)/2 + Height; //x坐标为每3个像素点递增 y = (160 - Height) / 2 + Height; /**< 居中 Y自下而上绘制 */
bytesNumOfRow = Width / 8; bytesNumOfRow = Width / 8;
for (i = 0; i < Height; i++) for (i = 0; i < Height; i++)
@@ -1453,32 +1475,29 @@ void Display_BMP(uint32_t Width, uint32_t Height, const uint8_t* picture)
WriteData(disp_point[(~picture[i * bytesNumOfRow + n] >> 2) & 0x03]); WriteData(disp_point[(~picture[i * bytesNumOfRow + n] >> 2) & 0x03]);
WriteData(disp_point[(~picture[i * bytesNumOfRow + n] >> 0) & 0x03]); WriteData(disp_point[(~picture[i * bytesNumOfRow + n] >> 0) & 0x03]);
} }
WriteData(0x00); //补全每行末尾的数据,使总点数能被三整除 WriteData(0x00); /**< 补足使每行点数能被 3 整除 */
} }
} }
/******************************************************************************* /**
* FunctionName : DisplayNANRUI_BMP() * @brief 显示南瑞NANRUILogo 位图
* Description : BMP格式显示测试 * @note 调用 Display_BMP尺寸 128×50 像素,数据源为 NANRUI_BMP
* EntryParameter : none * @retval 无
* ReturnValue : none */
*******************************************************************************/
void DisplayNANRUI_BMP(void) void DisplayNANRUI_BMP(void)
{ {
const uint32_t xmax = 128, ymax = 50; const uint32_t xmax = 128, ymax = 50;
Display_BMP(xmax, ymax, NANRUI_BMP); Display_BMP(xmax, ymax, NANRUI_BMP);
} }
/******************************************************************************* /**
* FunctionName : DisplayNANRUI_BMP() * @brief 显示 QQ Logo 位图
* Description : BMP格式显示测试 * @note 调用 Display_BMP尺寸 96×94 像素,数据源为 QQ_BMP
* EntryParameter : none * @retval 无
* ReturnValue : none */
*******************************************************************************/
void DisplayQQ_BMP(void) void DisplayQQ_BMP(void)
{ {
const uint32_t xmax = 96, ymax = 94; const uint32_t xmax = 96, ymax = 94;
Display_BMP(xmax, ymax, QQ_BMP); Display_BMP(xmax, ymax, QQ_BMP);
} }
/** /**
@@ -1589,58 +1608,6 @@ void DiaplayHeadGraph(void)
/* 绘制左侧垂直边框从第18行开始高度142 */ /* 绘制左侧垂直边框从第18行开始高度142 */
DisplayVerticalLine(0, 18, 142, 0x80); DisplayVerticalLine(0, 18, 142, 0x80);
} }
/**
* @brief 绘制带阴影效果的方框(内部函数)
* @param x 起始列坐标X坐标
* @param y 起始行坐标Y坐标
* @param len 方框宽度(像素单位)
* @param high 方框高度(行数)
* @note 绘制一个带3D阴影效果的方框
* 包括:左边框、上边框、下边框(带阴影)、右边框(带渐变阴影效果)
* @retval 无
*/
static void DrawBox(uint8_t x, uint8_t y, uint8_t len, uint8_t high)
{
/* 绘制左边框 */
DisplayVerticalLine(x, y, high, 0x80);
/* 绘制上边框 */
DisplayHorizontalLine(x, y, len);
/* 绘制下边框 */
DisplayHorizontalLine(x, y + high, len);
/* 绘制第一层阴影 */
writebyte(x, y + high + 1, 0x7F, RESET);
DisplayHorizontalLine(x + 1, y + high + 1, len);
/* 绘制第二层阴影 */
writebyte(x, y + high + 2, 0x3F, RESET);
DisplayHorizontalLine(x + 1, y + high + 2, len);
/* 绘制右边框(带渐变阴影效果) */
DisplayVerticalLine(x + len, y, high + 3, 0x80); /* 主边框 */
DisplayVerticalLine(x + len, y + 1, high + 2, 0xC0); /* 第一层阴影 */
DisplayVerticalLine(x + len, y + 2, high + 1, 0xE0); /* 第二层阴影 */
}
/**
* @brief 绘制无阴影效果的方框(内部函数)
* @param x 起始列坐标X坐标
* @param y 起始行坐标Y坐标
* @param len 方框宽度(像素单位)
* @param high 方框高度(行数)
* @note 绘制一个简单的方框,无阴影效果
* 包括:左边框、上边框、下边框、右边框
* @retval 无
*/
static void DrawBox_NoShadow(uint8_t x, uint8_t y, uint8_t len, uint8_t high)
{
DisplayVerticalLine(x, y, high, 0x80); /* 左边框 */
DisplayHorizontalLine(x, y, len); /* 上边框 */
DisplayHorizontalLine(x, y + high, len); /* 下边框 */
DisplayVerticalLine(x + len - 1, y, high, 0x20); /* 右边框 */
}
/** /**
* @brief 绘制一个像素点(带渐变效果) * @brief 绘制一个像素点(带渐变效果)
* @param x 列坐标X坐标 * @param x 列坐标X坐标
@@ -1665,168 +1632,3 @@ void DrawPoint(uint8_t x, uint8_t y)
DisplayVerticalLine(x + 1, y + 4, 1, 0x60); DisplayVerticalLine(x + 1, y + 4, 1, 0x60);
DisplayHorizontalLine(x + 1, y + 5, 1); DisplayHorizontalLine(x + 1, y + 5, 1);
} }
/**
* @brief 显示消息提示框(静态模式)
* @note 在屏幕指定位置显示一个带阴影的消息提示框
* 位置:坐标(10,40)尺寸33x50像素
* 先清除背景区域,然后绘制方框
* 动态框模式代码已注释需要定义DYNAMIC_BOX
* @retval 无
*/
void MessageBox(void)
{
/* 静态框模式 */
FillBoxScreen(9, 37, 35, 59, 0x00); /* 清除背景区域 */
DrawBox(10, 40, 33, 50); /* 绘制带阴影的方框 */
}
/**
* @brief 清除消息提示框
* @note 清除消息提示框的整个显示区域
* 位置和尺寸与MessageBox函数对应
* @retval 无
*/
void ClrMessageBox(void)
{
FillBoxScreen(9, 37, 35, 59, 0x00); /* 清除消息框区域 */
}
/**
* @brief 清除消息提示框内容区域
* @note 只清除消息框内部的内容区域,保留边框
* 用于刷新消息内容而不重新绘制边框
* @retval 无
*/
void ClrMessageBoxContent(void)
{
FillBoxScreen(11, 41, 31, 48, 0x00); /* 清除内容区域(保留边框) */
}
/**
* @brief 显示可编程消息提示框(支持静态和动态模式)
* @param x 起始列坐标X坐标
* @param y 起始行坐标Y坐标
* @param size 方框大小参数(影响方框尺寸)
* @note 根据编译选项显示静态或动态消息框
* 静态模式:直接绘制完整方框
* 动态模式:逐步绘制方框,产生动画效果
* 方框尺寸:宽度=size高度=size*3/2+9
* @retval 无
*/
void ProgramableMessageBox(uint8_t x, uint8_t y, uint8_t size)
{
#ifdef STATIC_BOX /* 静态框模式 */
FillBoxScreen(x - 1, y - 3, size + 2, size * 3 / 2 + 9, 0x00); /* 清除背景 */
DrawBox(x, y, size, size * 3 / 2 + 9); /* 绘制方框 */
#endif //#ifdef STATIC_BOX
#ifdef DYNAMIC_BOX /* 动态框模式 */
FillBoxScreen(x - 1, y - 3, size * 2 + 2, size * 3 + 9, 0x00); /* 清除背景 */
/* 逐步绘制方框,产生动画效果 */
for(int i = 0; i < size; i++)
{
EraseLine(x, y, i * 2, i * 3); /* 擦除旧线条 */
DrawBox(x, y, (i + 1) * 2, (i + 1) * 3); /* 绘制新方框 */
delay_ms(5); /* 延时,产生动画效果 */
}
#endif //#ifdef DYNAMIC_BOX
}
/**
* @brief 绘制下拉菜单组合框
* @param x 起始列坐标X坐标
* @param y 起始行坐标Y坐标
* @param len 菜单框宽度(像素单位)
* @param items 菜单项数量
* @note 根据编译选项显示静态或动态菜单框
* 静态模式:直接绘制完整菜单框
* 动态模式:逐步绘制菜单框,产生下拉动画效果
* 每个菜单项高度20像素
* @retval 无
*/
void DrawMenuComboBox(uint8_t x, uint8_t y, uint8_t len, uint8_t items)
{
#ifdef STATIC_BOX /* 静态框模式 */
FillBoxScreen(x - 1, y - 6, len + 3, items * 20 + 9, 0x00); /* 清除背景 */
DrawBox(x - 1, y - 3, len + 2, items * 20); /* 绘制菜单框 */
#endif //#ifdef STATIC_BOX
#ifdef DYNAMIC_BOX /* 动态框模式 */
FillBoxScreen(x - 2, y - 6, len + 3, items * 20 + 9, 0x00); /* 清除背景 */
/* 逐步绘制菜单框,产生下拉动画效果 */
for(int i = 0; i < items; i++)
{
EraseLine(x - 1, y - 6, len + 2, i * 20 + 3); /* 擦除旧线条 */
DrawBox(x - 1, y - 3, len + 2, (i + 1) * 20); /* 绘制新菜单框 */
delay_ms(15); /* 延时,产生动画效果 */
}
#endif //#ifdef DYNAMIC_BOX
}
/**
* @brief 显示消息设置提示框(带标题栏,清除背景)
* @param title 标题字符串指针GB2312编码
* @note 显示一个带标题栏的消息设置框
* 先清除背景区域,然后绘制无阴影方框
* 标题栏区域填充白色,标题文字反显
* 位置:坐标(8,40)尺寸37x70像素
* @retval 无
*/
void MessageSetBox(const uint8_t *title)
{
FillBoxScreen(7, 37, 39, 79, 0x00); /* 清除背景区域 */
DrawBox_NoShadow(8, 40, 37, 70); /* 绘制无阴影方框 */
FillBoxScreen(8, 40, 37, 20, 0xFF); /* 填充标题栏区域(白色) */
HZ12AndChar_Printf(18, 43, title, SET); /* 显示标题(反显) */
}
/**
* @brief 显示消息设置提示框(带标题栏,不清除背景)
* @param title 标题字符串指针GB2312编码
* @note 功能与MessageSetBox类似但不清除背景区域
* 用于在已有内容上叠加显示消息框
* @retval 无
*/
void MessageSetBox_NoClear(const uint8_t *title)
{
DrawBox_NoShadow(8, 40, 37, 70); /* 绘制无阴影方框 */
FillBoxScreen(8, 40, 37, 20, 0xFF); /* 填充标题栏区域(白色) */
HZ12AndChar_Printf(18, 43, title, SET); /* 显示标题(反显) */
}
/**
* @brief 清除消息设置框的内容显示区域
* @note 只清除消息框内部的内容区域,保留标题栏和边框
* 用于刷新消息内容而不重新绘制整个框
* @retval 无
*/
void ClrMessageSetBox(void)
{
FillBoxScreen(9, 61, 34, 48, 0x00); /* 清除内容区域(保留标题栏和边框) */
}
/**
* @brief 删除消息设置提示框
* @note 清除整个消息设置框的显示区域
* 包括标题栏、边框和内容区域
* @retval 无
*/
void DeleteMessageSetBox(void)
{
FillBoxScreen(7, 37, 39, 79, 0x00); /* 清除整个消息框区域 */
}
/**
* @brief 绘制记录框(用于显示记录列表)
* @param x 起始列坐标X坐标
* @param y 起始行坐标Y坐标
* @param len 框宽度(像素单位)
* @param items 记录项数量
* @note 绘制一个带阴影的记录列表框
* 每个记录项高度16像素显示区域19像素包括间距
* 先清除背景,然后绘制方框
* @retval 无
*/
void DrawRecordBox(uint8_t x, uint8_t y, uint8_t len, uint8_t items)
{
FillBoxScreen(x - 1, y - 3, len + 2, items * 16 + 6, 0x00); /* 清除背景 */
DrawBox(x - 1, y - 3, len + 2, items * 19); /* 绘制记录框 */
}

View File

@@ -1,117 +1,116 @@
/******************************************************************************
* @file 160160D.h
* @brief UC1698U控制器驱动的 160x160 像素 LCD 显示屏驱动头文件
* @details 本文件定义了 LCD 驱动的接口函数、状态码宏定义以及 ASCII 字体数据。
* 包含显示控制、字符显示、图形显示、菜单操作等功能的函数声明。
* @author 阜阳师范大学物电学院
* @version V0.1
* @date 2026.1.19
* @note 控制器: UC1698U
* 显示屏: 160x160像素
* 颜色模式: 4K色RGB444和 64K色RGB565
******************************************************************************/
#ifndef __160160D_H__ #ifndef __160160D_H__
#define __160160D_H__ #define __160160D_H__
#include "./SYSTEM/sys/sys.h" #include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h" #include "./SYSTEM/delay/delay.h"
#define LCD_EXT extern /* ============================================================================
* 显示状态码定义
* ============================================================================ */
#define DISPLAY_BLANK 0x00 /**< 显示空白 */
#define PROTEC_ON 0x01 /**< 保护开启 */
#define PROTEC_EXIT 0x02 /**< 保护退出 */
#define PROTEC_START 0x03 /**< 保护启动 */
#define PROTECT_TRIP 0x04 /**< 保护跳闸 */
#define HEART_ACCUM 0x05 /**< 心跳累加 */
#define HEART_REDUCE 0x06 /**< 心跳减少 */
#define NEXT_MENU 0x07 /**< 下一菜单 */
#define UP_DOWN 0x08 /**< 上下键 */
#define LEFT_RIGHT 0x09 /**< 左右键 */
#define DERECTION_KEY 0x0a /**< 方向键 */
#define ENTER_KEY 0x0b /**< 确认键 */
#define ESC_KEY 0x0c /**< 取消键 */
#define ADD_DEC 0x0d /**< 加减键 */
#define DI_CLOSE 0x0e /**< 数字输入关闭 */
#define DI_OPEN 0x0f /**< 数字输入开启 */
#define DI_UNCERT 0x10 /**< 数字输入不确定 */
#define DELET_MESS 0x11 /**< 删除消息 */
#define HAVE_MESS 0x12 /**< 有消息 */
#define CODE_PASS 0x13 /**< 密码通过 */
#define CODE_ERROR 0x14 /**< 密码错误 */
/* ============================================================================
* 函数声明(仅包含 160160D.C 中实际定义的函数)
* ============================================================================ */
/* ---------- 硬件初始化函数 ---------- */
void LCD_GPIO_Init(void);
#define DISPLAY_BLANK 0x00 void LCD_Reset(void);
#define PROTEC_ON 0x01 void LCD_InitXRD(void);
#define PROTEC_EXIT 0x02 void LcdInit(void);
#define PROTEC_START 0x03
#define PROTECT_TRIP 0x04
#define HEART_ACCUM 0x05
#define HEART_REDUCE 0x06
#define NEXT_MENU 0x07
#define UP_DOWN 0x08
#define LEFT_RIGHT 0x09
#define DERECTION_KEY 0x0a
#define ENTER_KEY 0x0b
#define ESC_KEY 0x0c
#define ADD_DEC 0x0d
#define DI_CLOSE 0x0e
#define DI_OPEN 0x0f
#define DI_UNCERT 0x10
#define DELET_MESS 0x11
#define HAVE_MESS 0x12
#define CODE_PASS 0x13
#define CODE_ERROR 0x14
LCD_EXT void display_datas(unsigned char datas); //R-G-B=4-4-4
LCD_EXT void display_line(unsigned char datas);
LCD_EXT void ReverseShow88(uint8_t column ,uint8_t lin,uint8_t const *address);
LCD_EXT void ReverseShow916(uint8_t column ,uint8_t lin,uint8_t const *address);
LCD_EXT void DisplayOneByteS(uint8_t ox, uint8_t oy, uint8_t byte1);
LCD_EXT void DisplayOneText(uint8_t ox,uint8_t oy,uint8_t ascii_code);
LCD_EXT void Clear_Line(uint8_t ox1,uint8_t ox2,uint8_t oy,uint8_t number1,uint8_t number2,uint8_t language);
LCD_EXT void DisplayBrokenLine(uint8_t ox,uint8_t oy,uint8_t num);
LCD_EXT void DisplaySolidLine(uint8_t ox,uint8_t oy,uint8_t num);
LCD_EXT void DisplayLineText2(uint8_t ox,uint8_t oy,uint8_t number,uint8_t const *ptr);
LCD_EXT void DisplayOneChinesetest(uint8_t column,uint8_t lin);
LCD_EXT void REDisplayOneChinesetest(uint8_t column,uint8_t lin);
LCD_EXT void ReverseDispOne(uint8_t ox,uint8_t oy,uint8_t ascii_code);
LCD_EXT void ReverseLineText(uint8_t ox,uint8_t oy,uint8_t number,uint8_t const *ptr);
LCD_EXT void ReverseDispLine(uint8_t ox,uint8_t oy,uint8_t number,uint8_t const *ptr);
LCD_EXT void ReverseOneGraphics(uint8_t column,uint8_t lin,uint16_t hzcode,uint8_t tb_index);
LCD_EXT void display_pic(unsigned char *p);
LCD_EXT void display_pic1(unsigned char *p);
LCD_EXT void DisplayOneGraphics(uint8_t column,uint8_t lin, uint16_t hzcode,uint8_t tb_index);
LCD_EXT void DisplayConnectGraphics(uint8_t column,uint8_t lin,uint16_t hzcode);
LCD_EXT void DisplayOneChinese(uint8_t column,uint8_t lin,uint8_t const *HzCode);
LCD_EXT void DisplayLineChinese(uint8_t ox1,uint8_t oy1,uint8_t number1,uint8_t const *ptr1,uint8_t ox2,uint8_t oy2,uint8_t number2,uint8_t const *ptr2,uint8_t language);
LCD_EXT void DeleteMenuCursor(uint8_t x1,uint8_t y1,uint8_t const *ptr1,uint8_t x2,uint8_t y2,uint8_t const *ptr2,uint8_t num,uint8_t num2,uint8_t language);
LCD_EXT void DisplayMenuCursor(uint8_t x1,uint8_t y1,uint8_t const *ptr1,uint8_t x2,uint8_t y2,uint8_t const *ptr2,uint8_t num,uint8_t num2,uint8_t language);
LCD_EXT void ClearScreen(void);
void ReverseScreen(void);
LCD_EXT void CloseDataCursor(void);
LCD_EXT void DisplayDataCursor2(void);
LCD_EXT void DisplayDataCursor(void);
LCD_EXT void DisplayOneInt(uint8_t ox,uint8_t oy, uint16_t int_value);
LCD_EXT void DisplayOneText2(uint8_t ox,uint8_t oy,uint8_t ascii_code);
LCD_EXT void DisplayHoriLine(uint8_t column,uint8_t lin,uint8_t num);
LCD_EXT void DisplayOneByte(uint8_t ox, uint8_t oy, uint8_t byte1);
LCD_EXT void DisplayGraphicsScreen(void);
LCD_EXT void DisplayLineText(uint8_t ox,uint8_t oy,uint8_t number,uint8_t const *ptr);
LCD_EXT void DisplayOneInt2(uint8_t ox,uint8_t oy, uint16_t int_value);
LCD_EXT void DisplayLongInt(uint8_t ox,uint8_t oy, uint32_t value,uint8_t redixs_point);
LCD_EXT void DisplayOneByte2(uint8_t ox, uint8_t oy, uint8_t byte1);
LCD_EXT void LcdInit(void);
LCD_EXT void ReverseDispNum2(uint8_t ox,uint8_t oy,uint8_t ascii_code);
LCD_EXT void DisplayCount(uint8_t ox, uint8_t oy, uint8_t byte1);
LCD_EXT void DisplayHex(uint8_t ox,uint8_t oy,uint16_t number);
LCD_EXT void ReverseDispOne2(uint8_t ox,uint8_t oy,uint8_t ascii_code);
void Fault_Disp(void);
void KeyRun_Disp(uint32_t Flag);
void IP_Sprintf(uint8_t *buf, uint32_t IPdata);
void IP_Printf(uint8_t x, uint32_t y, uint32_t IPdata, FlagStatus SetFlag, uint32_t cursor);
void HZ12AndChar_Printf(uint8_t x, uint8_t y, const uint8_t *ptr, FlagStatus Flag);
void HZ12AndChar_SignPrintf(uint8_t x, uint8_t y, const uint8_t *ptr, uint32_t SignNUM );
void DisplayNL_LOGO(void);
void DisplayNANRUI_LOGO(void);
void DisplayNANRUI_BMP(void);
void Display_BMP(uint32_t Width, uint32_t Height, const uint8_t* picture);
void DisplayQQ_BMP(void);
void ASCII_Printf(uint8_t x, uint32_t y, uint32_t data, FlagStatus Flag);
void ASCII_SignPrintf(uint8_t x, uint32_t y, uint32_t data, uint32_t SignNUM);
void IntValue_Printf(uint8_t x, uint32_t y, int32_t data, FlagStatus Flag);
void FloatValue_Printf(uint8_t x, uint32_t y, float data, FlagStatus Flag);
void FixLenIToF_Printf(uint32_t x, uint32_t y, int32_t data, uint32_t len, uint32_t dot, FlagStatus Flag, uint32_t cursor);
void FixLenIToF_Sprintf(uint8_t* str, int32_t data, uint32_t len, uint32_t dot);
void DisplayHorizontalLine(uint8_t x,uint8_t y,uint8_t len);
void DisplayVerticalLine(uint8_t x,uint8_t y,uint8_t high, uint8_t value);
void ClearMenuScreen(void);
void ScreenPrintf(uint8_t* ptr);
void Char6_Write(uint8_t x,uint8_t y, uint8_t CharCode, FlagStatus Flag);
void MessageBox(void);
void ClrMessageBox(void);
void ClrMessageBoxContent(void);
void MeunItem_Printf(uint8_t x, uint32_t y, int32_t data, FlagStatus Flag);
void MessageSetBox(const uint8_t *title);
void MessageSetBox_NoClear(const uint8_t *title);
void ClrMessageSetBox(void);
void DeleteMessageSetBox(void);
void ProgramableMessageBox(uint8_t x, uint8_t y, uint8_t size);
void DrawMenuComboBox(uint8_t x, uint8_t y, uint8_t len, uint8_t items);
void DrawRecordBox(uint8_t x, uint8_t y, uint8_t len, uint8_t items);
void BackLight_Close(void); void BackLight_Close(void);
void BackLight_ON(void); void BackLight_ON(void);
/* ---------- 屏幕控制函数 ---------- */
void ClearScreen(void);
void ReverseScreen(void);
void ClearMenuScreen(void);
void DisplayGraphicsScreen(void);
/* ---------- 字符显示函数 ---------- */
void Char6_Write(uint8_t x, uint8_t y, uint8_t CharCode, FlagStatus Flag);
void Char8_Write(uint8_t x, uint8_t y, uint8_t CharCode, FlagStatus Flag);
void Char12_Write(uint8_t x, uint8_t y, uint8_t CharCode, FlagStatus Flag);
void HZ12_Write(uint8_t x, uint8_t y, const uint8_t *HzCode, FlagStatus Flag);
void HZ12AndChar_Printf(uint8_t x, uint8_t y, const uint8_t *ptr, FlagStatus Flag);
void HZ12AndChar_SignPrintf(uint8_t x, uint8_t y, const uint8_t *ptr, uint32_t SignNUM);
void ASCII_Printf(uint8_t x, uint32_t y, uint32_t data, FlagStatus Flag);
void ASCII_SignPrintf(uint8_t x, uint32_t y, uint32_t data, uint32_t SignNUM);
void ScreenPrintf(uint8_t* ptr);
/* ---------- 数值显示函数 ---------- */
void IntValue_Printf(uint8_t x, uint32_t y, int32_t data, FlagStatus Flag);
void FloatValue_Printf(uint8_t x, uint32_t y, float data, FlagStatus Flag);
void FixLenIntValue_Printf(uint32_t x, uint32_t y, int32_t data, uint32_t len, FlagStatus Flag);
void FixLenIToF_Sprintf(uint8_t* str, int32_t data, uint32_t len, uint32_t dot);
void FixLenIToF_Printf(uint32_t x, uint32_t y, int32_t data, uint32_t len, uint32_t dot, FlagStatus Flag, uint32_t cursor);
void FixLenFloatValue_Printf(uint32_t x, uint32_t y, float data, uint32_t len, uint32_t dot, FlagStatus Flag);
void MeunItem_Printf(uint8_t x, uint32_t y, int32_t data, FlagStatus Flag);
/* ---------- IP 地址显示函数 ---------- */
void IP_Sprintf(uint8_t *buf, uint32_t IPdata);
void IP_Printf(uint8_t x, uint32_t y, uint32_t IPdata, FlagStatus SetFlag, uint32_t cursor);
/* ---------- 图形显示函数 ---------- */
void DisplayOneGraphics(uint8_t column, uint8_t lin, uint16_t hzcode, uint8_t tb_index);
void DisplayNL_LOGO(void);
void DisplayNANRUI_LOGO(void);
void Display_BMP(uint32_t Width, uint32_t Height, const uint8_t* picture);
void DisplayNANRUI_BMP(void);
void DisplayQQ_BMP(void);
void DiaplayHeadGraph(void);
void DrawPoint(uint8_t x, uint8_t y);
/* ---------- 线条绘制函数 ---------- */
void DisplayHorizontalLine(uint8_t x, uint8_t y, uint8_t len);
void DisplayVerticalLine(uint8_t x, uint8_t y, uint8_t high, uint8_t value);
/* ---------- 状态显示函数 ---------- */
void Fault_Disp(void);
void KeyRun_Disp(uint32_t Flag);
/* ============================================================================
* ASCII 字体数据
* ============================================================================ */
/**
* @brief 6x12 点阵 ASCII 字体数据
* @note 包含 95 个 ASCII 字符(空格到波浪号),每个字符 6 列 x 12 行 = 72 位 = 9 字节
* 字符索引0=' ', 1='!', ..., 94='~'
*/
static const uint8_t ASCII6x12[] = static const uint8_t ASCII6x12[] =
{ {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*" ",0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*" ",0*/
@@ -305,6 +304,11 @@ static const uint8_t ASCII6x12[] =
0x40,0xA4,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"~",94*/ 0x40,0xA4,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"~",94*/
}; };
/**
* @brief 8x12 点阵 ASCII 字体数据
* @note 包含 95 个 ASCII 字符(空格到波浪号),每个字符 8 列 x 12 行 = 96 位 = 12 字节
* 字符索引0=' ', 1='!', ..., 94='~'
*/
static const uint8_t ASCII8x12[] = { static const uint8_t ASCII8x12[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*" ",0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*" ",0*/
@@ -497,6 +501,11 @@ static const uint8_t ASCII8x12[] = {
0x30,0x4C,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"~",94*/ 0x30,0x4C,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"~",94*/
}; };
/**
* @brief 12x12 点阵 ASCII 字体数据
* @note 包含 95 个 ASCII 字符(空格到波浪号),每个字符 12 列 x 12 行 = 144 位 = 18 字节
* 字符索引0=' ', 1='!', ..., 94='~'
*/
static const uint8_t ASCII12x12[] = static const uint8_t ASCII12x12[] =
{ {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +0,0 @@
/****************************************************************************************
* 文件名FONT5_7.H
* 功能5*7 ASCII码字体显示函数。(头文件)
* 作者:黄绍斌
* 日期2004.02.26
* 备注使用GUI_SetColor()函数设置前景颜色及背景色。
****************************************************************************************/
#ifndef FONT5_7_H
#define FONT5_7_H
/****************************************************************************
* 名称GUI_PutChar()
* 功能显示ASCII码显示值为20H-7FH(若为其它值,则显示' ')。
* 入口参数: x 指定显示位置x坐标
* y 指定显示位置y坐标
* ch 要显示的ASCII码值。
* 出口参数返回值为1时表示操作成功为0时表示操作失败。
* 说明:操作失败原因是指定地址超出有效范围。
****************************************************************************/
extern unsigned char GUI_PutChar(unsigned int x, unsigned int y, unsigned char ch);
/****************************************************************************
* 名称GUI_PutString()
* 功能:输出显示字符串(没有自动换行功能)。
* 入口参数: x 指定显示位置x坐标
* y 指定显示位置y坐标
* str 要显示的ASCII码字符串
* 出口参数:无
* 说明:操作失败原因是指定地址超出有效范围。
****************************************************************************/
extern void GUI_PutString(unsigned int x, unsigned int y, char *str);
/****************************************************************************
* 名称GUI_PutNoStr()
* 功能:输出显示字符串(没有自动换行功能),若显示的字符个数大于指定个数,则直接退出。
* 入口参数: x 指定显示位置x坐标
* y 指定显示位置y坐标
* str 要显示的ASCII码字符串。
* no 最大显示字符的个数
* 出口参数:无
* 说明:操作失败原因是指定地址超出有效范围。
****************************************************************************/
extern void GUI_PutNoStr(unsigned int x, unsigned int y, char *str, unsigned char no);
/****************************************************************************
* 名称GUI_PutHex()
* 功能显示HEX码显示值为00H-FFH(若为其它值,则显示' ')。
* 入口参数: x 指定显示位置x坐标
* y 指定显示位置y坐标
* v 要显示的HEX。
*出口参数:无
****************************************************************************/
extern void GUI_PutHex(unsigned char x, unsigned char y,unsigned char v);
#endif

View File

@@ -1,63 +1,82 @@
/** /******************************************************************************
****************************************************************************** * @file key.c
* @文件 key.c * @brief 按键驱动模块 - 基于 MultiButton 库的高可移植性实现
* @作者 阜阳师范大学物电学院 * @details 本文件实现了基于 MultiButton 库的按键驱动功能,支持多按键配置、
* @版本 V0.1 * 按键状态检测、按键事件回调等功能。采用配置表方式管理按键,
* @日期 2026-01-15 * 便于扩展和维护。支持单次点击事件检测。
* @简介 按键驱动 - 基于 MultiButton 的高可移植性实现 * @author 阜阳师范大学物电学院
* @说明 * @version V0.1
* * @date 2026.1.19
**** * @note 依赖库: MultiButton
*/ * 支持按键: ENTER, UP, DOWN, LEFT, RIGHT, ESC, ADD, DEC, RESET
* 按键模式: 输入模式,无上拉/下拉
* 特殊处理: PB3 需要禁用 JTAG 才能作为普通 GPIO 使用
******************************************************************************/
#include "key.h" #include "key.h"
#include "160160D.h" #include "160160D.h"
// 按键配置结构体 /* ============================================================================
* 按键配置结构体定义
* ============================================================================ */
/**
* @brief 按键配置结构体
* @details 用于存储每个按键的硬件配置信息,包括 GPIO 端口、引脚和按键类型
*/
typedef struct { typedef struct {
uint16_t pin; // GPIO引脚 uint16_t pin; /**< GPIO 引脚编号(如 GPIO_PIN_12 */
GPIO_TypeDef* port; // GPIO端口 GPIO_TypeDef* port; /**< GPIO 端口(如 GPIOD */
KEY_TYPE key_type; // 按键ID KEY_TYPE key_type; /**< 按键类型标识(如 KEY_ENTER */
} KeyConfig_t; } KeyConfig_t;
/* /* ============================================================================
*******************详细引脚映射***************************** * 按键硬件引脚映射表
| 按键名称 | 端口 | 引脚 | 位定义 | 功能说明 | * ============================================================================ */
|---------|------|------|--------|----------| /**
| KEY_ENTER | GPIOB | PB15 | KEY_ENTER_BIT | 确认键 | * @brief 按键硬件引脚映射表
| KEY_UP | GPIOD | PD11 | KEY_UP_BIT | 上键 | * @details 详细引脚映射关系如下:
| KEY_DOWN | GPIOD | PD10 | KET_DOWN_BIT | 下键 | * | 按键名称 | 端口 | 引脚 | 按键类型 | 功能说明 |
| KEY_LEFT | GPIOD | PD13 | KET_LEFT_BIT | 左键 | * |---------|------|------|----------|----------|
| KEY_RIGHT | GPIOD | PD8 | KET_RIGHT_BIT | 键 | * | KEY_ENTER | GPIOD | PD12 | KEY_ENTER | 确认键 |
| KEY_ESC | GPIOD | PD12 | KET_ESC_BIT | 取消键 | * | KEY_UP | GPIOD | PD11 | KEY_UP | 键 |
| KEY_ADD | GPIOD | PD14 | KET_ADD_BIT | 键 | * | KEY_DOWN | GPIOD | PD10 | KEY_DOWN | 键 |
| KEY_DEC | GPIOD | PD9 | KET_DEC_BIT | 键 | * | KEY_LEFT | GPIOD | PD13 | KEY_LEFT | 键 |
| KEY_RESET | GPIOD | PD15 | KET_RESET_BIT | 复位键 | * | KEY_RIGHT | GPIOD | PD8 | KEY_RIGHT | 键 |
*************************************************************** * | KEY_ESC | GPIOB | PB15 | KEY_ESC | 取消键 |
* | KEY_ADD | GPIOB | PB3 | KEY_ADD | 加键(需禁用 JTAG |
* | KEY_RESET | GPIOD | PD15 | KEY_RESET | 复位键 |
* @note PB3 引脚默认被 JTAG 占用,需要禁用 JTAG 才能作为普通 GPIO 使用
*/ */
// 按键配置表
static const KeyConfig_t key_configs[] = { static const KeyConfig_t key_configs[] = {
{GPIO_PIN_12, GPIOD, KEY_ENTER}, {GPIO_PIN_12, GPIOD, KEY_ENTER}, /**< 确认键PD12 */
{GPIO_PIN_11, GPIOD, KEY_UP}, {GPIO_PIN_11, GPIOD, KEY_UP}, /**< 上键PD11 */
{GPIO_PIN_10, GPIOD, KEY_DOWN}, {GPIO_PIN_10, GPIOD, KEY_DOWN}, /**< 下键PD10 */
{GPIO_PIN_13, GPIOD, KEY_LEFT}, {GPIO_PIN_13, GPIOD, KEY_LEFT}, /**< 左键PD13 */
{GPIO_PIN_8, GPIOD, KEY_RIGHT}, {GPIO_PIN_8, GPIOD, KEY_RIGHT}, /**< 右键PD8 */
{GPIO_PIN_15, GPIOB, KEY_ESC}, {GPIO_PIN_15, GPIOB, KEY_ESC}, /**< 取消键PB15 */
{GPIO_PIN_3, GPIOB, KEY_ADD}, {GPIO_PIN_3, GPIOB, KEY_ADD}, /**< 加键PB3需禁用 JTAG */
{GPIO_PIN_15, GPIOD, KEY_RESET}, {GPIO_PIN_15, GPIOD, KEY_RESET}, /**< 复位键PD15 */
}; };
#define KEY_COUNT (sizeof(key_configs) / sizeof(key_configs[0]))
// 按键句柄数组 #define KEY_COUNT (sizeof(key_configs) / sizeof(key_configs[0])) /**< 按键总数 */
static Button btn_handles[KEY_COUNT];
// 业务逻辑回调函数指针由main.c注册
static KeyCallback key_callback = NULL;
/* ============================================================================
* 全局变量定义
* ============================================================================ */
static Button btn_handles[KEY_COUNT]; /**< MultiButton 按键句柄数组 */
static KeyCallback key_callback = NULL; /**< 业务逻辑层注册的回调函数指针 */
/* ============================================================================
* 内部函数实现
* ============================================================================ */
/** /**
* @brief 读取按键GPIO电平 * @brief 读取按键 GPIO 电平状态
* @param button_id 按键ID * @param button_id 按键 ID(对应 key_configs 数组索引0 到 KEY_COUNT-1
* @retval 0:按下, 1:释放 * @note 该函数由 MultiButton 库调用,用于读取按键的硬件状态
* 返回值0 表示按下低电平1 表示释放(高电平)
* @retval 0: 按键按下(低电平)
* @retval 1: 按键释放(高电平)
*/ */
static uint8_t button_read_level(uint8_t button_id) static uint8_t button_read_level(uint8_t button_id)
{ {
@@ -66,82 +85,117 @@ static uint8_t button_read_level(uint8_t button_id)
} }
/** /**
* @brief 统一的按键回调函数(内部使用,调用业务逻辑回调 * @brief 统一的按键事件回调函数(内部使用)
* @param btn 按键句柄指针 * @param btn MultiButton 库的按键句柄指针
* @note 当 MultiButton 库检测到按键事件时,会调用此函数
* 此函数将按键事件转换为业务逻辑层的按键类型,并调用注册的回调函数
* 处理流程:
* 1. 检查按键句柄和回调函数是否有效
* 2. 从按键句柄中获取 button_id
* 3. 通过 button_id 查找对应的按键类型
* 4. 调用业务逻辑层注册的回调函数
* @retval 无
*/ */
static void button_callback(Button* btn) static void button_callback(Button* btn)
{ {
if (btn != NULL && key_callback != NULL) if (btn != NULL && key_callback != NULL)
{ {
// 通过button_id获取对应的按键类型 /* 通过 button_id 获取对应的按键类型 */
uint8_t button_id = btn->button_id; uint8_t button_id = btn->button_id;
if (button_id < KEY_COUNT) if (button_id < KEY_COUNT)
{ {
// 调用业务逻辑层注册的回调函数 /* 调用业务逻辑层注册的回调函数 */
key_callback(key_configs[button_id].key_type); key_callback(key_configs[button_id].key_type);
} }
} }
} }
/** /**
* @brief 注册按键回调函数(供业务逻辑层调用) * @brief 使能指定 GPIO 端口的时钟
* @param callback 回调函数指针 * @param port GPIO 端口指针(如 GPIOA, GPIOB, GPIOD 等)
*/ * @note 使用 switch-case 结构实现,代码简洁清晰
void Key_RegisterCallback(KeyCallback callback) * 支持所有 GPIO 端口A-G
{ * 在初始化 GPIO 之前必须使能对应的时钟
key_callback = callback;
}
/**
* @brief 使能GPIO时钟
* @param port: GPIO端口
* @retval 无 * @retval 无
* @note 使用switch-case结构代码更简洁清晰支持所有GPIO端口A-G
*/ */
static void KEY_GPIO_ClockEnable(GPIO_TypeDef *port) static void KEY_GPIO_ClockEnable(GPIO_TypeDef *port)
{ {
switch ((uint32_t)port) { switch ((uint32_t)port) {
case (uint32_t)GPIOA: __HAL_RCC_GPIOA_CLK_ENABLE(); break; case (uint32_t)GPIOA: __HAL_RCC_GPIOA_CLK_ENABLE(); break; /**< 使能 GPIOA 时钟 */
case (uint32_t)GPIOB: __HAL_RCC_GPIOB_CLK_ENABLE(); break; case (uint32_t)GPIOB: __HAL_RCC_GPIOB_CLK_ENABLE(); break; /**< 使能 GPIOB 时钟 */
case (uint32_t)GPIOC: __HAL_RCC_GPIOC_CLK_ENABLE(); break; case (uint32_t)GPIOC: __HAL_RCC_GPIOC_CLK_ENABLE(); break; /**< 使能 GPIOC 时钟 */
case (uint32_t)GPIOD: __HAL_RCC_GPIOD_CLK_ENABLE(); break; case (uint32_t)GPIOD: __HAL_RCC_GPIOD_CLK_ENABLE(); break; /**< 使能 GPIOD 时钟 */
case (uint32_t)GPIOE: __HAL_RCC_GPIOE_CLK_ENABLE(); break; case (uint32_t)GPIOE: __HAL_RCC_GPIOE_CLK_ENABLE(); break; /**< 使能 GPIOE 时钟 */
case (uint32_t)GPIOF: __HAL_RCC_GPIOF_CLK_ENABLE(); break; case (uint32_t)GPIOF: __HAL_RCC_GPIOF_CLK_ENABLE(); break; /**< 使能 GPIOF 时钟 */
case (uint32_t)GPIOG: __HAL_RCC_GPIOG_CLK_ENABLE(); break; case (uint32_t)GPIOG: __HAL_RCC_GPIOG_CLK_ENABLE(); break; /**< 使能 GPIOG 时钟 */
default: break; default: break;
} }
} }
/**************************************************************************************
* FunctionName : Key_Init() /* ============================================================================
* Description : 按键硬件初始化使用MultiButton库 * 外部接口函数实现
* EntryParameter : none * ============================================================================ */
* ReturnValue : none
**************************************************************************************/ /**
* @brief 按键硬件初始化函数
* @note 初始化流程:
* 1. 使能 AFIO 时钟(修改 AFIO 寄存器前必须)
* 2. 禁用 JTAG保留 SWD释放 PA15、PB3、PB4 作为普通 GPIO
* 3. 遍历按键配置表,对每个按键进行初始化:
* a. 使能对应 GPIO 端口时钟
* b. 配置 GPIO 为输入模式,无上拉/下拉,高速模式
* c. 初始化 MultiButton 按键句柄
* d. 绑定单次点击事件到回调函数
* e. 启动按键检测
* @note 特殊处理:由于使用了 PB3 引脚,需要禁用 JTAG 功能
* JTAG 禁用后PA13、PA14 仍可用于 SWD 调试
* @retval 无
*/
void Key_Init(void) void Key_Init(void)
{ {
/*由于使用了 PB3 才需要特殊加的*/ /* ========== AFIO 和 JTAG 配置 ========== */
__HAL_RCC_AFIO_CLK_ENABLE(); // AFIO时钟修改AFIO寄存器前必须 __HAL_RCC_AFIO_CLK_ENABLE(); /**< 使能 AFIO 时钟(修改 AFIO 寄存器前必须) */
/* 禁用 JTAG保留 SWDPA13、PA14 仍可用于调试) */ /* 禁用 JTAG保留 SWDPA13、PA14 仍可用于调试) */
/* 这会释放 PA15、PB3、PB4 作为普通 GPIO */ /* 这会释放 PA15、PB3、PB4 作为普通 GPIO */
__HAL_AFIO_REMAP_SWJ_NOJTAG(); __HAL_AFIO_REMAP_SWJ_NOJTAG();
/* ========== GPIO 和按键初始化 ========== */
GPIO_InitTypeDef gpio_init_struct = {0}; GPIO_InitTypeDef gpio_init_struct = {0};
// 批量初始化GPIO
/* 批量初始化所有按键 */
for (uint8_t button_id = 0; button_id < KEY_COUNT; button_id++) for (uint8_t button_id = 0; button_id < KEY_COUNT; button_id++)
{ {
/* 使能对应GPIO时钟 */ /* 使能对应 GPIO 端口时钟 */
KEY_GPIO_ClockEnable(key_configs[button_id].port); KEY_GPIO_ClockEnable(key_configs[button_id].port);
// 配置并初始化引脚 /* 配置 GPIO 为输入模式 */
gpio_init_struct.Pin = key_configs[button_id].pin; gpio_init_struct.Pin = key_configs[button_id].pin;
gpio_init_struct.Mode = GPIO_MODE_INPUT; gpio_init_struct.Mode = GPIO_MODE_INPUT; /**< 输入模式 */
gpio_init_struct.Pull = GPIO_NOPULL; gpio_init_struct.Pull = GPIO_NOPULL; /**< 无上拉/下拉(外部电路处理) */
gpio_init_struct.Speed = GPIO_SPEED_HIGH; gpio_init_struct.Speed = GPIO_SPEED_HIGH; /**< 高速模式,提高响应速度 */
HAL_GPIO_Init(key_configs[button_id].port, &gpio_init_struct); HAL_GPIO_Init(key_configs[button_id].port, &gpio_init_struct);
/* 初始化 MultiButton 按键句柄 */
button_init(&btn_handles[button_id], button_read_level, 0, button_id); button_init(&btn_handles[button_id], button_read_level, 0, button_id);
/* 绑定单次点击事件到回调函数 */
button_attach(&btn_handles[button_id], BTN_SINGLE_CLICK, button_callback); button_attach(&btn_handles[button_id], BTN_SINGLE_CLICK, button_callback);
/* 启动按键检测 */
button_start(&btn_handles[button_id]); button_start(&btn_handles[button_id]);
} }
} }
/**
* @brief 注册按键回调函数(供业务逻辑层调用)
* @param callback 按键回调函数指针,当检测到按键事件时会调用此函数
* @note 业务逻辑层通过此函数注册按键处理回调
* 回调函数参数为 KEY_TYPE 类型,表示被按下的按键类型
* 如果传入 NULL则取消已注册的回调函数
* @retval 无
*/
void Key_RegisterCallback(KeyCallback callback)
{
key_callback = callback;
}

View File

@@ -1,35 +1,86 @@
/******************************************************************************
* @file key.h
* @brief 按键驱动模块头文件
* @details 本文件定义了按键驱动的接口和数据结构,包括按键类型枚举、
* 按键回调函数类型定义以及外部接口函数声明。
* @author 阜阳师范大学物电学院
* @version V0.1
* @date 2026.1.19
* @note 依赖库: MultiButton
* 使用方式:
* 1. 调用 Key_Init() 初始化按键硬件
* 2. 调用 Key_RegisterCallback() 注册按键回调函数
* 3. 在定时器中调用 Button_Ticks() 进行按键扫描
******************************************************************************/
#ifndef __KEY_H__ #ifndef __KEY_H__
#define __KEY_H__ #define __KEY_H__
#include "./SYSTEM/sys/sys.h" #include "./SYSTEM/sys/sys.h"
#include "MultiButton.h" #include "MultiButton.h"
/* ============================================================================
* 按键类型枚举定义
* ============================================================================ */
/**
* @brief 按键类型枚举
* @details 定义了系统中所有支持的按键类型
*/
typedef enum typedef enum
{ {
KEY_NONE = 0, // 没有按键 KEY_NONE = 0, /**< 无按键按下 */
KEY_ENTER, /**< 确认键 */
KEY_ENTER, // 确认 KEY_UP, /**< 向上键 */
KEY_UP, // 向上 KEY_DOWN, /**< 向下键 */
KEY_DOWN, // 向下 KEY_ESC, /**< 取消键 */
KEY_ESC, //取消 KEY_ADD, /**< 加键 */
KEY_ADD, //加 KEY_DEC, /**< 减键 */
KEY_DEC, //减 KEY_LEFT, /**< 向左键 */
KEY_LEFT, //左 KEY_RIGHT, /**< 向右键 */
KEY_RIGHT, //右 KEY_RESET, /**< 复位键 */
KEY_RESET, //复位 KEY_FACTORY, /**< 工厂模式键 */
KEY_FACTORY, //工厂
} KEY_TYPE; } KEY_TYPE;
// 按键回调函数类型定义(业务逻辑层使用) /* ============================================================================
// 参数: key_type - 按键类型 * 回调函数类型定义
* ============================================================================ */
/**
* @brief 按键回调函数类型定义
* @param key_type 按键类型KEY_TYPE 枚举值)
* @note 业务逻辑层需要实现此类型的回调函数,用于处理按键事件
* 当检测到按键按下时,会调用注册的回调函数
* @retval 无
*/
typedef void (*KeyCallback)(KEY_TYPE key_type); typedef void (*KeyCallback)(KEY_TYPE key_type);
/* ============================================================================
* 外部接口函数声明
* ============================================================================ */
/**
* @brief 按键硬件初始化函数
* @note 初始化所有按键的 GPIO 配置和 MultiButton 库
* 特殊处理:禁用 JTAG 以释放 PB3 等引脚
* 必须在系统初始化时调用一次
* @retval 无
*/
void Key_Init(void);
// 按键驱动函数在key.c中实现使用MultiButton库 /**
void Key_Init(void); // 按键初始化 * @brief 注册按键回调函数(供业务逻辑层调用)
void Key_RegisterCallback(KeyCallback callback); // 注册按键回调函数(业务逻辑层调用) * @param callback 按键回调函数指针,当检测到按键事件时会调用此函数
* @note 业务逻辑层通过此函数注册按键处理回调
* 如果传入 NULL则取消已注册的回调函数
* @retval 无
*/
void Key_RegisterCallback(KeyCallback callback);
/**
* @brief MultiButton 库的按键扫描函数宏定义
* @note 需要在定时器中周期性调用此函数(建议 5-10ms 调用一次)
* 用于 MultiButton 库进行按键状态扫描和事件检测
* @retval 无
*/
#define Button_Ticks() button_ticks() #define Button_Ticks() button_ticks()
#endif
#endif /* __KEY_H__ */

View File

@@ -1,127 +1,134 @@
/****************************************************************************** /******************************************************************************
* @file rs485.c * @file rs485.c
* @brief rs485串口配置,主要配置两种模式 中断接收模式 和 DMA 接收模式 * @brief RS485 串口驱动 - DMA 与中断接收
* @details * @details 本文件实现 RS485 串口通信驱动,支持两种模式:
* 1. DMA模式是主要工作模式 * 1. DMA 模式:主工作模式,使用 ReceiveToIdle 接收不定长帧
* 2. 中断模式用于错误恢复和接收第一个短帧 * 2. 中断模式用于错误恢复或短帧接收RXNE 逐字节接收
* 3. 中断处理完后自动切换回DMA模式 * 收发通过 DE 引脚PA1切换低电平发送高电平接收。
* 4. 中断接收主要用于异常情况处理
* @author 阜阳师范大学物电学院 * @author 阜阳师范大学物电学院
* @version V0.01 * @version V0.01
* @date 2026.1.24 * @date 2026.1.24
* @note 通信协议Modbus RTU * @note USART2TX=PA2RX=PA3DE=PA1波特率 700000
* 通信接口RS485
* 主站地址0x01
******************************************************************************/ ******************************************************************************/
#include "rs485.h" #include "rs485.h"
/* ============================================================================
* USART 与引脚宏定义
* ============================================================================ */
#define RS485_UX USART2 #define RS485_UX USART2
#define RS485_UX_IRQn USART2_IRQn #define RS485_UX_IRQn USART2_IRQn
#define RS485_UX_IRQHandler USART2_IRQHandler #define RS485_UX_IRQHandler USART2_IRQHandler
#define RS485_UX_CLK_ENABLE() do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0) /* 时钟使能 */ #define RS485_UX_CLK_ENABLE() do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0) /**< USART2 时钟使能 */
#define RS485_SEND_ENABLE() do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);}while(0) #define RS485_SEND_ENABLE() do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);}while(0) /**< DE 低:发送 */
#define RS485_RECEIVE_ENABLE() do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);}while(0) #define RS485_RECEIVE_ENABLE() do{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);}while(0) /**< DE 高:接收 */
#define RS485_ENABLE_GPIO_PORT GPIOA #define RS485_ENABLE_GPIO_PORT GPIOA
#define RS485_ENABLE_GPIO_PIN GPIO_PIN_1 #define RS485_ENABLE_GPIO_PIN GPIO_PIN_1
#define RS485_ENABLE_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */ #define RS485_ENABLE_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define RS485_TX_GPIO_PORT GPIOA #define RS485_TX_GPIO_PORT GPIOA
#define RS485_TX_GPIO_PIN GPIO_PIN_2 #define RS485_TX_GPIO_PIN GPIO_PIN_2
#define RS485_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */ #define RS485_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define RS485_RX_GPIO_PORT GPIOA #define RS485_RX_GPIO_PORT GPIOA
#define RS485_RX_GPIO_PIN GPIO_PIN_3 #define RS485_RX_GPIO_PIN GPIO_PIN_3
#define RS485_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */ #define RS485_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
RS485_REGISTER_TYPE RS485REG = {RESET, {0}, {0}}; /* 零初始化定义 */ #define BAUDRATE (700000) /**< 波特率 */
// HAL库UART句柄 /* ============================================================================
UART_HandleTypeDef rs485_handle; /* UART句柄 */ * 全局变量定义
// HAL库DMA句柄接收 * ============================================================================ */
DMA_HandleTypeDef hdma_rs485_rx; RS485_REGISTER_TYPE RS485REG = {RESET, {0}, {0}}; /**< RS485 收发寄存器,零初始化 */
// HAL库DMA句柄发送
DMA_HandleTypeDef hdma_rs485_tx;
UART_HandleTypeDef rs485_handle; /**< HAL UART 句柄 */
DMA_HandleTypeDef hdma_rs485_rx; /**< HAL DMA 接收句柄DMA1 Channel6 */
DMA_HandleTypeDef hdma_rs485_tx; /**< HAL DMA 发送句柄(未使用,保留) */
#define BAUDRATE (700000) /* ============================================================================
* 初始化函数
* ============================================================================ */
/** /**
* @brief 串口X初始化函数 * @brief RS485 初始化DMA 接收模式)
* @param baudrate: 波特率, 根据自己需要设置波特率值 * @note 配置流程:
* @note 注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常. * 1. 使能 GPIO、USART 时钟
* 这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了. * 2. 配置 TX(PA2)、RX(PA3)、DE(PA1)
* 3. 配置 DMA1 Channel6 接收,关联 UART
* 4. 使能 DMA、USART 中断
* 5. 初始化 UART8N1无流控波特率 BAUDRATE
* 6. 切换为接收,启动 ReceiveToIdle DMA
* USART 时钟源需在 sys_stm32_clock_init 中已配置。
* @retval 无 * @retval 无
*/ */
void RS485_DMA_init() void RS485_DMA_init(void)
{ {
/* 开启时钟 */ /* 使能时钟 */
RS485_TX_GPIO_CLK_ENABLE(); /* 使能串口TX脚时钟 */ RS485_TX_GPIO_CLK_ENABLE();
RS485_RX_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */ RS485_RX_GPIO_CLK_ENABLE();
RS485_ENABLE_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */ RS485_ENABLE_GPIO_CLK_ENABLE();
RS485_UX_CLK_ENABLE(); /* 使能串口时钟 */ RS485_UX_CLK_ENABLE();
/*GPIO 初始化设置*/ /* GPIO 初始化 */
GPIO_InitTypeDef gpio_init_struct = {0}; GPIO_InitTypeDef gpio_init_struct = {0};
gpio_init_struct.Pin = RS485_TX_GPIO_PIN; /* 串口发送引脚号 */ gpio_init_struct.Pin = RS485_TX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */ gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct); HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_RX_GPIO_PIN; /* 串口RX脚 模式设置 */ gpio_init_struct.Pin = RS485_RX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */ HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN; gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(RS485_ENABLE_GPIO_PORT, &gpio_init_struct); HAL_GPIO_Init(RS485_ENABLE_GPIO_PORT, &gpio_init_struct);
/* 配置DMA接收句柄 */ /* DMA 接收配置 */
hdma_rs485_rx.Instance = DMA1_Channel6; hdma_rs485_rx.Instance = DMA1_Channel6;
hdma_rs485_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到内存 hdma_rs485_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_rs485_rx.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不递增 hdma_rs485_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_rs485_rx.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增 hdma_rs485_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_rs485_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据对齐:字节 hdma_rs485_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_rs485_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 内存数据对齐:字节 hdma_rs485_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_rs485_rx.Init.Mode = DMA_NORMAL; // 正常模式 hdma_rs485_rx.Init.Mode = DMA_NORMAL;
hdma_rs485_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH; // 最高优先级 hdma_rs485_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
HAL_DMA_Init(&hdma_rs485_rx); /*初始化DMA 接收*/ HAL_DMA_Init(&hdma_rs485_rx);
/* 关联 DMA 到 UART重要*/
__HAL_LINKDMA(&rs485_handle, hdmarx, hdma_rs485_rx); __HAL_LINKDMA(&rs485_handle, hdmarx, hdma_rs485_rx);
/* 使能DMA传输完成中断 */
HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn); HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0); HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 0, 0);
/* UART 初始化设置 */ /* UART 初始化 */
rs485_handle.Instance = RS485_UX; /* USART_UX */ rs485_handle.Instance = RS485_UX;
rs485_handle.Init.BaudRate = BAUDRATE; /* 波特率 */ rs485_handle.Init.BaudRate = BAUDRATE;
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */ rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;
rs485_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */ rs485_handle.Init.StopBits = UART_STOPBITS_1;
rs485_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */ rs485_handle.Init.Parity = UART_PARITY_NONE;
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */ rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
rs485_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */ rs485_handle.Init.Mode = UART_MODE_TX_RX;
rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16; rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&rs485_handle); /* 初始化UART */ HAL_UART_Init(&rs485_handle);
HAL_NVIC_EnableIRQ(RS485_UX_IRQn); /* 使能USART中断通道 */ HAL_NVIC_EnableIRQ(RS485_UX_IRQn);
HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0); /* 组2抢占优先级1子优先级0 */ HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);
/* 启动 DMA 接收 */ /* 启动 DMA 接收 */
RS485_RECEIVE_ENABLE(); RS485_RECEIVE_ENABLE();
HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN); HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
} }
/** /**
* @brief 串口X初始化函数 * @brief RS485 初始化(中断接收模式)
* @note 注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常. * @note 配置 GPIO、UART使能 RXNE 中断。不启用 DMA。
* * 用于错误恢复或接收短帧。USART 时钟源需已配置。
* @retval 无 * @retval 无
*/ */
void RS485_init() void RS485_init(void)
{ {
/* 开启时钟 */ /* 开启时钟 */
RS485_TX_GPIO_CLK_ENABLE(); /* 使能串口TX脚时钟 */ RS485_TX_GPIO_CLK_ENABLE(); /* 使能串口TX脚时钟 */
@@ -129,40 +136,50 @@ void RS485_init()
RS485_ENABLE_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */ RS485_ENABLE_GPIO_CLK_ENABLE(); /* 使能串口RX脚时钟 */
RS485_UX_CLK_ENABLE(); /* 使能串口时钟 */ RS485_UX_CLK_ENABLE(); /* 使能串口时钟 */
/*GPIO 初始化设置*/ /* GPIO 初始化 */
GPIO_InitTypeDef gpio_init_struct; GPIO_InitTypeDef gpio_init_struct = {0};
gpio_init_struct.Pin = RS485_TX_GPIO_PIN; /* 串口发送引脚号 */ gpio_init_struct.Pin = RS485_TX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */ gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct); HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_RX_GPIO_PIN; /* 串口RX脚 模式设置 */ gpio_init_struct.Pin = RS485_RX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */ HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN; gpio_init_struct.Pin = RS485_ENABLE_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(RS485_ENABLE_GPIO_PORT, &gpio_init_struct); HAL_GPIO_Init(RS485_ENABLE_GPIO_PORT, &gpio_init_struct);
/* UART 初始化 */
rs485_handle.Instance = RS485_UX;
rs485_handle.Init.BaudRate = BAUDRATE;
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B;
rs485_handle.Init.StopBits = UART_STOPBITS_1;
rs485_handle.Init.Parity = UART_PARITY_NONE;
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
rs485_handle.Init.Mode = UART_MODE_TX_RX;
rs485_handle.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&rs485_handle);
/*UART 初始化设置*/
rs485_handle.Instance = RS485_UX; /* USART_UX */
rs485_handle.Init.BaudRate = BAUDRATE; /* 波特率 */
rs485_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
rs485_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
rs485_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
rs485_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
rs485_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&rs485_handle); /* 使能UART */
/* 该函数会开启接收中断标志位UART_IT_RXNE并且设置接收缓冲以及接收缓冲接收最大数据量 */
__HAL_UART_ENABLE_IT(&rs485_handle, UART_IT_RXNE); __HAL_UART_ENABLE_IT(&rs485_handle, UART_IT_RXNE);
HAL_NVIC_EnableIRQ(RS485_UX_IRQn); /* 使能USART中断通道 */ HAL_NVIC_EnableIRQ(RS485_UX_IRQn);
HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0); /* 组2抢占优先级1子优先级0 */ HAL_NVIC_SetPriority(RS485_UX_IRQn, 1, 0);
RS485_RECEIVE_ENABLE(); RS485_RECEIVE_ENABLE();
} }
/* ============================================================================
* 回调与中断服务函数
* ============================================================================ */
/**
* @brief UART 接收事件回调ReceiveToIdle 完成时由 HAL 调用)
* @param huart UART 句柄
* @param Size 本帧接收到的字节数
* @note 当为 RS485 所用 UART 时:置位 NewMessageFlag并重新启动 ReceiveToIdle DMA。
* @retval 无
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{ {
if (huart->Instance == RS485_UX) if (huart->Instance == RS485_UX)
@@ -171,9 +188,10 @@ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN); HAL_UARTEx_ReceiveToIdle_DMA(&rs485_handle, (uint8_t *)RS485REG.DR, UART_RX_LEN);
} }
} }
/** /**
* @brief 485串口中断服务函数 * @brief RS485 所用 USART 中断服务函数
* @param 无 * @note 调用 HAL_UART_IRQHandler 处理 UART 及关联 DMA 中断。
* @retval 无 * @retval 无
*/ */
void RS485_UX_IRQHandler(void) void RS485_UX_IRQHandler(void)
@@ -181,28 +199,31 @@ void RS485_UX_IRQHandler(void)
HAL_UART_IRQHandler(&rs485_handle); HAL_UART_IRQHandler(&rs485_handle);
} }
/******************************************************************************* /**
* FunctionName : DMA1_Channel6_IRQHandler() * @brief DMA1 Channel6 中断服务函数RS485 接收 DMA
* Description : 串口接收中断 * @note 调用 HAL_DMA_IRQHandler在 ReceiveToIdle 完成等事件时触发。
* EntryParameter : none * @retval 无
* ReturnValue : none */
********************************************************************************/
void DMA1_Channel6_IRQHandler(void) void DMA1_Channel6_IRQHandler(void)
{ {
HAL_DMA_IRQHandler(&hdma_rs485_rx); HAL_DMA_IRQHandler(&hdma_rs485_rx);
} }
/***********************************************************************
* FunctionName : RS485_SendBuff() /* ============================================================================
* Description : 485发送数组 * 发送接口
* EntryParameter : *ptr:待发送的字符串, len:待发送的数据个数 * ============================================================================ */
* ReturnValue : ptrLen:发送的数据个数
**************************************************************************/ /**
* @brief RS485 发送数据
* @param ptr 待发送数据指针
* @param len 待发送字节数
* @note 切换为发送DE 低)→ 阻塞发送 → 切换回接收DE 高)。超时 1000ms。
* @retval 无
*/
void RS485_SendBuff(uint8_t *ptr, uint32_t len) void RS485_SendBuff(uint8_t *ptr, uint32_t len)
{ {
RS485_SEND_ENABLE(); RS485_SEND_ENABLE();
HAL_UART_Transmit(&rs485_handle, ptr, len, 1000); HAL_UART_Transmit(&rs485_handle, ptr, len, 1000);
RS485_RECEIVE_ENABLE(); RS485_RECEIVE_ENABLE();
} }

View File

@@ -1,29 +1,70 @@
/******************************************************************************
* @file rs485.h
* @brief RS485 串口通信驱动头文件
* @details 本文件声明 RS485 通信相关接口与数据结构,支持 DMA 接收与中断接收两种模式。
* 使用 USART2收发切换通过 DE 引脚PA1控制。
* @author 阜阳师范大学物电学院
* @version V0.01
* @date 2026.1.24
* @note USARTUSART2
* TXPA2RXPA3DEPA1
* 波特率700000
******************************************************************************/
#ifndef __RS485_H__ #ifndef __RS485_H__
#define __RS485_H__ #define __RS485_H__
#include "./SYSTEM/sys/sys.h" #include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h" #include "./SYSTEM/delay/delay.h"
#define UART_RX_LEN (3208) /* ============================================================================
#define UART_TX_LEN (8) * 宏定义
* ============================================================================ */
#define UART_RX_LEN (3208) /**< 接收缓冲区长度(字节),与 Modbus 数据帧长度一致 */
#define UART_TX_LEN (8) /**< 发送缓冲区长度(字节) */
/* ============================================================================
* 数据结构定义
* ============================================================================ */
/**
* @brief RS485 收发寄存器结构体
* @note 用于存放一帧接收数据及发送缓存,与 Modbus 处理模块配合使用
*/
typedef struct typedef struct
{ {
volatile FlagStatus NewMessageFlag; //一帧数据接收完整 volatile FlagStatus NewMessageFlag; /**< 新消息标志:一帧数据接收完成时置位 */
volatile uint8_t DR[UART_RX_LEN]; //接收缓存 volatile uint8_t DR[UART_RX_LEN]; /**< 接收缓存 */
volatile uint8_t TDR[UART_TX_LEN]; //发送缓存 volatile uint8_t TDR[UART_TX_LEN]; /**< 发送缓存 */
} RS485_REGISTER_TYPE; } RS485_REGISTER_TYPE;
extern RS485_REGISTER_TYPE RS485REG; extern RS485_REGISTER_TYPE RS485REG; /**< 全局 RS485 收发寄存器 */
/* ============================================================================
* 函数声明
* ============================================================================ */
/**
* @brief RS485 初始化DMA 接收模式)
void USART2_Init(void); * @note 配置 GPIO、UART、DMA启动 ReceiveToIdle DMA 接收。为主工作模式。
* @retval 无
*/
void RS485_DMA_init(void); void RS485_DMA_init(void);
/**
* @brief RS485 初始化(中断接收模式)
* @note 配置 GPIO、UART使能 RXNE 中断接收。用于异常恢复或短帧接收。
* @retval 无
*/
void RS485_init(void);
/**
* @brief RS485 发送数据
* @param ptr 待发送数据指针
* @param len 待发送字节数
* @note 先切换为发送DE 高发送完成后切回接收DE 低)。使用阻塞方式发送。
* @retval 无
*/
void RS485_SendBuff(uint8_t *ptr, uint32_t len); void RS485_SendBuff(uint8_t *ptr, uint32_t len);
#endif /* __RS485_H__ */
#endif

View File

@@ -28,48 +28,61 @@
#define KEY_CMD (0) /**< 按键事件命令类型 */ #define KEY_CMD (0) /**< 按键事件命令类型 */
/* ============================================================================ /* ============================================================================
* 图片宏定义 * 图片与 Flash 宏定义
* ============================================================================ */ * ============================================================================ */
#define BMP_ADDR (0x0803F000) /**< BMP数据在Flash中的存储地址扇区126 */ #define BMP_ADDR (0x0803F000) /**< BMPFlash 中的存储地址(扇区 126 */
#define BMP_LEN (3208) /**< BMP数据长度,单位:字节 */ #define BMP_LEN (3208) /**< BMP 数据长度字节 */
/* ============================================================================
* 轮询与超时常量
* ============================================================================ */
#define RS485_RT_COUNT_MAX (55) /**< 实时轮询计数上限,约 275ms55×5ms */
#define RS485_ACK_COUNT_MAX (40) /**< 应答超时计数上限,约 200ms40×5ms */
#define RS485_ACK_OVERTIME_MAX (5) /**< 连续应答超时次数上限,超过则重初始化 RS485 */
/* ============================================================================
* 数据结构定义
* ============================================================================ */
/**
* @brief LOGO 图片数据结构体
* @note 用于存储从 138 端下发的 BMP 数据及尺寸,或从 Flash 读取的 LOGO
*/
typedef struct typedef struct
{ {
uint8_t bmpdata[3200]; uint8_t bmpdata[3200]; /**< BMP 点阵数据,最大 3200 字节 */
uint32_t biWidth; uint32_t biWidth; /**< 图片宽度(像素) */
uint32_t biHeight; uint32_t biHeight; /**< 图片高度(像素) */
} logo_type; } logo_type;
/**
* @brief RS485 轮询与应答状态结构体
* @note 用于 Modbus 主站轮询、应答超时检测及命令应答标志管理
*/
typedef struct typedef struct
{ {
volatile uint8_t RT_count; /* 实时命令计数 */ volatile uint8_t RT_count; /**< 实时命令计数,用于定时发送刷新命令 */
volatile uint16_t CMD_ACK; /* 普通命令接收完成标志位 */ volatile uint16_t CMD_ACK; /**< 命令接收完成标志SET=可发下一帧) */
volatile uint8_t ACK_COUNT_ABLE; //命令发出后计时使能 volatile uint8_t ACK_COUNT_ABLE; /**< 应答计时使能(发令后置位) */
volatile uint8_t ACK_count; /* 应答计时计数器 */ volatile uint8_t ACK_count; /**< 应答等待计数,超 RS485_ACK_COUNT_MAX 即超时 */
volatile uint8_t ACK_OverTime; /* 超时标志 */ volatile uint8_t ACK_OverTime; /**< 本次应答超时标志 */
volatile uint8_t ACK_OverTimeCnt; /* 超时次数技术器 */ volatile uint8_t ACK_OverTimeCnt; /**< 连续应答超时次数,超 RS485_ACK_OVERTIME_MAX 则重初始 */
} RS485_POLL_TYPE; } RS485_POLL_TYPE;
RS485_POLL_TYPE RS485POLL = {0}; RS485_POLL_TYPE RS485POLL = {0}; /**< 全局轮询状态 */
#define RS485_RT_COUNT_MAX (55) //轮询限值 275ms
#define RS485_ACK_COUNT_MAX (40) //应答超时限值 200ms
#define RS485_ACK_OVERTIME_MAX (5) // 75ms 应答超时计数最大次数,超过该次数显示通讯异常
/* ============================================================================ /* ============================================================================
* 私有变量定义 * 私有变量定义
* ============================================================================ */ * ============================================================================ */
static logo_type logo; /**< LOGO图片数据结构体包含BMP图片数据和尺寸信息 */ static logo_type logo = {0}; /**< LOGO 图片数据BMP + 宽高) */
static uint8_t Picture[3200]; static uint8_t Picture[3200] = {0}; /**< 接收图片缓冲,前 3200 字节为点阵数据 */
/* ============================================================================ /* ============================================================================
* MODBUS协议处理部分 * Modbus 命令与屏幕刷新
* ============================================================================ */ * ============================================================================ */
/** /**
* @brief 发送屏幕刷新命令(特殊格式的 Modbus 命令) * @brief 发送屏幕刷新命令(特殊格式的 Modbus 命令)
* @param func 功能码通常为READ_RIGISTER = 0x03 * @param func 功能码(通常为读寄存器 READ_REGISTER = 0x03
* @param type 命令类型RT_CMD(1)=实时刷新KEY_CMD(0)=按键事件 * @param type 命令类型RT_CMD(1)=实时刷新KEY_CMD(0)=按键事件
* @param key 按键值当type=KEY_CMD时有效 * @param key 按键值当type=KEY_CMD时有效
* @note 命令帧格式6字节 * @note 命令帧格式6字节
@@ -100,19 +113,12 @@ void RS485_RefreshScreenCMD(uint8_t func, uint8_t type, uint8_t key)
} }
///* ============================================================================
// * 屏幕刷新处理 远程处理图片
// * ============================================================================ */
/** /**
* @brief 发送屏幕刷新命令 * @brief 发送屏幕刷新命令并重置轮询状态
* @param type 命令类型RT_CMD(1)=实时刷新KEY_CMD(0)=按键事件 * @param type 命令类型RT_CMD(1)=实时刷新KEY_CMD(0)=按键事件
* @param key 按键值(type=KEY_CMD时有效 * @param key 按键值type=KEY_CMD 时有效)
* @note 发送屏幕刷新命令到138端请求更新屏幕显示 * @note 向 138 端发送屏幕刷新命令;发送后重置 CMD_ACK、RT_count
* 命令发送后: * 使能 ACK_COUNT_ABLE 并清零 ACK_count。
* - 重置命令应答标志
* - 重置实时数据计数
* - 使能应答计数
* - 重置应答计数值
* @retval 无 * @retval 无
*/ */
void RefreshScreen(uint8_t type, uint8_t key) void RefreshScreen(uint8_t type, uint8_t key)
@@ -125,6 +131,11 @@ void RefreshScreen(uint8_t type, uint8_t key)
RS485POLL.ACK_COUNT_ABLE = SET; /* 使能应答计数 */ RS485POLL.ACK_COUNT_ABLE = SET; /* 使能应答计数 */
RS485POLL.ACK_count = 0; /* 重置应答计数值 */ RS485POLL.ACK_count = 0; /* 重置应答计数值 */
} }
/* ============================================================================
* LOGO 与 BMP 处理
* ============================================================================ */
/** /**
* @brief 在 LCD 屏幕上显示 LOGO 图片 * @brief 在 LCD 屏幕上显示 LOGO 图片
* @note 调用Display_BMP函数将全局变量logo中的BMP图片数据显示在LCD屏幕上 * @note 调用Display_BMP函数将全局变量logo中的BMP图片数据显示在LCD屏幕上
@@ -185,6 +196,12 @@ void BMP_Get(void)
logo.biHeight = ptr->biHeight; logo.biHeight = ptr->biHeight;
} }
/**
* @brief 从 Flash 读取 LOGO 并显示,延时约 2 秒
* @note 调用 BMP_Get 从 Flash 读取 LOGO再 LOGO_Printf 显示;
* 每次显示后 delay 4×500ms = 2s
* @retval 无
*/
void NL_LOGO_Printf(void) void NL_LOGO_Printf(void)
{ {
BMP_Get(); BMP_Get();
@@ -194,8 +211,13 @@ void NL_LOGO_Printf(void)
delay_ms(500); delay_ms(500);
delay_ms(500); delay_ms(500);
} }
/* ============================================================================
* Modbus 主处理与轮询
* ============================================================================ */
/** /**
* @brief Modbus通信主处理函数-远程处理图片 * @brief Modbus 通信主处理函数远程处理图片
* @param key 按键值KEY_TYPE类型 * @param key 按键值KEY_TYPE类型
* @note 采用138端处理图片整体传上来显示的方式刷图 * @note 采用138端处理图片整体传上来显示的方式刷图
* 处理流程: * 处理流程:
@@ -338,6 +360,12 @@ uint8_t RS485_Process(KEY_TYPE key, uint8_t flag)
return ConnectFlg; return ConnectFlg;
} }
/**
* @brief 轮询计数处理(需在 5ms 周期定时器中调用)
* @note RT_count 递增,用于定时发实时刷新命令;若 ACK_COUNT_ABLE 置位,
* 则 ACK_count 递增,用于应答超时判断。
* @retval 无
*/
void Process_Count(void) void Process_Count(void)
{ {
RS485POLL.RT_count++; RS485POLL.RT_count++;
@@ -346,9 +374,16 @@ void Process_Count(void)
RS485POLL.ACK_count++; RS485POLL.ACK_count++;
} }
} }
/**
* @brief Modbus 轮询与应答相关变量初始化
* @note 清零 ACK_OverTime、ACK_count、ACK_OverTimeCnt关闭 ACK_COUNT_ABLE
* 置位 CMD_ACK 表示可发送。上电或通信异常恢复后调用。
* @retval 无
*/
void Process_Init(void) void Process_Init(void)
{ {
/* 清除控制字和应答相关标志 */ /* 清除超时与应答相关标志 */
RS485POLL.ACK_OverTime = RESET; /* 重置应答超时标志 */ RS485POLL.ACK_OverTime = RESET; /* 重置应答超时标志 */
RS485POLL.ACK_COUNT_ABLE = RESET; /* 禁用应答计数 */ RS485POLL.ACK_COUNT_ABLE = RESET; /* 禁用应答计数 */
RS485POLL.ACK_count = 0; /* 重置应答计数 */ RS485POLL.ACK_count = 0; /* 重置应答计数 */

View File

@@ -1,16 +1,56 @@
/******************************************************************************
* @file MODBUS.h
* @brief Modbus RTU 协议通信处理模块头文件
* @details 本文件声明了 Modbus RTU 通信相关的接口函数包括主处理、LOGO 显示、
* 轮询计数与初始化等。配合 MODBUS.c 使用,通过 RS485 与从站通信。
* @author 阜阳师范大学物电学院
* @version V0.01
* @date 2026.1.23
* @note 通信协议Modbus RTU
* 通信接口RS485
* 主站地址0x01
******************************************************************************/
#ifndef MODBUS_H__ #ifndef MODBUS_H__
#define MODBUS_H__ #define MODBUS_H__
#include "./SYSTEM/sys/sys.h" #include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h" #include "./SYSTEM/delay/delay.h"
#include "key.h" #include "key.h"
/* ============================================================================
* 函数声明
* ============================================================================ */
/**
* @brief Modbus 通信主处理函数(远程图片处理)
* @param key 按键值KEY_TYPE用于上报按键事件
* @param flag 连接标志(传入当前连接状态,用于更新)
* @note 处理接收帧、按键上报、定时刷新、应答超时等;采用 138 端处理图片、
* 整体上传显示的方式刷图。需在周期任务中调用。
* @retval 更新后的连接标志1=已连接0=未连接/超时)
*/
uint8_t RS485_Process(KEY_TYPE key, uint8_t flag); uint8_t RS485_Process(KEY_TYPE key, uint8_t flag);
void NL_LOGO_Printf(void);
void Process_Count(void);
void Process_Init(void);
#endif // end of ADC_H definition
/**
* @brief 从 Flash 读取 LOGO 并显示
* @note 调用 BMP_Get 读 Flash再 LOGO_Printf 显示,合计延时约 2s
* @retval 无
*/
void NL_LOGO_Printf(void);
/**
* @brief 轮询计数处理(需在 5ms 周期定时器中调用)
* @note 递增 RT_count若 ACK_COUNT_ABLE 置位则递增 ACK_count用于超时检测
* @retval 无
*/
void Process_Count(void);
/**
* @brief Modbus 轮询与应答相关变量初始化
* @note 清零超时、应答计数等标志,置位 CMD_ACK 允许发送。上电或重新建立通信前调用
* @retval 无
*/
void Process_Init(void);
#endif /* MODBUS_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,95 +1,203 @@
/** /******************************************************************************
****************************************************************************** * @file main.c
* @文件 main.c * @brief DTU 主程序 - 屏幕与按键控制
* @作者 阜阳师范大学物电学院 * @details 本文件实现了 DTU 设备的主程序逻辑,包括:
* @版本 V0.1 * - 系统初始化(时钟、外设、看门狗等)
* @日期 2026-01-15 * - 按键事件处理与业务逻辑
* @简介 DTU 屏幕按键部分的驱动程序 * - RS485 Modbus 通信处理
****************************************************************************** * - 定时器中断服务5ms 周期)
**/ * - 系统运行指示灯控制
* - 看门狗喂狗与系统复位保护
* @author 阜阳师范大学物电学院
* @version V0.1
* @date 2026.1.19
* @note 定时器周期5ms
* 系统复位时间:约 5000 秒1000000 × 5ms
* 通信协议Modbus RTU over RS485
******************************************************************************/
#include "main.h" #include "main.h"
#define TIMER 5 /* 定时器定时时间 */ /* ============================================================================
#define LED_TOGGLE_TIME 1000 /* 系统运行指示灯闪烁时间,也就是亮的时间*/ * 宏定义
* ============================================================================ */
#define TIMER 5 /**< 定时器周期(毫秒) */
#define LED_TOGGLE_TIME 1000 /**< 系统运行指示灯闪烁周期(毫秒),即 LED 点亮持续时间 */
volatile static uint32_t sysRunTime = 0; /* 系统运行时间计数器 */ /* ============================================================================
volatile static uint8_t LED_ToggleFlag = 0; /* LED闪烁定时标志位 */ * 全局变量定义
static KEY_TYPE keyStatus = KEY_NONE; /* 按键状态指示 */ * ============================================================================ */
static uint8_t ConnectFlg = 0; /**< 连接标志0x01=已连接0x00=未连接 */ volatile static uint32_t sysRunTime = 0; /**< 系统运行时间计数器单位5ms */
volatile static uint8_t LED_ToggleFlag = 0; /**< LED 闪烁定时标志位1=需要翻转) */
static KEY_TYPE keyStatus = KEY_NONE; /**< 按键状态指示(当前按键值) */
static uint8_t ConnectFlg = 0; /**< 连接标志1=已连接0=未连接 */
volatile static uint32_t KEY_RUN_Count = 0; /**< 按键按下时 LCD 屏幕显示计时计数器单位5ms */
/* ============================================================================
* 内部函数实现
* ============================================================================ */
/** /**
* @brief 按键业务逻辑回调函数 * @brief 按键业务逻辑回调函数
* @param key_type 按键类型 * @param key_type 按键类型KEY_TYPE 枚举值)
* @note 当检测到按键事件时,由按键驱动层调用此函数
* 处理流程:
* 1. 打开背光(任意按键按下都打开背光)
* 2. 显示按键按下指示KeyRun_Disp
* 3. 重置按键显示计时器
* 4. 更新按键状态(供主循环使用)
* @retval 无
*/ */
static void Key_ProcessCallback(KEY_TYPE key_type) static void Key_ProcessCallback(KEY_TYPE key_type)
{ {
// IntValue_Printf(2, 32, key_type, RESET); BackLight_ON(); /**< 任意按键按下,都打开背光 */
BackLight_ON(); /* 任意按键按下,都打开背光 */
keyStatus = key_type; /* 更新按键状态 */ KeyRun_Disp(1); /**< 有按键按下LCD 显示按键指示 */
KEY_RUN_Count = 0; /**< 重置按键显示计时器 */
keyStatus = key_type; /**< 更新按键状态,供主循环处理 */
} }
/**
* @brief 按键显示自动关闭处理
* @note 在定时器中断中调用,用于自动关闭按键按下后的 LCD 显示指示
* 处理逻辑:
* - KEY_RUN_Count 递增(每 5ms 一次)
* - 当计数达到 15约 75ms关闭按键显示
* - 计数限制在 15防止溢出
* @retval 无
*/
void KeyRun_Disp_Close(void)
{
KEY_RUN_Count++;
if (KEY_RUN_Count >= 15)
{
KEY_RUN_Count = 15; /**< 限制最大计数,防止溢出 */
KeyRun_Disp(0); /**< 关闭按键显示指示 */
}
}
/* ============================================================================
* 主程序入口
* ============================================================================ */
/**
* @brief 主函数
* @note 系统初始化流程:
* 1. HAL 库初始化
* 2. 系统时钟配置72MHz
* 3. 延时函数初始化
* 4. LED 初始化
* 5. 按键驱动初始化
* 6. 看门狗初始化
* 7. RS485 通信初始化
* 8. LCD 初始化
* 9. Modbus 通信任务初始化
* 10. 显示存储的 LOGO
* 11. 定时器初始化5ms 周期)
* 12. 注册按键回调函数
*
* 主循环处理:
* - 连接状态下的 LED 指示灯闪烁
* - RS485 Modbus 通信处理(包含按键上报)
* - 系统运行时间监控与自动复位保护
*
* 定时器中断5ms处理
* - 系统运行时间计数
* - LED 闪烁标志更新
* - 按键检测扫描
* - 看门狗喂狗
* - 背光关闭检测
* - Modbus 轮询计数
* - 按键显示自动关闭
* @retval 无(永不返回)
*/
int main(void) int main(void)
{ {
HAL_Init(); /* 初始化HAL库 */ /* ========== 系统初始化 ========== */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ HAL_Init(); /**< 初始化 HAL 库 */
delay_init(72); /* 延时初始化 */ sys_stm32_clock_init(RCC_PLL_MUL9); /**< 设置系统时钟72MHz */
LED_Init(); /* LED灯初始化 */ delay_init(72); /**< 延时函数初始化(基于 72MHz 时钟) */
Key_Init(); /* 初始化按键驱动 */
WDog_Init(); /* 看门狗初始化 */ /* ========== 外设初始化 ========== */
RS485_DMA_init(); /* Rs485初始化 */ LED_Init(); /**< LED 灯初始化 */
LcdInit(); Key_Init(); /**< 初始化按键驱动 */
Process_Init(); /* 通信任务初始化 */ WDog_Init(); /**< 看门狗初始化 */
NL_LOGO_Printf(); /* 显示存储的LOGO */ RS485_DMA_init(); /**< RS485 初始化DMA 方式) */
gtim_timx_int_init(50-1, 7200-1); /* 定时5毫秒 */ LcdInit(); /**< LCD 初始化 */
// 注册按键业务逻辑回调函数
Key_RegisterCallback(Key_ProcessCallback); /* 注册按键回调函数 */ /* ========== 通信与显示初始化 ========== */
Process_Init(); /**< Modbus 通信任务初始化 */
NL_LOGO_Printf(); /**< 显示存储的 LOGO从 Flash 读取) */
/* ========== 定时器与回调注册 ========== */
gtim_timx_int_init(50-1, 7200-1); /**< 定时器初始化5ms 周期50×0.1ms7200 分频) */
Key_RegisterCallback(Key_ProcessCallback); /**< 注册按键回调函数 */
/* ========== 主循环 ========== */
while (1) while (1)
{ {
/* LED 指示灯闪烁控制(仅在已连接状态下闪烁) */
if (ConnectFlg == 1) if (ConnectFlg == 1)
{ {
/* 指示灯闪烁 */
if (LED_ToggleFlag == 1) if (LED_ToggleFlag == 1)
{ {
LED_ToggleFlag = 0; LED_ToggleFlag = 0; /**< 清除闪烁标志 */
LED_Toggle(LED_RUN); LED_Toggle(LED_RUN); /**< 翻转运行指示灯 */
} }
} }
ConnectFlg = RS485_Process(keyStatus, ConnectFlg);
keyStatus = KEY_NONE;
/*系统运行超过一定时间立即复位重启*/ /* RS485 Modbus 通信处理(包含按键上报) */
if(sysRunTime > 1000000) ConnectFlg = RS485_Process(keyStatus, ConnectFlg);
keyStatus = KEY_NONE; /**< 清除按键状态(已处理) */
/* 系统运行时间监控与自动复位保护 */
if (sysRunTime > 1000000) /**< 约 5000 秒1000000 × 5ms */
{ {
HAL_NVIC_SystemReset(); /* 系统立即复位 */ HAL_NVIC_SystemReset(); /**< 系统立即复位(防止长时间运行异常) */
sysRunTime = 0; sysRunTime = 0;
} }
} }
} }
/* ============================================================================
* 中断服务函数
* ============================================================================ */
/** /**
* @brief 定时器中断服务函数 * @brief 定时器中断服务函数5ms 周期)
* @param 无 * @note 在定时器溢出中断中执行以下任务:
* 1. 系统运行时间计数sysRunTime++
* 2. LED 闪烁标志更新(每 200 次中断,即 1 秒)
* 3. 按键检测扫描Button_Ticks
* 4. 看门狗喂狗Clear_Watchdog
* 5. 背光关闭检测BackLight_Close
* 6. Modbus 轮询计数Process_Count
* 7. 按键显示自动关闭KeyRun_Disp_Close
*
* 定时器配置5ms 周期
* 中断标志TIM_FLAG_UPDATE定时器溢出标志
* @retval 无 * @retval 无
*/ */
void GTIM_TIMX_INT_IRQHandler(void) void GTIM_TIMX_INT_IRQHandler(void)
{ {
/*直接通过判断中断标志位来处理中断 */ /* 判断定时器溢出中断标志位 */
if (__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET) if (__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET)
{ {
sysRunTime++; sysRunTime++; /**< 系统运行时间计数递增 */
/* LED 闪烁标志更新:每 200 次中断1 秒)翻转一次 */
if (sysRunTime % (LED_TOGGLE_TIME / 5) == 0) if (sysRunTime % (LED_TOGGLE_TIME / 5) == 0)
{ {
LED_ToggleFlag = 1; LED_ToggleFlag = 1; /**< 设置 LED 闪烁标志 */
}
Button_Ticks(); /* 按键检测 */
Clear_Watchdog(); /* 看门狗喂狗 */
BackLight_Close(); /* 背光关闭检测 */
Process_Count(); /* 通信任务处理计数器 */
}
__HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /* 清楚定时器溢出中断 */
} }
Button_Ticks(); /**< 按键检测扫描MultiButton 库) */
Clear_Watchdog(); /**< 看门狗喂狗 */
BackLight_Close(); /**< 背光关闭检测 */
Process_Count(); /**< Modbus 通信轮询计数 */
KeyRun_Disp_Close(); /**< LCD 屏幕按键指示自动关闭处理 */
__HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /**< 清除定时器溢出中断标志 */
}
}

553
readme.md
View File

@@ -0,0 +1,553 @@
# DTU LED 项目
基于 STM32F103ZE 的数据传输单元DTU项目集成了 LCD 显示、按键控制、RS485 Modbus 通信等功能。
## 📋 项目简介
本项目是一个完整的 DTU 设备固件,实现了屏幕显示、按键交互、远程通信等核心功能。设备通过 RS485 接口与上位机进行 Modbus RTU 协议通信,支持图片传输、按键上报、状态监控等功能。
## 🎯 主要功能
- **LCD 显示**160×160 像素黑白 LCDUC1698U 控制器,支持图形和文本显示
- **按键控制**:基于 MultiButton 库的按键检测,支持多种按键事件
- **RS485 通信**Modbus RTU 协议DMA 接收模式,波特率 700000
- **LED 指示**:系统运行状态指示灯
- **看门狗保护**独立看门狗IWDG防止系统死机
- **Flash 存储**:支持 BMP 图片数据存储到 Flash扇区 126地址 0x0803F000
- **定时器管理**5ms 周期定时器,用于系统任务调度
## 🔧 硬件平台
- **MCU**STM32F103ZECortex-M3512KB Flash64KB RAM
- **系统时钟**72MHzPLL 倍频)
- **LCD**160×160 像素UC1698U 控制器
- **通信接口**RS485USART2TX=PA2RX=PA3DE=PA1
- **按键**:支持多按键输入(具体引脚见 `Drivers/BSP/KEY/key.c`
- **LED**:系统运行指示灯
## 📁 项目结构
```
LED/
├── Drivers/ # 驱动层
│ ├── BSP/ # 板级支持包
│ │ ├── 160160D/ # LCD 显示驱动
│ │ ├── KEY/ # 按键驱动MultiButton
│ │ ├── LED/ # LED 驱动
│ │ ├── RS485/ # RS485 通信驱动
│ │ ├── WDOG/ # 看门狗驱动
│ │ └── TIM/ # 定时器驱动
│ ├── CMSIS/ # CMSIS 核心文件
│ ├── STM32F1xx_HAL_Driver/ # STM32 HAL 库
│ └── SYSTEM/ # 系统核心驱动
│ ├── sys/ # 系统配置(时钟、中断等)
│ ├── delay/ # 延时函数
│ └── usart/ # 串口驱动(调试用)
├── Middlewares/ # 中间件层
│ └── Modbus/ # Modbus RTU 协议实现
│ ├── MODBUS.c/h # Modbus 主处理
│ └── CRC16.c/h # CRC16 校验
├── Users/ # 用户应用层
│ ├── main.c # 主程序入口
│ ├── File_Handle.c # Flash 文件处理
│ └── config.h # 配置文件
├── Projects/ # 工程文件
│ └── MDK-ARM/ # Keil MDK-ARM 工程
├── Output/ # 编译输出(仅保留 .hex
└── readme.md # 本文件
```
## 🚀 快速开始
### 开发环境要求
- **IDE**Keil MDK-ARM V5.06 或更高版本
- **编译器**ARM Compiler 5/6
- **调试器**ST-Link、J-Link 等
- **Pack**Keil.STM32F1xx_DFP.2.4.1 或更高版本
### 编译步骤
1. 打开 `Projects/MDK-ARM/atk_f103.uvprojx` 工程文件
2. 选择目标设备:`STM32F103ZE`
3. 编译工程F7 或 Project → Build Target
4. 编译成功后,`Output/` 目录下会生成 `DTU.hex` 文件
### 烧录程序
1. 连接 ST-Link 或其他调试器到目标板
2. 在 Keil 中点击 DownloadF8或使用外部烧录工具
3. 烧录 `Output/DTU.hex` 文件到 MCU
## 📡 通信协议
### Modbus RTU 配置
- **协议**Modbus RTU
- **主站地址**0x01
- **波特率**700000
- **数据位**8
- **停止位**1
- **校验位**:无
- **接收模式**DMA ReceiveToIdle不定长帧接收
### 通信功能
- **实时数据上报**:定时上报系统状态
- **按键事件上报**:按键按下时上报按键值
- **图片传输**:接收并显示远程发送的 BMP 图片数据
- **屏幕刷新**:支持远程刷新屏幕显示内容
## 🔄 工作流程
本项目核心功能是**按键检测上报**和**图像接收显示**,主要逻辑在 `MODBUS.c` 文件中实现。
### 系统整体架构
```
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ 定时器 │ 5ms中断 │ 按键驱动 │ 检测按键 │ 主循环 │
│ (5ms周期) │────────>│ (MultiButton)│────────>│ (main.c) │
└─────────────┘ └──────────────┘ └──────┬──────┘
│──────────────────────│
│ RS485_Process
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ RS485 │ DMA接收 │ MODBUS.c │ 处理数据 │ LCD显示 │
│ (USART2) │────────>│ (主处理) │────────>│ (160160D) │
└─────────────┘ └──────┬───────┘ └─────────────┘
┌─────────────┼─────────────┐
│ 发送命令 │ │ 解析数据控制
▼ │ ▼
┌─────────────┐ │ ┌─────────────┐
│ 上位机 │ │ │ LED │
│ (138端) │ │ │ (状态指示) │
└─────────────┘ │ └─────────────┘
```
**说明**`MODBUS.c` 接收 RS485 数据后解析;普通图片帧中的 `info[0..3]` 为 LED 状态,经 `LED_Printf()` 更新灯状态LOGO 图片不含 LED 控制。
### 按键检测与上报流程
#### 1. 按键检测流程
```
定时器中断 (5ms)
├─> Button_Ticks() // MultiButton 库扫描按键
└─> 检测到按键按下
├─> Key_ProcessCallback() // 按键回调函数
│ │
│ ├─> BackLight_ON() // 打开背光
│ ├─> KeyRun_Disp(1) // 显示按键指示
│ └─> keyStatus = key_type // 更新按键状态
└─> 主循环检测 keyStatus
└─> RS485_Process(keyStatus, ConnectFlg)
```
#### 2. 按键上报流程
```
RS485_Process() 函数
├─> 检测到按键 (key != KEY_NONE)
│ │
│ └─> 保存到 key_bk静态变量保持状态
├─> 检查发送条件
│ │
│ ├─> CMD_ACK == SET // 上次命令已应答
│ └─> ACK_OverTime == SET // 或应答超时
└─> 满足条件时发送按键命令
├─> RefreshScreen(KEY_CMD, key_bk)
│ │
│ ├─> RS485_RefreshScreenCMD(0x03, KEY_CMD, key)
│ │ │
│ │ ├─> 构建命令帧6字节
│ │ │ [0] = 0x01 (从站地址)
│ │ │ [1] = 0x03 (功能码)
│ │ │ [2] = 0x00 (KEY_CMD)
│ │ │ [3] = key (按键值)
│ │ │ [4-5] = CRC16校验
│ │ │
│ │ └─> RS485_SendBuff() 发送
│ │
│ └─> 重置轮询状态
└─> key_bk = KEY_NONE // 清除按键备份
```
**按键命令帧格式**6字节
```
┌──────┬──────┬──────┬──────┬──────┬──────┐
│ 0x01 │ 0x03 │ 0x00 │ key │ CRC │ CRC │
│地址 │功能码 │按键 │按键值 │ 高 │ 低 │
└──────┴──────┴──────┴──────┴──────┴──────┘
```
### 图像接收与显示流程
#### 1. 数据接收流程
```
RS485 DMA 接收 (ReceiveToIdle)
├─> 接收到一帧数据3208字节
│ │
│ └─> HAL_UARTEx_RxEventCallback()
│ │
│ ├─> RS485REG.NewMessageFlag = SET
│ └─> 重新启动 DMA 接收
└─> 主循环检测 NewMessageFlag
└─> RS485_Process() 处理新消息
```
#### 2. 数据解析与显示流程
```
RS485_Process() 检测到新消息
├─> NewMessageFlag == SET
│ │
│ ├─> ConnectFlg = 1 // 设置连接标志
│ │
│ ├─> CRC 校验
│ │ │
│ │ ├─> 校验通过
│ │ │ │
│ │ │ ├─> 提取数据:
│ │ │ │ Picture[3200] = DR[0:3199] // 图片数据
│ │ │ │ info[8] = DR[3200:3207] // 命令信息
│ │ │ │
│ │ │ ├─> 解析 info
│ │ │ │
│ │ │ │ info[4-5] = 图片类型标识
│ │ │ │
│ │ │ └─> 判断图片类型
│ │ │ │
│ │ │ ├─> LOGO 图片 (info[4]==0x89 && info[5]==0x45)
│ │ │ │ │─>info[0-1] = 图片宽度 (16位)
│ │ │ │ │─>info[2-3] = 图片高度 (16位)
│ │ │ │ ├─> 复制到 logo 结构体
│ │ │ │ ├─> BMP_SAVE2False() // 保存到 Flash
│ │ │ │ ├─> ClearScreen() // 清屏
│ │ │ │ ├─> LOGO_Printf() // 显示 LOGO
│ │ │ │ └─> delay_ms(2000) // 延时 2 秒
│ │ │ │
│ │ │ └─> 普通图片
│ │ │ │─>info[0-3] = LED 状态
│ │ │ ├─> LED_Printf(LED_BUFF) // 更新 LED 状态
│ │ │ └─> ScreenPrintf(Picture) // 显示图片
│ │ │
│ │ └─> 校验失败
│ │ │
│ │ ├─> CRC_ERR_COUNT++
│ │ └─> 连续 3 次错误 → RS485_DMA_init() 重新初始化
│ │
│ └─> 清除标志并设置 CMD_ACK = SET
└─> 继续处理其他任务
```
**接收数据帧格式**3208字节
```
┌─────────────────────┬──────────────────┐
│ Picture[3200] │ info[8] │
│ 图片点阵数据 │ 命令信息 │
└─────────────────────┴──────────────────┘
│ │
│ ├─> info[0-1]: 宽度
│ ├─> info[2-3]: 高度
│ ├─> info[4-5]: 类型标识
│ └─> info[6-7]: LED状态(普通图片)
└─> 3200 字节 BMP 点阵数据
```
### 实时数据轮询流程
```
定时器中断 (5ms)
└─> Process_Count()
├─> RT_count++ // 实时轮询计数递增
└─> ACK_count++ (如果 ACK_COUNT_ABLE == SET)
└─> 主循环检查
├─> RT_count > 55 (约 275ms)
│ │
│ └─> RefreshScreen(RT_CMD, 0) // 发送实时刷新命令
└─> ACK_count > 40 (约 200ms)
└─> ACK_OverTime = SET // 应答超时
└─> 连续 5 次超时 → 重新初始化 RS485
```
### 完整工作流程图
```
┌─────────────────────────────────────────────────────────────┐
│ 系统初始化 │
│ - HAL_Init() │
│ - RS485_DMA_init() // 启动 DMA 接收 │
│ - Process_Init() // 初始化轮询状态 │
│ - NL_LOGO_Printf() // 显示 Flash 中的 LOGO │
└──────────────────────┬──────────────────────────────────────┘
┌──────────────────────────────┐
│ 主循环 (while(1)) │
└──────────────┬───────────────┘
┌──────────────┴───────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ 定时器中断 │ │ RS485_Process│
│ (5ms周期) │ │ (主处理) │
└───────┬───────┘ └───────┬───────┘
│ │
├─> Button_Ticks() ├─> 检查新消息
├─> Process_Count() │ (NewMessageFlag)
└─> 其他任务 │
├─> CRC 校验
├─> 解析数据
│ ├─> LOGO 图片 → 保存 Flash + 显示
│ └─> 普通图片 → 显示图片 + LED
├─> 检查按键上报
│ └─> 发送按键命令 (KEY_CMD)
└─> 检查定时刷新
└─> 发送实时命令 (RT_CMD)
```
### 关键数据结构
#### RS485_POLL_TYPE轮询状态
```c
typedef struct {
volatile uint8_t RT_count; // 实时命令计数0-55约275ms
volatile uint16_t CMD_ACK; // 命令应答标志SET=可发送)
volatile uint8_t ACK_COUNT_ABLE; // 应答计时使能
volatile uint8_t ACK_count; // 应答等待计数0-40约200ms
volatile uint8_t ACK_OverTime; // 本次应答超时标志
volatile uint8_t ACK_OverTimeCnt; // 连续超时次数0-5
} RS485_POLL_TYPE;
```
#### logo_typeLOGO 图片数据)
```c
typedef struct {
uint8_t bmpdata[3200]; // BMP 点阵数据
uint32_t biWidth; // 图片宽度(像素)
uint32_t biHeight; // 图片高度(像素)
} logo_type;
```
### 关键时间参数
| 参数 | 值 | 说明 |
|------|-----|------|
| 定时器周期 | 5ms | 系统基础时钟周期 |
| 实时轮询周期 | 55×5ms = 275ms | 定时发送实时刷新命令 |
| 应答超时时间 | 40×5ms = 200ms | 等待应答的最大时间 |
| 连续超时上限 | 5 次 | 超过则重新初始化 RS485 |
| CRC 错误上限 | 3 次 | 连续错误则重新初始化 RS485 |
| LOGO 显示时间 | 2000ms | LOGO 图片显示持续时间 |
### 错误处理机制
1. **CRC 校验失败**
- 累计错误计数 `CRC_ERR_COUNT`
- 连续 3 次错误 → 重新初始化 RS485 DMA 接收
2. **应答超时**
- 发送命令后启动应答计时(`ACK_COUNT_ABLE = SET`
- 200ms 内未收到应答 → 设置超时标志
- 连续 5 次超时 → 清除连接标志,重新初始化 RS485
3. **通信恢复**
- 重新初始化后,`CMD_ACK = SET`,允许重新发送命令
- 系统自动恢复通信流程
## 🔌 引脚分配
以下引脚定义与 `led.c``key.c``rs485.c``160160D.C` 中的配置一致。
### LED 灯(共 20 个)
所有 LED 均为**低电平有效**(输出低电平点亮,高电平熄灭)。推挽输出,上拉,高速。
| LED ID | 宏/用途 | 端口 | 引脚 | 说明 |
|--------|-------------|------|-------|--------------|
| 0 | `LED_POW` | PC | PC8 | 电源指示灯 |
| 1 | `LED_RUN` | PC | PC9 | 运行指示灯 |
| 2 | — | PA | PA9 | 通用 LED |
| 3 | — | PA | PA11 | 通用 LED |
| 4 | — | PA | PA15 | 通用 LED |
| 5 | — | PC | PC11 | 通用 LED |
| 6 | — | PD | PD0 | 通用 LED |
| 7 | — | PD | PD2 | 通用 LED |
| 8 | — | PD | PD4 | 通用 LED |
| 9 | — | PD | PD6 | 通用 LED |
| 10 | — | PC | PC7 | 通用 LED |
| 11 | — | PA | PA8 | 通用 LED |
| 12 | — | PA | PA10 | 通用 LED |
| 13 | — | PA | PA12 | 通用 LED |
| 14 | — | PC | PC10 | 通用 LED |
| 15 | — | PC | PC12 | 通用 LED |
| 16 | — | PD | PD1 | 通用 LED |
| 17 | — | PD | PD3 | 通用 LED |
| 18 | — | PD | PD5 | 通用 LED |
| 19 | — | PD | PD7 | 通用 LED |
- **代码位置**`Drivers/BSP/LED/led.c``led_config[]` 数组。
- **API**`LED_On(id)` / `LED_Off(id)` / `LED_Toggle(id)`;电源灯 `LED_POW(x)`,运行灯 `LED_Toggle(LED_RUN)`
### 按键(共 8 个)
按键为**输入**,无上拉/下拉(由外部电路决定)。按下为低电平,释放为高电平。
| 按键类型 | 端口 | 引脚 | 说明 |
|-------------|------|-------|------------------------------|
| `KEY_ENTER` | PD | PD12 | 确认键 |
| `KEY_UP` | PD | PD11 | 上键 |
| `KEY_DOWN` | PD | PD10 | 下键 |
| `KEY_LEFT` | PD | PD13 | 左键 |
| `KEY_RIGHT` | PD | PD8 | 右键 |
| `KEY_ESC` | PB | PB15 | 取消键 |
| `KEY_ADD` | PB | PB3 | 加键(需禁用 JTAG见下 |
| `KEY_RESET` | PD | PD15 | 复位键 |
- **代码位置**`Drivers/BSP/KEY/key.c``key_configs[]` 数组。
- **JTAG 说明**:使用 PB3 时,`Key_Init()` 中会调用 `__HAL_AFIO_REMAP_SWJ_NOJTAG()` 禁用 JTAG、保留 SWD以释放 PA15、PB3、PB4 作 GPIO。调试请使用 SWDPA13/PA14
### RS485 通信
| 信号 | 端口 | 引脚 | 说明 |
|------|------|-------|-------------------------------------|
| TX | PA | PA2 | USART2_TX |
| RX | PA | PA3 | USART2_RX |
| DE | PA | PA1 | 收发使能,低电平发送、高电平接收 |
### LCD 显示160160D / UC1698U
| 信号 | 端口 | 引脚 | 说明 |
|----------|------|---------|---------------------|
| 背光 | PB | PB0 | 背光控制 |
| RST | PB | PB10 | 复位 |
| CS | PB | PB11 | 片选 |
| RD | PB | PB12 | 读使能 |
| WR | PB | PB13 | 写使能 |
| CD | PB | PB14 | 命令/数据选择 |
| D0D7 | PE | PE8PE15 | 8 位并行数据总线 |
## ⚙️ 系统配置
### 时钟配置
- **系统时钟**72MHz
- **AHB 时钟**72MHz
- **APB1 时钟**36MHz
- **APB2 时钟**72MHz
### 定时器配置
- **定时器周期**5ms
- **中断优先级**:组 2抢占优先级 1子优先级 0
### 看门狗配置
- **类型**独立看门狗IWDG
- **喂狗周期**5ms在定时器中断中喂狗
### 系统保护
- **自动复位**:系统运行时间超过 5000 秒(约 83 分钟)自动复位,防止长时间运行异常
## 📝 代码规范
本项目遵循以下代码规范:
- **注释风格**Doxygen 风格注释(`@file``@brief``@param``@retval` 等)
- **命名规范**:驼峰命名法(函数、变量),宏定义全大写
- **文件组织**:按功能模块划分,驱动层、中间件层、应用层分离
## 🔍 主要模块说明
### LCD 显示模块160160D
- 驱动 UC1698U 控制器
- 支持 4K 色RGB444和 64K 色RGB565
- 提供字符显示、图形显示、LOGO 显示等功能
### 按键模块KEY
- 基于 MultiButton 库
- 支持单击、长按、双击等事件
- 按键事件通过回调函数处理
### RS485 通信模块
- DMA 接收模式(主工作模式)
- 中断接收模式(错误恢复)
- 支持不定长帧接收ReceiveToIdle
### Modbus 模块
- Modbus RTU 协议实现
- 支持实时数据上报、按键上报
- 支持图片数据传输和屏幕刷新
### Flash 存储模块
- BMP 图片数据存储地址0x0803F000
- 扇区大小2KB
- 支持读取和写入操作
## 🐛 调试说明
### 调试接口
- **SWD 接口**SWDIO、SWCLK标准 STM32 SWD 接口)
- **串口调试**:可通过 USART1 进行调试输出(如需要)
### 常见问题
1. **编译错误**:检查 Pack 是否安装正确,设备型号是否为 STM32F103ZE
2. **烧录失败**:检查调试器连接,确认目标板供电正常
3. **通信异常**:检查 RS485 接线,确认波特率配置正确
4. **显示异常**:检查 LCD 接线,确认背光控制正常
## 📄 许可证
本项目由阜阳师范大学物电学院开发。
## 👥 作者
- **开发单位**:阜阳师范大学物电学院
- **版本**V0.1
- **日期**2026.1.19
## 📚 参考资料
- [STM32F103 参考手册](https://www.st.com/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)
- [Modbus RTU 协议规范](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)
- [UC1698U 数据手册](https://www.ultrachip.com.cn/)
## 🔄 版本历史
- **V0.1**2026.1.19
- 初始版本
- 实现 LCD 显示、按键控制、RS485 Modbus 通信等基础功能
- 添加看门狗保护和系统自动复位功能
---
**注意**:本项目仅供学习和研究使用。使用前请仔细阅读代码注释,确保硬件连接正确。