新手村中打满级大BOSS 2025-08-25 17:07 采纳率: 0%
浏览 13

LED屏幕如何去重影,鬼影


#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "Delay.h"
#include "LED_Driver.h"
#include "stm32f10x_it.h"
#include "Handlers.h"
#include "hanzi.h"
/*

屏幕大小为16*64,0-15行,0-63列

一、屏幕数据流向,箭头方向的左下角,到右上角

二、点阵字模要求
1,点阵字模软件:PCtoLCD2002
2,字模选项
(1)点阵格式:阴码
(2)取模方式:逐行式
(3)每行显示数据:点阵16,索引16
(4)取模走向:逆向
(5)输出进制:16进制
(6)格式:C51
            
*/
#define HC245_A_PORT GPIOA
#define HC245_B_PORT GPIOB
#define HC245_C_PORT GPIOC

/*
HC245    
DIR接5V   OE接GND   A->B

HUB12
第一列引脚,从上到下为OE,N,N,N,N,N,N,N
第二列引脚,从上到下为A,B,C,SCK,LAT,R,G,D

*/

#define A_A1_PA0   GPIO_Pin_0
#define B_A2_PA1   GPIO_Pin_1
#define C_A3_PB5   GPIO_Pin_5
#define D_A4_PB4   GPIO_Pin_4
#define OE_A5_PA8  GPIO_Pin_8
#define LAT_A6_PC8 GPIO_Pin_8
#define SCK_A7_PC9 GPIO_Pin_9
#define R_A15_PB8     GPIO_Pin_8
#define G_A16_PC0     GPIO_Pin_0


void HC245_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);
    
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;    
    
    GPIO_InitStructure.GPIO_Pin=A_A1_PA0|B_A2_PA1|OE_A5_PA8;    
  GPIO_Init(HC245_A_PORT,&GPIO_InitStructure);    
    
    GPIO_InitStructure.GPIO_Pin=C_A3_PB5|D_A4_PB4|R_A15_PB8;    
  GPIO_Init(HC245_B_PORT,&GPIO_InitStructure);
    
  GPIO_InitStructure.GPIO_Pin=LAT_A6_PC8|SCK_A7_PC9|G_A16_PC0;    
  GPIO_Init(HC245_C_PORT,&GPIO_InitStructure);

}


// 数据线 - 红色 (R -> PB8)
#define HIGH_HUB12_DR     (GPIOB->BSRR = GPIO_Pin_8)    
#define LOW_HUB12_DR      (GPIOB->BRR  = GPIO_Pin_8)

// 数据线 - 绿色 (G -> PC0)
#define HIGH_HUB12_DG     (GPIOC->BSRR = GPIO_Pin_0)     
#define LOW_HUB12_DG      (GPIOC->BRR  = GPIO_Pin_0)

// 时钟线 (SCK / SH_CP -> PC9)
#define HIGH_HUB12_CLK    (GPIOC->BSRR = GPIO_Pin_9)     
#define LOW_HUB12_CLK     (GPIOC->BRR  = GPIO_Pin_9)

// 锁存线 (LAT / ST_CP -> PC8)
#define HIGH_HUB12_LAT    (GPIOC->BSRR = GPIO_Pin_8)     
#define LOW_HUB12_LAT     (GPIOC->BRR  = GPIO_Pin_8)

// 输出使能 (OE -> PA8, 低电平有效)
#define HIGH_HUB12_OE     (GPIOA->BSRR = GPIO_Pin_8)     
#define LOW_HUB12_OE      (GPIOA->BRR  = GPIO_Pin_8)     

// 行地址线 A (A -> PA0)
#define HIGH_HUB12_A      (GPIOA->BSRR = GPIO_Pin_0)
#define LOW_HUB12_A       (GPIOA->BRR  = GPIO_Pin_0)

// 行地址线 B (B -> PA1)
#define HIGH_HUB12_B      (GPIOA->BSRR = GPIO_Pin_1)
#define LOW_HUB12_B       (GPIOA->BRR  = GPIO_Pin_1)

// 行地址线 C (C -> PB5)
#define HIGH_HUB12_C      (GPIOB->BSRR = GPIO_Pin_5)
#define LOW_HUB12_C       (GPIOB->BRR  = GPIO_Pin_5)

