一、时钟的配置:

AG32 通常使用 HSE 外部晶体(范围:4M~16M)。
AG32 中不需要手动设置 PLL 时钟(时钟树由系统自动配置,无须用户关注)。用户只需在配置文件中给出外部晶振频率和系统主频即可。
配置方式:
在 ve 文件中配置如下:

这里配置的值,会在mcu的系统初始化时,代码中自动获取并使能。
系统主频的可配置范围:参考 datasheet 中各型号的最高主频(通常是 248M)
外部晶振的可配置范围:4 ~ 16
这里是最简单的描述,只是使用MCU时可以简单如上配置。

如果需要使用外部有源晶振,或者使用内部振荡器(内部振荡器有 5%以内误差),或者 cpld 中需要额外主频输入,请参考AG32时钟的配置与限制

.

二、管脚的配置:

1. 信号线和关键的分离:

这里先描述两个概念:信号线和管脚

信号线,是指MCU里能操作的信号线,比如gpio1_1/uart0_tx/spi_cs等等,在连接在mcu内核上的信号接点;

管脚,就是芯片裸露在外边的引脚(32PIN的有32个管脚,64PIN的有64个管脚...)

在传统芯片里(如ST、GD等),一颗芯片出来后,信号线和管脚是绑定死的(最多一个管脚可以复用成几种信号)。

但在AG32里,信号线和管脚是彻底分离的。

也就是说,可以把任意信号线绑定到任意引脚。

打个比方,比如管脚1,这个管脚用于什么功能,完全是由用户来自行配置的。用户可以把它配置成uart0_tx,也可以配置成GPIO1_2,还可以配置成spi_cs,等等。

这种管脚可配置性,为应用设计带来巨大的便利性:

首先,为PCB布线带来了很大的便利性。按最近的引脚走线,不用绕来绕去。

其次,可以提升管脚利用度(应用中没用到的外设不去配置,那它就不会占用管脚)。 比如,本来需要100脚的ST才能满足的外设需求,用AG芯片可能32脚的就够了。

2.管脚的配置方法:

管脚的配置,在ve文件中 信号线<->管脚 一行一行对应即可

如:GPIO0_0 PIN_2

本文后续会逐项讲述各种外设怎么配置信号和管脚。

先概述下配置引脚的两个注意项:

1. 信号线名称:

信号线名称是在VE里配置使用的。

比如:uart0的tx信号线名称是UART0_UARTTXD,gpio第0组的第0个信号线名称是 GPIO0_0。

那么,对于mcu端来说,共有哪些信号线呢?

mcu的信号线名称,全部定义在《AGRV2K_逻辑设置.pdf》中的“Function_Pin 列表”, 或点击这里查看。

配置举例:

  1. 配置GPIO0_1为PIN2,则定义:GPIO0_1 PIN_2
  2. 配置UART1_TX为PIN3,则定义:UART1_UARTTXD PIN_3
  3. 配置SPI0的clk为PIN4,则定义:SPI0_SCK PIN_4
  4. 配置CAN0的TX为PIN5,则定义:CAN0_TX0 PIN_5

2. 注意不可配置的管脚

上述描述的“任意信号线可以绑定到任意引脚”,只是便于理解。真实使用时,有少量管脚是不可配置的。

不可配置包括:基础类(电源、时钟、地、RESET、BOOT0)、ADC(DAC/CMP)、USB。

除去这些,其他的外设均为配置管脚。

另外,其中的ADC和USB的管脚,如果不接ADC和USB,仍然是可以被用做普通IO的。

具体每种封装下管脚的详细定义,请参考文档《AG32_pinout_100_64_48_32_2K.xlsx》。

打开后,如下图:

凡是带有IO的,都是可以被配置的管脚。

比如:上图的PIN_33,如果ADC的channal14在使用,那这个管脚只能用于这路ADC。如果这路adc没有使能,则PIN_33可以配置为其他信号线(用于其他功能)。

32/48/64/100,各种封装下的引脚定义是不同的,注意找到跟实际匹配的那组。

.

三、GPIO的使用:

可用GPIO(非管脚):

AG32芯片内部可用gpio共有80个,分为10组,每组8个

代码中各组对应为:GPIO0、GPIO1、GPIO2、... GPIO9

组内各IO用bit表示:GPIO_BIT0、GPIO_BIT1、GPIO_BIT2、...、GPIO_BIT7

使用时,用【组ID+组内id】来标识唯一的IO。

这里和ST是相仿的,ST分为GPIOA/GPIOB/GPIOC..., PIN_1/PIN_2/PIN_3...

AG32为:GPIO0/GPIO1/GPIO2..., GPIO_BIT0/GPIO_BIT1/GPIO_BIT2...

在VE中的命名方式如:GPIO0_0,表示的是第0组的第0个IO。

对外映射

上边已经讲述过,AG32中MCU信号线和管脚是分离的,GPIO信号线也不例外。

程序中用到的GPIO要连接到管脚PIN,才能最终使用。

GPIO在ve文件中配置如下图:

