[CW32F030系列] 【CW32F030CxTx StartKit测评】4.软件模拟SPI驱动OLED

[复制链接]
2198|12
 楼主| zhouminjie 发表于 2022-8-8 00:37 | 显示全部楼层 |阅读模式

SPI是一种高速、全双工、同步串行总线,SPI与I2C对比:

    SPI是全双工,I2C是半双工

    SPI只能有一个主机,I2C支持多主机多从机

    I2C占用更少的GPIO,更节省资源
    SPI的数据位宽更灵活,可以根据需要选择多位数数据宽度
    SPI协议没有响应机制,主机无法得知从机是否接收到所发的数据,需采用一些方法来防止数据丢帧
    SPI协议可以做到非常高的速率,每一个SCLK都可以进行数据的传输,通过引入CRC等校验方法,可以即高速传输数据,又能保持数据的准确度
    I2C通过器件地址来选择从机,从机数量的增加不会导致GPIO的增加,而SPI通过CS片选信号选择从机,每增加一个从机就要多占用一个GPIO;也可以通过加入译码器来实现多从机控制
    SPI、I2C都应用于板内器件短距离通讯

SPI总线详解可参考: SPI Block Guide V04.01.pdf (428.89 KB, 下载次数: 2)

此篇主要介绍GPIO软件模拟实现SPI来驱动0.96寸OLED显示屏

硬件连接

GND ——  GND

VCC ——  3.3V

DO  ——  PA5
DI  ——  PA7
RES ——  PB1
DC  ——  PB0
CS  ——  PA4

软件代码

SPI代码:

  1. //SPI初始化
  2. void SPI_Initial(void)
  3. {
  4.     GPIO_InitTypeDef GPIO_InitStructure;

  5.     //根据GPIO组初始化GPIO时钟
  6.     RCC_AHBPeriphClk_Enable( RCC_SPI_CS | RCC_SPI_SCK | RCC_SPI_MOSI | RCC_SPI_MISO, ENABLE);

  7.     //GPIO_CS初始化设置
  8.     GPIO_InitStructure.Pins = PIN_SPI_CS;
  9.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  10.     GPIO_InitStructure.IT = GPIO_IT_NONE;
  11.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  12.     GPIO_Init(PORT_SPI_CS, &GPIO_InitStructure);
  13.     //GPIO_SCK初始化设置
  14.     GPIO_InitStructure.Pins = PIN_SPI_SCK;
  15.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  16.     GPIO_InitStructure.IT = GPIO_IT_NONE;
  17.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  18.     GPIO_Init(PORT_SPI_SCK, &GPIO_InitStructure);
  19.     //GPIO_MISO初始化设置
  20.     GPIO_InitStructure.Pins = PIN_SPI_MISO;
  21.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  22.     GPIO_InitStructure.IT = GPIO_IT_NONE;
  23.     GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  24.     GPIO_Init(PORT_SPI_MISO, &GPIO_InitStructure);
  25.     //GPIO_MOSI初始化设置
  26.     GPIO_InitStructure.Pins = PIN_SPI_MOSI;
  27.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  28.     GPIO_InitStructure.IT = GPIO_IT_NONE;
  29.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  30.     GPIO_Init(PORT_SPI_MOSI, &GPIO_InitStructure);

  31.     SPI_CS_DISABLE;
  32.     SPI_SCK_HIGH;
  33.     SPI_MOSI_HIGH;

  34. }


  35. //SPI写一字节数据,TxData --- 发送的字节数据
  36. void SPI_WriteByte(unsigned char TxData)
  37. {
  38.     unsigned char cnt;

  39.     for(cnt = 0; cnt < 8; cnt++)
  40.     {
  41.         SPI_SCK_LOW;                                 //时钟 - 低
  42.         Delay_Us(1);

  43.         if(TxData & 0x80)                            //发送数据
  44.             SPI_MOSI_HIGH;
  45.         else
  46.             SPI_MOSI_LOW;

  47.         TxData <<= 1;

  48.         Delay_Us(1);
  49.         SPI_SCK_HIGH;                                //时钟 - 高
  50.         Delay_Us(1);
  51.     }
  52. }


  53. //SPI读一字节数据,读回来的字节数据
  54. unsigned char SPI_ReadByte(void)
  55. {
  56.     unsigned char cnt;
  57.     unsigned char RxData = 0;

  58.     for(cnt = 0; cnt < 8; cnt++)
  59.     {
  60.         SPI_SCK_LOW;                                 //时钟 - 低
  61.         Delay_Us(1);

  62.         RxData <<= 1;

  63.         if(SPI_MISO_READ)                            //读取数据
  64.         {
  65.             RxData |= 0x01;
  66.         }

  67.         SPI_SCK_HIGH;                                //时钟 - 高
  68.         Delay_Us(1);
  69.     }

  70.     return RxData;
  71. }

