模式
MCU驱动使用
MCU驱动使用
一、时钟的配置
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 列表”,或点击这里查看。
配置举例:
- 配置
GPIO0_1
为PIN2
,则定义:GPIO0_1 PIN_2
- 配置
UART1_TX
为PIN3
,则定义:UART1_UARTTXD PIN_3
- 配置
SPI0
的clk
为PIN4
,则定义:SPI0_SCK PIN_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初始化,并实现中断函数:

步骤三:
如果外部电路没有上拉设计,需要内部上拉。设置方式(二选一):
在工程的 example_board.asf
文件中, 或者在 \platforms\AgRV\boards\agrv2k_x0x\board.asf
文件中(不建议), 添加以下红框内的语句:
内容:
plaintext
set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to PIN_96
或者使用:
plaintext
set_instance_assignment -name CFG_KEEP -to PIN_96 2'b10 -extension
(以上两个上拉的语句意义等价,使用时二选一)
注意:如果是CPLD中要实现上拉,这里的 PIN_96
要用CPLD里的信号名字。
如果设置下拉,则使用以下方式:

内容:
plaintext
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
函数进入后调用即可):
c
SYS_DisableNJTRST();
SYS_DisableJTDI();
SYS_DisableJTDO();
(AG32默认使用JTAG的SWD模式,保留 JTMS
、JTCK
即可通过JTAG烧录仿真)。
3. 设置为OD模式
IO默认输出是PP模式。
如果要设置为OD模式,有两种方式(二选一):
方式一、在 ve
里定义引脚如下(以34脚为例):
plaintext
GPIO4_1 PIN_34:OUTPUT:!PIN_34_out_data
方式二、在 asf
文件(工程中 example_board.asf
)中设置:
plaintext
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
文件),加入:
plaintext
set_instance_assignment -name CURRENT_STRENGTH -to PIN_32 16MA
驱动电流默认为8MA,支持4MA/8MA/12MA/16MA。
四、MTimer的使用
MTime是RISC-V中定义的一个64位系统定时器。
在STM32中,我们一般用 systick
(滴答计时器)作为时基,而在RISC-V中我们用 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触发一次的连续定时,需要调用:
c
INT_SetMtime(0);
INT_SetMtimeCmp(SYS_GetSysClkFreq() / 1000); //1ms
然后在中断里重新计时:
c
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
函数说明
c
void TIM_Init1(TIMER_TypeDef *tim, uint32_t timeInUs, TIMER_ModeTypeDef mode)
作用:启动 Timer0
或 Timer1
的第一个定时器(TIM_Init2
则启动第二个定时器)。
参数:
tim
:TIMER0
或TIMER1
timeInUs
:多少us触发定时mode
:TIMER_MODE_PERIODIC
(循环触发) 或TIMER_CTRL_ONESHOT
(只触发一次)
举例:
c
TIM_Init1(TIMER0, 500000, TIMER_MODE_PERIODIC);
表示启动 TIMER0
的第一个定时器,500ms触发一次定时中断,循环触发。
除了直接调用 TIM_Init1
来启动一个定时外,也可以调用各个子函数来启动。
如,
c
TIM_Init2(TIMER0, 500000, TIMER_MODE_PERIODIC)
功能等价于:
c
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可被用户直接使用。
串口使用较为简单,这里讲述下几个重要函数:
初始化函数
c
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
或UART4
Baudrate
:波特率,如 115200Databits
/stopbits
/parity
:Fifo
:是否开启16字节的FIFO缓冲
收发函数
c
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的简单收发
增加 ve
对 uart1
的管脚配置:

代码中实现如下:

样例2、使用接收中断来收取数据
代码部分可参考以下方式(新增的红框代码):

中断函数 UART1_isr
在SDK中已经默认关联,不用手动设置。
在中断函数中,要判别中断来源再继续操作。
上例中,收FIFO因为设置为16字节,半数触发时,收到8个字节就会触发中断。
如果每来一个字节中断接收一次,可以在 UART_Init
中设置参数为 UART_LCR_FIFO_1
,并且不用再调用 UART_SetRxIntFifoLevel
函数。
样例3、使用DMA收发
如果要启用DMA功能,参考SDK中自带的样例。
需要增加3个函数:
DMAC_Init
:启动DMAUART_SetDmaMode
:设置只要收/发DMA,或收发都要DMADMAC_Config
:设置DMA的详细参数。
如果收发都要DMA,则需要调用2次 DMAC_Config
来分别设置。
函数 DMAC_Config
的参数说明:
c
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,会设置参数如:
c
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,会设置参数如:
c
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时的流程:
Ve
中先配置对应的引脚:

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

- 使能I2C;

收发数据;
IIC的收过程和发过程,都有对应的应答流程,启动->收/发->结束。
使用中,收发函数会被完整的封装。
请参考例程函数(函数流程可参考,封装请自行调整):
cbool I2cReadPROM(uint8_t *mem, bool verify) bool I2cWritePROM(uint8_t *mem)
关闭I2C:

