Skip to content

nokia5110.c 完整代码

c

/**
 * @file nokia5110.c
 * @brief Nokia 5110 LCD Display Driver Implementation
 */

#include "nokia5110.h"
#include "stdlib.h"

/* Private variables ---------------------------------------------------------*/
static uint8_t LCD_Buffer[LCD_WIDTH * LCD_HEIGHT / 8]; // 显示缓冲区


// 字符字模数组 (ASCII 32-127,每个字符6x8像素,占用6字节)
// 这是一个简化的字模,实际应用中您可能需要更完整的字模
const uint8_t LCD5110_Font[][6] = {
    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ' ' (Space)
    {0x00, 0x00, 0x5F, 0x00, 0x00, 0x00}, // '!'
    {0x00, 0x07, 0x00, 0x07, 0x00, 0x00}, // '"'
    {0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00}, // '#'
    {0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00}, // '$'
    {0x23, 0x13, 0x08, 0x64, 0x62, 0x00}, // '%'
    {0x36, 0x49, 0x55, 0x22, 0x50, 0x00}, // '&'
    {0x00, 0x05, 0x03, 0x00, 0x00, 0x00}, // '''
    {0x00, 0x1C, 0x22, 0x41, 0x00, 0x00}, // '('
    {0x00, 0x41, 0x22, 0x1C, 0x00, 0x00}, // ')'
    {0x14, 0x08, 0x3E, 0x08, 0x14, 0x00}, // '*'
    {0x08, 0x08, 0x3E, 0x08, 0x08, 0x00}, // '+'
    {0x00, 0x50, 0x30, 0x00, 0x00, 0x00}, // ','
    {0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, // '-'
    {0x00, 0x60, 0x60, 0x00, 0x00, 0x00}, // '.'
    {0x20, 0x10, 0x08, 0x04, 0x02, 0x00}, // '/'
    {0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00}, // '0'
    {0x00, 0x42, 0x7F, 0x40, 0x00, 0x00}, // '1'
    {0x42, 0x61, 0x51, 0x49, 0x46, 0x00}, // '2'
    {0x21, 0x41, 0x45, 0x4B, 0x31, 0x00}, // '3'
    {0x18, 0x14, 0x12, 0x7F, 0x10, 0x00}, // '4'
    {0x27, 0x45, 0x45, 0x45, 0x39, 0x00}, // '5'
    {0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00}, // '6'
    {0x01, 0x71, 0x09, 0x05, 0x03, 0x00}, // '7'
    {0x36, 0x49, 0x49, 0x49, 0x36, 0x00}, // '8'
    {0x06, 0x49, 0x49, 0x29, 0x1E, 0x00}, // '9'
    {0x00, 0x36, 0x36, 0x00, 0x00, 0x00}, // ':'
    {0x00, 0x56, 0x36, 0x00, 0x00, 0x00}, // ';'
    {0x08, 0x14, 0x22, 0x41, 0x00, 0x00}, // '<'
    {0x14, 0x14, 0x14, 0x14, 0x14, 0x00}, // '='
    {0x00, 0x41, 0x22, 0x14, 0x08, 0x00}, // '>'
    {0x02, 0x01, 0x51, 0x09, 0x06, 0x00}, // '?'
    {0x32, 0x49, 0x79, 0x41, 0x3E, 0x00}, // '@'
    {0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00}, // 'A'
    {0x7F, 0x49, 0x49, 0x49, 0x36, 0x00}, // 'B'
    {0x3E, 0x41, 0x41, 0x41, 0x22, 0x00}, // 'C'
    {0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00}, // 'D'
    {0x7F, 0x49, 0x49, 0x49, 0x41, 0x00}, // 'E'
    {0x7F, 0x09, 0x09, 0x09, 0x01, 0x00}, // 'F'
    {0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00}, // 'G'
    {0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00}, // 'H'
    {0x00, 0x41, 0x7F, 0x41, 0x00, 0x00}, // 'I'
    {0x20, 0x40, 0x41, 0x3F, 0x01, 0x00}, // 'J'
    {0x7F, 0x08, 0x14, 0x22, 0x41, 0x00}, // 'K'
    {0x7F, 0x40, 0x40, 0x40, 0x40, 0x00}, // 'L'
    {0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00}, // 'M'
    {0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00}, // 'N'
    {0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00}, // 'O'
    {0x7F, 0x09, 0x09, 0x09, 0x06, 0x00}, // 'P'
    {0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00}, // 'Q'
    {0x7F, 0x09, 0x19, 0x29, 0x46, 0x00}, // 'R'
    {0x46, 0x49, 0x49, 0x49, 0x31, 0x00}, // 'S'
    {0x01, 0x01, 0x7F, 0x01, 0x01, 0x00}, // 'T'
    {0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00}, // 'U'
    {0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00}, // 'V'
    {0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00}, // 'W'
    {0x63, 0x14, 0x08, 0x14, 0x63, 0x00}, // 'X'
    {0x07, 0x08, 0x70, 0x08, 0x07, 0x00}, // 'Y'
    {0x61, 0x51, 0x49, 0x45, 0x43, 0x00}, // 'Z'
    {0x00, 0x7F, 0x41, 0x41, 0x00, 0x00}, // '['
    {0x55, 0x2A, 0x55, 0x2A, 0x55, 0x00}, // '\' (Backslash)
    {0x00, 0x41, 0x41, 0x7F, 0x00, 0x00}, // ']'
    {0x04, 0x02, 0x01, 0x02, 0x04, 0x00}, // '^'
    {0x40, 0x40, 0x40, 0x40, 0x40, 0x00}, // '_'
    {0x00, 0x01, 0x02, 0x04, 0x00, 0x00}, // '`'
    {0x20, 0x54, 0x54, 0x54, 0x78, 0x00}, // 'a'
    {0x7F, 0x48, 0x44, 0x44, 0x38, 0x00}, // 'b'
    {0x38, 0x44, 0x44, 0x44, 0x20, 0x00}, // 'c'
    {0x38, 0x44, 0x44, 0x48, 0x7F, 0x00}, // 'd'
    {0x38, 0x54, 0x54, 0x54, 0x18, 0x00}, // 'e'
    {0x08, 0x7E, 0x09, 0x01, 0x02, 0x00}, // 'f'
    {0x0C, 0x52, 0x52, 0x52, 0x3E, 0x00}, // 'g'
    {0x7F, 0x08, 0x04, 0x04, 0x78, 0x00}, // 'h'
    {0x00, 0x44, 0x7D, 0x40, 0x00, 0x00}, // 'i'
    {0x20, 0x40, 0x44, 0x3D, 0x00, 0x00}, // 'j'
    {0x7F, 0x10, 0x28, 0x44, 0x00, 0x00}, // 'k'
    {0x00, 0x41, 0x7F, 0x40, 0x00, 0x00}, // 'l'
    {0x7C, 0x04, 0x18, 0x04, 0x78, 0x00}, // 'm'
    {0x7C, 0x08, 0x04, 0x04, 0x78, 0x00}, // 'n'
    {0x38, 0x44, 0x44, 0x44, 0x38, 0x00}, // 'o'
    {0x7C, 0x14, 0x14, 0x14, 0x08, 0x00}, // 'p'
    {0x08, 0x14, 0x14, 0x18, 0x7C, 0x00}, // 'q'
    {0x7C, 0x08, 0x04, 0x04, 0x08, 0x00}, // 'r'
    {0x48, 0x54, 0x54, 0x54, 0x20, 0x00}, // 's'
    {0x04, 0x3F, 0x44, 0x40, 0x20, 0x00}, // 't'
    {0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00}, // 'u'
    {0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00}, // 'v'
    {0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00}, // 'w'
    {0x44, 0x28, 0x10, 0x28, 0x44, 0x00}, // 'x'
    {0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00}, // 'y'
    {0x44, 0x64, 0x54, 0x4C, 0x44, 0x00}, // 'z'
    {0x00, 0x08, 0x36, 0x41, 0x00, 0x00}, // '{'
    {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00}, // '|'
    {0x00, 0x41, 0x36, 0x08, 0x00, 0x00}, // '}'
    {0x02, 0x01, 0x02, 0x04, 0x02, 0x00}, // '~' (DEL or non-extended)
};

/* Private function prototypes -----------------------------------------------*/
static void LCD_Write(uint8_t data, LcdCmdDataType type);
static void LCD_Reset(void);
static void LCD_WriteCommand(uint8_t cmd);
static void LCD_WriteData(uint8_t data);
static void LCD_Update(void);

/* Private functions ---------------------------------------------------------*/

/**
 * @brief  写入一个字节到LCD
 * @param  data: 要写入的数据
 * @param  type: 数据类型 (命令或数据)
 * @retval None
 */
static void LCD_Write(uint8_t data, LcdCmdDataType type)
{
  // 设置DC引脚,决定是命令还是数据
  if (type == LCD_CMD) {
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET); // 命令模式
  } else {
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET);   // 数据模式
  }

  // 拉低片选,选择LCD
  HAL_GPIO_WritePin(LCD_SCE_GPIO_Port, LCD_SCE_Pin, GPIO_PIN_RESET);

  // 发送数据(SPI模拟)
  for (uint8_t i = 0; i < 8; i++) {
    // 准备数据位
    if (data & 0x80) {
      HAL_GPIO_WritePin(LCD_DI_GPIO_Port, LCD_DI_Pin, GPIO_PIN_SET); // 数据位为1
    } else {
      HAL_GPIO_WritePin(LCD_DI_GPIO_Port, LCD_DI_Pin, GPIO_PIN_RESET); // 数据位为0
    }

    // 时钟上升沿
    HAL_GPIO_WritePin(LCD_SCLK_GPIO_Port, LCD_SCLK_Pin, GPIO_PIN_SET);

    // 时钟下降沿,数据被采样
    HAL_GPIO_WritePin(LCD_SCLK_GPIO_Port, LCD_SCLK_Pin, GPIO_PIN_RESET);

    // 移动到下一位
    data <<= 1;
  }

  // 拉高片选,取消选择
  HAL_GPIO_WritePin(LCD_SCE_GPIO_Port, LCD_SCE_Pin, GPIO_PIN_SET);
}