上图的示例,就是把 gpio4_1映射到管脚92。 (VE中的#为注释)

在AG32中,必须映射后,代码中操作gpio时,才会真正使能到硬件管脚。

这里GPIOx_y的角标取值范围:x (0 ~ 9), y (0 ~ 7)

PIN_z的取值范围:z小于所用芯片的最大引脚数

在取值范围内,满足限制条件下,任意GPIOx_y可以映射到任意PIN_z。

(“哪些管脚不能被使用”的限制条件,参考文档:AGRV2K_逻辑设置.pdf)

这里配置的GPIO0_0,等同于代码中的 (GPIO0, GPIO_BIT0)。

对SDK下gpio样例代码的解释:

在examples/example/example_gpio.c里,只有最简单的一个IO翻转示例:

GPIO_Toggle是反转函数。从这个函数点进去(Ctrl +鼠标左键),可以看到gpio函数集:

EXT_GPIO和EXT_GPIO_BITS是定义的gpio宏。

注:这里能用GPIO_Toggle,是因为在前边的board_init()函数里已经初始化过该gpio了。

点进去EXT_GPIO宏可以看到定义:

注意,这里的EXT_GPIO_BITS值为0b1110(即:0x0e),意思是BIT1/BIT2/BIT3的3位一 起操作。0b1110这里等价为:GPIO_BIT1|GPIO_BIT2|GPIO_BIT3

那么,GPIO_Toggle(EXT_GPIO, EXT_GPIO_BITS) 这句代码的意思,就是对GPIO4_1/GPIO4_2 /GPIO4_3的3个IO一起反转。

在样例的VE里,定义引脚如下:

那么,跑样例时,就可以看到开发板上的3个LED(LED1 LED2和LED3)闪烁了。

而LED4因为没有操作该IO,LED4依然是灭的状态。

1.配置为输出(举例)

用 pin3 引脚接 led 灯,并控制亮灯(高为亮)。

步骤一:

先在ve文件中定义引脚映射(gpio使用 4-1):

步骤二:

定义使用的宏:(也可以不定义,直接在代码中使用)

步骤三:

  代码中调用:

步骤四:

  编译并烧录ve文件,编译并烧录code;

补充,驱动开放的API包含

GPIO_SetOutput/GPIO_SetInput    ---设置IO为输入输出

GPIO_SetHigh/GPIO_SetLow      ---置高置低

GPIO_Toggle                   ---高低切换

GPIO_IntConfig                 ---配置中断触发方式

GPIO_EnableInt/GPIO_DisableInt/GPIO_ClearInt  ---中断控制

GPIO_AF_ENABLE/GPIO_AF_DISABLE         ---切换GPIO模式(如果有复用)

Gpio中断函数SDK中已经默认指定:GPIOx_isr

如果要重定向为函数,通过 plic_isr[GPIOx_IRQn] = gpio_xxx_isr 的方式来设置;

2.配置为输入(举例)

用pin96接外部按键,处理按键消息;

步骤一:

  在ve文件中配置gpio4_5映射到pin96;

步骤二:

  在测试代码中,编写IO初始化,并实现中断函数:

  注:这里的中断函数GPIO4_isr无需程序中再次指定。

步骤三:

 如果外部电路没有上拉设计,需要内部上拉。设置方式(二选一):

  1. 在工程的example_board.asf文件中,
  2. 或者在\platforms\AgRV\boards\agrv2k_x0x\board.asf 文件中(不建议),

添加以下红框内的语句:

内容:set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to PIN_96

或者使用:set_instance_assignment -name CFG_KEEP -to PIN_96 2'b10 -extension

(以上两个上拉的语句意义等价,使用时二选一)

注意:如果是cpld中要实现上拉,这里的PIN_96要用cpld里的信号名字。

如果设置下拉,则使用以下方式:

内容:set_instance_assignment -name CFG_KEEP -to PIN_32 2'b01 -extension

注意:如果是cpld中要实现下拉,这里的PIN_32要用cpld里的信号名字。

注意:上边一行添加完后,务必在后边添加回车换行(保证这行不是文件最后一行)。

步骤四:

编译并烧录ve文件,编译并烧录code;

结束。

.

3.GPIO高级用法

1. 设置内部上拉/下拉:

  参考上边步骤3的描述。

2. 引脚复用:

AG32中也有引脚复用。

常见的复用引脚,默认都是IO功能。用做复用功能(如uart_rx/uart_tx)时,需要使用函数GPIO_AF_ENABLE来设置(参考具体的样例代码)。

特别的,JTAG引脚(JNTRST、JTDO、JTDI、JTMS、JTCK),默认是JTAG功能,而不是IO功能。用作普通IO时,需要先切换设置。

可使用如下函数来设置(在main函数进入后调用即可):

SYS_DisableNJTRST()、SYS_DisableJTDI()、SYS_DisableJTDO().

(AG32默认使用jtag的swd模式,保留JTMS、JTCK即可通过jtag烧录仿真)。

3. 设置为OD模式:

IO默认输出是PP模式。

如果要设置为OD模式,有两种方式(二选一):

方式一、在ve里定义引脚如下(以34脚为例):

GPIO4_1 PIN_34:OUTPUT:!PIN_34_out_data

方式二、在asf文件(工程中example_board.asf)中设置:

set_instance_assignment -name AUTO_OPEN_DRAIN_PINS ON -to PIN_34

两种方式的输出效果是一样的。

注意:

如果用gpio模拟I2c的数据线,则只能使用第2种方式。

如果一定要使用方式一,必须要新增一个GPIO做为输入,绑定到一个PIN上。如:GPIO4_2 PIN_34:INPUT,然后在程序里SDA切换读的时候就用这个GPIO。

4. 配置电流输出驱动能力:

同上边上拉/下拉的设置文件(example_board.asf 文件),加入:

set_instance_assignment -name CURRENT_STRENGTH -to PIN_32 16MA

驱动电流默认为8MA,支持4MA/8MA/12MA/16MA。

.

四、MTimer的使用:

MTime是risc-v中定义的一个64位系统定时器。

在STM32中,我们一般用systick(滴答计时器)作为时基,而在riscv中我们用mechine timer(简称mtime)作为时基。

MTime中有两个主要寄存器:mtime和mtimecmp;

当mtime使能后,mtime寄存器里的值会随着tick自增,当自增到 大于等于 mtimecmp 寄存器的值时(无符号比较),就触发MTimer中断。

在移植操作系统时,mtime一般被用于系统时间片的调度定时。

相关函数:

INT_SetMtime:设置寄存器的值;

INT_SetMtimeCmp:设置比较寄存器的值;

INT_EnableIntTimer:打开timer中断;

中断函数SDK中已默认指定:void MTIMER_isr()

如果要重定向函数,通过clint_isr[IRQ_M_TIMER] = MTIMER_user_isr 来设置;

如果要设置1ms触发一次的连续定时,需要调用:

INT_SetMtime(0);

INT_SetMtimeCmp(SYS_GetSysClkFreq() / 1000); //1ms

然后在中断里重新计时:

INT_SetMtime(0);

完整代码样例请参考example部分:

MTIME较为简单,用法上只有上述用法。

.

五、Base Timer的使用:

AG32中包含2个Base Timer:分别对应TIMER0和TIMER1。

这两个timer中,每个又有两组寄存器,每组寄存器可以单独产生定时。

所以,真正可用的普通定时器有4个:TIMER0-0、TIMER0-1、TIMER1-0、TIMER1-1。

4个定时器均可独立设置。

普通定时器特点:

定时器支持16位和32位的设置,

支持3种类型分频(1分频,16分频,256分频),

支持单次定时和循环定时。

驱动API函数命名中的1和2,分别对应第一组和第二组寄存器。也就是说,一个Timer可以用于2个独立计时器。

如,TIM_Init1设置的是第一组寄存器,TIM_Init2设置的是第二组寄存器。

举例:

用Timer1的group2产生1s的循环定时:

中断函数TIMER1_isr在SDK中已经默认指定。

说明:

设置函数:TIM_Init1 <-> TIM_SetLoad1/TIM_SetSize1/TIM_SetMode1/...

中断函数:TIMER0_isr/TIMER1_isr

函数说明:

void TIM_Init1(TIMER_TypeDef *tim, uint32_t timeInUs, TIMER_ModeTypeDef mode)

作用:启动Timer0或Timer1的第一个定时器(TIM_Init2则启动第二个定时器)。

参数:tim:TIMER0 or TIMER1

  timeInUs:多少us触发定时

  mode:TIMER_MODE_PERIODIC:循环触发  TIMER_CTRL_ONESHOT:只触发一次

举例:

  TIM_Init1(TIMER0, 500000, TIMER_MODE_PERIODIC);  

表示启动TIMER0的第一个定时器,500ms触发一次定时中断,循环触发。

除了直接调用 TIM_Init1来启动一个定时外,也可以调用各个子函数来启动。

如,

  TIM_Init2(TIMER0, 500000, TIMER_MODE_PERIODIC)

功能等价于:

TIM_SetLoad2(TIMER0, SYS_GetPclkFreq() / 1000000 * 500000);

TIM_SetSize2(TIMER0, TIMER_SIZE_32);

TIM_SetMode2(TIMER0, TIMER_MODE_PERIODIC);

TIM_SetPrescaler2(TIMER0, TIMER_PRESCALE_1);

TIM_EnableInt2(TIMER0);

TIM_EnableTimer2(TIMER0);

以上几个函数中,

TIM_SetPrescaler2 是设置分频,

  三个参数可选:TIMER_PRESCALE_1/TIMER_PRESCALE_16/TIMER_PRESCALE_256

  分别表示分频数:1分频,16分频,256分频;

TIM_SetSize2 设置计时器位宽,

  两个参数可选:TIMER_SIZE_32/TIMER_SIZE_16

  表示计数器的load的位宽是32位还是16位。

TIM_SetLoad2 设置触发时间(以tick为单位)

  如果定时单位为ms,则需要将tick转为ms:SYS_GetPclkFreq()/1000000*ms

中断函数:void TIMER0_isr()

函数说明:该函数为TIMER0的中断函数;

在该函数中需要先查询是第一个还是第二个定时器,然后再清中断。

该中断函数已默认关联,不需要程序中来手工设置。

完整代码样例请参考example下example_timer.c。

.

六、gpTimer的使用:

General Purpose Timer,即通用计时器。相当于ST中的Advanced Timer.

AG32中包含5个通用计时器(GpTimer),

代码中分别对应:GPTIMER0、GPTIMER1、GPTIMER2...

通用定时器可以实现更多功能,包括:计时、生成pwm、生成任意波形、输入捕获。

5个定时器均可独立设置。

每个定时器支持4个独立通道(channel):

-- 输入捕获

-- PWM输出(边缘或中间对齐模式)

-- 单脉冲输出

主要函数:GPTIMER_Init / GPTIMER_OC_Init.

1、用于简单定时

用于简单定时,只需要关注一个函数:GPTIMER_Init,

设置好参数后,启动计时即可。

举例:

用gpTimer1产生2秒一次的定时。

这里使用到的中断函数GPTIMER1_isr,已被SDK自动设置。

2、用于pwm输出

用于pwm输出时,要设置两个函数:GPTIMER_Init 和 GPTIMER_OC_Init。

GPTIMER_Init中设置多长时间触发一次timer;

GPTIMER_OC_Init中指定pwm输出通道及设置pwm的占空比;

举例:

用gpTimer4在通道0上产生pwm输出。

除了上述的代码控制外,还需要在ve中添加映射关系:

这样的情况下,pwm才会输出到管脚上。

典型案例:呼吸灯(用timer+timerPWM来控制led灯逐渐变量逐渐变暗)

3、输出反向PWM(带死区)

样例程序,请参考网盘下“其他文档\驱动样例补充\example_gptimer_pwm_N.c”,或点击这里

4、输出任意波形

如果要输出的不是pwm的规则波形,而是不规则波形(比如正弦波),则可借助于DMA方式来模拟实现。

思路:事先在数组中定义好数据序列,然后通过dma每次搬运,作用到输出。

这部分功能,参考例程函数:TestGpTimerDma

这种方式也同样需要管脚映射。

5、用于输入捕获

用于输入捕获时,要设置两个函数:GPTIMER_Init 和 GPTIMER_IC_Init。

样例程序,请参考网盘下“其他文档\驱动样例补充\example_gptimer_capture.c”,或点击这里

.

七、Uart的使用

AG32可用的UART有5个,分别对应UART0、UART1、UART2、UART3、UART4。几个Uart的功能和用法是完全相同的。

样例工程中,UART0被做为输出log的串口。其他几个UART可被用户直接使用。

串口使用较为简单,这里讲述下几个重要函数:

初始化函数:

void UART_Init(UART_TypeDef *uart, UART_BaudRateTypeDef baudrate, UART_LCR_DataBitsTypeDef databits,

        UART_LCR_StopBitsTypeDef stopbits, UART_LCR_ParityTypeDef parity, UART_LCR_FifoTypeDef fifo)

参数说明:

  • Uart:UART0、UART1、UART2、UART3 or UART4
  • Baudrate:波特率,如 115200
  • Databits/stopbits/parity:
  • Fifo:是否开启16字节的fifo缓冲

收发函数:

  • UART_Send(UART_TypeDef *uart, const unsigned char *p, unsigned int num)
  • UART_Receive(UART_TypeDef *uart, unsigned char *p, unsigned int num, unsigned int timeout)
  • 收函数的timeout,是如果收不满num个字符,就等待多少个tick。可以为0。

样例1、实现Uart1的简单收发

  1. 增加ve对uart1的管脚配置:
  • 代码中实现如下:

样例2、使用接收中断来收取数据

代码部分可参考以下方式(新增的红框代码):

中断函数UART1_isr在SDK中已经默认关联,不用手动设置。

在中断函数中,要判别中断来源再继续操作。

上例中,收FIFO因为设置为16字节,半数触发时,收到8个字节就会触发中断。

如果每来一个字节中断接收一次,可以在UART_Init中设置参数为UART_LCR_FIFO_1,并且不用再调用UART_SetRxIntFifoLevel函数。

样例3、使用DMA收发

如果要启用DMA功能,参考sdk中自带的样例。

需要增加3个函数:

DMAC_Init:启动dma

UART_SetDmaMode:设置只要收/发dma,或收发都要dma

DMAC_Config:设置dma的详细参数。

如果收发都要dma,则需要调用2次DMAC_Config来分别设置。

函数DMAC_Config的参数说明:

void DMAC_Config(

    DMAC_ChannelNumTypeDef channel, //DMA通道

    uint32_t srcAddr, //DMA数据源地址

    uint32_t dstAddr, //DMA数据目标地址

    DMAC_AddrIncTypeDef srcIncr, //传输后源地址是否自增

    DMAC_AddrIncTypeDef dstIncr, //传输后目标地址是否自增

    DMAC_WidthTypeDef srcWidth,  //源地址传输数据的字节宽度(可选8/16/32)

    DMAC_WidthTypeDef dstWidth,  //目标地址传输数据的字节宽度(可选8/16/32)

    DMAC_BurstTypeDef srcBurst,  //源地址一次传输多少

    DMAC_BurstTypeDef dstBurst,

    uint32_t transferSize,   //传输多少次

    DMAC_FlowControlTypeDef transferType, //传输方向类型(8种)

    uint32_t srcPeripheral,      //源地址的外设类型

    uint32_t dstPeripheral      //目标地址的外设类型

)

比如,设置收DMA,会设置参数如:

DMAC_Config(DMAC_CHANNEL1,

    (uint32_t)&UART3->DR, //串口数据寄存器

    (uint32_t)rxbuf,     //收缓冲buff

    DMAC_ADDR_INCR_OFF,    //源地址不自增

    DMAC_ADDR_INCR_ON,     //目标地址自增

    DMAC_WIDTH_8_BIT,      //源数据宽度以8bit为单位

    DMAC_WIDTH_8_BIT,     //目标数据宽度以8bit为单位

    DMAC_BURST_1,

    DMAC_BURST_1,

    0,                    //传输多少次,如果是0则无限制

    DMAC_PERIPHERAL_TO_MEM_PERIPHERAL_CTRL, //外设到内存的方向

    UART3_RX_DMA_REQ,      //源数据外设类型

    0 );                  //目标数据外设类型

设置发的DMA,会设置参数如:

DMAC_Config(DMAC_CHANNEL0,

    (uint32_t)txbuf,      //发缓冲

    (uint32_t)&UART3->DR, //串口数据寄存器

    DMAC_ADDR_INCR_ON,     //发缓冲自增

    DMAC_ADDR_INCR_OFF,    //寄存器不自增

    DMAC_WIDTH_8_BIT,     //源数据宽度以8bit为单位

    DMAC_WIDTH_8_BIT,     //目标数据宽度以8bit为单位

    DMAC_BURST_1,

    DMAC_BURST_1,

    dma_count,            //要传输的数据量

    DMAC_MEM_TO_PERIPHERAL_DMA_CTRL, //内存到外设的方向

    0,                //源数据外设类型

    tx_dma_req);           //目标数据外设类型

以上完整代码样例请参考example部分。

更多样例,请参考网盘

1).dma中断:“其他文档\驱动样例补充\example_uart_dmaIrq.c”,或点击这里