OLED代码:

  1. //向设备写控制命令
  2. static void OLED_Write_CMD(unsigned char cmd)
  3. {
  4.     #ifdef HW_I2C
  5.     unsigned char tx_buf[BUF_SIZE] = {0x00, cmd};
  6.     I2C_MasterSendDataToSlave(CW_I2C2, tx_buf, BUF_SIZE);
  7.     #endif
  8.     #ifdef SW_I2C
  9.     I2C_Start();
  10.     I2C_Send_Byte(0x78);
  11.     I2C_Wait_Ack();
  12.     I2C_Send_Byte(0x00);
  13.     I2C_Wait_Ack();
  14.     I2C_Send_Byte(cmd);
  15.     I2C_Wait_Ack();
  16.     I2C_Stop();
  17.     #endif
  18.     #ifdef HW_SPI
  19.     OLED_CS_LOW;
  20.     OLED_DC_LOW;

  21.     while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_TXE) == RESET);

  22.     SPI_SendData(SPI_MASTER, cmd);

  23.     while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_RXNE) == RESET);

  24.     SPI_ReceiveData(SPI_MASTER);
  25.     OLED_CS_HIGH;
  26.     #endif
  27.     #ifdef SW_SPI
  28.     OLED_CS_LOW;
  29.     OLED_DC_LOW;
  30.     SPI_WriteByte(cmd);
  31.     OLED_CS_HIGH;
  32.     #endif
  33. }

  34. //向设备写数据
  35. static void OLED_Write_Date(unsigned char date)
  36. {
  37.     #ifdef HW_I2C
  38.     unsigned char tx_buf[BUF_SIZE] = {0x40, date};
  39.     I2C_MasterSendDataToSlave(CW_I2C2, tx_buf, BUF_SIZE);
  40.     #endif
  41.     #ifdef SW_I2C
  42.     I2C_Start();
  43.     I2C_Send_Byte(0x78);
  44.     I2C_Wait_Ack();
  45.     I2C_Send_Byte(0x40);
  46.     I2C_Wait_Ack();
  47.     I2C_Send_Byte(date);
  48.     I2C_Wait_Ack();
  49.     I2C_Stop();
  50.     #endif
  51.     #ifdef HW_SPI
  52.     OLED_CS_LOW;
  53.     OLED_DC_HIGH;

  54.     while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_TXE) == RESET);

  55.     SPI_SendData(SPI_MASTER, date);

  56.     while (SPI_GetFlagStatus(SPI_MASTER, SPI_FLAG_RXNE) == RESET);

  57.     SPI_ReceiveData(SPI_MASTER);
  58.     OLED_CS_HIGH;
  59.     #endif
  60.     #ifdef SW_SPI
  61.     OLED_CS_LOW;
  62.     OLED_DC_HIGH;
  63.     SPI_WriteByte(date);
  64.     OLED_CS_HIGH;
  65.     #endif
  66. }

  67. //坐标设置
  68. static void OLED_Set_Pos(unsigned char x, unsigned char y)
  69. {
  70.     OLED_Write_CMD(0xB0 + y);
  71.     OLED_Write_CMD(((x & 0xF0) >> 4) | 0x10);
  72.     OLED_Write_CMD(x & 0x0F);
  73. }

  74. //开启OLED显示
  75. static void OLED_Display_On(void)
  76. {
  77.     OLED_Write_CMD(0x8D); //SET DCDC命令
  78.     OLED_Write_CMD(0x14); //DCDC ON
  79.     OLED_Write_CMD(0xAF); //DISPLAY ON
  80. }

  81. //关闭OLED显示
  82. static void OLED_Display_Off(void)
  83. {
  84.     OLED_Write_CMD(0x8D); //SET DCDC命令
  85.     OLED_Write_CMD(0x10); //DCDC OFF
  86.     OLED_Write_CMD(0xAE); //DISPLAY OFF
  87. }

  88. //OLED清屏
  89. void OLED_Clear(void)
  90. {
  91.     unsigned char cnt, count;

  92.     for(cnt = 0; cnt < 8; cnt++)
  93.     {
  94.         OLED_Write_CMD(0xB0 + cnt);
  95.         OLED_Write_CMD(0x00);
  96.         OLED_Write_CMD(0x10);

  97.         for(count = 0; count < 128; count++)
  98.         {
  99.             OLED_Write_Date(0x00);
  100.         }
  101.     }
  102. }

  103. //OLED清行
  104. void OLED_Clear_Row(unsigned char n)
  105. {
  106.     unsigned char count;

  107.     OLED_Write_CMD(0xB0 + n);
  108.     OLED_Write_CMD(0x00);
  109.     OLED_Write_CMD(0x10);

  110.     for(count = 0; count < 128; count++)
  111.     {
  112.         OLED_Write_Date(0x00);
  113.     }
  114. }

  115. //OLED填满屏幕
  116. void OLED_Fill(void)
  117. {
  118.     unsigned char cnt, count;

  119.     for(cnt = 0; cnt < 8; cnt++)
  120.     {
  121.         OLED_Write_CMD(0xB0 + cnt); //设置页地址(0~7)
  122.         OLED_Write_CMD(0x00); //设置显示位置—列低地址
  123.         OLED_Write_CMD(0x10); //设置显示位置—列高地址

  124.         for(count = 0; count < 128; count++)
  125.         {
  126.             OLED_Write_Date(0x01);
  127.         }
  128.     }
  129. }

  130. //指定位置显示一个字符
  131. //x:0~127
  132. //y:0~63
  133. //chr:字符
  134. //mode:0,反白显示;1,正常显示
  135. //size:选择字体 16/12
  136. void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr, unsigned char size)
  137. {
  138.     unsigned char offset = 0, cnt = 0;

  139.     offset = chr - ' '; //计算偏移量

  140.     if(x > 128 - 1)
  141.     {
  142.         x = 0;
  143.         y = y + 2;
  144.     }

  145.     if(size == 16)
  146.     {
  147.         OLED_Set_Pos(x, y);

  148.         for(cnt = 0; cnt < 8; cnt++)
  149.         {
  150.             OLED_Write_Date(F8x16[offset * 16 + cnt]);
  151.         }

  152.         OLED_Set_Pos(x, y + 1);

  153.         for(cnt = 0; cnt < 8; cnt++)
  154.         {
  155.             OLED_Write_Date(F8x16[offset * 16 + cnt + 8]);
  156.         }
  157.     }
  158.     else
  159.     {
  160.         OLED_Set_Pos(x, y);

  161.         for(cnt = 0; cnt < 6; cnt++)
  162.         {
  163.             OLED_Write_Date(F6x8[offset][cnt]);
  164.         }
  165.     }
  166. }

  167. unsigned int oled_pow(unsigned char m, unsigned char n)
  168. {
  169.     unsigned int result = 1;

  170.     while(n--)
  171.     {
  172.         result *= m;
  173.     }

  174.     return result;
  175. }

  176. //指定位置显示一个数字
  177. //x,y:起点坐标
  178. //num:数值(0~4294967295)
  179. //len:数字的位数
  180. //size:字体大小
  181. //mode:模式        0,填充模式;1,叠加模式
  182. void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len, unsigned char size)
  183. {
  184.     unsigned char cnt, temp;
  185.     unsigned char show = 0;

  186.     for(cnt = 0; cnt < len; cnt++)
  187.     {
  188.         temp = (num / oled_pow(10, len - cnt - 1)) % 10;

  189.         if(show == 0 && cnt < (len - 1))
  190.         {
  191.             if(temp == 0)
  192.             {
  193.                 OLED_ShowChar(x + (size / 2) * cnt, y, ' ', size);
  194.                 continue;
  195.             }
  196.             else
  197.             {
  198.                 show = 1;
  199.             }
  200.         }

  201.         OLED_ShowChar(x + (size / 2) * cnt, y, temp + '0', size);
  202.     }
  203. }

  204. //指定位置显示字符串
  205. void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr, unsigned char size)
  206. {
  207.     unsigned char cnt = 0;

  208.     while(chr[cnt] != '\0')
  209.     {
  210.         OLED_ShowChar(x, y, chr[cnt], size);
  211.         x += 8;

  212.         if(x > 120)
  213.         {
  214.             x = 0;
  215.             y += 2;
  216.         }

  217.         cnt++;
  218.     }
  219. }

  220. //显示汉字
  221. void OLED_ShowCHinese(unsigned char x, unsigned char y, unsigned char no)
  222. {
  223.     unsigned char cnt, addr = 0;

  224.     OLED_Set_Pos(x, y);

  225.     for(cnt = 0; cnt < 16; cnt++)
  226.     {
  227.         OLED_Write_Date(Hzk[2 * no][cnt]);
  228.         addr++;
  229.     }

  230.     OLED_Set_Pos(x, y + 1);

  231.     for(cnt = 0; cnt < 16; cnt++)
  232.     {
  233.         OLED_Write_Date(Hzk[2 * no + 1][cnt]);
  234.         addr++;
  235.     }
  236. }

  237. //显示图片
  238. /*
  239.         @brief                        显示图片
  240.         @param                        x0:起始列地址
  241.                                         y0:起始页地址
  242.                                         x1:终止列地址
  243.                                         y1:终止页地址
  244.                                         BMP[]:存放图片代码的数组
  245.         @retval                        无
  246. */
  247. void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[])
  248. {
  249.     unsigned int j = 0; //定义变量
  250.     unsigned char x, y; //定义变量

  251.     if(y1 % 8 == 0)
  252.     {
  253.         y = y1 / 8; //判断终止页是否为8的整数倍
  254.     }
  255.     else
  256.     {
  257.         y = y1 / 8 + 1;
  258.     }

  259.     for(y = y0; y < y1; y++) //从起始页开始,画到终止页
  260.     {
  261.         OLED_Set_Pos(x0, y); //在页的起始列开始画

  262.         for(x = x0; x < x1; x++) //画x1 - x0 列
  263.         {
  264.             OLED_Write_Date(BMP[j++]); //画图片的点
  265.         }
  266.     }
  267. }

  268. //显示动图
  269. /*
  270.         @brief                        显示动图
  271.         @param                        x0:起始列地址
  272.                                 y0:起始页地址
  273.                                 x1:终止列地址
  274.                                 y1:终止页地址
  275.                                 k: 帧个数
  276.                                 m: 单帧数组大小
  277.                                 BMP[][m]:存放动图代码的数组
  278.         @retval                        无
  279. */
  280. void OLED_DrawGIF(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char k, int m, const unsigned char GIF[][m])
  281. {
  282.     unsigned int j = 0; //定义变量
  283.     unsigned char x, y, i; //定义变量

  284.     if(y1 % 8 == 0)
  285.     {
  286.         y = y1 / 8; //判断终止页是否为8的整数倍
  287.     }
  288.     else
  289.     {
  290.         y = y1 / 8 + 1;
  291.     }

  292.     for (i = 0; i < k; i++) //从第一帧开始画
  293.     {
  294.         j = 0;

  295.         for(y = y0; y < y1; y++) //从起始页开始,画到终止页
  296.         {
  297.             OLED_Set_Pos(x0, y); //在页的起始列开始画

  298.             for(x = x0; x < x1; x++) //画x1 - x0 列
  299.             {
  300.                 OLED_Write_Date(GIF[i][j++]); //画图片的点
  301.             }
  302.         }

  303.         //Delay_Ms(80);
  304.     }
  305. }


  306. #if defined SW_SPI || defined HW_SPI
  307. //SPI驱动方式DC、RES引脚初始化
  308. void OLED_SPI_GPIO_Init(void)
  309. {
  310.     GPIO_InitTypeDef GPIO_InitStructure;

  311.     //根据GPIO组初始化GPIO时钟
  312.     RCC_AHBPeriphClk_Enable( RCC_OLED_DC | RCC_OLED_RES, ENABLE);
  313.     //GPIO_DC初始化设置
  314.     GPIO_InitStructure.Pins = PIN_OLED_DC;
  315.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  316.     GPIO_InitStructure.IT = GPIO_IT_NONE;
  317.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  318.     GPIO_Init(PORT_OLED_DC, &GPIO_InitStructure);
  319.     //GPIO_RES初始化设置
  320.     GPIO_InitStructure.Pins = PIN_OLED_RES;
  321.     GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  322.     GPIO_InitStructure.IT = GPIO_IT_NONE;
  323.     GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  324.     GPIO_Init(PORT_OLED_RES, &GPIO_InitStructure);

  325.     OLED_RES_LOW;
  326.     Delay_Ms(200);
  327.     OLED_RES_HIGH;
  328. }
  329. #endif


  330. //OLED初始化
  331. void OLED_Init(void)
  332. {
  333.     #ifdef SW_I2C
  334.     I2C_Init();
  335.     #endif
  336.     #ifdef HW_I2C
  337.     I2C_Init();
  338.     #endif
  339.     #ifdef SW_SPI
  340.     SPI_Initial();
  341.     OLED_SPI_GPIO_Init();
  342.     #endif
  343.     #ifdef HW_SPI
  344.     SPI_Initial();
  345.     OLED_SPI_GPIO_Init();
  346.     #endif
  347.     Delay_Ms(200);
  348.     OLED_Write_CMD(0xAE); //display off
  349.     OLED_Write_CMD(0x00); //set low column address
  350.     OLED_Write_CMD(0x10); //set high column address
  351.     OLED_Write_CMD(0x40); //set start line address
  352.     OLED_Write_CMD(0xB0); //set page address
  353.     OLED_Write_CMD(0x81); //contract control
  354.     OLED_Write_CMD(0xFF); //128
  355.     OLED_Write_CMD(0xA1); //set segment remap
  356.     OLED_Write_CMD(0xA6); //normal / reverse
  357.     OLED_Write_CMD(0xA8); //set multiplex ratio(1 to 64)
  358.     OLED_Write_CMD(0x3F); //1/32 duty
  359.     OLED_Write_CMD(0xC8); //Com scan direction
  360.     OLED_Write_CMD(0xD3); //set display offset
  361.     OLED_Write_CMD(0x00); //
  362.     OLED_Write_CMD(0xD5); //set osc division
  363.     OLED_Write_CMD(0x80); //
  364.     OLED_Write_CMD(0xD8); //set area color mode off
  365.     OLED_Write_CMD(0x05); //
  366.     OLED_Write_CMD(0xD9); //Set Pre-Charge Period
  367.     OLED_Write_CMD(0xF1); //
  368.     OLED_Write_CMD(0xDA); //set com pin configuartion
  369.     OLED_Write_CMD(0x12); //
  370.     OLED_Write_CMD(0xDB); //set Vcomh
  371.     OLED_Write_CMD(0x30); //
  372.     OLED_Write_CMD(0x8D); //set charge pump enable
  373.     OLED_Write_CMD(0x14); //
  374.     OLED_Write_CMD(0xAF); //turn on oled panel
  375. }