// 行地址线 D (D -> PB4)
#define HIGH_HUB12_D      (GPIOB->BSRR = GPIO_Pin_4)
#define LOW_HUB12_D       (GPIOB->BRR  = GPIO_Pin_4)

/* 显示汉字总数 */
#define WORD_NUM 4
/* 每行显示的字节数 */
#define SCAN_NUM (8 * WORD_NUM)


u8 Chinese[4][32] =
{
    { 
0x00,0x01,0x00,0x01,0x3F,0x01,0x20,0x3F,0xA0,0x20,0x92,0x10,0x54,0x02,0x28,0x02,
0x08,0x02,0x14,0x05,0x24,0x05,0xA2,0x08,0x81,0x08,0x40,0x10,0x20,0x20,0x10,0x40,
    },

    {

0x00,0x00,0x04,0x01,0xC8,0x3C,0x48,0x24,0x40,0x24,0x40,0x24,0x4F,0x24,0x48,0x24,
0x48,0x24,0x48,0x2D,0xC8,0x14,0x48,0x04,0x08,0x04,0x14,0x04,0xE2,0x7F,0x00,0x00,
    },

    {
0x80,0x00,0x84,0x10,0x88,0x10,0x90,0x08,0x90,0x04,0x80,0x00,0xFF,0x7F,0x20,0x02,
0x20,0x02,0x20,0x02,0x20,0x02,0x10,0x42,0x10,0x42,0x08,0x42,0x04,0x7C,0x03,0x00,
    },

    {
0x10,0x01,0x10,0x01,0x10,0x01,0x92,0x7F,0x92,0x02,0x52,0x04,0x32,0x04,0x12,0x00,
0x92,0x3F,0x92,0x24,0x92,0x24,0x92,0x24,0x92,0x24,0x90,0x3F,0x90,0x20,0x10,0x00,
    },
};



uint8_t dispram[128] = {0}; 
uint8_t copy_dispram[128]={0};

void hub12DataSerialInput(uint8_t data){
                uint16_t         i;
                for( i = 0; i < 8; i++){
                                if(data & 0x80){
                                        
                                        LOW_HUB12_DR;
                                }else{
                                       HIGH_HUB12_DR;
                                }
                                                                Delay_us(1); 
                                LOW_HUB12_CLK;
                                                                Delay_us(1);     
                                HIGH_HUB12_CLK;
                                                                Delay_us(1); 
                                data        = data << 1;
                }
}


// 行选择 (1/4扫描)
void  hub12SelectRows(uint8_t rows){
    LOW_HUB12_C;
    LOW_HUB12_D;
                switch(rows){
                        case 0:
                                        LOW_HUB12_A;
                                        LOW_HUB12_B;
                                        break;
                        case 1:
                                        HIGH_HUB12_A;
                                        LOW_HUB12_B;
                                        break;                                               
                        case 2:
                                        LOW_HUB12_A;
                                        HIGH_HUB12_B;
                                        break;                                               
                        case 3:
                                        HIGH_HUB12_A;
                                        HIGH_HUB12_B;
                                        break;                       
                        default:
                                        break;
                }
}




// 行选择 (1/4扫描)
void hub12Display(uint16_t bright,uint8_t *buffer)
{
    for(u8 s=0; s<4; s++){  // 4个行组(1/4扫描)
            Delay_us(2);
              HIGH_HUB12_OE; 
            Delay_us(1);            
        hub12SelectRows(s);
                        Delay_us(2);
        LOW_HUB12_LAT;
                        Delay_us(2);
      for(int i=0; i<SCAN_NUM; i++) {
            hub12DataSerialInput(buffer[s*SCAN_NUM + i]); //128字节
        }
           
        HIGH_HUB12_LAT;
                Delay_us(2); 
        LOW_HUB12_OE;
        Delay_us(bright);
        HIGH_HUB12_OE;    
  
    }
}