2).闲时中断:“其他文档\驱动样例补充\example_uart_rcvIqr.c”,或点击这里

.

八、IIC的使用

AG32支持两路I2C,分别对应:I2C0、I2C1;

I2C是一种简单的双向两线制总线协议,半双工,支持多主从模式。I2C最大的特点之一就是有完善的应答机制。

MCU端是I2C的主端。

样例程序参考example_i2c.c

在使用I2C时的流程:

1. Ve中先配置对应的引脚:

2. 代码中时钟使能、中断使能、设置频率;

3. 使能I2C;

4. 收发数据;

   IIC的收过程和发过程,都有对应的应答流程,启动->收/发->结束。

   使用中,收发函数会被完整的封装。

   请参考例程函数(函数流程可参考,封装请自行调整):

   bool I2cReadPROM(uint8_t *mem, bool verify)

   bool I2cWritePROM(uint8_t *mem)

5. 关闭I2C:

另外,例程中还使用到了中断函数。当I2C准备好时,会触发该中断。

注意,IIC例程需要接入设备才能测通,否则在I2C_WaitForTransfer函数中会因为等不到Ack而卡住。

.

九、CAN的使用

AG32支持1路CAN,对应:CAN0

样例程序参考example_can.c

在使用CAN时的流程:

1. ve中先配置对应的引脚:

2. 代码中使能时钟、开中断:

3. 配置参数(参数较多)并开启can、开启收中断:

4. 发送数据:

5. 在中断函数中接收数据:

使用时,请参考样例修改。

.

十、USB的使用

AG32已经在工程中集成tinyUSB,可自行关联使用。

usb使用到的PIN脚,是固定的管脚,不能在ve中进行改变。

目前支持:单纯device端、单纯host端、OTG自动切换主从端;

三种情况要支持的枚举类型,可以在配置头文件中自行配置。

例程位于路径examples\usb下:

简单应用举例(使用device样例,其他两个相似):

在device的例程中,usb被同时枚举为cdc和 msc(还支持HID和MIDI)。

1. vsCode打开该文件夹工程;

打开后如图:

2. 直接烧录ve和程序bin;

烧录ve:

烧录bin:

3. 上电启动,然后USB线连接到电脑(开发板上对应micro口的那个USB口)

就可以看到PC端的U盘和cdc串口,如下图:

(cdc串口)

(U盘)

如果没有显示出来,可以烧录并跟踪程序,看是否出现board_init失败(板子不同, 可能会带来board初始化的失败)

以上是demo验证。

如果要集成到自己的工程(如,要在example中使用),需要修改:

1. Platformio.ini中增加对tinyUsb的引用:

