Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread Studio
RT-Thread活动
瑞萨_RA6M4
基于RT-Thread+RA6M4的虚拟仪器开发:第七章实现高效可移植的串口驱动
发布于 2022-07-21 10:53:11 浏览:601
订阅该版
[TOC] # 前言 为了实现上位机和下位机之间的双向通讯,我们需要实现高效的串口驱动。首先实现串口驱动,是因为它更简单方便,也更具备通用性,基本所有的单片机都会有串口。后面也会实现USB通讯来满足更大的数据量更快的速度的要求。 本章直接根据芯片手册写寄存器来实现串口驱动,不使用官方的FSP和rtthread的设备驱动。一方面这两者都太复杂了,要了解其实现机制实际上要花费很多时间,往往会让初学者云里雾里,哪怕通了也不知怎么通的,有问题也不知道如何分析,有这个时间完全可以自己去实现了;另外考虑后面用串口做通讯需要高效易用可移植,而且本文本着教程的属性,希望读者知其然知其所以然,所以完全从零开始去实现一个高效易移植的串口驱动;一通百通,了解该过程之后,这样对于其他任何芯片都能根据芯片手册去实现了。在此基础上再去用FSP和rtthread的设备驱动也会更清晰,哪怕有问题也知道如何去分析解决。 一般来说,要实现一个单片机的外设模块的驱动,第一手资料就是芯片的手册,我们这里参考的就是`<
>`。一般外设相关的有以下几个部分,需要去详读。 引脚配置:输入输出,多功能复用配置等。 时钟配置:时钟源配置,有些芯片需要使能外设时钟才能使用,之所以有外设时钟能使能和禁止,是为了低功耗,也就是只有需要用的才使能。RA6M4这部分功能是在`《10. Low Power Modes》`中描述,也写芯片是在时钟模块中描述。 模块配置:也就是串口本身相关的配置,波特率参数,中断方式等。 中断配置:本芯片是CORTEX-M33的内核,所以使用的是NVIC,但是多了一个ICU进行中断到NVIC的映射。同时也包括中断向量,回调函数等。 我们分别针对这几部分去阅读芯片手册,然后一个一个寄存器配置。 # RA6M4的串口介绍 ## 引脚配置 见前面`<
>`章节的描述。 前面通过看寄存器知道可以通过PWPRS的 PSEL[4:0]选择具体引脚对应是哪一个外设,我们可以继续看`<<19.6 Peripheral Select Settings for Each Product>>`有PSEL的对应表。 比如我们规划P304作为RXD6,P305作为TXD6. P304PFS,P305PFS的PSEL[4:0]都要配置为00100b ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/5bbb576c06add853b923ab19d91cd69e.png.webp) 所以可以有下初始化代码 ``` /* P304作为RXD6,P305作为TXD6.*/ R_PMISC->PWPR_b.B0WI = 0; /* 解锁 PmnPFS */ R_PMISC->PWPR_b.PFSWE = 1; /* P305外设输出 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.PMR = 1; /* 外设 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.PDR = 1; /* 输出 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.PCR = 1; /* 使能上拉*/ R_PFS->PORT[3].PIN[5].PmnPFS_b.NCODR = 0; /* CMOS模式 非开漏*/ R_PFS->PORT[3].PIN[5].PmnPFS_b.DSCR = 3; /* 高驱动能力 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.EOFR = 0; /* 未使用事件模式所以写默认值 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.ISEL = 0; /* 不使能外部中断 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.ASEL = 0; /* 非AD */ R_PFS->PORT[3].PIN[5].PmnPFS_b.PODR = 0; /* 默认输出0 */ R_PFS->PORT[3].PIN[5].PmnPFS_b.PSEL = 4; /* TXD6 */ /* P304外设输入 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.PMR = 1; /* 外设 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.PDR = 0; /* 输入 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.PCR = 1; /* 使能上拉*/ R_PFS->PORT[3].PIN[4].PmnPFS_b.NCODR = 0; /* CMOS模式 非开漏*/ R_PFS->PORT[3].PIN[4].PmnPFS_b.DSCR = 3; /* 高驱动能力 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.EOFR = 0; /* 未使用事件模式所以写默认值 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.ISEL = 0; /* 不使能外部中断 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.ASEL = 0; /* 非AD */ R_PFS->PORT[3].PIN[4].PmnPFS_b.PODR = 0; /* 默认输出0 */ R_PFS->PORT[3].PIN[4].PmnPFS_b.PSEL = 4; /* RXD6 */ R_PMISC->PWPR_b.PFSWE = 0; /* 锁定 PmnPFS */ R_PMISC->PWPR_b.B0WI = 1; ``` ## 时钟配置 从手册第29章如下描述`”In this section, PCLK refers to PCLKA.”`可以知道SCI的时钟来源于PCLKA。我们再参考第8章可以看到PCLKA的信息,最大可达100MHz。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/92335f555bba14ab2a04a17c2778ab26.png.webp) 我们从启动代码`Reset_Handler->SystemInit->bsp_clock_init` `bsp_prv_clock_set_hard_reset`设置分频值`BSP_PRV_STARTUP_SCKDIVCR` 其中`bsp_clock_cfg.h`定义了PCLK的分频值`BSP_CFG_PCLKA_DIV BSP_PRV_STARTUP_SCKDIVCR_PCLKA_BITS` `SystemCoreClockUpdate`更新计算值。 `BSP_STARTUP_PCLKA_HZ`可以获取当前的PCLKA频率。 可通过如下代码打印频率 ``` SystemCoreClockUpdate(); rt_kprintf("\nCore:%dHz!\n",SystemCoreClock); rt_kprintf("\nPCLKA:%dHz!\n",BSP_STARTUP_PCLKA_HZ); ``` ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/511176221c88a839cf6f2be1162e6388.png.webp) ## 模块使能 模块使能 见`<<10. Low Power Modes>>` `R_MSTP->MSTPCRB_b.MSTPB25 = 0;` ## 中断配置 ### 指定用户中断 参见`<
>` ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/bc886273b38ff4673e9e389bbaa22a37.png.webp) 从g_vector_table中可以看出 IELSR0~IELSR4用掉了,我们从5开始。 中断源如下,有5个 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/20fd22264ff091e903c5ddcfcd6ccb36.png.webp) ### 添加中断服务函数 在`vector_data.h`添加函数申明 `vector_data.c`的数组中添加中断服务函数。 ### NVIC配置 CORTEX-M33内核,可以参见`<
>` 在`\ra\arm\CMSIS_5\CMSIS\Core\Include\core_cm33.h`中是CMSIS标准的NVIC操作接口。 中断配置主要使用以下接口 `NVIC_SetPriorityGrouping `设置优先级组,具体含义参考上述文档。 `NVIC_SetPriority `设置中断优先级 `NVIC_EnableIRQ` 使能中断 要定义中断号 ``` /* This list includes only Arm standard exceptions. Renesas interrupts are defined in vector_data.h. */ typedef enum IRQn { Reset_IRQn = -15, /* 1 Reset Vector invoked on Power up and warm reset */ NonMaskableInt_IRQn = -14, /* 2 Non maskable Interrupt cannot be stopped or preempted */ HardFault_IRQn = -13, /* 3 Hard Fault all classes of Fault */ MemoryManagement_IRQn = -12, /* 4 Memory Management MPU mismatch, including Access Violation and No Match */ BusFault_IRQn = -11, /* 5 Bus Fault Pre-Fetch-, Memory Access, other address/memory Fault */ UsageFault_IRQn = -10, /* 6 Usage Fault i.e. Undef Instruction, Illegal State Transition */ SecureFault_IRQn = -9, /* 7 Secure Fault Interrupt */ SVCall_IRQn = -5, /* 11 System Service Call via SVC instruction */ DebugMonitor_IRQn = -4, /* 12 Debug Monitor */ PendSV_IRQn = -2, /* 14 Pendable request for system service */ SysTick_IRQn = -1, /* 15 System Tick Timer */ SCI7_RXI_IRQn = 0, SCI7_TXI_IRQn = 1, SCI7_TEI_IRQn = 2, SCI7_ERI_IRQn = 3, ICU_IRQ0_IRQn = 4, SCI6_RXI_IRQn = 5, SCI6_TXI_IRQn = 6, SCI6_TEI_IRQn = 7, SCI6_ERI_IRQn = 8, SCI6_AM_IRQn = 9, } IRQn_Type; ``` ### 串口中断使能 根据前面介绍可看到支持5个中断源,我们主要使用接收中断和发送完中断。分别是SCR寄存器的RIE,TEIE控制。 我们这里为什么选用发送完中断而不是选用发送空TE中断, 是因为我们要设计缓冲区,在中断中判断是否要继续发送下一个字节,在发送最后一个字节后进入中断需要停止发送,否则会一直重复发送最后一个字节(本芯片的特性,有些芯片并不会,只有写TDR才会发送)。如果设置为发送空中断,则写最后一个字节到TDR后进入中断,最后一个字节只是放到了移位寄存器并没有发送出去,此时停止发送就会最后一个字节没有发送出去。而设置为发送完中断则是最后一个字节从移位寄存器发送出去才会中断,此时关闭发送所有字节都发送完了,没有问题。 补充一点,使用发送完中断实际发送速率要比设置为发送空中断要慢一点,这在某些追求极限的设计时需要考虑。因为设置为发送空中断则是TDR可以写入了之后就可以马上写入下一个字节,相当于流水线不间断。而设置为发送完中断则,则是TDR转移到移位寄存器,直到移位寄存器发送完才能中断,此时移位寄存器正在发送时实际TDR已经空了已经可以写了,等待发送完再写TDR所以多等了TDR转移到移位寄存器的时间,会发送慢一点。 另外这里为什么没有选择FIFO操作,是因为FIFO需要接收到一定数量才中断,所以对于接收要考虑接收的字节数少于FIFO的触发水平的情况,可以使用BREAK检测或者超时等,但是软件变得复杂了。对于发送则也是要考虑同样的FIFO触发水平的问题。为了简洁通用,因为有些芯片并不支持FIFO,所以选择了没有使用FIFO。如果需要更高的效率减少中断次数,可以考虑使用FIFO,升值DMA,这里不再介绍。 ### 中断配置代码 根据如上分析可以得到如下初始化代码 在vector_data.h中申明5个中断服务函数,在uart.c中实现这几个服务函数。 ``` void sci_uart6_rxi_isr(void); void sci_uart6_txi_isr(void); void sci_uart6_tei_isr(void); void sci_uart6_eri_isr(void); void sci_uart6_am_isr(void); ``` 同时将 ` #define VECTOR_DATA_IRQ_COUNT (5)` 改为 ` #define VECTOR_DATA_IRQ_COUNT (10)` `vector_data.c中g_vector_table`中添加 ``` [5] = sci_uart6_rxi_isr, /* SCI6 RXI (Received data full) */ [6] = sci_uart6_txi_isr, /* SCI6 TXI (Transmit data empty) */ [7] = sci_uart6_tei_isr, /* SCI6 TEI (Transmit end) */ [8] = sci_uart6_eri_isr, /* SCI6 ERI (Receive error) */ [9] = sci_uart6_am_isr, /* SCI6 AM (Address match) */ ``` `g_interrupt_event_link_select`中添加 ``` [5] = BSP_PRV_IELS_ENUM(EVENT_SCI6_RXI), /* SCI6 RXI (Received data full) */ [6] = BSP_PRV_IELS_ENUM(EVENT_SCI6_TXI), /* SCI6 TXI (Transmit data empty) */ [7] = BSP_PRV_IELS_ENUM(EVENT_SCI6_TEI), /* SCI6 TEI (Transmit end) */ [8] = BSP_PRV_IELS_ENUM(EVENT_SCI6_ERI), /* SCI6 ERI (Receive error) */ [9] = BSP_PRV_IELS_ENUM(EVENT_SCI6_AM), /* SCI6 AM (Address match) */ ``` 其中`#define BSP_PRV_IELS_ENUM(vector) (ELC_ ## vector)` 而`ELC_EVENT_SCI6_RXI`等在`bsp_elc.h`中定义 其他类似 在`SystemInit->bsp_irq_cfg`函数中配置`R_ICU->IELSR`寄存器 IELSR可以参考手册ICU章节的描述,简单来说就是该芯片外设中断并不是固定对应到中断向量表的固定地址,而是通过IELSRx去指定x号中断向量表中对应哪个外设。这样就灵活一点。 可以通过仿真器查看配置是否正确 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/96eb68b6cccc545cd43f0389b7acc4d0.png.webp) ## 模块配置 先阅览下串口相关的寄存器 RSR:接收移位寄存器,软件不能直接访问。移位寄存器接收全数据后自动转移到RDR或者接收FIFO中。 RDR:接收数据寄存器。 RDRHL:异步模式的9位模式时使用,低八位就是RDR的影子寄存器。如果是7或者8位模式不能访问,只能访问RDR。 RDRHL_MAN:Manchester模式时使用。 FRDRHL:接收FIFO,低9位是数据,高7位是状态。 TSR:发送移位寄存器。 TDR:发送数据寄存器。转移到TSR按位发送。 TDRHL:异步模式的9位模式时使用,低八位就是TDR的影子寄存器。如果是7或者8位模式不能访问,只能访问TDR。注意未使用位写1. TDRHL_MAN:Manchester模式时使用。注意未使用位写1. FTDRHL:低9位为数据,注意未使用位写1. SMR:非Smart Card使用,时钟,多机,停止位,校验,模式,长度配置。 SMR_SMCI:Smart Card模式使用,同上. SCR:非Smart Card使用,时钟,收发中断使能。 SCR_SMC:Smart Card模式使用,同上. SSR:非FIFO,非Smart Card使用,状态寄存器。写0清除。 SSR_FIFO:FIFO,非Smart Card使用,状态寄存器。写0清除。 SSR_SMCI:Smart Card使用,状态寄存器。写0清除。 SSR_MANC:Manchester模式使用,,状态寄存器。写0清除。 SCMR: Smart Card模式还是非 Smart Card,引脚极性配置,高低位在前配置。 BRR:波特率配置寄存器。 MDDR:波特率校正寄存器。 SEMR:扩展配置寄存器。 SNFR:噪声过滤寄存器 SIMR1~SIMR3,SISR:IIC模式配置。 SPMR:SPI模式配置 FCR:FIFO控制 FDR:FIFO中数据个数寄存器。 LSR:溢出错误计数状态 CDR:匹配地址 DCCR:匹配控制 SPTR:引脚状态与控制 ACTR:时序调整 MMR,TMPR,RMPR,MESR,MECR: Manchester 模式配置 ESMER: Extended Serial Module使能 CR0~CR3 PCR ICR:中断控制 STR:状态 STCR:状态清除 CF0DR,CF0CR,CF0RR,PCF1DR,SCF1DR CF1CR,CF1RR TCR,TMR,TPRE,TCNT:定时器相关 异步模式配置步骤 参见Table 29.28 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/2d764602b5bf4630f6c940f631cde901.png.webp) 1. SCR清除各bit,关闭收发 2. FCR.FM = 0 3. SCR.CKE[1:0] 4. SIMR1.IICM = 0. SPMR.CKPH = 0 CKPOL= 0 5. SMR,SCMR, SEMR 6. SPTR and ACTR 7. BRR MDDR 8. SCR.TE RE = 1, SCR.TIE RIE = 1. FIFO模式参见Table 29.29 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/758d621af139ee128f659c067460c3e6.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/be07ac2ae679340024506f5fd58a035d.png.webp) 波特率计算 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/28ea03190f3c5ed1404260b918c504e6.png.webp) ## 驱动代码实现 流程框图如下 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/69d51e4fd5749854c3106a6dc23d715c.png.webp) 详见代码 ## 速度测试 ``` void uartcomu_thread_entry(void *parameter) { uart_cfg_t cfg; int8_t erro; cfg.id = UART_ID_6; cfg.baud = UART_BAUD_115200; cfg.datalen = UART_DATA_8; cfg.parity = UART_CHECK_NONE; cfg.stopb = UART_STOPB_1; driver_uart_init(UART_ID_6); driver_uart_set(&cfg); driver_uart_flush(UART_ID_6); for(int i=0;i
>24)&0xFF; buffer[1]=(pre>>16)&0xFF; buffer[2]=(pre>>8)&0xFF; buffer[3]=(pre>>0)&0xFF; driver_uart_send(UART_ID_6, buffer, 4, 1000, &erro); /* 收发测试 */ while(1) { int len = driver_uart_recv(UART_ID_6, buffer, BUFFER_MAX/2, 100, &erro); if(len > 0) { driver_uart_send(UART_ID_6, buffer, len, 100, &erro); } } } ``` 115200bps 理论最大1秒可以发送 115200*8/11(1开始位,8数据位,1停止位,1间隔位) 10473 字节/S 实际测试发送16384个字节花了0x615毫秒,1.557秒,10523字节/S 达到了理论最大速度。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/8e7814e066bda723c8d47725a33f101e.png.webp) ## 收发测试 代码同上 串口调试助手按照每20毫秒间隔发送1280字节。10秒共发送94720字节收到同样数据量的返回,没有丢失。速度为9472字节/秒。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/03f22a3e52ff5eb9573e14b013b7968b.png.webp) ## 波特率优化 `BRR`设置值N计算如下,其中`BSP_STARTUP_PCLKA_HZ`为`100000000`,`cfg->baud=230400,div=64,mul=1`.计算为12.56取整为12. `N = BSP_STARTUP_PCLKA_HZ / (div * cfg->baud * mul/2) -1; ` 由于BRR有取整所以变小了,反过来算波特率B=240,384.6取整为240384. ` B = BSP_STARTUP_PCLKA_HZ / ((N + 1) * div * mul/2); //128<
baud /B 其中M是设置的MDDR cfg->baud是理论设置值,比如这里的230400 B是根据正向计算得到的N再反向计算得到的理论值这里是240384 得到M=245 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/1f7ffc0fd1f9e48643c0457396ea4d5c.png.webp) 根据仿真器可以看到计算值也是对的,这里寄存器设置值要减少1,也就是寄存器设置255实际M=256. ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/17fc7a95789d1b666dad3dfa5eb09f33.png.webp) 再实际测量波形,可以看到 26.167/6=4.3612 1/4.3612us = 229295 误差变为了(230400- 229295)/230400=0.48%. ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/ff4c9e89a88d7e9abf2376ae2c947f91.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20220721/ff4c9e89a88d7e9abf2376ae2c947f91.png.webp) 对应代码如下 ``` /* 波特率 */ uint8_t div = 0; uint8_t n; if(R_SCI6->SEMR_b.BGDM == 1) { div |= 0x04; } if(R_SCI6->SEMR_b.ABCS == 1) { div |= 0x02; } if(R_SCI6->SEMR_b.ABCSE == 1) { div |= 0x01; } switch(div) { case 0: div = 64; break; case 2: case 4: div = 32; break; case 6: div = 16; break; default: div = 12; break; } n = R_SCI6->SMR_b.CKS; uint8_t mul = 1; uint8_t N = 0; uint32_t B = 0; for(int i = 0; i<2*n; i++) { mul *= 2; } //N = PCLK x 10^6 / (div x 2^(2n-1) x B) - 1 SystemCoreClockUpdate(); N = BSP_STARTUP_PCLKA_HZ / (div * cfg->baud * mul/2) -1; B = BSP_STARTUP_PCLKA_HZ / ((N + 1) * div * mul/2); //128<
BRR = N; #if 1 uint8_t mddr = 128; R_SCI6->SEMR_b.BRME = 1; if(B > cfg->baud) { mddr = ((uint32_t)256*cfg->baud/B) - 1; if(mddr<128) { mddr = 127; } R_SCI6->MDDR = mddr; } #endif ```
2
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
qinyunti
这家伙很懒,什么也没写!
文章
30
回答
1
被采纳
0
关注TA
发私信
相关文章
1
(苏州站)RT-Thread物联网开发者沙龙【已结束】
2
(成都站)RT-Thread物联网开发者沙龙
3
(深圳站)RT-Thread物联网开发者沙龙
4
(西安站)RT-Thread物联网开发者沙龙
5
成都站2018 RT-Thread开发者沙龙回顾及PPT下载
6
2018 RT-Thread物联网开发者沙龙(北京站)
7
2018 RT-Thread物联网开发者沙龙(南京站)
8
第十三届研电赛RT-Thread企业专项奖发布通知
9
RT-Thread应用作品征集大赛开始啦!
10
你的投票将决定RT-Thread官网应该优先准备的文档是哪些
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
ART-Pi
FinSH
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
rt-smart
FAL
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
cubemx
PWM
flash
packages_软件包
freemodbus
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
keil_MDK
SFUD
rt_mq_消息队列_msg_queue
C++_cpp
at_device
ulog
本月问答贡献
出出啊
1516
个答案
342
次被采纳
小小李sunny
1440
个答案
289
次被采纳
张世争
793
个答案
171
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
1
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
4
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部