本文首先介绍龙芯1c库中封装的硬件SPI相关的几个接口函数,然后使用双路16位ADC芯片TM7705来测试硬件SPI相关接口是否正常工作,然后以linux为参考,分析了硬件SPI接口的要点,最后才是接口函数的源码清单。
接口简介
SPI初始化——spi_init()
函数原型
/*
* 初始化指定SPI模块
* @spi_info_p SPI模块信息
*/
void spi_init(ls1c_spi_info_t *spi_info_p);
入参的类型如下
// 硬件SPI信息
typedef struct
{
ls1c_spi_t SPIx; // SPI模块编号
unsigned long max_speed_hz; // 最大通信速度,单位hz
unsigned char cs; // 片选
unsigned char cpol; // 时钟极性
unsigned char cpha; // 时钟相位
}ls1c_spi_info_t;
结构体中成员可能的取值如下
// SPI模块编号
typedef enum
{
LS1C_SPI_0 = 0,
LS1C_SPI_1,
}ls1c_spi_t;
// 片选
#define LS1C_SPI_INVALID_CS (-1)
#define LS1C_SPI_CS_0 (0)
#define LS1C_SPI_CS_1 (1)
#define LS1C_SPI_CS_2 (2)
#define LS1C_SPI_CS_3 (3)
// 时钟极性和相位
#define SPI_CPOL_1 (1)
#define SPI_CPOL_0 (0)
#define SPI_CPHA_1 (1)
#define SPI_CPHA_0 (0)
使用示例
假设将TM7705接在SPI0的CS1上,通信速度为100khz,cpol=1,cpha=1,那么初始化SPI的代码如下
ls1c_spi_info_t tm7705_spi_info = {0};
tm7705_spi_info.SPIx = LS1C_SPI_0;
tm7705_spi_info.cs = LS1C_SPI_CS_1;
tm7705_spi_info.max_speed_hz = 100*1000;
tm7705_spi_info.cpol = SPI_CPOL_1;
tm7705_spi_info.cpha = SPI_CPHA_1;
spi_init(&tm7705_spi_info);
设置片选——spi_set_cs()
函数原型
/*
* 设置指定片选为指定状态
* @spi_info_p SPI模块信息
* @new_status 片选引脚的新状态,取值为0或1,即高电平或低电平
*/
void spi_set_cs(ls1c_spi_info_t *spi_info_p, int new_status);
使用示例
spi_set_cs(&tm7705_spi_info, 0);
把cs拉低
spi_set_cs(&tm7705_spi_info, 1);
把cs拉高
收发一个字节——spi_txrx_byte()
函数原型
/*
* 通过指定SPI发送接收一个字节
* 注意,在多任务的系统中,此函数需要互斥。
* 即保证在和某个从设备收发某个字节的过程中,不能被切换到其它任务同时与另外的在同一个SPI总线上的从设备通信
* 因为龙芯1c的每路SPI上可能接有不同的从设备,通信频率、模式等可能不同
* @spi_info_p SPI接口
* @tx_ch 待发送的数据
* @ret 收到的数据
*/
unsigned char spi_txrx_byte(ls1c_spi_info_t *spi_info_p, unsigned char tx_ch);
SPI的特性就是接收和发送是同时进行的,所以函数spi_txrx_byte()是收发一体的。如果只关注发送,那么可以忽略返回值。如果只是关注接收,那么可以把发送的内容设为0。如果发送和返回值都关心,那么入参和返回值都有意义。
使用示例
unsigned char tx = 0;
tx = TM7705_REG_DATA | TM7705_READ | channel;
spi_txrx_byte(&tm7705_spi_info, tx);
把需要发送的内容发送出去了,接收到的信息被忽略了,即函数spi_txrx_byte()的返回值被忽略了。
unsigned char rx[2] = {0};
rx[0] = spi_txrx_byte(&tm7705_spi_info, 0);
rx[1] = spi_txrx_byte(&tm7705_spi_info, 0);
只关注接收到的信息,发送的内容可以为任意值,这里选择发送0。
注意事项
龙芯1c的支持两路SPI,每路都有4个片选。在每路的不同片选上可能接有不同的SPI从设备,使用不同的时钟、cpol和cpha。
Linux中,同一个SPI不同片选上所有从设备的收发都是集中在一个线程中完成的。虽然解决了互斥问题,但是在每次收发之前,可能需要重新设置spi时钟,cpol和cpha等,具体是调用ls1x_spi_setup_transfer()来实现的。
如果不采用linux这种方式,即同一个SPI的不同片选上的设备分别由不同的任务来单独执行收发,这时就需要注意互斥了。比如出现一个任务还未收发完成,就调换到另外一个任务中对另外一个spi从设备收发数据。
Linux中的函数ls1x_spi_setup_transfer()中的内容已整合到龙芯1c库中的spi_init()了,所以当在不同设备间切换时,需要调用spi_init()和spi_set_cs()重新设置spi相关寄存器,然后再调用spi_txrx_byte()收发数据。
打印指定SPI模块的所有寄存器的值(调试用)——spi_print_all_regs_info()
函数原型
/*
* 打印指定SPI模块的所有寄存器的值
* @spi_info_p SPI模块信息
*/
void spi_print_all_regs_info(ls1c_spi_info_t *spi_info_p);
使用示例
spi_print_all_regs_info(&tm7705_spi_info);
将tm7705所在spi模块的所有寄存器信息打印出来,其中变量tm7705_spi_info已在前面初始化了。
测试示例——使用ADC芯片TM7705
如果对TM7705的电路和具体使用细节还不大了解,请先移步到
《【龙印】在龙芯1c上用TM7705+NTC热敏电阻实现温度测量 》http://blog.youkuaiyun.com/caogos/article/details/53126628
《【龙印】龙芯1c上双路16位AD芯片TM7705的linux驱动 》http://blog.youkuaiyun.com/caogos/article/details/53034196
实物图
重点代码分析
函数tm7705_init()
/*
* tm7705初始化
*/
void tm7705_init(void)
{
// 初始化DRDY和RESET引脚
gpio_init(TM7705_DRDY_PIN, gpio_mode_input);
gpio_init(TM7705_RESET_PIN, gpio_mode_output);
gpio_set(TM7705_RESET_PIN, gpio_level_high);
// 初始化SPI
// tm7705在SPI0的CS1上,通信速度为100khz,cpol=1,cpha=1
tm7705_spi_info.SPIx = LS1C_SPI_0;
tm7705_spi_info.cs = LS1C_SPI_CS_1;
/*
// SPI0 CS3
tm7705_spi_info.SPIx = LS1C_SPI_0;
tm7705_spi_info.cs = LS1C_SPI_CS_3;
*/
/*
// SPI1 CS0
pin_set_remap(LS1C_SPI_1_MISO_GPIO, PIN_REMAP_THIRD);
pin_set_remap(LS1C_SPI_1_MOSI_GPIO, PIN_REMAP_THIRD);
pin_set_remap(LS1C_SPI_1_CLK_GPIO, PIN_REMAP_THIRD);
pin_set_remap(LS1C_SPI_1_CS_0_GPIO, PIN_REMAP_THIRD); // cs0
tm7705_spi_info.SPIx = LS1C_SPI_1;
tm7705_spi_info.cs = LS1C_SPI_CS_0; // cs0
*/
/*
// SPI1 CS1
pin_set_remap(LS1C_SPI_1_MISO_GPIO, PIN_REMAP_THIRD);
pin_set_remap(LS1C_SPI_1_MOSI_GPIO, PIN_REMAP_THIRD);
pin_set_remap(LS1C_SPI_1_CLK_GPIO, PIN_REMAP_THIRD);
pin_set_remap(LS1C_SPI_1_CS_1_GPIO, PIN_REMAP_THIRD); // cs1
tm7705_spi_info.SPIx = LS1C_SPI_1;
tm7705_spi_info.cs = LS1C_SPI_CS_1; // cs1
*/
tm7705_spi_info.max_speed_hz = 100*1000;
tm7705_spi_info.cpol = SPI_CPOL_1;
tm7705_spi_info.cpha = SPI_CPHA_1;
spi_init(&tm7705_spi_info);
// 复位tm7705并重新配置
tm7705_reset_and_reconfig();
return ;
}
重点关注spi初始化那部分,即设置全局变量tm7705_spi_info。我把TM7705接在SPI0的CS1、CS3,SPI1的CS0、CS1,共测试了4种情况,其它片选也类似。除了设置全局变量tm7705_spi_info,调用spi_init()外,需要复用的可以调用函数pin_set_remap()设置相应的复用。
函数tm7705_read_channel()
/*
* 读取指定通道的ad值
* @channel 通道
* @adc_p 读取的AD值
* @ret 成功 或 失败
*/
int tm7705_read_channel(int channel, unsigned short *adc_p)
{
int ret = TM7705_RET_TIMEOUT;
unsigned char tx = 0;
unsigned char rx[2] = {0};
unsigned short ad = 0;
// 等待转换完成
ret = tm7705_wait_DRDY();
if (TM7705_RET_OK != ret)
{
myprintf("[%s] tm7705 timeout!\r\n", __FUNCTION__);
return ret;
}
// 读
spi_set_cs(&tm7705_spi_info, 0);
delay_us(1);
tx = TM7705_REG_DATA | TM7705_READ | channel;
spi_txrx_byte(&tm7705_spi_info, tx);
rx[0] = spi_txrx_byte(&tm7705_spi_info, 0);
rx[1] = spi_txrx_byte(&tm7705_spi_info, 0);
spi_set_cs(&tm7705_spi_info, 1);
delay_us(1);
ad = (rx[0] << 8) + rx[1];
if (0xfff == ad)
{
myprintf("[%s] ad=0xfff\r\n", __FUNCTION__);
return TM7705_RET_OTHER_ERR;
}
*adc_p = ad;
return TM7705_RET_OK;
}
读取一次TM7705的流程是,先调用spi_set_cs()将cs拉低,然后调用spi_txrx_byte()发送命令并读取AD值,最后调用spi_set_cs()将cs拉高。
读取一次TM7705的过程中,包含三次调用spi_txrx_byte(),第一次调用为发送命令,所以忽略了返回值,第二次和第三次调用为读取AD值,主要是为了获取返回值,发送的数据是多少不重要,这里选择发送零,也可以选其它的。
注意,这是在裸机编程环境中测试的,可以认为测试时tm7705独占SPI0,。当在RT-Thread中,并且在SPI0上还接有其它设备时,需要注意互斥。
完整的测试代码清单
test_spi.c
// 测试硬件spi源文件
#include "../lib/ls1c_public.h"
#include "../lib/ls1c_pin.h"
#include "../lib/ls1c_spi.h"
#include "../lib/ls1c_gpio.h"
#include "../lib/ls1c_delay.h"
// spi复用
#define LS1C_SPI_1_CS_0_GPIO (49) // gpio49/spi1_cs0/CAMHSYNC
#define LS1C_SPI_1_CS_1_GPIO (50) // gpio50/spi1_cs1/CAMDATA0
#define LS1C_SPI_1_CS_2_GPIO (51) // gpio51/spi1_cs2/CAMDATA1
#define LS1C_SPI_1_CS_3_GPIO (52) // gpio52/spi1_cs3/CAMDATA2
#define LS1C_SPI_1_MISO_GPIO (47) // gpio47/spi1_miso/CAMCLKOUT
#define LS1C_SPI_1_MOSI_GPIO (48) // gpio48/spi1_mosi/CAMVSYNC
#define LS1C_SPI_1_CLK_GPIO (46) // gpio46/spi1_clk/CAMPCLKIN
// 通信寄存器bit定义
enum
{
// 寄存器选择 RS2 RS1 RS0
TM7705_REG_COMM = (0 << 4), // 通信寄存器
TM7705_REG_SETUP = (1 << 4), // 设置寄存器
TM7705_REG_CLOCK = (2 << 4), // 时钟寄存器
TM7705_REG_DATA = (3 << 4), // 数据寄存器
TM7705_REG_TEST = (4 << 4), // 测试寄存器
TM7705_REG_OFFSET = (6 << 4), // 偏移寄存器
TM7705_REG_GAIN = (7 << 4), // 增益寄存器
// 读写操作
TM7705_WRITE = (0 << 3), // 写操作
TM7705_READ = (1 << 3), // 读操作
// 通道
TM7705_CH_1 = 0, // AIN1+ AIN1-
TM7705_CH_2 = 1, // AIN2+ AIN2-
TM7705_CH_3 = 2, // AIN1- AIN1-
TM7705_CH_4 = 3 // AIN1- AIN2-
};
/* 设置寄存器bit定义 */
enum
{
TM7705_MD_NORMAL = (0 << 6), /* 正常模式 */
TM7705_MD_CAL_SELF = (1 << 6), /* 自校准模式 */
TM7705_MD_CAL_ZERO = (2 << 6), /* 校准0刻度模式 */
TM7705_MD_CAL_FULL = (3 << 6), /* 校准满刻度模式 */
TM7705_GAIN_1 = (0 << 3), /* 增益 */
TM7705_GAIN_2 = (1 << 3), /* 增益 */
TM7705_GAIN_4 = (2 << 3), /* 增益 */
TM7705_GAIN_8 = (3 << 3), /* 增益 */
TM7705_GAIN_16 = (4 << 3), /* 增益 */
TM7705_GAIN_32 = (5 << 3), /* 增益 */
TM7705_GAIN_64 = (6 << 3), /* 增益 */
TM7705_GAIN_128 = (7 << 3), /* 增益 */
/* 无论双极性还是单极性都不改变任何输入信号的状态,它只改变输出数据的代码和转换函数上的校准点 */
TM7705_BIPOLAR = (0 << 2), /* 双极性输入 */
TM7705_UNIPOLAR = (1 << 2), /* 单极性输入 */
TM7705_BUF_NO = (0 << 1), /* 输入无缓冲(内部缓冲器不启用) */
TM7705_BUF_EN = (1 << 1), /* 输入有缓冲 (启用内部缓冲器) */
TM7705_FSYNC_0 = 0, // 模拟调制器和滤波器正常处理数据
TM7705_FSYNC_1 = 1 // 模拟调制器和滤波器不启用
};
/* 时钟寄存器bit定义 */
enum
{
TM7705_CLKDIS_0 = (0 << 4), /* 时钟输出使能 (当外接晶振时,必须使能才能振荡) */
TM7705_CLKDIS_1 = (1 << 4), /* 时钟禁止 (当外部提供时钟时,设置该位可以禁止MCK_OUT引脚输出时钟以省电 */
TM7705_CLKDIV_0 = (0 << 3), // 不分频
TM7705_CLKDIV_1 = (1 << 3), // 2分频,外部晶振为4.9152Mhz时,应2分频
TM7705_CLK_0 = (0 << 2), // 主时钟=1Mhz并且CLKDIV=0,主时钟=2Mhz并且CLKDIV=1
TM7705_CLK_1 = (1 << 2), // 主时钟=2.4576Mhz并且CLKDIV=0, 主时钟=4.9152Mhz并且CLKDIV=1
// 注意输出更新率与clk位有关
// 当TM7705_CLK_0时,输出更新率只能为20,25,100,200
TM7705_UPDATE_20 = (0),
TM7705_UPDATE_25 = (1),
TM7705_UPDATE_100 = (2),
TM7705_UPDATE_200