注意,引用多个库时,用逗号隔开,并且逗号后边要加空格。

2. ve文件中增加:

3. 代码部分调整:

将tinyusb下的src路径文件,修改main.c后放入example下的src。

修改点:

Main.c文件重命名;main()函数重命名;去除main()中的board_init函数;

重命名后的main函数在example下的main()中调用;

4. 编译并烧录ve和代码,即可正常运行。

在例程中,USB描述符、回调、配置(CDC、HID、MSC、MIDI)均已通过接口开放出来, 在src路径下的.c.h中。用户可根据自己的需求订制或修改。

可通过头文件中的宏来配置要开放的枚举类型:

更多配置部分及usb接口使用详解,可参考sdk下tinyUSB路径下的文件描述,或者参考tinyUSB官方介绍。

目前样例是cdc+msc,更多的样例工程(目前有:cdc_msc_hid_midi、两个cdc、单个cdc、单个msc、在103上集成usb),

请从网盘下载:https://pan.baidu.com/s/1wcBnqnray7bu4IURDIoDDQ?pwd=1205 目录:\其他文档\常用样例工程补充\

.

十一、MAC的使用

AG32支持MAC模块。

支持RMII/MII接口。

目前SDK中集成了Lwip2.1.0版本。在lwip样例中,使用了server端的功能。

