问答

汇集网友智慧,解决技术难题

21ic问答首页 - 硬件SPI驱动OLED出现奇怪的BUG

STM32 STM32F1 ST 嵌入式

硬件SPI驱动OLED出现奇怪的BUG

Pohlen2023-05-06
设备是0.96寸OLED屏幕,7脚。商家给的资料是用软件模拟SPI通信,没有出现问题。想尝试自己啃手册,写硬件SPI驱动OLED。遇到各种奇奇怪怪的问题。
问题一
代码第一次烧录进STM32,显示内容的位置正常。但是,当设备断电再重新上电,OLED会出现字符串向左移一位,最靠前的一个字符会被挤到最后面。但是如果重新上电后,用按钮触发显示的新内容位置却是正确的。这种问题有人遇到过吗?怎么排查问题?
问题二
设置SPI波特率的问题,如果分频过多,16到256这个范围都不行,会出现花屏。只有分频值在2到8之间,才能正常点亮OLED。请问,我在OLED手册里找关键词Baud Rate,没有相关参数,这部分怎么设置才是正确的?
回答 +关注 15
1521人浏览 5人回答问题 分享 举报
5 个回答
  • 终于解决的这个奇怪的BUG,敲代码不认真 真的会害死人的!!!我之前一直以为是因为修改了硬件SPI驱动后才是出现的问题,所以盯着写入数据的部分不放。
    结果经过老哥提醒了以下,回去跑了之前没有改为硬件SPI驱动OLED的代码,结果也出现相同的情况。于是就锁定到了一段代码上。
    其实,在改为硬件驱动时,我已经修改了显示数字的函数,因为之前的函数要传输入数字多少位的参数。在函数中添加一个循环,遍历出整数多少位。
    for循环里uint8_t i = 0, j ;其中j忘记初始化了,这个罪魁祸首!下面是源代码

    void OLED_ShowNum2(uint8_t Line, uint8_t Column, u32 num)
    {                
            //写一个不用输入数字长短,就能显示数字的版本
            uint8_t flag = 0, temp;
           
            for(uint8_t i = 0,j ;i < 10; i++ ){   //j忘记初始化
                    temp = num / oled_pow(10, 10 - i - 1) % 10;
                    if(flag == 0){
                            if(temp == 0) continue;
                            else flag = 1;
                    }
                    if(flag == 1){
                            OLED_ShowChar(Line, Column + j, temp + '0');
                            j++;
                    }
            }
    }


  • 商家给的驱动代码贴上来看看
    Pohlen 2023-5-9 23:17 回复TA
    感谢兄弟提醒了我,我回去跑了之前代码,也出现了类似的情况。 void OLED_ShowNum2(u8 Line, u8 Column, u32 num) { //写一个不用输入数字长短,就能显示数字的版本 uint8_t flag = 0, temp; for(uint8_t i = 0,j = 0;i < 10; i++ ){ //j忘记初始化 temp = num / oled_pow(10, 10 - i - 1) % 10; if(flag == 0){ if(temp == 0) continue; else flag = 1; } if(flag == 1){ OLED_ShowChar(Line, Column + j, temp + '0'); j++; } } }  
  • void OLED_CheckStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG, uint8_t status)
    {
        uint32_t Timeout;
        Timeout = 10000;
        while (SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == status)
        {
            Timeout --;
            if (Timeout == 0)
            {
            //在实际项目中,当然不式直接退出就行了,应该做一些相应的错误处理操作
                break;
            }
        }
    }

    //向SSD1106写入一个字节。
    //dat:要写入的数据/命令
    //cmd:数据/命令标志 0,表示命令;1,表示数据;
    void OLED_WR_Byte(uint8_t dat, uint8_t cmd)
    {       
             //CS低电平有效
            OLED_CS_Clr();
            if(cmd) //命令0还是数据1
              OLED_DC_Set();
            else
              OLED_DC_Clr();
           
    //        u8 i=0; // 测试
    //        while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != RESET)  //等待BSY为0,发送下一个数据
    //        {       
    //                i++;
    //                if(i>200) break;
    //        }
           
            //OLED_CheckStatus(SPI1, SPI_I2S_FLAG_TXE, RESET);//放在这上面,屏幕点不亮
            //OLED_CheckStatus(SPI1, SPI_I2S_FLAG_BSY, RESET);//放在这上面,屏幕点不亮
           
            SPI_I2S_SendData(SPI1, dat);//不太明白发送的是uint8_t数据,但是函数用uint16_t类型接收
           
            OLED_CheckStatus(SPI1, SPI_I2S_FLAG_BSY, RESET);//只有放下面这两个才生效
            //OLED_CheckStatus(SPI1, SPI_I2S_FLAG_TXE, RESET);
           
            OLED_DC_Set();
            OLED_CS_Set();
    }


    void OLED_SPI_Init(void)
    {        
            GPIO_InitTypeDef  GPIO_InitStructure;
           
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);//使能A端口时钟

            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
            GPIO_Init(GPIOA, &GPIO_InitStructure);       
           
            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//初始化CS DC和RES  
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
            GPIO_Init(GPIOA, &GPIO_InitStructure);          
            GPIO_SetBits(GPIOA, GPIO_Pin_1|GPIO_Pin_2);
           
            SPI_InitTypeDef SPI_InitStruct;
            SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;//2到8可以,16到256就不行了
            SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//CPHA=0,CPOL=0 或 CPHA=1,CPOL=0 试了下都可以
            SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
            SPI_InitStruct.SPI_CRCPolynomial = 0;//看了好多代码都填7,不明白
            SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
            SPI_InitStruct.SPI_Direction = SPI_Direction_1Line_Tx;
            SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//看时序图,应该是高位先行
            SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
            SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//手动
           
            SPI_Init(SPI1, &SPI_InitStruct);       
            SPI_Cmd(SPI1, ENABLE);
            //SPI_SSOutputCmd(SPI1, ENABLE);
           
            //Delay_ms(100);
           
              OLED_RST_Set();
            Delay_ms(100);
            OLED_RST_Clr();
            Delay_ms(200);
            OLED_RST_Set();
                                              
            OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
            OLED_WR_Byte(0x02,OLED_CMD);//---set low column address
            OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
            OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
            OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
            OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
            OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
            OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
            OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
            OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
            OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
            OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
            OLED_WR_Byte(0X7F,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
            OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset        Shift Mapping RAM Counter (0x00~0x3F)
            OLED_WR_Byte(0x00,OLED_CMD);//-not offset
            OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
            OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
            OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
            OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
            OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
            OLED_WR_Byte(0x12,OLED_CMD);
            OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
            OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
            OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
            OLED_WR_Byte(0x02,OLED_CMD);//
            OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
            OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
            OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
            OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
            OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
           
            //OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/
            OLED_Clear();
            Delay_ms(100);
            OLED_Set_Pos(0,0);        
    }  
  • 我遇到过OLED断电情况了,如果单片机给它发信号,它通电后会出现乱码、速度慢等问题,要保证先给OLED正常通电后单片机再操作OLED屏
    Pohlen 2023-5-7 10:25 回复TA
    您的意思是在初始化OLED之前先延时一段时间,对吗?但我加了1000ms延时后还是会出现重新上电内容右移现象 
  • SPI模式不对,有两个配置,一个是CLK空闲是低电平还是高电平,还有一个是第一个时钟上升沿还是下降沿,
    有示波器没?分析下就行了。
    Pohlen 2023-5-7 10:47 回复TA
    @Pohlen :嘴瓢了,是左移一位 
    Pohlen 2023-5-7 10:38 回复TA
    而且目前手上没有示波器 ,头大 Π _ Π 
    Pohlen 2023-5-7 10:22 回复TA
    其实CPHA和CPOL的配置我都试了一遍。闲置高电平有效,低电平无效。但是CPHA不管是1还是0,都能驱动OLED,而且还都会出现重新上电后显示内容向右移。这个地方我也很疑惑,按照OLED手册上的时序图,需要CLK上升沿有效,闲置高电平。 

您需要登录后才可以回复 登录 | 注册