Ai_Std_25 发表于 2025-7-6 11:13:59

学习

mechray 发表于 2025-7-6 13:12:27

运行示例代码之后发现有行偏移情况如下

经检查代码,发现在屏幕刷新函数中,设置行起始地址的代码应为0x40,这里写成了0xB0。提示官方修订后再上传。





stc-xuewei 发表于 2025-7-6 17:15:18

用AiCube (V100S) 设置串口时,显示的是波特率列表的序号,而不是具体的波特率,如9600、115200,生成的工程,编译后串口异常。


mechray 发表于 2025-7-6 20:29:42

另外,OLED_I2C_DMA的Demo运行不正常,有时候黑屏,有时候花屏(但非DMA版本的Demo代码正常)。不知是什么原因。

mechray 发表于 2025-7-7 21:19:09

mechray 发表于 2025-7-6 20:29
另外,OLED_I2C_DMA的Demo运行不正常,有时候黑屏,有时候花屏(但非DMA版本的Demo代码正常)。不知是什么 ...
上面的问题还请看看能否帮忙,卡在这里两天了,一直没弄明白。附上代码,感谢。

//<<AICUBE_USER_HEADER_REMARK_BEGIN>>
////////////////////////////////////////
// 在此添加用户文件头说明信息
// 文件名称: i2c.c
// 文件描述:
// 文件版本: V1.0
// 修改记录:
//   1. (2025-07-06) 创建文件
////////////////////////////////////////
//<<AICUBE_USER_HEADER_REMARK_END>>


#include "config.h"


//<<AICUBE_USER_INCLUDE_BEGIN>>
// 在此添加用户头文件包含
//<<AICUBE_USER_INCLUDE_END>>


//<<AICUBE_USER_GLOBAL_DEFINE_BEGIN>>
// 在此添加用户全局变量定义、用户宏定义以及函数声明
#define USE_DMA

BOOLfI2CDMABusy=0;
//<<AICUBE_USER_GLOBAL_DEFINE_END>>


uint8_t xdata pu8I2CDMATxBuffer;//I2C DMA发送缓冲区数组

////////////////////////////////////////
// I2C初始化函数
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void I2C_Init(void)
{
    I2C_SwitchP3332();                  //选择I2C数据口: SCL(P3.2), SDA(P3.3)

    I2C_MasterMode();                   //设置I2C为主机模式
    I2C_SetClockDivider(2);             //设置I2C为主机模式时钟

    I2C_Enable();                     //使能I2C功能

    //DMA_I2C_DisableDMA();                //使能I2C DMA
    DMA_I2C_SetDMAAmount(1025);         //使能I2C DMA发送/接收总字节数
    DMA_I2C_SetTxAmount(1025);          //设置I2C DMA发送总字节数
    DMA_I2C_SetTxAddress(pu8I2CDMATxBuffer); //设置I2C DMA发送缓冲区地址
    DMA_I2C_SetInterval(10);             //设置I2C DMA发送/接收字节间隔时间(系统时钟)
    DMA_I2C_ClearTxFlag();            //清除I2C 发送DMA中断标志
    DMA_I2C_SetTxBusPriority(3);      //设置总线访问为最低优先级
    DMA_I2C_SetTxIntPriority(3);      //设置中断为最低优先级
    DMA_I2C_EnableTxInt();            //使能I2C DMA发送中断
    DMA_I2C_EnableTx();               //使能I2C DMA发送功能
//DMA_I2C_TriggerTx();                //触发I2C DMA发送

    //<<AICUBE_USER_I2C_INITIAL_BEGIN>>
    // 在此添加用户初始化代码
        fI2CDMABusy = 0;
        DMA_I2CT_STA = 0x00;
    //<<AICUBE_USER_I2C_INITIAL_END>>
}

////////////////////////////////////////
// 主机模式等待命令完成
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void I2C_MasterWait(void)
{
    while (!I2C_CheckMasterFlag());   //等待完成标志
    I2C_ClearMasterFlag();            //清除完成标志
    I2C_Idle();                         //恢复IDLE状态
}

////////////////////////////////////////
// 主机模式发送起始信号
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void I2C_MasterStart(void)
{
    I2C_Start();                        //触发主机模式起始命令
    I2C_MasterWait();                   //等待命令完成
}