样例使用:

打开样例工程lwip,

在开发板上测试例程时,步骤:

  1. 分别编译并烧录ve和code;
  2. 然后用网线连接 PC和开发板,并修改PC的IP地址为 192.168.5.2;
  3. 在PC的浏览器上输入: http://192.168.5.1

此时,可以在网页上看到开发板中展示的画面:

移植到自己的板子上时,注意两项配置:

1. 根据自己的板子,可能需要修改的是phy地址:

2. 修改ve配置文件中mac相关IO对应,如:

上层部分,使用什么样的网络,则自行配置lwip。

.

十二、SPI的使用

AG32支持两路SPI,分别对应:SPI0、SPI1;

两路是功能对等的。仅支持SPI-Master端。

SPI是一种全双工同步的串行通信,可支持高速数据传输。

采用主-从模式(Master-Slave) 的控制方式,通过对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备。

样例程序参考example_spi.c

在例程中,使用了SPI_FLASH的dma方式。

注意,这里的SPI驱动都是针对SPI_FLASH的封装,并不用于通用SPI。

SPI支持1线、2线和4线。

如果是用于通用SPI外设(非FLASH),请参考样例程序example_spi_common.c。

需要注意的是:

由于这里SPI底层是针对FLASH使用的封装,所以对通用SPI外设支持并不全面。

如果要用于普通外设,则该外设必须满足如下时序:

  • 1. SPI交互时第一段只能是tx(不能是rx);
  • 2. 收和发不能同时进行(只能是发完再收);
  • 3. 并且极性(CPOL)和相位(CPHA)的设置值都是1。

更详细的使用说明和样例,请参考example和datasheet。

补充:

从SDK1.2.4版本开始,增加了对通用SPI的支持。

原 example_spi_common.c 中提供的函数:

1. Send:单纯发送数据,字节数不限制;

2. SendAndRecv:在一个片选周期内,发送一段数据,再接收一段数据;

其中发送长度最长 4byte,接收长度不限。

(如果发送长度要更长,请自行在 C 驱动中扩展。)

这个版本开始,会在以上基础上,扩展出来两个函数:

3. Recv:单纯收取数据,接收长度不限; 

4. SendWithRecv:在发送数据的同时来收(双向传输),而不是发完后再收。

收取数据和发送数据的长度等长,并且增加了对极性和相位的设置。

但这两个函数,需要cpld的支持,用起来比较费劲。

更详细的使用说明,请参考 网盘下 \其他文档\AG32下spi的拓展使用\AG32下spi的拓展使用.pdf

.

十三、ADC/DAC的使用

ADC/DAC包含模拟电路,需要cpld部分的支持。

AG32自带一套cpld逻辑(默认ip),

在默认的ip中,支持3路ADC和2路DAC,1路比较器CMP(双通道,可独立运行)。

使用样例ADC:

在样例代码example_analog.c中,adc默认是宏关闭的。可在platformio.ini中打开该宏:

【-DIPS_ANALOG_IP】

同时,使能默认的ip,在platformio.ini中配置:

然后在main()函数中放开TestAnalog() 即可。

注意,第一次打开ADC/DAC功能时,需要重新编译烧录一次ve:

ADC共有15个channal,每个channal均可配置到任一个ADC上。

ADC的简单使用:

参考TestAdc函数,ADC不需要在ve里管脚映射,不需要设置IO复用。

使用以下4个函数即可:

如果需要多次转换,则重复调用后3个函数。

如果需要不间断的循环采集,例程部分点击这里

如果有自己的cpld,希望裁剪ADC,或者对ADC有更高的定制,请参考网盘下ADC的专题讲解《analog中对ADC的剪裁.pdf》,以及《Analog代码分析.pdf》,这部分比较复杂。

.

十四、WatchDog的使用

AG32支持1个独立看门狗模块。

WDOG主要性能:

●自由运行的递减计数器

●看门狗被激活后,则在计数器计数至0x000时产生复位

默认情况下,在debug状态下看门狗是不工作的。

使用逻辑:

1. 为看门狗使能时钟,并使能中断(中断可选);

   SYS_EnableAPBClock(APB_MASK_WATCHDOG0);

   INT_EnableIRQ(WATCHDOG0_IRQn, WDOG_PRIORITY);

