皋陶 发表于 2020-8-24 12:03:48

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

本帖最后由 皋陶 于 2020-8-25 18:02 编辑

isc-v Sifive learn inventor基础之串口
本文目录
[*]Sifive Learn Inventor 基础之gpio 按键中断
[*]Sifive learn inventor基础之串口&操作寄存器
[*]Sifive learn inventor基础之硬件pwm&寄存器
[*]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 查询手册
      intuart0_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.rxbyte&0x0ff;
      uart0_buff.rn++;
      //UART0_RXDATA寄存器的31位为1时表示FIFO里已经没有数据,说明接收完成
      if((UART0_RXDATA>>31)&1){
                uart0_buff.rxdata=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;

      }
}示例: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也需要操作寄存器来进行开发。
本篇完,感谢关注:RISC-V单片机中文网
页: [1]
查看完整版本: risc-v Sifive learn inventor基础之串口&操作寄存器 HifiveRev B