//高低字节交换,0b10110000 → 0b00001101
u8 SwapByte(u8 data)
{
    data=(data<<4)|(data>>4);
    data=((data<<2)&0xcc)|((data>>2)&0x33);
    data=((data<<1)&0xaa)|((data>>1)&0x55);
    return data;
}
void WriteScreen(u8 locatin, u8 *text)
{
    u8 i; // 原始的扫描行组
    u8 j; // 显示缓冲区的标号
    u8 k;
    u8 buffer[32];
    
    const u8 row_map[4] = {1,2,3, 0}; //行扫描修正

    /* 字模处理 */
    for(k = 0; k < 32; k++) buffer[k] = text[31 - k];   //从左到右,从上到下依次显示
    for(k = 0; k < 32; k++) buffer[k] =SwapByte(buffer[k]);
        
        
    /* 写缓冲区 */
    for(i = 0; i < 4; i++) // 4组扫描行 (i=0,1,2,3 对应 4/1,4/2,4/3,4/4)
    {
        // 通过查找表,获取用于行修正计算的 "new_i"
        u8 new_i = row_map[i]; 

        for(j = (SCAN_NUM * i); j < SCAN_NUM * (i + 1); j++)
        {
            if((j >= ((SCAN_NUM * i) + 8 * locatin)) && (j <= ((SCAN_NUM * i + 3) + 8 * locatin)))//0-3列
            {
                // 使用 new_i 来计算行修正项,但 j 的范围仍基于原始的 i
                dispram[j] = buffer[(7 - 2 * new_i) + 8 * (j - 8 * locatin - SCAN_NUM * i)]; //右边字节                
            }

            if((j >= ((SCAN_NUM * i + 4) + 8 * locatin)) && (j <= ((SCAN_NUM * i + 7) + 8 * locatin)))//4-7列
            {
                dispram[j] = buffer[(6 - 2 * new_i) + 8 * (j - 8 * locatin - SCAN_NUM * i - 4)]; //左边字节
            }
        }
    }
        
}


//静态显示汉字
void StaticDisplay(uint8_t font[4][32])
{
    memset(dispram, 0, sizeof(dispram));  // 清空显存
    //locatin,0-3,表示第几个汉字的位置
    for(u8 locatin=0;locatin<4;locatin++)
    {
    WriteScreen(locatin, font[locatin]);
    }

}




u8 display_buffer[16][10]; // 扩展为10字节,容纳5个汉字
u8 hanzi_data[4][32]; // 4个汉字的缓冲区

// 初始化显示缓冲区
void InitDisplayBuffer() {
    memset(display_buffer, 0, sizeof(display_buffer));
    // 初始化前4个汉字
    for(u8 i = 0; i < 4; i++) {
        for(u8 row = 0; row < 16; row++) {
            display_buffer[row][i*2] = Chinese[i][row*2];
            display_buffer[row][i*2+1] = Chinese[i][row*2+1];
        }
    }
}

void ScrollTextLeft(u8 Chinese[][32], u8 hanzi_num) {
    static u8 start_index = 0;
    static u8 shift_count = 0;
    u8 next_index = 4 % hanzi_num; // 下一个汉字索引
    
    // 移动一列
    for (u8 row = 0; row < 16; row++) {
        // 将当前字节左移1位,然后从下一个字节的最高位取1位,放到当前字节的最低位
        for (u8 col = 0; col < 9; col++) {
            display_buffer[row][col] = (display_buffer[row][col] << 1) |  
                                      ((display_buffer[row][col+1] & 0x80) ? 1 : 0);
        }
        //将第一个字节左移8位到高8位,将第二个字节放到低8位
        u16 row_data = (Chinese[next_index][row*2] << 8) | Chinese[next_index][row*2+1];
                //从16位数据中按列提取相应的位
        u8 bit_value = (row_data >> (15 - shift_count)) & 0x01;
                //将取到的位放到显示缓冲区最右侧字节的最低位
        display_buffer[row][9] = (display_buffer[row][9] << 1) | bit_value;
    }
    
    // 每次循环提取一个完整的汉字送到 WriteScreen 函数进行显示处理。
    for (u8 i = 0; i < 4; i++) {
        for (u8 row = 0; row < 16; row++) {
            hanzi_data[i][row*2] = display_buffer[row][i*2];
            hanzi_data[i][row*2+1] = display_buffer[row][i*2+1];
        }
        WriteScreen(i, hanzi_data[i]);
    }
    
    __disable_irq();
        memset(copy_dispram, 0, sizeof(copy_dispram));
    memcpy(copy_dispram, dispram, sizeof(dispram));
    __enable_irq();
    
    // 更新计数和索引
    shift_count++;
    if (shift_count >= 16) {
        shift_count = 0;
        start_index = (start_index + 1) % hanzi_num;
        next_index = (start_index + 4) % hanzi_num;
        
        // 将下一个汉字加载到缓冲区
        for(u8 row = 0; row < 16; row++) {
            display_buffer[row][8] = Chinese[next_index][row*2];
            display_buffer[row][9] = Chinese[next_index][row*2+1];
        }
    }
    
    Delay_ms(30);
}


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Serial.h"
#include "LED_Driver.h"
#include "ChineseFont.h"
#include "flash.h"
#include "hanzi.h"

