找回密码
 立即注册
楼主: zhp

AiCube图形化程序自动生成【SPI,SPI-DMA,I2C,I2C-DMA】代码,驱动OLED-12864

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:42
  • 最近打卡:2025-07-14 09:01:51
已绑定手机

2

主题

45

回帖

256

积分

中级会员

积分
256
发表于 2025-7-6 11:13:59 | 显示全部楼层
学习
回复

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:80
  • 最近打卡:2025-07-14 00:03:28
已绑定手机

13

主题

60

回帖

235

积分

中级会员

积分
235
发表于 2025-7-6 13:12:27 | 显示全部楼层
运行示例代码之后发现有行偏移情况如下
IMG_9277.jpg
经检查代码,发现在屏幕刷新函数中,设置行起始地址的代码应为0x40,这里写成了0xB0。提示官方修订后再上传。
屏幕截图 2025-07-06 130051.png


屏幕截图 2025-07-06 125645.png

点评

这个指令没有用错,是对指令产生的误解。 这是数据手册里关于指令 0xB0,和 0x40 的配置表 [attachimg]107737[/attachimg] 两个都是“Start”,但是作用是不一样的。 “0xB0~0xB7”对应的是 Page, “0x40~0x7  详情 回复 发表于 6 天前
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:373
  • 最近打卡:2025-07-13 16:52:42

1

主题

30

回帖

744

积分

高级会员

积分
744
发表于 2025-7-6 17:15:18 | 显示全部楼层
用AiCube (V100S) 设置串口时,显示的是波特率列表的序号,而不是具体的波特率,如9600、115200,生成的工程,编译后串口异常。


微信截图_20250706170955.png
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:80
  • 最近打卡:2025-07-14 00:03:28
已绑定手机

13

主题

60

回帖

235

积分

中级会员

积分
235
发表于 2025-7-6 20:29:42 | 显示全部楼层
另外,OLED_I2C_DMA的Demo运行不正常,有时候黑屏,有时候花屏(但非DMA版本的Demo代码正常)。不知是什么原因。
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:80
  • 最近打卡:2025-07-14 00:03:28
已绑定手机

13

主题

60

回帖

235

积分

中级会员

积分
235
发表于 7 天前 | 显示全部楼层
mech*** 发表于 2025-7-6 20:29
另外,OLED_I2C_DMA的Demo运行不正常,有时候黑屏,有时候花屏(但非DMA版本的Demo代码正常)。不知是什么 ...