/**
 * @brief  写入命令到LCD
 * @param  cmd: 命令字节
 * @retval None
 */
static void LCD_WriteCommand(uint8_t cmd)
{
  LCD_Write(cmd, LCD_CMD);
}

/**
 * @brief  写入数据到LCD
 * @param  data: 数据字节
 * @retval None
 */
static void LCD_WriteData(uint8_t data)
{
  LCD_Write(data, LCD_DATA);
}

/**
 * @brief  硬件复位LCD
 * @retval None
 */
static void LCD_Reset(void)
{
  HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);
  HAL_Delay(100);
  HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET);
  HAL_Delay(100);
}

/**
 * @brief  更新LCD显示(将缓冲区内容写入LCD)
 * @retval None
 */
static void LCD_Update(void)
{
  for (uint8_t y = 0; y < LCD_HEIGHT / 8; y++) {
    LCD_WriteCommand(LCD_SETXADDR | 0);     // 设置X地址为0
    LCD_WriteCommand(LCD_SETYADDR | y);     // 设置Y地址

    for (uint8_t x = 0; x < LCD_WIDTH; x++) {
      LCD_WriteData(LCD_Buffer[x + (y * LCD_WIDTH)]);
    }
  }
}

/* Exported functions --------------------------------------------------------*/

