#include #include "LCD1602.h" #include "Delay.h" #include "C51Math.h" /* * LCD1602 使用 8 位并口模式。 * * 写命令/写数据的基本时序: * 1. 设置 RS,选择命令寄存器或数据寄存器。 * 2. 把 8 位数据放到数据口。 * 3. EN 拉高后再拉低,让 LCD 锁存数据。 * 4. 等待 LCD 内部执行完成。 */ #define LCD1602_LINE1_ADDR 0x80 #define LCD1602_LINE2_ADDR 0xC0 #define LCD1602_MAX_COLUMN 16 #define LCD1602_POINT '.' /* 十进制位权表,配合 C51Math_TakeDigitU16() 避免 / 和 %。 */ static unsigned int code lcd1602_decimal_divisors[4] = { 10000U, 1000U, 100U, 10U }; /** * @brief 产生 LCD1602 使能脉冲 */ static void LCD1602_PulseEnable(void) { /* EN 高电平期间 LCD 读取数据口。 */ LCD1602_EN = 1; Delay_10us(1); /* EN 下降沿后,LCD 锁存刚才的数据或命令。 */ LCD1602_EN = 0; Delay_10us(5); } /** * @brief 向 LCD1602 写命令 */ static void LCD1602_WriteCommand(unsigned char command) { /* RS=0 表示写入命令寄存器。 */ LCD1602_RS = 0; LCD1602_DATA_PORT = command; LCD1602_PulseEnable(); } /** * @brief 向 LCD1602 写数据 */ static void LCD1602_WriteData(unsigned char value) { /* RS=1 表示写入显示数据。 */ LCD1602_RS = 1; LCD1602_DATA_PORT = value; LCD1602_PulseEnable(); } /** * @brief 把 unsigned int 拆成 5 个十进制数字 * @param value 要拆分的数值 * @param digits 保存结果,digits[0] 是万位,digits[4] 是个位 */ static void LCD1602_SplitUInt(unsigned int value, unsigned char data *digits) { unsigned char i; unsigned int data decimal_value; decimal_value = value; for (i = 0; i < 4; i++) { /* 每次取出当前位后,decimal_value 会变成余数。 */ digits[i] = C51Math_TakeDigitU16(&decimal_value, lcd1602_decimal_divisors[i]); } digits[4] = (unsigned char)decimal_value; } /** * @brief 输出无符号整数,不设置光标 * @param value 要显示的数值 * @param min_width 最少显示位数,0 表示自然宽度 * @retval 实际输出的数字位数 */ static unsigned char LCD1602_WriteUInt(unsigned int value, unsigned char min_width) { unsigned char data digits[5]; unsigned char i; unsigned char start; if (min_width > 5) { /* unsigned int 最大 65535,最多 5 位十进制数字。 */ min_width = 5; } LCD1602_SplitUInt(value, digits); /* * 跳过前导 0。 * 但必须保留至少 min_width 位;value 为 0 时至少保留个位。 */ start = 0; while ((start < 4) && (digits[start] == 0) && ((5 - start) > min_width) && ((5 - start) > 1)) { start++; } for (i = start; i < 5; i++) { LCD1602_WriteData(digits[i] + '0'); } return 5 - start; } /** * @brief 读取指定位上的十六进制数字 * @param value 要显示的数值 * @param index 从左到右的显示位置,范围 0~3 * @retval 当前十六进制位,范围 0~15 */ static unsigned char LCD1602_GetHexDigit(unsigned int value, unsigned char index) { if (index == 0) { return (unsigned char)((value >> 12) & 0x0F); } if (index == 1) { return (unsigned char)((value >> 8) & 0x0F); } if (index == 2) { return (unsigned char)((value >> 4) & 0x0F); } return (unsigned char)(value & 0x0F); } /** * @brief 把 0~15 转成十六进制字符 */ static char LCD1602_HexToChar(unsigned char value) { if (value < 10) { return value + '0'; } return value - 10 + 'A'; } /** * @brief 输出定点数绝对值,不设置光标 * @param value 已经取绝对值的定点整数 * @param decimals 小数位数 */ static void LCD1602_WriteFixedAbs(unsigned int value, unsigned char decimals) { unsigned char data digits[5]; unsigned char start; unsigned char digit_count; unsigned char point_after; unsigned char i; if (decimals == 0) { LCD1602_WriteUInt(value, 1); return; } if (decimals > 4) { decimals = 4; } LCD1602_SplitUInt(value, digits); /* * 定点数至少需要 decimals+1 位数字。 * 例如 value=5, decimals=1,要显示 0.5,而不是 .5。 */ start = 0; while ((start < 4) && (digits[start] == 0) && ((5 - start) > (decimals + 1))) { start++; } digit_count = 5 - start; point_after = digit_count - decimals; for (i = 0; i < digit_count; i++) { /* 小数点放在小数部分左侧。 */ if (i == point_after) { LCD1602_WriteData(LCD1602_POINT); } LCD1602_WriteData(digits[start + i] + '0'); } } /** * @brief 初始化 LCD1602 */ void LCD1602_Init(void) { /* 上电后 LCD 需要一小段时间完成内部复位。 */ Delay_ms(15); LCD1602_WriteCommand(0x38); /* 8 位数据接口,两行显示,5x7 点阵。 */ LCD1602_WriteCommand(0x0C); /* 显示开,光标关,闪烁关。 */ LCD1602_WriteCommand(0x06); /* 写入后光标右移,画面不移动。 */ LCD1602_Clear(); } /** * @brief 清屏并复位光标 */ void LCD1602_Clear(void) { LCD1602_WriteCommand(0x01); /* 清屏命令执行时间较长,需要额外等待。 */ Delay_ms(2); } /** * @brief 设置光标位置 */ void LCD1602_SetCursor(unsigned char line, unsigned char column) { unsigned char address; if (column < 1) { column = 1; } else if (column > LCD1602_MAX_COLUMN) { column = LCD1602_MAX_COLUMN; } if (line == 1) { address = LCD1602_LINE1_ADDR + column - 1; } else if (line == 2) { address = LCD1602_LINE2_ADDR + column - 1; } else { /* 非法行号直接忽略。 */ return; } LCD1602_WriteCommand(address); } /** * @brief 在指定位置显示一个字符 */ void LCD1602_ShowChar(unsigned char line, unsigned char column, char ch) { LCD1602_SetCursor(line, column); LCD1602_WriteData(ch); } /** * @brief 在指定位置显示 code 区字符串 */ void LCD1602_ShowString(unsigned char line, unsigned char column, const char code *str) { LCD1602_SetCursor(line, column); while (*str != '\0') { LCD1602_WriteData(*str++); } } /** * @brief 在指定位置显示 data 区字符串 */ void LCD1602_ShowDataString(unsigned char line, unsigned char column, char data *str) { LCD1602_SetCursor(line, column); while (*str != '\0') { LCD1602_WriteData(*str++); } } /** * @brief 显示无符号十进制整数 */ void LCD1602_ShowUInt(unsigned char line, unsigned char column, unsigned int value, unsigned char width) { LCD1602_SetCursor(line, column); LCD1602_WriteUInt(value, width); } /** * @brief 显示有符号十进制整数 */ void LCD1602_ShowInt(unsigned char line, unsigned char column, int value, unsigned char width) { unsigned int abs_value; LCD1602_SetCursor(line, column); if (value < 0) { /* 用无符号减法得到绝对值,避免 int 最小值取负溢出。 */ abs_value = 0U - (unsigned int)value; LCD1602_WriteData('-'); } else { abs_value = (unsigned int)value; } LCD1602_WriteUInt(abs_value, width); } /** * @brief 显示十六进制整数 */ void LCD1602_ShowHex(unsigned char line, unsigned char column, unsigned int value, unsigned char width) { unsigned char i; unsigned char start; unsigned char digit; if (width == 0) { width = 1; } else if (width > 4) { width = 4; } start = 4 - width; LCD1602_SetCursor(line, column); for (i = start; i < 4; i++) { digit = LCD1602_GetHexDigit(value, i); LCD1602_WriteData(LCD1602_HexToChar(digit)); } } /** * @brief 显示二进制整数 */ void LCD1602_ShowBin(unsigned char line, unsigned char column, unsigned int value, unsigned char width) { unsigned char i; unsigned int mask; if (width == 0) { width = 1; } else if (width > 16) { width = 16; } mask = 0x8000U; for (i = width; i < 16; i++) { /* 把掩码移动到要显示的最高位。 */ mask >>= 1; } LCD1602_SetCursor(line, column); for (i = 0; i < width; i++) { LCD1602_WriteData((value & mask) ? '1' : '0'); mask >>= 1; } } /** * @brief 显示定点数 */ void LCD1602_ShowFixed(unsigned char line, unsigned char column, int value, unsigned char decimals) { unsigned int abs_value; LCD1602_SetCursor(line, column); if (value < 0) { /* 用无符号减法得到绝对值,避免 int 最小值取负溢出。 */ abs_value = 0U - (unsigned int)value; LCD1602_WriteData('-'); } else { abs_value = (unsigned int)value; } LCD1602_WriteFixedAbs(abs_value, decimals); } /** * @brief 从指定位置开始清空若干字符 */ void LCD1602_ClearChars(unsigned char line, unsigned char column, unsigned char length) { unsigned char i; if (length == 0) { return; } LCD1602_SetCursor(line, column); for (i = 0; i < length; i++) { LCD1602_WriteData(' '); } }