使用芯源CW32的CW32L012开发评估板做了spi屏幕驱动 电子说
在CW32生态社区以极高的性价比入手了这块CW32L012开发评估板,开发板以底板、核心板、1.77寸TFT显示屏构成,所有的IO口都引出了排针,这点好评,使用杜邦线可以非常方便的连接其他模块。引脚、接口模块的布局都非常合理,并且整板子都使用了黑色,颜值也杠杠在线的,非常方便我用来学习这颗芯片。这里边还有一个非常小的细节,这个细小的设计很贴中我的使用习惯,核心板中的功能按键和复位按键进行了区分,白色的按键是功能按键,红色的按键才是复位按键,让我这种分不清左边是复位按键还是右边是复位按键的人一下就能按中复位按键了;
开发板介绍

这款开发评估板的设计非常巧妙,首先是电源部分,给出了一个12V的DC直流电源插口,有自恢复保险丝和保证电流单项导通的二极管,7805线性稳压器将电压从15V的电压稳压到5V,需要用到5V电压的模块此时接到这里完成5V供电,同时我们的芯片也从这里获得5V的供电,这款芯片是宽电压的芯片,用5V是完全没有问题的;AMS1117-3.3是一款非常经典的5V稳压到3.3V的线性稳压器,此时需要用到3.3V的模块在这里供电,在开发板的右侧区域预留了大量的+5V、+3V3、GND的排针,方便外接模块供电,这就是电源部分。

接着是显示屏幕区域,这块开发板做足的对屏幕的兼容,对0.91OLED,0.96OLED,1.77TFT屏幕都兼容,接口方面是4线SPI,2线的I2C,官方默认给配置的是一个8针的1.77TFT屏幕,在这篇文章我将着重介绍4线SPI,通过软件模拟来实现SPI通讯协议,并测量其通讯速率。

板载的模块设计了一个有源蜂鸣器,通过MCU的PB3引脚来控制SS8050三极管来开关断蜂鸣器,可以完成蜂鸣器相关的实验,三个按键分别是KEY1,KEY2,KEY3,三个LED灯分别是LED1,LED2,LED3,对应MCU的PB13,PB14,PB15,PA7,PA8,PC13,LED灯是低电平点亮,可以完成流水灯以及按键电灯实验,一个电位器,可以调节电阻的大小,对应MCU的PB0口,通过调节电位器,来完成adc采集模拟电压的实验。
在核心板区域提供了TYPE-C单独供电,留出了4针的SWO程序调式口,有一个复位按键和一个功能按键,预留了一个外部高速晶振和一个低速晶振,高速晶振支持4-32MHz,低速晶振是32.768KHz的,这款芯片没有PLL倍频器,内部时钟频率连接到外部高速晶振时,外部高速晶振最高是多少,那么内部时钟频率最高就是多少,但是这款芯片内部的RC振荡器是96MHz的,这个频率可以满足大部分使用场景,还可以节省使用外部晶振的成本,那么使用这款芯片的时候大概率就是使用内部RC振荡器了,官方也没有配置外部晶振,只是设计了这个位置,估计也是和我有类似的考量吧。
最后这块板子还提供了大量的外部模块接口,可以拓展更多的模块,增添了可玩性,由于这块开发板已经极具性价比了,出于对成本的考量,官方是没有配备拓展模块,预留出来的模块接口有8Pin的mpu6050,我们外接了这个模块可以做一些与运动检测相关的实验,因为这个模块是测量横滚角、俯仰角、偏航角的,4Pin的电子秤模块接口,可以做电子秤的实验,2*4Pin的WIFI模块,可以插ESP8266做物联网相关的实验,6Pin的蓝牙模块,可以做和手机通讯的实验,9针的矩阵键盘接口,3Pin的温湿度,插上dht11模块可以测量温度和湿度,以及预留了2个4针的串口,可以做串口通讯的实验。
原理图
原理图来源于CW32生态社区,官方开源了硬件原理图;