另外,例程中还使用到了中断函数。当I2C准备好时,会触发该中断。
注意,IIC例程需要接入设备才能测通,否则在 I2C_WaitForTransfer
函数中会因为等不到Ack而卡住。
九、CAN的使用
AG32支持1路CAN,对应:CAN0
样例程序参考 example_can.c
在使用CAN时的流程:
ve
中先配置对应的引脚:

- 代码中使能时钟、开中断:

- 配置参数(参数较多)并开启CAN、开启收中断:

- 发送数据:

- 在中断函数中接收数据:

使用时,请参考样例修改。
十、USB的使用
AG32已经在工程中集成 tinyUSB
,可自行关联使用。
USB使用到的PIN脚,是固定的管脚,不能在 ve
中进行改变。
目前支持:单纯device端、单纯host端、OTG自动切换主从端;
三种情况要支持的枚举类型,可以在配置头文件中自行配置。
例程位于路径 examples\usb
下:

简单应用举例(使用device样例,其他两个相似):
在device的例程中,USB被同时枚举为CDC和MSC(还支持HID和MIDI)。
- VSCode打开该文件夹工程;
打开后如图:

- 直接烧录
ve
和程序bin
;
烧录 ve
:

烧录 bin
:

- 上电启动,然后USB线连接到电脑(开发板上对应micro口的那个USB口)
就可以看到PC端的U盘和CDC串口,如下图:
(CDC串口)

(U盘)

如果没有显示出来,可以烧录并跟踪程序,看是否出现 board_init
失败(板子不同,可能会带来 board
初始化的失败)
以上是demo验证。
如果要集成到自己的工程(如,要在 example
中使用),需要修改:
Platformio.ini
中增加对tinyUsb
的引用:

注意,引用多个库时,用逗号隔开,并且逗号后边要加空格。
ve
文件中增加:

- 代码部分调整:
将 tinyusb
下的 src
路径文件,修改 main.c
后放入 example
下的 src
。
修改点:
Main.c
文件重命名;main()
函数重命名;- 去除
main()
中的board_init
函数;
重命名后的 main
函数在 example
下的 main()
中调用;
- 编译并烧录
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
,

在开发板上测试例程时,步骤:
- 分别编译并烧录
ve
和code
; - 然后用网线连接 PC 和开发板,并修改PC的IP地址为
192.168.5.2
; - 在PC的浏览器上输入:
http://192.168.5.1
- 此时,可以在网页上看到开发板中展示的画面:

移植到自己的板子上时,注意两项配置:
- 根据自己的板子,可能需要修改的是
phy
地址:

- 修改
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外设支持并不全面。
如果要用于普通外设,则该外设必须满足如下时序:
- SPI交互时第一段只能是
tx
(不能是rx
); - 收和发不能同时进行(只能是发完再收);
- 并且极性(
CPOL
)和相位(CPHA
)的设置值都是1。
更详细的使用说明和样例,请参考 example
和 datasheet
。
补充:
从SDK1.2.4版本开始,增加了对通用SPI的支持。
原 example_spi_common.c
中提供的函数:
Send
:单纯发送数据,字节数不限制;SendAndRecv
:在一个片选周期内,发送一段数据,再接收一段数据;
其中发送长度最长 4byte,接收长度不限。
(如果发送长度要更长,请自行在 C 驱动中扩展。)
这个版本开始,会在以上基础上,扩展出来两个函数:
Recv
:单纯收取数据,接收长度不限;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个 channel
,每个 channel
均可配置到任一个ADC上。
ADC的简单使用
参考 TestAdc
函数,ADC不需要在 ve
里管脚映射,不需要设置IO复用。
使用以下4个函数即可:

如果需要多次转换,则重复调用后3个函数。
如果需要不间断的循环采集,例程部分点击这里。
如果有自己的CPLD,希望裁剪ADC,或者对ADC有更高的定制,请参考网盘下ADC的专题讲解《analog中对ADC的剪裁.pdf》,以及《Analog代码分析.pdf》,这部分比较复杂。
十四、WatchDog的使用
AG32支持1个独立看门狗模块。
WDOG主要性能:
- 自由运行的递减计数器
- 看门狗被激活后,则在计数器计数至
0x000
时产生复位
默认情况下,在 debug
状态下看门狗是不工作的。
使用逻辑
为看门狗使能时钟,并使能中断(中断可选);
cSYS_EnableAPBClock(APB_MASK_WATCHDOG0); INT_EnableIRQ(WATCHDOG0_IRQn, WDOG_PRIORITY);
启动看门狗,同时设置看门狗时间;
cWDOG_Init(SYS_GetPclkFreq()); // 1 second
定时(或在中断函数中)喂狗;
cWDOG_Feed();
看门狗中断函数为:
void WATCHDOG0_isr();
系统启动后,可以通过查看寄存器,确定是否为看门狗导致的重启;
cif (READ_BIT(SYS->RST_CNTL, SYS_RSTF_WDOG)) { /*reset by Watchdog*/}
在处理中需要自行清除掉该标记,以避免下次重启时误判。
看门狗中是否使用中断
- 如果开启看门狗中断,则中断来了后,必须要在中断里清除标记(清除中断的动作就是喂狗的动作),不然中断函数会一直被调用。
- 如果关闭看门狗中断,则必须要应用程序在重启时间到来前及时喂狗。
- 还可以中断函数和应用程序两者一起喂狗。即:开启看门狗中断,然后在中断函数和应用中都调用喂狗函数。如果应用中的喂狗周期比中断周期短,则中断函数永远不会被触发。
看门狗的中断时间和重启时间
在 WDOG_Init
函数中设置的时间,是看门狗中断来一次的时间。而重启的时间,是两个这样的时间(2倍整)。
比如:WDOG_Init
设置了5秒,没启动看门狗中断,当应用中10秒没喂狗动作时,系统才被重启。
十五、RTC的使用
RTC(Real Time Clock)是个独立的定时器。
RTC模块拥有一个连续计数的计数器,可进行软件配置,提供时钟日历的功能。RTC还包含用于管理低功耗模式的自动唤醒单元。
只要芯片的备用电源一直供电,在MCU断电情况下RTC仍可以独立运行。
RTC只支持LSE作为时钟源(32768);
支持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 的校验值或者记录一些重要的数据。
可通过以下两个函数接口来读写:
c
RTC_WriteBackupRegister(uint16_t idx, uint16_t value)
RTC_ReadBackupRegister(uint16_t idx)
RTC常用于三种定时
秒中断
RTC的秒中断功能类似
SysTick
系统滴答的功能。RTC秒中断功能其实是每计数一次就中断一次。注意,秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决定的“秒”的时间,当然也是可以做到1秒钟中断一次。通常通过函数RTC_SetPrescaler(32768)
来进行设置。完整代码需要:
cRTC_Init(board_rtc_source()); RTC_EnableInt(RTC_FLAG_SEC); RTC_SetPrescaler(32768); RTC_SetOutputMode(RTC_OUTPUT_CLOCK);
溢出中断
溢出中断是
RTC_CNT
的值溢出时触发的中断。定时中断
使用时一般设置秒中断周期为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)
来设置使能。中断向量表和中断函数名都已内置定义。从用户角度,只需要设置中断使能即可使用对应的中断函数。
开关系统总中断函数
c
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
是低电平中断。
使用方法:
VE
中配置对应引脚,如下图:代码中使能该中断,配置中断函数。如下图:
以上两处配置完后,当 PIN_51
的电平变低后,将触发 Eint_isr
中断函数。
使用特点:
- 这个中断是不用清的。只要电平存在,中断就一直会进来。就算清了,执行完后,马上还会进来。电平置高,中断就不再进来。
- 使用时,该引脚要配置成上拉。不然一启动可能就会进中断的。
- 这个信号也可以从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漏掉该中断。
正常的做法是:
- CPLD要触发MCU中断时,置高该信号
local_int[3] = 1;
- MCU触发中断后,要在中断里“通知”CPLD,可以置低该信号。(这里的“通知”,可以是用GPIO信号输出到CPLD)
这样“一来一回”的一个交互,是最安全的做法。
十八、系统休眠(sleep、stop、standby)
AG32支持3种休眠方式:sleep
、stop
、standby
。
代码样例参考 example_system.c
其中进入 standby
后,有三种唤醒方式:IWDG
、RTC(Alarm)
、WAKEUP
(该引脚上升沿)。
如果要使用低功耗,在系统进入休眠前,需要先关掉不需要的外设时钟。
内核电流大小
STOP mode
: 5.5mASTANDBY 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
中设置三项:
ini
ip_name = user_ip
logic_dir = logic
board_logic.ve = project_xxx.ve
其中,
ip_name
为新建的user_ip
名字;logic_dir
为生成的文件夹名称;board_logic.ve
为生成自定义IP时共同使用的ve
文件;
总结
三种情况(不用ADC、仅用ADC/DAC/CMP、使用更多的CPLD功能),在 platformio.ini
文件中的配置对比:
- 如果连默认IP都用不到(没有用ADC/DAC/CMP):
只需要配置一项:
ini
board_logic.ve = project_xxx.ve
- 如果仅用到默认IP(使用到ADC/DAC/CMP):
需要配置两项:
ini
board_logic.ve = project_xxx.ve
ip_name = analog_ip
- 如果要用到自定义
logic
(需要更多的CPLD功能):
需要配置三项:
ini
board_logic.ve = project_xxx.ve
ip_name = xxxxx_ip
logic_dir = logic
关于自定义 logic
,这里仅是配置说明,更多信息参考CPLD部分的说明。