risc-v Sifive learn inventor基础之串口&操作寄存器 HifiveRev B

risc-v Sifive learn inventor基础之串口

目录

  1. Sifive Learn Inventor 基础之gpio 按键中断
  2. Sifive learn inventor基础之串口&操作寄存器
  3. Sifive learn inventor基础之硬件pwm&寄存器
  4. risc-v Sifive learn inventor基础之硬件i2c与LSM303AGR通信

上一章了解了中断后,继续实践另一个重要的外设串口以及Sifive提供的操作寄存器的函数__METAL_ACCESS_ONCE
Sifive learn inventor基础之串口gpio中断配置

开发板只有两个串口,分别是uart0和uart1,uart0外接到jlink模块,可以用于与上位机的通信,uart1与esp32连接。
本章以uart0为例子来初始化uart0,并且实现pc端串口发送数据,开发板自动返回接收的数据。

一,硬件连接

由芯片手册可以知道,uart0_rx对应gpio16,uart0_tx对应gpio17;所以我们需要复用这两gpio口;

二,代码编写

1,初始化uart0

/**
 * 串口0 波特率115200 用于打印数据
 */
void uart0_init()
{
	//enable rx and tx
	UART0_TXCTRL  |= (1 <<0);
	UART0_RXCTRL  |= (1 <<0);
	
	//RXWM 1 watermark=0 
	//__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_RXCTRL))  |= (1 <<16);

	//enable rx inturupt
	UART0_IE |= (1 <<1);

	//先将div寄存器清零,再进行赋值操作
	UART0_DIV  &= 0;
	//设置波特率为 115200 时钟频率64M,div=(64M/115200)-1=554
	UART0_DIV |=554;
	//复用gpio16,17
	GPIO0_IOF_EN  |= (1 <<16);
	GPIO0_IOF_EN  |= (1 <<17);

}

因为库函数用不惯(好多bug),所以自己通过操作寄存器初始化uart,首先要去看芯片手册的uart章节,了解各个寄存器的功能。学过stm32的人一看就清楚这是在配置寄存器,需要搭配芯片手册看才能了解每一步的意义。
以下是在uart.h中对用到的uart寄存器的定义,有了这些宏定义,对寄存器的操作看起来就比较简洁。
这里介绍一个非常重要的库函数__METAL_ACCESS_ONCE,可以看到这是一个宏定义函数,大概的意思就是操作地址为(x)的寄存器;
具体寄存器地址在手册里可以找到,另外bsp/install/include/metal/machine目录下的platform.h文件里,定义了大部分寄存器的地址,使用起来就是复制粘贴,非常方便

在这里插入图片描述
uart.h

#define UART1_RXDATA (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_1_BASE_ADDRESS + METAL_SIFIVE_UART0_RXDATA)))
#define UART0_RXDATA __METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_RXDATA))
#define UART1_TXDATA (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_1_BASE_ADDRESS + METAL_SIFIVE_UART0_TXDATA)))
#define UART0_TXDATA __METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_TXDATA))

#define UART0_TXCTRL (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_TXCTRL)))
#define UART0_RXCTRL (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_RXCTRL)))
#define UART0_IE (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_IE)))
#define UART0_DIV (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_DIV)))

#define UART1_TXCTRL (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_1_BASE_ADDRESS + METAL_SIFIVE_UART0_TXCTRL)))
#define UART1_RXCTRL (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_1_BASE_ADDRESS + METAL_SIFIVE_UART0_RXCTRL)))
#define UART1_IE (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_1_BASE_ADDRESS + METAL_SIFIVE_UART0_IE)))
#define UART1_DIV (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_1_BASE_ADDRESS + METAL_SIFIVE_UART0_DIV)))

#define GPIO0_IOF_EN (__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_GPIO0_0_BASE_ADDRESS + METAL_SIFIVE_GPIO0_IOF_EN)))

初始化函数中的被注释部分__METAL_ACCESS_ONCE((__metal_io_u32 *)(METAL_SIFIVE_UART0_0_BASE_ADDRESS + METAL_SIFIVE_UART0_RXCTRL)) |= (1 <<16);可能比较难以理解,结合芯片手册,得知操作的是watermark功能 。因为接收FIFO是8个字节长,当设置watermark=2时,只有当FIFO里的数据超过2个字节时,uart才会产生中断。当FIFO里的数据少于2时,中断标志位就会自动清除。所以watermark可以认为是一个门槛,超过门槛就会触发中断。我一般设置为默认,就是0;
在这里插入图片描述
watermark官网手册说明
在这里插入图片描述
2,配置plic
就像上一章一样,配置plic中断,这一次就非常能理解了。

/**
*串口0接收中断初始化
*uart0 uart0对象
*flag 退出中断后的标志
*/
void Uart0_rx_interrupt_init(struct metal_uart *uart0,int *flag)
{
	//uart0 id=33 查询手册
	int  uart0_id=33;
	uart0_intr=uart0->vtable->controller_interrupt(uart0);

	metal_interrupt_init(uart0_intr);
    
	//注册回调函数 传递flag
	metal_interrupt_register_handler(uart0_intr,uart0_id,uart0_isr,flag);
	//设置优先级
	metal_interrupt_set_priority(uart0_intr,uart0_id,4);
	metal_interrupt_enable(uart0_intr,uart0_id);
	
}
/**
*串口0接收中断回调函数
*每接收一个字节进入一次此函数 每次进入会读取一个字节数据到buff
*/
void uart0_isr (int id, void *data) {
	int *flag=(int *)data;
	//读取uart0接收寄存器
	uart0_buff.rxbyte=UART0_RXDATA;
	//将读到的一个字节的数据放到buff
	uart0_buff.rxdata[uart0_buff.rn]=uart0_buff.rxbyte&0x0ff;
	uart0_buff.rn++;
	//UART0_RXDATA寄存器的31位为1时表示FIFO里已经没有数据,说明接收完成
	if((UART0_RXDATA>>31)&1){
		uart0_buff.rxdata[uart0_buff.rn]=0;
		*flag=3;
	}
}

3,当uart0接收全部数据后,flag=3,再把数据发送回去

/**
 * 串口0发送 用于打印
 * char *p 字符串的首地址
 * len 字符串长度(字节)
 */
void uart0send(char *p,int len)
{
	//UART0_TXDATA的第31位为1时表示发送FIFO为空,即发送完成
	for(int i=0;i<len;i++){
		while(UART0_TXDATA&(1<<31));
		//发送一个字节的数据
		UART0_TXDATA  |=p[i];

	}
}

示例:

if(flag==3){
	char *c="uart0 recieve data:\r\n";
	int len=strlen(c);
	uartsend(c,len);
	uart0send(uart0_buff.rxdata,uart0_buff.rn);
}

三,小结

这个操作寄存器的函数非常有用,结合芯片手册,我们可以避开库函数,更好的去了解芯片的底层逻辑。对一些库函数没有涉及的外设,例如pwm也需要操作寄存器来进行开发。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/106603894