历史上的今天
返回首页

历史上的今天

今天是:2025年04月11日(星期五)

正在发生

2019年04月11日 | STM32之SPI通信

2019-04-11 来源:eefocus

之前一直对SPI通信一知半解,所以想抽空把它搞得明白一些。考虑到之前是结合Flash芯片来学的,十分不直观,而且主要把时间和精力都花在Flash芯片的datasheet和驱动上了,SPI通信也没学好。所以这次就考虑用4位数码管显示模块,模块是直接买的现成的,如下图所示,这样可以简化操作,把精力聚焦到学习的核心–SPI通信本身上来。 


 这里写图片描述 


该模块是用2片74HC595串联驱动的,一片用来控制数码管的位选(U1),一片用来控制数码管的段选(U2)。接口比较简单,总共5个引脚,2个引脚分别接VCC和GND,DIO用来接收串行数据的输入,SCLK用来接收同步时钟,每个SCLK上升沿74HC595内部的移位寄存器会移一位,RCLK用来控制数据的输出,每个RCLK上升沿74HC595内部的移位寄存器的数据会被放进存储寄存器并输出到外部引脚QA~QH上。而QH’是串行输出引脚,该引脚会接收最高位的溢出,从而实现多片74HC595的级联。 

 

这里写图片描述 


当两片74HC595串联时,先发八位数据用于段选,再发八位数据用于位选,然后RCLK上升沿,就可以驱动某位数码管显示某个字符,通过动态扫描数码管,由于人眼的视觉暂停效果,就可以实现4位数码管的同时显示。先用通用I/O来实现该数码管的驱动,程序如下: 

头文件74HC595.h


#ifndef __74HC595_H__

#define __74HC595_H__


#include"stm32f10x_lib.h"       //包含所有的头文件

#include


// 4-Bit LED Digital Tube Module

#define  HC595_SCLK_PIN  GPIO_Pin_5   // SPI1_SCK  PA5

#define  HC595_RCLK_PIN  GPIO_Pin_12   // SPI1_NSS  PA4

#define  HC595_DIO_PIN   GPIO_Pin_7   // SPI1_MOSI PA7

#define  HC595_GPIO           GPIOA 

#define  HC595_RCLK_GPIO      GPIOB 

#define  HC595_RCC            RCC_APB2Periph_GPIOA 

#define  HC595_RCLK_RCC       RCC_APB2Periph_GPIOB 


void HC595_Init(void);

void HC595_SendByte(u8 data);

u8 HC595_Display(u16 num, u8 dp);


#endif


源文件74HC595.c


// 用于HC595实现的4Bit-LED Digit Tube Module

// 注意:该4位数码管是共阳的!

#include "74HC595.h"


// 码表

const u8 digitTable[] = 

{

// 0       1       2       3       4       5       6       7       8       9

    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90,

// A        b       C     d     E     F    -

    0x8C, 0xBF, 0xC6, 0xA1, 0x86, 0xFF, 0xbf

};


/*******************************************************************************

* Function Name  : HC595_Init

* Description    : 初始化HC595

* Input          : None

* Output         : None

* Return         : None

*******************************************************************************/

void HC595_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;            //声明一个结构体变量

    RCC_APB2PeriphClockCmd(HC595_RCC | HC595_RCLK_RCC, ENABLE); //使能HC595的时钟    


    //74HC595, SCLK RCLK DIO 推挽输出

    GPIO_InitStructure.GPIO_Pin = HC595_SCLK_PIN| HC595_DIO_PIN;       

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    //管脚频率为50MHZ

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     //输出模式为推挽输出

    GPIO_Init(HC595_GPIO, &GPIO_InitStructure);              //初始化寄存器   


    GPIO_InitStructure.GPIO_Pin = HC595_RCLK_PIN;       

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    //管脚频率为50MHZ

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     //输出模式为推挽输出

    GPIO_Init(HC595_RCLK_GPIO, &GPIO_InitStructure);     //初始化寄存器   


}


/*******************************************************************************

* Function Name  : HC595_SendByte

* Description    : 发送一个字节

* Input          : data

* Output         : None

* Return         : None

*******************************************************************************/

void HC595_SendByte(u8 data)

