单片机读取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就行了。

1354

被折叠的 条评论
为什么被折叠?