2. 启动看门狗,同时设置看门狗时间;

   WDOG_Init(SYS_GetPclkFreq()); // 1 second

3. 定时(或在中断函数中)喂狗;

   WDOG_Feed();

   看门狗中断函数为:void WATCHDOG0_isr();

4. 系统启动后,可以通过查看寄存器,确定是否为看门狗导致的重启;

   if (READ_BIT(SYS->RST_CNTL, SYS_RSTF_WDOG)) { /*reset by Watchdog*/}

   在处理中需要自行清除掉该标记,以避免下次重启时误判。

看门狗中是否使用中断:

  1. 如果开启看门狗中断,则中断来了后,必须要在中断里清除标记(清除中断的动作就是喂狗的动作),不然中断函数会一直被调用。
  2. 如果关闭看门狗中断,则必须要应用程序在重启时间到来前及时喂狗。
  3. 还可以中断函数和应用程序两者一起喂狗。即:开启看门狗中断,然后在中断函数和应用中都调用喂狗函数。如果应用中的喂狗周期比中断周期短,则中断函数永远不会被触发。

看门狗的中断时间和重启时间:

   在WDOG_Init函数中设置的时间,是看门狗中断来一次的时间。而重启的时间,是两个这样的时间(2倍整)。

   比如:WDOG_Init设置了5秒,没启动看门狗中断,当应用中10秒没喂狗动作时,系统才被重启。

.

十五、RTC的使用

RTC(Real Time CLock)是个独立的定时器。

RTC模块拥有一个连续计数的计数器,可进行软件配置,提供时钟日历的功能。RTC还 包含用于管理低功耗模式的自动唤醒单元。

只要芯片的备用电源一直供电,在mcu断电情况下RTC仍可以独立运行。

RTC只支持LSE作为时钟源(32768);

支持3种中断类型:

  1. 秒中断;
  2. 溢出中断;
  3. 定时中断;

主要寄存器:

  • RTC控制寄存器 (RTC_CRH, RTC_CRL)
  • RTC预分频装载寄存器 (RTC_PRLH, RTC_PRLL)
  • RTC预分频余数寄存器 (RTC_DIVH, RTC_DIVL)
  • RTC计数器寄存器 (RTC_CNTH, RTC_CNTL)
  • RTC闹钟寄存器 (RTC_ALRH ,RTC_ALRL)

以上寄存器都有对应的函数来进行操作。

执行逻辑:

RTC_PRL(预分频装载寄存器)的值决定TR_CLK脉冲产生的周期,RTC_DIV(预分频器 余数寄存器)可读不可写,当RTCCLK的一个上升沿到来,RTC_DIV的值减1,减到0 后硬件重载为RTC_PRL的值同时产生一个TR_CLK脉冲,一个TR_CLK脉冲的到来会使 RTC_CNT(计数器寄存器)的值加1,同时产生一个RTC_Second中断(由软件配置是否 使能,“秒中断”并不一定是一秒触发一次,具体是根据RTC时钟和RTC_PRL的值决定)。

当RTC_CNT的值溢出后从0开始,并产生一个溢出中断(由软件配置是否使能)。当 RTC_CNT等于RTC_CNTRTC_ALR(闹钟寄存器)时,产生一个闹钟中断(由软件配置是 否使能,可在用在系统待机模式下唤醒系统)。

BKP备份寄存器

备份寄存器有16组16位的寄存器(每组2个)。可用来存储64个字节数据。

它们处在备份区域,当VDD电源切断,仍然由VBAT维持供电。

当系统在待机模式下被唤醒,或者系统复位或者电源复位,它们也不会复位。

一般用 BKP 来存储 RTC 的校验值或者记录一些重要的数据。

可通过以下两个函数接口来读写:

  • RTC_WriteBackupRegister(uint16_t idx, uint16_t value)
  • RTC_ReadBackupRegister(uint16_t idx)

RTC常用于三种定时:

1. 秒中断

RTC的秒中断功能类似SysTick系统滴答的功能。RTC秒中断功能其实是每计数一次 就中断一次。注意,秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决 定的“秒”的时间,当然也是可以做到1秒钟中断一次。通常通过函数 RTC_SetPrescaler(32768) 来进行设置。

完整代码需要:

  • RTC_Init(board_rtc_source());
  • RTC_EnableInt(RTC_FLAG_SEC);
  • RTC_SetPrescaler(32768);
  • RTC_SetOutputMode(RTC_OUTPUT_CLOCK);

2. 溢出中断

溢出中断是RTC_CNT的值溢出时触发的中断。

3. 定时中断

使用时一般设置秒中断周期为1s,用RTC_CNT计数器计数。假如1970设置为时间 起点为0s,通过当前时间的秒数计算得到当前的时间。RTC_ALR是设置闹钟时间, RTC_CNT计数到RTC_ALR就会产生计数中断。

中断函数在SDK中已经默认关联,函数名:RTC_isr

在中断函数中,需要先判断中断来源,再进行相应的处理。

.

十六、中断与异常