{

    u8 i;

    for (i=8; i>=1; i--)

    {

        // 高位在前

        if (data&0x80) 

            GPIO_SetBits(HC595_GPIO, HC595_DIO_PIN); 

        else 

            GPIO_ResetBits(HC595_GPIO, HC595_DIO_PIN);

        data <<= 1;

        // SCLK上升沿

        GPIO_ResetBits(HC595_GPIO, HC595_SCLK_PIN);

        GPIO_SetBits(HC595_GPIO, HC595_SCLK_PIN);

    }

}


/*******************************************************************************

* Function Name  : HC595_Display

* Description    : 显示4位数字(包括小数点)

* Input          : num: 0000 - 9999 

*                  dp: 小数点的位置1-4 

* Output         : None

* Return         : 正常返回0,错误返回1

*******************************************************************************/

u8 HC595_Display(u16 num, u8 dp)

{

    u8 qian = 0, bai = 0, shi = 0, ge = 0;


    // 对显示的参数范围进行检查

    if (num > 9999 || dp > 4)

        //报错

        return 1;


    // 对num进行分解

    qian = num / 1000;

    bai = num % 1000 / 100;

    shi = num % 100 / 10;

    ge = num % 10;


    // 千位

    if(dp == 1)

        HC595_SendByte(digitTable[qian] & 0x7F);

    else

        HC595_SendByte(digitTable[qian]);

    HC595_SendByte(0x08);       

    GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

    GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);


    // 百位

    if(dp == 2)

        HC595_SendByte(digitTable[bai] & 0x7F);

    else

        HC595_SendByte(digitTable[bai]);

    HC595_SendByte(0x04);       

    GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

    GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);


    // 十位

    if(dp == 3)

        HC595_SendByte(digitTable[shi] & 0x7F);

    else

        HC595_SendByte(digitTable[shi]);

    HC595_SendByte(0x02);       

    GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

    GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);


    // 个位

    if(dp == 4)

        HC595_SendByte(digitTable[ge] & 0x7F);

    else

        HC595_SendByte(digitTable[ge]);

    HC595_SendByte(0x01);       

    GPIO_ResetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);

    GPIO_SetBits(HC595_RCLK_GPIO, HC595_RCLK_PIN);


    return 0;

}


接下来就可以把重心都放在STM32的SPI外设上了,首先需要读一下STM32F10x的参考手册的SPI(串行外设接口)部分,这样对SPI就可以有较好的理解,比较重要的是要看懂SPI的结构框图和主从机通信的示意图,如下: 

这里写图片描述 
这里写图片描述 

这个理解以后,我们就可以参考《STM32F103XX固件库用户手册》的SPI部分来实现STM32的SPI外设配置和收发数据了,具体代码如下: 

头文件74HC595_SPI.h


#ifndef __74HC595_SPI_H__

#define __74HC595_SPI_H__


#include"stm32f10x_lib.h"       //包含所有的头文件

#include


// 4-Bit LED Digital Tube Module

// 引脚                                // SPI1       4位数码管            

#define  HC595_NSS_PIN   GPIO_Pin_4    // SPI1_NSS   未用

#define  HC595_SCK_PIN   GPIO_Pin_5    // SPI1_SCK   SCLK

#define  HC595_MISO_PIN  GPIO_Pin_6    // SPI1_MISO  未用 

#define  HC595_MOSI_PIN  GPIO_Pin_7    // SPI1_MOSI  DIO

#define  HC595_RCLK_PIN  GPIO_Pin_12   //            RCLK


// 端口

#define  HC595_SPI1_GPIO      GPIOA  

#define  HC595_RCLK_GPIO      GPIOB   

// 时钟

#define  HC595_SPI1_RCC  RCC_APB2Periph_GPIOA

#define  HC595_RCLK_RCC  RCC_APB2Periph_GPIOB 


void HC595_Init(void);

void HC595_SendByte(u8 data);

u8 HC595_Display(u16 num, u8 dp);


#endif


源文件74HC595_SPI.c


/************************省略部分代码见(74HC595.c)************************/

/*******************************************************************************

* Function Name  : HC595_Init

* Description    : 初始化HC595

* Input          : None

* Output         : None

* Return         : None

*******************************************************************************/

void HC595_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;    

  SPI_InitTypeDef SPI_InitStructure;                                  // 声明一个结构体变量

    // 不需要开启AFIO时钟

    RCC_APB2PeriphClockCmd(HC595_SPI1_RCC | HC595_RCLK_RCC | RCC_APB2Periph_SPI1, ENABLE);  // 使能HC595及SPI1的时钟  


    //74HC595, SPI1_NSS、SPI1_SCK、SPI1_MOSI 

    GPIO_InitStructure.GPIO_Pin = HC595_NSS_PIN | HC595_SCK_PIN |HC595_MOSI_PIN;       

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       // 管脚频率为50MHZ

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         // 输出模式为复用推挽输出

    GPIO_Init(HC595_SPI1_GPIO, &GPIO_InitStructure);        // 初始化寄存器   


    //74HC595, SPI1_MISO

    GPIO_InitStructure.GPIO_Pin = HC595_MISO_PIN;       

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   // 输入模式为浮空输入

    GPIO_Init(HC595_SPI1_GPIO, &GPIO_InitStructure);        // 初始化寄存器   


    //74HC595, RCLK

    GPIO_InitStructure.GPIO_Pin = HC595_RCLK_PIN;       

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       // 管脚频率为50MHZ

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        // 输出模式为复用推挽输出

    GPIO_Init(HC595_RCLK_GPIO, &GPIO_InitStructure);        // 初始化寄存器   


    /* Initialize the SPI1 according to the SPI_InitStructure members */

    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

    // 第一步:设置主从模式和通信速率

    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

    // SPI_NSS_Hard时需要外部电路把NSS接VCC, SPI_NSS_Soft时SPI外设会将SSM和SSI置位

    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

    // 实测波特率最低为SPI_BaudRatePrescaler_8,否则出错

    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;


    // 第二步:设置数据格式

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

    // MSB在前还是LSB在前要根据码表和数码管与74HC595的接法来定

    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;


    // 第三步:设置时钟和极性

    // 当SPI_CPOL_Low且SPI_CPHA_2Edge出错

    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;

    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;

    //第四步:其它,CRC校验,可靠通信,这步可以不设置

    SPI_InitStructure.SPI_CRCPolynomial = 7;

    SPI_Init(SPI1, &SPI_InitStructure);


    /* Enable SPI1 */

  SPI_Cmd(SPI1, ENABLE);

}


/*******************************************************************************

* Function Name  : HC595_SendByte

* Description    : 发送一个字节

* Input          : data

* Output         : None

* Return         : None

*******************************************************************************/

void HC595_SendByte(u8 data)

{

    SPI_I2S_SendData(SPI1, data);

    while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));

}

/************************省略部分代码(见74HC595.c)************************/


这样就大工告成啦,STM32的SPI外设还是比较简单的,尤其是通过库函数来调用。用数码管模块这种简单的可视化工具,我们就可以更好的研究通信协议本身的特性啦,后续我还会用这种方式来学习其它的通信协议,好了,”talk is cheap, show me the code”!

推荐阅读

史海拾趣

EOREX公司的发展小趣事

随着环保意识的日益提高,EOREX公司积极响应国家的绿色发展战略,将环保理念融入到产品研发和生产过程中。他们采用环保材料和绿色生产工艺,减少产品对环境的影响。同时,EOREX还加大了对环保技术的研发投入,推出了一系列具有环保功能的电子产品。这些举措不仅提升了公司的品牌形象和社会责任感,还为公司的可持续发展奠定了坚实的基础。

请注意,以上故事均为虚构内容,旨在展示一个电子公司可能的发展路径和策略。在实际应用中,每个公司的发展故事都有其独特性和复杂性。

ALLEN BRADLEY公司的发展小趣事

为了进一步提升公司的综合实力和市场竞争力,EOREX公司积极开展跨界合作。他们与汽车制造商、通信设备商等多个行业的领军企业建立了紧密的合作关系,共同研发和推广具有创新性和前瞻性的电子产品。这些跨界合作不仅为EOREX带来了更多的商业机会和市场份额,还推动了整个电子行业的创新和发展。

Advanced Power Solutions公司的发展小趣事

随着全球环保意识的提高,Advanced Power Solutions公司开始关注电源管理技术的环保性能。公司投入大量资源研发绿色、低碳的电源管理产品,并成功推出了一系列符合环保标准的新产品。这些产品不仅受到了消费者的欢迎,也赢得了政府和环保组织的认可。公司的环保理念和创新精神为其赢得了良好的社会声誉。

中电熊猫(CEC)公司的发展小趣事