/**
 * @brief  初始化LCD
 * @retval None
 */
void LCD_Init(void)
{
  // 硬件复位
  LCD_Reset();

  // 进入扩展命令模式
  LCD_WriteCommand(LCD_FUNCTIONSET_EXTENDED);

  // 设置LCD偏置电压
  LCD_WriteCommand(LCD_BIAS_MODE | 0x02);

  // 设置温度系数
  LCD_WriteCommand(LCD_TEMP_COEF | 0x01);

  // 设置对比度 (VOP)
  LCD_WriteCommand(LCD_VOP | 0x40);

  // 进入基本命令模式
  LCD_WriteCommand(LCD_FUNCTIONSET_BASIC);

  // 设置显示模式
  LCD_WriteCommand(LCD_DISPLAYCONTROL_NORMAL);

  // 清空显示
  LCD_Clear();
}

/**
 * @brief  清空LCD显示
 * @retval None
 */
void LCD_Clear(void)
{
  // 清空缓冲区
  for (uint16_t i = 0; i < sizeof(LCD_Buffer); i++) {
    LCD_Buffer[i] = 0x00;
  }

  // 更新显示
  LCD_Update();
}

/**
 * @brief  设置光标位置
 * @param  x: 水平位置 (0-83)
 * @param  y: 垂直位置 (0-5)
 * @retval None
 */