RISC-V系统支持中断嵌套。但SDK中推荐(并默认)使用非嵌套中断方式,如果需要嵌套方式,请打开宏AGRV_NESTED_INTERRUPT。

打开宏的方式,请在 platformio.ini 里通过 build_flags = -DAGRV_NESTED_INTERRUPT 来实现。使能中断可嵌套后,高优先级(数字越大优先级越高)的中断,会打断正在执行中的低优先级的中断。

中断系统被封装在SDK的interrupt.c中,由INT_Init()函数来完成初始化。

RISC-V有两套中断向量,分别对应于PLIC和CLINT。目前只有MTime是对应到CLINT 中断,其他都对应于PLIC中断。

用户级别的中断设置,都通过函数INT_EnableIRQ(uint32_t irq, uint32_t priority)来设置使能。中断向量表和中断函数名都已内置定义。从用户角度,只需要设置中断使能即可使 用对应的中断函数。

开关系统总中断函数:

INT_EnableIntGlobal/INT_DisableIntGlobal

系统中断向量表及中断函数名,可从AltaRiscv.h中查看。如:

向量ID:TIMER0_IRQn

中断函数:TIMER0_isr

异常和中断都在这里处理。

异常处理:

如果程序运行中跑飞(进入异常中断函数exception_handler),可以参考专题(异常处理)。

.

IO口配置为外部中断的三种形式:

EXT_INT 是低电平中断,不支持边沿触发。

local int 是高电平中断,不支持边沿触发。

如果要配置边沿触发,只能使用 GPIO中断。

.

EXT_INT的用法:

EXT_INT是外部中断,在AG32中有8组可用。EXT_INT是低电平中断。

使用方法:

1.VE中配置对应引脚,如下图:

2.代码中使能该中断,配置中断函数。如下图:

以上两处配置完后,当PIN_51的电平变低后,将触发Eint_isr中断函数。

使用特点:

1.这个中断是不用清的。只要电平存在,中断就一直会进来。就算清了,执行完后,马上还会进来。电平置高,中断就不再进来。

2.使用时,该引脚要配置成上拉。不然一启动可能就会进中断的。

3.这个信号也可以从cpld过来。在VE里配置。(这时,VE里该信号不配置成PIN引脚,而配置成cpld信号)

.

Local int的用法:

Local int是从cpld接入mcu的信号。有4组可用。是高电平中断。

Local int不能配置为外部引脚接入,故无需在VE文件里配置。

当cpld中设置local_int[x]为高时,将触发mcu端的local int中断。

mcu端使用如下:

注意:

mcu不会缓存local_int的上升信号。

当cpld给出的这个高电平脉冲太短,或者cpld置高期间mcu端正在处理其他中断,都会导致mcu漏掉该中断。

正常的做法是:

1.cpld要触发mcu中断时,置高该信号 local_int[3] = 1;

2.Mcu触发中断后,要在中断里“通知”cpld,可以置低该信号。(这里的“通知”,可以是用gpio信号输出到cpld)

这样“一来一回”的一个交互,是最安全的做法。

.

十七、系统休眠(sleep、stop、stanby)

AG32支持3种休眠方式:sleep、stop、standby。

代码样例参考example_system.c

其中进入standby后,有三种唤醒方式:IWDG、RTC(Alarm)、WAKEUP(该引脚上升沿)。

如果要使用低功耗,在系统进入休眠前,需要先关掉不需要的外设时钟。

内核电流大小:

  • STOP mode: 5.5mA
  • STANDBY mode:  5uA

更多信息,参考《AG32 MCU Reference Manual.pdf》

另外注意,standby的时候整个3.3v电源域是关掉的,这个时候cpld也就关掉了。想要 cpld运行的话只能进入stop。

.

十八、使用自定义的logic

上边章节“ADC/DAC的使用”部分,描述了使用默认logic的方法。

默认logic中只包含了ADC/DAC/CMP的功能,如果有额外需求,则需要构建自定义logic。

在自定义logic中,可以编写cpld,为芯片增加更多的功能支持。

构建的详细流程,参考《AG32下fpga和cpld的使用入门.pdf》,或点击这里

在构建自定义logic时,需要platformio.ini中设置三项:

其中,

ip_name 为新建的user_ip名字;

logic_dir 为生成的文件夹名称;

board_logic.ve 为生成自定义ip时共同使用的ve文件;

总结:

三种情况(不用ADC、仅用ADC/DAC/CMP、使用更多的cpld功能),在platformio.ini 文件中的配置对比:

1. 如果连默认ip都用不到(没有用ADC/DAC/CMP):

  只需要配置一项:

  •   board_logic.ve = project_xxx.ve

2. 如果仅用到默认ip(使用到ADC/DAC/CMP):

  需要配置两项:

  •   board_logic.ve = project_xxx.ve
  •   ip_name = analog_ip

3. 如果要用到自定义logic(需要更多的cpld功能):

  需要配置三项:

  •   board_logic.ve = project_xxx.ve
  •   ip_name = xxxxx_ip
  •   logic_dir = logic

关于自定义logic,这里仅是配置说明,更多信息参考cpld部分的说明。

.