完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
开发板只有两个串口,分别是uart0和uart1,uart0外接到jlink模块,可以用于与上位机的通信,uart1与esp32连接。 一,硬件连接 由芯片手册可以知道,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 //发送一个字节的数据 UART0_TXDATA |=p; } } 示例: if(flag==3){ char *c="uart0 recieve data:rn"; int len=strlen(c); uartsend(c,len); uart0send(uart0_buff.rxdata,uart0_buff.rn); } 三,小结 这个操作寄存器的函数非常有用,结合芯片手册,我们可以避开库函数,更好的去了解芯片的底层逻辑。对一些库函数没有涉及的外设,例如pwm也需要操作寄存器来进行开发。 |
|
|
|
只有小组成员才能发言,加入小组>>
3278 浏览 9 评论
2955 浏览 16 评论
3455 浏览 1 评论
8987 浏览 16 评论
4050 浏览 18 评论
1102浏览 3评论
570浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
568浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2301浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1857浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 09:15 , Processed in 1.181914 second(s), Total 49, Slave 39 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号