使用STM32 DMA+SPI读取74HC165数据的正确方法

单片机读取74HC165芯片的数据在网上一搜一大把,但是能做到高速,高效,低cpu占用的做法目前我没有见到,所以在此我补充一篇文档,如果能帮上大家那肯定是再好不过啦。

众所周知,74HC165是并入转串出的芯片,这类芯片其实还有很多,甚至包括传入转并出的595,思路与本文雷同。

上图是芯片的引脚图,LD,是锁存电平,低电平锁存,高电平读取锁存数据,CE低电平时芯片正常工作,相当于使能脚。那么对于STM32来说,读取和级联的正确的接法应该如下:

我用文字描述一下:

NSS就是随便用一个IO口,比如PA4连接,需要连接所有的LD脚;

SPICLK就是时钟 对于SPI1来说就是PA5,需要连接所有的CLK脚;

MISO串行数据读取,连接PA6; 在165上仅连接第一片的SO,后面的级联即可;

MOSI数据输出,连接PA7,在165上需要连接所有的CE;

,有人会问,165是只读取数据的,为什么还要连接SPI的输出端呢,这是因为CE需要低电平信号作为使能,如果不接SPI的数据,就需要一个额外的IO口提供这个信号,当然,也可以直接接地。但是经过长期反复测试,你听我的肯定是最好的。不要接地!

以下为编程思路:

1,让spi处于SPI_Direction_2Lines_RxOnly模式,此时启动后clk会持续发送时钟脉冲,但是LD信号需要在每次读取前发送,ce可以直接给低电平。问题是什么时候给,那就需要中断,由于连续工作,中断频繁,cpu开销很大,此方案最终验证不合格。

2,让spi处于SPI_Direction_2Lines_RxOnly模式,ce直接低电平,使用dma接收数据,在数据接收完毕后产生一个中断,此时需要将spi停止,再给个LD锁存信号,然后再重新开启DMA和SPI开启下一轮读取,此方案大大减少了cpu的占用,提高了速度,但是使用中发现,读取的数据与内存的位置始终是错位的,观测时序后发现实际上是DMA与SPI启动的不同步造成的。但是勉强是可以用的了。

3。让spi处于SPI_Direction_2Lines_FullDuplex模式,DMA读取,在读取前使用SPI发送一个数据

SPI_I2S_SendData(SPI2, 0x00);,发送数据的次数取决于芯片的数量,这是因为要利用发送的时钟同时将数据读取回来。此时可以关闭dma中断,因为不需要在中断里停止spi了。需要保存spi一致开启,因为在这个模式下,如果不发送数据,是不会自动发送时钟脉冲的,所以脉冲的启动实际上需要数据发送本身来驱动了。此时你会发现只要发送一个字节,就会发送对应的时钟,此时也可以同时读取一个字节的数据了。由于没有了中断,cpu的负担又少了一些,但是运行后发现,数据位置是错位了,而且读取的数据少于实际发送的数量,监测时序后发现还是DMA与SPI的硬件启动时间不同步造成的。

4.经过上面一系列的尝试,经过3天,最终的结局来了。spi需要处于SPI_Direction_2Lines_FullDuplex模式,DMA的SPI双通道同时工作,他会内部同步协调,DMA负责发送0x00,有几片165就发送几个0,在CE端实际上呈现的就是连续的低电平,DMA的RX通道负责自动接收。DMA模式设置为DMA_Mode_Normal,不要循环读。在每次启动DMA之前做一次LD的电平脉冲,启动之后立刻处理上一次回来的数据。这后面就做其他的事就行了。不需要中断。只要有合适的任务间隔,该方法可以用最低的cpu负担处理165的数据。

上代码:

// SPI1 初始化配置
void SPI1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

    // 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);

    // 配置SCK, MISO, MOSI引脚复用推挽输出PA5-CLK PA6-MISO PA7-MOSI
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // SPI初始化设置
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    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_2;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE); //使能SPI外设
}

// DMA发送初始化
void SPI1_DMA_TX_Init(uint8_t *tx_buffer, uint16_t size) {
    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA时钟

    DMA_DeInit(DMA1_Channel3);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = size;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel3, &DMA_InitStructure);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); //使能SPI的DMA发送请求
}

// DMA接收初始化
void SPI1_DMA_RX_Init(uint8_t *rx_buffer, uint16_t size) {
    DMA_InitTypeDef DMA_InitStructure;

    DMA_DeInit(DMA1_Channel2);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = size;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);

    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE); //使能SPI的DMA接收请求
}

// 启动一次DMA传输并等待完成
void SPI1_TransmitReceive_DMA(uint8_t *tx_data, uint8_t *rx_data, uint16_t len) {
  
NSS=0;//PA4引脚 接LD

    // 关闭DMA通道
    DMA_Cmd(DMA1_Channel2, DISABLE);
    DMA_Cmd(DMA1_Channel3, DISABLE);
    // 设置缓冲区大小
    DMA_SetCurrDataCounter(DMA1_Channel2, len);
    DMA_SetCurrDataCounter(DMA1_Channel3, len);//len就是165的片数
NSS=1;
    // 启动DMA通道
    DMA_Cmd(DMA1_Channel2, ENABLE);
    DMA_Cmd(DMA1_Channel3, ENABLE);
//这里可以处理上一次回来的数据    
}
//只要在合适的任务间隔中调用SPI1_TransmitReceive_DMA就行了

精简后就是上面这些,在合适的任务间隔中调用SPI1_TransmitReceive_DMA就行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csaaa2005

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值