// ... existing code ...
void LCD_SetCursor(uint8_t x, uint8_t y)
{
  if (x >= LCD_WIDTH || y >= LCD_HEIGHT / 8) {
    return; // 超出范围
  }

  LCD_WriteCommand(LCD_SETXADDR | x);
  LCD_WriteCommand(LCD_SETYADDR | y);
}

/**
 * @brief  写入单个字符到LCD
 * @param  ch: 要写入的字符
 * @retval None
 */
void LCD_WriteChar(char ch, uint8_t reverse)
{
    // 检查字符是否在可显示范围内
    if(ch < 32 || ch > 127) return;
    
    // 从字符字模数组中获取字符点阵数据
    const uint8_t* font = LCD5110_Font[ch - 32]; // 直接访问数组元素
    
    for(uint8_t i = 0; i < 6; i++) {
        if(reverse) {
            LCD_WriteData(~font[i]); // 反转显示
        } else {
            LCD_WriteData(font[i]);
        }

    }
}

/**
 * @brief  写入字符串到LCD
 * @param  str: 要写入的字符串
 * @retval None
 */
void LCD_WriteString(const char* str, uint8_t reverse)
{
    while(*str) {
        LCD_WriteChar(*str++, reverse);
    }
}

/**
 * @brief  写入二进制数值到LCD(以二进制格式显示)
 * @param  value: 要显示的8位二进制值
 * @retval None
 */
void LCD_WriteBinary(uint8_t value)
{
    char binary_str[9]; // 8位二进制 + '\0'
    for(int i = 7; i >= 0; i--) {
        binary_str[7-i] = ((value >> i) & 0x01) ? '1' : '0';
    }
    binary_str[8] = '\0';
    LCD_WriteString(binary_str,0);
}

/**
 * @brief  设置LCD对比度
 * @param  contrast: 对比度值 (0-0x7F)
 * @retval None
 */
void LCD_SetContrast(uint8_t contrast)
{
    // 进入扩展命令模式
    LCD_WriteCommand(LCD_FUNCTIONSET_EXTENDED);
    
    // 设置对比度 (VOP)
    LCD_WriteCommand(LCD_VOP | (contrast & 0x7F));
    
    // 回到基本命令模式
    LCD_WriteCommand(LCD_FUNCTIONSET_BASIC);
}

/**
 * @brief  反转显示
 * @param  invert: 1为反转显示,0为正常显示
 * @retval None
 */
void LCD_InvertDisplay(uint8_t invert)
{
    LCD_WriteCommand(invert ? LCD_DISPLAYCONTROL_INVERSE : LCD_DISPLAYCONTROL_NORMAL);
}

/**
 * @brief  打开LCD显示
 * @retval None
 */
void LCD_DisplayOn(void)
{
    LCD_WriteCommand(LCD_DISPLAYCONTROL_NORMAL);
}

/**
 * @brief  关闭LCD显示
 * @retval None
 */
void LCD_DisplayOff(void)
{
    LCD_WriteCommand(LCD_DISPLAYCONTROL_BLANK);
}

/**
 * @brief  绘制单个像素点
 * @param  x: X坐标 (0-83)
 * @param  y: Y坐标 (0-47)
 * @param  color: 像素颜色 (0-清除, 1-绘制)
 * @retval None
 */
void LCD_DrawPixel(uint8_t x, uint8_t y, uint8_t color)
{
    if(x >= LCD_WIDTH || y >= LCD_HEIGHT) return;
    
    uint16_t index = x + (y / 8) * LCD_WIDTH;  // 计算字节在缓冲区中的位置
    uint8_t bit = y % 8;                       // 计算在字节中的位位置
    
    if(color) {
        LCD_Buffer[index] |= (1 << bit);       // 设置位
    } else {
        LCD_Buffer[index] &= ~(1 << bit);      // 清除位
    }
    
    // 更新显示
    LCD_Update();
}

/**
 * @brief  绘制线段
 * @param  x1, y1: 起始坐标
 * @param  x2, y2: 终止坐标
 * @param  color: 线条颜色 (0-清除, 1-绘制)
 * @retval None
 */
void LCD_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color)
{
    int dx = x2 - x1;
    int dy = y2 - y1;
    int steps;
    
    // 确定步数
    if(abs(dx) > abs(dy)) {
        steps = abs(dx);
    } else {
        steps = abs(dy);
    }
    
    float x_increment = (float)dx / steps;
    float y_increment = (float)dy / steps;
    
    float x = x1;
    float y = y1;
    
    for(int i = 0; i <= steps; i++) {
        LCD_DrawPixel((uint8_t)x, (uint8_t)y, color);
        x += x_increment;
        y += y_increment;
    }
}

