沁恒CH32V307使用记录:SPI基础使用

2023-10-30

目的

SPI是单片机中比较常用的一个功能。这篇文章将对CH32V307中相关内容进行说明。

本文使用沁恒官方的开发板 (CH32V307-EVT-R1沁恒RISC-V模块MCU赤兔评估板) 进行演示。

基础说明

SPI的基础概念见下面文章:
SPI基础概念:https://blog.csdn.net/Naisu_kun/article/details/118102844

从上面文章中可以看到SPI主要涉及两条通讯线(MOSI、MISO)、一条时钟线(SCLK)、一条片选线(CS或NSS,非必需)。

除了接线,SPI使用上还需要了解极性和相位的概念,在此之上理解读写时序逻辑等内容。

另在在SPI读数据时需要注意,这时候虽然数据是从机发送的,但时钟还是主机给出的,所以往往读数据时主机还需要通过写数据(任意均可)来给出时钟信号。

使用演示

下面是个简单的使用SPI发送数据的示例:

#include "debug.h"

void SPI1_Master_Init(void) {
    // 初始化时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);

    // 初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;         // CS
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 这里使用普通推挽输出模式,而不是复用模式,所以下面需要手动控制
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure);
    GPIO_SetBits( GPIOA, GPIO_Pin_4);                 // 拉高不工作

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // SCK
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // MISO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; // MOSI
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure);

    // 初始化SPI1
    SPI_InitTypeDef SPI_InitStructure = { 0 };

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 全双工模式
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       // 作为主机
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   // 8bit数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                          // 时钟不通讯时低电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                        // 在第一个边缘进行采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                           // 片选引脚手动控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // SPI外设时钟源32分频
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                  // 传输时高比特在前
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);                                 // 初始化SPI1设置

    SPI_Cmd(SPI1, ENABLE);                                              // 启动SPI1
}

int main(void) {
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);

    SPI1_Master_Init();                                                       // 初始SPI1

    while(1) {
        GPIO_ResetBits( GPIOA, GPIO_Pin_4);                                   // 拉低片选信号,开始SPI通讯
        u8 data[4] = {0x00, 0xff, 0x22, 0x33};
        for (int i = 0; i < 4; i++) {
            while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) { } // 等待可以写数据到发送缓冲
            SPI_I2S_SendData(SPI1, data[i]);                                  // 写数据
        }
        Delay_Us(6);                                                          // 写数据并不代表发送完成,发送完成前后不能拉高片选信号
                                                                              // 目前设置下SPI时钟为 96/32 = 3MHz, 每发送一个字节时间为 8/3 = 2.667us
                                                                              // 通常为了保险最好等待两个字节以上时间
        GPIO_SetBits( GPIOA, GPIO_Pin_4);                                     // 拉高片选信号,结束SPI通讯

        Delay_Us(20);
    }
}

使用逻辑分析仪抓取上面代码实际运行中的一帧数据如下:
在这里插入图片描述
需要注意的是逻辑分析仪的采样速度要远远大于SPI时钟速度,不然解析数据时可能出现一个bit的异常。

下面代码是使用SPI1作为主机,SPI2作为从机的实验,除了上面的发送数据外还进行了数据读取操作:

#include "debug.h"

void SPI1_Master_Init(void) {
    // 初始化时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);

    // 初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;         // CS
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 这里使用普通推挽输出模式,而不是复用模式,所以下面需要手动控制
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure);
    GPIO_SetBits( GPIOA, GPIO_Pin_4);                 // 拉高不工作

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // SCK
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // MISO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; // MOSI
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure);

    // 初始化SPI1
    SPI_InitTypeDef SPI_InitStructure = { 0 };

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 全双工模式
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       // 作为主机
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   // 8bit数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                          // 时钟不通讯时低电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                        // 在第一个边缘进行采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                           // 片选引脚手动控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // SPI外设时钟源64分频
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                  // 传输时高比特在前
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);                                 // 初始化SPI1设置

    SPI_Cmd(SPI1, ENABLE);                                              // 启动SPI1
}