运行测试

WeChat_20220807224825_ (1).gif

测试代码

CW32F030CxTx_SW_SPI_OLED.zip (261.28 KB, 下载次数: 7)





duo点 发表于 2022-8-8 09:30 来自手机 | 显示全部楼层
屏幕上的几个图标是啥意思啊
 楼主| zhouminjie 发表于 2022-8-8 16:39 | 显示全部楼层
duo点 发表于 2022-8-8 09:30
屏幕上的几个图标是啥意思啊

几个车标
weifeng90 发表于 2022-8-8 21:14 来自手机 | 显示全部楼层
软件模拟效率不高啊
 楼主| zhouminjie 发表于 2022-8-8 23:36 | 显示全部楼层
weifeng90 发表于 2022-8-8 21:14
软件模拟效率不高啊

速度?
updownq 发表于 2022-8-16 20:30 | 显示全部楼层
模拟spi可以。  
hearstnorman323 发表于 2022-8-20 16:25 | 显示全部楼层
有硬件iic或者spi驱动的吗   
 楼主| zhouminjie 发表于 2022-8-22 23:26 | 显示全部楼层
hearstnorman323 发表于 2022-8-20 16:25
有硬件iic或者spi驱动的吗

有的,硬件SPI https://bbs.21ic.com/icview-3244756-1-1.html;硬件I2C https://bbs.21ic.com/icview-3244752-1-1.html
wwk1996 发表于 2022-8-23 06:31 来自手机 | 显示全部楼层
OLED的驱动芯片是?
 楼主| zhouminjie 发表于 2022-8-31 21:04 | 显示全部楼层
wwk1996 发表于 2022-8-23 06:31
OLED的驱动芯片是?

内部芯片是SSD1306
abotomson 发表于 2022-9-8 09:02 | 显示全部楼层
这个模拟spi的速度快吗   
bartonalfred 发表于 2022-9-8 15:42 | 显示全部楼层
CW32F030CxTx 价格可以吗   
您需要登录后才可以回帖 登录 | 注册

本版积分规则

33

主题

140

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部