/**
 * @brief  绘制矩形框
 * @param  x1, y1: 矩形左上角坐标
 * @param  x2, y2: 矩形右下角坐标
 * @param  color: 矩形颜色 (0-清除, 1-绘制)
 * @retval None
 */
void LCD_DrawRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color)
{
    // 绘制四条边
    LCD_DrawLine(x1, y1, x2, y1, color); // 上边
    LCD_DrawLine(x1, y2, x2, y2, color); // 下边
    LCD_DrawLine(x1, y1, x1, y2, color); // 左边
    LCD_DrawLine(x2, y1, x2, y2, color); // 右边
}

/**
 * @brief  绘制填充矩形
 * @param  x1, y1: 矩形左上角坐标
 * @param  x2, y2: 矩形右下角坐标
 * @param  color: 矩形颜色 (0-清除, 1-绘制)
 * @retval None
 */
void LCD_DrawFilledRectangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color)
{
    // 确保坐标顺序正确
    if(x1 > x2) { uint8_t temp = x1; x1 = x2; x2 = temp; }
    if(y1 > y2) { uint8_t temp = y1; y1 = y2; y2 = temp; }
    
    // 绘制每一行
    for(uint8_t y = y1; y <= y2; y++) {
        LCD_DrawLine(x1, y, x2, y, color);
    }
}

/**
 * @brief  绘制圆形
 * @param  x0, y0: 圆心坐标
 * @param  radius: 半径
 * @param  color: 圆形颜色 (0-清除, 1-绘制)
 * @retval None
 */
void LCD_DrawCircle(uint8_t x0, uint8_t y0, uint8_t radius, uint8_t color)
{
    int x = 0;
    int y = radius;
    int d = 3 - 2 * radius;
    
    // 如果半径为0,只绘制一个点
    if(radius == 0) {
        LCD_DrawPixel(x0, y0, color);
        return;
    }
    
    // 绘制初始点
    LCD_DrawPixel(x0 + x, y0 + y, color);
    LCD_DrawPixel(x0 - x, y0 + y, color);
    LCD_DrawPixel(x0 + x, y0 - y, color);
    LCD_DrawPixel(x0 - x, y0 - y, color);
    LCD_DrawPixel(x0 + y, y0 + x, color);
    LCD_DrawPixel(x0 - y, y0 + x, color);
    LCD_DrawPixel(x0 + y, y0 - x, color);
    LCD_DrawPixel(x0 - y, y0 - x, color);
    
    while(y >= x) {
        x++;
        
        if(d > 0) {
            y--;
            d = d + 4 * (x - y) + 10;
        } else {
            d = d + 4 * x + 6;
        }
        
        LCD_DrawPixel(x0 + x, y0 + y, color);
        LCD_DrawPixel(x0 - x, y0 + y, color);
        LCD_DrawPixel(x0 + x, y0 - y, color);
        LCD_DrawPixel(x0 - x, y0 - y, color);
        LCD_DrawPixel(x0 + y, y0 + x, color);
        LCD_DrawPixel(x0 - y, y0 + x, color);
        LCD_DrawPixel(x0 + y, y0 - x, color);
        LCD_DrawPixel(x0 - y, y0 - x, color);
    }
}

/**
 * @brief  绘制位图
 * @param  x, y: 位图绘制起始坐标
 * @param  bitmap: 位图数据指针
 * @param  w: 位图宽度
 * @param  h: 位图高度
 * @retval None
 */
void LCD_DrawBitmap(uint8_t x, uint8_t y, const uint8_t* bitmap, uint8_t w, uint8_t h)
{
    uint8_t byte_width = (w + 7) / 8; // 计算位图每行占用的字节数
    
    for(uint8_t i = 0; i < h; i++) {
        for(uint8_t j = 0; j < w; j++) {
            uint8_t byte_index = i * byte_width + j / 8;
            uint8_t bit_index = 7 - (j % 8);
            uint8_t pixel = (bitmap[byte_index] >> bit_index) & 1;
            
            LCD_DrawPixel(x + j, y + i, pixel);
        }
    }
}

/* USER CODE BEGIN 1 */
// ... existing code ...
/* Private variables ---------------------------------------------------------*/

// ... existing code ...
/* USER CODE END 1 */