有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 皋陶 于 2020-8-25 18:02 编辑
isc-v Sifive learn inventor基础之串口
本文目录
上一章了解了中断后,继续实践另一个重要的外设串口以及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也需要操作寄存器来进行开发。
|