上面的问题还请看看能否帮忙,卡在这里两天了,一直没弄明白。附上代码,感谢。

  1. //<<AICUBE_USER_HEADER_REMARK_BEGIN>>
  2. ////////////////////////////////////////
  3. // 在此添加用户文件头说明信息  
  4. // 文件名称: i2c.c
  5. // 文件描述:
  6. // 文件版本: V1.0
  7. // 修改记录:
  8. //   1. (2025-07-06) 创建文件
  9. ////////////////////////////////////////
  10. //<<AICUBE_USER_HEADER_REMARK_END>>
  11. #include "config.h"
  12. //<<AICUBE_USER_INCLUDE_BEGIN>>
  13. // 在此添加用户头文件包含  
  14. //<<AICUBE_USER_INCLUDE_END>>
  15. //<<AICUBE_USER_GLOBAL_DEFINE_BEGIN>>
  16. // 在此添加用户全局变量定义、用户宏定义以及函数声明
  17. #define USE_DMA
  18. BOOL  fI2CDMABusy=0;
  19. //<<AICUBE_USER_GLOBAL_DEFINE_END>>
  20. uint8_t xdata pu8I2CDMATxBuffer[1026];  //I2C DMA发送缓冲区数组
  21. ////////////////////////////////////////
  22. // I2C初始化函数
  23. // 入口参数: 无
  24. // 函数返回: 无
  25. ////////////////////////////////////////
  26. void I2C_Init(void)
  27. {
  28.     I2C_SwitchP3332();                  //选择I2C数据口: SCL(P3.2), SDA(P3.3)
  29.     I2C_MasterMode();                   //设置I2C为主机模式
  30.     I2C_SetClockDivider(2);             //设置I2C为主机模式时钟
  31.     I2C_Enable();                       //使能I2C功能
  32.     //DMA_I2C_DisableDMA();                //使能I2C DMA
  33.     DMA_I2C_SetDMAAmount(1025);           //使能I2C DMA发送/接收总字节数
  34.     DMA_I2C_SetTxAmount(1025);          //设置I2C DMA发送总字节数
  35.     DMA_I2C_SetTxAddress(pu8I2CDMATxBuffer); //设置I2C DMA发送缓冲区地址
  36.     DMA_I2C_SetInterval(10);             //设置I2C DMA发送/接收字节间隔时间(系统时钟)
  37.     DMA_I2C_ClearTxFlag();              //清除I2C 发送DMA中断标志
  38.     DMA_I2C_SetTxBusPriority(3);        //设置总线访问为最低优先级
  39.     DMA_I2C_SetTxIntPriority(3);        //设置中断为最低优先级
  40.     DMA_I2C_EnableTxInt();              //使能I2C DMA发送中断
  41.     DMA_I2C_EnableTx();                 //使能I2C DMA发送功能
  42. //  DMA_I2C_TriggerTx();                //触发I2C DMA发送
  43.     //<<AICUBE_USER_I2C_INITIAL_BEGIN>>
  44.     // 在此添加用户初始化代码  
  45.         fI2CDMABusy = 0;
  46.         DMA_I2CT_STA = 0x00;
  47.     //<<AICUBE_USER_I2C_INITIAL_END>>
  48. }
  49. ////////////////////////////////////////
  50. // 主机模式等待命令完成
  51. // 入口参数: 无
  52. // 函数返回: 无
  53. ////////////////////////////////////////
  54. void I2C_MasterWait(void)
  55. {
  56.     while (!I2C_CheckMasterFlag());     //等待完成标志
  57.     I2C_ClearMasterFlag();              //清除完成标志
  58.     I2C_Idle();                         //恢复IDLE状态
  59. }
  60. ////////////////////////////////////////
  61. // 主机模式发送起始信号
  62. // 入口参数: 无
  63. // 函数返回: 无
  64. ////////////////////////////////////////
  65. void I2C_MasterStart(void)
  66. {
  67.     I2C_Start();                        //触发主机模式起始命令
  68.     I2C_MasterWait();                   //等待命令完成
  69. }
  70. ////////////////////////////////////////
  71. // 主机模式发送停止信号
  72. // 入口参数: 无
  73. // 函数返回: 无
  74. ////////////////////////////////////////
  75. void I2C_MasterStop(void)
  76. {
  77.     I2C_Stop();                         //触发主机模式停止命令
  78.     I2C_MasterWait();                   //等待命令完成
  79. }
  80. ////////////////////////////////////////
  81. // 主机模式发送1字节数据
  82. // 入口参数: dat (待发送的字节数据)
  83. // 函数返回: 0   (接收的应答信号为ACK)
  84. //           1   (接收的应答信号为NAK)
  85. ////////////////////////////////////////
  86. BOOL I2C_MasterSendByte(uint8_t dat)
  87. {
  88.     I2C_WriteData(dat);                 //将数据写入I2C数据寄存器
  89.     I2C_SendData();                     //触发主机模式写数据命令
  90.     I2C_MasterWait();                   //等待命令完成
  91.     I2C_RecvACK();                      //触发主机模式接收应答命令
  92.     I2C_MasterWait();                   //等待命令完成
  93.     return I2C_MasterReadACK();         //读取并返回应答信号
  94. }
  95. ////////////////////////////////////////
  96. // 主机模式接收1字节数据
  97. // 入口参数: ack (待发送的应答信号)
  98. // 函数返回:     (接收的字节数据)
  99. ////////////////////////////////////////
  100. uint8_t I2C_MasterReadByte(BOOL ack)
  101. {
  102.     uint8_t dat;
  103.     I2C_RecvData();                     //触发主机模式读数据命令
  104.     I2C_MasterWait();                   //等待命令完成
  105.     dat = I2C_ReadData();               //读取接收的数据
  106.     if (!ack)
  107.         I2C_MasterSetACK();             //将ACK数据写入寄存器
  108.     else
  109.         I2C_MasterSetNAK();             //将NAK数据写入寄存器
  110.     I2C_SendACK();                      //触发主机模式发送应答命令
  111.     I2C_MasterWait();                   //等待命令完成
  112.     return dat;                         //返回接收的数据
  113. }
  114. ////////////////////////////////////////
  115. // I2C发送DMA中断服务程序
  116. // 入口参数: 无
  117. // 函数返回: 无
  118. ////////////////////////////////////////
  119. void DMA_I2CTX_ISR(void) interrupt DMA_I2CT_VECTOR
  120. {
  121.     //<<AICUBE_USER_I2C_ISR_CODE2_BEGIN>>
  122.     // 在此添加中断函数用户代码  
  123.     if (DMA_I2C_CheckTxFlag())          //判断I2C发送DMA中断
  124.     {
  125.         DMA_I2C_ClearTxFlag();          //清除I2C发送DMA中断标志
  126.                 I2C_ClearMasterFlag();          //清除I2C中断标志
  127.         I2C_Idle();                     
  128.         DMA_I2C_DisableDMA();           //DMA传输完成后必须关闭DMA功能,否则下次触发会异常
  129.         
  130.         fI2CDMABusy = 0;                //清除DMA传输忙标志
  131.     }
  132.     //<<AICUBE_USER_I2C_ISR_CODE2_END>>
  133. }
  134. //<<AICUBE_USER_FUNCTION_IMPLEMENT_BEGIN>>
  135. // 在此添加用户函数实现代码
  136. ////////////////////////////////////////
  137. // 写OLED命令
  138. // 入口参数: cmd (命令序列)
  139. //           len (命令长度)
  140. // 函数返回: 无
  141. ////////////////////////////////////////
  142. void OLED_WR_CMD(uint8_t *cmd, uint16_t len)
  143. {
  144.         #ifndef USE_DMA
  145.     I2C_MasterStart();
  146.     I2C_MasterSendByte(0x78);           //从机地址
  147.     I2C_MasterSendByte(0x00);           //写命令 (DC = 0)
  148.         #else
  149.         /*DMA*/
  150.         while (fI2CDMABusy);              //等待上一次传输完成
  151.     pu8I2CDMATxBuffer[0] = 0x78;      //从机地址
  152.     pu8I2CDMATxBuffer[1] = 0x00;      //写命令 (DC = 0)
  153.     #endif
  154.     OLED_SendData(cmd, len);
  155. }
  156. ////////////////////////////////////////
  157. // 写OLED数据
  158. // 入口参数: dat (数据序列)
  159. //           len (数据长度)
  160. // 函数返回: 无
  161. ////////////////////////////////////////
  162. void OLED_WR_DAT(uint8_t *dat, uint16_t len)
  163. {
  164.         #ifndef USE_DMA
  165.     I2C_MasterStart();
  166.     I2C_MasterSendByte(0x78);                    //从机地址
  167.     I2C_MasterSendByte(0x40);                    //写数据 (DC = 1)
  168.         #else
  169.         /*DMA*/
  170.         while (fI2CDMABusy);              //等待上一次传输完成
  171.     pu8I2CDMATxBuffer[0] = 0x78;      //从机地址
  172.     pu8I2CDMATxBuffer[1] = 0x40;      //写命令 (DC = 0)
  173.     #endif
  174.        
  175.     OLED_SendData(dat, len);
  176. }
  177. ////////////////////////////////////////
  178. // 发送OLED数据
  179. // 入口参数: dat (数据序列)
  180. //           len (数据长度)
  181. // 函数返回: 无
  182. ////////////////////////////////////////
  183. void OLED_SendData(uint8_t *dat, uint16_t len)
  184. {
  185.         #ifndef USE_DMA
  186.     while (len--)
  187.         I2C_MasterSendByte(*dat++);
  188.     I2C_MasterStop();
  189.        
  190.         #else
  191.         /*DMA*/
  192.         memcpy(&pu8I2CDMATxBuffer[2], dat, len);
  193.     while (I2C_CheckMasterBusy());      //等待I2C模块退出忙状态
  194.        
  195.     DMA_I2C_EnableDMA();                //发送DMA命令前,先使能I2C DMA功能
  196.     I2C_StartSendDataRecvACK();         //I2C DMA发送数据必须使用09命令
  197.     DMA_I2C_SetDMAAmount(len + 1);      //使能I2C DMA发送/接收总字节数
  198.     DMA_I2C_SetTxAmount(len + 1);       //设置I2C DMA发送总字节数
  199.     DMA_I2C_TriggerTx();                //触发I2C DMA发送
  200.     fI2CDMABusy = 1;                    //设置DMA传输忙标志
  201.         #endif
  202. }
  203. //<<AICUBE_USER_FUNCTION_IMPLEMENT_END>>
复制代码


OLED_IIC_DMA.zip

573.66 KB, 下载次数: 3

点评

在AI8051U上试了下你上传的代码包,用不大于24MHz的频率烧录之后,可以正常显示 [apoyl_aliyunvideo]832[/apoyl_aliyunvideo] 硬件I2C和DMA驱动OLED的时候,发送速度对现实的影响很大。 比如I2C的时钟分频 I2C_Se  详情 回复 发表于 6 天前
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:462
  • 最近打卡:2025-07-14 09:19:51

30

主题

354

回帖

2986

积分

荣誉版主

积分
2986
发表于 6 天前 | 显示全部楼层
mech*** 发表于 2025-7-7 21:19
上面的问题还请看看能否帮忙,卡在这里两天了,一直没弄明白。附上代码,感谢。

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




硬件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楼的两个效果,也都是在这个基础上进行的操作。

能体会到发现一个不理解的现象然后找原因然后要么解决掉问题要么被问题解决掉的那种快乐是我的幸运
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:462
  • 最近打卡:2025-07-14 09:19:51

30

主题

354

回帖

2986

积分

荣誉版主

积分
2986
发表于 6 天前 | 显示全部楼层
mech*** 发表于 2025-7-6 13:12
运行示例代码之后发现有行偏移情况如下

经检查代码,发现在屏幕刷新函数中,设置行起始地址的代码应为0x40 ...

这个指令没有用错,是对指令产生的误解。


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



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

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


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

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行。
截图202507081024034803.jpg

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

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

与之配套的控制指令还有(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的指令对应的就是下图的顺序:
截图202507081133189175.jpg
指令0xB0操作的是指定八个page中的某一个page作为起始行,对屏幕硬件设置没有影响。
如果使用的是水平地址模式,那么在填满一个page之后,会自动转到下一个page继续;
如果使用的屏幕只有页地址模式,那么就需要通过0xB0~0xB7来手动指定要显示的起始page。


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

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

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








能体会到发现一个不理解的现象然后找原因然后要么解决掉问题要么被问题解决掉的那种快乐是我的幸运
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民II
  • 打卡总天数:80
  • 最近打卡:2025-07-14 00:03:28
已绑定手机

13

主题

60

回帖

235

积分

中级会员

积分
235
发表于 6 天前 | 显示全部楼层
大*** 发表于 2025-7-8 11:00
这个指令没有用错,是对指令产生的误解。感谢非常详细的分析。为此我又再仔细读了一下ssd1306的数据手册,主要发现如下:

1.ssd1306的寻址方式分为页寻址、行寻址和列寻址,由寄存器22设定,默认为页寻址。
屏幕截图 2025-07-08 204833.png
2.根据官方例程的初始化设定,寻址模式被设为行寻址(尽管注释写为页寻址)。
从其刷新函数void OLED_Refresh(void)的写法来看,也确实使用的是行寻址模式。
即一次性向oled内存中写入所有数据,到页尾自动从下一页开头继续写。
屏幕截图 2025-07-08 204726.png
3.根据ssd1306数据手册,0xB0、0x00和0x10三个命令仅使用于页寻址模式,
如果是行寻址,其实应该用的是0x21和0x22。
屏幕截图 2025-07-08 204428.png
4.所以,刷新函数中0xB0、0x00和0x10三个命令其实都是无效的。
或者替换为0x21和0x22,或者直接不写。
因为0x21和0x22的复位值已经自动满足从0-7页和0-127列的数据寻址范围。
此外,如你所说,0x40确实也不用写,因为复位值已自动设为从0行显示。
屏幕截图 2025-07-08 204751.png
5.照理来说,0xB0既然在行寻址模式下无效,其写与不写应该并不会影响显示效果。
但实际情况是,如果写了0xB0,则会出现我楼上贴图的情况,
会显示错行,整个图像会上移。删掉就正常了。

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

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

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:141
  • 最近打卡:2025-07-14 07:21:20
已绑定手机

0

主题

1

回帖

520

积分

高级会员

积分
520
发表于 4 小时前 | 显示全部楼层
这个生成器不能选择管脚个数吗?

点评

少管脚的用最大管脚的来设置就行了  详情 回复 发表于 4 小时前
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:127
  • 最近打卡:2025-07-14 08:05:17

755

主题

1万

回帖

1万

积分

管理员

积分
17864
发表于 4 小时前 | 显示全部楼层
myh*** 发表于 2025-7-14 08:05
这个生成器不能选择管脚个数吗?

少管脚的用最大管脚的来设置就行了
回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-7-14 12:43 , Processed in 0.480865 second(s), 115 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表