在智能制造领域,中电熊猫也取得了显著进展。2010年,中电熊猫开始研发液晶面板工厂的智能运储系统,打破了以往依赖国外供应商的局面。到了2011年,中电熊猫成功完成了国内首条高世代液晶面板智能运储系统的研发,并逐渐成为国内该系统的主要供应商。此外,中电熊猫还在液晶玻璃生产线系统等方面实现了国产化研发,提升了整体产业的竞争力。

科通(COMTEK)公司的发展小趣事

在多年的努力下,科通技术逐渐成长为一家具有影响力的电子企业。为了进一步提升公司的竞争力和市场地位,科通技术积极筹备IPO上市。虽然公司在上市过程中遇到了一些波折和挑战,但最终成功实现了上市目标。未来,科通技术将继续坚持创新驱动、市场导向的发展战略,不断提升自身的核心竞争力,为客户提供更加优质的产品和服务。

请注意,由于篇幅限制,上述故事仅为简要概括,并未涵盖所有细节。同时,由于信息来源的不确定性,部分故事可能无法完全还原真实情况。

喜美克斯(Cvilux)公司的发展小趣事

喜美克斯公司深知人才是企业发展的核心动力。因此,他们高度重视人才的引进和培养。公司建立了一套完善的人才选拔和培养机制,通过内部培训、外部引进等多种方式,不断为公司注入新鲜血液。同时,公司还为员工提供广阔的职业发展空间和优厚的福利待遇,激发了员工的工作热情和创造力。这些举措为喜美克斯公司的长期发展提供了坚实的人才保障。

问答坊 | AI 解惑

超强优惠价Altera USB Blaster下载线198元热卖中

超强优惠价Altera USB Blaster下载线198元热卖中超强优惠价Altera USB Blaster下载线198元热卖中   支持的Altera FPGA/CPLD器件:   Stratix II、Stratix II GX、Stratix GX及Stratix系列   Cyclone II及Cyclone ...…

查看全部问答>

飞思卡尔MC9S08DZ60中文参考手册

这是飞思卡尔公司HCS08系列8位MCU中的一款MC9S08DZ60的中文手册,飞思卡尔网站上有英文的手册,考虑到国内不少人还是习惯看中文的,把这篇中文的转过来,呵呵特别说明下: 虽然这是DZ60这一款MCU的中文手册,但由于飞思卡尔公司整个HCS08系列都是 ...…

查看全部问答>

怎样利用设计语言实现电路提高电路速度?

1. 采用并行结构。利用流水线方式; 2. 采用全同步设计; 3. 用CASE 语句 而不用带有优先级的IF ....ELSIF ....ELSIF…

查看全部问答>

【监控系统如何考虑防雷】

【监控系统如何考虑防雷】     笔者的观点是: 1) 全面防雷不是安防工程应该考虑的问题。有些防雷的文章,让安防工程全面考虑接闪,防静电和防雷电电磁感应问题,弄得安防弱电工程商头都大了,无所适从,只好把防雷任务交给“防雷专业 ...…

查看全部问答>

C#如何在WINCE系统下控制外部IO

我用的是三星2440芯片,有人说用VirtualAlloc()与VirtualCopy()函数实现,我查了很多资料不知道怎么写好,比如我的IO口是GB5,GB6,GB7,GB8下面是他们的相关地址: PORT B CONTROL REGISTERS (GPBCON, GPBDAT, GPBUP)       GPBCO ...…

查看全部问答>

0

攒分赚人品谢谢清洁工和大兔子…

查看全部问答>

点亮应急灯LED

刚点亮LED,本想上图但我用的是奔迈600手机,我想明天或者后天再上图,因为手机数据线在单位   这次做遇到最大的困难是我申请封装同我打开的不一样,我记得申通时是SOIC的就是一般的那种,可是一打开MAXIM邮件才发现是非常小的那种,这始我 ...…

查看全部问答>

求助

看了一些5438与rs485的例子,想看看怎么用RS485,但发现程序中没有特别的设置,只有一条 “P3SEL = 0x30;                       ...…

查看全部问答>

【聊聊DSP】分享我的DSP开发经验

看到DSP征文这个活动,很有兴趣! 活动进行快1周了,还没有人参与,是不是大家还在酝酿中呢,我先献丑了! -------------------------------------------------------------------------------------------------------------------------------- ...…

查看全部问答>