【龙芯1c库】封装硬件SPI接口和使用示例

本文详细介绍了如何在龙芯1C上封装硬件SPI接口,包括spi_init(), spi_set_cs(), spi_txrx_byte()等函数,并通过ADC芯片TM7705的测试案例展示其实用性。同时,文章对比了Linux中硬件SPI的处理方式,强调了多任务环境下互斥问题的重要性。" 105082267,9265334,Django图形验证码实战指南,"['Django', '图形验证码', '表单验证']
摘要由CSDN通过智能技术生成
龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库。完整源码请移步到https://gitee.com/caogos/OpenLoongsonLib1c

本文首先介绍龙芯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.csdn.net/caogos/article/details/53126628
《【龙印】龙芯1c上双路16位AD芯片TM7705的linux驱动 》http://blog.csdn.net/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       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值