void SPI2_Slave_Init(void) {
    // 初始化时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    // 初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;    // CS
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 片选信号为低才工作
    GPIO_Init( GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // SCK
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; // MISO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; // MOSI
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOB, &GPIO_InitStructure);

    // 初始化SPI2
    SPI_InitTypeDef SPI_InitStructure = { 0 };

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 全双工模式
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;                        // 作为从机
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   // 8bit数据
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                          // 时钟不通讯时低电平(和主机保持一致)
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                        // 在第一个边缘进行采样(和主机保持一致)
    SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;                           // 片选信号依赖外部信号
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // 从模式下其实不用关心这个
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                  // 传输时高比特在前
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI2, &SPI_InitStructure);                                 // 初始化SPI2设置

    SPI_Cmd(SPI2, ENABLE);                                              // 启动SPI2
}

int main(void) {
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);

    // 接线方式: PA4-PB12  PA5-PB13  PA6-PB14  PA7-PB15

    SPI1_Master_Init();                                                       // 初始化SPI1作为主机
    SPI2_Slave_Init();                                                        // 初始化SPI2作为从机

    while(1) {
        u8 data1 = 0;
        u8 data2 = 0;

        GPIO_ResetBits( GPIOA, GPIO_Pin_4);                                   // 拉低片选信号,开始SPI通讯

        while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) { }
        SPI_I2S_SendData(SPI2, 0x33);                                         // 从机先写一个数据到发送区,这样当主机开始发送消息产生时钟信号时,该数据就会被传输

        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) { }
        SPI_I2S_SendData(SPI1, 0x22);                                         // 主机发送数据给从机

        while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) { }    // 等待可以读取接收到的数据
        data2 = SPI_I2S_ReceiveData(SPI2);                                    // 读取接收到的数据

        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) { }
        data1 = SPI_I2S_ReceiveData(SPI1);

        GPIO_SetBits( GPIOA, GPIO_Pin_4);                                     // 拉高片选信号,结束SPI通讯

        printf("rx1 = %d, rx2 = %d \r\n", data1, data2);

        Delay_Ms(500);
    }
}

在这里插入图片描述

其它补充

SPI除了最基础的上面查询的方式使用外,本身也可以使用中断方式或者DMA方式来使用。不过一般来说只是作为主机使用的话,因为SPI的速度很快,所以很多时候上面方式就够用了。如果有需要的话中断和DMA的方式使用起来也不难。

总结

单片机本身的SPI外设使用比较简单,真正工作时进行SPI通讯更多的是要能够理解SPI的时序逻辑概念,能够读懂芯片手册上的时序逻辑图。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

沁恒CH32V307使用记录:SPI基础使用 的相关文章

