灵动微电子 MM32
直播中

zhu

8年用户 1640经验值
擅长:控制/MCU
私信 关注
[原创]

灵动微课堂 (第126讲) | 基于MM32 MCU的OS移植与应用——AMetal SPI操作

`
SPI 接口广泛用于不同设备之间的板级通讯,如扩展串行Flash,DAC,LCD等。SPI允许 MCU 与外部设备以全双工、同步、串行方式通信。应用软件可以通过查询状态或SPI中断来通信,支持DMA请求以达到更快的通信速率。

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进行读写操作。


Part1
初始化

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

MM32L073 支持SPI功能的外设有SPI1 和SPI2,为方便用户使用,AMetal提供了与各外设对应的实例初始化函数。
函数原型
功能简介
am_spi_handle_t am_mm32l073_spi1_int_inst_init (void)
SPI1 实例初始化
am_spi_handle_t am_mm32l073_spi2_int_inst_init (void)

SPI2 实例初始化
表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初始化。


Part2
接口函数

MCU 的 SPI 主要用于主从机的通信,AMetal提供了8个接口函数。
函数原型
功能简介
void am_spi_mkdev(
am_spi_device_t *p_dev,
am_spi_handle_t handle,
uint8_t bits_per_word,
uint16_t mode,
uint32_t max_speed_hz,
int cs_pin,
void (*pfunc_cs)(am_spi_device_t *p_dev, int state))
SPI 从机实例初始化
int am_spi_setup (am_spi_device_t *p_dev)
设置 SPI 从机实例
void am_spi_mktrans(
am_spi_transfer_t *p_trans,
const void *p_txbuf,
void *p_rxbuf,
uint32_t nbytes,
uint8_t cs_change,
uint8_t bits_per_word,
uint16_t delay_usecs,
uint32_t speed_hz,
uint32_t flags)
SPI 传输初始化
void am_spi_msg_init(
am_spi_message_t *p_msg,
am_pfnvoid_t pfn_complete,
void *p_arg)
SPI 消息初始化
void am_spi_trans_add_tail(
am_spi_message_t *p_msg,
am_spi_transfer_t *p_trans)
添加传输至消息中
int am_spi_msg_start (
am_spi_device_t *p_dev,
am_spi_message_t *p_msg)
启动 SPI 消息处理
int am_spi_write_then_read(
am_spi_device_t *p_dev,
const uint8_t *p_txbuf,size_t n_tx,
uint8_t *p_rxbuf,size_t n_rx)
SPI 先写后读
int am_spi_write_then_write (
am_spi_device_t *p_dev,
const uint8_t *p_txbuf0,
size_t n_tx0,
const uint8_t *p_txbuf1,
size_t n_tx1)
执行 SPI 两次写
表2 SPI 标准接口函数

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


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 从机实例

模式标志
含义
解释
AM_SPI_MODE_0
SPI 模式 0
CPOL=0, CPHA=0
AM_SPI_MODE_1
SPI 模式 1
CPOL=0, CPHA=1
AM_SPI_MODE_2
SPI 模式 2
CPOL=1, CPHA=0
AM_SPI_MODE_3
SPI 模式 3
CPOL=1, CPHA=1
表3 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


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 从设备


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 传输结构体

标志宏
含义
AM_SPI_READ_MOSI_HIGH
读数据时,MOSI 输出高电平,默认低电平
表4 传输特殊控制标志

因为 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); // 无特殊标志


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
}


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!
");
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 擦除完成
");
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 数据写入完成
");
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
", i, g_rx_buf);
if(g_rx_buf != ((1+ i) & 0xFF))
{
AM_DBG_INFO("verify failed!
");
while(1);
}
}
}           


Part8
测试截图

1.png
` 2.jpg

更多回帖

×
20
完善资料,
赚取积分