volatile uint8_t TYPE=2;

//TYPE=1静态显示
void Static_Type(void)
{
    if(TYPE)
    {
        StaticDisplay(Chinese);
    }
}

int main(void)
{

    Serial_Init();    

    HC245_Init();
    FLASH_Init();
    printf("init\n");
    
//TYPE=1静态显示
Static_Type();

InitDisplayBuffer();
 while (1) {
      hub12Display(300,copy_dispram);
     switch(TYPE)
{
      case 2:
        ScrollTextLeft(Chinese,4);
        //Delay_ms(30);
        break;
        case 3:

        break;
        
}    

    }
 
}

当TYPE=1的时候,因为只往dispram写了一次数据,所以dispram内容是固定的,所以在main中循环hub12Display(300,copy_dispram);的时候,是一直在读取dispram,所以LED屏幕在显示的时候,是稳定的u8 Chinese[4][32] 中的四个汉字,没有鬼影,重叠的现象。

当TYPE=2的时候,ScrollTextLeft循环,然后循环执行WriteScreen(i, hanzi_data);计算出dispram,所以dispram在不断地被写入,数据在一直更新,所以再调用hub12Display写LED屏的时候,屏幕上面有鬼影,重影。
在void ScrollTextLeft(u8 Chinese[][32], u8 hanzi_num)中,添加了双重缓存区,
__disable_irq();
memset(copy_dispram, 0, sizeof(copy_dispram));
memcpy(copy_dispram, dispram, sizeof(dispram));
__enable_irq();,也没有用,各位,有没有什么办法?

  • 写回答

