1.写在前面
项目需要用到模拟信号转换(ADC),由于精度、速度及准确度都要求比较高,故选择外设独立ADC芯片。外置独立ADC价格上都比较贵,首先考虑的是ADI的器件,经过综合价格、性能、采购渠道等,最终选择AD7683/AD7684。AD7683和AD7684的区别是前者是伪差分输入,后者是真差分输入,其他性能基本一致,程序兼容。ADS8317是德州仪器(TI)产的,与AD7684兼容,都是真差分输入,程序三者都兼容。
2.器件特点
主要特点:
1)转换精度:16bit
2)转换速率:100kSPS
3)独立供电和参与源
4)伪差分输入/真差分输入
5)3线spi控制接口
6)使用简单,无复杂的寄存器配置,直接通过spi获取转换数据
7)8 pin msop封装
3.驱动程序
通过芯片数据手册可以知道,对于驱动程序,只需将spi总线实现,即是成功了90%。上一篇博客中我有写到spi的封装过程和相关代码,那么这次使用起来应该非常方便和易理解,也体现下这样做的好处,具体查看上一篇文章:“spi抽象/硬件spi”
1)驱动一个器件,首先是查看器件时序图,AD7683的spi时序图如图:
具体分析:
a)读取一个完整的16bit数据,至少需要24个时钟周期;
b)有效数据为中间16bit,注意前后无效bit数据。
ADI的器件特别喜欢这样的非标spi(避专利?),很多人第一想法就是用模拟spi去读取,ADI官方一些例程大部分也是模拟spi,其实都是可以用硬件spi去实现,取其中有效数据即可。当然用模拟spi也很容易实现。下面是以stm32f1单片机为硬件平台,利用上一文章“spi模型/硬件spi”中的spi总线模型驱动AD7683。
#include <string.h>
#include "spi_hw.h"
/* ad7684 spi device */
static struct spi_dev_device ad7684_spi_dev[3];
static void ad7684_spi_cs0(uint8_t state)
{
if (state)
{
GPIO_SetBits(GPIOC, GPIO_Pin_6);
}
else
{
GPIO_ResetBits(GPIOC, GPIO_Pin_6);
}
}
static void ad7684_spi_cs1(uint8_t state)
{
if (state)
{
GPIO_SetBits(GPIOC, GPIO_Pin_8);
}
else
{
GPIO_ResetBits(GPIOC, GPIO_Pin_8);
}
}
static void ad7684_spi_cs2(uint8_t state)
{
if (state)
{
GPIO_SetBits(GPIOA, GPIO_Pin_11);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_11);
}
}
int8_t ad7684_init(struct spi_bus_device *spi_bus)
{
GPIO_InitTypeDef GPIO_InitStructure;
if (NULL == spi_bus)
{
return -1;
}
/* spi cs */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC |RCC_APB2Periph_GPIOA ,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_6);
GPIO_SetBits(GPIOC,GPIO_Pin_8);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_11);
/* device init */
ad7684_spi_dev[0].spi_cs = ad7684_spi_cs0;
ad7684_spi_dev[0].spi_bus = spi_bus;
ad7684_spi_dev[1].spi_cs = ad7684_spi_cs1;
ad7684_spi_dev[1].spi_bus = spi_bus;
ad7684_spi_dev[2].spi_cs = ad7684_spi_cs2;
ad7684_spi_dev[2].spi_bus = spi_bus;
return 0;
}
/**
* @brief read ad data from ad7684.
* @param \index ad7684 device,1 or 2 or 3
* @retval none
*/
uint16_t ad7684_read(uint8_t index)
{
uint8_t send_buff[3] = {0};
uint8_t recv_buff[3] = {0};
if (index < 1 || index > 3)
{
return 0;
}
spi_send_recv(&ad7684_spi_dev[index-1], send_buff, recv_buff, 3);
return ((((recv_buff[0] << 16) | (recv_buff[1] << 8) | recv_buff[2])>>2)&0xffff);
}
spi的配置模式:
a)速率:SPI_BaudRatePrescaler_8分频;
b)模式:L 1或者 H 3;
c)方向:全双工;
d)其他查看上一文章源码。
具体分析:
a)源码是之前AD7684程序,命名没有修改,程序百分百兼容;
b)上面是一根spi总线挂了3片AD7683,定义了3个spi设备结构体和一个spi总线结构体;初始化了3个片选;总线初始化由“spi_core.h”及“spi_hw.c”实现,无需再调试修改;
c)初始化函数是片选IO设置及指针赋值;
d)读函数,用的API是“spi_send_recv”,从时序图也可看出;此时的“发送”动作并不是真正的发送,只是用来产生接收数据的时钟信号;
——第一个参数即为前面初始化的3个设备之一
——send_buff:“发送”3个字节,产生24个时钟信号(非真正发送)
——recv_buff:接收3个字节的返回值,高位在前
e)返回值,“return ((((recv_buff[0] << 16) | (recv_buff[1] << 8) | recv_buff[2])>>2)&0xffff)”通过移位(<</>>)和与(&)动作获取16位有效的数据。
4.总结
1)主要是体现上一文章中,spi封装的好处,驱动一个新的spi器件,或者多个spi器件时,可以非常方便地实现;
2)学会看器件时序图,非标准spi大部分情况都可以用标准spi实现;
3)摆脱重复调试spi的无用工作;
4)方便移植驱动程序到新的平台。
5.相关源码
[1] https://github.com/Prry/drivers-for-mcu
6.参考
[1] http://blog.youkuaiyun.com/qq_20553613/article/details/78998617