////////////////////////////////////////
// 主机模式发送停止信号
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void I2C_MasterStop(void)
{
    I2C_Stop();                         //触发主机模式停止命令
    I2C_MasterWait();                   //等待命令完成
}

////////////////////////////////////////
// 主机模式发送1字节数据
// 入口参数: dat (待发送的字节数据)
// 函数返回: 0   (接收的应答信号为ACK)
//         1   (接收的应答信号为NAK)
////////////////////////////////////////
BOOL I2C_MasterSendByte(uint8_t dat)
{
    I2C_WriteData(dat);               //将数据写入I2C数据寄存器
    I2C_SendData();                     //触发主机模式写数据命令
    I2C_MasterWait();                   //等待命令完成
    I2C_RecvACK();                      //触发主机模式接收应答命令
    I2C_MasterWait();                   //等待命令完成

    return I2C_MasterReadACK();         //读取并返回应答信号
}

////////////////////////////////////////
// 主机模式接收1字节数据
// 入口参数: ack (待发送的应答信号)
// 函数返回:   (接收的字节数据)
////////////////////////////////////////
uint8_t I2C_MasterReadByte(BOOL ack)
{
    uint8_t dat;

    I2C_RecvData();                     //触发主机模式读数据命令
    I2C_MasterWait();                   //等待命令完成
    dat = I2C_ReadData();               //读取接收的数据
    if (!ack)
      I2C_MasterSetACK();             //将ACK数据写入寄存器
    else
      I2C_MasterSetNAK();             //将NAK数据写入寄存器
    I2C_SendACK();                      //触发主机模式发送应答命令
    I2C_MasterWait();                   //等待命令完成

    return dat;                         //返回接收的数据
}


////////////////////////////////////////
// I2C发送DMA中断服务程序
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void DMA_I2CTX_ISR(void) interrupt DMA_I2CT_VECTOR
{
    //<<AICUBE_USER_I2C_ISR_CODE2_BEGIN>>
    // 在此添加中断函数用户代码
    if (DMA_I2C_CheckTxFlag())          //判断I2C发送DMA中断
    {
      DMA_I2C_ClearTxFlag();          //清除I2C发送DMA中断标志
                I2C_ClearMasterFlag();          //清除I2C中断标志
      I2C_Idle();                     
      DMA_I2C_DisableDMA();         //DMA传输完成后必须关闭DMA功能,否则下次触发会异常
      
      fI2CDMABusy = 0;                //清除DMA传输忙标志
    }
    //<<AICUBE_USER_I2C_ISR_CODE2_END>>
}


//<<AICUBE_USER_FUNCTION_IMPLEMENT_BEGIN>>
// 在此添加用户函数实现代码
////////////////////////////////////////
// 写OLED命令
// 入口参数: cmd (命令序列)
//         len (命令长度)
// 函数返回: 无
////////////////////////////////////////
void OLED_WR_CMD(uint8_t *cmd, uint16_t len)
{
        #ifndef USE_DMA
    I2C_MasterStart();
    I2C_MasterSendByte(0x78);         //从机地址
    I2C_MasterSendByte(0x00);         //写命令 (DC = 0)
        #else
        /*DMA*/
        while (fI2CDMABusy);            //等待上一次传输完成
    pu8I2CDMATxBuffer = 0x78;      //从机地址
    pu8I2CDMATxBuffer = 0x00;      //写命令 (DC = 0)
    #endif
    OLED_SendData(cmd, len);

}

////////////////////////////////////////
// 写OLED数据
// 入口参数: dat (数据序列)
//         len (数据长度)
// 函数返回: 无
////////////////////////////////////////
void OLED_WR_DAT(uint8_t *dat, uint16_t len)
{
        #ifndef USE_DMA
    I2C_MasterStart();
    I2C_MasterSendByte(0x78);                  //从机地址
    I2C_MasterSendByte(0x40);                  //写数据 (DC = 1)
        #else
        /*DMA*/
        while (fI2CDMABusy);            //等待上一次传输完成
    pu8I2CDMATxBuffer = 0x78;      //从机地址
    pu8I2CDMATxBuffer = 0x40;      //写命令 (DC = 0)
    #endif
       
    OLED_SendData(dat, len);

}

