RTT笔记-SPI
该笔记类别主要是在自己学习时做的一些记录,方便自己很久不用忘掉时进行快速回忆
1 简述
和串口不同,SPI属于总线类型,所以操作方式也不同,串口是通过rt_device_find打开设备,然后进行读写,而SPI是将某个设备挂载到这个总线上,然后针对这个设备再进行读写。
2 函数说明
名称 | 说明 |
---|---|
rt_spi_bus_attach_device | 将函数挂载到指定SPI总线上 |
rt_spi_configure | 对spi模式等进行配置,如高地位,主从,时钟极性频率等 |
3 函数使用
rt_spi_bus_attach_device挂载设备
//成功返回RT_EOK
rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,
const char *name,
const char *bus_name,
void *user_data)
参数说明
参数名 | 说明 |
---|---|
device | 挂载的设备,由用户自定义如static struct rt_spi_device spi_dev_led |
name | 设备名称,字符串,自定义,主要在串口打印时方便标识 |
bus name | SPI总线名称,可在msh shell输入list_device 命令查看,这是在SPI驱动里面被定义的 |
user_data | 一般为SPI设备的CS引脚指针,进行数据传输时SPI控制器会操作此引脚进行片选 |
rt_spi_transfer_message | 核心数据传输,通过配置message完成各种传输方式 |
rt_spi_send | 衍生函数,发送一条数据,忽略接收 |
rt_spi_send_then_send | 此函数可以连续发送2个缓冲区的数据,忽略接收到的数据 |
rt_spi_send_then_recv | 发送一条数据,忽略接收,发送一条空数据,接收数据 |
使用示例
#define SPI_BUS_NAME "spi1" /* SPI总线名称 */
#define SPI_SSD1351_DEVICE_NAME "spi10" /* SPI设备名称 */
... ...
static struct rt_spi_device spi_dev_ssd1351; /* SPI设备ssd1351对象 */
static struct stm32_hw_spi_cs spi_cs; /* SPI设备CS片选引脚 */
... ...
static int rt_hw_ssd1351_config(void)
{
rt_err_t res;
/* oled use PC8 as CS */
spi_cs.pin = CS_PIN;
rt_pin_mode(spi_cs.pin, PIN_MODE_OUTPUT); /* 设置片选管脚模式为输出 */
res = rt_spi_bus_attach_device(&spi_dev_ssd1351, SPI_SSD1351_DEVICE_NAME, SPI_BUS_NAME, (void*)&spi_cs);
if (res != RT_EOK)
{
OLED_TRACE("rt_spi_bus_attach_device!\r\n");
return res;
}
... ...
}
rt_spi_configure模式参数配置
rt_err_t rt_spi_configure(struct rt_spi_device *device,
struct rt_spi_configuration *cfg)
参数说明
参数 | 说明 |
---|---|
device | 设备,也就是在挂载的时候填充了他,之后针对该设备操作都是使用它来表示 |
cfg | 配置参数,老套路,自定义rt_spi_configuration 结构体,填充后传入 |
struct rt_spi_configuration
{
rt_uint8_t mode; //spi模式
rt_uint8_t data_width; //数据宽度,可取8位、16位、32位
rt_uint16_t reserved; //保留
rt_uint32_t max_hz; //最大频率
};
其中mode可以由多个模式或操作来并用,各种模式宏定义如下
/* 设置数据传输顺序是MSB位在前还是LSB位在前 */
#define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */
#define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */
/* 设置SPI的主从模式 */
#define RT_SPI_MASTER (0<<3) /* SPI master device */
#define RT_SPI_SLAVE (1<<3) /* SPI slave device */
/* 设置时钟极性和时钟相位 */
#define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */
#define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */
#define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */
#define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */
#define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */
#define RT_SPI_NO_CS (1<<5) /* No chipselect */
#define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */
#define RT_SPI_READY (1<<7) /* Slave pulls low to pause */
配置示例
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
cfg.max_hz = 20 * 1000 *1000; /* 20M,SPI max 42MHz,ssd1351 4-wire spi */
rt_spi_configure(&spi_dev_ssd1351, &cfg);
rt_spi_transfer_message数据传输
struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,struct rt_spi_message *message)
message参数的结构体
struct rt_spi_message
{
const void *send_buf; /* 发送缓冲区指针 */
void *recv_buf; /* 接收缓冲区指针 */
rt_size_t length; /* 发送/接收 数据字节数 */
struct rt_spi_message *next; /* 指向继续发送的下一条消息的指针 */
unsigned cs_take : 1; /* 值为1,CS引脚拉低,值为0,不改变引脚状态 */
unsigned cs_release : 1; /* 值为1,CS引脚拉高,值为0,不改变引脚状态 */
};
由于SPI一发必定一收,如果无需接收则可以将对于buffer置NULL。这里的message其实表示的是一次通信,类似一个数据包,当有多个message时,可以通过next参数将其连接起来,然后自动发送,无需使用时也可以置NULL。
rt_spi_send发送一条数据
由rt_spi_transfer_message衍生出来的,也就是固定了message的配置,目的就是单纯的发送一条数据
rt_size_t rt_spi_send(struct rt_spi_device *device,
const void *send_buf,
rt_size_t length)
参数很明显,无需多言,其中message被固定为了下列配置
struct rt_spi_message msg;
msg.send_buf = send_buf;
msg.recv_buf = RT_NULL;
msg.length = length;
msg.cs_take = 1;
msg.cs_release = 1;
msg.next = RT_NULL;
示例
len = rt_spi_send(&spi_dev_ssd1351, &cmd, 1);
if (len != 1)
{
return -RT_ERROR;
}
rt_spi_send_then_send连续发送两条
rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,
const void *send_buf1,
rt_size_t send_length1,
const void *send_buf2,
rt_size_t send_length2);
发送第一条前打开片选,发完第二条再关闭片选,message配置被固定为
struct rt_spi_message msg1,msg2;
msg1.send_buf = send_buf1;
msg1.recv_buf = RT_NULL;
msg1.length = send_length1;
msg1.cs_take = 1;
msg1.cs_release = 0;
msg1.next = &msg2;
msg2.send_buf = send_buf2;
msg2.recv_buf = RT_NULL;
msg2.length = send_length2;
msg2.cs_take = 0;
msg2.cs_release = 1;
msg2.next = RT_NULL;
rt_spi_send_then_recv发而不收,发空再收
该函数通常应用在读取设备寄存器值得时候,首先发送寄存器地址,这个时候返回的是无效数据,然后需要再次发送一个数据来换回真实寄存器数据
rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,
const void *send_buf,
rt_size_t send_length,
void *recv_buf,
rt_size_t recv_length);
message配置被固定为
struct rt_spi_message msg1,msg2;
msg1.send_buf = send_buf;
msg1.recv_buf = RT_NULL;
msg1.length = send_length;
msg1.cs_take = 1;
msg1.cs_release = 0;
msg1.next = &msg2;
msg2.send_buf = RT_NULL;
msg2.recv_buf = recv_buf;
msg2.length = recv_length;
msg2.cs_take = 0;
msg2.cs_release = 1;
msg2.next = RT_NULL;
rt_spi_sendrecv8()和rt_spi_sendrecv16()函数是对此函数的封装,rt_spi_sendrecv8()发送一个字节数据同时收到一个字节数据,rt_spi_sendrecv16()发送2个字节数据同时收到2个字节数据
实例
开发板我使用的是STM32F103ZET6 原子的战舰版,其中spi2连接着w25Q128,片选是PB12。由于我仅仅测试spi功能,所以就读取该设备chip ID。
//SPI测试,使用SPI2,片选PB12
struct stm32_hw_spi_cs
{
rt_uint32_t pin;
};
static struct rt_spi_device spi_dev_w25qxx;
static struct stm32_hw_spi_cs spi_cs;
uint8_t rcv[6]={0,0,0,0,0,0};
uint8_t sed[6]={0X90,0,0,0,0,0};
void spi_thread_entry(void *paramter)
{
rt_err_t res;
rt_kprintf("spi_thread_entry\n");
spi_cs.pin = 73;
rt_pin_mode(spi_cs.pin, PIN_MODE_OUTPUT);
res = rt_spi_bus_attach_device(&spi_dev_w25qxx, "w25qxx", "spi2", (void*)&spi_cs);
if (res != RT_EOK)
{
rt_kprintf("rt_spi_bus_attach_device!\r\n");
}
else
{
rt_kprintf("spi--ok");
}
//配置
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
cfg.max_hz = 20 * 1000 *1000; /* 20M,SPI max 42MHz,ssd1351 4-wire spi */
rt_spi_configure(&spi_dev_w25qxx, &cfg);
//读取ID
struct rt_spi_message msg1;
msg1.send_buf = sed;
msg1.recv_buf = rcv;
msg1.length = 6;
msg1.cs_take = 1;
msg1.cs_release = 1;
msg1.next = RT_NULL;
rt_spi_transfer_message(&spi_dev_w25qxx,&msg1);
rt_kprintf("%02X,%02X\n",rcv[4],rcv[5]);
}
启动该进程
rt_thread_t w25qxx_hander;
w25qxx_hander = rt_thread_create("w25qxx_thd"
,spi_thread_entry
,RT_NULL
,1024
,2
,10
);
if (w25qxx_hander != RT_NULL)
{
rt_thread_startup(w25qxx_hander);
}
最后串口输出EF,17,则表示通信成功,否则一般是返回两个0XFF的