打印
[MM32生态]

分享灵动微单片机基于MM32 MCU的OS移植与应用-AMetal SPI操作

[复制链接]
1756|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
SPI 接口广泛用于不同设备之间的板级通讯,如扩展串行Flash,DAC,LCD等。SPI允许 MCU 与外部设备以全双工、同步、串行方式通信。应用软件可以通过查询状态或SPI中断来通信,支持DMA请求以达到更快的通信速率。

使用特权

评论回复
沙发
两只袜子|  楼主 | 2021-6-15 09:45 | 只看该作者

MM32 MCU支持如下特征:


完全兼容 Motorola 的 SPI 规格
支持 DMA 请求
在 3 根线上支持全双工同步传输
16 位的可编程波特率生成器
支持主机模式和从机模式
8 个字节的接收/发送 FIFO
SPI 作为主机模式下 SPI 的时钟最快可高达 pclk/2(pclk 为 APB 时钟),作为从机模式下 SPI 的时钟最快可高达 pclk/4
可编程的时钟极性和相位
可编程的数据顺序, MSB 在前或者 LSB 在前
支持一个主机多个从机操作
支持 1 ∼ 32 位的数据位长度同时发送和接收
除了 8 位数据收发,其余1 ∼ 32位数据收发只支持LSB模式,不支持MSB模式
支持各 8 个对应配置数据位(Data size)的发送缓冲器和接收缓冲器
中断驱动操作
        – 发送端空,发送端溢出

        – 接收的数据有效,接收端的数据溢出
        – 在 SPI 主模式完整接收,发送端为空

我们将基于AMetal平台来操作SPI对外部SPI FLASH进行读写操作。


使用特权

评论回复
板凳
两只袜子|  楼主 | 2021-6-15 09:46 | 只看该作者

Part1
初始化

在使用SPI通用接口前,必须先完成SPI的初始化,以获取标准的SPI实例句柄。

MM32L073 支持SPI功能的外设有SPI1 和SPI2,为方便用户使用,AMetal提供了与各外设对应的实例初始化函数。


表1 SPI 实例初始化函数

这些函数的返回值均为 am_spi_handle_t 类型的 SPI 实例句柄,该句柄将作为 SPI 通用接口中 handle 参数的实参。类型 am_spi_handle_t(am_spi.h)定义如下:
typedef struct am_spi_serv *am_spi_handle_t

因为函数返回的SPI实例句柄仅作为参数传递给SPI通用接口,不需要对该句柄做其它任何操作,因此完全不需要了解该类型。注意,若函数返回的实例句柄的值为NULL,则表明初始化失败,不能使用该实例句柄。

如需使用 SPI1,则直接调用 SPI1 实例初始化函数,即可获取对应的实例句柄:
am_spi_handle_t spi1_handle = am_mm32l073_spi1_int_inst_init ()

打开新建工程的main.c文件, 添加SPI头文件和MM32L073的外设实例初始化函数声明,在am_main函数中添加 SPI1实例初始化函数,并编译该工程,即可完成SPI初始化。


使用特权

评论回复
地板
两只袜子|  楼主 | 2021-6-15 09:53 | 只看该作者
本帖最后由 两只袜子 于 2021-6-15 09:54 编辑

Part2
接口函数

MCU 的 SPI 主要用于主从机的通信,AMetal提供了8个接口函数。


本例中选择MX25L1606 为从机,MCU通过SPI对它写入数据。MX25L1606总容量为 16M(16× 1024× 1024)bits,即2M字节。每个字节对应一个存储地址,因此其存储数据的地址范围为 0x000000 ~ 0x1FFFFF。

使用特权

评论回复
5
两只袜子|  楼主 | 2021-6-15 09:55 | 只看该作者
Part3
从机实例初始化

对于用户来说,使用 SPI 往往是直接操作一个从机器件, MCU 作为 SPI 主机,为了与从机器件通信,需要知道从机器件的相关信息,比如, SPI 模式、 SPI 速率、数据位宽等。