4条回答 默认 最新

  • 檀越@新空间 2025-08-25 17:09
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    在使用LED屏幕时,重影(鬼影) 是一个常见的问题,尤其是在动态显示或刷新频率不够高的情况下。这可能是由于以下原因导致的:

    1. 刷新率不足:如果LED屏幕刷新速度太慢,人眼会看到残留的图像。
    2. 数据传输不稳定:信号线(如SCK、LAT、OE等)没有正确控制,可能导致数据未及时更新。
    3. 行扫描顺序错误:如果行地址控制逻辑有误,会导致同一行被多次写入。
    4. 电源或接地不良:电压波动或接地不稳也可能造成图像异常。

    一、解决LED屏幕重影(鬼影)的方法

    1. 提高刷新率

    • 确保每帧画面刷新足够快(建议60Hz以上),避免人眼感知到残影。
    • 在代码中增加适当的延时控制,确保每一行数据完全写入后才进行下一行。

    2. 检查并优化数据传输逻辑

    • 确保SCK(时钟)LAT(锁存) 的控制逻辑正确。
    • 时钟脉冲必须稳定且与数据同步,否则数据可能错位。
    • 锁存信号应紧跟数据传输结束,以确保数据正确锁存。

    3. 确保行地址控制逻辑正确

    • 行地址(A, B, C, D)的组合必须按照正确的顺序选择当前行。
    • 如果行地址控制逻辑混乱,可能会导致多行同时亮起,形成重影。

    4. 优化数据写入顺序

    • 数据写入顺序应严格按照点阵字模的要求(如逐行式、逆向等)。
    • 确保每行数据在写入前,OE(输出使能)已关闭,防止数据未写完就显示。

    5. 增加去重影处理

    • 可以通过延迟控制数据覆盖清屏等方式减少重影。

    二、修改后的代码示例

    以下是基于你提供的代码片段,优化后的hub12DataSerialInput函数和相关控制逻辑,用于减少重影问题:

    void hub12DataSerialInput(uint8_t data) {
        uint16_t i;
        for (i = 0; i < 8; i++) {
            if (data & 0x80) {
                LOW_HUB12_DR;   // 数据为1,设置DR为低电平
            } else {
                HIGH_HUB12_DR;  // 数据为0,设置DR为高电平
            }
            Delay_us(1);        // 微秒级延时,确保数据稳定
            HIGH_HUB12_CLK;     // 时钟上升沿
            Delay_us(1);
            LOW_HUB12_CLK;      // 时钟下降沿
            data <<= 1;         // 左移一位,准备下一个位
        }
    }
    
    // 写入一行数据
    void writeLine(uint8_t *lineData, uint8_t lineNum) {
        // 设置行地址
        switch (lineNum) {
            case 0:  LOW_HUB12_A; LOW_HUB12_B; LOW_HUB12_C; LOW_HUB12_D; break;
            case 1:  HIGH_HUB12_A; LOW_HUB12_B; LOW_HUB12_C; LOW_HUB12_D; break;
            case 2:  LOW_HUB12_A; HIGH_HUB12_B; LOW_HUB12_C; LOW_HUB12_D; break;
            case 3:  HIGH_HUB12_A; HIGH_HUB12_B; LOW_HUB12_C; LOW_HUB12_D; break;
            case 4:  LOW_HUB12_A; LOW_HUB12_B; HIGH_HUB12_C; LOW_HUB12_D; break;
            case 5:  HIGH_HUB12_A; LOW_HUB12_B; HIGH_HUB12_C; LOW_HUB12_D; break;
            case 6:  LOW_HUB12_A; HIGH_HUB12_B; HIGH_HUB12_C; LOW_HUB12_D; break;
            case 7:  HIGH_HUB12_A; HIGH_HUB12_B; HIGH_HUB12_C; LOW_HUB12_D; break;
            case 8:  LOW_HUB12_A; LOW_HUB12_B; LOW_HUB12_C; HIGH_HUB12_D; break;
            case 9:  HIGH_HUB12_A; LOW_HUB12_B; LOW_HUB12_C; HIGH_HUB12_D; break;
            case 10: LOW_HUB12_A; HIGH_HUB12_B; LOW_HUB12_C; HIGH_HUB12_D; break;
            case 11: HIGH_HUB12_A; HIGH_HUB12_B; LOW_HUB12_C; HIGH_HUB12_D; break;
            case 12: LOW_HUB12_A; LOW_HUB12_B; HIGH_HUB12_C; HIGH_HUB12_D; break;
            case 13: HIGH_HUB12_A; LOW_HUB12_B; HIGH_HUB12_C; HIGH_HUB12_D; break;
            case 14: LOW_HUB12_A; HIGH_HUB12_B; HIGH_HUB12_C; HIGH_HUB12_D; break;
            case 15: HIGH_HUB12_A; HIGH_HUB12_B; HIGH_HUB12_C; HIGH_HUB12_D; break;
        }
    
        // 使能输出
        LOW_HUB12_OE;
    
        // 写入数据
        for (uint8_t i = 0; i < 8; i++) {
            hub12DataSerialInput(lineData[i]);
        }
    
        // 锁存数据
        HIGH_HUB12_LAT;
        Delay_us(1);
        LOW_HUB12_LAT;
    
        // 关闭输出
        HIGH_HUB12_OE;
    }
    

    三、其他建议

    1. 使用双缓冲技术

    • 在显示过程中,使用两个缓冲区(dispramcopy_dispram),每次只更新一个缓冲区,再切换显示,避免显示过程中的数据冲突。

    2. 增加延时控制

    • 在写入每一行数据后,加入适当延时(如 Delay_ms(1)),确保数据完全写入后再进行下一行操作。

    3. 优化显示逻辑

    • 使用逐行扫描的方式,确保每行数据在写入后立即锁存,并关闭输出,避免数据重叠。

    四、总结

    重点部分如下:

    1. 提高刷新率:确保每帧刷新率高于60Hz。
    2. 优化数据传输逻辑:确保时钟和锁存信号正确,数据稳定。
    3. 检查行地址控制:确保每行数据只写入一次。
    4. 使用双缓冲技术:避免数据冲突,提升显示稳定性。
    5. 加入延时控制:确保数据写入完成后再进行下一行操作。

    如果你需要进一步的调试工具(如示波器检测信号波形)或更详细的代码优化,请继续提问!

    评论

报告相同问题?

问题事件

  • 创建了问题 8月25日