spi的通信方式和协议我之前已经写过了,这里不再多写了。最近在研究rtthread,里面有硬件spi,最近因为项目需要用io口来模拟spi,但是网上能找到的资料不多,所以自己研究了一下,搞出来两套写法,一套是基于rtthread封装好的spi-bit-ops这个文件中的函数来注册一个软件spi。另一种就是纯自己手搓出来的。两套我都测试过了,都可以用,但是频率差了好多。下面简单贴一下基于rtthread已经给出的RT_USING_SPI_BITOPS这个宏定义来写。
struct at32_soft_spi_config
{
rt_uint8_t clk;
rt_uint8_t miso;
rt_uint8_t mosi;
const char *bus_name;
};
/* at32 sspi dirver class */
struct at32_sspi
{
struct rt_spi_bus spi_bus;
struct rt_spi_configuration config;
struct rt_spi_bit_ops ops;
};
#ifdef BSP_USING_SSPI
#define SSPI_BUS_CONFIG \
{ \
.clk = BSP_SSPI_CLK_PIN, \
.miso = BSP_SSPI_MISO_PIN, \
.mosi = BSP_SSPI_MOSI_PIN, \
.bus_name = "sspi", \
}
#endif
static const struct at32_soft_spi_config soft_spi_config[] =
{
#ifdef BSP_USING_SSPI
SSPI_BUS_CONFIG,
#endif
};
static struct at32_sspi sspi_obj[sizeof(soft_spi_config) / sizeof(soft_spi_config[0])];
static void at32_sspi_gpio_init(struct at32_sspi *sspi)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)sspi->ops.data;
rt_pin_mode(cfg->clk, PIN_MODE_OUTPUT);
rt_pin_mode(cfg->mosi, PIN_MODE_OUTPUT);
rt_pin_mode(cfg->miso, PIN_MODE_INPUT);
// rt_pin_write(cfg->clk, PIN_LOW);
// rt_pin_write(cfg->mosi, PIN_LOW);
// rt_pin_write(cfg->miso, PIN_LOW);
}
static void sspi_tog_sclk(void *data)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
if (rt_pin_read(cfg->clk) == SET)
{
rt_pin_write(cfg->clk, PIN_LOW);
}
else
{
rt_pin_write(cfg->clk, PIN_HIGH);
}
}
static void sspi_set_sclk(void *data, rt_int32_t state)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
rt_pin_write(cfg->clk, state);
// if (state)
// {
// rt_pin_write(cfg->clk, PIN_HIGH);
// }
// else
// {
// rt_pin_write(cfg->clk, PIN_LOW);
// }
}
static void sspi_set_mosi(void *data, rt_int32_t state)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
rt_pin_write(cfg->mosi, state);
// if (state)
// {
// rt_pin_write(cfg->mosi, PIN_HIGH);
// }
// else
// {
// rt_pin_write(cfg->mosi, PIN_LOW);
// }
}
static void sspi_set_miso(void *data, rt_int32_t state)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
rt_pin_write(cfg->miso, state);
// if (state)
// {
// rt_pin_write(cfg->miso, PIN_HIGH);
// }
// else
// {
// rt_pin_write(cfg->miso, PIN_LOW);
// }
}
static rt_int32_t sspi_get_sclk(void *data)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
return rt_pin_read(cfg->miso);
}
static rt_int32_t sspi_get_mosi(void *data)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
return rt_pin_read(cfg->mosi);
}
static rt_int32_t sspi_get_miso(void *data)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
return rt_pin_read(cfg->miso);
}
static void sspi_dir_mosi(void *data, rt_int32_t state)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
if (state)
{
rt_pin_mode(cfg->mosi, PIN_MODE_INPUT_PULLUP);
}
else
{
rt_pin_mode(cfg->mosi, PIN_MODE_OUTPUT_OD);
}
}
static void sspi_dir_miso(void *data, rt_int32_t state)
{
struct at32_soft_spi_config *cfg = (struct at32_soft_spi_config *)data;
if (state)
{
rt_pin_mode(cfg->miso, PIN_MODE_INPUT_PULLUP);
}
else
{
rt_pin_mode(cfg->miso, PIN_MODE_OUTPUT_OD);
}
}
static void sspi_udelay(rt_uint32_t us)
{
rt_uint32_t ticks;
rt_uint32_t told, tnow, tcnt = 0;
rt_uint32_t reload = SysTick->LOAD;
ticks = us * reload / (1000000 / RT_TICK_PER_SECOND);
told = SysTick->VAL;
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow;
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks)
{
break;
}
}
}
}
static struct rt_spi_bit_ops at32_sspi_bit_ops_default =
{
.data = RT_NULL,
.tog_sclk = sspi_tog_sclk,
.set_sclk = sspi_set_sclk,
.set_mosi = sspi_set_mosi,
.set_miso = sspi_set_miso,
.get_sclk = sspi_get_sclk,
.get_mosi = sspi_get_mosi,
.get_miso = sspi_get_miso,
.dir_mosi = sspi_dir_mosi,
.dir_miso = sspi_dir_miso,
.udelay = sspi_udelay,
};
rt_err_t rt_sf_spi_device_attach(const char *bus_name, const char *device_name, rt_base_t pinnum)
{
gpio_init_type gpio_init_struct;
RT_ASSERT(bus_name != RT_NULL);
RT_ASSERT(device_name != RT_NULL);
rt_err_t result;
struct rt_spi_device *spi_device;
rt_base_t cs_pin;
rt_pin_mode(pinnum, PIN_MODE_OUTPUT);
//片选线要拉高,我这里电平时反的所以拉低了
rt_pin_write(pinnum, PIN_LOW);
/* attach the device to spi bus */
spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
RT_ASSERT(spi_device != RT_NULL);
cs_pin = (rt_base_t *)rt_malloc(sizeof(rt_base_t));
RT_ASSERT(cs_pin != RT_NULL);
cs_pin = pinnum;
result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)cs_pin);
;
if (result != RT_EOK)
{
LOG_D("%s attach to %s faild, %d\n", device_name, bus_name, result);
}
RT_ASSERT(result == RT_EOK);
LOG_D("%s attach to %s done", device_name, bus_name);
return result;
}
int rt_soft_spi_init(void)
{
rt_size_t obj_num = sizeof(sspi_obj) / sizeof(struct at32_sspi);
rt_err_t result;
for (int i = 0; i < obj_num; i++)
{
sspi_obj[i].ops.data = (void *)&soft_spi_config[i];
at32_sspi_bit_ops_default.data = (void *)&soft_spi_config[i];
at32_sspi_gpio_init(&sspi_obj[i]);
result = rt_spi_bit_add_bus(&(sspi_obj[i].spi_bus), soft_spi_config[i].bus_name, &at32_sspi_bit_ops_default);
RT_ASSERT(result == RT_EOK);
LOG_D("software simulation %s init done, pin clk: %d, pin miso %d,pin miso %d\n",
soft_spi_config[i].bus_name,
soft_spi_config[i].clk,
soft_spi_config[i].miso,
soft_spi_config[i].mosi);
}
}
INIT_BOARD_EXPORT(rt_soft_spi_init);
上面注册完成后,使用函数
rt_err_t rt_sf_spi_device_attach(const char *bus_name, const char *device_name, rt_base_t pinnum)
就可以把对应的传感器挂载在软件SPI上了。还有就是rtthread官方的spi-bit-ops里面的xfer读写有点小bug,我这里把这个函数更改了一点。
rt_uint32_t spi_bit_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
struct rt_spi_bit_obj *obj = rt_container_of(device->bus, struct rt_spi_bit_obj, bus);
struct rt_spi_bit_ops *ops = obj->ops;
struct rt_spi_configuration *config = &obj->config;
rt_base_t cs_pin = (rt_base_t)device->parent.user_data;
RT_ASSERT(device != NULL);
RT_ASSERT(message != NULL);
#ifdef RT_SPI_BITOPS_DEBUG
if (!ops->tog_sclk || !ops->set_sclk || !ops->get_sclk)
{
LOG_E("SPI bus error, SCLK line not defined");
}
if (!ops->set_mosi || !ops->get_mosi)
{
LOG_E("SPI bus error, MOSI line not defined");
}
if (!ops->set_miso || !ops->get_miso)
{
LOG_E("SPI bus error, MISO line not defined");
}
#endif
/* take CS */
if (message->cs_take)
{
LOG_I("spi take cs\n");
// rt_pin_write(cs_pin, PIN_LOW);
rt_pin_write(cs_pin, PIN_HIGH);
spi_delay(ops);
/* spi phase */
}
//这里如果放在cs_take里面的话,在模式1和3时clk时钟线翻转会少一次从而导致第二个字节clk的时钟完全混乱,要注意
if (config->mode & RT_SPI_CPHA)
{
spi_delay(ops);
TOG_SCLK(ops);
}
if (config->mode & RT_SPI_3WIRE)
{
if (config->data_width <= 8)
{
spi_xfer_3line_data8(ops,
config,
message->send_buf,
message->recv_buf,
message->length);
}
else if (config->data_width <= 16)
{
spi_xfer_3line_data16(ops,
config,
message->send_buf,
message->recv_buf,
message->length);
}
}
else
{
if (config->data_width <= 8)
{
spi_xfer_4line_data8(ops,
config,
message->send_buf,
message->recv_buf,
message->length);
}
else if (config->data_width <= 16)
{
spi_xfer_4line_data16(ops,
config,
message->send_buf,
message->recv_buf,
message->length);
}
}
/* release CS */
if (message->cs_release)
{
spi_delay(ops);
// rt_pin_write(cs_pin, PIN_HIGH);
rt_pin_write(cs_pin, PIN_LOW);
LOG_I("spi release cs\n");
}
return message->length;
}
上面这个可以直接使用,但是频率只有400K,如果去掉延时可能还能高一些,但是最高也就400K了。
如果用自己纯手搓的代码最高速度能到2.5M,所以这个能看懂并且会用就行了。