这就需要定义一个与从机器件对应的实例(从机实例),并使用相关信息完成对从机实例的初始化。其函数原型为:
void am_spi_mkdev (
am_spi_device_t *p_dev, //待初始化的从机实例
am_spi_handle_t handle, //通过SPI实例初始化函数获得句柄
uint8_t bits_per_word, // 数据宽度,为 0 默认 8Bit
uint16_t mode, //模式选择
uint32_t max_speed_hz, // 从设备支持的最高时钟频率
int cs_pin, // 片选引脚
void (*pfunc_cs)(am_spi_device_t *p_dev, int state))

p_dev 是指向 SPI 从机实例描述符的指针, am_spi_device_t 在 am_spi.h 文件中定义:
typedef struct am_spi_device am_spi_device_t

该类型用于定义从机实例,用户无需知道其定义的具体内容,只需要使用该类型定义一个从机实例。即:
am_spi_device_t spi_dev; // 定义一个 SPI 从机实例


mode 指定使用的模式,SPI 协议定义了 4 种模式,各种模式的主要区别在于空闲时钟极性( CPOL)和时钟相位选择(CPHA)的不同。CPOL 和 CPHA均有两种选择,因此两两组合可以构成 4 种不同的模式,即模式 0~3。当 CPOL 为 0 时,表示时钟空闲时,时钟线为低电平,反之,空闲时为高电平;当 CPHA 为 0 时,表示数据在第 1 个时钟边沿采样,反之,则表示数据在第 2 个时钟边沿采样。

cs_pin 和 pfunc_cs 均与片选引脚相关。pfunc_cs 是指向自定义片选控制函数的指针,若pfunc_cs 的值为 NULL,驱动将自动控制由 cs_pin 指定的引脚实现片选控制;若 pfunc_cs 的值不为 NULL,指向了有效的自定义片选控制函数,则 cs_pin 不再被使用,片选控制将完全由应用实现。当需要片选引脚有效时,驱动将自动调用 pfunc_cs 指向的函数,并传递 state的值为 1。当需要片选引脚无效时,也会调用 pfunc_cs 指向的函数,并传递 state 的值为 0。

一般情况下,片选引脚自动控制即可,即设置 pfunc_cs 的值为 NULL, cs_pin 为片选引脚,如 PIOA_4。
am_spi_mkdev()范例程序am_spi_handle_t spi0_hanlde =am_mm32l073_spi1_inst_init(); //使用MM32L073 的 SPI1 获取 SPI 句柄
am_spi_device_tspi_dev; // 定义从机设备
am_spi_mkdev(
&spi_dev, // 传递从机设备
spi1_handle, // SPI1 操作句柄
8, // 数据宽度为 8-bit
AM_SPI_MODE_0, // 选择模式 0
3000000, // 最大频率 3000000Hz
PIOA_4, // 片选引脚 PIOA_4
NULL); // 无自定义片选控制函数,设置为 NULL


使用特权

评论回复
6
两只袜子|  楼主 | 2021-6-15 09:57 | 只看该作者

Part4
设置从机实例

设置 SPI 从机实例时, 会检查 MCU 的 SPI 主机是否支持从机实例的相关参数和模式。

如果不能支持, 则设置失败, 说明该从机不能使用。其函数原型为:int am_spi_setup (am_spi_device_t *p_dev)

其中的 p_dev 是指向 SPI 从机实例描述符的指针,如果返回 AM_OK,说明设置成功;如果返回-AM_ENOTSUP,说明设置失败,不支持的位宽、模式等。
am_spi_handle_t spi0_handle =am_mm32l073_spi1_inst_init(); // 使用 MM32L073 的 SPI1 获取 SPI 句柄
am_spi_device_t spi_dev;
am_spi_mkdev(
&spi_dev,
spi1_handle,
8, // 数据宽度为 8-bit
AM_SPI_MODE_0, // 选择模式 0
3000000, // 最大频率 3000000Hz
PIOA_4, // 片选引脚 PIOA_4
NULL); // 无自定义片选控制函数,设置为 NULL am_spi_setup(&spi_dev); // 设置 SPI 从设备


