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 列表", 或点击这里查看。

配置举例:

  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的描述。

  1. 引脚复用:

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烧录仿真)。

  1. 设置为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_ 3 4

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

注意:

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

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

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

同上边上拉/下拉的设置文件(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中先配置对应的引脚:
  1. 代码中时钟使能、中断使能、设置频率;
  1. 使能I2C;
  1. 收发数据;

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

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

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

bool I2cReadPROM(uint8_t *mem, bool verify)

bool I2cWritePROM(uint8_t *mem)

  1. 关闭I2C:

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

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

.

九、CAN的使用

AG32支持1路CAN,对应:CAN0

样例程序参考example_can.c

在使用CAN时的流程:

  1. ve中先配置对应的引脚:
  1. 代码中使能时钟、开中断:
  1. 配置参数(参数较多)并开启can、开启收中断:
  1. 发送数据:
  1. 在中断函数中接收数据:

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

.

十、USB的使用

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

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

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

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

例程位于路径examples\usb下:

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

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

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

打开后如图:

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

烧录ve:

烧录bin:

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

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

(cdc串口)

(U盘)

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

以上是demo验证。

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

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

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

  1. ve文件中增加:
  1. 代码部分调整:

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

修改点:

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

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

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

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

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

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

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

请从网盘下载:百度网盘 请输入提取码 目录:\其他文档\常用样例工程补充\

.

十一、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地址:
  1. 修改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);
    1. 收和发不能同时进行(只能是发完再收);
    1. 并且极性(CPOL)和相位(CPHA)的设置值都是1。

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

补充:

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

原 example_spi_common.c 中提供的函数:

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

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

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

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

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

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

  2. 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);

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

WDOG_Init(SYS_GetPclkFreq()); // 1 second

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

WDOG_Feed();

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

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

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
  1. 如果仅用到默认ip(使用到ADC/DAC/CMP):

需要配置两项:

  • board_logic.ve = project_xxx.ve
  • ip_name = analog_ip
  1. 如果要用到自定义logic(需要更多的cpld功能):

需要配置三项:

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

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

相关推荐
欢乐熊嵌入式编程6 分钟前
智能手表 MCU 任务调度图
单片机·嵌入式硬件·智能手表
【云轩】20 分钟前
电机密集型工厂环境下的无线通信技术选型与优化策略
经验分享·嵌入式硬件
sword devil9001 小时前
将arduino开发的Marlin部署到stm32(3D打印机驱动)
stm32·单片机·嵌入式硬件
GodKK老神灭1 小时前
STM32 变量存储
stm32·单片机·嵌入式硬件
木宁kk2 小时前
51单片机引脚功能概述
单片机·嵌入式硬件
JANYI20182 小时前
嵌入式MCU和Linux开发哪个好?
linux·单片机·嵌入式硬件
sword devil9003 小时前
Arduino快速入门
stm32·单片机·嵌入式硬件
GodKK老神灭3 小时前
STM32实现循环队列
stm32·单片机·嵌入式硬件
不脱发的程序猿6 小时前
从MCU到SoC的开发思维转变
单片机·嵌入式硬件
&Cheems6 小时前
ZYNQ笔记(二十一): VDMA HDMI 彩条显示
笔记·嵌入式硬件·fpga开发