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
这个生成器不能选择管脚个数吗?管脚少的用最大管脚的来设置就行了