使用特权

评论回复
7
两只袜子|  楼主 | 2021-6-15 10:00 | 只看该作者

Part5
传输初始化

在 AMetal 中,将收发一次数据的过程抽象为一个“传输” 的概念,要完成一次数据传输,首先就需要初始化一个传输结构体,指定该次数据传输的相关信息。其函数原型为:
void am_spi_mktrans(
am_spi_transfer_t  *p_trans, //待初始化的 SPI 传输
const void *p_txbuf, // 发送数据缓冲区,NULL无数据
void *p_rxbuf, //接收数据缓冲区,NULL无数据
uint32_t nbytes, //传输的字节数
uint8_t cs_change, //传输是否影响片选, 0-不影响,1-影响
uint8_t bits_per_word, //为 0 默认使用设备的字大小
uint16_t delay_usecs, //传输结束后的延时(us)
uint32_t speed_hz, //为0默认使用设备中的max_speed_hz
uint32_t f lags); // 本次传输的特殊标志

其中,p_trans 为指向SPI传输结构体的指针,am_spi_transfer_t类型是在 am_spi.h 中定义的。即:
typedef struct am_spi_transfer am_spi_transfer_t

在实际使用时,只需要定义一个该类型的传输结构体即可。比如:
am_spi_transfer_t spi_trans; //定义一个 SPI 传输结构体


因为 SPI 是全双工通信协议,所以单次传输过程中同时包含了数据的发送和接收。函数的参数中,p_txbuf 指定了发送数据的缓冲区,p_rxbuf 指定了接收数据的缓冲区,nbytes 指定了传输的字节数。特别地,有时候可能只希望单向传输数据,若只发送数据,则可以设置p_rxbuf 为 NULL;若只接收数据,则可以设置 p_txbuf 为 NULL。

当传输正常进行时,片选会置为有效状态, cs_change 的值将影响片选何时被置为无效状态。若 cs_change 的值为 0,表明不影响片选,此时,仅当该次传输是消息(多次传输组成一个消息,消息的概念后文会介绍)的最后一次传输时,片选才会被置为无效状态。若cs_change 的值为 1,表明影响片选,此时,若该次传输不是消息的最后一次传输,则在本次传输结束后会立即将片选设置为无效状态,若该次传输是消息的最后一次传输,则不会立即设置片选无效,而是保持有效直到下一个消息的第一次传输开始。
uint8_t tx_buf[8];
uint8_t rx_buf[8];
am_spi_transfer_t spi_trans;
am_spi_mktrans(
&spi_trans,
tx_buf, // 发送数据缓冲区
rx_buf, // 接收数据缓冲区
8, // 传输数据个数为 8
0, // 本次传输不影响片选
0, // 位宽为 0,使用默认位宽(设备中的位宽)
0, // 传输后无需延时
0, // 时钟频率,使用默认速率
0); // 无特殊标志


使用特权

评论回复
8
两只袜子|  楼主 | 2021-6-15 10:06 | 只看该作者

Part6
消息初始化

一般来说,与实际的 SPI 器件通信时,往往采用的是“命令” +“数据”的格式,这就需要两次传输:一次传输命令,一次传输数据。为此, AMetal 提出了“消息”的概念,一个消息的处理即为一次有实际意义的 SPI 通信,其间可能包含一次或多次传输。

一次消息处理中可能包含很多次的传输,耗时可能较长,为避免阻塞,消息的处理采用异步方式。这就要求指定一个完成回调函数,当消息处理完毕时,自动调用回调函数以通知用户消息处理完毕。回调函数的指定在初始化函数中完成,初始化函数的原型为:
void am_spi_msg_init (
am_spi_message_t  *p_msg, // 待初始化的 SPI 传输
am_pfnvoid_t  pfn_complete, // 消息处理完成回调函数
void  *p_arg); // 回调函数的参数