SPI通讯简介
SPI,全称为 串行外设接口,是一种高速、全双工、同步的串行通信总线。它由摩托罗拉公司开发,因其简单和高效的特点,被广泛应用于微控制器、传感器、存储器、SD卡、触摸屏等设备之间的短距离通信。
一、核心特点
全双工通信:数据可以同时在两个方向上传输(主机发送数据的同时,也能接收从机返回的数据)。
同步通信:所有数据传输都由主机产生的时钟信号同步,通信双方无需预先配置波特率。
高速:相比I2C、UART等协议,SPI可以达到很高的通信速率(通常可达几十MHz甚至更高)。
简单:协议本身非常简单,没有复杂的地址帧和应答机制,硬件实现也相对容易。
二、硬件连接(4线制)
一个标准的SPI总线需要至少4根信号线:
td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}
| 信号线名称 | 全称 | 方向(以主机视角) | 功能描述 |
| SCLK | 串行时钟 | 输出 | 由主机产生,用于同步数据位传输的时钟信号。 |
| MOSI | 主设备输出,从设备输入 | 输出 | 主机向从机发送数据的线路。 |
| MISO | 主设备输入,从设备输出 | 输入 | 主机从从机接收数据的线路。 |
| CS/SS | 片选 / 从设备选择 | 输出 | 由主机控制,用于选中要进行通信的特定从机。此信号通常低电平有效。 |
连接方式:
一主一从:连接最为简单,四根线直接相连即可。
一主多从(标准模式):这是最常用的方式。主机为每个从机提供独立的片选信号。同一时刻,只有一个片选信号有效,从而选中一个从机进行通信。
一主多从(菊花链模式):所有从机共用一根片选线,数据像链条一样从一个从机传递到下一个。这种方式节省主机的I/O口,但软件控制更复杂,且并非所有从设备都支持。
三、通信过程与工作原理
初始化:主机配置好时钟极性(CPOL)和时钟相位(CPHA),这决定了时钟的空闲状态和数据的采样边沿。
选择从机:主机将目标从机的CS引脚拉至低电平。
数据传输:
主机在SCLK时钟线上产生时钟脉冲。
在每个时钟周期内,主机通过MOSI线发送一位数据,同时通过MISO线读取一位数据。
这意味着每次移位操作实际上是一次主从设备的数据交换。主机发送一个字节的同时,也会收到从机返回的一个字节。
取消选择:通信完成后,主机将CS引脚拉回高电平。
一个生动的比喻:
可以把SPI通信想象成两个面对面的的人(主机和从机)在进行一场同步的“说听”练习。SCLK是节拍器,确保节奏一致。MOSI是主机说话的通道,MISO是主机听音的通道。CS就像是主机决定是否要与对面的人对话的开关。当开关打开(CS拉低),随着节拍器的每个节拍,主机说一个字的同时,也能听到对方回一个字。
四、时钟极性与相位
这是SPI配置中的一个关键概念,决定了时钟空闲时的电平和数据采样的时刻。通过组合CPOL和CPHA,主要有四种模式:
td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}
| 模式 | CPOL (时钟极性) | CPHA (时钟相位) | 时钟空闲状态 | 数据采样时刻 |
| 0 | 0 | 0 | 低电平 | 时钟的第一个边沿(上升沿) |
| 1 | 0 | 1 | 低电平 | 时钟的第二个边沿(下降沿) |
| 2 | 1 | 0 | 高电平 | 时钟的第一个边沿(下降沿) |
| 3 | 1 | 1 | 高电平 | 时钟的第二个边沿(上升沿) |
要点:主设备和从设备必须工作在相同的模式下才能正常通信。
软件模拟SPI通讯
总之,SPI是一种在嵌入式领域极其重要且实用的通讯协议,它在高速数据交换的设备中扮演着关键角色。
SPI可以通过软件模拟和硬件外设资源实现,软件模拟SPI是指在不用硬件电路SPI控制器的情况下,通过通用输入输出引脚(GPIO)和程序代码来模拟SPI通信时序的方法。我将通过这种方式来点亮官方配置的这一块1.77寸TFT屏幕,让屏幕显示出图片。
实现思路
使用普通的GPIO引脚分别模拟SCLK、MOSI、MISO、CS功能
通过程序控制引脚电平变化来生成SPI时序
用延时函数或精确计时控制通信时序
代码实现
#if !defined(__SPI_H__) #define __SPI_H__ #include "cw32l012_sysctrl.h" #include "cw32l012_gpio.h" #include "cw32l012_spi.h" #include "cw32l012_systick.h" void SPI1_Init(void); uint8_t SPI_SwapByte(uint8_t byte); #define SPI_W_CS(x) GPIO_WritePin(CW_GPIOB, GPIO_PIN_5, (GPIO_PinState)x) #define SPI_W_CLK(x) GPIO_WritePin(CW_GPIOB, GPIO_PIN_6, (GPIO_PinState)x) #define SPI_W_MOSI(x) GPIO_WritePin(CW_GPIOB, GPIO_PIN_7, (GPIO_PinState)x) #define SPI_R_MISO() GPIO_ReadPin(CW_GPIOA, GPIO_PIN_6) #define SPI_W_RTS(x) GPIO_WritePin(CW_GPIOA,GPIO_PIN_15, (GPIO_PinState)x) #define SPI_W_DC(x) GPIO_WritePin(CW_GPIOB,GPIO_PIN_4, (GPIO_PinState)x) #endif // __SPI_H__
#include "SPI.h"
void SPI1_Init(void)
{
SYSCTRL_AHBPeriphClk_Enable(SYSCTRL_AHB_PERIPH_GPIOA|SYSCTRL_AHB_PERIPH_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pins = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pins = GPIO_PIN_6;
GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
GPIO_WritePin(CW_GPIOB, GPIO_PIN_6, GPIO_Pin_RESET);
GPIO_WritePin(CW_GPIOB, GPIO_PIN_5, GPIO_Pin_SET);
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pins = GPIO_PIN_15;
GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
}
uint8_t SPI_SwapByte(uint8_t byte)
{
uint8_t read_byte = 0x00;
for (uint8_t i = 0; i < 8; i++)
{
if (byte & 0x80)
{
SPI_W_MOSI(1);
}
else
{
SPI_W_MOSI(0);
}
byte < <= 1;
SPI_W_CLK(1);
read_byte < <= 1;
if ((uint8_t)SPI_R_MISO())
{
read_byte |= 0x01;
}
SPI_W_CLK(0);
}
return read_byte;
}
这就是软件模拟SPI的底层驱动,由于屏幕显示图片的代码数量太长了,这里就没有分享出来了;
软件模拟SPI的优缺点
优点
灵活性高:可以在任何有GPIO的设备上实现
引脚可配置:可以任意选择可用的GPIO引脚
多设备支持:可以轻松创建多个SPI总线实例
调试方便:可以添加调试信息和时序监控
成本低:不需要专门的硬件SPI控制器
缺点
速度较慢:受限于CPU速度和软件延时精度
CPU占用高:通信期间CPU需要持续处理时序
时序精度差:受中断和其他任务影响,时序可能不稳定
实时性差:不适合高速或实时性要求高的应用
实现复杂:需要仔细处理各种SPI模式和时序
实现效果

传输速度测试
这次对于SPI的实验做到这里其实基本算已经完成了,但是每次写通讯协议的代码的时候,我都希望把每种通讯协议的性能拉满,然后看看具体能达到多少速度,这次正好写了SPI通讯的文章,出于对软件模拟SPI通讯协议速度的好奇,那么我就想在这里测一测我这个实验的通讯协议的速度;
这颗芯片的官方手册说明了内部集成 3个串行外设接口(SPI), 主机模式下 SCK 频率高达 24MHz,理论上最大的传输速度是24Mbit/s,实际的传输速度是远远低于这个传输速度的,因为成功发送一个字节数据有协议开销,处理器和软件开销。这是硬件SPI支持的最大数据传输速度,大家可以从我的底层代码可以看到,MCU内核对于代码的执行我是没有添加任何的延时阻塞的,我在main.c中把时钟的也配置到了这颗芯片最大的时钟频率96MHz,那么这颗芯片处理SPI底层代码,也就是发送一个字节的数据是按照最大速度执行的,然后我找来了示波器。
我们来看看示波器测试的结果:

黄色的线是SCLK的波形,蓝色是MOSI信号线的波形

通过示波器的光标我们可以看到发送一个字节需要的时间是18.6us,发送一位需要的时间就是18.6us/8=2.325us,那么1s发送的位数为:1s/2.325us=430107.5268,约等于430.107Kbit/s。即使去掉MCU内核去处理其他代码的时间,黄色的线间距较宽的地方就是内核去处理其他代码的时候,那么发送8个bit位用时14us,发送一位需要的时间就是14us/8=1.75us,1s发送的数据位是1s/1.75us=571428.5714,约等于571.428Kbit/s;
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !