前言

概述

本文档主要介绍BS2X设备驱动开发的相关内容,主要包括工作原理、按场景描述接口使用方法和注意事项。

本文档以BS21为例进行说明示例,后续不再单独说明,请用户知悉。

所有的驱动接口都有返回值,用户使用时,建议每个接口都进行下返回值校验,根据返回值,判断是否执行成功。返回值错误码详见“include/errcode.h”。

产品版本

产品名称

产品版本

BS2X

V100

读者对象

本文档主要适用于以下工程师:

  • 技术支持工程师

  • 软件开发工程师

符号约定

在本文中可能出现下列标志,它们所代表的含义如下。

符号

说明

表示如不避免则将会导致死亡或严重伤害的具有高等级风险的危害。

表示如不避免则可能导致死亡或严重伤害的具有中等级风险的危害。

表示如不避免则可能导致轻微或中度伤害的具有低等级风险的危害。

用于传递设备或环境安全警示信息。如不避免则可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果。

“须知”不涉及人身伤害。

对正文中重点信息的补充说明。

“说明”不是安全警示信息,不涉及人身、设备及环境伤害信息。

修改记录

文档版本

发布日期

修改说明

07

2025-11-07

  • 更新“SPI”的“概述”章节内容。
  • 更新“I2C”的“功能描述”章节内容。
  • 新增“TRNG”章节内容。

06

2025-08-07

  • 更新“Pinctrl”的“注意事项”章节内容。
  • 更新“UART”的“KCONFIG配置”章节内容。
  • 更新“SPI”的“开发指引”章节内容。

05

2025-05-30

  • 更新“Pinctrl”章节内容。
  • 更新“SPI”章节内容。
  • 更新“I2C”的“KCONFIG配置”章节内容。
  • 更新“ADC”章节内容。
  • 更新“Flash”章节内容。
  • 更新“PDM”章节内容。

04

2025-01-24

  • 更新“Pinctrl”章节内容。
  • 更新“GPIO”章节内容。
  • 更新“UART”章节内容。
  • 更新“SPI”章节内容。
  • 更新“ADC”章节内容。
  • 更新“DMA”的“注意事项”章节内容。
  • 更新“Flash”的“功能描述”章节内容。
  • 更新“KEYSCAN”的“开发指引”章节内容。

03

2024-08-29

  • 更新“UART”~“KEYSCAN”章节内容。
  • 新增“附:MIDDLEWARE”章节内容。

02

2024-07-05

  • 更新“GPIO”章节内容。
  • 更新“UART”章节内容。
  • 更新“SPI”章节内容。
  • 更新“I2C”章节内容。
  • 更新“ADC”章节内容。
  • 更新“PWM”章节内容。
  • 更新“WDT”章节内容。
  • 更新“Timer”章节内容。
  • 新增“SFC”章节内容。
  • 更新“KEYSCAN”章节内容。

01

2024-05-24

第一次正式版本发布。

00B03

2024-03-01

  • 新增“ADC”章节内容。
  • 新增“Flash”章节内容。

00B02

2023-10-27

新增“USB_DFU”章节内容。

00B01

2023-09-27

第一次临时版本发布。

Pinctrl

概述

提供Pinctrl控制器用于控制IO管脚的复用功能,可配置规格如下:

  • 支持配置32个IO管脚。

  • 支持配置IO驱动能力、IO功能复用以及设置IO上下拉状态等功能。

功能描述

Pinctrl驱动模块提供的接口及功能如下:

  • uapi_pin_init(void):初始化Pinctrl。

  • uapi_pin_deinit(void):去初始化Pinctrl。

  • uapi_pin_set_mode(pin_t pin, pin_mode_t mode):设置指定IO复用模式(pin表示当前IO管脚,mode表示当前引脚需要配置的复用模式)。

  • uapi_pin_get_mode(pin_t pin):获取指定IO的复用模式(pin表示当前IO管脚)。

  • uapi_pin_set_ds(pin_t pin, pin_drive_strength_t ds):设置指定IO驱动能力(pin表示当前IO管脚,ds表示当前引脚需要配置的驱动能力)。

  • uapi_pin_get_ds(pin_t pin):获取指定IO驱动能力(pin表示当前IO管脚)。

  • uapi_pin_set_pull(pin_t pin, pin_pull_t pull_type):设置指定IO的上拉/下拉状态(pin表示当前IO管脚,pull_type表示当前引脚需要配置的上下拉状态)。

  • uapi_pin_get_pull(pin_t pin):获取指定IO的上拉/下拉状态(pin表示当前IO管脚)。

  • uapi_pin_set_ie(pin_t pin,pin_input_enable_t ie):设置指定IO的IE使能/去使能(pin表示当前IO管脚,ie表示当前引脚需要配置的输入中断使能状态)。(打开CONFIG_PINCTRL_SUPPORT_IE宏才能使用)

  • uapi_pin_get_ie(pin_t pin):获取指定IO的IE使能/去使能(pin表示当前IO管脚)。(打开CONFIG_PINCTRL_SUPPORT_IE宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表1描述不一致,请以表1为准。

配置宏具体描述如表1所示。

表 1 PINCTRL相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开

CONFIG_PINCTRL_SUPPORT_LPM

PINCTRL support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认打开,本产品实际暂未实现,建议关闭

CONFIG_PINCTRL_USING_BS2X

Using pinctrl BS2X.

BS2X系列特定宏

默认打开,必选

CONFIG_PINCTRL_SUPPORT_IE

Set pinctrl support input-enable.

该宏代表Pinctrl的输入中断使能,若某个管脚配成输入模式或者有输入功能,要想使其输入值或者输入中断生效,则该宏必须打开,否则视情况选择

默认打开,建议打开

CONFIG_PINCTRL_SUPPORT_ST

Set pinctrl support schmitt-trigger.

Pinctrl支持施密特触发

默认关闭,本产品实际暂未实现,建议关闭

开发指引

Pinctrl接口使用遵循如下操作步骤(以下步骤根据实际需要可选):

  1. 调用uapi_pin_init接口,初始化PINCTRL模块。

  2. 调用uapi_pin_set_ie、uapi_pin_get_ie接口,设置/查看指定IO的IE输入中断使能/去使能。

  3. 调用uapi_pin_set_mode、uapi_pin_get_mode接口,设置/查看指定IO的复用模式。

  4. 调用uapi_pin_set_ds、uapi_pin_get_ds接口,设置/查看指定IO的驱动能力。

  5. 调用uapi_pin_set_pull、uapi_pin_get_pull接口,设置/查看指定IO的上拉/下拉状态。

  6. 调用uapi_pin_deinit接口,去初始化PINCTRL模块。

示例:

    /* 初始化PINCTRL模块 */ 
    uapi_pin_init();
    /* 设置S_MGPIO19的IE使能 */
    uapi_pin_set_ie(S_MGPIO19, PIN_IE_1);
    /* 设置S_MGPIO19的复用功能为gpio */
    uapi_pin_set_mode(S_MGPIO19, HAL_PIO_FUNC_GPIO);
    /* 获取S_MGPIO19的复用功能为gpio */
    pin_mode_t mode = uapi_pin_get_mode(S_MGPIO19);
    /* 设置S_MGPIO19的驱动能力为PIN_DS_2 */
    uapi_pin_set_ds(S_MGPIO19, PIN_DS_2);
    /* 获取S_MGPIO19的驱动能力为PIN_DS_2 */
    pin_drive_strength_t ds = uapi_pin_get_ds(S_MGPIO19);
    /* 设置S_MGPIO19为上拉模式 */
    uapi_pin_set_pull(S_MGPIO19, PIN_PULL_UP);
    /* 获取S_MGPIO19为上拉模式 */
    pin_pull_t pull = uapi_pin_get_pull(S_MGPIO19);
    /* 去初始化PINCTRL模块 */ 
    uapi_pin_deinit();

注意事项

  1. 配置IO复用功能时,应关注此IO是否支持目标功能或者已经被复用为其他功能,避免影响既有功能, IO复用请参考“sdk\drivers\chips\bs2x\porting\pinctrl\pinctrl_porting.h”源码中“pin_mode_t”结构体的定义。

  2. SDK默认配置的管脚IE全部为使能,一般来说,对于管脚上会发生中断或者承担数据的传输的管脚,要使能IE,避免影响IP功能;除此之外的管脚,可以去使能IE,避免不必要的IO漏电,影响功耗。

    本产品已经将需要IE使能的管脚在对应的驱动内配置完毕。

    配置流程 :通过函数uapi_pin_set_ie(pin, PIN_IE_0/PIN_IE_1)去使能/使能IE,并确保CONFIG_PINCTRL_SUPPORT_IE宏打开。

  3. 驱动能力PIN_DS_0为最弱,PIN_DS_3为最强。

说明: 如果IE不使能但是注册了gpio低电平中断,CPU会一直收到中断。 IE是控制输入中断使能的功能,如果GPIO配置输出为高电平,建议Pinctrl配置上拉,否则会漏电。

GPIO

概述

GPIO(General-purpose input/output)是通用输入输出的缩写,是一种通用的I/O接口标准。可以配置为输入或输出模式,以便控制外部设备或与其他设备通信。可用于连接各种设备,如LED灯、传感器、执行器等。

GPIO规格如下:

  • 支持设置/获取GPIO管脚方向、设置/获取输出电平状态。

  • 支持外部电平中断以及外部边沿中断上报。

  • 支持每个GPIO独立中断。

功能描述

GPIO模块提供的接口及功能如下:

  • uapi_gpio_init(void):初始化GPIO。

  • uapi_gpio_deinit(void):去初始化GPIO。

  • uapi_gpio_set_dir(pin_t pin, gpio_direction_t dir):设置指定GPIO方向(输入/输出)(pin表示当前IO管脚,dir表示当前引脚需要配置的输入输出方向)。

  • uapi_gpio_get_dir(pin_t pin):获取指定GPIO方向(输入/输出)(pin表示当前IO管脚)。

  • uapi_gpio_set_val(pin_t pin, gpio_level_t level):设置指定GPIO输出电平状态(pin表示当前IO管脚,level表示当前引脚需要配置的电平状态)。

  • uapi_gpio_get_val(pin_t pin):获取指定GPIO的输入电平状态(pin表示当前IO管脚)。

  • uapi_gpio_get_output_val(pin_t pin):获取指定GPIO的输出电平状态(pin表示当前IO管脚)。

  • uapi_gpio_toggle(pin_t pin):GPIO输出电平状态翻转(pin表示当前IO管脚)。

  • uapi_gpio_register_isr_func(pin_t pin, uint32_t trigger, gpio_callback_t callback):注册指定GPIO中断(pin表示当前IO管脚,trigger表示当前引脚需要配置的中断类型,callback表示当前引脚配置的回调函数)。

  • uapi_gpio_unregister_isr_func(pin_t pin):去注册指定GPIO中断(pin表示当前IO管脚)。

  • uapi_gpio_enable_interrupt(pin_t pin):使能GPIO中断(pin表示当前IO管脚)。

  • uapi_gpio_disable_interrupt(pin_t pin): 关闭GPIO中断(pin表示当前IO管脚)。

  • uapi_gpio_clear_interrupt(pin_t pin):清除GPIO中断(pin表示当前IO管脚)。

KCONFIG配置

说明: 若上述图片所示与表1描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 GPIO相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_GPIO_SUPPORT_LPM

GPIO support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认打开,视情况选择,建议打开

CONFIG_GPIO_SELECT_CORE

GPIO support choose to select core.

GPIO支持多核选择,本产品是单核,不使用

默认关闭,本产品暂未使用,建议关闭

CONFIG_GPIO_USING_V100

Using GPIO V100.

该宏代表使用GPIO V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_GPIO_USING_V150

Using GPIO V150.

该宏代表使用GPIO V150的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_GPIO_BANK_NUM

GPIO max group numbers.

本产品当前仅支持一组GPIO,默认管脚数是32

默认值是1,建议不做修改

CONFIG_GPIO0_WIDTH

GPIO pin number of group 0.

本产品当前仅支持一组GPIO的默认管脚数

默认值是32,建议不做修改

CONFIG_GPIO_SUPPORT_MULTISYSTEM

Using set/clr reg to control multi system gpio.

GPIO支持多子系统,本产品不使用

默认关闭,本产品暂未使用,建议关闭

开发指引

GPIO接口使用遵循如下操作步骤:

  1. 调用uapi_pin_init,uapi_gpio_init接口,初始化PINCTRL模块以及GPIO模块。

  2. 调用uapi_pin_set_mode接口,将PIN复用为GPIO功能。

  3. 根据用户开发需求,可设置GPIO接口为输出、输入和中断模式,设置方法如下:

    • 输出模式:

      1. 调用uapi_gpio_set_dir接口,设置GPIO方向为OUT,可通过调用uapi_gpio_get_dir接口,确定该GPIO方向是否设置合适。

      2. 调用uapi_gpio_set_val接口,设置GPIO输出电平状态(高/低)。

    • 输入模式:

      1. 调用uapi_gpio_set_dir接口,设置GPIO方向为IN,可通过调用uapi_gpio_get_dir接口,确定该GPIO方向是否设置合适。

      2. 调用uapi_pin_set_pull接口,配置pull up\pull done\pull none。

      3. 调用uapi_gpio_get_val接口,获取GPIO输入电平状态。

    • 中断模式:

      1. 调用uapi_gpio_set_dir接口,设置GPIO方向为IN,可通过调用uapi_gpio_get_dir接口,确定该GPIO方向是否设置合适。

      2. 调用uapi_gpio_register_isr_func接口,注册GPIO中断回调函数。

      3. 调用uapi_gpio_enable_interrupt接口,使能GPIO中断。

      4. 调用uapi_gpio_unregister_isr_func接口,注销GPIO中断回调函数(去注册中断时调用)。

      5. 调用uapi_gpio_disable_interrupt接口,去使能GPIO中断。

      6. 调用uapi_gpio_clear_interrupt接口:清除GPIO中断。

  4. 调用uapi_gpio_deinit接口,去初始化GPIO模块。

此处以输入模式为例:

#include "gpio.h"
void gpio_callback_func(pin_t pin, uintptr_t param)
{
    unused(param);
    uapi_gpio_disable_interrupt(pin);
    osal_printk("PIN:%d interrupt success. \r\n", pin);
}
errcode_t sample_gpio_test(pin_t pin)
{
    uapi_pin_init();
    uapi_gpio_init();
    uapi_pin_set_mode(pin, HAL_PIO_FUNC_GPIO); /* 设置指定IO复用为GPIO模式 */
    uapi_gpio_set_dir(pin, GPIO_DIRECTION_INPUT); /* 设置指定GPIO为输入模式 */
    /* 注册指定GPIO上升沿中断,回调函数为gpio_callback_func */
    if (uapi_gpio_register_isr_func(pin, GPIO_INTERRUPT_RISING_EDGE, gpio_callback_func) != ERRCODE_SUCC) {
        uapi_gpio_unregister_isr_func(pin); /* 清理残留 */
        return ERRCODE_FAIL;
    }
    uapi_gpio_enable_interrupt(pin);
    return ERRCODE_SUCC;
}

注意事项

  • 在使用GPIO电平中断时,需要在回调函数中清除中断并暂时去使能中短触发,否则会导致系统一直处于中断处理中,无法执行其他功能。

  • 触发方式(trigger)在没有明确需求场景时,推荐使用默认配置。

UART

概述

UART(Universal Asynchronous Receiver/Transmitter)是通用异步收发器的缩写,是一种串行、异步、全双工的通信协议,用于设备间的数据传输。UART是最常用的设备间通信协议之一,正确配置后,UART可以配合许多不同类型的涉及发送和接收串行数据的串行协议工作 。

芯片MCU侧提供了3个可配置的UART外设单元:UART0(UART_L0)、UART1(UART_H0)、UART2(UART_L1)。

UART规格如下:

  • 支持可编程数据位、可编程停止位、可编程校验位。

  • UART支持流控工作模式,并支持配置RTS水线。

  • 提供64×8的TX FIFO以及RX FIFO。

  • 支持接收FIFO中断、发送FIFO中断、接收超时中断、错误中断等中断屏蔽与响应。

  • 支持DMA数据搬移方式。

功能描述

说明: 若UART驱动需要支持DMA数据收发,需确保DMA驱动已完成初始化。DMA初始化请参考“DMA”进行配置。

驱动代码在include\driver\uart.h声明了UART驱动相关函数,提供的接口及功能如下:

  • uapi_uart_init(uart_bus_t bus, const uart_pin_config_t *pins, const uart_attr_t *attr, const uart_extra_attr_t *extra_attr, uart_buffer_config_t *uart_buffer_config):初始化UART模块(bus表示当前UART bus串口号,pins表示UART中使用的PIN,包括TX, RX, RTS和CTS,attr表示UART的基础配置参数,extra_attr表示UART的高级配置参数,uart_buffer_config表示指定UART的接收Buffer)。

  • uapi_uart_deinit(uart_bus_t bus):去初始化UART(bus表示当前UART bus串口号)。

  • uapi_uart_set_attr(uart_bus_t bus, const uart_attr_t *attr):设置UART配置参数(bus表示当前UART bus串口号,attr表示UART的基础配置参数)。

  • uapi_uart_get_attr(uart_bus_t bus, const uart_attr_t *attr):获取UART配置参数(bus表示当前UART bus串口号,attr表示UART的基础配置参数)。

  • uapi_uart_has_pending_transmissions(uart_bus_t bus):查询UART是否正在传输数据(bus表示当前UART bus串口号)。

  • uapi_uart_rx_fifo_is_empty(uart_bus_t bus):判断RX FIFO是否为空(bus表示当前UART bus串口号)。

  • uapi_uart_tx_fifo_is_empty(uart_bus_t bus):判断TX FIFO是否为空(bus表示当前UART bus串口号)。

  • uapi_uart_write(uart_bus_t bus, const uint8_t *buffer, uint32_t length, uint32_t timeout):将数据发送到已经打开的UART上,使用直接发送的方式(bus表示当前UART bus串口号,buffer表示要发送的数据Buffer,length表示要发送的数据Buffer长度,timeout表示超时时间,该参数暂未使用(需开启CONFIG_SUPPORT_UART_TX_POLL_TIMEOUT, timeout参数才生效))。(打开CONFIG_UART_SUPPORT_TX宏才能使用)

  • uapi_uart_write_nolock(uart_bus_t bus, const uint8_t *buffer, uint32_t length, uint32_t timeout):将数据发送到已经打开的UART上,使用直接发送不会锁中断的方式(bus表示当前UART bus串口号,buffer表示要发送的数据Buffer,length表示要发送的数据Buffer长度,timeout表示超时时间,该参数暂未使用(需开启CONFIG_SUPPORT_UART_TX_POLL_TIMEOUT, timeout参数才生效))。(打开CONFIG_UART_SUPPORT_TX宏才能使用)

  • uapi_uart_write_int(uart_bus_t bus, const uint8_t *buffer, uint32_t length, void *params, uart_tx_callback_t finished_with_buffer_func):使用中断模式将数据发送到已打开的UART上,当数据发送完成,会调用回调函数(bus表示当前UART bus串口号,buffer表示要发送的数据Buffer,length表示要发送的数据Buffer长度,params表示传递到完成传输的回调函数的参数,finished_with_buffer_func表示数据发送完成后的回调函数)。(打开CONFIG_UART_SUPPORT_TX宏才能使用)

  • uapi_uart_write_by_dma(uart_bus_t bus, const void *buffer, uint32_t length, uart_write_dma_config_t *dma_cfg):通过DMA发送数据(bus表示当前UART bus串口号,buffer表示要发送的数据Buffer,length表示要发送的数据Buffer长度,dma_cfg表示发送数据时的DMA配置)。(打开CONFIG_UART_SUPPORT_TX以及CONFIG_UART_SUPPORT_DMA宏才能使用)

  • uapi_uart_read_by_dma(uart_bus_t bus, const void *buffer, uint32_t length, uart_write_dma_config_t *dma_cfg):通过DMA接收数据(bus表示当前UART bus串口号,buffer表示存储接收数据的Buffer,length表示存储接收数据的Buffer长度,dma_cfg表示接收数据时的DMA配置)。(打开CONFIG_UART_SUPPORT_TX以及CONFIG_UART_SUPPORT_DMA宏才能使用)

  • uapi_uart_register_read_by_dma_callback(uart_bus_t bus, uart_write_dma_config_t *dma_cfg):注册中断触发DMA搬运的回调(bus表示当前UART bus串口号,dma_cfg表示接收数据时的DMA配置)。

    (打开CONFIG_UART_SUPPORT_TX、CONFIG_UART_SUPPORT_DMA以及CONFIG_UART_SUPPORT_INT_TRIGGER_DMA宏才能使用)

  • uapi_uart_unregister_read_by_dma_callback(uart_bus_t bus):去注册中断触发DMA搬运的回调(bus表示当前UART bus串口号)。(打开CONFIG_UART_SUPPORT_TX、CONFIG_UART_SUPPORT_DMA以及CONFIG_UART_SUPPORT_INT_TRIGGER_DMA宏才能使用)

  • uapi_uart_register_write_by_dma_callback(uart_bus_t bus, uart_tx_by_dma_callback_t tx_dma_cb):注册在DMA中断里直接调用DMA写的回调函数(bus表示当前UART bus串口号,tx_dma_cb表示DMA完成中断里调用DMA写的回调函数)。(打开CONFIG_UART_SUPPORT_SEND_IN_DMA_ISR宏才能使用)

  • uapi_uart_register_rx_callback(uart_bus_t bus, uart_rx_condition_t condition, uint32_t size, uart_rx_callback_t callback):注册接收回调函数,此回调函数会根据触发条件和Size触发(bus表示当前UART bus串口号,condition表示回调触发的条件,size表示如果触发条件涉及到数据长度,该参数就表示需要的数据长度,callback表示接收数据后的回调函数)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_unregister_rx_callback(uart_bus_t bus):取消注册接收回调函数(bus表示当前UART bus串口号)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_register_parity_error_callback(uart_bus_t bus, uart_error_callback_t callback):注册奇偶校验错误处理的回调函数(bus表示当前UART bus串口号,callback表示奇偶校验错误处理回调函数)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_register_frame_error_callback(uart_bus_t bus, uart_error_callback_t callback):注册帧错误处理回调函数(bus表示当前UART bus串口号,callback表示帧错误错误处理回调函数)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_register_overrun_error_callback(uart_bus_t bus, uart_error_callback_t callback):注册溢出错误处理回调函数(bus表示当前UART bus串口号,callback表示溢出错误错误处理回调函数)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_read(uart_bus_t bus, const uint8_t *buffer, uint32_t length, uint32_t timeout):从UART读数据(bus表示当前UART bus串口号,buffer表示存储接收数据的Buffer,length表示存储接收数据的Buffer长度,timeout表示超时时间,该参数暂未使用)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_flush_rx_data(uart_bus_t bus):刷新UART接收Buffer中的数据(bus表示当前UART bus串口号)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_update_rx_buffer(uart_bus_t bus, uint8_t *rx_buffer, uint16_t rx_buffer_size):更新UART接收Buffer的地址和长度(bus表示当前UART bus串口号,rx_buffer表示存储接收数据的Buffer,length表示存储接收数据的Buffer长度)。(打开CONFIG_UART_SUPPORT_RX宏才能使用)

  • uapi_uart_suspend(uintptr_t arg):挂起所有的UART通道,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_UART_SUPPORT_LPM宏才能使用)

  • uapi_uart_resume(uintptr_t arg):恢复所有的UART通道,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_UART_SUPPORT_LPM宏才能使用)

  • uart_port_lli_init_config:配置UART所需管脚的接口,入参为测试需要的UART通道。

  • uapi_uart_dma_send:使用UART-DMA循环链表发送的接口入参为测试所用的UART、发送数据的buffer地址、发送数据的长度和发送数据后的回调函数。回调函数类型为typedef void (*uart_data_send_cb_t)(uint8_t channel, errcode_t result) ,errcode_t result会返回发送数据时出现错误的类型。该接口调用一次发送一次数据,多包传输需要重复调用。该接口内可以通过(Top) → Drivers → Drivers → UART → Uart Configuration → Whether DMA need send head or not.的Kconfig来控制是否发送包头,如果要发送不定长的数据包,则必须要发送包头。如果发送定长数据包则可以在DMA初始化配置中确认好数据长度。

  • uapi_uart_dma_recv_register:使用UART-DMA做接收的初始化接口,入参为测试所用的UART、接收数据的buffer地址、接收数据的长度和接收到数据后的回调函数。回调函数类型为typedef bool (*uart_data_recv_cb_t)(uint8_t channel, uint16_t length, errcode_t result),返回值的布尔型告知驱动内是否可以继续接受,errcode_t result会返回接收数据时出现的错误的类型。收数据只需要注册好回调即可自动接收,故只需要调用一次。

  • uapi_uart_dma_recv_register:恢复UART-DMA循环链表接收的接口,接收方可以选择在uapi_uart_dma_recv_register函数的回调函数里返回false来暂停接收,在需要恢复接收时调用uapi_uart_dma_lli_continue_recv来继续接收。

  • uapi_uart_update_dma_recv_buff:切换UART-DMA循环链表接收buffer地址的接口,接收方可以使用该接口来实现如乒乓buffer的功能。

中断描述

驱动代码在drivers\drivers\hal\uart\v151\hal_uart_v151_regs_op.h文件里声明了UART中断类型,如下图1所示,具体描述可参考表1

图 1 UART中断类型

表 1 中断描述

Interrupt ID

Interrupt Type

Software Interrupt Type

Interrupt Source

0x0

Modem status

HAL_UART_INTERRUPT_MODEM_STATUS

清除发送或数据就绪或数据载体检测。如果启用了自动流控模式,CTS 的更改不会引起中断

该中断本产品暂不涉及

0x1

None

HAL_UART_INTERRUPT_NO_INTERRUPT

0x2

Transmit holding register empty

HAL_UART_INTERRUPT_TX

情况一:发送器保持寄存器为空(THRE模式已禁用)

情况二:FIFO低于阈值( THRE模式已启用)

0x4

Received data available

HAL_UART_INTERRUPT_RX

情况一:接收器数据可用(非 FIFO 模式或 FIFO 禁用)

情况二:RCVR FIFO 达到水线触发级别(FIFO 模式和 FIFO 已启用)

0x6

Receiver line status

HAL_UART_INTERRUPT_RECEIVER_LINE_STATUS

发生溢出/奇偶校验/帧同步错误中断、断点中断或地址接收中断的时候会触发该中断

0x7

Busy detect indication

HAL_UART_INTERRUPT_BUSY_DETECT

UART工作的时候重新配置UART_CTL寄存器的值会触发该中断

0xC

Character timeout indication

HAL_UART_INTERRUPT_CHAR_TIMEOUT

原意是指在过去的4个字符时间内,接收器FIFO中没有字符进出,但在此期间至少有1个字符存在(即RX FIFO中有数,但是未达到水线且tx不再发送数据过来,一段时间后会触发该中断)。实际在本产品同0x4中断

0xD

None

HAL_UART_INTERRUPT_ERROR

实际在本产品同0x6中断

0xE

None

HAL_UART_INTERRUPT_IDLE

该中断本产品不涉及

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 UART相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_UART_BAUD_RATE_DIV_8

UART Baud rate div 8.

UART采用8分频后的波特率,本产品暂未使用

默认打开,本产品实际暂未使用,建议关闭

CONFIG_UART_SUPPORT_TX

UART support TX.

UART支持TX发送,视情况选择,和CONFIG_UART_SUPPORT_RX至少二选一打开,建议都打开

默认打开,建议打开

CONFIG_SUPPORT_UART_TX_POLL_TIMEOUT

UART support timeout in uart TX poll mode.

UART TX接口是否打开超时机制

CONFIG_UART_SUPPORT_TX

默认关闭,视情况选择

CONFIG_UART_SUPPORT_TX_INT

UART support TX interrupt mode.

UART支持tx中断模式发送,视情况选择

默认关闭,建议关闭

CONFIG_UART_SUPPORT_RX

UART support RX.

UART支持RX接收,视情况选择,和CONFIG_UART_SUPPORT_TX至少二选一打开,建议都打开

默认打开,建议打开

CONFIG_SUPPORT_UART_RX_POLL_TIMEOUT

UART support timeout in uart RX poll mode.

UART RX接口是否开启超时机制

CONFIG_UART_SUPPORT_RX

默认关闭,视情况选择

CONFIG_UART_SUPPORT_RX_THREAD

UART support RX thread.

UART支持RX线程,本产品暂未使用

CONFIG_UART_SUPPORT_RX

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_UART_SUPPORT_DMA

UART support dma.

UART支持DMA模式,视情况选择,若选择采用UART DMA模式,则需同步选择DMA驱动的KCONFIG配置,请参考“KCONFIG配置”

CONFIG_DRIVER_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_UART_SUPPORT_INT_TRIGGER_DMA

UART support rx interrupt trigger dma.

UART支持在RX中断里触发DMA,视情况选择

CONFIG_UART_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_UART_SUPPORT_SEND_IN_DMA_ISR

UART support send in dma isr.

UART支持在DMA中断里发送数据,视情况选择,若选择采用UART DMA模式,则需同步选择DMA驱动的KCONFIG配置,请参考“KCONFIG配置”

CONFIG_DRIVER_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_UART_SUPPORT_DMA_LLI

UART Support DMA lli transport.

UART支持DMA LLI传输

CONFIG_DRIVER_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_UART_LLI_BAUDRATE

UART LLI BAUDRATE. Depends on hardware constraints.

UART使用DMA传输时的波特率

CONFIG_UART_SUPPORT_DMA_LLI

默认值是115200,视硬件限制选择

CONFIG_UART_DMA_LLI_MAX_MEMBER

Max lli member number.

UART LLI发送队列的深度。

CONFIG_UART_SUPPORT_DMA_LLI

默认值是6,视情况选择

CONFIG_UART_TX_DMA_NEED_HEADER

Whether DMA need send head or not.

UART是否发送包头信息。

CONFIG_UART_SUPPORT_DMA_LLI

默认开启,如果发送的包长固定则可以关闭。

CONFIG_UART_SUPPORT_FLOW_CTRL

UART support flow control.

UART支持流控,该宏只有在调用初始化接口flow_ctrl参数配3的情况下打开才会生效,否则无用

默认关闭,视情况选择

CONFIG_UART_SUPPORT_HW_FLOW_CTRL

UART support hardware flow control.

UART支持硬件流控,本产品暂未使用

CONFIG_UART_SUPPORT_FLOW_CTRL

默认关闭,本产品实际暂未实现,建议关闭

CONFIG_UART_SUPPORT_SW_FLOW_CTRL

UART support software flow control.

UART支持软件流控,本产品暂未使用

CONFIG_UART_SUPPORT_FLOW_CTRL

默认关闭,本产品实际暂未实现,建议关闭

CONFIG_UART_SUPPORT_LPM

UART support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认打开,视情况选择,建议打开

CONFIG_UART_LOG_WRITE_WITH_NOLOCK

UART support write logs with nolock.

UART支持不锁中断日志

默认打开,视情况选择,建议打开

CONFIG_UART_FIFO_DEPTH

UART FIFO depth.

UART FIFO深度配置,本产品默认FIFO深度为64,建议保持默认值

默认值是64,建议不做修改

CONFIG_UART_SUPPORT_LPC

UART support low power control, control power and clock.

UART 工作时钟

默认打开,必选

CONFIG_UART_MULTI_CORE_RESUME

UART support low power resume skip some port for multi-core.

该宏仅用于多核场景下UART低功耗恢复使用,本产品暂未使用

CONFIG_UART_SUPPORT_LPM

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_UART_NOT_SUPPORT_RX_CONDITON_SIZE_OPTIMIZE

UART don't support rx condition size optimization.

UART不支持RX条件大小优化,主要用来影响回调触发条件

CONFIG_UART_SUPPORT_RX

默认关闭,建议关闭

CONFIG_UART_SUPPORT_QUERY_REGS

UART support query regs values.

UART支持回读所有相关寄存器的值,视情况选择

默认关闭,视情况选择

CONFIG_UART_L0_TX_PIN

UART config uart L0 tx pin.

UART配置BUS 0即L0的TX 管脚,本产品默认使用SMGPIO19

默认值是19,建议不做修改

CONFIG_UART_L0_RX_PIN

UART config uart L0 rx pin.

UART配置BUS 0即L0的RX 管脚,本产品默认使用SMGPIO20

默认值是20,建议不做修改

CONFIG_UART_H0_TX_PIN

UART config uart H0 tx pin.

UART配置BUS 1即H0的TX 管脚,本产品默认使用SMGPIO17

默认值是17,建议不做修改

CONFIG_UART_H0_RX_PIN

UART config uart H0 rx pin.

UART配置BUS 1即H0的RX 管脚,本产品默认使用SMGPIO18

默认值是18,建议不做修改

CONFIG_UART_H0_CTS_PIN

UART config uart H0 cts pin, which is not used by default.

UART配置BUS 1即H0的CTS管脚

CONFIG_UART_SUPPORT_FLOW_CTRL

默认值是100,该值不生效,按实际情况配置

CONFIG_UART_H0_RTS_PIN

UART config uart H0 rts pin, which is not used by default.

UART配置BUS 1即H0的RTS管脚

CONFIG_UART_SUPPORT_FLOW_CTRL

默认值是100,该值不生效,按实际情况配置

CONFIG_UART_DLF_SIZE

UART_DLF_SIZE.

UART DLF SIZE,本产品暂未使用

默认值是4,本产品实际暂未使用,建议不做修改

CONFIG_UART_USING_V100

Using UART V100.

该宏代表使用UART V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_UART_USING_V120

Using UART V120.

该宏代表使用UART V120的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_UART_USING_V151

Using UART V151.

该宏代表使用UART V151的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_UART_IP_VERSION_V151_PRO

The uart ip version from svn version for uartv151 PRO.

本产品不使用UART V151 PRO

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_UART_RX_EMPTY_TRIG_H

The rx empty high trigger for uartv151 PRO.

该宏仅用于UART V151 PRO的RX空触发使用,本产品不使用

CONFIG_UART_IP_VERSION_V151_PRO

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_UART_USING_LP_UART

Using LP_UART.

本产品不使用

默认关闭,本产品实际暂未使用,建议关闭

开发指引

以应用UART1为例,数据收发流程如下:

  1. 配置IO复用。将对应的IO分别复用为UART1的TX、RX、RTS、CTS功能。

    如果不需要支持硬件流控,仅配置TX、RX即可。

    void usr_uart_io_config(void)
    {
        /* 如下IO复用配置,也可集中在SDK中的usr_io_init函数中进行配置 */
        uapi_pin_set_mode(S_AGPIO5, HAL_PIO_UART_H0_RTS); /* uart1 rts */
        uapi_pin_set_mode(S_AGPIO6, HAL_PIO_UART_H0_CTS); /* uart1 cts */
        uapi_pin_set_mode(S_AGPIO12, HAL_PIO_UART_H0_TXD); /* uart1 tx */
        uapi_pin_set_mode(S_AGPIO13, HAL_PIO_UART_H0_RXD); /* uart1 rx */
    }
    
  2. UART初始化。配置UART的波特率、数据位等属性,并使能UART。

    #define TEST_UART_RX_BUFF_SIZE 0x40
    unsigned char g_uart_rx_buff[TEST_UART_RX_BUFF_SIZE] = { 0 };
    uart_buffer_config_t g_uart_buffer_config = {
        .rx_buffer = g_uart_rx_buff,
        .rx_buffer_size = TEST_UART_RX_BUFF_SIZE
    };
    errcode_t usr_uart_init_config(void)
    {
        errcode_t errcode;
        uart_attr_t attr = {
            .baud_rate = 115200, /* 波特率 */
            .data_bits = 3,      /* 8bit数据位 */
            .stop_bits = 0,      /* 1bit停止位 */
            .parity = 0,         /* 校验位 */
            .flow_ctrl = 0       /* 流控 */
        };
        uart_pin_config_t pin_config = {
            .tx_pin = S_AGPIO12, /* uart1 tx */
            .rx_pin = S_AGPIO13, /* uart1 rx */
            .cts_pin = S_AGPIO6, /* 流控功能,可选 */
            .rts_pin = S_AGPIO5  /* 流控功能,可选 */
        };
        uapi_uart_deinit(UART_BUS_1);   /* 此处去初始化操作,是为了清除之前在其他地方使用的一些配置,排除干扰 */
        errcode = uapi_uart_init(UART_BUS_1, &pin_config, &attr, NULL, &g_uart_buffer_config);
        if (errcode != ERRCODE_SUCC) {
            osal_printk("uart init fail\r\n");
        }
        return errcode;
    }
    
  3. UART数据收发。调用UART轮询读写数据接口,进行数据收发。

    void usr_uart_read_data(void)
    {
        int len;
        unsigned char g_test_uart_rx_buffer[64];
        len = uapi_uart_read(UART_BUS_1, g_test_uart_rx_buffer, 64, 0);
        if(len > 0) {
            /* process */
        }
    }
    int usr_uart_write_data(unsigned int size, char* buff)
    {
        unsigned char tx_buff[64] = { 0 };
        if (memcpy_s(tx_buff, 64, buff, size) != EOK) {
            return ERRCODE_FAIL;
        }
        int ret = uapi_uart_write(UART_BUS_1, tx_buff, size, 0);
        if(ret == -1) {
            return ERRCODE_FAIL;
        }
        return ERRCODE_SUCC;
    }
    

UART DMA模式发送数据流程如下:

  1. 配置IO复用。将对应的IO复用为UART1的TX、RX、RTS、CTS功能。

    如果不需要支持硬件流控,仅配置TX、RX即可。

    void usr_uart_io_config(void)
    {
        /* 如下IO复用配置,也可集中在SDK中的usr_io_init函数中进行配置 */
        uapi_pin_set_mode(S_MGPIO21, HAL_PIO_UART_H0_RTS); /* uart1 rts */
        uapi_pin_set_mode(S_MGPIO22, HAL_PIO_UART_H0_CTS); /* uart1 cts */
        uapi_pin_set_mode(S_MGPIO17, HAL_PIO_UART_H0_TXD); /* uart1 tx */
        uapi_pin_set_mode(S_MGPIO18, HAL_PIO_UART_H0_RXD); /* uart1 rx */
    }
    
  2. UART初始化。配置UART的波特率、数据位等属性,并使能UART。

    errcode_t usr_uart_init_config(void)
    {
        errcode_t errcode;
        uart_attr_t attr = {
            .baud_rate = 115200, /* 波特率 */
            .data_bits = 3,      /* 8bit数据位 */
            .stop_bits = 0,      /* 1bit停止位 */
            .parity = 0,         /* 校验位 */
            .flow_ctrl = 0       /* 流控 */
        };
        uart_pin_config_t pin_config = {
            .tx_pin = S_MGPIO17, /* uart1 tx */
            .rx_pin = S_MGPIO18, /* uart1 rx */
            .cts_pin = S_MGPIO22, /* 流控功能,可选 */
            .rts_pin = S_MGPIO21/* 流控功能,可选 */
        };
        uart_extra_attr_t ext_config = {
            .tx_dma_enable = true,
            .tx_int_threshold = UART_FIFO_INT_TX_LEVEL_EQ_0_CHARACTER,
            .rx_dma_enable = true,
            .rx_int_threshold = UART_FIFO_INT_RX_LEVEL_1_CHARACTER
        }
        errcode = uapi_uart_init(UART_BUS_1, &pin_config, &attr, &ext_config, &g_uart_buffer_config);
        if (errcode != ERRCODE_SUCC) {
            osal_printk("uart init fail\r\n");
        }
        return errcode;
    }
    
  3. UART DMA数据收发。

    #define TEST_UART_DMA_SEND_BUFF_SIZE 1024
    
    
    static errcode_t test_uart_read_by_dma(void)
    {
        int len;
        unsigned char g_test_uart_rx_buffer[TEST_UART_DMA_SEND_BUFF_SIZE] = { 0 };
        uart_write_dma_config_t dma_cfg = {
            .src_width = HAL_DMA_TRANSFER_WIDTH_8,              /* 0代表8bit */
            .dest_width = HAL_DMA_TRANSFER_WIDTH_8,             /* 0代表8bit */
            .burst_length = HAL_DMA_BURST_TRANSACTION_LENGTH_1, /* 代表1字节 */
            .priority = 0                                       /* 优先级0 */
        };
        len = uapi_uart_read_by_dma(UART_BUS_1, g_test_uart_rx_buffer, TEST_UART_DMA_SEND_BUFF_SIZE, &dma_cfg);
        if(len > 0) {
            /* process */
        }
    }
    static errcode_t test_uart_write_by_dma(void)
    {
        uint8_t dma_buff[TEST_UART_DMA_SEND_BUFF_SIZE] = { 0 };
        if (memset_s(dma_buff, TEST_UART_DMA_SEND_BUFF_SIZE, 0XA5, TEST_UART_DMA_SEND_BUFF_SIZE) != 0) {
            return ERRCODE_FAIL;
        }
        uart_write_dma_config_t dma_cfg = {
            .src_width = HAL_DMA_TRANSFER_WIDTH_8,              /* 0代表8bit */
            .dest_width = HAL_DMA_TRANSFER_WIDTH_8,             /* 0代表8bit */
            .burst_length = HAL_DMA_BURST_TRANSACTION_LENGTH_1, /* 代表1字节 */
            .priority = 0                                       /* 优先级0 */
        };
        if (uapi_uart_write_by_dma(UART_BUS_1, dma_buff, len, &dma_cfg) != len) {
            osal_printk("[UART] *** memory to uart fail!\r\n");
            return ERRCODE_FAIL;
        }
        return ERRCODE_SUCC;
    }
    

三种模式(轮询模式、中断模式、DMA模式)下的UART收发完整代码流程可参考以下代码:

#include "pinctrl.h"
#include "uart.h"
#include "watchdog.h"
#include "soc_osal.h"
#include "app_init.h"
#if defined(CONFIG_UART_SUPPORT_DMA)
#include "dma.h"
#include "hal_dma.h"
#endif

#define UART_BAUDRATE                      115200
#define CONFIG_UART_INT_WAIT_MS            5

#define UART_TASK_PRIO                     24
#define UART_TASK_STACK_SIZE               0x1000

static uint8_t g_app_uart_rx_buff[CONFIG_UART_TRANSFER_SIZE] = { 0 };
#if defined(CONFIG_UART_SUPPORT_INT_MODE)
static uint8_t g_app_uart_int_rx_flag = 0;
static volatile uint8_t g_app_uart_int_index = 0;
static uint8_t g_app_uart_int_rx_buff[CONFIG_UART_TRANSFER_SIZE] = { 0 };
#endif
static uart_buffer_config_t g_app_uart_buffer_config = {
    .rx_buffer = g_app_uart_rx_buff,
    .rx_buffer_size = CONFIG_UART_TRANSFER_SIZE
};

#if defined(CONFIG_UART_SUPPORT_DMA)
uart_write_dma_config_t g_app_dma_cfg = {
    .src_width = HAL_DMA_TRANSFER_WIDTH_8,
    .dest_width = HAL_DMA_TRANSFER_WIDTH_8,
    .burst_length = HAL_DMA_BURST_TRANSACTION_LENGTH_1,
    .priority = HAL_DMA_CH_PRIORITY_0
};
#endif

static void app_uart_init_pin(void)
{
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
    uapi_pin_set_ie(CONFIG_UART_RXD_PIN, PIN_IE_1);
#endif /* CONFIG_PINCTRL_SUPPORT_IE */
    uapi_pin_set_mode(CONFIG_UART_TXD_PIN, CONFIG_UART_TXD_PIN_MODE);
    uapi_pin_set_mode(CONFIG_UART_RXD_PIN, CONFIG_UART_RXD_PIN_MODE);
}

static void app_uart_init_config(void)
{
    uart_attr_t attr = {
        .baud_rate = UART_BAUDRATE,
        .data_bits = UART_DATA_BIT_8,
        .stop_bits = UART_STOP_BIT_1,
        .parity = UART_PARITY_NONE,
        .flow_ctrl = 0       /* 流控,配0表示不开流控 */
    };

    uart_pin_config_t pin_config = {
        .tx_pin = CONFIG_UART_TXD_PIN,
        .rx_pin = CONFIG_UART_RXD_PIN,
        .cts_pin = PIN_NONE,
        .rts_pin = PIN_NONE
    };

#if defined(CONFIG_UART_SUPPORT_DMA)
    uart_extra_attr_t extra_attr = {
        .tx_dma_enable = true,
        .tx_int_threshold = UART_FIFO_INT_TX_LEVEL_EQ_0_CHARACTER,
        .rx_dma_enable = true,
        .rx_int_threshold = UART_FIFO_INT_RX_LEVEL_1_CHARACTER
    };
    uapi_dma_init();
    uapi_dma_open();
    uapi_uart_deinit(CONFIG_UART_BUS_ID); /* 在此处调用仅表示排除在其他地方使用的配置,排除干扰。*/
    uapi_uart_init(CONFIG_UART_BUS_ID, &pin_config, &attr, &extra_attr, &g_app_uart_buffer_config);
#else
    uapi_uart_deinit(CONFIG_UART_BUS_ID);
    uapi_uart_init(CONFIG_UART_BUS_ID, &pin_config, &attr, NULL, &g_app_uart_buffer_config);
#endif
}

#if defined(CONFIG_UART_SUPPORT_INT_MODE)
static void app_uart_read_int_handler(const void *buffer, uint16_t length, bool error)
{
    unused(error);
    if (buffer == NULL || length == 0) {
        osal_printk("uart%d int mode transfer illegal data!\r\n", CONFIG_UART_BUS_ID);
        return;
    }

    uint8_t *buff = (uint8_t *)buffer;
    if (memcpy_s(g_app_uart_rx_buff, length, buff, length) != EOK) {
        osal_printk("uart%d int mode data copy fail!\r\n", CONFIG_UART_BUS_ID);
        return;
    }
    if (memcpy_s(g_app_uart_int_rx_buff + g_app_uart_int_index, length, g_app_uart_rx_buff, length) != EOK) {
        g_app_uart_int_index = 0;
        osal_printk("uart%d int mode data2 copy fail!\r\n", CONFIG_UART_BUS_ID);
    }
    g_app_uart_int_index += length;
    g_app_uart_int_rx_flag = 1;
}

static void app_uart_write_int_handler(const void *buffer, uint32_t length, const void *params)
{
    unused(params);
    uint8_t *buff = (void *)buffer;
    for (uint8_t i = 0; i < length; i++) {
        osal_printk("uart%d write data[%d] = %d\r\n", CONFIG_UART_BUS_ID, i, buff[i]);
    }
}

static void app_uart_register_rx_callback(void)
{
    osal_printk("uart%d int mode register receive callback start!\r\n", CONFIG_UART_BUS_ID);
    if (uapi_uart_register_rx_callback(CONFIG_UART_BUS_ID, UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE,
                                       1, app_uart_read_int_handler) == ERRCODE_SUCC) {
        osal_printk("uart%d int mode register receive callback succ!\r\n", CONFIG_UART_BUS_ID);
    }
}
#endif

static void *uart_task(const char *arg)
{
    unused(arg);
#if defined(CONFIG_UART_SUPPORT_DMA)
    int32_t ret = CONFIG_UART_TRANSFER_SIZE;
#if defined(CONFIG_UART_USING_V151)
    ret = ERRCODE_SUCC;
#endif
#endif
    /* UART pinmux. */
    app_uart_init_pin();

    /* UART init config. */
    app_uart_init_config();

#if defined(CONFIG_UART_SUPPORT_INT_MODE)
    app_uart_register_rx_callback();
#endif

    while (1) {
#if defined(CONFIG_UART_SUPPORT_INT_MODE)
        while (g_app_uart_int_rx_flag != 1) { osal_msleep(CONFIG_UART_INT_WAIT_MS); }
        g_app_uart_int_rx_flag = 0;
        osal_printk("uart%d int mode send back!\r\n", CONFIG_UART_BUS_ID);
        if (uapi_uart_write_int(CONFIG_UART_BUS_ID, g_app_uart_int_rx_buff, CONFIG_UART_TRANSFER_SIZE, 0,
                                app_uart_write_int_handler) == ERRCODE_SUCC) {
            osal_printk("uart%d int mode send back succ!\r\n", CONFIG_UART_BUS_ID);
        }
#elif defined(CONFIG_UART_SUPPORT_DMA)
        osal_printk("uart%d dma mode receive start!\r\n", CONFIG_UART_BUS_ID);
        if (uapi_uart_read_by_dma(CONFIG_UART_BUS_ID, g_app_uart_rx_buff, CONFIG_UART_TRANSFER_SIZE,
            &g_app_dma_cfg) == ret) {
            osal_printk("uart%d dma mode receive succ!\r\n", CONFIG_UART_BUS_ID);
        }
        osal_printk("uart%d dma mode send back!\r\n", CONFIG_UART_BUS_ID);
        if (uapi_uart_write_by_dma(CONFIG_UART_BUS_ID, g_app_uart_rx_buff, CONFIG_UART_TRANSFER_SIZE,
            &g_app_dma_cfg) == ret) {
            osal_printk("uart%d dma mode send back succ!\r\n", CONFIG_UART_BUS_ID);
        }
#else
        osal_printk("uart%d poll mode receive start!\r\n", CONFIG_UART_BUS_ID);
        (void)uapi_watchdog_kick();
        if (uapi_uart_read(CONFIG_UART_BUS_ID, g_app_uart_rx_buff, CONFIG_UART_TRANSFER_SIZE,
            0) == CONFIG_UART_TRANSFER_SIZE) {
            osal_printk("uart%d poll mode receive succ!\r\n", CONFIG_UART_BUS_ID);
        }
        osal_printk("uart%d poll mode send back!\r\n", CONFIG_UART_BUS_ID);
        if (uapi_uart_write(CONFIG_UART_BUS_ID, g_app_uart_rx_buff, CONFIG_UART_TRANSFER_SIZE,
            0) == CONFIG_UART_TRANSFER_SIZE) {
            osal_printk("uart%d poll mode send back succ!\r\n", CONFIG_UART_BUS_ID);
        }
#endif
    }

    return NULL;
}

static void uart_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)uart_task, 0, "UartTask", UART_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, UART_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the uart_entry. */
app_run(uart_entry);

------------------------------------------------------------------

双板UART DMA链表收发完整代码流程可参考以下代码:
master侧代码:
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "app_init.h"
#include "osal_task.h"
#include "dma.h"
#include "uart.h"
#include "osal_debug.h"
#define UART_TASK_PRIO                     24
#define UART_TASK_DURATION_MS              500
#define UART_TASK_STACK_SIZE               0x1000
#define UART_PAYLOAD_LEN                   255
rn uart_pin_config_t g_pin_config_map[UART_BUS_MAX_NUM];
static uint8_t g_app_uart_tx_payload_buff[UART_PAYLOAD_LEN] = { 0 };
static uint8_t g_app_uart_rx_payload_buff[UART_PAYLOAD_LEN] = { 0 };
volatile uint32_t g_success = 0;
volatile uint32_t g_fail = 0;
static void uart_dma_lli_write_cb(uint8_t channel, errcode_t result)
{
    unused(result);
    unused(channel);
}
static bool uart_dma_lli_read_cb(uint8_t channel, uint16_t length, errcode_t result)
{
    unused(channel);
    unused(length);
    unused(result);
    if (memcmp(g_app_uart_rx_payload_buff, g_app_uart_tx_payload_buff, UART_PAYLOAD_LEN) == 0) {
        g_success++;
    } else {
        g_fail++;
        for (uint32_t i = 0; i < UART_PAYLOAD_LEN; i++) {
            osal_printk("[%d] tx:0x%x,rx:0x%x\r\n", i, g_app_uart_tx_payload_buff[i], g_app_uart_rx_payload_buff[i]);
        }
    }
    if ((g_success + g_fail) % 0x400 == 0) {
        osal_printk("g_success:%d\r\n", g_success);
        osal_printk("g_fail:%d\r\n", g_fail);
    }
    for (uint16_t i = 0; i < UART_PAYLOAD_LEN; i++) {
        g_app_uart_tx_payload_buff[i] = rand() % 0xff;
    }
    uapi_uart_dma_send(CONFIG_UART_BUS_ID, g_app_uart_tx_payload_buff, UART_PAYLOAD_LEN, uart_dma_lli_write_cb);
    return true;
}
static void uart_dma_lli_master_entry(void)
{
    for (uint16_t i = 0; i < UART_PAYLOAD_LEN; i++) {
        g_app_uart_tx_payload_buff[i] = rand() % 0xff;
    }
    uapi_pin_set_pull(g_pin_config_map[bus].rx_pin, PIN_PULL_UP);
    uart_port_lli_init_config(CONFIG_UART_BUS_ID);
    uapi_uart_dma_recv_register(CONFIG_UABUS_ID, g_app_uart_rx_payload_buff, UART_PAYLOAD_LEN, uart_dma_lli_read_cb);
    uapi_uart_dma_send(CONFIG_UART_BUS_ID, g_app_uart_tx_payload_buff, UART_PAYLOAD_LEN, uart_dma_lli_write_cb);
    return;
}
/* Run the uart_dma_lli_master_entry. */
app_run(uart_dma_lli_master_entry);

#include "app_init.h"
#include "uart.h"
#include "osal_debug.h"
#define UART_TASK_PRIO                     24
#define UART_TASK_DURATION_MS              500
#define UART_TASK_STACK_SIZE               0x1000
#define UART_PAYLOAD_LEN                   255
extern uart_pin_config_t g_pin_config_map[UART_BUS_MAX_NUM];
static uint8_t g_app_uart_tx_payload_buff[UART_PAYLOAD_LEN] = { 0 };
static uint8_t g_app_uart_rx_payload_buff[UART_PAYLOAD_LEN] = { 0 };
static uint8_t g_app_uart_rx_payload_buff1[UART_PAYLOAD_LEN] = { 0 };
static bool g_double_rx_buffer = true;
volatile uint32_t g_recv_count = 0;
static void uart_dma_lli_write_cb(uint8_t channel, errcode_t result)
{
    unused(result & channel);
}
static bool uart_dma_lli_read_cb(uint8_t channel, uint16_t length, errcode_t result)
{
    unused(channel);
    unused(length);
    unused(result);
    if (g_recv_count++ % 0x200 == 0) {
        osal_printk("g_recv_count:%d\r\n", g_recv_count);
    }
    if (g_double_rx_buffer) {
        uapi_uart_dma_send(CONFIG_UART_BUS_ID, g_app_uart_rx_payload_buff, UART_PAYLOAD_LEN, uart_dma_lli_write_cb);
        uapi_uart_update_dma_recv_buff(CONFIG_UART_BUS_ID, g_app_uart_rx_payload_buff1, UART_PAYLOAD_LEN);
        g_double_rx_buffer = false;
    } else {
        uapi_uart_dma_send(CONFIG_UART_BUS_ID, g_app_uart_rx_payload_buff1, UART_PAYLOAD_LEN, uart_dma_lli_write_cb);
        uapi_uart_update_dma_recv_buff(CONFIG_UART_BUS_ID, g_app_uart_rx_payload_buff, UART_PAYLOAD_LEN);
        g_double_rx_buffer = true;
    }
    return true;
}
static void uart_dma_lli_slave_entry(void)
{
    uapi_pin_set_pull(g_pin_config_map[bus].rx_pin, PIN_PULL_UP);
    uart_port_lli_init_config(CONFIG_UART_BUS_ID);
    uapi_uart_dma_recv_register(CONFIG_UART_BUS_ID, g_app_uart_rx_payload_buff, UART_PAYLOAD_LEN,
                                uart_dma_lli_read_cb);
    return;
}
/* Run the uart_dma_lli_slave_entry. */
app_run(uart_dma_lli_slave_entry);

注意事项

  • SDK中,UART0(UART_L0)默认作为程序烧写、Testsuite、AT以及数据打印共享串口。

  • SDK中,UART1(UART_H0)默认作为DebugKits工具维测数据通道。

  • 当使用DMA传输时,UART的RX管脚需要在初始化时配置为输入上拉。

  • UART的管脚配置在drivers/chips/bs2x/porting/uart/uart_porting.c的全局变量g_pin_config_map中生效。

SPI

概述

SPI(Serial Peripheral Interface)是一种高速、全双工、同步的通信总线。它可以使MCU与各种外围设备以串行方式进行通信以交换信息。SPI总线可直接与各个厂家生产的多种标准外围器件相连,包括FLASH、RAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。标准SPI总线一般使用4条线:串行时钟线(SCLK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOSI和低电平有效的从机选择线CS 。

本产品提供SPI0~SPI2共3组可配置的全双工标准SPI外设,SPI规格如下:

  • 3组都支持Master/Slave模式。

  • 每个SPI具有收发分开的位宽为32bit×8的FIFO。

  • 支持最大传输位宽为32bit。

  • 3组都支持单线(标准)、三线模式。

功能描述

说明: 如果SPI驱动想配置DMA模式读写数据,需要确保DMA驱动已经初始化。DMA初始化请参考“DMA”进行配置。

SPI主模式支持轮询模式读写、中断模式读写以及DMA模式读写;从模式支持中断模式读写和DMA模式读写。

BS2X SPI支持单线SPI和三线SPI:

  • 单线SPI,即为标准SPI,它共有四个引脚:CLK、CS、MISO、MOSI,标准SPI是全双工的,MISO、MOSI分别负责不同方向的传输数据,收发数据可同时进行。库上默认配置即为标准SPI,除管脚配置外无需进行修改。

  • 三线SPI,总共有三个引脚:CLK、CS、MOSI,此时的SPI为半双工的,数据收发只有一根线,主机只发送或只接收从机的数据,收发不能同时进行。

SPI模块提供的接口及功能如下:

  • uapi_spi_init(spi_bus_t bus, spi_attr_t *attr, spi_extra_attr_t *extra_attr):初始化SPI(包括:主从设备、极性、相性、帧协议、传输频率、传输位宽等设定)(bus表示当前使用的SPI bus,attr表示SPI的基础配置参数,extra_attr表示SPI的高级配置参数)。

  • uapi_spi_deinit(spi_bus_t bus):去初始化SPI(关闭相应的SPI单元,释放资源)(bus表示当前使用的SPI bus)。

  • uapi_spi_set_tmod(spi_bus_t bus, hal_spi_trans_mode_t tmod, uint8_t data_frame_num):设置SPI的传输模式(bus表示当前使用的SPI bus,tmod表示SPI传输模式,data_frame_num表示SPI RX数据帧的数量)。

  • uapi_spi_set_attr(spi_bus_t bus, spi_attr_t *attr):设置SPI的基础配置参数(bus表示当前使用的SPI bus,attr表示SPI的基础配置参数)。

  • uapi_spi_get_attr(spi_bus_t bus, spi_attr_t *attr): 获取SPI的基础配置参数(主从模式、时钟极性、时钟相位、时钟分频系数、SPI工作频率、串行传输协议、SPI帧格式、SPI帧长度、SPI传输模式等)(bus表示当前使用的SPI bus,attr表示SPI的基础配置参数)。

  • uapi_spi_set_extra_attr(spi_bus_t bus, spi_extra_attr_t *extra_attr):设置SPI的高级配置参数(bus表示当前使用的SPI bus,attr表示SPI的高级配置参数)。

  • uapi_spi_get_extra_attr(spi_bus_t bus, spi_extra_attr_t *extra_attr):获取SPI的高级配置参数(SPI是否使用DMA发送数据、SPI是否使用DMA接收数据、QSPI参数等)(bus表示当前使用的SPI bus,attr表示SPI的高级配置参数)。

  • uapi_spi_set_dma_mode(spi_bus_t bus, bool en, const spi_dma_config_t *dma_cfg):使能/去使能DMA模式下SPI传输(bus表示当前使用的SPI bus,en表示是否使能DMA传输,dma_cfg表示SPI DMA的相关配置)。(打开CONFIG_SPI_SUPPORT_DMA且不打开CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH宏才能使用)

  • uapi_spi_set_irq_mode(spi_bus_t bus, bool irq_en, spi_rx_callback_t rx_callback, spi_tx_callback_t tx_callback):使能/去使能中断模式在主机模式下传输SPI数据(bus表示当前使用的SPI bus,irq_en表示是否使能中断传输,rx_callback表示接收数据完成时的回调函数,tx_callback表示数据发送完成时的回调函数)。(打开CONFIG_SPI_SUPPORT_INTERRUPT且不打开CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH宏才能使用)

  • uapi_spi_set_loop_back_mode(spi_bus_t bus, bool loopback_en):设置环回测试模式(bus表示当前使用的SPI bus,loopback_en表示是否使能环回模式传输)。(打开CONFIG_SPI_SUPPORT_LOOPBACK宏才能使用,本产品暂未实现)

  • uapi_spi_set_crc_mode(spi_bus_t bus, const spi_crc_config_t *crc_config, spi_crc_err_callback_t cb):设置SPI发送和接收CRC模式(bus表示当前使用的SPI bus,crc_config表示配置SPI的crc参数,cb表示crc检验错误回调函数)。(打开CONFIG_SPI_SUPPORT_CRC宏才能使用,本产品暂未实现)

  • uapi_spi_select_slave(spi_bus_t bus, spi_slave_t cs):Master模式下选择需要对通的Slave设备(bus表示当前使用的SPI bus,cs表示被选中的Slave设备)。(打开CONFIG_SPI_SUPPORT_MASTER宏才能使用)

  • uapi_spi_master_write(spi_bus_t bus, const spi_xfer_data_t *data, uint32_t timeout):SPI主机向从机发送数据(bus表示当前使用的SPI bus,data表示SPI传输的数据,timeout表示当前传输的超时时间)。(打开CONFIG_SPI_SUPPORT_MASTER宏才能使用)

  • uapi_spi_master_read(spi_bus_t bus, const spi_xfer_data_t *data, uint32_t timeout):SPI主机从从机读取数据(bus表示当前使用的SPI bus,data表示SPI传输的数据,timeout表示当前传输的超时时间)。(打开CONFIG_SPI_SUPPORT_MASTER宏才能使用)

  • uapi_spi_master_writeread(spi_bus_t bus, const spi_xfer_data_t *data, uint32_t timeout):SPI主机全双工收发数据(bus表示当前使用的SPI bus,data表示SPI传输的数据,timeout表示当前传输的超时时间)。(打开CONFIG_SPI_SUPPORT_MASTER宏才能使用)

  • uapi_spi_slave_write(spi_bus_t bus, const spi_xfer_data_t *data, uint32_t timeout):SPI从机向主机发送数据(bus表示当前使用的SPI bus,data表示SPI传输的数据,timeout表示当前传输的超时时间)。(打开CONFIG_SPI_SUPPORT_SLAVE宏才能使用)

  • uapi_spi_slave_read(spi_bus_t bus, const spi_xfer_data_t *data, uint32_t timeout):SPI从机从主机读取数据(bus表示当前使用的SPI bus,data表示SPI传输的数据,timeout表示当前传输的超时时间)。(打开CONFIG_SPI_SUPPORT_SLAVE宏才能使用)

  • uapi_spi_slave_writeread(spi_bus_t bus, const spi_xfer_data_t *data, uint32_t timeout):SPI从机全双工收发数据(bus表示当前使用的SPI bus,data表示SPI传输的数据,timeout表示当前传输的超时时间)。(打开CONFIG_SPI_SUPPORT_SLAVE宏才能使用)

  • uapi_spi_suspend(uintptr_t arg):挂起所有SPI通道,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_SPI_SUPPORT_LPM宏才能使用)

  • uapi_spi_resume(uintptr_t arg):恢复所有SPI通道,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_SPI_SUPPORT_LPM宏才能使用)

  • spi_porting_set_rx_mode(spi_bus_t bus, uint16_t num):将SPI配置为EEPROM模式接收数据,num表示多少个数据进行一次传输。

  • spi_porting_set_tx_mode(spi_bus_t bus):将SPI配置为TX模式发送数据。

  • spi_porting_set_txrx_mode(spi_bus_t bus):将SPI配置为TXRX模式收发数据。

KCONFIG配置

单线(标准)SPI Kconfig宏配置如下:

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 SPI相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_SPI_SUPPORT_MASTER

SPI support MASTER.

SPI支持主机选择,视情况选择

默认打开,建议打开

CONFIG_SPI_SUPPORT_SLAVE

SPI support SLAVE.

SPI支持从机选择,视情况选择

默认打开,建议打开

CONFIG_SPI_SUPPORT_DMA

SPI support DMA transfer.

SPI支持DMA模式,视情况选择,若选择采用SPI DMA模式,则需同步选择DMA驱动的KCONFIG配置,请参考“KCONFIG配置”。

CONFIG_DRIVER_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH

SPI support poll and dma mode switch.

SPI支持根据设定的阈值大小自动切换轮询和DMA模式,视情况选择

CONFIG_SPI_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_SPI_AUTO_SWITCH_DMA_THRESHOLD

SPI indicates the threshold of the automatic switchover mode.

SPI支持自动切换轮询和DMA模式时设置的阈值大小,默认值是32个数

CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH

默认值是32,视情况选择配置

CONFIG_SPI_SUPPORT_INTERRUPT

SPI support interrupt transfer.

SPI支持中断模式,视情况选择

默认关闭,视情况选择

CONFIG_SPI_SUPPORT_CONCURRENCY

SPI support concurrency and the mode can be switched only after the transmission is complete.

如果打开该宏,则能够确保当前传输的完整性,只有在当前传输完成后才会进行下一次传输,没加锁的情况下要打开该宏

默认关闭,视情况选择,建议打开

CONFIG_SPI_SUPPORT_LOOPBACK

SPI support LOOPBACK.

SPI支持回环模式,本产品暂未实现

默认关闭,本产品实际暂未实现,建议关闭

CONFIG_SPI_SUPPORT_CRC

SPI support CRC.

SPI支持CRC模式,本产品暂未实现

默认关闭,本产品实际暂未实现,建议关闭

CONFIG_SPI_MAX_TIMEOUT

Max time of SPI waiting.

SPI发送限制的最大超时时间

默认值是0xF,视情况选择配置,建议不做修改

CONFIG_SPI_TXFTLR

Transmit FIFO threshold level.

SPI TX FIFO阈值

默认值是8,建议不做修改

CONFIG_SPI_RXFTLR

Receive FIFO threshold level.

SPI RX FIFO阈值

默认值是8,建议不做修改

CONFIG_SPI_SUPPORT_LPC

SPI support low power control, control power and clock.

SPI 工作时钟

默认打开,必选

CONFIG_SPI_SUPPORT_LPM

SPI support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认关闭,视情况选择

CONFIG_SPI_NOT_SUPPORT_TEXAS_FORMAT

SPI support Texas Instruments SSP Frame Format or not.

SPI支持使用德州仪器SSP帧格式

默认关闭,视情况选择

CONFIG_SPI_SUPPORT_SINGLE_SPI

SPI support single mode.

SPI支持单线模式(即SCLK线,CS线以及DATA收发线)

默认打开,视情况选择,建议关闭

CONFIG_SPI_SUPPORT_QUAD_SPI

SPI support quad mode.

SPI支持四线模式

BS2X不支持

CONFIG_SPI_SUPPORT_QUERY_REGS

SPI support query regs values.

SPI支持回读所有相关寄存器的值,供维测使用

默认关闭,视情况选择,建议关闭

CONFIG_SPI_SUPPORT_TXRX_TRANS_MODE

SPI support transmit and receive mode.

SPI支持全双工模式,

默认关闭,视情况选择,建议关闭

CONFIG_SPI_SUPPORT_DELAY_FOR_WRITEREAD

SPI support delay between write and read.

该宏仅用于TX和RX之间需要更长时间准备的场景,目前仅用于特殊的sensor使用

默认关闭,视情况选择,建议关闭

CONFIG_SPI_AUTO_TMOD

SPI support auto tmod switch.

SPI支持自动TMOD切换

默认关闭,视情况选择,建议关闭

CONFIG_SPI_USING_V100

Using SPI V100.

该宏代表使用SPI V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_SPI_USING_V151

Using SPI V151.

该宏代表使用SPI V151的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_SPI_USING_V151_100

Using SPI V151_100.

该宏代表使用SPI V151_100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_SPI_BUS_MAX_NUM

SPI Max Bus num, the BS2x chip supports a maximum of 3 buses.

该宏表示SPI BUS的最大数量,默认值是1,若使用除SPI BUS0以外的BUS,则必须对应修改该宏,否则会出现异常情况

CONFIG_DRIVER_SUPPORT_SPI

默认值是1,根据实际SPI BUS使用情况修改配置

开发指引

SPI用于对接支持SPI协议的设备,SPI单元可以作为主设备或从设备。以SPI单元作为主设备为例,写数据操作如下:

  1. 通过IO复用,复用SPI功能用到的管脚为SPI功能。

    管脚复用各功能请参考platform_core.h中各个管脚功能的定义。

    #define SPI_PIN_MISO_PINMUX     HAL_PIO_SPI2_RXD
    #define SPI_PIN_MOSI_PINMUX     HAL_PIO_SPI2_TXD
    #define SPI_PIN_CLK_PINMUX      HAL_PIO_SPI2_CLK
    #define SPI_PIN_CS_PINMUX       HAL_PIO_SPI2_CS0
    #define SPI_PIN_MISO    S_MGPIO16
    #define SPI_PIN_MOSI    S_MGPIO17
    #define SPI_PIN_CLK     S_MGPIO18
    #define SPI_PIN_CS      S_MGPIO19
    void usr_spi_io_init(void)
    {
        /* 设置spi pinmux */
        uapi_pin_set_mode(SPI_PIN_MISO, SPI_PIN_MISO_PINMUX);       /* 设置 spi miso pinmux */
        uapi_pin_set_mode(SPI_PIN_MOSI, SPI_PIN_MOSI_PINMUX);       /* 设置 spi mosi pinmux */
        uapi_pin_set_mode(SPI_PIN_CLK, SPI_PIN_CLK_PINMUX);         /* 设置 spi clk pinmux */
        uapi_pin_set_mode(SPI_PIN_CS, SPI_PIN_CS_PINMUX);           /* 设置 spi cs pinmux */
    }
    
  2. 调用uapi_spi_init,初始化SPI资源,选择SPI功能单元以及配置SPI参数。

    #define TEST_SPI                SPI_BUS_2
    #define BUS_CLOCK               32000000    /* 32M */
    #define SPI_FREQUENCY           2
    errcode_t usr_spi_init(void)
    {
        spi_attr_t config = { 0 };
        spi_extra_attr_t ext_config = { 0 };
        ext_config.sspi_param.wait_cycles = 0x10;
        usr_spi_io_init();
        config.freq_mhz = SPI_FREQUENCY;                            /* spi 分频后工作频率 */
        config.is_slave = false;                                    /* 主机模式 */
        config.frame_size = HAL_SPI_FRAME_SIZE_8;                   /* spi 帧大小,使用8位 */
        config.salve_num = 1;                                       /* 使用片选 0 */
        config.spi_frame_format = HAL_SPI_FRAME_FORMAT_STANDARD;    /* spi传输模式:标准spi */
        config.bus_clk = BUS_CLOCK;                                 /* spi时钟源频率 */
        config.frame_format = SPI_CFG_FRAME_FORMAT_MOTOROLA_SPI;    /* spi协议格式:摩托罗拉SPI协议格式 */
        config.tmod = HAL_SPI_TRANS_MODE_TXRX;                      /* spi传输模式:收发模式 */
        config.clk_phase = SPI_CFG_CLK_CPHA_0;                      /* spi相位:SPI_CFG_CLK_CPHA_0 */
        config.clk_polarity = SPI_CFG_CLK_CPOL_0;                   /* spi极性:SPI_CFG_CLK_CPOL_0 */
        /* 初始化spi */
        errcode_t err = uapi_spi_init(TEST_SPI, &config, &ext_config);
        return err;
    }
    
  3. 调用uapi_spi_master_writeread,进行SPI主设备写读操作。

    以主设备写读数据为例:

    errcode_t usr_spi_writeread(uint8_t *wdata, uint8_t wlen, uint8_t *rdata, uint8_t rlen)
    {
        spi_xfer_data_t spi_recv_xfer = { 0 };
        spi_recv_xfer.tx_buff = wdata;                               /* 设置 tx buff */
        spi_recv_xfer.tx_bytes = wlen;                               /* 设置 tx buff 长度 */
        spi_recv_xfer.rx_buff = rdata;                               /* 设置 rx buff */
        spi_recv_xfer.rx_bytes = rlen;                               /* 设置 rx buff 长度 */
        spi_porting_set_rx_mode(TEST_SPI, rlen);                     /* 设置 写读接口的 rx 接收模式*/
        return uapi_spi_master_writeread(TEST_SPI, &spi_recv_xfer, 100); /* 读取数据 */
    }
    

双板SPI三种模式(轮询模式、中断模式、DMA模式)下主从收发完整代码流程可参考以下代码:

#include "pinctrl.h"
#include "spi.h"
#include "soc_osal.h"
#include "app_init.h"
#include "dma.h"

#define SPI_SLAVE_NUM                   1
#define SPI_FREQUENCY                   2
#define SPI_CLK_POLARITY                0
#define SPI_CLK_PHASE                   0
#define SPI_FRAME_FORMAT                0
#define SPI_FRAME_FORMAT_STANDARD       0
#define SPI_FRAME_SIZE                  0x1f
#define SPI_TMOD                        0
#define SPI_WAIT_CYCLES                 0x10
#if defined(CONFIG_SPI_SUPPORT_DMA) && !(defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH))
#define SPI_DMA_WIDTH                   2
#endif
#if defined(CONFIG_SPI_MASTER_SUPPORT_QSPI)
#define QSPI_WRITE_CMD                  0x38
#define QSPI_WRITE_ADDR                 0x123
#endif
#define SPI_TASK_DURATION_MS            500
#define SPI_TASK_PRIO                   24
#define SPI_TASK_STACK_SIZE             0x1000

static void app_spi_init_pin(void)
{
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
    uapi_pin_set_ie(CONFIG_SPI_MASTER_DI_PIN, PIN_IE_1);
#endif /* CONFIG_PINCTRL_SUPPORT_IE */
    uapi_pin_set_mode(CONFIG_SPI_MASTER_CLK_PIN, CONFIG_SPI_MASTER_CLK_PIN_MODE);
    uapi_pin_set_mode(CONFIG_SPI_MASTER_CS_PIN, CONFIG_SPI_MASTER_CS_PIN_MODE);
    uapi_pin_set_mode(CONFIG_SPI_MASTER_DI_PIN, CONFIG_SPI_MASTER_DI_PIN_MODE);
    uapi_pin_set_mode(CONFIG_SPI_MASTER_DO_PIN, CONFIG_SPI_MASTER_DO_PIN_MODE);
}

#if defined(CONFIG_SPI_SUPPORT_INTERRUPT) && (CONFIG_SPI_SUPPORT_INTERRUPT == 1)
static void app_spi_master_write_int_handler(const void *buffer, uint32_t length)
{
    unused(buffer);
    unused(length);
    osal_printk("spi master write interrupt start!\r\n");
}

static void app_spi_master_rx_callback(const void *buffer, uint32_t length, bool error)
{
    if (buffer == NULL || length == 0) {
        osal_printk("spi master transfer illegal data!\r\n");
        return;
    }
    if (error) {
        osal_printk("app_spi_master_read_int error!\r\n");
        return;
    }

    uint8_t *buff = (uint8_t *)buffer;
    for (uint32_t i = 0; i < length; i++) {
        osal_printk("buff[%d] = %x\r\n", i, buff[i]);
    }
    osal_printk("app_spi_master_read_int success!\r\n");
}
#endif  /* CONFIG_SPI_SUPPORT_INTERRUPT */

static void app_spi_master_init_config(void)
{
    spi_attr_t config = { 0 };
    spi_extra_attr_t ext_config = { 0 };

    config.is_slave = false;
    config.slave_num = SPI_SLAVE_NUM;
    config.bus_clk = SPI_CLK_FREQ;
    config.freq_mhz = SPI_FREQUENCY;
    config.clk_polarity = SPI_CLK_POLARITY;
    config.clk_phase = SPI_CLK_PHASE;
    config.frame_format = SPI_FRAME_FORMAT;
    config.spi_frame_format = HAL_SPI_FRAME_FORMAT_STANDARD;
    config.frame_size = SPI_FRAME_SIZE;
    config.tmod = SPI_TMOD;
    config.sste = 0;

    ext_config.qspi_param.wait_cycles = SPI_WAIT_CYCLES;
    uapi_spi_init(CONFIG_SPI_MASTER_BUS_ID, &config, &ext_config);
#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1)
    uapi_dma_init();
    uapi_dma_open();
#ifndef CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH
    spi_dma_config_t dma_cfg = {
        .src_width = SPI_DMA_WIDTH,
        .dest_width = SPI_DMA_WIDTH,
        .burst_length = 0,
        .priority = 0
    };
    if (uapi_spi_set_dma_mode(CONFIG_SPI_MASTER_BUS_ID, true, &dma_cfg) != ERRCODE_SUCC) {
        osal_printk("spi%d master set dma mode fail!\r\n");
    }
#endif
#endif  /* CONFIG_SPI_SUPPORT_DMA */

#if defined(CONFIG_SPI_SUPPORT_INTERRUPT) && (CONFIG_SPI_SUPPORT_INTERRUPT == 1)
    if (uapi_spi_set_irq_mode(CONFIG_SPI_MASTER_BUS_ID, true, app_spi_master_rx_callback,
        app_spi_master_write_int_handler) == ERRCODE_SUCC) {
        osal_printk("spi%d master set irq mode succ!\r\n", CONFIG_SPI_MASTER_BUS_ID);
    }
#endif  /* CONFIG_SPI_SUPPORT_INTERRUPT */
}

static void *spi_master_task(const char *arg)
{
    unused(arg);
    /* SPI pinmux. */
    app_spi_init_pin();

    /* SPI master init config. */
    app_spi_master_init_config();

    /* SPI data config. */
    uint8_t tx_data[CONFIG_SPI_TRANSFER_LEN] = { 0 };
    for (uint32_t loop = 0; loop < CONFIG_SPI_TRANSFER_LEN; loop++) {
        tx_data[loop] = (loop & 0xFF);
    }
    uint8_t rx_data[CONFIG_SPI_TRANSFER_LEN] = { 0 };
    spi_xfer_data_t data = {
        .tx_buff = tx_data,
        .tx_bytes = CONFIG_SPI_TRANSFER_LEN,
        .rx_buff = rx_data,
        .rx_bytes = CONFIG_SPI_TRANSFER_LEN,
#if defined(CONFIG_SPI_MASTER_SUPPORT_QSPI)
        .cmd = QSPI_WRITE_CMD,
        .addr = QSPI_WRITE_ADDR,
#endif
    };

    while (1) {
        osal_msleep(SPI_TASK_DURATION_MS);
#ifndef CONFIG_SPI_MASTER_SUPPORT_WRITEREAD
        osal_printk("spi%d master send start!\r\n", CONFIG_SPI_MASTER_BUS_ID);
        if (uapi_spi_master_write(CONFIG_SPI_MASTER_BUS_ID, &data, 0xFFFFFFFF) == ERRCODE_SUCC) {
            osal_printk("spi%d master send succ!\r\n", CONFIG_SPI_MASTER_BUS_ID);
        } else {
            continue;
        }
        osal_printk("spi%d master receive start!\r\n", CONFIG_SPI_MASTER_BUS_ID);
        if (uapi_spi_master_read(CONFIG_SPI_MASTER_BUS_ID, &data, 0xFFFFFFFF) == ERRCODE_SUCC) {
#ifndef CONFIG_SPI_SUPPORT_INTERRUPT
            for (uint32_t i = 0; i < data.rx_bytes; i++) {
                osal_printk("spi%d master receive data is %x\r\n", CONFIG_SPI_MASTER_BUS_ID, data.rx_buff[i]);
            }
#endif
            osal_printk("spi%d master receive succ!\r\n", CONFIG_SPI_MASTER_BUS_ID);
        }
#else
        osal_printk("spi%d master writeread start!\r\n", CONFIG_SPI_MASTER_BUS_ID);
        if (uapi_spi_master_writeread(CONFIG_SPI_MASTER_BUS_ID, &data, 0xFFFFFFFF) == ERRCODE_SUCC) {
            for (uint32_t i = 0; i < data.rx_bytes; i++) {
                osal_printk("spi%d master writeread data is %x\r\n", CONFIG_SPI_MASTER_BUS_ID, data.rx_buff[i]);
            }
            osal_printk("spi%d master writeread succ!\r\n", CONFIG_SPI_MASTER_BUS_ID);
        }
#endif
    }

    return NULL;
}

static void spi_master_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)spi_master_task, 0, "SpiMasterTask", SPI_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, SPI_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the spi_master_entry. */
app_run(spi_master_entry);

------------------------------------------------------------------------------

#include "pinctrl.h"
#include "spi.h"
#include "soc_osal.h"
#include "app_init.h"
#include "dma.h"

#define SPI_SLAVE_NUM                   1
#define SPI_FREQUENCY                   2
#define SPI_CLK_POLARITY                0
#define SPI_CLK_PHASE                   0
#define SPI_FRAME_FORMAT                0
#define SPI_FRAME_FORMAT_STANDARD       0
#define SPI_FRAME_SIZE                  0x1f
#define SPI_TMOD                        0
#define SPI_WAIT_CYCLES                 0x10
#if defined(CONFIG_SPI_SUPPORT_DMA) && !(defined(CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH))
#define SPI_DMA_WIDTH                   2
#endif

#define SPI_TASK_DURATION_MS            500
#define SPI_TASK_PRIO                   24
#define SPI_TASK_STACK_SIZE             0x1000

static void app_spi_init_pin(void)
{
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
    uapi_pin_set_ie(CONFIG_SPI_SLAVE_CLK_PIN, PIN_IE_1);
    uapi_pin_set_ie(CONFIG_SPI_SLAVE_CS_PIN, PIN_IE_1);
    uapi_pin_set_ie(CONFIG_SPI_SLAVE_DI_PIN, PIN_IE_1);
#endif /* CONFIG_PINCTRL_SUPPORT_IE */
    uapi_pin_set_mode(CONFIG_SPI_SLAVE_CLK_PIN, CONFIG_SPI_SLAVE_CLK_PIN_MODE);
    uapi_pin_set_mode(CONFIG_SPI_SLAVE_CS_PIN, CONFIG_SPI_SLAVE_CS_PIN_MODE);
    uapi_pin_set_mode(CONFIG_SPI_SLAVE_DI_PIN, CONFIG_SPI_SLAVE_DI_PIN_MODE);
    uapi_pin_set_mode(CONFIG_SPI_SLAVE_DO_PIN, CONFIG_SPI_SLAVE_DO_PIN_MODE);
}

#if defined(CONFIG_SPI_SUPPORT_INTERRUPT) && (CONFIG_SPI_SUPPORT_INTERRUPT == 1)
static void app_spi_slave_write_int_handler(const void *buffer, uint32_t length)
{
    unused(buffer);
    unused(length);
    osal_printk("spi slave write interrupt start!\r\n");
}

static void app_spi_slave_rx_callback(const void *buffer, uint32_t length, bool error)
{
    if (buffer == NULL || length == 0) {
        osal_printk("spi slave transfer illegal data!\r\n");
        return;
    }
    if (error) {
        osal_printk("app_spi_slave_read_int error!\r\n");
        return;
    }

    uint8_t *buff = (uint8_t *)buffer;
    for (uint32_t i = 0; i < length; i++) {
        osal_printk("buff[%d] = %x\r\n", i, buff[i]);
    }
    osal_printk("app_spi_slave_read_int success!\r\n");
}
#endif  /* CONFIG_SPI_SUPPORT_INTERRUPT */

static void app_spi_slave_init_config(void)
{
    spi_attr_t config = { 0 };
    spi_extra_attr_t ext_config = { 0 };

    config.is_slave = true;
    config.slave_num = SPI_SLAVE_NUM;
    config.bus_clk = SPI_CLK_FREQ;
    config.freq_mhz = SPI_FREQUENCY;
    config.clk_polarity = SPI_CLK_POLARITY;
    config.clk_phase = SPI_CLK_PHASE;
    config.frame_format = SPI_FRAME_FORMAT;
    config.spi_frame_format = HAL_SPI_FRAME_FORMAT_STANDARD;
    config.frame_size = SPI_FRAME_SIZE;
    config.tmod = SPI_TMOD;
    config.sste = 0;

    ext_config.qspi_param.wait_cycles = SPI_WAIT_CYCLES;

    uapi_spi_init(CONFIG_SPI_SLAVE_BUS_ID, &config, &ext_config);
#if defined(CONFIG_SPI_SUPPORT_DMA) && (CONFIG_SPI_SUPPORT_DMA == 1)
    uapi_dma_init();
    uapi_dma_open();
#ifndef CONFIG_SPI_SUPPORT_POLL_AND_DMA_AUTO_SWITCH
    spi_dma_config_t dma_cfg = {
        .src_width = SPI_DMA_WIDTH,
        .dest_width = SPI_DMA_WIDTH,
        .burst_length = 0,
        .priority = 0
    };
    if (uapi_spi_set_dma_mode(CONFIG_SPI_SLAVE_BUS_ID, true, &dma_cfg) != ERRCODE_SUCC) {
        osal_printk("spi%d slave set dma mode fail!\r\n");
    }
#endif
#endif  /* CONFIG_SPI_SUPPORT_DMA */

#if defined(CONFIG_SPI_SUPPORT_INTERRUPT) && (CONFIG_SPI_SUPPORT_INTERRUPT == 1)
    if (uapi_spi_set_irq_mode(CONFIG_SPI_SLAVE_BUS_ID, true, app_spi_slave_rx_callback,
        app_spi_slave_write_int_handler) == ERRCODE_SUCC) {
        osal_printk("spi%d slave set irq mode succ!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
    }
#endif  /* CONFIG_SPI_SUPPORT_INTERRUPT */
}

static void *spi_slave_task(const char *arg)
{
    unused(arg);
    /* SPI pinmux. */
    app_spi_init_pin();

    /* SPI slave init config. */
    app_spi_slave_init_config();

    /* SPI data config. */
    uint8_t tx_data[CONFIG_SPI_TRANSFER_LEN] = { 0 };
    for (uint32_t loop = 0; loop < CONFIG_SPI_TRANSFER_LEN; loop++) {
        tx_data[loop] = (loop & 0xFF);
    }
    uint8_t rx_data[CONFIG_SPI_TRANSFER_LEN] = { 0 };
    spi_xfer_data_t data = {
        .tx_buff = tx_data,
        .tx_bytes = CONFIG_SPI_TRANSFER_LEN,
        .rx_buff = rx_data,
        .rx_bytes = CONFIG_SPI_TRANSFER_LEN,
    };

    while (1) {
        osal_msleep(SPI_TASK_DURATION_MS);
#ifndef CONFIG_SPI_SLAVE_SUPPORT_WRITEREAD
        osal_printk("spi%d slave receive start!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
        if (uapi_spi_slave_read(CONFIG_SPI_SLAVE_BUS_ID, &data, 0xFFFFFFFF) == ERRCODE_SUCC) {
#ifndef CONFIG_SPI_SUPPORT_INTERRUPT
            for (uint32_t i = 0; i < data.rx_bytes; i++) {
                osal_printk("spi%d slave receive data is %x\r\n", CONFIG_SPI_SLAVE_BUS_ID, data.rx_buff[i]);
            }
#endif
            osal_printk("spi%d slave receive succ!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
        }
        osal_printk("spi%d slave send start!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
        if (uapi_spi_slave_write(CONFIG_SPI_SLAVE_BUS_ID, &data, 0xFFFFFFFF) == ERRCODE_SUCC) {
            osal_printk("spi%d slave send succ!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
        } else {
            continue;
        }
#else
        osal_printk("spi%d slave writeread start!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
        if (uapi_spi_slave_writeread(CONFIG_SPI_SLAVE_BUS_ID, &data, 0xFFFFFFFF) == ERRCODE_SUCC) {
            for (uint32_t i = 0; i < data.rx_bytes; i++) {
                osal_printk("spi%d slave writeread data is %x\r\n", CONFIG_SPI_SLAVE_BUS_ID, data.rx_buff[i]);
            }
            osal_printk("spi%d slave writeread succ!\r\n", CONFIG_SPI_SLAVE_BUS_ID);
        }
#endif
    }

    return NULL;
}

static void spi_slave_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)spi_slave_task, 0, "SpiSlaveTask", SPI_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, SPI_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the spi_slave_entry. */
app_run(spi_slave_entry);

三线SPI在使用时要注意2点:

  1. SPI的CS管脚不能配置为片选模式,而是配置为gpio模式,输出高电平:

    void spi_set_pinctrl(void)
    {
    #if defined(CONFIG_PINCTRL_SUPPORT_IE)
        uapi_pin_set_ie(CONFIG_MOUSE_PIN_SPI_MISO, PIN_IE_1);
    #endif
        uapi_pin_set_mode(CONFIG_MOUSE_PIN_SPI_MISO, SPI_PIN_MISO_PINMUX);
        uapi_pin_set_mode(CONFIG_MOUSE_PIN_SPI_MOSI, SPI_PIN_MOSI_PINMUX);
        uapi_pin_set_mode(CONFIG_MOUSE_PIN_SPI_CLK, SPI_PIN_CLK_PINMUX);
        uapi_pin_set_pull(CONFIG_MOUSE_PIN_SPI_CLK, PIN_PULL_UP);
        uapi_pin_set_mode(CONFIG_MOUSE_PIN_SPI_CS, HAL_PIO_FUNC_GPIO);
        uapi_gpio_set_dir(CONFIG_MOUSE_PIN_SPI_CS, GPIO_DIRECTION_OUTPUT);
        uapi_gpio_set_val(CONFIG_MOUSE_PIN_SPI_CS, GPIO_LEVEL_HIGH);
    }
    
  2. 在每次调用SPI读写接口时,需手动改变CS管脚电平,使SPI在读写期间保持低电平。

    以主设备写读数据为例:

    errcode_t usr_spi_writeread(uint8_t *wdata, uint8_t wlen, uint8_t *rdata, uint8_t rlen)
    {
        spi_xfer_data_t spi_recv_xfer = { 0 };
        spi_recv_xfer.tx_buff = wdata;                               /* 设置 tx buff */
        spi_recv_xfer.tx_bytes = wlen;                               /* 设置 tx buff 长度 */
        spi_recv_xfer.rx_buff = rdata;                               /* 设置 rx buff */
        spi_recv_xfer.rx_bytes = rlen;                               /* 设置 rx buff 长度 */
        spi_porting_set_rx_mode(TEST_SPI, rlen);                     /* 设置 写读接口的 rx 接收模式 */
        uapi_gpio_set_val(TEST_SPI, GPIO_LEVEL_LOW);                 /* 设置 cs管脚为低电平 */
        return uapi_spi_master_writeread(TEST_SPI, &spi_recv_xfer, 100); /* 读取数据 */
        uapi_gpio_set_val(TEST_SPI, GPIO_LEVEL_HIGH);                /* 设置 cs管脚为高电平 */
    }
    

注意事项

  • 当不再使用SPI时,必须调用uapi_spi_deinit进行资源释放,否则再进行初始化时将返回错误。

  • 使用microwire帧协议时,由于microwire帧协议限制,主设备只能发送8bit位宽数据。

  • 芯片作为主设备时,如果从设备速率较慢,主设备在每次调用读写接口后进行适当延时,避免从设备因读写数据太慢导致数据出错。

I2C

概述

IIC(Inter-Integrated Circuit)也叫做I2C,译作集成电路总线,是一种串行通信总线,使用主从架构,便于MCU与周边设备组件之间的通讯。

I2C总线包含两条线:SDA(Serial Data Line)和SCL(Serial Clock Line),其中SDA是数据线,SCL是时钟线。I2C总线上的每个设备都有一个唯一的地址,主机可以通过该地址与设备进行通信 。

提供I2C0~I2C1共2组支持Master/Slaver模式的I2C外设,I2C规格如下:

  • 支持标速、快速以及高速三种工作模式,在串行8位双向数据传输场景下,标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。

  • 支持位宽为32bit×8的FIFO。

  • 支持7bit/10bit寻址模式。

功能描述

说明: 如果I2C驱动想配置DMA模式读写数据,需要确保DMA驱动已经初始化。DMA初始化请参考“DMA”进行配置。

I2C模块提供的接口及功能如下:

  • uapi_i2c_master_init(i2c_bus_t bus, uint32_t baudrate, uint8_t hscode):初始化该I2C设备为主机,需要传入的参数有总线号、波特率、高速模式主机码(只有高速模式需要配置,范围0~7)(bus表示当前使用的I2C bus总线,baudrate表示I2C波特率,hscode表示I2C高速模式主机码)。(打开CONFIG_I2C_SUPPORT_MASTER宏才能使用)

  • uapi_i2c_master_write(i2c_bus_t bus, uint16_t dev_addr, i2c_data_t *data):I2C主机将数据发送到目标从机上(bus表示当前使用的I2C bus总线,dev_addr表示目标从机地址,data表示I2C发送的数据)。(打开CONFIG_I2C_SUPPORT_MASTER宏才能使用)

  • uapi_i2c_master_read(i2c_bus_t bus, uint16_t dev_addr, i2c_data_t *data):主机接收来自目标I2C从机的数据(bus表示当前使用的I2C bus总线,dev_addr表示目标从机地址,data表示I2C接收的数据)。(打开CONFIG_I2C_SUPPORT_MASTER宏才能使用)

  • uapi_i2c_master_writeread(i2c_bus_t bus, uint16_t dev_addr, i2c_data_t *data):主机发送数据到目标I2C从机,并接收来自此从机的数据(bus表示当前使用的I2C bus总线,dev_addr表示目标从机地址,data表示I2C数据配置)。(打开CONFIG_I2C_SUPPORT_MASTER宏才能使用)

  • uapi_i2c_slave_init(i2c_bus_t bus, uint32_t baudrate, uint16_t addr):初始化该I2C设备为从机,需要传入的参数有总线号、波特率、从机地址(支持7/10位地址寻址)(bus表示当前使用的I2C bus总线,baudrate表示I2C波特率,addr表示I2C作为从机工作时的从机地址)。(打开CONFIG_I2C_SUPPORT_SLAVE宏才能使用)

  • uapi_i2c_slave_write(i2c_bus_t bus, i2c_data_t *data):从机将数据发送给主机(bus表示当前使用的I2C bus总线,data表示I2C发送的数据)。(打开CONFIG_I2C_SUPPORT_SLAVE宏才能使用)

  • uapi_i2c_slave_read(i2c_bus_t bus, i2c_data_t *data):从机接收来自主机的数据(bus表示当前使用的I2C bus总线,data表示I2C接收的数据)。(打开CONFIG_I2C_SUPPORT_SLAVE宏才能使用)

  • uapi_i2c_deinit(i2c_bus_t bus):去初始化I2C设备,支持主从机(bus表示当前使用的I2C bus总线)。

  • uapi_i2c_set_baudrate(i2c_bus_t bus, uint32_t baudrate):对已初始化的I2C重置波特率,支持主从机(bus表示当前使用的I2C bus总线,baudrate表示I2C波特率)。

  • uapi_i2c_set_dma_mode(i2c_bus_t bus, bool en):使能/去使能DMA模式下I2C传输(bus表示当前使用的I2C bus总线,en表示是否使能DMA传输)。(打开CONFIG_I2C_SUPPORT_DMA宏才能使用)

  • uapi_i2c_set_irq_mode(i2c_bus_t bus, bool irq_en):设置是是否使用中断模式传输数据(bus表示当前使用的I2C bus总线,en表示是否使能中断模式传输)。(打开CONFIG_I2C_SUPPORT_INT宏才能使用)

  • uapi_i2c_register_irq_callback(i2c_bus_t bus, i2c_irq_callback_t callback):注册I2C中断事件回调函数(bus表示当前使用的I2C bus总线,callback表示I2C中断事件回调函数)。(打开CONFIG_I2C_SUPPORT_INT宏才能使用)

  • uapi_i2c_unregister_irq_callback(i2c_bus_t bus):取消注册I2C中断事件回调函数(bus表示当前使用的I2C bus总线)。(打开CONFIG_I2C_SUPPORT_INT宏才能使用)

  • uapi_i2c_suspend(uintptr_t arg):挂起所有I2C通道,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_I2C_SUPPORT_LPM宏才能使用)

  • uapi_i2c_resume(uintptr_t arg):恢复所有I2C通道,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_I2C_SUPPORT_LPM宏才能使用)

  • uapi_i2c_master_writeread_with_flag(i2c_bus_t bus, uint16_t dev_addr, i2c_buffer_wrap_t *write_buffer, i2c_buffer_wrap_t *read_buffer) :主机发送数据到目标I2C从机,并接收来自此从机的数据(bus表示当前使用的I2C bus总线,dev_addr表示目标从机地址,write_buffer表示I2C发送数据的信息指针,read_buffer表示I2C接收数据的信息指针)。(打开CONFIG_I2C_SUPPORT_MASTER宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 I2C相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_I2C_SUPPORT_MASTER

I2C support MASTER.

I2C支持主机选择,视情况选择

默认打开,建议打开

CONFIG_I2C_SUPPORT_SLAVE

I2C support SLAVE.

I2C支持从机选择,视情况选择

默认打开,建议打开

CONFIG_I2C_WAIT_CONDITION_TIMEOUT

The timeout of I2C wait condition when I2C write and read, uint is ms.

I2C等待的最大超时时间

默认值是0x3E8,视情况选择配置,建议不做修改

CONFIG_I2C_SUPPORT_INT

I2C support interrupt transfer.

I2C支持中断模式,视情况选择

默认关闭,视情况选择

CONFIG_I2C_SUPPORT_IN_CHIP_LOOPBACK

I2C support loopback, which can read and write in one chip with two diferrent I2C buses.

I2C支持同一块单板不同的BUS中断回环模式

CONFIG_I2C_SUPPORT_INT

默认关闭,视情况选择

CONFIG_I2C_SUPPORT_DMA

I2C support DMA.

I2C支持DMA模式,视情况选择,若选择采用I2C DMA模式,则需同步选择DMA驱动的KCONFIG配置,请参考KCONFIG配置

CONFIG_DRIVER_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_I2C_SUPPORT_POLL_AND_DMA_AUTO_SWITCH

I2C support poll and dma auto switch.

I2C支持根据设定的阈值大小自动切换轮询和DMA模式,视情况选择

CONFIG_I2C_SUPPORT_DMA

默认关闭,视情况选择

CONFIG_I2C_POLL_AND_DMA_AUTO_SWITCH_THRESHOLD

I2C poll and dma auto switch threshold.

I2C支持自动切换轮询和DMA模式时设置的阈值大小,默认值是16个数

CONFIG_I2C_SUPPORT_POLL_AND_DMA_AUTO_SWITCH

默认值是16,视情况选择配置

CONFIG_I2C_SUPPORT_LPC

I2C support low power control, control power and clock.

I2C工作时钟

默认打开,必选

CONFIG_I2C_SUPPORT_LPM

I2C support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认关闭,视情况选择

CONFIG_I2C_SUPPORT_CONCURRENCY

I2C support concurrency.

如果打开该宏,则能够确保当前传输的完整性,只有在当前传输完成后才会进行下一次传输,没加锁的情况下要打开该宏

默认打开,建议打开

CONFIG_I2C_SUPPORT_QUERY_REGS

I2C support query regs values.

I2C支持回读所有相关寄存器的值,视情况选择

默认关闭,视情况选择,建议关闭

CONFIG_I2C_USING_V100

Using I2C V100.

该宏代表使用I2C V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_I2C_USING_V150

Using I2C V150.

该宏代表使用I2C V150的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_I2C_USING_V151

Using I2C V151.

该宏代表使用I2C V151的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_I2C_ADDR_VALID_CHECK

I2C checks the address validity based on the protocol standard.

I2C根据协议标准校验地址合法性

默认打开,建议打开

CONFIG_I2C_NOT_SUPPORT_HIGH_SPEED

I2C do not support high speed mode.

I2C不支持高速波特率,本产品暂不使用

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_I2C_SUPPORT_FAST_PLUS_SPEED

I2C support fast plus speed mode.

I2C支持快速plus波特率

默认关闭,视情况选择,建议关闭

CONFIG_I2C_BUS_MAX_NUM

I2C Max Bus num, the BS2x chip supports a maximum of 2 buses.

该宏表示I2C BUS的最大数量,默认值是1,若使用除I2C BUS0以外的BUS,则必须对应修改该宏,否则会出现异常情况

CONFIG_DRIVER_SUPPORT_I2C

默认值是1,根据实际I2C BUS使用情况修改配置

开发指引

I2C用于对接支持I2C协议的设备,I2C单元可以作为主设备或从设备。以I2C单元作为主设备为例:

  1. 通过IO复用,将用到的管脚复用为I2C功能。

  2. 调用uapi_i2c_init接口,初始化I2C资源,此处以初始化I2C主机为例:

    #define TEST_I2C                I2C_BUS_0
    #define I2C_BAUDRATE            400000    /* 400kHz */
    #define I2C_PIN_CLK_PINMUX      HAL_PIO_I2C0_CLK
    #define I2C_PIN_DAT_PINMUX      HAL_PIO_I2C0_DATA
    #define I2C_PIN_CLK             S_MGPIO24
    #define I2C_PIN_DAT             S_MGPIO25
    errcode_t sample_i2c_init(void)
    {
        /* 设置 i2c pinmux */
        uapi_pin_set_mode(I2C_PIN_CLK, I2C_PIN_CLK_PINMUX);       /* 设置 i2c clk pinmux */
        uapi_pin_set_mode(I2C_PIN_DAT, I2C_PIN_DAT_PINMUX);       /* 设置 i2c dat pinmux */
        /* 初始化 i2c */
        return uapi_i2c_master_init(TEST_I2C, I2C_BAUDRATE, 0);     /* 初始化 i2c0 */
    }
    
  3. 调用uapi_i2c_master_write接口,实现主机发送数据。

    errcode_t sample_i2c_write(uint8_t *data, uint8_t len, uint16_t addr)
    {
        i2c_data_t i2c_send_data = { 0 };
        i2c_send_data.send_buf = data;                                   /* 设置 tx buff */
        i2c_send_data.send_len = len;                                    /* 设置 tx buff 长度 */
        return uapi_i2c_master_write(TEST_I2C, addr, &i2c_send_data);     /* 发送数据 */
    }
    

双板I2C三种模式(轮询模式、中断模式、DMA模式)下主从收发完整代码流程可参考以下代码:

#include "pinctrl.h"
#include "i2c.h"
#include "soc_osal.h"
#include "app_init.h"
#if defined(CONFIG_I2C_SUPPORT_DMA) && (CONFIG_I2C_SUPPORT_DMA == 1)
#include "dma.h"
#endif

#define I2C_MASTER_ADDR                   0x0
#define I2C_SLAVE_ADDR                    0x8
#define I2C_SET_BAUDRATE                  400000
#define I2C_TASK_DURATION_MS              500
#if defined(CONFIG_I2C_SUPPORT_INT) && (CONFIG_I2C_SUPPORT_INT == 1)
#define I2C_INT_TRANSFER_DELAY_MS         800
#endif

#define I2C_TASK_PRIO                     24
#define I2C_TASK_STACK_SIZE               0x1000

static i2c_data_t data = { 0 };
static uint8_t tx_buff[CONFIG_I2C_TRANSFER_LEN] = { 0 };
static uint8_t rx_buff[CONFIG_I2C_TRANSFER_LEN] = { 0 };

static void app_i2c_init_pin(void)
{
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
    uapi_pin_set_ie(CONFIG_I2C_MASTER_SCL_PIN, PIN_IE_1);
    uapi_pin_set_ie(CONFIG_I2C_MASTER_SDA_PIN, PIN_IE_1);
#endif /* CONFIG_PINCTRL_SUPPORT_IE */
    /* I2C pinmux. */
    uapi_pin_set_mode(CONFIG_I2C_MASTER_SCL_PIN, CONFIG_I2C_MASTER_SCL_PIN_MODE);
    uapi_pin_set_mode(CONFIG_I2C_MASTER_SDA_PIN, CONFIG_I2C_MASTER_SDA_PIN_MODE);
}

static void app_i2c_data_config(void)
{
    /* I2C data config. */
    for (uint32_t loop = 0; loop < CONFIG_I2C_TRANSFER_LEN; loop++) {
        tx_buff[loop] = (loop & 0xFF);
    }
    data.send_buf = tx_buff;
    data.send_len = CONFIG_I2C_TRANSFER_LEN;
    data.receive_buf = rx_buff;
    data.receive_len = CONFIG_I2C_TRANSFER_LEN;
}

static void *i2c_master_task(const char *arg)
{
    unused(arg);

    uint32_t baudrate = I2C_SET_BAUDRATE;
    uint8_t hscode = I2C_MASTER_ADDR;
    uint16_t dev_addr = I2C_SLAVE_ADDR;

#if defined(CONFIG_I2C_SUPPORT_DMA) && (CONFIG_I2C_SUPPORT_DMA == 1)
    uapi_dma_init();
    uapi_dma_open();
#ifndef CONFIG_I2C_SUPPORT_POLL_AND_DMA_AUTO_SWITCH
    uapi_i2c_set_dma_mode(CONFIG_I2C_MASTER_BUS_ID, true);
#endif
#endif  /* CONFIG_I2C_SUPPORT_DMA */

    /* I2C master init config. */
    app_i2c_init_pin();
    uapi_i2c_master_init(CONFIG_I2C_MASTER_BUS_ID, baudrate, hscode);

#if defined(CONFIG_I2C_SUPPORT_INT) && (CONFIG_I2C_SUPPORT_INT == 1)
    uapi_i2c_set_irq_mode(CONFIG_I2C_MASTER_BUS_ID, true);
#endif  /* CONFIG_I2C_SUPPORT_INT */

    app_i2c_data_config();

    while (1) {
        osal_msleep(I2C_TASK_DURATION_MS);
#ifndef CONFIG_I2C_MASTER_SUPPORT_WRITEREAD
        osal_printk("i2c%d master send start!\r\n", CONFIG_I2C_MASTER_BUS_ID);
        if (uapi_i2c_master_write(CONFIG_I2C_MASTER_BUS_ID, dev_addr, &data) == ERRCODE_SUCC) {
            osal_printk("i2c%d master send succ!\r\n", CONFIG_I2C_MASTER_BUS_ID);
        } else {
            continue;
        }
#if defined(CONFIG_I2C_SUPPORT_INT) && (CONFIG_I2C_SUPPORT_INT == 1)
        osal_msleep(I2C_INT_TRANSFER_DELAY_MS);
#endif
        osal_printk("i2c%d master receive start!\r\n", CONFIG_I2C_MASTER_BUS_ID);
        if (uapi_i2c_master_read(CONFIG_I2C_MASTER_BUS_ID, dev_addr, &data) == ERRCODE_SUCC) {
            for (uint32_t i = 0; i < data.receive_len; i++) {
                osal_printk("i2c%d master receive data is %x\r\n", CONFIG_I2C_MASTER_BUS_ID, data.receive_buf[i]);
            }
            osal_printk("i2c%d master receive succ!\r\n", CONFIG_I2C_MASTER_BUS_ID);
        }
#else
        osal_printk("i2c%d master writeread start!\r\n", CONFIG_I2C_MASTER_BUS_ID);
        if (uapi_i2c_master_writeread(CONFIG_I2C_MASTER_BUS_ID, dev_addr, &data) == ERRCODE_SUCC) {
            for (uint32_t i = 0; i < data.receive_len; i++) {
                osal_printk("i2c%d master writeread data is %x\r\n", CONFIG_I2C_MASTER_BUS_ID, data.receive_buf[i]);
            }
            osal_printk("i2c%d master writeread succ!\r\n", CONFIG_I2C_MASTER_BUS_ID);
        }
#endif
    }

    return NULL;
}

static void i2c_master_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2c_master_task, 0, "I2cMasterTask", I2C_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2C_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2c_master_entry. */
app_run(i2c_master_entry);

----------------------------------------------------------------

#include "pinctrl.h"
#include "i2c.h"
#include "soc_osal.h"
#include "app_init.h"
#if defined(CONFIG_I2C_SUPPORT_DMA) && (CONFIG_I2C_SUPPORT_DMA == 1)
#include "dma.h"
#endif

#define I2C_SLAVE_ADDR                    0x8
#define I2C_SET_BAUDRATE                  500000
#define I2C_TASK_DURATION_MS              100
#if defined(CONFIG_I2C_SUPPORT_DMA) && (CONFIG_I2C_SUPPORT_DMA == 1)
#define I2C_DMA_TRANSFER_DELAY_MS         500
#endif

#define I2C_TASK_PRIO                     24
#define I2C_TASK_STACK_SIZE               0x1000

static void app_i2c_init_pin(void)
{
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
    uapi_pin_set_ie(CONFIG_I2C_SLAVE_SCL_PIN, PIN_IE_1);
    uapi_pin_set_ie(CONFIG_I2C_SLAVE_SDA_PIN, PIN_IE_1);
#endif /* CONFIG_PINCTRL_SUPPORT_IE */
    /* I2C pinmux. */
    uapi_pin_set_mode(CONFIG_I2C_SLAVE_SCL_PIN, CONFIG_I2C_SLAVE_SCL_PIN_MODE);
    uapi_pin_set_mode(CONFIG_I2C_SLAVE_SDA_PIN, CONFIG_I2C_SLAVE_SDA_PIN_MODE);
}

void *i2c_slave_task(const char *arg)
{
    unused(arg);
    i2c_data_t data = { 0 };

    uint32_t baudrate = I2C_SET_BAUDRATE;
    uint16_t dev_addr = I2C_SLAVE_ADDR;

#if defined(CONFIG_I2C_SUPPORT_DMA) && (CONFIG_I2C_SUPPORT_DMA == 1)
    uapi_dma_init();
    uapi_dma_open();
#ifndef CONFIG_I2C_SUPPORT_POLL_AND_DMA_AUTO_SWITCH
    uapi_i2c_set_dma_mode(CONFIG_I2C_SLAVE_BUS_ID, true);
#endif
#endif  /* CONFIG_I2C_SUPPORT_DMA */

    /* I2C slave init config. */
    app_i2c_init_pin();
    uapi_i2c_slave_init(CONFIG_I2C_SLAVE_BUS_ID, baudrate, dev_addr);

#if defined(CONFIG_I2C_SUPPORT_INT) && (CONFIG_I2C_SUPPORT_INT == 1)
    uapi_i2c_set_irq_mode(CONFIG_I2C_SLAVE_BUS_ID, true);
#endif /* CONFIG_I2C_SUPPORT_INT */

    /* I2C data config. */
    uint8_t tx_buff[CONFIG_I2C_TRANSFER_LEN] = { 0 };
    for (uint32_t loop = 0; loop < CONFIG_I2C_TRANSFER_LEN; loop++) {
        tx_buff[loop] = (loop & 0xFF);
    }

    uint8_t rx_buff[CONFIG_I2C_TRANSFER_LEN] = { 0 };
    data.send_buf = tx_buff;
    data.send_len = CONFIG_I2C_TRANSFER_LEN;
    data.receive_buf = rx_buff;
    data.receive_len = CONFIG_I2C_TRANSFER_LEN;

    while (1) {
        osal_msleep(I2C_TASK_DURATION_MS);
        osal_printk("i2c%d slave receive start!\r\n", CONFIG_I2C_SLAVE_BUS_ID);
        if (uapi_i2c_slave_read(CONFIG_I2C_SLAVE_BUS_ID, &data) == ERRCODE_SUCC) {
            for (uint32_t i = 0; i < data.receive_len; i++) {
                osal_printk("i2c slave receive data is %x\r\n", data.receive_buf[i]);
            }
            osal_printk("i2c%d slave receive succ!\r\n", CONFIG_I2C_SLAVE_BUS_ID);
        } else {
            continue;
        }
        osal_printk("i2c%d slave send start!\r\n", CONFIG_I2C_SLAVE_BUS_ID);
#if defined(CONFIG_I2C_SUPPORT_DMA) && (CONFIG_I2C_SUPPORT_DMA == 1)
        osal_msleep(I2C_DMA_TRANSFER_DELAY_MS);
#endif
        if (uapi_i2c_slave_write(CONFIG_I2C_SLAVE_BUS_ID, &data) == ERRCODE_SUCC) {
            osal_printk("i2c%d slave send succ!\r\n", CONFIG_I2C_SLAVE_BUS_ID);
        }
    }

    return NULL;
}

static void i2c_slave_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2c_slave_task, 0, "I2cSlaveTask", I2C_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2C_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2c_slave_entry. */
app_run(i2c_slave_entry);

注意事项

  • 函数uapi_i2c_set_baudrate需要先初始化,再调用,方便修改波特率。如果I2C未经过初始化,直接调用uapi_i2c_set_baudrate函数,会返回错误码ERRCODE_I2C_NOT_INIT。

  • 需要主动确保数据发送指针send_buf和数据接收指针receive_buf不可传入空指针。

  • 当发送的数据大于对接设备的可接受范围时,会发送失败;如果发送数据失败,再切换另一个I2C设备继续发送时,会造成总线挂死,所有I2C设备都无法正确发送数据。

  • uapi_i2c_master_init不能多次初始化,使用完成后需要调用uapi_i2c_deinit进行去初始化。

ADC

概述

ADC(Analog-to-Digital Converter)模/数转换器,是指将连续变化的模拟信号转换为离散的数位信号的器件。

真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发送的数字信号,模/数转换器可以实现这个功能,可应用于电量检测、按键检测等。

提供GAFE(General Analog Front End)、AAFE(Audio Analog Front End)两种模式开发者选择:

  • GAFE:8通道 13bit 1.6Msps SAR ADC,提供过采样和buf功能,用于Tag-ID、Battery电压测量、NTC温度测量等低频单端/差分量测场景。

  • AAFE:40dB前级放大,复用SAR ADC,数据域降采样到16ksps或32ksps,适用于模拟麦克风采样场景。

功能描述

ADC模块提供的接口及功能如下:

  • uapi_adc_init(adc_clock_t clock):初始化ADC模块(clock表示ADC采样的时钟)。

  • uapi_adc_deinit(void):去初始化ADC。

  • uapi_adc_power_en(afe_scan_mode_t afe_scan_mode, bool en):上下电并启用或关闭ADC(afe_scan_mode表示需要使用的AFE精度模式,en表示上电或下电状态)。

  • uapi_adc_is_using(void):检查ADC是否正在使用。

  • adc_calibration:ADC校准,包含4步校准。

  • uapi_adc_open_channel(uint8_t channel):开启一个ADC通道(适用于所有Channel)(channel表示需要开启的ADC通道)。

  • uapi_adc_close_channel(uint8_t channel):关闭一个ADC通道(适用于所有Channel)(channel表示需要关闭的ADC通道)。

  • uapi_adc_open_differential_channel(uint8_t postive_ch, uint8_t negative_ch):开启差分ADC通道(适用于GADC_CHANNEL_6与GADC_CHANNEL_7)(postive_ch表示ADC正极通道,negative_ch表示ADC负极通道)。(打开CONFIG_ADC_SUPPORT_DIFFERENTIAL宏才能使用)

  • uapi_adc_close_differential_channel(uint8_t postive_ch, uint8_t negative_ch):关闭差分ADC通道(适用于GADC_CHANNEL_6与GADC_CHANNEL_7)(postive_ch表示ADC正极通道,negative_ch表示ADC负极通道)。(打开CONFIG_ADC_SUPPORT_DIFFERENTIAL宏才能使用)

  • uapi_adc_auto_sample(uint8_t channel):ADC采样接口,上报值为与参考电压的码字值(32位有符号数),需要按公式转换为电压值(channel表示ADC通道)。(打开CONFIG_ADC_SUPPORT_AFE宏才能使用)

  • adc_port_gadc_entirely_open(adc_channel_t channel, bool self_cali):包含初始化一个GADC通道的全部流程,self_cali参数表示本次初始化是否要校准ADC,如校准则需要花费约170ms,如已存储校准值,则可以传入false。

  • adc_port_gadc_entirely_sample(adc_channel_t channel):包含采样一个GADC通道的全部流程,返回值是转换为mV的电压值。

  • adc_port_gadc_entirely_close(adc_channel_t channel):包含关闭GADC通道的全部流程,会记录下当前的校准值。

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 ADC相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_ADC_SUPPORT_AUTO_SCAN

ADC support auto scan.

ADC支持自动扫描采样

默认关闭,建议关闭,非BS2X使用

CONFIG_ADC_SUPPORT_LONG_SAMPLE

ADC support long sample mode.

ADC支持周期自动采样

CONFIG_ADC_SUPPORT_AUTO_SCAN

默认关闭,建议关闭

CONFIG_ADC_SUPPORT_QUERY_REGS

ADC support query regs values.

ADC支持回读所有相关寄存器的值,视情况选择

默认关闭,视情况选择,建议关闭

CONFIG_ADC_USING_V153

ADC use version 153.

该宏代表使用ADC V153的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_ADC_SUPPORT_AFE

ADC use Analog front-end mode.

支持模拟前端ADC

CONFIG_ADC_USING_V153

默认打开,建议打开

CONFIG_ADC_SUPPORT_DIFFERENTIAL

ADC support differential sample.

ADC支持使用差分通道进行采样

CONFIG_ADC_USING_V153

默认打开,建议打开

CONFIG_ADC_SUPPORT_AMIC

Whether ADC support AMIC.

ADC支持AMIC

CONFIG_ADC_USING_V153

默认打开,建议打开

CONFIG_AFE_SAMPLE_TIMES

The numbers ADC sample to average.

ADC数采配置的默认长度,本产品暂未使用

默认值是4,本产品实际暂未使用,建议不做修改

开发指引

可配置项主要在ADC对应的porting层头文件中,下文将介绍重要可配置项的功能。

GAFE:

  • cfg_adc_data0.b.data_num:计数模式下采样点数为2x个。

  • cfg_adc_data0.b.wait_num:等待数据链路稳定抛弃的采样值数目。

  • cfg_adc_data1.b.osr_len:过采样累加样点数。

  • cfg_adc_data1.b.osr_sel:过采样平均样数,和累加样点数配置为相同值,上报值为(data1+data2+...+data_osr_len)/osr_sel。

  • 如果想要减短GADC采样时间,可以减少上述四个变量的值,此处给出一组极限值:

  • .cfg_adc_data0.b.data_num = 0xa,

    .cfg_adc_data0.b.wait_num = 4,

    .cfg_adc_data1.b.osr_len= 2,

    .cfg_adc_data1.b.osr_sel= 2,

AAFE:

  • .pga_gain:AAFE电路前极放大器PGA增益倍数。

示例:

GAFE使能:

static void test_afe_set_io(uint8_t channel)
{
    pin_t adc_pin[] = {S_MGPIO2, S_MGPIO3, S_MGPIO4, S_MGPIO5, S_MGPIO28, S_MGPIO29, S_MGPIO30, S_MGPIO31};
    uapi_pin_set_mode(adc_pin[channel], PIN_MODE_0);
    uapi_gpio_set_dir(adc_pin[channel], GPIO_DIRECTION_INPUT);
    uapi_pin_set_pull(adc_pin[channel], PIN_PULL_NONE);
    uapi_pin_set_ie(adc_pin[channel], PIN_IE_1);
}
int gadc_open_demo(void)
{
)   uint8_t channel = GADC_CHANNEL_0;
    if (channel >= ADC_CHANNEL_MAX_NUM) {
        return ERROR_BAD_PARAMS;
    }
    test_afe_set_io(channel);
    uapi_adc_init(ADC_CLOCK_NONE);
    uapi_adc_power_en(AFE_GADC_MODE, true);
    uapi_adc_open_channel(channel);
    adc_calibration(AFE_GADC_MODE, true, true, true);
    return TEST_OK;
}

GAFE采样:

#define ADC_AUTO_SAMPLE_TEST_TIMES 10
int adc_auto_sample_demo(void)
{
    adc_v153_gadc_channel_sel_t channel = GADC_CHANNEL_1;  // 采样时选择希望采样的channel
    uapi_adc_open_channel(channel);
    int adc_value = 0;
    for (int i = 0; i < ADC_AUTO_SAMPLE_TEST_TIMES; i++) {
        adc_value =  adc_port_gadc_entirely_sample(channel);
        test_suite_log_stringf("adc:%dmV%x\n", adc_value);
    }
    return TEST_OK;
}

GAFE通道切换采样:

int adc_auto_sample_demo(void)
{
    adc_v153_gadc_channel_sel_t channel = GADC_CHANNEL_1;  // 采样时选择希望采样的channel
 
    int adc_value = 0;
    for (int i = 0; i < ADC_AUTO_SAMPLE_TEST_TIMES; i++) {
        adc_value =  adc_port_gadc_entirely_sample(channel);
        test_suite_log_stringf("adc: %dmV%x\n", adc_value);
    }
    channel = GADC_CHANNEL_3;  // 切换到下一个需要采样的channel
    for (int i = 0; i < ADC_AUTO_SAMPLE_TEST_TIMES; i++) {
        adc_value =  adc_port_gadc_entirely_sample(channel);
        test_suite_log_stringf("adc: %dmV%x\n", adc_value);
    }
    return TEST_OK;
}

GAFE entire接口使用:
void adc_entire_demo(void) {
    uint8_t gadc_channel1 = GADC_CHANNEL_0;
    uint8_t gadc_channel2 = GADC_CHANNEL_5;
    int adc_value = 0;
    adc_port_gadc_entirely_open(gadc_channel1,true); // ADC第一次开启,传入true表示本次校准.
    adc_value =  adc_port_gadc_entirely_sample(gadc_channel1);
    osal_printk("gadc: %dmv\n", adc_value );
    adc_port_gadc_entirely_close(gadc_channel1);    //close时会记录校准值
    adc_port_gadc_entirely_open(gadc_channel1, false);  // False表示本次开启使用记录好的校准值.
    adc_value =  adc_port_gadc_entirely_sample(gadc_channel2);
    osal_printk("gadc_channel2: %dmv\n", adc_value);
    adc_port_gadc_entirely_close(gadc_channel2);
}

AAFE使能&采样:
int amic_open_demo(void)
{
    test_afe_set_io(AMIC_CHANNEL_0);
    test_afe_set_io(AMIC_CHANNEL_1);
    uapi_adc_init(ADC_CLOCK_NONE);
    uapi_adc_power_en(AFE_AMIC_MODE, true);
    uapi_adc_open_differential_channel(AMIC_CHANNEL_0, AMIC_CHANNEL_1);
    adc_calibration(AFE_AMIC_MODE, true, true, true);
    uapi_adc_auto_sample(AMIC_CHANNEL_0);  // 开启采样后数据需要被音频驱动搬走
    return TEST_OK;
}

注意事项

  • GAFE模拟输入电压范围。

    受限于数模复用的GPIO供电电压,ADC参考电压为1.5V,故八个端口只能输入0~1.5V电压,禁止输入1.5V以上电压。更高电压建议片外电阻分压到1.5V以内再接到GAFE输入。分压电路设计请与硬件工程师沟通后再投板。增加分压电路后,可能因存在电容导致引入充放电时间,需要实测确定。

  • AAFE采集音频信号处理方法。

    通过uapi_adc_auto_sample采集到的音频信号与GAFE采集到的电量信号格式相同,包含13bit有效位及1bit符号位,处理完的值通过PDM模块发送,具体实现参考“开发指引”章节。

  • 推荐使用带entirely的三个接口,较为简洁。上电后第一次初始化时校准即可。

DMA

概述

DMA(Directory Memory Access)直接存储器访问是一种完全由硬件执行数据交换的工作方式。在这种方式中,直接存储器访问控制器DMAC(Directory Memory Access Controller)直接在存储器和外设、外设和外设、存储器和存储器之间进行数据传输,减少处理器的干涉和开销。

DMA方式一般用于高速传输成组的数据。DMAC在收到DMA传输请求后根据CPU对通道的配置启动总线主控制器,向存储器和外设发出地址和控制信号,对传输数据的个数计数,并以中断方式向CPU报告传输操作的结束或错误。

提供的DMA规格如下:

  • 支持存储器到存储器、存储器到外设、外设到存储器、外设到外设传输类型。

  • MCU侧DMA支持8个通道,16个硬件握手接口,且通道参数优先级可配置。

  • 所有通道支持单个包长度最大4095Byte数据,4K数据需要在同一个sector内。

  • 支持大小端可配置。

功能描述

说明: 如果需要在SPI/UART/I2C/I2S/PDM中使用DMA传输数据,需要在系统启动时进行DMA初始化。

DMA模块提供的接口及功能如下:

  • uapi_dma_init(void):初始化DMA。

  • uapi_dma_deinit(void):去初始化DMA。

  • uapi_dma_open(void):打开DMA。

  • uapi_dma_close(void):关闭DMA。

  • uapi_dma_start_transfer(uint8_t channel):启动指定通道的DMA传输(channel表示需要开启的DMA通道数)。

  • uapi_dma_end_transfer(uint8_t channel):停止指定通道的DMA传输(channel表示需要停止的DMA通道数)。

  • uapi_dma_get_block_ts(uint8_t channel):获取DMA传输的数据量(channel表示DMA通道数)。

  • uapi_dma_transfer_memory_single(const dma_ch_user_memory_config_t *user_cfg, dma_transfer_cb_t callback, uintptr_t arg):通过DMA通道传输类型为内存到内存的数据(user_cfg表示DMA通道传输配置,callback表示通道传输完成回调函数,arg表示传输完成时回传给回调函数的参数)。

  • uapi_dma_configure_peripheral_transfer_single(const dma_ch_user_peripheral_config_t *user_cfg, uint8_t *channel, dma_transfer_cb_t callback, uintptr_t arg):通过DMA通道传输类型为内存到外设或外设到内存的数据(user_cfg表示DMA通道传输配置,channel表示被选择的DMA通道数,callback表示通道传输完成回调函数,arg表示传输完成时回传给回调函数的参数)。

  • uapi_dma_get_lli_channel(uint8_t burst_length, uint8_t handshaking):获取DMA链表传输通道(burst_length表示DMA的burst传输长度,handshaking表示DMA传输外设种类)。(打开CONFIG_DMA_SUPPORT_LLI宏才能使用)

  • uapi_dma_transfer_memory_lli(uint8_t channel, const dma_ch_user_memory_config_t *user_cfg, dma_transfer_cb_t callback):通过DMA通道以链表模式传输类型为内存到内存的数据(channel表示DMA通道数,user_cfg表示DMA通道传输配置,callback表示通道传输完成回调函数)。(打开CONFIG_DMA_SUPPORT_LLI宏才能使用)

  • uapi_dma_configure_peripheral_transfer_lli(uint8_t channel, const dma_ch_user_peripheral_config_t *user_cfg, dma_transfer_cb_t callback):通过DMA通道以链表模式传输类型为内存到外设或外设到内存的数据channel表示DMA通道数,user_cfg表示DMA通道传输配置,callback表示通道传输完成回调函数)。(打开CONFIG_DMA_SUPPORT_LLI宏才能使用)

  • uapi_dma_enbale_lli(uint8_t channel, dma_transfer_cb_t callback, uintptr_t arg):启用DMA链表传输(channel表示DMA通道数,callback表示通道传输完成回调函数,arg表示传输完成时回传给回调函数的参数)。

    (打开CONFIG_DMA_SUPPORT_LLI宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 DMA相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_DMA_SUPPORT_LLI

DMA support lli.

DMA支持链表模式

默认打开,视情况选择,建议打开

CONFIG_DMA_SUPPORT_CIRCULAR_LLI

DMA support circular lli.

DMA支持循环链表模式

CONFIG_DMA_SUPPORT_LLI

默认关闭,建议关闭

CONFIG_DMA_SUPPORT_LPM

DMA support low power mode.

该宏仅用于低功耗情况下使用

默认打开,视情况选择

CONFIG_DMA_SUPPORT_CLOCK

DMA choose clock.

DMA 工作时钟

默认打开,必选

CONFIG_DMA_SUPPORT_QUERY_REGS

DMA support query regs values.

DMA支持回读所有相关寄存器的值,视情况选择

默认关闭,视情况选择,建议关闭

CONFIG_DMA_USING_V100

Using DMA V100.

该宏代表使用DMA V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_DMA_USING_V120

Using DMA V120.

该宏代表使用DMA V120的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_DMA_USING_V151

Using DMA V151.

该宏代表使用DMA V151的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_DMA_TRANSFER_LOCK_BUS

DMA transfer lock bus.

DMA支持锁总线,本产品暂未使用

CONFIG_DMA_USING_V151

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_DMA_SUPPORT_SMDMA

DMA support SMDMA.

支持使用SMDMA,

本产品暂未使用

CONFIG_DMA_USING_V100

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_SUPPORT_DATA_CACHE

DMA support DATA CACHE.

DMA支持数据缓存

默认关闭,视情况选择,建议关闭

CONFIG_DMA_LLI_NODE_FIX_MEM

DMA lli node using static memory.

DMA链表结点分配使用静态内存

默认关闭,视情况选择,建议关闭

CONFIG_DMA_LLI_NODE_MAX

DMA lli node max count.

DMA链表结点分配最大个数,默认值是4

CONFIG_DMA_LLI_NODE_FIX_MEM

默认值是4,视情况选择配置,建议不做修改

开发指引

DMA接口仅对外提供存储器到存储器的拷贝功能(其他拷贝方式可参考本文档内对应外设驱动开发指引),操作步骤如下:

  1. 调用uapi_dma_init接口,初始化DMA模块。

  2. 调用uapi_dma_open接口,打开DMA通道。

  3. 调用uapi_dma_transfer接口,DMA开始传输,通过参数block可以设置是否为阻塞模式。

示例:

#include "dma.h"
#include "hal_dma.h"
/* 传输完成后回调函数处理 */
static bool g_dma_trans_done;
static bool g_dma_trans_succ;
void test_dma_trans_done_callback(uint8_t intr, uint8_t channel, uintptr_t arg)
{
    unused(channel);
    unused(arg);
    switch (intr) {
        case HAL_DMA_INTERRUPT_TFR:
            g_dma_trans_done = true;
            g_dma_trans_succ = true;
            break;
        case HAL_DMA_INTERRUPT_BLOCK:
            g_dma_trans_done = true;
            g_dma_trans_succ = true;
            break;
        case HAL_DMA_INTERRUPT_ERR:
            g_dma_trans_done = true;
            g_dma_trans_succ = false;
            break;
        default:
            break;
    }
    osal_printk("[DMA] int_type is %d. \r\n", intr);
}
static void test_fill_test_buffer(void *data, unsigned int length)
{
    for (unsigned int i = 0; i < length; i++) {
        *((unsigned char *)data + i) = (unsigned char)i;
    }
}
static void test_clear_test_buffer(void *data, unsigned int length)
{
    memset_s(data, length, 0, length);
}
errcode_t test_dma_mem_to_mem_single(void)
{
    dma_ch_user_memory_config_t transfer_config;
    /* 填充源地址要发送的数据 */
    test_fill_test_buffer((void *)(uintptr_t)g_dma_src_data, sizeof(g_dma_src_data));
    /* 清空目的地址的数据 */
    test_clear_test_buffer((void *)(uintptr_t)g_dma_desc_data, sizeof(g_dma_desc_data));
    /* 初始化DMA */
    uapi_dma_init();
    /* 开启DMA模块 */
    uapi_dma_open();
    /* 源地址 */
    transfer_config.src = ((uint32_t)(uintptr_t)g_dma_src_data);
    /* 目的地址 */
    transfer_config.dest = ((uint32_t)(uintptr_t)g_dma_desc_data);
    /* 传输数目 */
    transfer_config.transfer_num = 100;
    /* 优先级0-3, 0最低 */
    transfer_config.priority = 0;
    /* 传输宽度 0:1字节 1:2字节 2:4字节 */
    transfer_config.width = 0;
    /* 调用接口按块发送函数,并注册回调函数 */
    if (uapi_dma_transfer_memory_single(&transfer_config, test_dma_trans_done_callback, 0) != ERRCODE_SUCC) {
        return ERRCODE_FAIL;
    }
    /* 等待发送完成 */
    while (!g_dma_trans_done) { }
    if (!g_dma_trans_succ) {
        return ERRCODE_FAIL;
    }
    return ERRCODE_SUCC;
}

DMA完整代码流程可参考以下代码:
#include "hal_dma.h"
#include "soc_osal.h"
#include "securec.h"
#include "app_init.h"
#include "dma.h"

#define DMA_TRANSFER_WORD_NUM       32
#define DMA_TRANSFER_PRIORITY       0
#define DMA_TRANSFER_WIDTH          2
#define DMA_TASK_DURATION_MS        500

#define DMA_TASK_PRIO               24
#define DMA_TASK_STACK_SIZE         0x1000

static uint32_t g_app_dma_src_data[DMA_TRANSFER_WORD_NUM] = { 0 };
static uint32_t g_app_dma_desc_data[DMA_TRANSFER_WORD_NUM] = { 0 };
static uint8_t g_dma_trans_done = 0;

static void app_dma_trans_done_callback(uint8_t int_type, uint8_t channel, uintptr_t arg)
{
    unused(arg);
    unused(channel);
    switch (int_type) {
        case HAL_DMA_INTERRUPT_TFR:
            g_dma_trans_done = 1;
            break;
        case HAL_DMA_INTERRUPT_BLOCK:
            g_dma_trans_done = 1;
            break;
        case HAL_DMA_INTERRUPT_ERR:
            osal_printk("DMA transfer error.\r\n");
            break;
        default:
            break;
    }
}

static void *dma_task(const char *arg)
{
    unused(arg);
    /* DMA init. */
    uapi_dma_deinit();
    uapi_dma_init();
    uapi_dma_open();

#if defined(CONFIG_DMA_MEMORY_LLI_TRANSFER_MODE)
    dma_channel_t dma_channel = DMA_CHANNEL_NONE;
    /* The following two input parameters need to be adapted according to the actual situation when using SMDMA. */
    dma_channel = uapi_dma_get_lli_channel(0, HAL_DMA_HANDSHAKING_MAX_NUM);
#endif

    for (uint32_t i = 0; i < DMA_TRANSFER_WORD_NUM; i++) {
        g_app_dma_src_data[i] = i;
    }
    memset_s(g_app_dma_desc_data, DMA_TRANSFER_WORD_NUM, 0, DMA_TRANSFER_WORD_NUM);

    dma_ch_user_memory_config_t transfer_config = { 0 };
    transfer_config.src = (uint32_t)(uintptr_t)g_app_dma_src_data;
    transfer_config.dest = (uint32_t)(uintptr_t)g_app_dma_desc_data;
    transfer_config.transfer_num = DMA_TRANSFER_WORD_NUM;
    transfer_config.priority = DMA_TRANSFER_PRIORITY;
    transfer_config.width = DMA_TRANSFER_WIDTH;

    while (1) {
        osal_msleep(DMA_TASK_DURATION_MS);
        g_dma_trans_done = 0;
#if defined(CONFIG_DMA_MEMORY_LLI_TRANSFER_MODE)
/* 此处链表只配置了一个结点且执行的是内存到内存之间的搬运,外设到内存搬运的接口使用请参考其他驱动示例代码 */
        osal_printk("dma config link list item of memory to memory start!\r\n");
        if (uapi_dma_transfer_memory_lli(dma_channel, &transfer_config, app_dma_trans_done_callback) == ERRCODE_SUCC) {
            osal_printk("dma config link list item of memory to memory succ!\r\n");
        }
        osal_printk("dma enable lli memory transfer start!\r\n");
        if (uapi_dma_enable_lli(dma_channel, app_dma_trans_done_callback, (uintptr_t)NULL) == ERRCODE_SUCC) {
            osal_printk("dma enable lli memory transfer succ!\r\n");
        }
        while (!g_dma_trans_done) {}
        if (uapi_dma_end_transfer(dma_channel) == ERRCODE_SUCC) {
            osal_printk("dma channel transfer finish!\r\n");
        }
#else
        osal_printk("dma single memory transfer start!\r\n");
        if (uapi_dma_transfer_memory_single(&transfer_config, app_dma_trans_done_callback,
                                            (uintptr_t)NULL) == ERRCODE_SUCC) {
            osal_printk("dma single memory transfer succ!\r\n");
        }
        while (!g_dma_trans_done) {}
        osal_printk("dma checking transfer from 0x%08x to 0x%08x...\r\n", transfer_config.src, transfer_config.dest);
        if (memcmp((void *)transfer_config.src, (void *)transfer_config.dest, transfer_config.transfer_num) == 0) {
            osal_printk("dma memory copy test succ, length = %d block\r\n", transfer_config.transfer_num);
        }
#endif
    }

    return NULL;
}

static void dma_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)dma_task, 0, "DmaTask", DMA_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, DMA_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the dma_entry. */
app_run(dma_entry);

注意事项

  • 建议仅在需要非阻塞进行数据拷贝的场景下使用DMA,此时可让出CPU,传输完成之后CPU会上报中断,可以在回调函数中根据事件类型判断传输成功与失败。传输阻塞场景下,仍建议使用memcpy_s进行数据拷贝。

  • DMA的IDLE中断不会在FIFO为空的时候触发,只有在FIFO有数且一段时间未到达水线时触发,故水线设为1字节时也不会触发IDLE中断。

PWM

概述

PWM(Pulse Width Modulation)脉宽调制模块通过对一系列脉冲的宽度进行调制,等效出所需波形。即对模拟信号电平进行数字编码,通过调节频率、占空比的变化来调节信号的变化。

PWM规格如下:

  • 支持12路PWM输出,寄存器单独可配置。

  • 支持PWM分组,一个组可以分配多个通道,同一个通道不能同时位于不同的组里。

  • 支持0电平宽度和1电平宽度可调。

  • 支持固定周期数发送模式。

  • 支持发送完成中断,支持中断清除和中断查询。

  • 仅支持低16bit的相位控制计数、频率控制计数以及占空比计数。

功能描述

PWM模块提供的接口及功能如下:

  • uapi_pwm_init(void):初始化PWM。

  • uapi_pwm_deinit(void):去初始化PWM。

  • uapi_pwm_open(uint8_t channel, const pwm_config_t *cfg):打开PWM通道(channel表示PWM通道数,cfg表示PWM设备的配置信息)。

  • uapi_pwm_close(uint8_t channel):关闭PWM通道(channel表示PWM通道数)。

  • uapi_pwm_get_frequency(uint8_t channel):获取PWM工作频率(channel表示PWM通道数)。

  • uapi_pwm_start(uint8_t channel):启动PWM信号输出(channel表示PWM通道数)。

  • uapi_pwm_isr(uint8_t channel):PWM中断服务例程(channel表示PWM通道数)。

  • uapi_pwm_register_interrupt(uint8_t channel, pwm_callback_t callback):为PWM注册中断回调(channel表示PWM通道数,callback表示PWM通道传输完成的回调函数)。

  • uapi_pwm_unregister_interrupt(uint8_t channel):去PWM注册中断回调(channel表示PWM通道数)。

  • uapi_pwm_set_group(uint8_t group, const uint8_t *channel_set, uint32_t channel_set_len):为PWM通道分组(group表示PWM组数,channel_set表示进行分组设置的通道集合,channel_set_len表示进行分组设置的通道集合长度)。(打开CONFIG_PWM_USING_V151宏才能使用)

  • uapi_pwm_clear_group(uint8_t group):清理PWM通道分组(group表示PWM组数)。(打开CONFIG_PWM_USING_V151宏才能使用)

  • uapi_pwm_start_group(uint8_t group):启动指定分组的PWM(group表示PWM组数)。(打开CONFIG_PWM_USING_V151宏才能使用)

  • uapi_pwm_stop_group(uint8_t group):停止指定分组的PWM(group表示PWM组数)。(打开CONFIG_PWM_USING_V151宏才能使用)

  • uapi_pwm_update_cfg(uint8_t channel, const pwm_config_t *cfg):更新指定PWM通道的配置(channel表示PWM通道数,cfg表示PWM设备的配置信息)。(打开CONFIG_PWM_USING_V151宏才能使用)

  • uapi_pwm_config_preload(uint8_t group, uint8_t channel, const pwm_config_t *cfg):PWM预配置,当上一个PWM配置完成时,此配置会自动加载(group表示PWM组数,channel表示PWM通道数,cfg表示PWM设备的配置信息)。(打开CONFIG_PWM_USING_V151以及CONFIG_PWM_PRELOAD宏才能使用)

  • uapi_pwm_suspend(uintptr_t arg):挂起所有PWM通道,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_PWM_SUPPORT_LPM宏才能使用)

  • uapi_pwm_resume(uintptr_t arg):恢复所有PWM通道,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_PWM_SUPPORT_LPM宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 PWM相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_PWM_SUPPORT_LPM

PWM support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认关闭,视情况选择

CONFIG_PWM_USING_V150

Using PWM V150.

该宏代表使用PWM V150的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_PWM_USING_V151

Using PWM V151.

该宏代表使用PWM V151的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_PWM_PRELOAD

Using PWM preload.

该宏主要用来重新配置PWM的相关参数

CONFIG_PWM_USING_V151

默认关闭,视情况选择

CONFIG_PWM_GROUP_NUM

The number of groups in PWM.

PWM配置的分组数,默认值是6

默认值是6,视情况选择配置,建议不做修改

CONFIG_PWM_CHANNEL_NUM

PWM channel number.

PWM通道数配置,默认值是12

默认值是12,视情况选择配置,建议不做修改

开发指引

PWM利用微处理器的数字输出对模拟电路进行控制,操作步骤如下:

  1. 调用uapi_pin_set_mode接口,将IO复用为PWM功能。

  2. 调用uapi_pwm_init对PWM进行初始化。

  3. 调用uapi_pwm_set_group对PWM进行分组设置。

  4. 调用uapi_pwm_open,配置PWM参数,打开指定通道。

  5. 调用uapi_pwm_register_interrupt接口,注册PWM中断的回调函数。

  6. 调用uapi_pwm_start或者uapi_pwm_start_group接口,开启指定ID或者指定分组的PWM信号输出。

  7. 调用uapi_pwm_stop_group接口,停止指定分组的PWM信号输出。

  8. 调用uapi_pwm_close接口,关闭指定ID的PWM信号输出。

  9. 调用uapi_pwm_deinit接口,去初始化PWM模块。

示例:

#include "pwm.h"
#include "pwm_porting.h"
#define TEST_MAX_TIMES 10
#define TEST_DELAY_MS 1000
#define GROUP_ID      0
/* PWM注册中断回调函数 */
static errcode_t pwm_test_callback(pwm_channel_t channel)
{
    osal_printk("PWM channel number is %d, func of interrupt start. \r\n", channel);
    uapi_pwm_isr(channel);
    return ERRCODE_SUCC;
}
void test_pwm_sample(pin_t pin, pin_mode_t mode, pwm_channel_t channel)
{
    /* 设置循环次数 */
    unsigned int test_times;
    /* 配置 low_time、high_time、cycles, repeat. 当repeat为true时候,cycles无效 */
    /* offset_time 未使用到配置为0 */
    pwm_config_t cfg_repeat = { 100, 100, 0, 0, true };
    /* 设置可作为PWM IO的模式 */
    uapi_pin_set_mode(pin, mode);
    uapi_pwm_init();
    uint8_t channel_id = channel;
    uapi_pwm_set_group(GROUP_ID, channel_id, 1);
    /* 打开指定channel的PWM */
    uapi_pwm_open(channel, &cfg_repeat);
    /* 注册回调函数 */
    uapi_pwm_register_interrupt(channel, pwm_test_callback);
    /* 启动指定channel pwm输出 */
    uapi_pwm_start(channel);
    /* 当前设置为循环输出,循环TEST_MAX_TIMES次,每次delay TEST_DELAY_MS,后关闭pwm输出 */
    for (test_times = 0; test_times <= TEST_MAX_TIMES; test_times++) {
    if (test_times == TEST_MAX_TIMES) {
            uapi_pwm_stop_group(GROUP_ID);
            uapi_pwm_close(channel);
            osal_printk("now close the pwm output and trigger interrupt \r\n");
        }
        osal_mdelay(TEST_DELAY_MS);
    }
    uapi_pwm_deinit();
    return;
}

PWM 完整代码流程可参考以下代码:
#if defined(CONFIG_PWM_SUPPORT_LPM)
#include "pm_veto.h"
#endif
#include "common_def.h"
#include "pinctrl.h"
#include "pwm.h"
#include "tcxo.h"
#include "soc_osal.h"
#include "app_init.h"

#define TEST_TCXO_DELAY_500MS      500
#define PWM_LOW_TIME_CYC           0
#define PWM_HIGH_TIME_CYC          20
#define PWM_TASK_PRIO              24
#define PWM_TASK_STACK_SIZE        0x1000

static uint32_t g_pwm_cyc_done_cnt = 0;
static errcode_t pwm_sample_callback(uint8_t channel)
{
    unused(channel);
    g_pwm_cyc_done_cnt++;
    return ERRCODE_SUCC;
}

void pwm_repeat_mode(void)
{
    pwm_config_t cfg_repeat = {
        PWM_LOW_TIME_CYC,
        PWM_HIGH_TIME_CYC,
        0,
        0xFF,
        true
    };

    uapi_pwm_init();
    uapi_pwm_open(CONFIG_PWM_CHANNEL, &cfg_repeat);
    uapi_pwm_register_interrupt(CONFIG_PWM_CHANNEL, pwm_sample_callback);
#if defined(CONFIG_PWM_SUPPORT_LPM)
    /* veto sleep, or pwm will stop after sleep */
    uapi_pm_add_sleep_veto(PM_USER0_VETO_ID);
#endif
#ifdef CONFIG_PWM_USING_V151
    uint8_t channel_id = CONFIG_PWM_CHANNEL;
    uapi_pwm_set_group(CONFIG_PWM_GROUP_ID, &channel_id, 1);
    uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
#else
    uapi_pwm_start(CONFIG_PWM_CHANNEL);
#endif
    uapi_tcxo_delay_ms((uint32_t)TEST_TCXO_DELAY_500MS);

#ifdef CONFIG_PWM_USING_V151
    /* update duty ratio config */
    while (cfg_repeat.low_time <= PWM_HIGH_TIME_CYC) {
        uapi_pwm_update_cfg(CONFIG_PWM_CHANNEL, &cfg_repeat);
        uapi_tcxo_delay_ms((uint32_t)TEST_TCXO_DELAY_500MS);
        osal_printk("pwm cyc_done_cnt:%u, duty_radio:[%u/%u]\r\n", g_pwm_cyc_done_cnt,
            cfg_repeat.high_time, cfg_repeat.high_time + cfg_repeat.low_time);
        cfg_repeat.low_time++;
        cfg_repeat.high_time--;
    }
    uapi_pwm_stop_group(CONFIG_PWM_GROUP_ID);
#endif

#if defined(CONFIG_PWM_SUPPORT_LPM)
    uapi_pm_remove_sleep_veto(PM_USER0_VETO_ID);  /* remove veto sleep */
#endif
    uapi_pwm_close(CONFIG_PWM_CHANNEL);
    uapi_pwm_deinit();
}

static void *pwm_task(const char *arg)
{
    unused(arg);
    pwm_config_t cfg_no_repeat = {
        100,
        100,
        0,
        0xFF,
        false
    };

    uapi_pin_set_mode(CONFIG_PWM_PIN, CONFIG_PWM_PIN_MODE);
    uapi_pwm_init();
    uapi_pwm_open(CONFIG_PWM_CHANNEL, &cfg_no_repeat);
    uapi_pwm_register_interrupt(CONFIG_PWM_CHANNEL, pwm_sample_callback);
#ifdef CONFIG_PWM_USING_V151
    uint8_t channel_id = CONFIG_PWM_CHANNEL;
    /* channel_id can also choose to configure multiple channels, and the third parameter also needs to be adjusted
        accordingly. */
    uapi_pwm_set_group(CONFIG_PWM_GROUP_ID, &channel_id, 1);
    /* Here you can also call the uapi_pwm_start interface to open each channel individually. */
    uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
#else
    uapi_pwm_start(CONFIG_PWM_CHANNEL);
#endif

    uapi_tcxo_delay_ms((uint32_t)TEST_TCXO_DELAY_500MS);
    uapi_pwm_close(CONFIG_PWM_CHANNEL);
    uapi_pwm_deinit();

    /* pwm repeat mode */
    pwm_repeat_mode();

    return NULL;
}

static void pwm_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)pwm_task, 0, "PwmTask", PWM_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, PWM_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the pwm_entry. */
app_run(pwm_entry);

注意事项

  • 在调用uapi_pwm_deinit接口之前,需要先调用uapi_pwm_close接口。

  • PWM支持占空比为0。

  • PWM支持多路互补输出。

  • 深睡唤醒之后需要先配置复用关系,再调用uapi_pwm_deinit接口去初始化,之后调用uapi_pwm_init接口初始化。

WDT

概述

WDT(Watch Dog Timer)看门狗计时器,一般用于CPU运行异常时实现异常恢复,如果系统正常运行,会定期喂狗,以防止计时器超时。如果系统由于某种原因停止运行或无法正常喂狗,导致计时器在设定的超时时间内未被重置,此时看门狗会认为系统出现故障,触发相应的处理措施,如复位系统或执行特定的错误处理程序。

WDT规格如下:

  • 拥有一个CPU看门狗以及一个PMU看门狗,其中PMU看门狗不对用户开放使用。

  • CPU看门狗超时时间支持2s~32768s可调。

  • CPU看门狗支持直接复位以及中断后复位两种工作模式。

功能描述

WDT模块提供的接口及功能如下:

  • uapi_watchdog_init(uint32_t timeout):初始化Watchdog功能,设置看门狗超时时间,单位s(timeout表示看门狗超时时间)。

  • uapi_watchdog_deinit(void):去初始化Watchdog功能。

  • uapi_watchdog_set_time(uint32_t timeout):设置看门狗超时时间,单位s(如不设置,默认时间是8s)(timeout表示看门狗超时时间)。

  • uapi_watchdog_kick(void):重新启动计数器。

  • uapi_watchdog_enable(wdt_mode_t mode):使能看门狗(mode表示看门狗模式)。

  • uapi_watchdog_disable(void):关闭看门狗。

  • uapi_watchdog_get_left_time(uint32_t *timeout):获取看门狗剩余时间,单位ms(timeout表示剩余时间值)。

  • uapi_register_watchdog_callback(watchdog_callback_t callback):注册看门狗回调(callback表示看门狗超时后处理异常的回调函数)。

  • uapi_watchdog_suspend(uintptr_t arg):挂起看门狗模块,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_WATCHDOG_SUPPORT_LPM宏才能使用)

  • uapi_watchdog_resume(uintptr_t arg):恢复看门狗模块,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_WATCHDOG_SUPPORT_LPM宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 WATCHDOG相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_WATCHDOG_ALREADY_START

Watchdog has been started in another binary.

该宏表示WATCHDOG已经开启,无需再重复配置

默认关闭,视情况选择,建议关闭

CONFIG_WATCHDOG_SUPPORT_ULP_WDT

Watchdog support ulp watchdog.

支持低功耗ulp WATCHDOG

默认打开,建议打开

CONFIG_WATCHDOG_SUPPORT_LPM

Watchdog support low power mode.

该宏仅用于低功耗情况下使用

默认打开,视情况选择,建议打开

CONFIG_WATCHDOG_USING_V100

Using watchdog V100.

该宏代表使用WATCHDOG V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_WATCHDOG_USING_V151

Using watchdog v151.

该宏代表使用WATCHDOG V151的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_WATCHDOG_USING_V152

Using watchdog v152.

该宏代表使用WATCHDOG V152的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_WATCHDOG_USING_V151_RST_PL

Using watchdog V151 rst_pl.

WATCHDOG 配置的系统复位有效长度

CONFIG_WATCHDOG_USING_V151

默认值是7,视情况选择配置,建议不做修改

开发指引

WDT一般用于检测是否死机,如果超过喂狗等待的时间没有进行喂狗操作,根据Watchdog配置的使能模式产生一个系统复位或者上报狗中断。参考代码如下:

  1. 调用uapi_watchdog_init初始化并设置看门狗超时时间。

  2. 调用uapi_watchdog_enable,使能看门狗模块,可配置复位模式和中断模式。

  3. 调用uapi_watchdog_kick,进行喂狗操作,此时在idle任务中已实现了喂狗操作。

  4. 调用uapi_watchdog_get_left_time,获取看门狗定时器剩余时间(可选)。

  5. 调用uapi_watchdog_disable,关闭看门狗(正常情况下不建议关闭看门狗)。

示例:

#include "watchdog.h"
#include "watchdog_porting.h"
void sample_wdt(void)
{
    uint32_t sample_remain_ms;
    /* 设置开门狗超时时间 */
    uapi_watchdog_init(CHIP_WDT_TIMEOUT_32S);/* 设置超时时间 */
    uapi_watchdog_enable(WDT_MODE_RESET);/* 使能看门狗 */
    osal_mdelay(5000); /* delay 5000 ms */
    uapi_watchdog_kick(); /* 喂狗 */
    uapi_watchdog_get_left_time(&sample_remain_ms); /* 获取剩余超时时间 */
    osal_printk("sample_remain_ms = %x! \n", sample_remain_ms);
    uapi_watchdog_disable();/* 关闭看门狗 */
}

注意事项

  • 如果获取时间为0xFFFFFFFF,说明看门狗处于未使能状态。

  • 看门狗在SDK中已经使能且已存在喂狗动作,在特殊场景下,如需长时间占用CPU,可将看门狗去使能或在业务代码中增加喂狗操作,避免正常业务场景引起看门狗复位。

  • 看门狗默认超时时间为8s,flashboot中将狗重新配置为32s,使用时请注意。一般情况下,不建议修改超时时间。

Timer

概述

Timer是一种用来计时和产生定时事件的重要模块。它通常由一个计数器和一些相关的寄存器组成。定时器的核心功能是根据设定的时钟源和预设的计数值来进行计数,并在特定条件下产生中断或触发其他事件。

Timer规格如下:

  • 提供4个定时器(Timer0~3)。

  • 每个定时器提供两个32位寄存器用于计数。

  • 支持超时中断以及重装载值。

功能描述

Timer模块提供的接口及功能如下:

  • uapi_timer_adapter(timer_index_t index, uint32_t int_id, uint16_t int_priority):适配定时器配置(index表示硬件定时器索引,int_id表示硬件定时器中断ID,int_priority表示硬件定时器中断优先级)。

  • uapi_timer_init(void):初始化Timer。

  • uapi_timer_deinit(void):去初始化Timer。

  • uapi_timer_create(timer_index_t index, timer_handle_t *timer):创建定时器(index表示硬件定时器索引,timer表示定时器处理返回值)。

  • uapi_timer_delete(timer_handle_t timer):删除指定定时器(timer表示被创建后的定时器)。

  • uapi_timer_start(timer_handle_t timer, uint32_t time_us, timer_callback_t callback, uintptr_t data):开启指定的定时器,开始计时(timer表示被创建后的定时器,time_us表示定时器超时时间,callback表示定时器回调函数,data表示传递给定时器回调函数的参数)。

  • uapi_timer_stop(timer_handle_t timer):停止当前定时器计时(timer表示被创建后的定时器)。

  • uapi_timer_get_max_us(void):用户可以获取到Timer最大可以设置的延时时间(us)。

  • uapi_timer_get_current_time_us(timer_index_t index, uint32_t *current_time_us):获取指定底层Timer定时器的当前时间(us)(index表示底层Timer定时器索引,current_time_us表示底层Timer定时器当前时间us值)。

  • uapi_timer_start_high_precision(timer_index_t index, timer_trigger_mode_t mode, uint32_t time_us, timer_irq_info_t* irq_info, high_precision_timer_callback_t callback):启动一个高精度的专用定时器(index表示硬件定时器索引,mode表示计时模式,time_us表示定时器超时时间,irq_info表示中断信息,callback表示定时器回调函数)。(打开CONFIG_TIMER_SUPPORT_HIGH_PRECISION宏才能使用)

  • uapi_timer_reset_high_precision(timer_index_t index, timer_trigger_mode_t mode, uint32_t time_us):重启一个高精度的专用定时器(index表示硬件定时器索引,mode表示计时模式,time_us表示定时器超时时间)。

    (打开CONFIG_TIMER_SUPPORT_HIGH_PRECISION宏才能使用)

  • uapi_timer_stop_high_precision(timer_index_t index):停止一个高精度的专用定时器(index表示硬件定时器索引)。(打开CONFIG_TIMER_SUPPORT_HIGH_PRECISION宏才能使用)

  • uapi_timer_suspend(uintptr_t val):挂起定时器模块,低功耗情况使用(val表示挂起时所需要的参数)。(打开CONFIG_TIMER_SUPPORT_LPM宏才能使用)

  • uapi_timer_resume(uintptr_t val):恢复定时器模块,低功耗情况使用(val表示恢复时所需要的参数)。(打开CONFIG_TIMER_SUPPORT_LPM宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 TIMER相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_TIMER_MAX_TIMERS_NUM

Max timers num.

最大可配TIMER定时器数量,默认值是8

默认值是8,视情况选择配置,建议不做修改

CONFIG_TIMER_CLOCK_VALUE

Timer clock value in Hz.

TIMER的工作时钟频率,默认是32MHZ

默认值是32000000,建议不做修改

CONFIG_TIMER_SUPPORT_LPC

Timer support low power control, control power and clock.

TIMER 工作时钟

默认打开,必选

CONFIG_TIMER_USING_OLD_VERSION

Timer ip using old version, load count must be aligned with 0xF.

本产品暂未使用旧版本,计数无需与0xF对齐

默认关闭,本产品暂未使用,建议关闭

CONFIG_TIMER_SUPPORT_LPM

Timer support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认打开,视情况选择,建议打开

CONFIG_TIMER_SUPPORT_HIGH_PRECISION

Timer support timer high precision.

支持一个高精度的专用定时器

默认打开,建议打开

CONFIG_TIMER_USING_V100

Using Timer V100.

该宏代表使用TIMER V100的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_TIMER_USING_V150

Using Timer V150.

该宏代表使用TIMER V150的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_TIMER_MAX_NUM

Timer max low layer nums.

TIMER 可配置的最大定时器数量

默认值是4,视情况选择配置,建议不做修改

CONFIG_TIMER_0_WIDTH_64

Using 64 bits width of Timer0.

定时器0位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_1_WIDTH_64

Using 64 bits width of Timer1.

定时器1位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_2_WIDTH_64

Using 64 bits width of Timer2.

定时器2位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_3_WIDTH_64

Using 64 bits width of Timer3.

定时器3位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_4_WIDTH_64

Using 64 bits width of Timer4.

定时器4位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_5_WIDTH_64

Using 64 bits width of Timer5.

定时器5位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_6_WIDTH_64

Using 64 bits width of Timer6.

定时器6位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

CONFIG_TIMER_7_WIDTH_64

Using 64 bits width of Timer7.

定时器7位宽默认使用64bits

CONFIG_TIMER_USING_V150 &&

CONFIG_TIMER_MAX_NUM

默认值是0,视情况选择配置,建议不做修改

开发指引

使用Timer驱动接口创建一个5ms周期触发中断的定时器,参考步骤如下:

  1. 调用uapi_timer_adapter接口,配置定时器索引、定时器中断号和中断优先级。

  2. 调用uapi_timer_init接口,初始化定时器功能。

  3. 调用uapi_timer_create接口,创建一个定时器,函数参数句柄为唯一定时器标识。

  4. 调用uapi_timer_start接口,设置超时时间、超时回调函数、回调函数入参以及启动定时器。

  5. 调用uapi_timer_stop接口,停止当前定时器计时。

  6. 调用uapi_timer_delete接口,删除当前定时器。

示例:

#include "timer.h"
#define DELAY_5MS       5000
#define DELAY_1S        1000000
#define TIMER_IRQ_PRIO  3      /* 中断优先级范围,从高到低:  0~7 */
static timer_handle_t timer1_handle = 0;
static void timer1_callback(uintptr_t data);
void timer1_callback(uintptr_t data)
{
    unused(data);
    osal_printk("Timer1 5ms int test!\r\n");
    /* 开启下一次timer中断 */
    uapi_timer_start(timer1_handle, DELAY_5MS, timer1_callback, 0);
}
errcode_t test_timer_sample(void)
{
    errcode_t ret;
    /* timer 软件初始化 */
    uapi_timer_init();
    /* 设置 timer1 硬件初始化,设置中断号,配置优先级 */
    ret = uapi_timer_adapter(TIMER_INDEX_1, TIMER_1_IRQN, TIMER_IRQ_PRIO);
    /* 创建 timer1 软件定时器控制句柄 */
    uapi_timer_create(TIMER_INDEX_1, &timer1_handle);
    /* 启动定时器 */
    uapi_timer_start(timer1_handle, DELAY_5MS, timer1_callback, 0);
    osal_mdelay(DELAY_1S);
    /* 停止定时器 */
    uapi_timer_stop(timer1_handle);
    /* 删除定时器 */
    uapi_timer_delete(timer1_handle);
    return ret;
}

注意事项

  • Timer创建的定时器超时时间单位为μs。

  • 默认最多可同时创建3个高精度定时器(Timer0~2)。

  • Timer3默认作为liteos的系统时钟源,禁止使用uapi接口配置。

  • 在非低功耗模式下,Timer可配置的最大超时时间约为4294s。

  • 确定不需要使用当前高精度定时器后,需要调用uapi_timer_delete接口,释放该定时器资源。

  • 禁止在Timer的回调函数中调用uapi_timer_stop、uapi_timer_delete等接口。

Systick

概述

Systick是单片机系统中的一种硬件设备或功能模块,用于提供精确的时间基准和定时功能。

系统定时规格如下:

  • Systick提供了两个32位的寄存器用于存放计数值,最高可计数到264-1。

  • 可使用外部32.768kHz晶振或内部32kHz时钟作为时钟源。

功能描述

Systick模块提供的接口及功能如下:

  • uapi_systick_init(void):初始化Systick。

  • uapi_systick_deinit(void):去初始化Systick。

  • uapi_systick_count_clear(void):清除Systick计数。

  • uapi_systick_get_count(void):获取Systick计数值。

  • uapi_systick_get_s(void):获取Systick计数秒值。

  • uapi_systick_get_ms(void):获取Systick计数毫秒值。

  • uapi_systick_get_us(void):获取Systick计数微秒值。

  • uapi_systick_delay_count(uint64_t c_delay):按count计数延时(c_delay表示延时时间)。

  • uapi_systick_delay_s(uint32_t s_delay):按秒数延时(s_delay表示延时时间)。

  • uapi_systick_delay_ms(uint32_t m_delay):按毫秒数延时(m_delay表示延时时间)。

  • uapi_systick_delay_us(uint32_t u_delay):按微秒数延时(us_delay表示延时时间)。

  • uapi_systick_suspend(uintptr_t arg):挂起Systick模块,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_SYSTICK_SUPPORT_LPM宏才能使用)

  • uapi_systick_resume(uintptr_t arg):恢复Systick模块,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_SYSTICK_SUPPORT_LPM宏才能使用)

KCONFIG配置

配置宏具体描述如表1所示。

表 1 SYSTICK相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_SYSTICK_SUPPORT_LPM

Systick support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认打开,视情况选择,建议打开

CONFIG_SYSTICK_WITH_TWO_DATA_REGS

Systick calculated with two data registers.

该宏表示使用两个数据寄存器来计算数据

默认关闭,建议关闭

开发指引

  1. 调用uapi_systick_init接口,初始化Systick模块。

  2. 调用uapi_systick_get_count接口,获取当前Systick计数值。

  3. 调用uapi_systick_delay_ms接口,延迟入参传入的时间。

  4. 再次调用uapi_systick_get_count接口,获取当前Systick计数值。

示例:

#include "systick.h"
void test_systick_sample(void)
{
    uint64_t count_before_delay_count;
    uint64_t count_after_delay_count;
    /* systick模块初始化 */
    uapi_systick_init();
    /* 通过count数差值验证延迟时间 */
    count_before_delay_count= uapi_systick_get_count();
    uapi_systick_delay_ms(1000);
    count_after_delay_count = uapi_systick_get_count();
    osal_printk("test case delay count %lu.\r\n", count_before_delay_count - count_after_delay_count);
}

注意事项

  • Systick时钟源使用外部32.768k或者内部32k时钟,最小计数单元为30μs左右,使用μs延时接口请注意。

  • Systick一般用于提供了一个稳定的时钟信号,作为整个单片机系统的基准时钟。高精度延时则使用TCXO。

TCXO

概述

TCXO(Temperature Compensated Crystal Oscillator)是一种温度补偿晶体振荡器,通过在电路中引入温度传感器和温度补偿电路,以降低温度对振荡频率的影响,从而提供更稳定的时钟信号。芯片内置了一块TCXO晶振及其计数单元,用于计数和延时,用户也可以修改时钟配置,使用外部晶振作为TCXO计数单元的时钟输入,TCXO规格如下:

  • 内部TCXO高达32M,最小计数单元约为32ns。

  • TCXO计数器提供了两个32位的寄存器用于存放计数值,最高可计数到2^64-1。

功能描述

TCXO模块提供的接口及功能如下:

  • uapi_tcxo_init(void):初始化TCXO。

  • uapi_tcxo_deinit(void):去初始化TCXO。

  • uapi_tcxo_get_count(void):获取TCXO计数值。

  • uapi_tcxo_get_ms(void):获取TCXO计数毫秒值。

  • uapi_tcxo_get_us(void):获取TCXO计数微秒值。

  • uapi_tcxo_delay_ms(uint32_t m_delay):设置延迟毫秒数(m_delay表示延时毫秒数)。

  • uapi_tcxo_delay_us(uint32_t u_delay):设置延迟微秒数(u_delay表示延时微秒数)。

  • uapi_tcxo_suspend(uintptr_t arg):挂起TCXO模块,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_TCXO_SUPPORT_LPM宏才能使用)

  • uapi_tcxo_resume(uintptr_t arg):恢复TCXO模块,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_TCXO_SUPPORT_LPM宏才能使用)

KCONFIG配置

配置宏具体描述如表1所示。

表 1 TCXO相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_TCXO_SUPPORT_LPM

TCXO support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用

默认打开,视情况选择,建议打开

CONFIG_TCXO_WITH_TWO_DATA_REGS

TCXO calculated with two data registers.

该宏表示使用两个数据寄存器来计算数据

默认关闭,建议关闭

开发指引

  1. 调用uapi_tcxo_init接口,初始化TCXO模块。

  2. 调用uapi_tcxo_get_count接口,获取当前TCXO计数值。

  3. 调用uapi_tcxo_delay_ms接口,延迟入参传入的时间。

示例:

#include "tcxo.h"
void test_tcxo_sample(void)
{
    uint64_t count_before_delay_count;
    uint64_t count_after_delay_count;
    /* tcxo模块初始化 */
    uapi_tcxo_init();
    /* 通过count差值验证延迟时间 */
    count_before_delay_count = uapi_tcxo_get_count();
    uapi_tcxo_delay_ms(1000);
    count_after_delay_count= uapi_tcxo_get_count();
    osal_printk("test case delay count %lu.\r\n", count_before_delay_count - count_after_delay_count);
    return;
}

注意事项

无。

Flash

概述

Flash是一种非易失快闪记忆体技术,又称为闪存,通常支持SPI协议。Flash可以通过SPI协议实现读、写和擦除等多种命令。

功能描述

Flash模块提供的接口及功能如下如下:

  • uapi_flash_init(flash_id_t id):初始化Flash(id表示flash id)。

  • uapi_flash_deinit(flash_id_t id):去初始化Flash(id表示flash id)。

  • uapi_flash_write_data(flash_id_t id, uint32_t addr, const uint8_t *src, uint32_t length):写入数据到Flash(id表示flash id,addr表示flash写入地址,src表示数据地址,length表示数据长度)。

  • uapi_flash_write_data_without_align(flash_id_t id, uint32_t addr, const uint8_t *src, uint32_t length):以非对齐地址写入数据到Flash(id表示flash id,addr表示flash写入地址,src表示数据地址,length表示数据长度)。

  • uapi_flash_read_data(flash_id_t id, uint32_t addr, uint8_t *dst, uint32_t length):从Flash读取数据(id表示flash id,addr表示flash读入地址,dst表示数据地址,length表示数据长度)。

  • uapi_flash_read_data_without_align(flash_id_t id, uint32_t addr, uint8_t *dst, uint32_t length):以非对齐地址从Flash读取数据(id表示flash id,addr表示flash读入地址,dst表示数据地址,length表示数据长度)。

  • uapi_flash_chip_erase(flash_id_t id):擦除整片Flash数据(id表示flash id)。

  • uapi_flash_block_erase(flash_id_t id, uint32_t addr, uint32_t length, bool is_wait):按块擦除Flash数据(id表示flash id,addr表示flash擦除地址,length表示擦除长度,is_wait表示是否等待擦除完成)。

  • uapi_flash_sector_erase(flash_id_t id, uint32_t addr, bool is_wait):按扇区擦除Flash数据(id表示flash id,addr表示flash擦除地址,is_wait表示是否等待擦除完成)。

  • uapi_flash_suspend(flash_id_t id):Flash休眠(id表示flash id)。

  • uapi_flash_resume(flash_id_t id):Flash休眠唤醒(id表示flash id)。

  • uapi_flash_switch_to_deeppower(flash_id_t id):Flash进入掉电模式(id表示flash id)。

  • uapi_flash_resume_from_deeppower(flash_id_t id):Flash掉电模式唤醒(id表示flash id)。

  • uapi_flash_read_security_status(flash_id_t id, uint8_t *read_data):读取Flash安全寄存器状态(id表示flash id,read_data表示读取的数据)。

  • uapi_flash_send_cmd_exe(flash_id_t id, flash_cmd_exe_t *cmd_exe):发送命令到Flash(id表示flash id,cmd_exe表示flash命令)。

  • uapi_flash_get_info(flash_id_t id, flash_info_t *flash_info):获取Flash当前信息(id表示flash id,flash_info表示flash信息)。

  • uapi_flash_reset(flash_id_t id):Flash复位(id表示flash id)。

  • uapi_flash_is_processing(flash_id_t id):Flash是否正在工作(id表示flash id)。

  • uapi_flash_read_id(flash_id_t id, uint32_t *manufacture_id):Flash读取制造ID(id表示flash id,manufacture_id表示flash制造id)。

  • uapi_flash_read_unique_id(flash_id_t id, uint8_t *unique_id:Flash读取唯一ID(id表示flash id,manufacture_id表示flash唯一id)。

  • uapi_flash_read_device_id(flash_id_t id, uint16_t *device_id):Flash读取设备ID(id表示flash id,device_id表示flash设备id)。

  • uapi_flash_read_status(flash_id_t id, uint8_t *status):读Flash状态等待空闲(id表示flash id,status表示flash状态)。

  • uapi_flash_save_manufacture(flash_id_t id, uint32_t manufacture_id):保存Flash制造ID(id表示flash id,manufacture_id表示flash制造id)。

  • uapi_flash_set_spi_baud(flash_id_t id, uint32_t bus_clk, uint32_t freq_mhz):设置SPI波特率(id表示flash id,bus_clk表示总线时钟,freq_mhz表示SPI的工作时钟频率)。

KCONFIG配置

配置宏具体描述如表1所示。

表 1 FLASH相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_FLASH_SUPPORT_XIP

Flash support xip mode.

Flash支持XIP模式,本产品暂未使用

默认关闭,本产品实际暂不使用,建议关闭

CONFIG_SPI_WAIT_FIFO_LONG_TIMEOUT

Max time of SPI waiting.

SPI等待最大超时时间

默认值是0xF,建议不做修改

CONFIG_SPI_TRAN_MAX_TIMEOUT

Max time of SPI transfer.

SPI传输最大超时时间

默认值是0xF,建议不做修改

CONFIG_SPI_WAIT_READ_TIMEOUT

Max time of SPI reading.

SPI读取数据最大超时时间

默认值是0xF,建议不做修改

CONFIG_FLASH_POWER_ON_TIMEOUT

Max time of power on.

Flash上电最大超时时间

默认值是0xFF,建议不做修改

CONFIG_SPI_RX_FIFO_THRESHOLD

FIFO threshold of SPI RX.

SPI RX FIFO水线

默认值是0x8,建议不做修改

CONFIG_SPI_TX_FIFO_THRESHOLD

FIFO threshold of SPI TX.

SPI TX FIFO水线

默认值是0x8,建议不做修改

CONFIG_FLASH_ALREADY_START

Flash has been started in another binary.

该宏表示Flash已经开启,无需再重复配置,本产品暂未使用

默认关闭,本产品实际暂未使用,建议关闭

CONFIG_FLASH_SUPPORT_LPC

Flash support low power control, control power and clock.

该宏仅用于低功耗情况下使用

默认关闭,视情况选择,建议关闭

CONFIG_FLASH_SUPPORT_SPI_SINGLE_LINE_MODE

Flash support spi single line mode.

Flash支持使用SPI单线模式,本产品暂未使用

默认打开,本产品实际暂未使用,建议关闭

CONFIG_FLASH_USE_SPI_SINGLE_LINE_MODE_ONLY

Flash use spi single-line mode only.

Flash仅支持使用SPI单线模式

默认关闭,视情况选择

CONFIG_FLASH_USE_CUSTOMIZED_DEVICE_INFO

Flash use custom device information, which is defined in the porting.

Flash使用自定义设备信息

默认关闭,视情况选择

开发指引

外部Flash开发适配需由客户自己选型和适配,部分参考代码见文件:drivers/chips/bs2x/porting/flash/flash_porting.c和drivers/chips/bs2x/main_init/main_init.c

/* drivers/chips/bs2x/porting/flash/flash_porting.c */
static flash_cfg_t g_flash_porting_config[FLASH_MAX] = {
    {
        .isinit = 0,
        .flash_manufacturer = FLASH_MANUFACTURER_MAX,
        .unique_id = 0,
        .bus = SPI_BUS_1,
        .mode = HAL_SPI_FRAME_FORMAT_STANDARD,
        .is_xip = false,
        .attr = {
            .is_slave = false,
            .slave_num = 1,
            .bus_clk = SPI_CLK_FREQ,
            .freq_mhz = 4,
            .clk_polarity = (uint32_t)SPI_CFG_CLK_CPOL_0,
            .clk_phase = (uint32_t)SPI_CFG_CLK_CPHA_0,
            .frame_format = SPI_CFG_FRAME_FORMAT_MOTOROLA_SPI,
            .spi_frame_format = (uint32_t)HAL_SPI_FRAME_FORMAT_STANDARD,
            .frame_size = (uint32_t)HAL_SPI_FRAME_SIZE_8,
            .sste = SPI_CFG_SSTE_DISABLE,
        },
        .extra_attr = {
            .tx_use_dma = false,
            .rx_use_dma = false,
            .qspi_param = { 0 },
        },
    },
};

/* drivers/chips/bs2x/main_init/main_init.c */
static void ota_extern_flash_init(void)
{
#if defined(SUPPORT_EXTERN_FLASH)
    flash_porting_pinmux_cfg(0);
    uint32_t manufacture_id = 0;
    uapi_flash_init(0);
    uapi_flash_read_id(0, &manufacture_id);
    flash_save_manufacturer(0, manufacture_id);
#endif
}

新选型的外部Flash适配,需要在drivers/chips/bs2x/porting/flash/flash_config_info_porting.h中维护全局变量g_flash_device_parameter,该全局变量的结构体为flash_device_parameter_t:

typedef struct flash_device_parameter {
    uint32_t manufacturer_id;                               /*!< @if Eng Flash manufacturer id.
    @else Flash 制造id。@endif */
    uint32_t flash_size;                                    /*!< @if Eng Flash size.
    @else Flash 大小。@endif */
    uint8_t security_reg_cmd;                               /*!< @if Eng Security reg cmd.
    @else 安全寄存器命令。@endif */
    uint8_t suspend_cmd;                                    /*!< @if Eng Suspend cmd.
    @else 休眠命令。@endif */
    uint8_t resume_cmd;                                     /*!< @if Eng Resume cmd.
    @else 唤醒命令。@endif *
}
  • manufacturer_id:在单线模式下即可读取,发送0x9F读取id,通过手册中查找,对比两种方式得到的结果是否一致。下面列举一些常见厂商的manufacturer_id位置 。

    uint32_t manufacture_id;
    uapi_flash_init(FLASH_0);
    uapi_flash_read_id(FLASH_0, &manufacture_id);
    
  • flash_size:Flash总空间通常在第一章介绍

  • security_reg_cmd:读取Flash状态寄存器的指令,查找手册中的Read Security Registers,可在Commands Table中查到。

  • suspend_cmd和resume_cmd:支持休眠的Flash,手册中搜索suspend和resume,可在Commands Table中查到。

    drivers/chips/bs2x/porting/flash/flash_config_info_porting.h中还维护了一个全局变量g_flash_need_set_qe,该全局变量保存了使能四线访问前需要开启QE Bit的FLash的manufacturer id,以及开启QE Bit的操作,是否需要开启QE Bit,可以在手册中搜索Quad Enable (QE)。

注意事项

  • 不支持XIP模式。

  • 对Flash进行读写擦操作时,请确保Flash已完成初始化。

  • 需客户基于Flash选型自行适配。

SFC

概述

SFC(SPI Flash Controller)是一个AHB SPI Flash控制器,可通过对SFC的映射地址区间以AHB Slave访问来操作SFC模块挂载的Flash。其同样支持以APB Slave方式以类似传统SPI的形式访问挂载的Flash。

功能描述

  • uapi_sfc_init(sfc_flash_config_t *config): 初始化SFC模块,配置参数包括对映射地址空间和读写形式的配置(config表示初始化参数)。

  • uapi_sfc_init_rom(sfc_flash_config_t *config): 以兼容模式初始化SFC模块,此接口下仅mapping_addr参数生效,读写形式均以Flash通用的读(0x03)写(0x02)指令进行。在uapi_sfc_init返回值为 ERRCODE_SFC_FLASH_NOT_SUPPORT(0x80001341)时也会套用该配置(config表示初始化参数)。

  • uapi_sfc_deinit(void): 对SFC驱动进行去初始化。

  • uapi_sfc_reg_read(uint32_t flash_addr, uint8_t *read_buffer, uint32_t read_size): 提供寄存器模式读功能,读取的数据将按字节存入read_buffer中。不允许在中断中调用(flash_addr表示数据所在的Flash首地址,read_buffer表示用于接收数据的buffer,read_size表示读取的字节数)。

  • uapi_sfc_reg_write(uint32_t flash_addr, uint8_t *write_data, uint32_t write_size): 提供寄存器模式写功能,预计写入的数据按字节存入write_data中。不允许在中断中调用(flash_addr表示目标Flash首地址,write_data表示预计写入的数据,write_size表示写入数据的字节数)。

  • uapi_sfc_reg_erase(uint32_t flash_addr, uint32_t erase_size): 使用寄存器模式进行对Flash的擦除,不使能写回时强制要求地址和大小按扇区对齐。不允许在中断中调用(flash_addr表示擦除的首地址,erase_size表示擦除的Flash空间的大小)。

  • uapi_sfc_reg_erase_chip(void): 使用寄存器模式对整片Flash进行擦除。不允许在中断中调用。

  • uapi_sfc_regs_other_flash_opt(sfc_flash_op_t cmd_type, uint8_t cmd, uint8_t *buffer, uint32_t length): 使用寄存器模式对Flash属性进行读写。不允许在中断中调用(cmd_type表示设置指令的读写类型,cmd表示SPI指令,buffer表示数据缓冲区,length表示需要读/写的数据长度,其值需要小于4)。

  • uapi_sfc_lock_protect(sfc_flash_protect_region_t region): 使能SFC写保护(region表示Flash保护的区域)。(打开CONFIG_SFC_SUPPORT_WRITE_PROTECT宏才能使用)

  • uapi_sfc_unlock_protect(void): 封装接口,关闭Flash写保护。(打开CONFIG_SFC_SUPPORT_WRITE_PROTECT宏才能使用)

  • uapi_sfc_dma_write(uint32_t flash_addr, uint8_t *write_buffer, uint32_t write_size):提供寄存器模式写功能,预计写入的数据按字节存入write_data中。不允许在中断中调用(flash_addr表示目标Flash首地址,write_buffer表示预计写入的数据,write_size表示写入数据的字节数)。(打开CONFIG_SFC_SUPPORT_DMA宏才能使用)

  • uapi_sfc_dma_read(uint32_t flash_addr, uint8_t *read_buffer, uint32_t read_size):提供DMA模式读功能,读取的数据将按字节存入read_buffer中。不允许在中断中调用(flash_addr表示数据所在的Flash首地址,read_buffer表示用于接收数据的buffer,read_size表示读取的字节数)。(打开CONFIG_SFC_SUPPORT_DMA宏才能使用)

  • uapi_sfc_suspend(uintptr_t arg):挂起SFC模块,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_SFC_SUPPORT_LPM宏才能使用)

  • uapi_sfc_resume(uintptr_t arg):恢复SFC模块,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_SFC_SUPPORT_LPM宏才能使用)

KCONFIG配置

配置宏具体描述如表1所示。

表 1 SFC相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_SFC_SUPPORT_DMA

SFC support DMA.

SFC支持DMA模式,本产品的SFC规格不支持APB_MASTER DMA访问,故在此定义下的uapi_sfc_dma_xxx接口不可用

默认关闭,本产品实际暂不支持,建议关闭

CONFIG_SFC_ALLOW_ERASE_WRITEBACK

Allows Erase at Any Address, needs 4KB RAM at least.

擦除接口内部自动回写,需要8K的DTCM空间充当全局变量,本产品的规格不建议开启

默认关闭,视情况选择,建议关闭

CONFIG_SFC_ALREADY_INIT

SFC has been inited, need not to set reg in init/deinit func.

该宏代表SFC已经初始化,不做重复操作

默认打开,视情况选择,建议打开

CONFIG_SFC_SUPPORT_LPM

SFC support low power mode, supend and reusme.

该宏仅用于低功耗情况下使用,支持SFC低功耗现场保存恢复,SDK内部低功耗流程默认已经实现,调试低功耗请务必打开此配置项

默认打开,视情况选择,建议打开

CONFIG_SFC_SUPPORT_DATA_CACHE

SFC support data cache.

SFC支持数据缓存

默认关闭,视情况选择,建议关闭

CONFIG_SFC_SUPPORT_RWE_INDEPENDENT

Independent sfc read/write erase interface.

支持独立的sfc读/写擦除接口

默认关闭,视情况选择,建议关闭

CONFIG_SFC_SUPPORT_WRITE_PROTECT

SFC support write protect.

开启SDK内部封装的写保护接口

默认打开,视情况选择,建议打开

CONFIG_SFC_USE_CUSTOMIZED_DEVICE_INFO

SFC use custom device information, which is defined in the porting.

打开即使用porting层进行适配的自定义配置组,关闭该宏则使用SDK内部include/driver/sfc/sfc_config_info.h中默认适配的配置项。合封Flash已于SDK中自定义配置组中适配,打开该宏即可

默认打开,视情况选择,建议打开

CONFIG_SFC_DEBUG

SFC support DEBUG.

SFC支持debug调试

默认关闭,视情况选择,建议关闭

  • SFC_SUPPORT_DMA:SFC规格不支持APB_MASTER DMA访问,故在此定义下的uapi_sfc_dma_xxx接口不可用。

  • SFC_ALLOW_ERASE_WRITEBACK:擦除接口内部自动回写,需要8K的DTCM空间充当全局变量,本产品的规格不建议开启。

  • SFC_SUPPORT_LPM:支持SFC低功耗现场保存恢复,SDK内部低功耗流程默认已经实现,调试低功耗请务必打开此配置项。

  • SFC_SUPPORT_WRITE_PROTECT:开启SDK内部封装的写保护接口。

  • SFC_USE_CUSTOMIZED_DEVICE_INFO:打开即使用porting层进行适配的自定义配置组,关闭该宏则使用SDK内部include/driver/sfc/sfc_config_info.h中默认适配的配置项。合封Flash已于SDK中自定义配置组中适配,打开该宏即可。

开发指引

SDK默认会对SFC驱动进行初始化,使用APB Slave接口时无需初始化,示例代码如下:

#include "soc_osal.h"
#include "securec.h"
#include "sfc.h"
#include "sfc_porting.h"
#include "app_init.h"
#include "memory_config_common.h"

#define SFC_TASK_PRIO                   24
#define SFC_TASK_STACK_SIZE             0x1000
#define SFC_SAMPLE_LEN                  0x80000
#define SFC_PRINT_BUFF_LEN              32
uint8_t g_print_data_buff[SFC_PRINT_BUFF_LEN] = {0};
uint8_t g_write_data_buff[SFC_PRINT_BUFF_LEN] = {0};

static void sfc_sample_start_api_test(void)
{
    osal_printk("API test start\r\n");
    uint32_t remained_len = CONFIG_SFC_SAMPLE_USER_SIZE;
    uint32_t start_addr = CONFIG_SFC_SAMPLE_USER_ADDR;
    while (remained_len > 0) {
        uint32_t cur_len = remained_len > SFC_PRINT_BUFF_LEN ? SFC_PRINT_BUFF_LEN : remained_len;
        uapi_sfc_reg_read(start_addr, g_print_data_buff, cur_len);
        for (uint8_t i = 0; i < cur_len; i++) {
            osal_printk("%02x ", g_print_data_buff[i]);
        }
        uapi_sfc_reg_write(start_addr, g_write_data_buff, cur_len);
        start_addr += cur_len;
        remained_len -= cur_len;
        osal_printk("\r\n");
    }
    start_addr = CONFIG_SFC_SAMPLE_USER_ADDR;
    remained_len = CONFIG_SFC_SAMPLE_USER_SIZE;
    while (remained_len > 0) {
        uint32_t cur_len = remained_len > SFC_PRINT_BUFF_LEN ? SFC_PRINT_BUFF_LEN : remained_len;
        uapi_sfc_reg_read(start_addr, g_print_data_buff, cur_len);
        for (uint8_t i = 0; i < cur_len; i++) {
            osal_printk("%02x ", g_print_data_buff[i]);
        }
        start_addr += cur_len;
        remained_len -= cur_len;
        osal_printk("\r\n");
    }
}

static void *sfc_task(const char *arg)
{
    unused(arg);
    for (uint8_t i = 0; i < SFC_PRINT_BUFF_LEN; i++) {
        g_write_data_buff[i] = i;
    }
    /* Erase User space */
    osal_printk("Erasing for API sample...\r\n");
    errcode_t ret = uapi_sfc_reg_erase(CONFIG_SFC_SAMPLE_USER_ADDR, CONFIG_SFC_SAMPLE_USER_SIZE);
    if (ret != ERRCODE_SUCC) {
        osal_printk("flash erase failed! ret = %x\r\n", ret);
        return NULL;
    }
    osal_printk("Start API read sample...\r\n");
    sfc_sample_start_api_test();
    return NULL;
}

static void sfc_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)sfc_task, 0, "SFCTask", SFC_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, SFC_TASK_PRIO);
        osal_kfree(task_handle);
    }
    osal_kthread_unlock();
}

注意事项

  • SFC相关的代码请务必放在ITCM上,本产品的SFC驱动是保持XIP执行链路的唯一手段。

  • XIP执行时,取指性能将降低10~60倍(取决于Flash性能及老化程度),故有性能要求的驱动务必不要放到Flash上使用XIP执行。

  • 驱动提供的uapi形式的APB slave接口,传入的地址为Flash真实地址而非映射地址。

  • 根据芯片限制,严禁使用直接访问映射地址的形式写合封Flash。

QDEC

概述

QDEC(Quadrature Decoder)为正交编码器,提供正交编码传感器信号的缓冲解码。编码器内有2个监测点,相位相差90°,这意味着一个监测点的电平总是在另一个监测点之前改变。滚轮拨动一格,2个监测点将分别检测到一个脉冲。当正交编码器启动时,解码器对两种输入波形进行连续采样,并将当前采样(n)与前一采样(n-1)进行比较解码,比较结果如表1所示。

表 1 QDEC输入波形采样结果对比

前一采样(n-1)

当前采样(n)

采样寄存器

累加寄存器

双滚寄存器

描述

A

B

A

B

-

-

-

-

0

0

0

0

0

No change

No change

No movement

0

0

0

1

1

Increment

No change

Movement in positive direction

0

0

1

0

-1

Decrement

No change

Movement in negative direction

0

0

1

1

2

No change

Increment

Error: Double transition

0

1

0

0

-1

Decrement

No change

Movement in negative direction

0

1

0

1

0

No change

No change

No movement

0

1

1

0

2

No change

Increment

Error: Double transition

0

1

1

1

1

Increment

No change

Movement in positive direction

1

0

0

0

1

Increment

No change

Movement in positive direction

1

0

0

1

2

No change

Increment

Error: Double transition

1

0

1

0

0

No change

No change

No movement

1

0

1

1

-1

Decrement

No change

Movement in negative direction

1

1

0

0

2

No change

Increment

Error: Double transition

1

1

0

1

-1

Decrement

No change

Movement in negative direction

1

1

1

0

1

Increment

No change

Movement in positive direction

1

1

1

1

0

No change

No change

No movement

功能描述

QDEC模块提供的接口及功能如下:

  • uapi_qdec_init(qdec_config_t const *config):初始化QDEC模块(config表示初始化配置参数)。

  • uapi_qdec_deinit(void):去初始化QDEC。

  • uapi_qdec_enable(void):使能QDEC。

  • uapi_qdec_disable(void):去使能QDEC。

  • uapi_qdec_read_accumulators(int16_t *acc, int16_t *accdbl):读取QDEC累加寄存器(acc表示注册累计有效转换的次数,accdbl表示注册累计检测到重复转换的次数)。

  • uapi_qdec_register_callback(qdec_callback_t callback):注册QDEC中断回调函数(callback表示QDEC中断回调函数)。

  • qdec_port_set_acc_per_roll(uint8_t acc_per_roll):配置滚轮滚动一次时,累加器增加的值,适用于较为特殊的光栅传感器。

开发指引

示例:

  1. 首先调用qdec_port_pinmux_init接口,将IO复用为QDEC功能。

  2. 其次调用uapi_qdec_init接口,对QDEC做初始化,初始化时可配的参数如结构体所示。

    uapi_qdec_init入参结构体:

    typedef struct {
        hal_qdec_report_per_t reportper;            /*!< @if Eng Report period in samples.
                                                         @else   样本报告周期 @endif */
        hal_qdec_sample_per_t sampleper;            /*!< @if Eng Sampling period in microseconds.
                                                         @else   采样周期(微秒级) @endif */
        hal_qdec_defen_num_t  defen_num;            /*!< @if Eng Debounce num.
                                                         @else   去抖周期 @endif */
        uint32_t              psela;                /*!< @if Eng Pin number for A input.
                                                         @else   A输入的管脚编号 @endif */
        uint32_t              pselb;                /*!< @if Eng Pin number for B input.
                                                         @else   B输入的管脚编号 @endif */
        uint32_t              pselled;              /*!< @if Eng Pin number for LED output.
                                                         @else   LED输出的管脚编号 @endif */
        uint32_t              ledpre;               /*!< @if Eng Time of LED is switched on before sampling in microseconds
                                                         @else   采样前LED打开的时间(微秒级) @endif */
        hal_qdec_led_pol_t    ledpol;               /*!< @if Eng Active LED polarity.
                                                         @else   有源LED极性 @endif */
        bool                  defence;              /*!< @if Eng Switch of debouncing filter.
                                                         @else   去抖滤波器功能开关 @endif */
        bool                  sample_inten;         /*!< @if Eng Enabling sample ready interrupt.
                                                         @else   使能样本就绪中断 @endif */
        uint8_t               interrupt_priority;   /*!< @if Eng QDEC interrupt priority.
                                                         @else   QDEC中断优先级 @endif */
    } qdec_config_t;
    #define QDEC_DEFAULT_CONFIG                                              \
    {                                                                        \
        .reportper           = QDEC_REPORTPER_1,                             \
        .sampleper           = QDEC_SAMPLEPER_512US,                         \
        .defen_num           = QDEC_DEBOUNCE_NUM_2,                          \
        .psela               = QDEC_CONFIG_PIO_A,                            \
        .pselb               = QDEC_CONFIG_PIO_B,                            \
        .pselled             = QDEC_CONFIG_PIO_LED,                          \
        .ledpre              = 256,                                          \
        .ledpol              = QDEC_LED_POL_ACTIVE_HIGH,                     \
        .defence             = true,                                         \
        .sample_inten        = false,                                        \
        .interrupt_priority  = IRQ_QDEC,                                     \
    }
    
  3. 调用uapi_qdec_register_callback以及uapi_qdec_enable接口,注册QDEC中断触发时的回调函数并使能QDEC。

    static qdec_config_t g_qdec_config = QDEC_DEFAULT_CONFIG;
    static int32_t qdec_report_callback(int argc, char *argv[])
    {
        UNUSED(argv);
        g_qdec_count += argc;
        printf("curren count is: %d. \r\n", g_qdec_count);
        return ERRCODE_SUCC;
    }
    int32_t test_qdec_open(void)
    {
        uapi_qdec_init(&g_qdec_config);
        uapi_qdec_register_callback(qdec_report_callback);
        uapi_qdec_enable();
        return TEST_SUITE_OK;
    }
    

注意事项

如果接地错误,很容易出现双滚错误或误报滚动。首次初始化时管脚可能产生电平变化导致上报一次双滚错误,该错误可忽略。

KEYSCAN

概述

KEYSCAN为键盘扫描模块,支持行扫描和列扫描模式,最大支持16×8矩阵键盘,未使用的引脚可以通过配置寄存器关闭,支持芯片算法防鬼键,支持IO端口可旁路的防抖设计。

功能描述

KEYSCAN模块提供的接口及功能如下:

  • uapi_keyscan_init(keyscan_pulse_time_t time, keyscan_mode_t mode, keyscan_int_t event_type):KEYSCAN初始化(time表示Keyscan配置扫描时长,mode表示 Keyscan配置扫描模式,event_type表示Keyscan配置中断类型)。

  • uapi_keyscan_deinit(void):KEYSCAN去初始化。

  • uapi_keyscan_enable(void):KEYSCAN使能。

  • uapi_keyscan_disable(void):KEYSCAN去使能。

  • uapi_keyscan_register_callback(keyscan_report_callback_t callback):KEYSCAN回调注册(callback表示Keyscan的回调函数)。

  • uapi_keyscan_unregister_callback(keyscan_report_callback_t callback):KEYSCAN回调去注册(callback表示Keyscan的回调函数)。

  • uapi_set_keyscan_value_map(uint8_t **map_addr, uint16_t row, uint8_t col):KEYSCAN键值map注册(map_addr表示键值矩阵地址,row表示键值矩阵的行数,col表示键值矩阵的列数)。

  • uapi_keyscan_suspend(uintptr_t arg):挂起所有Keyscan通道,低功耗情况使用(arg表示挂起时所需要的参数)。(打开CONFIG_KEYSCAN_SUPPORT_LPM宏才能使用)

  • uapi_keyscan_resume(uintptr_t arg):恢复所有Keyscan通道,低功耗情况使用(arg表示恢复时所需要的参数)。(打开CONFIG_KEYSCAN_SUPPORT_LPM宏才能使用)

KCONFIG配置

说明: 注意:若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 KEYSCAN相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_KEYSCAN_REPORT_MAX_NUMS

The maximum number of key combinations that can be reported at one time, support 6/7/8/10.

KEYSCAN最多同时上报的按键数,默认值是6

默认值是6,视情况选择配置,建议不做修改

CONFIG_KEYSCAN_SUPPORT_LPM

KEYSCAN support low power mode, suspend and resume.

该宏仅用于低功耗情况下使用,视情况选择

默认打开,视情况选择,建议打开

CONFIG_KEYSCAN_SUPPORT_SLEEP

KEYSCAN support low power mode, enter sleep.

该宏仅用于KEYSCAN低功耗进入睡眠的情况

默认打开,视情况选择,建议打开

CONFIG_KEYSCAN_IDLE_WAIT_US

KEYSCAN set a timer to enter sleep.

设置KEYSCAN进入睡眠的时间,默认值是60000000us

默认值是60000000,视情况选择配置,建议不做修改

CONFIG_KEYSCAN_SUPPORT_SW_DEFENCE

KEYSCAN avoids the tiny jitter.

该宏用于KEYSCAN滤抖场景,过滤毛刺抖动

默认关闭,视情况选择

CONFIG_KEYSCAN_ANOTHER_COL

This pulled down column makes all rows continuously scanning if the software defence function is open.

改宏用于配置滤抖功能开启时的冗余列管脚,默认值是0

CONFIG_KEYSCAN_SUPPORT_SW_DEFENCE

默认关闭,默认值为0,是请款选择

CONFIG_KEYSCAN_USE_FULL_KEYS_TYPE

KEYSCAN use full keys type.

KEYSCAN支持全键模式

默认打开,视情况选择,建议打开

CONFIG_KEYSCAN_USE_SIX_KEYS_TYPE

KEYSCAN use six keys type.

KEYSCAN支持六键模式

默认关闭,视情况选择

CONFIG_KEYSCAN_USER_CONFIG_TYPE

KEYSCAN user config type.

KEYSCAN支持自定义按键模式

默认关闭,视情况选择

CONFIG_KEYSCAN_ENABLE_COL

The number of columns to start.

KEYSCAN列数配置

默认全键模式的列数,值为8,如果配置成其他键盘模式,需自行调整

CONFIG_KEYSCAN_ENABLE_ROW

The number of rows to start.

KEYSCAN行数配置

默认全键模式的行数,值为16,如果配置成其他键盘模式,需自行调整

CONFIG_KEYSCAN_USE_LP

KEYSCAN V150 uses key_int_lp register when it's going to sleep mode.

该宏仅在睡眠场景下使用

CONFIG_KEYSCAN_SUPPORT_SLEEP

默认关闭,建议关闭

CONFIG_KEYSCAN_ENABLE_REP_ALL_KEY

KEYSCAN V150 report all keys reported.

KEYSCAN全键无冲功能开关,开启后MaxNum配置无作用

默认关闭,视情况选择,建议关闭

CONFIG_KEYSCAN_SOFTWARE_GHOST_DETECT

KEYSCAN use software to detect ghost key

KEYSCAN软件防鬼开关,不能与硬件防鬼同时打开

默认关闭,视情况选择

开发指引

可配置项主要在KEYSCAN对应的porting层头文件中,下文将介绍重要可配置项的功能。

#define KEYSCAN_DEFAULT_CONFIG                       \
{                                                    \
.direction             = ROW_SCAN_DIR,               \    行列扫描方向
.scan_mode             = HAL_KEYSCAN_MODE_0,         \    行列扫描模式
.pulse_time            = EVERY_ROW_PULSE_10_US,      \   扫描脉冲时长
.wait_time             = WAITING_30_AND_5_US,        \  扫描持续时长
.idle_time             = IDLE_3_MS_AFTER_SCAN,       \  多次扫描间等待时长
.defence_time          = SAMPLE_3TIMES_DEFENCE,      \   防抖次数
.ghost_check           = false,                      \硬件防鬼开关
.io_de                = true,                      \
}
//  keyscan两级分频器的分频比配置,分频系数为CLK_HIGH_DIV*CLK_LOW_DIV,默认配置为
#define CLK_HIGH_DIV 0x28
#define CLK_LOW_DIV  0xa
//  如需降低分频比以优化keyscan时延,推荐配置为8M,即
#define CLK_HIGH_DIV 0x2
#define CLK_LOW_DIV  0x2

示例:

  1. 如使用自定义模式,首先调用keyscan_porting_set_gpio接口确认要使用的goio,先写row,再写column。

    #if defined(CONFIG_KEYSCAN_USER_CONFIG_TYPE)
        uint8_t user_gpio_map[CONFIG_KEYSCAN_ENABLE_ROW + CONFIG_KEYSCAN_ENABLE_COL] = { 31, 24, 14, 23, 27, 28, 10, 11, 30, 13, 15, 16, 25, 26, 12, 22, 2, 3, 4, 5, 6, 21, 9, 29};
    if (keyscan_porting_set_gpio(user_gpio_map)) {
    return;
    }
    #endif
    
  2. 调用uapi_set_keyscan_value_map接口进行键值map注册,键值map数组的行列需要和行列最大值一致,不使用的按键填充为0x00即可。

    uint8_t g_key_map_test[KEY_SCAN_MAX_ROW][KEY_SCAN_MAX_COL] = {
        { 0x29, 0x2B, 0x14, 0x35, 0x04, 0x1E, 0x1D, 0x00 },
        { 0x3D, 0x3C, 0x08, 0x3B, 0x07, 0x20, 0x06, 0x00 },
        { 0x00, 0x39, 0x1A, 0x3A, 0x16, 0x1F, 0x1B, 0x00 },
        { 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE4, 0x00 },
        { 0x0A, 0x17, 0x15, 0x22, 0x09, 0x21, 0x19, 0x05 },
        { 0x0B, 0x1C, 0x18, 0x23, 0x0D, 0x24, 0x10, 0x11 },
        { 0x3F, 0x30, 0x0C, 0x2E, 0x0E, 0x25, 0x36, 0x00 },
        { 0x00, 0x00, 0x12, 0x40, 0x0F, 0x26, 0x37, 0x00 },
        { 0x34, 0x2F, 0x13, 0x2D, 0x33, 0x27, 0x00, 0x38 },
        { 0x3E, 0x2A, 0x00, 0x41, 0x31, 0x42, 0x28, 0x2C },
        { 0x00, 0x00, 0xE3, 0x00, 0x00, 0x43, 0x00, 0x51 },
        { 0xE2, 0x00, 0x00, 0x00, 0x00, 0x45, 0xE5, 0xE6 },
        { 0x00, 0x53, 0x00, 0x00, 0xE1, 0x44, 0x00, 0x4F },
        { 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x50 },
        { 0x5F, 0x5C, 0x61, 0x5E, 0x59, 0x62, 0x55, 0x5B },
        { 0x54, 0x60, 0x56, 0x57, 0x5D, 0x5A, 0x58, 0x63 },
    };
    uapi_set_keyscan_value_map((uint8_t **)g_key_map_test, KEY_SCAN_MAX_ROW, KEY_SCAN_MAX_COL);
    
  3. 调用uapi_keyscan_init接口,初始化KEYSCAN,当前入参为脉冲时间(param1)、扫描模式(param2)、中断时间(param3),默认值为0、0、3。

    uapi_keyscan_init(pulse, mode, scan_int);
    
  4. 调用uapi_keyscan_register_callback以及uapi_keyscan_enable接口,注册KEYSCAN中断回调函数并使能KEYSCAN。

    int test_key_scan_funcs(void)
    {
        errcode_t ret = ERRCODE_FAIL;
        ret = uapi_keyscan_register_callback(key_scan_report_callback);
        if (ret != ERRCODE_SUCC) {
            return TEST_SUITE_TEST_FAILED;
        }
        uapi_keyscan_enable();
        return TEST_SUITE_OK;
    }
    

全键无冲模式

开启CONFIG_KEYSCAN_ENABLE_REP_ALL_KEY后,中断上报将换为keyscan_porting_all_keys_report,会上报同时按下的所有按键,长按时会每个扫描周期上报一次,用户需根据扫描频率调整中断响应的阈值。

防抖模式

开启CONFIG_KEYSCAN_SUPPORT_SW_DEFENCE宏后,将一个空闲的管脚配置到CONFIG_KEYSCAN_ANOTHER_COL,并将列管脚的个数+1。keyscan功能执行时便不会出现微小抖动下的按键误报。由于按键防抖功能开启后会占用一个gpio管脚,因此CONFIG_KEYSCAN_SUPPORT_SW_DEFENCE宏默认关闭,需要用户根据情况进行配置。

软件防鬼模式

BS2X提供了软件防鬼键功能,开启CONFIG_KEYSCAN_SOFTWARE_GHOST_DETECT后,Keyscan的回调函数里会添加keyscan_sw_ghost_detect,将形成鬼键的第三个按键和鬼键上报的第四个按键从上报的buffer中删除,只上报最初按下的两个键,与此同时新按下的其余按键也不会继续上报,以此告知用户出现了鬼键现象。软件防鬼功能不能与硬件防鬼同时打开。

注意事项

KEYSCAN使用时需要做好接地,否则会出现键值误报的问题。

PDM

概述

PDM(Pulse Density Modulation)是一种用数字信号表示模拟信号的调制方法。而另一种常用的将模拟量转换为数字量的方法PCM则使用等间隔采样方法,将每次采样的模拟分量幅度表示为N位的数字分量(N = 量化深度),因此PCM方式每次采样的结果都是N bit字长的数据。PDM使用远高于PCM采样率的时钟采样调制模拟分量,只有1位输出时,为0或1。因此通过PDM方式表示的数字音频也被称为Oversampled 1-bit Audio。

282X搭载的音频数模转换模块采用PDM方法进行采样,处理后输出PCM数据。数据来源可以为DMIC或AMIC。

功能描述

PDM模块提供的接口及功能如下:

  • uapi_pdm_init(void):初始化PDM。

  • uapi_pdm_deinit(void):去初始化PDM。

  • uapi_pdm_set_attr(uint8_t mic, const pdm_config_t *attr):设置PDM通道的属性(mic表示需要配置的通道,attr表示配置参数)。

  • uapi_pdm_get_attr(uint8_t mic, pdm_config_t *attr):获取PDM通道的属性(mic表示需要获取的通道,attr表示用于存储获取的属性参数)。

  • uapi_pdm_start(void):启动PDM采样。

  • uapi_pdm_stop(void):停止PDM采样。

  • uapi_pdm_get_fifo_addr(void):获取PDM的FIFO基地址。

  • uapi_pdm_get_fifo_deepth(void):获取PDM的FIFO深度。

KCONFIG配置

配置宏具体描述如表1所示。

表 1 PDM相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_PDM_SUPPORT_LPC

PDM support low power control, control power and clock.

PDM 工作时钟

默认打开,必选

CONFIG_PDM_SUPPORT_QUERY_REGS

PDM support query regs values.

PDM支持回读所有相关寄存器的值,视情况选择

默认关闭,视情况选择,建议关闭

CONFIG_PDM_USING_V150

Using PDM V150.

该宏代表使用PDM V150的驱动规格,本产品默认使用该规格

默认打开,必选

CONFIG_PDM_USING_V151

Using PDM V151.

该宏代表使用PDM V151的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_MIC_CH_NUM

Max number of the MIC.

PDM支持的MIC通道数

CONFIG_PDM_USING_V150

默认值是2,视情况选择配置,建议不做修改

CONFIG_PDM_AFIFO_AEMPTY_TH

The atemp to be EMPTY threshold of MIC.

配置PDM FIFO将空的阈值,默认值是3

默认值是3,视情况选择配置,建议不做修改

CONFIG_PDM_AFIFO_AFULL_TH

The atemp to be FULL threshold of MIC.

配置PDM FIFO将满的阈值,默认值是16

默认值是16,视情况选择配置,建议不做修改

CONFIG_PDM_ADC_DC_OFFSET

HPF DC Offset.

配置PDM ADC HPF DC偏移值,默认值是0

默认值是0,建议不做修改

CONFIG_PDM_CIC_GAIN

The PDM CIC gain.

配置PDM ADC CIC gain值,默认值是0x14

默认值是0x14,建议不做修改

CONFIG_PDM_LP_CIC_GAIN

The low power PDM CIC gain.

配置低功耗情况下的PDM ADC CIC gain值,默认值是0x0

默认值是0x0,建议不做修改

开发指引

示例:

  1. 首先进行PDM通道参数配置,目前可配的参数如下结构体所示。

    信号源为DMIC时按如下配置:
    int test_pdm_config(void)
    {
        pdm_config_t config = { 0 };
        if (argc != PDM_CONFIG_PARAM_NUM) {
            return ERRCODE_TEST_SUITE_ERROR_BAD_PARAMS;
        }
        pdm_mic_t mic = (pdm_mic_t)HAL_PDM_DMIC_0;
        config.srcdn_src_mode = DOUBLE_EXTRACT;
        errcode_t ret = uapi_pdm_set_attr(mic, &config);
        if (ret != ERRCODE_SUCC) {
            return ERRCODE_TEST_SUITE_CONFIG_FAILED;
        }
        return ERRCODE_TEST_SUITE_OK;
    }
    信号源为AMIC时按如下配置:
    void test_pdm_config(void)
    {
        uint8_t mic = HAL_PDM_AMIC;
        pdm_config_t config = { 0 };
        config.srcdn_src_mode = TRIPLE_EXTRACT;
        if (uapi_pdm_set_attr(mic, &config) == ERRCODE_SUCC) {
            osal_printk("pdm config attr succ!\r\n");
        } else {
            osal_printk("pdm config attr fail!\r\n");
        }
    }
    
  2. 开启PDM采样并从FIFO中读取采样值。

    int32_t app_pdm_start_dma_transfer(uint32_t *pdm_buffer, dma_transfer_cb_t trans_done, dma_channel_t *channel)
    {
        dma_ch_user_peripheral_config_t transfer_config;
    
        transfer_config.src = uapi_pdm_get_fifo_addr();
        transfer_config.dest = (uint32_t)(uintptr_t)pdm_buffer;
        transfer_config.transfer_num = CONFIG_PDM_TRANSFER_LEN_BY_DMA;
        transfer_config.src_handshaking = HAL_DMA_HANDSHAKING_MIC45_UPLINK_REQ;
        transfer_config.dest_handshaking = 0;
        transfer_config.trans_type = HAL_DMA_TRANS_PERIPHERAL_TO_MEMORY_DMA;
        transfer_config.trans_dir = HAL_DMA_TRANSFER_DIR_PERIPHERAL_TO_MEM;
        transfer_config.priority = PDM_DMA_PRIORITY;
        transfer_config.src_width = HAL_DMA_TRANSFER_WIDTH_32;
        transfer_config.dest_width = HAL_DMA_TRANSFER_WIDTH_32;
        transfer_config.burst_length = 0;
        transfer_config.src_increment = HAL_DMA_ADDRESS_INC_NO_CHANGE;
        transfer_config.dest_increment = HAL_DMA_ADDRESS_INC_INCREMENT;
        transfer_config.protection = HAL_DMA_PROTECTION_CONTROL_BUFFERABLE;
    
        if (uapi_dma_configure_peripheral_transfer_single(&transfer_config, channel, trans_done,
                                                          (uintptr_t)NULL) != ERRCODE_SUCC) {
            osal_printk("DMA configure fail.\r\n");
            return 1;
        }
    
        if (uapi_dma_start_transfer((uint8_t)*channel) != ERRCODE_SUCC) {
            osal_printk("DMA start transfer fail.\r\n");
            return 1;
        }
    
        return 0;
    }
    static void app_dma_transfer_done_callback(uint8_t int_type, uint8_t channel, uintptr_t arg)
    {
        unused(arg);
        unused(channel);
        switch (int_type) {
            case HAL_DMA_INTERRUPT_TFR:
                g_dma_trans_done = 1;
                break;
            case HAL_DMA_INTERRUPT_BLOCK:
                g_dma_trans_done = 1;
                break;
            case HAL_DMA_INTERRUPT_ERR:
                osal_printk("DMA transfer error.\r\n");
                break;
            default:
                break;
        }
    }
    信号源为DMIC时按如下配置:
    void *pdm_task(const char *arg)
    {
        unused(arg);
        dma_channel_t channel;
        app_pdm_init_pin();
        /* PDM init. */
        uapi_pdm_deinit();
        uapi_pdm_init();
        app_pdm_set_config();
        if (uapi_pdm_start() == ERRCODE_SUCC) {
            osal_printk("pdm start transfer succ!\r\n");
        }
        uapi_dma_init();
        uapi_dma_open();
        while (1) {
            g_dma_trans_done = 0;
            if (app_pdm_start_dma_transfer(g_app_pdm_dma_data, app_dma_transfer_done_callback, &channel) != 0) {
                continue;
            }
            while (!g_dma_trans_done) {
                uapi_watchdog_kick();
            }
            uapi_dma_end_transfer(channel);
            for (uint32_t i = 0; i < CONFIG_PDM_TRANSFER_LEN_BY_DMA; i++) {
                g_app_pdm_buffer[g_app_pdm_buffer_filled_count++] = (uint8_t)(g_app_pdm_dma_data[i] >> PDM_OFFSET_16);
                g_app_pdm_buffer[g_app_pdm_buffer_filled_count++] = (uint8_t)(g_app_pdm_dma_data[i] >> PDM_OFFSET_24);
            }
            if (g_app_pdm_buffer_filled_count < CONFIG_PDM_MAX_RECORD_DATA) {
                continue;
            } else {
                osal_printk("The current number of records in PDM exceeds the maximum allowed limit,addr:0x%x\r\n",
                            (uintptr_t)(&g_app_pdm_buffer));
                uapi_pdm_stop();
                return NULL;
            }
        }
        return NULL;
    }
    信号源为AMIC时按如下配置:
    void *pdm_amic_task(const char *arg)
    {
        unused(arg);
        dma_channel_t channel;
        app_pdm_init_pin(CONFIG_ADC_USE_PIN1);
        app_pdm_init_pin(CONFIG_ADC_USE_PIN2);
        uapi_adc_init(ADC_CLOCK_NONE);
        uapi_adc_power_en(AFE_AMIC_MODE, true);
        uapi_adc_open_differential_channel(ADC_POSTIVE_CHANNEL, ADC_NEGATIVE_CHANNEL);
        adc_calibration(AFE_AMIC_MODE, true, true, true);
        /* PDM init. */
        uapi_pdm_init();
        app_pdm_set_config();
        if (uapi_pdm_start() == ERRCODE_SUCC) {
            osal_printk("pdm start transfer succ!\r\n");
        }  else {
            osal_printk("pdm start transfer fail!\r\n");
        }
        uapi_dma_init();
        uapi_dma_open();
        while (1) {
            g_dma_trans_done = 0;
            if (app_pdm_start_dma_transfer(g_app_pdm_dma_data, app_dma_transfer_done_callback, &channel) != 0) {
                continue;
            }
            while (!g_dma_trans_done) {
                uapi_watchdog_kick();
            }
            uapi_dma_end_transfer(channel);
            for (uint32_t i = 0; i < CONFIG_PDM_TRANSFER_LEN_BY_DMA; i++) {
                g_app_pdm_buffer[g_app_pdm_buffer_filled_count++] = (uint8_t)(g_app_pdm_dma_data[i] >> PDM_OFFSET_16);
                g_app_pdm_buffer[g_app_pdm_buffer_filled_count++] = (uint8_t)(g_app_pdm_dma_data[i] >> PDM_OFFSET_24);
            }
            if (g_app_pdm_buffer_filled_count < CONFIG_PDM_MAX_RECORD_DATA_SIZE) {
                osal_msleep(1);
                continue;
            } else {
                osal_printk("The current number of records in PDM exceeds the maximum allowed limit,addr:0x%x\r\n",
                            (uintptr_t)(&g_app_pdm_buffer));
                uapi_pdm_stop();
                dma_port_clock_disable();
                return NULL;
            }
        }
        return NULL;
    }
    
    

注意事项

如果读取FIFO不及时,会出现FIFO full的错误,读取完FIFO后,要写CLR位清除对应的状态。

USB_HID

概述

USB_HID类是USB设备的一个标准设备类,属于人机交互操作的设备,包括鼠标、键盘等,主要用于人与计算机进行交互。还可用来传输数据、控制设备等,如影像显示设备可能使用HID接口来做亮度、对比度的软件控制,而使用传统的影像接口来传送要显示的数据。USB扩音器可以使用实时传输来播放语音,同时使用HID接口来控制音量等。HID设备可以作为低速、 全速、高速设备用。由于HID设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。

功能概述

HID模块提供的接口及功能如下:

  • usb_init:初始化USB。

  • usb_deinit:去初始化USB。

  • usbd_set_device_info:设置device设备的VID、PID、设备版本号和厂商、产品、序列号字符串信息。

  • hid_add_report_desc_hid:配置报告描述符信息,设置报告数据格式和功能。

  • fhid_send_data:鼠标数据发送。

  • fhid_recv_data: 鼠标数据接收,最大接收长度为64字节。为阻塞式接口,需要独自起一个线程调用。

KCONFIG配置

开发指引

示例:

  1. 配置报告描述符信息,设置报告数据格式和功能。

    int tesetsuit_usb_set_report_desc_hid(void)
    {
        return hid_add_report_descriptor(g_report_desc_hid, sizeof(g_report_desc_hid), 0);
    }
    

    g_report_desc_hid为报告描述符,返回值为描述符的索引,用在“4”中的发送接口里。

  2. 首先配置device设备的VID、 PID、设备版本号和厂商、产品、序列号字符串信息。

        const char manufacturer[TEST_USB_MANUFACTURER_LEN] =  TEST_USB_MANUFACTURER;  /* 厂商ID */
        struct device_string str_manufacturer = {
            .str = manufacturer,
            .len = TEST_USB_MANUFACTURER_LEN  /* 厂商ID长度 */
        };
        const char product[TEST_USB_PRODUCT_LEN] = TEST_USB_PRODUCT;  /* 产品ID */
        struct device_string str_product = {
            .str = product,
            .len = TEST_USB_PRODUCT_LEN  /* 产品ID长度 */
        };
        const char serial[TEST_USB_SERIAL_LEN] = TEST_USB_SERIAL;  /* 序列号 */
        struct device_string str_serial_number = {
            .str = serial,
            .len = TEST_USB_SERIAL_LEN  /* 序列号长度 */
        };
        struct device_id dev_id = {
            .vendor_id = 0x1111,  /* VID */
            .product_id = 0x0009,  /* PID */
            .release_num = 0x0800  /* 版本号 */
        };
    
        if (usbd_set_device_info(dtype, &str_manufacturer, &str_product, &str_serial_number, dev_id) != 0) {
            test_suite_log_stringf("set device info fail!\n");
            return TEST_SUITE_TEST_FAILED;
        }
    
  3. USB初始化。device type为DEV_HID。

    if (usb_init(DEVICE, dtype) != 0) {
            test_suite_log_stringf("usb_init failed!\n");
            return TEST_SUITE_TEST_FAILED;
    }
    
  4. 发送鼠标数据。这里以SOF中断中发送数据举例,也可自行调用发送数据的接口。

    uint8_t g_mouse_init_flag = 0;
    void usb_sof_intr_callback(void)
    {
        if (g_mouse_init_flag == 0) {
            return;
        }
        static uint8_t usb_sof_cnt = 0;
        usb_sof_cnt = (usb_sof_cnt + 1) % g_usb_mouse_polling_rate;
        if (usb_sof_cnt != 0) {
            return;
        }
        int16_t x = 0;
        int16_t y = 0;
        g_usb_hid_hs_mouse_operator.get_xy(&x, &y);
        g_send_mouse_msg.x = x;
        g_send_mouse_msg.y = y;
        fhid_send_data(g_mouse_index, (char *)&g_send_mouse_msg, USB_MOUSE_REPORTER_LEN);
    }
    

    由于数据接收接口为阻塞式接口,因此接收host数据。需要新起一个线程,在线程里执行数据接收操作,fhid_recv_data返回值为实际接收数据的长度,当返回值为负数时表示当前设备未连接、参数有误或者设备断连。

    下述样例为:接收host发送的数据,判断数据是否为切换至dfu设备的命令,如果是则执行去初始化再初始化为dfu设备(dfu设备一般做升级用,详见USB_DFU章节)

    osal_kthread_create(usb_recv_task, NULL, "lcd_task", USB_RECV_STACK_SIZE); // 起线程
    int usb_recv_task(void *para) // 数据接收线程
    {
        UNUSED(para);
        uint8_t recv_data[RECV_MAX_LENGTH];
        for (;;) {
            int32_t ret = fhid_recv_data(tesetsuit_usb_get_hid_index(), (char*)recv_data, RECV_MAX_LENGTH); // 接收数据
            if (ret <= 0) {
                osDelay(USB_RECV_TASK_DELAY);
                continue;
            }
            if (ret == RECV_MAX_LENGTH && recv_data[0] == CUSTUMER_PAGE_REPORT_ID) { // 判断接收数据类型
                seboot_switch_dfu_t command;
                if (memcpy_s(&command, sizeof(seboot_switch_dfu_t), &recv_data[1], RECV_MAX_LENGTH - 1) != EOK) {
                    continue;
                }
                osal_printk("start_flag:%x\r\n", command.start_flag);
                osal_printk("packet_size:%x\r\n", command.packet_size);
                osal_printk("frame_type:%x\r\n", command.frame_type);
                osal_printk("frame_type_reserve:%x\r\n", command.frame_type_reserve);
                osal_printk("flag:%x\r\n", command.flag);
                osal_printk("check_sum:%x\r\n", command.check_sum);
                if (command.frame_type == SWITCH_TO_DFU_FLAG) {
                    osal_printk("start dfu\n");
                    usb_deinit();
                    usb_dfu_init(); // 初始化为dfu设备,详见USB_DFU章节
                    break;
                }
            }
        }
        osal_printk("usb recv task over\n");
        return 0;
    }
    
    

注意事项

HID报告描述符配置后返回的索引一定要和发送数据接口的第一个参数保持一致,并且发送数据的格式是根据报告描述符定义的,也需要保持一致。

host下发的数据的第一个字节需要同报告描述符描述的report_id一致,字节长度也需和报告描述符描述一致。

说明: 若usb_deinit接口的调用同fhid_recv_data不在同一个线程,则需要保证fhid_recv_data所在的线程的优先级比usb_deinit所在线程的优先级高。以此确保在usb_deinit时fhid_recv_data能够及时退出,防止deinit失败。

USB_DFU

概述

USB DFU设备全称为Device Firmware Upgrade设备固件更新,支持从host处接收新固件做升级。

功能概述

DFU设备类型为通用串行总线设备,支持从host处接收大量数据,一般做设备升级用。

KCONFIG配置

暂无。

开发指引

  1. 配置device设备的VID、 PID、设备版本号和厂商、产品、序列号字符串信息。

    #define DFU_MANUFACTURER  { 'H', 0, 'H', 0, 'H', 0, 'H', 0, 'l', 0, 'i', 0, 'c', 0, 'o', 0, 'n', 0 }
    #define DFU_MANUFACTURER_LEN   20
    #define DFU_PRODUCT  { 'H', 0, 'H', 0, '6', 0, '6', 0, '6', 0, '6', 0, ' ', 0, 'U', 0, 'S', 0, 'B', 0 }
    #define DFU_PRODUCT_LEN        22
    #define DFU_SERIAL   { '2', 0, '0', 0, '2', 0, '0', 0, '0', 0, '6', 0, '2', 0, '4', 0 }
    #define DFU_SERIAL_LEN         16
    int usb_dfu_init(void)
    {
        int ret;
        const char manufacturer[DFU_MANUFACTURER_LEN] = DFU_MANUFACTURER;
        struct device_string str_manufacturer = {
            .str = manufacturer,
            .len = DFU_MANUFACTURER_LEN
        };
        const char product[DFU_PRODUCT_LEN] = DFU_PRODUCT;
        struct device_string str_product = {
            .str = product,
            .len = DFU_PRODUCT_LEN
        };
        const char serial[DFU_SERIAL_LEN] = DFU_SERIAL;
        struct device_string str_serial_number = {
            .str = serial,
            .len = DFU_SERIAL_LEN
        };
        struct device_id dev_id = {
            .vendor_id = 0x1111,
            .product_id = 0xa,
            .release_num = 0x0119
        };
        ret = usbd_set_device_info(DEV_DFU, &str_manufacturer, &str_product, &str_serial_number, dev_id); // 配置设备类型和设备信息
        if (ret != 0) {
            osal_printk("set device info fail!\n");
            return -1;
        }
        ret = usb_init(DEVICE, DEV_DFU);  // usb初始化为dfu设备
        if (ret != 0) {
            osal_printk("usb_init failed!\n");
            return -1;
        }
        return 0;
    }
    
  2. 数据接收和处理。

    usb_dfu_download_callback(const uint8_t *buf, uint32_t len)为dfu设备数据接收的回调函数,当设备接收到数据时会自动调用此函数,参数表示数据地址和长度,下述示例为dfu升级流程:

    
    void usb_dfu_download_callback(const uint8_t *buf, uint32_t len)
    {
        static bool is_pack_header = true;
        if (buf == NULL || len == 0) {
            printf("upgrade done\r\n");  // 数据接收完成
            return;
        }
        if (is_pack_header) {
            dfu_packge_header_t *head = (dfu_packge_header_t *)buf;
            osal_printk("is_pack_header:%x\r\n", is_pack_header);
            osal_printk("start_flag:%x\r\n", head->start_flag);
            osal_printk("packet_size:%x\r\n", head->packet_size);
            osal_printk("frame_type:%x\r\n", head->frame_type);
            osal_printk("frame_type_reverse:%x\r\n", head->frame_type_reverse);
            osal_printk("file_addr:%x\r\n", head->file_addr);
            osal_printk("file_len:%x\r\n", head->file_len);
            osal_printk("erase_size:%x\r\n", head->erase_size);
            osal_printk("formal:%x\r\n", head->formal);
            osal_printk("formal_reverse:%x\r\n", head->formal_reverse);
            osal_printk("checksum:%x\r\n", head->checksum);
            is_pack_header = false;
            return;
        }
        osal_printk("          buf:");
        for (int i = 0; i < DFU_SERIAL_LEN; i++) {
            osal_printk("%x  ", buf[i]);
        }
        osal_printk("\n");
        uapi_eflash_erase((uint32_t)g_dfu_data, len);  // 将g_dfu_data处起始的flash空间清除len个字节
        uapi_eflash_write((uint32_t)g_dfu_data, (uint32_t *)(uintptr_t)buf, len); // 将接收到的数据写入flash
        g_dfu_write_len += len;
        if (uapi_crc16(0, buf, len) != uapi_crc16(0, (uint8_t *)(g_dfu_data + EMBED_FLASH_START), len)) {  // 验证写入flash后的数据和接收的数据一致
            osal_printk("check fail!\r\n");
            uapi_eflash_erase(FRAME_DOWNLOAD_OFFSET, g_dfu_write_len);
            cpu_utils_reset_chip_with_log((cores_t)APPS, REBOOT_CAUSE_APPLICATION_SYSRESETREQ);
        }
        for (int i = 0; i < DFU_SERIAL_LEN; i++) {
            osal_printk("%x  ", *(char *)(g_dfu_data + EMBED_FLASH_START + i));
        }
        osal_printk("\ng_dfu_write_len:%u  \n", g_dfu_write_len);
        g_dfu_data += len;
    }
    
    

注意事项

USB在切换为DFU设备后其他功能无法正常使用,比如原先是HID鼠标设备,切换为DFU设备做升级时鼠标功能不会生效。

DFU设备和正常功能的设备的PID不能一样,例如:如果HID鼠标设备,PID为0x9, 在切换为DFU设备做升级时需要配置DFU设备的PID不等于0x9,否则在切换为DFU设备后host任会将设备识别成HID鼠标设备而不时DFU设备。

I2S

概述

I2S(Inter-IC Sound, Integrated Interchip Sound,或简写IIS),是一种数字音频传输标准,用于数字音频数据在系统内部器件之间传输,例如编解码器 CODEC、DSP、数字输入/输出接口、ADC、DAC 和数字滤波器等。I2S是一个简单的数字接口协议,没有地址或设备选择机制,只能同时存在一个主设备和发送设备。在 I2S 总线上,提供时钟(SCK 和 WS)的设备为主设备。 I2S主要是通过SIO(Serial Input&Output)串行输入输出模块实现其功能,SIO接口支持I2S模式,I2S接口通常用于高保真音频应用,如音频播放器、音频录制设备和音频处理器等。

I2S总线包含四条线:时钟信号SCK,左右声道选择信号WS,数据输入信号DI,数据输出信号DO。

产品仅提供了I2S0这一组支持Master/Slaver模式的I2S外设,I2S规格如下:

  • 支持两种操作模式,主模式和从模式,主模式SCK需要模块内部自己产生,而从模式由外部提供。

  • 支持位宽为32bit×8的FIFO。

功能描述

说明: 如果I2S驱动想配置DMA模式读写数据,需要确保DMA驱动已经初始化。DMA初始化请参考“DMA”进行配置。

I2S模块提供的接口及功能如下:

  • uapi_i2s_init(sio_bus_t bus, i2s_callback_t callback):初始化该I2S模块(bus表示当前使用的I2S bus总线,callback表示I2S设备的回调函数)。

  • uapi_i2s_deinit(sio_bus_t bus):去初始化I2S模块(bus表示当前使用的I2S bus总线)。

  • uapi_i2s_set_config(sio_bus_t bus, const i2s_config_t *config):设置I2S设备的配置信息(bus表示当前使用的I2S bus总线,config表示I2S设备的配置信息)。

  • uapi_i2s_get_config(sio_bus_t bus, i2s_config_t *config):获取I2S设备的配置信息(bus表示当前使用的I2S bus总线,config表示I2S设备的配置信息)。

  • uapi_i2s_write_data(sio_bus_t bus, i2s_tx_data_t *data):I2S发送数据(bus表示当前使用的I2S bus总线,data表示I2S需要发送的数据)。

  • uapi_i2s_read_start(sio_bus_t bus):I2S rx使能,开始接收数据(bus表示当前使用的I2S bus总线)。

  • uapi_i2s_loopback(sio_bus_t bus, bool en):设置I2S回环自测模式(bus表示当前使用的I2S bus总线,en表示是否配置I2S回环自测模式)。(打开CONFIG_I2S_SUPPORT_LOOPBACK宏才能使用)

  • uapi_i2s_loop_trans(sio_bus_t bus, i2s_tx_data_t *data):开启I2S回环自测(bus表示当前使用的I2S bus总线,data表示I2S的数据传输配置)。

  • uapi_i2s_dma_config(sio_bus_t bus, i2s_dma_attr_t *i2s_dma_cfg):配置I2S DMA传输参数(bus表示当前使用的I2S bus总线,i2s_dma_cfg表示I2S DMA传输时的配置参数)。(打开CONFIG_I2S_SUPPORT_DMA宏才能使用)

  • uapi_i2s_merge_write_by_dma(sio_bus_t bus, const void *buffer, uint32_t length, i2s_dma_config_t *dma_cfg, uintptr_t arg, bool block):merge模式下I2S通过DMA写数据(bus表示当前使用的I2S bus总线,buffer表示I2S需要发送的数据,length表示发送数据的长度,dma_cfg表示I2S DMA传输时的配置参数,arg表示自定义参数指针,可被传递到中断处理函数,block表示阻塞传输还是非阻塞)。(打开CONFIG_I2S_SUPPORT_DMA宏才能使用)

  • uapi_i2s_merge_read_by_dma(sio_bus_t bus, const void *buffer, uint32_t length, i2s_dma_config_t *dma_cfg, uintptr_t arg, bool block):merge模式下I2S通过DMA读数据(bus表示当前使用的I2S bus总线,buffer表示I2S读取数据缓冲区,length表示读取数据的长度,dma_cfg表示I2S DMA传输时的配置参数,arg表示自定义参数指针,可被传递到中断处理函数,block表示阻塞传输还是非阻塞)。(打开CONFIG_I2S_SUPPORT_DMA宏才能使用)

KCONFIG配置

说明: 若上述图片所示与表格描述不一致,请以表格为准。

配置宏具体描述如表1所示。

表 1 I2S相关宏描述

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

CONFIG_I2S_BUS_MAX_NUM

I2S_BUS_MAX_NUM.

I2S最大可配总线数量,本产品仅支持一组(I2S BUS0总线)

默认值是1,建议不做修改

CONFIG_I2S_SUPPORT_LOOPBACK

I2S_SUPPORT_LOOPBACK.

I2S支持单板回环模式

默认打开,建议打开

CONFIG_I2S_SUPPORT_DMA

I2S SUPPORT DMA.

I2S支持DMA模式

默认关闭,视情况选择

CONFIG_I2S_SCLK_PIN

Choose I2S SCLK pin.

I2S时钟管脚

默认值是13,视情况选择配置

CONFIG_I2S_WS_PIN

Choose I2S WS pin.

I2S左右声道管脚

默认值是14,视情况选择配置

CONFIG_I2S_DO_PIN

Choose I2S DO pin.

I2S输出管脚

默认值是15,视情况选择配置

CONFIG_I2S_DI_PIN

Choose I2S DI pin..

I2S输入管脚

默认值是16,视情况选择配置

CONFIG_I2S_SCLK_PIN_MODE

Choose I2S SCLK pin mode.

I2S时钟管脚对应配置的模式

默认值是35,视情况选择配置

CONFIG_I2S_WS_PIN_MODE

Choose I2S WS pin mode.

I2S左右声道管脚对应配置的模式

默认值是36,视情况选择配置

CONFIG_I2S_DO_PIN_MODE

Choose I2S DO pin mode.

I2S输出管脚对应配置的模式

默认值是37,视情况选择配置

CONFIG_I2S_DI_PIN_MODE

Choose I2S DI pin mode.

I2S输入管脚对应配置的模式

默认值是38,视情况选择配置

CONFIG_I2S_SUPPORT_QUERY_REGS

I2S support query regs values.

I2S支持回读所有相关寄存器的值,视情况选择

默认关闭,视情况选择,建议关闭

CONFIG_SIO_USING_V150

SIO Using V150.

该宏代表使用SIO V150的驱动规格,本产品不使用该规格

默认关闭,必须关闭

CONFIG_DATA_LEN_MAX

DATA_LEN_MAX.

I2S发送数据的最大长度,本产品不使用

CONFIG_SIO_USING_V150

默认关闭,必须关闭

CONFIG_I2S_RX_THRESHOLD

Threshold of the I2S RX channel.

I2S的RX水线阈值,本产品不使用

CONFIG_SIO_USING_V150

默认关闭,必须关闭

CONFIG_I2S_TX_THRESHOLD

Threshold of the I2S TX channel.

I2S的TX水线阈值,本产品不使用

CONFIG_SIO_USING_V150

默认关闭,必须关闭

CONFIG_I2S_BCLK_DIV_NUM

NUM of the I2S BCLK DIV.

I2S BCLK分频数,本产品暂不使用

CONFIG_SIO_USING_V150

默认关闭,必须关闭

CONFIG_SIO_USING_V151

SIO Using V151.

该宏代表使用SIO V151的驱动规格,本产品默认使用该规格

默认打开,必选

开发指引

I2S用于对接支持I2S协议的设备,I2S单元可以作为主设备或从设备。以I2S单元作为主设备为例:

  1. 通过调用sio_porting_i2s_pinmux接口,进行IO复用,将用到的管脚复用为I2S功能。

  2. 调用uapi_i2s_init以及uapi_i2s_set_config接口,初始化I2S资源,此处以初始化I2S主机为例:

    void sample_i2s_init(void)
    {
        i2s_config_t config = {
            .drive_mode= MASTER,
            .transfer_mode = STD_MODE,
            .data_width = TWENTY_FOUR_BIT,
            .channels_num = TWO_CH,
            .timing = NONE_TIMING_MODE,
            .clk_edge = RISING_EDGE,
            .div_number = I2S_DIV_NUM,
            .number_of_channels = I2S_NUMBER_OF_CHANNELS,
        };
        /* 设置 i2s pinmux */
        sio_porting_i2s_pinmux();       /* 设置 i2s pinmux */
        /* 初始化 i2s0 */
        uapi_i2s_init(SIO_BUS_0, app_i2s_rx_callback);
        uapi_i2s_set_config(SIO_BUS_0, &config);
    }
    
  3. 调用uapi_i2s_write_data接口,实现主机发送数据。

    errcode_t sample_i2s_write(i2s_tx_data_t *data)
    {
        return uapi_i2s_write_data(SIO_BUS_0, &data);     /* 发送数据 */
    }
    

I2S DMA主从收发完整代码流程可参考以下代码:

#include "i2s.h"
#include "watchdog.h"
#include "hal_sio.h"
#include "hal_dma.h"
#include "soc_osal.h"
#include "app_init.h"

#define I2S_DIV_NUMBER              32
#define I2S_CHANNEL_NUMBER          2
#define I2S_TX_INT_THRESHOLD        7
#define I2S_RX_INT_THRESHOLD        1
#define I2S_DMA_SRC_WIDTH           2
#define I2S_DMA_DEST_WIDTH          2
#define I2S_DMA_BURST_LENGTH        0
#define I2S_DMA_TRANS_STEP          2

#define I2S_TASK_PRIO               24
#define I2S_TASK_STACK_SIZE         0xc00

static uint32_t g_i2s_first_data = 0x10000000;    /* 32 bits */
static uint32_t g_i2s_send_dma_data[CONFIG_I2S_TRANSFER_LEN_OF_DMA] = { 0 };

static void *i2s_dma_master_task(const char *arg)
{
    unused(arg);
    int32_t ret = CONFIG_I2S_TRANSFER_LEN_OF_DMA;
#if defined(CONFIG_SIO_USING_V151)
    ret = ERRCODE_SUCC;
#endif
    uapi_i2s_deinit(SIO_BUS_0);
    uapi_dma_deinit();
    uapi_i2s_init(SIO_BUS_0, NULL);
    sio_porting_i2s_pinmux();
    i2s_config_t config = {
        .drive_mode= MASTER,
        .transfer_mode = STD_MODE,
        .data_width = THIRTY_TWO_BIT,
        .channels_num = TWO_CH,
        .timing = NONE_TIMING_MODE,
        .clk_edge = RISING_EDGE,
        .div_number = I2S_DIV_NUMBER,
        .number_of_channels = I2S_CHANNEL_NUMBER,
    };
    i2s_dma_attr_t attr = {
        .tx_dma_enable = 1,
        .tx_int_threshold = I2S_TX_INT_THRESHOLD,
        .rx_dma_enable = 0,
        .rx_int_threshold = I2S_RX_INT_THRESHOLD,
    };
    i2s_dma_config_t dma_cfg = {
        .src_width = I2S_DMA_SRC_WIDTH,
        .dest_width = I2S_DMA_DEST_WIDTH,
        .burst_length = I2S_DMA_BURST_LENGTH,
        .priority = 0,
    };
    uapi_i2s_set_config(SIO_BUS_0, &config);
    uapi_i2s_dma_config(SIO_BUS_0, &attr);

    for (uint32_t i = 0; i < CONFIG_I2S_TRANSFER_LEN_OF_DMA; i += I2S_DMA_TRANS_STEP) {
        g_i2s_send_dma_data[i] = g_i2s_first_data;
        g_i2s_send_dma_data[i + 1] = g_i2s_first_data;
        g_i2s_first_data++;
    }
    /* DMA init. */
    uapi_dma_init();
    uapi_dma_open();

    osal_printk("DMA master transfer start.\r\n");
    while (1) {
        uapi_watchdog_kick();
        if (uapi_i2s_merge_write_by_dma(SIO_BUS_0, &g_i2s_send_dma_data, CONFIG_I2S_TRANSFER_LEN_OF_DMA, &dma_cfg,
            (uintptr_t)NULL, true) != ret) {
            osal_printk("master uapi_i2s_merge_write_by_dma error.\r\n");
        }
    }
    return NULL;
}

static void i2s_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2s_dma_master_task, 0, "I2sDmaMasterTask",
                                      I2S_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2S_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2s_entry. */
app_run(i2s_entry);

-----------------------------------------------------------------

#include "i2s.h"
#include "watchdog.h"
#include "hal_sio.h"
#include "hal_dma.h"
#include "soc_osal.h"
#include "app_init.h"

#define I2S_DIV_NUMBER              32
#define I2S_CHANNEL_NUMBER          2
#define I2S_TX_INT_THRESHOLD        7
#define I2S_RX_INT_THRESHOLD        1
#define I2S_DMA_TRANSFER_EVENT      1
#define I2S_RING_BUFFER_NUMBER      4

#define I2S_TASK_PRIO               24
#define I2S_TASK_STACK_SIZE         0xc00

static uint32_t g_i2s_dma_data0[CONFIG_I2S_TRANSFER_LEN_OF_DMA] = { 0 };
static uint32_t g_i2s_dma_data1[CONFIG_I2S_TRANSFER_LEN_OF_DMA] = { 0 };
static uint32_t g_i2s_dma_data2[CONFIG_I2S_TRANSFER_LEN_OF_DMA] = { 0 };
static uint32_t g_i2s_dma_data3[CONFIG_I2S_TRANSFER_LEN_OF_DMA] = { 0 };
static uint32_t *g_i2s_dma_data[I2S_RING_BUFFER_NUMBER] = {
    g_i2s_dma_data0, g_i2s_dma_data1, g_i2s_dma_data2, g_i2s_dma_data3};
static uint8_t g_read_buffer_state = 0;
static osal_event g_i2s_dma_id;

static void i2s_dma_transfer_restart(void);

static int32_t i2s_start_dma_transfer(uint32_t *i2s_buffer, dma_transfer_cb_t trans_done)
{
    dma_ch_user_peripheral_config_t transfer_config;
    uint8_t channel = 0;

    transfer_config.src = i2s_porting_rx_merge_data_addr_get(SIO_BUS_0);
    transfer_config.dest = (uint32_t)(uintptr_t)i2s_buffer;
    transfer_config.transfer_num = (uint16_t)CONFIG_I2S_TRANSFER_LEN_OF_DMA;
    transfer_config.src_handshaking = HAL_DMA_HANDSHAKING_I2S_RX;
    transfer_config.dest_handshaking = 0;
    transfer_config.trans_type = HAL_DMA_TRANS_PERIPHERAL_TO_MEMORY_DMA;
    transfer_config.trans_dir = HAL_DMA_TRANSFER_DIR_PERIPHERAL_TO_MEM;
    transfer_config.priority = 0;
    transfer_config.src_width = HAL_DMA_TRANSFER_WIDTH_32;
    transfer_config.dest_width = HAL_DMA_TRANSFER_WIDTH_32;
    transfer_config.burst_length = 0;
    transfer_config.src_increment = HAL_DMA_ADDRESS_INC_NO_CHANGE;
    transfer_config.dest_increment = HAL_DMA_ADDRESS_INC_INCREMENT;
    transfer_config.protection = HAL_DMA_PROTECTION_CONTROL_BUFFERABLE;

    errcode_t ret = uapi_dma_configure_peripheral_transfer_single(&transfer_config, &channel,
                                                                  trans_done, (uintptr_t)NULL);
    if (ret != ERRCODE_SUCC) {
        osal_printk("%s Configure the DMA fail. %x\r\n", "i2s dma", ret);
        return 1;
    }
    ret = uapi_dma_start_transfer(channel);
    if (ret != ERRCODE_SUCC) {
        osal_printk("%s Start the DMA fail. %x\r\n", "i2s dma", ret);
        return 1;
    }
    hal_sio_set_rx_enable(SIO_BUS_0, 1);
    return 0;
}

static void i2s_dma_trans_done_callback(uint8_t intr, uint8_t channel, uintptr_t arg)
{
    unused(channel);
    unused(arg);
    switch (intr) {
        case HAL_DMA_INTERRUPT_TFR:
            i2s_dma_transfer_restart();
            break;
        case HAL_DMA_INTERRUPT_ERR:
            osal_printk("i2s DMA transfer error.\r\n");
            break;
        default:
            break;
    }
}

static void i2s_dma_transfer_restart(void)
{
    g_read_buffer_state = (g_read_buffer_state + 1) % I2S_RING_BUFFER_NUMBER;
    if (osal_event_write(&g_i2s_dma_id, I2S_DMA_TRANSFER_EVENT) != OSAL_SUCCESS) {
        osal_printk("osal_event_write fail!\r\n");
        return;
    }
    if (i2s_start_dma_transfer(g_i2s_dma_data[g_read_buffer_state], i2s_dma_trans_done_callback) != 0) {
        return;
    }
}

static void *i2s_dma_slave_task(const char *arg)
{
    unused(arg);
    if (osal_event_init(&g_i2s_dma_id) != OSAL_SUCCESS) {
        return NULL;
    }
    uapi_i2s_deinit(SIO_BUS_0);
    uapi_dma_deinit();
    uapi_i2s_init(SIO_BUS_0, NULL);
    sio_porting_i2s_pinmux();
    i2s_config_t config = {
        .drive_mode= SLAVE,
        .transfer_mode = STD_MODE,
        .data_width = THIRTY_TWO_BIT,
        .channels_num = TWO_CH,
        .timing = NONE_TIMING_MODE,
        .clk_edge = RISING_EDGE,
        .div_number = I2S_DIV_NUMBER,
        .number_of_channels = I2S_CHANNEL_NUMBER,
    };
    i2s_dma_attr_t attr = {
        .tx_dma_enable = 0,
        .tx_int_threshold = I2S_TX_INT_THRESHOLD,
        .rx_dma_enable = 1,
        .rx_int_threshold = I2S_RX_INT_THRESHOLD,
    };

    uapi_i2s_set_config(SIO_BUS_0, &config);
    uapi_i2s_dma_config(SIO_BUS_0, &attr);

    /* DMA init. */
    uapi_dma_init();
    uapi_dma_open();
    if (i2s_start_dma_transfer(g_i2s_dma_data[g_read_buffer_state], i2s_dma_trans_done_callback) != 0) {
        return NULL;
    }
    osal_printk("DMA slave receive start.\r\n");
    while (1) {
        if (!(osal_event_read(&g_i2s_dma_id, I2S_DMA_TRANSFER_EVENT, OSAL_WAIT_FOREVER,
                              OSAL_WAITMODE_AND | OSAL_WAITMODE_CLR))) {
            uapi_watchdog_kick();
            continue;
        }
    }
    return NULL;
}

static void i2s_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2s_dma_slave_task, 0, "I2sDmaSlaveTask",
                                      I2S_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2S_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2s_entry. */
app_run(i2s_entry);

I2S DMA循环链表主从收发完整代码流程可参考以下代码(此处slave端使用DMA循环链表完全接收数据):
#include "i2s.h"
#include "watchdog.h"
#include "hal_sio.h"
#include "hal_dma.h"
#include "soc_osal.h"
#include "app_init.h"
#include "dma.h"

#define I2S_DIV_NUMBER              32
#define I2S_CHANNEL_NUMBER          2
#define I2S_TX_INT_THRESHOLD        7
#define I2S_RX_INT_THRESHOLD        1
#define I2S_DMA_SRC_WIDTH           2
#define I2S_DMA_DEST_WIDTH          2
#define I2S_DMA_BURST_LENGTH        0
#define I2S_DMA_TRANS_STEP          2

#define I2S_TASK_PRIO               24
#define I2S_TASK_STACK_SIZE         0xc00

static uint32_t g_i2s_first_data = 0x10000000;    /* 32 bits */
static uint32_t g_i2s_send_dma_data[CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI] = { 0 };

static void *i2s_dma_master_task(const char *arg)
{
    unused(arg);
    int32_t ret = CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI;
#if defined(CONFIG_SIO_USING_V151)
    ret = ERRCODE_SUCC;
#endif
    uapi_i2s_deinit(SIO_BUS_0);
    uapi_i2s_init(SIO_BUS_0, NULL);
    sio_porting_i2s_pinmux();
    i2s_config_t config = {
        .drive_mode= MASTER,
        .transfer_mode = STD_MODE,
        .data_width = THIRTY_TWO_BIT,
        .channels_num = TWO_CH,
        .timing = NONE_TIMING_MODE,
        .clk_edge = RISING_EDGE,
        .div_number = I2S_DIV_NUMBER,
        .number_of_channels = I2S_CHANNEL_NUMBER,
    };
    i2s_dma_attr_t attr = {
        .tx_dma_enable = 1,
        .tx_int_threshold = I2S_TX_INT_THRESHOLD,
        .rx_dma_enable = 0,
        .rx_int_threshold = I2S_RX_INT_THRESHOLD,
    };
    i2s_dma_config_t dma_cfg = {
        .src_width = I2S_DMA_SRC_WIDTH,
        .dest_width = I2S_DMA_DEST_WIDTH,
        .burst_length = I2S_DMA_BURST_LENGTH,
        .priority = 0,
    };
    uapi_i2s_set_config(SIO_BUS_0, &config);
    uapi_i2s_dma_config(SIO_BUS_0, &attr);

    for (uint32_t i = 0; i < CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI; i += I2S_DMA_TRANS_STEP) {
        g_i2s_send_dma_data[i] = g_i2s_first_data;
        g_i2s_send_dma_data[i + 1] = g_i2s_first_data;
        g_i2s_first_data++;
    }
    /* DMA init. */
    uapi_dma_init();
    uapi_dma_open();

    osal_printk("DMA master transfer start.\r\n");
    while (1) {
        uapi_watchdog_kick();
        if (uapi_i2s_merge_write_by_dma(SIO_BUS_0, &g_i2s_send_dma_data, CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI, &dma_cfg,
            (uintptr_t)NULL, true) != ret) {
            osal_printk("master uapi_i2s_merge_write_by_dma error.\r\n");
        }
    }
    return NULL;
}

static void i2s_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2s_dma_master_task, 0, "I2sDmaMasterTask",
                                      I2S_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2S_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2s_entry. */
app_run(i2s_entry);
#include "i2s.h"
#include "watchdog.h"
#include "hal_sio.h"
#include "hal_dma.h"
#include "soc_osal.h"
#include "app_init.h"
#include "dma.h"

#define I2S_DIV_NUMBER              32
#define I2S_CHANNEL_NUMBER          2
#define I2S_TX_INT_THRESHOLD        7
#define I2S_RX_INT_THRESHOLD        1
#define I2S_DMA_SRC_WIDTH           2
#define I2S_DMA_DEST_WIDTH          2
#define I2S_DMA_BURST_LENGTH        0
#define I2S_DMA_TRANS_STEP          2

#define I2S_TASK_PRIO               24
#define I2S_TASK_STACK_SIZE         0xc00

static uint32_t g_i2s_first_data = 0x10000000;    /* 32 bits */
static uint32_t g_i2s_send_dma_data[CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI] = { 0 };

static void *i2s_dma_master_task(const char *arg)
{
    unused(arg);
    int32_t ret = CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI;
#if defined(CONFIG_SIO_USING_V151)
    ret = ERRCODE_SUCC;
#endif
    uapi_i2s_deinit(SIO_BUS_0);
    uapi_i2s_init(SIO_BUS_0, NULL);
    sio_porting_i2s_pinmux();
    i2s_config_t config = {
        .drive_mode= MASTER,
        .transfer_mode = STD_MODE,
        .data_width = THIRTY_TWO_BIT,
        .channels_num = TWO_CH,
        .timing = NONE_TIMING_MODE,
        .clk_edge = RISING_EDGE,
        .div_number = I2S_DIV_NUMBER,
        .number_of_channels = I2S_CHANNEL_NUMBER,
    };
    i2s_dma_attr_t attr = {
        .tx_dma_enable = 1,
        .tx_int_threshold = I2S_TX_INT_THRESHOLD,
        .rx_dma_enable = 0,
        .rx_int_threshold = I2S_RX_INT_THRESHOLD,
    };
    i2s_dma_config_t dma_cfg = {
        .src_width = I2S_DMA_SRC_WIDTH,
        .dest_width = I2S_DMA_DEST_WIDTH,
        .burst_length = I2S_DMA_BURST_LENGTH,
        .priority = 0,
    };
    uapi_i2s_set_config(SIO_BUS_0, &config);
    uapi_i2s_dma_config(SIO_BUS_0, &attr);

    for (uint32_t i = 0; i < CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI; i += I2S_DMA_TRANS_STEP) {
        g_i2s_send_dma_data[i] = g_i2s_first_data;
        g_i2s_send_dma_data[i + 1] = g_i2s_first_data;
        g_i2s_first_data++;
    }
    /* DMA init. */
    uapi_dma_init();
    uapi_dma_open();

    osal_printk("DMA master transfer start.\r\n");
    while (1) {
        uapi_watchdog_kick();
        if (uapi_i2s_merge_write_by_dma(SIO_BUS_0, &g_i2s_send_dma_data, CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI, &dma_cfg,
            (uintptr_t)NULL, true) != ret) {
            osal_printk("master uapi_i2s_merge_write_by_dma error.\r\n");
        }
    }
    return NULL;
}

static void i2s_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2s_dma_master_task, 0, "I2sDmaMasterTask",
                                      I2S_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2S_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2s_entry. */
app_run(i2s_entry);

---------------------------------------------------------------------

#include "i2s.h"
#include "watchdog.h"
#include "hal_sio.h"
#include "hal_dma.h"
#include "soc_osal.h"
#include "app_init.h"
#include "dma.h"

#define I2S_DIV_NUMBER              32
#define I2S_CHANNEL_NUMBER          2
#define I2S_TX_INT_THRESHOLD        7
#define I2S_RX_INT_THRESHOLD        1
#define I2S_DMA_TRANSFER_EVENT      1
#define I2S_RING_BUFFER_NUMBER      4
#define I2S_DMA_DATA_CMP_MIDDLE     9
#define I2S_DMA_DATA_CMP_END        (CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI - 1)

#define I2S_TASK_PRIO               24
#define I2S_TASK_STACK_SIZE         0xc00

static uint32_t g_i2s_dma_data0[CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI] = { 0 };
static uint32_t g_i2s_dma_data1[CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI] = { 0 };
static uint32_t g_i2s_dma_data2[CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI] = { 0 };
static uint32_t g_i2s_dma_data3[CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI] = { 0 };
static uint32_t *g_i2s_dma_data[I2S_RING_BUFFER_NUMBER] = {
    g_i2s_dma_data0, g_i2s_dma_data1, g_i2s_dma_data2, g_i2s_dma_data3};
static osal_event g_i2s_dma_id;
static uint8_t g_transfer_err_flag = 0;
static uint32_t g_cb_count = 0;

static int32_t i2s_add_dma_lli_node(uint8_t index, dma_channel_t dma_channel, dma_transfer_cb_t trans_done)
{
    dma_ch_user_peripheral_config_t transfer_config;

    transfer_config.src = i2s_porting_rx_merge_data_addr_get(SIO_BUS_0);
    transfer_config.dest = (uint32_t)(uintptr_t)g_i2s_dma_data[index];
    transfer_config.transfer_num = (uint16_t)CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI;
    transfer_config.src_handshaking = HAL_DMA_HANDSHAKING_I2S_RX;
    transfer_config.dest_handshaking = 0;
    transfer_config.trans_type = HAL_DMA_TRANS_PERIPHERAL_TO_MEMORY_DMA;
    transfer_config.trans_dir = HAL_DMA_TRANSFER_DIR_PERIPHERAL_TO_MEM;
    transfer_config.priority = 0;
    transfer_config.src_width = HAL_DMA_TRANSFER_WIDTH_32;
    transfer_config.dest_width = HAL_DMA_TRANSFER_WIDTH_32;
    transfer_config.burst_length = 0;
    transfer_config.src_increment = HAL_DMA_ADDRESS_INC_NO_CHANGE;
    transfer_config.dest_increment = HAL_DMA_ADDRESS_INC_INCREMENT;
    transfer_config.protection = HAL_DMA_PROTECTION_CONTROL_BUFFERABLE;

    errcode_t ret = uapi_dma_configure_peripheral_transfer_lli(dma_channel, &transfer_config, trans_done);
    if (ret != ERRCODE_SUCC) {
        osal_printk("%s Configure the DMA fail. %x\r\n", "i2s dma lli", ret);
        return 1;
    }
    return 0;
}

void app_printf_err(void)
{
    if (g_cb_count >= I2S_RING_BUFFER_NUMBER && g_transfer_err_flag)
    {
        if ((g_i2s_dma_data0[0] == g_i2s_dma_data1[0]) && (g_i2s_dma_data1[0] == g_i2s_dma_data2[0]) &&
            (g_i2s_dma_data2[0] == g_i2s_dma_data3[0]) &&
            (g_i2s_dma_data0[I2S_DMA_DATA_CMP_MIDDLE] == g_i2s_dma_data1[I2S_DMA_DATA_CMP_MIDDLE]) &&
            (g_i2s_dma_data1[I2S_DMA_DATA_CMP_MIDDLE] == g_i2s_dma_data2[I2S_DMA_DATA_CMP_MIDDLE]) &&
            (g_i2s_dma_data2[I2S_DMA_DATA_CMP_MIDDLE] == g_i2s_dma_data3[I2S_DMA_DATA_CMP_MIDDLE]) &&
            (g_i2s_dma_data0[I2S_DMA_DATA_CMP_END] == g_i2s_dma_data1[I2S_DMA_DATA_CMP_END]) &&
            (g_i2s_dma_data1[I2S_DMA_DATA_CMP_END] == g_i2s_dma_data2[I2S_DMA_DATA_CMP_END]) &&
            (g_i2s_dma_data2[I2S_DMA_DATA_CMP_END] == g_i2s_dma_data3[I2S_DMA_DATA_CMP_END]))
        {
            osal_printk("recv OK\r\n");
        } else {
            for (uint32_t j = 0; j < I2S_RING_BUFFER_NUMBER; j++) {
                for (uint32_t i = 0; i < CONFIG_I2S_TRANSFER_LEN_OF_DMA_LLI; i += 2) {
                    osal_printk("%d, %d  ~  %x\r\n", j, i, g_i2s_dma_data[j][i]);
                }
            }
        }
        g_transfer_err_flag = 0;
    }
}

static void i2s_dma_trans_done_callback(uint8_t intr, uint8_t channel, uintptr_t arg)
{
    unused(channel);
    unused(arg);
    switch (intr) {
        case HAL_DMA_INTERRUPT_TFR:
            g_cb_count++;
            if (g_cb_count >= I2S_RING_BUFFER_NUMBER) {
                g_transfer_err_flag = 1;
            }
            if (osal_event_write(&g_i2s_dma_id, I2S_DMA_TRANSFER_EVENT) != OSAL_SUCCESS) {
                osal_printk("osal_event_write fail!\r\n");
                return;
            }
            break;
        case HAL_DMA_INTERRUPT_ERR:
            osal_printk("i2s DMA transfer error.\r\n");
            break;
        default:
            break;
    }
}

static void *i2s_dma_slave_task(const char *arg)
{
    unused(arg);
    if (osal_event_init(&g_i2s_dma_id) != OSAL_SUCCESS) {
        return NULL;
    }
    uapi_i2s_deinit(SIO_BUS_0);
    uapi_i2s_init(SIO_BUS_0, NULL);
    sio_porting_i2s_pinmux();
    i2s_config_t config = {
        .drive_mode= SLAVE,
        .transfer_mode = STD_MODE,
        .data_width = THIRTY_TWO_BIT,
        .channels_num = TWO_CH,
        .timing = NONE_TIMING_MODE,
        .clk_edge = RISING_EDGE,
        .div_number = I2S_DIV_NUMBER,
        .number_of_channels = I2S_CHANNEL_NUMBER,
    };
    i2s_dma_attr_t attr = {
        .tx_dma_enable = 0,
        .tx_int_threshold = I2S_TX_INT_THRESHOLD,
        .rx_dma_enable = 1,
        .rx_int_threshold = I2S_RX_INT_THRESHOLD,
    };

    uapi_i2s_set_config(SIO_BUS_0, &config);
    uapi_i2s_dma_config(SIO_BUS_0, &attr);

    /* DMA init. */
    uapi_dma_init();
    uapi_dma_open();

    dma_channel_t dma_channel = uapi_dma_get_lli_channel(0, HAL_DMA_HANDSHAKING_MAX_NUM);
    for (uint8_t i = 0; i < I2S_RING_BUFFER_NUMBER; i++) {
        if (i2s_add_dma_lli_node(i, dma_channel, i2s_dma_trans_done_callback) != 0) {
            osal_printk("i2s_add_dma_lli_node fail!\r\n");
            return NULL;
        }
    }

    if (uapi_dma_enable_lli(dma_channel, i2s_dma_trans_done_callback, (uintptr_t)NULL) == ERRCODE_SUCC) {
        osal_printk("dma enable lli memory transfer succ!\r\n");
    }
    hal_sio_set_rx_enable(SIO_BUS_0, 1);

    while (1) {
        if (!(osal_event_read(&g_i2s_dma_id, I2S_DMA_TRANSFER_EVENT, OSAL_WAIT_FOREVER,
                              OSAL_WAITMODE_AND | OSAL_WAITMODE_CLR))) {
            uapi_watchdog_kick();
            continue;
        }
        app_printf_err();
    }
    return NULL;
}

static void i2s_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)i2s_dma_slave_task, 0, "I2sDmaSlaveTask",
                                      I2S_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2S_TASK_PRIO);
    }
    osal_kthread_unlock();
}

/* Run the i2s_entry. */
app_run(i2s_entry);

注意事项

若要保证接收数据的连续性,建议slave端使用I2S DMA循环链表接收数据,普通的I2S接口因为主从时序性的问题,接收数据无法完全保证数据连续性,可能会出现丢失数据或者接收到0的存在。

TRNG

概述

TRNG是一种随机数获取接口。

功能描述

TRNG模块提供的接口及功能如下如下:

  • uapi_drv_cipher_trng_get_random(uint32_t *randnum):获取硬件随机数(randnum表示指向存储生成的随机数的缓冲区的指针)。

  • uapi_drv_cipher_trng_get_random_bytes(uint8_t *randnum, uint32_t size):获取指定大小的硬件随机数(randnum表示指向存储生成的随机数的缓冲区的指针,size表示生成随机数的大小)。

KCONFIG配置

开发指南

注意事项

附:MIDDLEWARE

配置宏具体描述如表1所示。

表 1 middleware相关宏描述

归属菜单

宏名称(CONFIG_是默认添加的前缀)

kconfig界面显示的具体描述

使用场景

是否已经实现

是否依赖其他宏

是否选择打开或修改配置

PM

CONFIG_PM_SLEEP_RECORD_ENABLE

ENABLE SLEEP RECORD.

记录睡眠状况信息

默认关闭,视情况选择

CONFIG_PM_POWER_GATING_ENABLE

ENABLE POWERGATING.

使能POWERGATING

默认打开,建议打开

CONFIG_PM_VETO_TRACK_ENABLE

VETO ENABLE TRACK.

主要用于睡眠投票使用

默认关闭,视情况选择

CONFIG_PM_LIGHT_SLEEP_THRESHOLD_MS

The light sleep time threshold.

浅睡时间阈值

默认值是5ms,建议不做修改

CONFIG_PM_DEEP_SLEEP_THRESHOLD_MS

The deep sleep time threshold.

深睡时间阈值

默认值是10ms,建议不做修改

CONFIG_PM_DEBUG

Enable DEBUG Record.

主要用于PM调试追踪使用

默认打开,建议打开

CONFIG_PM_FSM_TRACE_NUM

Number of FSM Traces.

配置PM FSM系统状态机数量,用于debug使用

默认值是32,建议不做修改

CONFIG_PM_ENABLE_WAKEUP_INTERRUPT

Enable wakeup interrupt.

主要用于使能唤醒中断

默认打开,建议打开

CONFIG_PM_SYS_SUPPORT

Pm sys support, switch system (work,standby,sleep) state.

主要用于PM状态切换使用

默认关闭,建议关闭

CONFIG_PM_SYS_SUPPORT_MSGQUEUE

Pm sys support message queue.

PM状态机支持使用消息队列

CONFIG_PM_SYS_SUPPORT

默认关闭,建议关闭

CONFIG_PM_SYS_STACK_SIZE

Pm sys stack size.

PM状态机支持使用消息队列的栈大小

CONFIG_PM_SYS_SUPPORT_MSGQUEUE

默认关闭,视情况选择,建议关闭

DFX

CONFIG_DFX_SUPPORT_USERS_PRINT

ENABLE USER TO REGISTER LOG API.

主要用于使能用户自定义的寄存器日志信息接口

默认关闭,视情况选择

CONFIG_DFX_SUPPORT_PRINT

dfx print with level limit.

主要用于打印DFX日志信息

默认关闭,视情况选择

CONFIG_CONFIG_ERRCODE_SUPPORT_REPORT

Enable user to register errcode report callback.

主要用于使能用户自定义接口errcode上报回调使用

默认关闭,视情况选择

AT

CONFIG_AT_SUPPORT_PLT

platform at command.

主要用于平台AT命令

默认关闭,视情况选择

CONFIG_AT_DEBUG_REGISTER_CCPRIV

ccpriv at debug command.

主要用于debug AT命令

默认关闭,视情况选择

CODEC

CONFIG_CODEC_ENABLE_SBC

SBC

支持使用SBC编解码

默认打开,视情况选择

CONFIG_CODEC_ENABLE_MSBC

MSBC

支持使用MSBC编解码

默认关闭,视情况选择

CONFIG_CODEC_ENABLE_OPUS

OPUS

支持使用OPUS编解码

默认关闭,视情况选择

CONFIG_CODEC_ENABLE_L2HC

L2HC

支持使用L2HC编解码

默认关闭,视情况选择