随机推荐

  • PostgreSQL的基本使用整理

    我是目录 1 数据库操作 2 表操作 3 Schema 模式 4 如何备份PG数据库 5 用户操作 6 常用命令总结 1 数据库操作 1 创建数据库 create database mydb 2 查看所有数据库 list 或 l 3 切换当
  • [网络安全提高篇] 一一九.恶意软件动态分析经典沙箱Cape的安装和基础用法详解

    终于忙完初稿 开心地写一篇博客 网络安全提高班 新的100篇文章即将开启 包括Web渗透 内网渗透 靶场搭建 CVE复现 攻击溯源 实战及CTF总结 它将更加聚焦 更加深入 也是作者的慢慢成长史 换专业确实挺难的 Web渗透也是块硬骨头 但
  • Flask 项目用到的插件和技术

    项目地址 https github com laoqiu pypress 作者 老秋 老秋是05年开始从事前端设计的设计师 于07年喜欢上python 目前从事python项目开发 学习并使用过一些流行框架 如django webpy fl
  • 不到 20 人的 IT 公司该去吗?

    周末就不分享技术了 今天早上在知乎看到一个挺有意思的话题 不到 20 人的 IT 公司该去吗 回答区有一位老哥分享了自己在一个20 来人的小公司的奇葩工作经历 分享一下 下面是正文 刚到西安有幸加入了一个 20 来人的 it 公司 本来是不
  • 教育大数据总体解决方案(1)

    目录 一 方案背景 1 1 以教育现代化支撑国家现代化 1 2 教育信息化是教育现代化重要内容和标志 1 3 大数据驱动教育信息化发展 1 4 政策指导大数据推动教育变革 1 5 教育大数据应用生态服务教育现代化 二 建设需求 2 1 地区
  • 二叉树的最大深度C++

    Definition for a binary tree node struct TreeNode int val TreeNode left TreeNode right TreeNode int x val x left NULL ri
  • 使用HTTP爬虫ip中的常见误区与解决方法

    在如今的互联网时代 为了保障个人隐私和实现匿名浏览 许多人选择使用HTTP爬虫ip 然而 由于缺乏了解和使用经验 常常会出现一些误区 本文将为大家介绍使用HTTP爬虫ip过程中常见的误区 并提供相应的解决方法 帮助大家更好地使用HTTP爬虫
  • 用赋值代替 protobuf CopyFrom()

    用赋值代替 protobuf CopyFrom 示例 Replace protobuf CopyFrom with assignment protobuf 生成的 C 代码中 因为 CopyFrom 可以接受任何 Message 作为参数
  • 都2023年啦~用python来玩一次股票.....

    人生苦短 我用python 这不是2023年已经来了吗 总不能空着手回去吧 这次简单用python来玩一下股票 本章源码 更多电子书点击文末名片 准备工作 我们需要使用这些模块 通过pip安装即可 后续使用的其它的模块都是Python自带的
  • HttpRunner3.x(6)参数化数据驱动

    在进行接口测试时 有时候需要给一个接口传入多组数据 这时候就会用到参数化数据驱动 HttpRunner v3 x开始 测试用例和测试用例集都可以实现参数化数据驱动 需要使用parameters关键字 定义参数名称并指定数据源取值方式 数据源
  • 开源的7大理念

    点击上方 开源社 关注我们 作者 卫剑钒 编辑 Corrie 软件正在慢条斯理地吞噬世界 开源正在慢条斯理地吞噬软件业 软件正在吞噬世界 是的 对于购物 吃饭 健身 交停车费都需要使用软件的年代 对于平均每人每天都要花费5到6个小时使用手机
  • 字节流文件读取

    import java io public class Test public static void main String args try 源文件 FileInputStream inputStream new FileInputSt
  • 初步了解ES

    一 ES基础查询 1 es基础查询 1 1 准备数据 准备数据 PUT test index doc 1 name 顾老二 age 30 from gu desc 皮肤黑 武器长 性格直 tags 黑 长 直 PUT test index
  • 39条常见的Linux系统简单面试题

    本文系转载 原文链接 http www cnblogs com chengjian physique p 8313175 html 1 如何看当前Linux系统有几颗物理CPU和每颗CPU的核数 答 root centos6 10 55 3
  • CAN记录仪应用—如何实现特种车辆远程数据监控

    在目前生活中 路边随处可以看到一些特种车辆 比如洒水车 消毒车等 在大家看不到的领域种 特种车在一些条件艰苦的条件下也发挥着重要的作用 比如掘进车 矿下防爆车 那么工作人员如何远程获得车辆的数据呢 来可电子提供可靠的解决方案 车联网综合网关
  • vue移动端绑定click事件失效问题

    原因是使用了better scroll 它会阻止touch事件 所以在配置中需要加上click true 例 this scroll new BScroll this refs search click true
  • 彩条发生模块(verilog)

    像素时钟输入 1280x720 60P的像素时钟为74 25MHz Description 彩条发生模块
  • 跨境电商如何防关联?关联因素有哪些?

    相信许多想要入局跨境电商小伙伴都遇到 防关联 的难题 想要在跨境路上驰骋无阻 那你一定不能忽略各平台的多账号防关联问题 下面小编为大家介绍 我的实操解决防关联的经验 往下看 通常防关联需要从以下因素进行考虑 1 浏览器指纹 当我们在网络上浏
  • 二分查找算法的Python实现(头歌教学实践平台)

    第1关 二分查找算法 任务描述 本关任务 编写代码实现二分查找算法 相关知识 为了完成本关任务 你需要掌握 1 查找的基本概念 2 如何实现二分查找 查找的基本概念 如果数据项被保存在如列表这样的集合中 我们会称这些数据项具有线性或者顺序关
  • 沁恒CH32V307使用记录:SPI基础使用

    文章目录 目的 基础说明 使用演示 其它补充 总结 目的 SPI是单片机中比较常用的一个功能 这篇文章将对CH32V307中相关内容进行说明 本文使用沁恒官方的开发板 CH32V307 EVT R1沁恒RISC V模块MCU赤兔评估板 进行