其中的 p_msg 为指向 SPI 消息结构体的指针, am_spi_message_t 类型是在 am_spi.h 中定义的。即:
typedef struct am_spi_message am_spi_message_t

实际使用时,仅需使用该类型定义一个消息结构体。即:
am_spi_message_t spi_msg; // 定义一个 SPI 消息结构体

pfn_callback 指向的是消息处理完成回调函数, 当消息处理完毕时, 将调用指针指向的函数。其类型 am_pfnvoid_t 在 am_types.h 中定义的。即:
typedef void (*am_pfnvoid_t) (void *)

由此可见,函数指针指向的是参数为void *类型的无返回值函数。驱动调用回调函数时,传递给该回调函数的void*类型的参数即为 p_arg 的设定值。
static void __spi_msg_complete_callback (void *p_arg)
{
// 消息处理完毕
}

int am_main()
{
am_spi_message_t spi_msg; // 定义一个 SPI 消息结构体
am_spi_msg_init (
&spi_msg,
__spi_msg_complete_callback, // 消息处理完成回调函数
NULL); // 未使用回调函数的参数 p_arg,设置为 NULL
}


使用特权

评论回复
9
两只袜子|  楼主 | 2021-6-15 10:10 | 只看该作者

Part7
应用实例

MM32L073 通过SPI与SPI Flash通信,对地址为0x0000进行擦写读操作,并将写入数据读出进行校验。
#include "ametal.h"
#include "am_board.h"
#include "am_vdebug.h"
#include "am_delay.h"
#include "am_gpio.h"
#include "demo_all_entries.h"
#include "am_spi.h"
#include "am_mm32l073_inst_init.h"
#include "mm32l073_pin.h"
#define FLASH_PAGE_SIZE 256 // SPI Flsah 页大小定义
#define TEST_ADDR 0x0000 // 测试地址
#define TEST_LEN FLASH_PAGE_SIZE // 测试字节长度
static uint8_t g_tx_buf[TEST_LEN]={0x9F}; // 写数据缓存
static uint8_t g_rx_buf[TEST_LEN]={0}; // 读数据缓存

int am_main (void)
{
uint32_t ength, i;
AM_DBG_INFO("Start up successful!\r\n");
am_spi_handle_t spi_handle = am_mm32l073_spi1_int_inst_init();
am_spi_device_t spi_dev;
am_spi_mkdev(&spi_dev,
spi_handle,
8, // 数据宽度 8-bit
AM_SPI_MODE_0, // 模式 0
3000000, // 最大频率 3000000Hz
PIOA_4, // 片选 PIOA_4
NULL); // 无自定义函数,设置 NULL
am_spi_setup(&spi_dev); // 设置从机设备
spi_flash_erase(&spi_dev, TEST_ADDR);//擦除当前地址中数据
AM_DBG_INFO("FLASH 擦除完成\r\n");
for (i = 0; i < length; i++) // 填充数据
{
g_tx_buf = i +1;
}
spi_flash_write(&spi_dev, TEST_ADDR, length); //写入数据到设定 SPI_FLASH 地址 am_mdelay(10);
AM_DBG_INFO("FLASH 数据写入完成\r\n");
for (i = 0; i < length; i++)
{
g_rx_buf = 0;
}
spi_flash_read(&spi_dev, TEST_ADDR, length); //从设定的FLASH地址中读取数据
am_mdelay(10);
for (i = 0; i < length; i++) // 数据校验
{
AM_DBG_INFO(" read %2dst data is : 0x%2x \r\n", i, g_rx_buf);
if(g_rx_buf != ((1+ i) & 0xFF))
{
AM_DBG_INFO("verify faiLED!\r\n");
while(1);
}
}
}           


使用特权

评论回复
10
两只袜子|  楼主 | 2021-6-15 10:13 | 只看该作者

Part8


测试截图


使用特权

评论回复
11
菜鸟的第一步| | 2021-6-15 13:25 | 只看该作者
为啥我找不到SPI的初始化呀

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2054

主题

7452

帖子

10

粉丝