////////////////////////////////////////
// 发送OLED数据
// 入口参数: dat (数据序列)
//         len (数据长度)
// 函数返回: 无
////////////////////////////////////////
void OLED_SendData(uint8_t *dat, uint16_t len)
{
        #ifndef USE_DMA
    while (len--)
      I2C_MasterSendByte(*dat++);
    I2C_MasterStop();
       
        #else
        /*DMA*/
        memcpy(&pu8I2CDMATxBuffer, dat, len);

    while (I2C_CheckMasterBusy());      //等待I2C模块退出忙状态
       
    DMA_I2C_EnableDMA();                //发送DMA命令前,先使能I2C DMA功能
    I2C_StartSendDataRecvACK();         //I2C DMA发送数据必须使用09命令

    DMA_I2C_SetDMAAmount(len + 1);      //使能I2C DMA发送/接收总字节数
    DMA_I2C_SetTxAmount(len + 1);       //设置I2C DMA发送总字节数
    DMA_I2C_TriggerTx();                //触发I2C DMA发送

    fI2CDMABusy = 1;                  //设置DMA传输忙标志
        #endif

}
//<<AICUBE_USER_FUNCTION_IMPLEMENT_END>>




大明狐 发表于 2025-7-8 09:36:04

mechray 发表于 2025-7-7 21:19
上面的问题还请看看能否帮忙,卡在这里两天了,一直没弄明白。附上代码,感谢。



在AI8051U上试了下你上传的代码包,用不大于24MHz的频率烧录之后,可以正常显示

832


硬件I2C和DMA驱动OLED的时候,发送速度对现实的影响很大。
比如I2C的时钟分频 I2C_SetClockDivider,多数屏幕用2就可以,但有些屏幕至少需要3。
如果在没有改动官方例程的I2C速度和发送间隔时钟的情况下,出现花屏黑屏,还可以考虑换块屏幕试试。
屏幕模块的线路故障或者芯片故障也会造成花屏黑屏之类问题。

还有就是官方给的演示程序是针对SSD1306之类 支持水平地址模式的屏幕,
如果你用的屏幕芯片是SH1106之类不支持水平地址模式的,
也会出现花屏等现象。


===================================================
另外13楼的两个视频,是我自己手动创建I2C_DMA的OLED工程的全过程
在40MHz之内都可以正常显示
https://www.stcaimcu.com/forum.p ... id=18574&pid=172378

跟着视频的流程创建的工程,都可以正确显示。

15楼的两个效果,也都是在这个基础上进行的操作。

大明狐 发表于 2025-7-8 11:00:45

mechray 发表于 2025-7-6 13:12
运行示例代码之后发现有行偏移情况如下

经检查代码,发现在屏幕刷新函数中,设置行起始地址的代码应为0x40 ...
这个指令没有用错,是对指令产生的误解。


这是数据手册里关于指令 0xB0,和 0x40 的配置表




两个都是“Start”,但是作用是不一样的。

“0xB0~0xB7”对应的是 Page(8个像素点一行),
“0x40~0x7F”对应的是 Line(1个像素点一行)。


先说“0x40~0x7F”
这条指令是针对屏幕硬件方面的配置。


OLED12864显示屏,从像素层面看,竖向一共有64行,对应的序号是0~63。靠近屏幕芯片的一边是第0行。
0x40这个指令是告诉屏幕芯片,把这64个行里的哪一行作为第一行,然后后面的行就会自动向后排。

所以“0x40~0x7F”,可以看做是“0x40+line”,其中line的范围是“0x00~0x3F(0~63)”,“0x40+0x3F”也就是这个指令的最大值“0x7F”。


如果不进行配置,屏幕芯片会使用默认值0x40,也就是“0x40+0”。此时屏幕上的行定义,恰好是从一边到另一边。

假如将屏幕上的第30行当做显示范围的第0行,配置指令就要写成“0x5E”,也就是“0x40+30”。
这时在屏幕上显示内容,第30行就是显示范围的第0行,向上一直排到第33行。之后超出屏幕的部分,会从屏幕另一边继续排。整个屏幕都向上卷了30行。


指令0x40影响的是屏幕像素点的硬件排序,跟显示内容无关。

就像电视机画面的起始位置发生了偏移,但是显示内容的坐标值并不受影响


与之配套的控制指令还有(0xA8,0x3F)。
0xA8是指定从起始行开始,用多少行进行显示,后面的0x3F就是63,也就是使用整个屏幕的64行。它的最小值是0x0F,也就是16个line。
比如在使用OLED12832屏幕的时候,就会看到(0xA8,0x1F),只是用屏幕的32行进行显示;
而OLED128128屏幕的配置,则是(0xA8,0x7F)。
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

然后说“0xB0~0xB7”
这条指令是定位显示的起始位置的。
因为这种OLED屏幕,最小的显示控制单元是8个像素点的小竖棍(Column)。128×64像素被分成了竖向八个Page(0~7),横向每个Page里有128个Column(0~127)。
指令 0x00 和 0x10 用来表示Column的地址;0xB0用来表示Page的地址。
所以“0xB0~0xB7”,也可以看成是“0xB0+page”,其中page的范围是“0x00~0x07(0~7)”。
假如在初始化函数里,显示范围的起始Line配置的是0x40(或者不进行配置),并且使用了屏幕上下翻转的指令0xC8,也就是把屏幕配置成跟丝印文字相同的方向,
那么指定起始page的指令对应的就是下图的顺序:

指令0xB0操作的是指定八个page中的某一个page作为起始行,对屏幕硬件设置没有影响。
如果使用的是水平地址模式,那么在填满一个page之后,会自动转到下一个page继续;
如果使用的屏幕只有页地址模式,那么就需要通过0xB0~0xB7来手动指定要显示的起始page。


==========================================================

换一个角度看这个事情,之所以在定位显示起点的时候,用指令0x40,也可以起作用,
是因为0x40本身就是将屏幕一边当第一行,另一边当最后一行,
而0xB0也是把屏幕的一边当做Page0,另一边当成Page7。
而0x40和0xB0这两条指令,在不使用的时候,屏幕芯片都会默认使用默认值,
所以即便把0xB0换成了0x40,在进行全屏操作的时候,如果使用的是水平地址模式,看起来效果也是一样的。
但是在需要在屏幕不同位置进行局部操作的时候,就会出现问题。

基于这个思路,您把0xB0换成了0x40,涉及到局部定位的时候就会出现定位错误,或许也就可以解释您为什么有时候会遇到黑屏或者花屏问题了。









mechray 发表于 2025-7-8 21:09:15

大明狐 发表于 2025-7-8 11:00
这个指令没有用错,是对指令产生的误解。感谢非常详细的分析。为此我又再仔细读了一下ssd1306的数据手册,主要发现如下:
1.ssd1306的寻址方式分为页寻址、行寻址和列寻址,由寄存器22设定,默认为页寻址。

2.根据官方例程的初始化设定,寻址模式被设为行寻址(尽管注释写为页寻址)。
从其刷新函数void OLED_Refresh(void)的写法来看,也确实使用的是行寻址模式。
即一次性向oled内存中写入所有数据,到页尾自动从下一页开头继续写。

3.根据ssd1306数据手册,0xB0、0x00和0x10三个命令仅使用于页寻址模式,
如果是行寻址,其实应该用的是0x21和0x22。

4.所以,刷新函数中0xB0、0x00和0x10三个命令其实都是无效的。
或者替换为0x21和0x22,或者直接不写。
因为0x21和0x22的复位值已经自动满足从0-7页和0-127列的数据寻址范围。
此外,如你所说,0x40确实也不用写,因为复位值已自动设为从0行显示。

5.照理来说,0xB0既然在行寻址模式下无效,其写与不写应该并不会影响显示效果。
但实际情况是,如果写了0xB0,则会出现我楼上贴图的情况,
会显示错行,整个图像会上移。删掉就正常了。

对于DMA的问题,我也试了一下,果然将频率设为24MHz工作就正常了。
再往上调,最高36.864MHz同样也能正常运行,唯独到40MHz就不行了。。。
另外,如果用40MHz,I2C的时钟分频必须设为3才行,设为小于3或大于3都不行。
这个现象就很玄乎了,背后的原因到底是什么呢,好纠结。。。

我总觉得是不是应该在哪里加个延时等待之类的语句,期待达人告知确切的原因。

myh007 发表于 2025-7-14 08:05:52

这个生成器不能选择管脚个数吗?

神农鼎 发表于 2025-7-14 08:11:40

myh007 发表于 2025-7-14 08:05
这个生成器不能选择管脚个数吗?管脚少的用最大管脚的来设置就行了
页: 1 2 [3] 4 5 6 7
查看完整版本: AiCube图形化程序自动生成【SPI,SPI-DMA,I2C,I2C-DMA】代码,驱动OLED-12864