一 项目背景
项目需要使用一款UART串口编码器,编码器的数据以波特率57600持续向外发送。但这组数据包没有固定的包头和校验尾,仅仅是由多圈圈数和单圈角度组成的六字节数据码,这样接收到的数组无法确定实际的下标,所以这边考虑用串口接收超时中断+DMA来实现。
二 原理说明
【1】UART原理说明:参考【嵌入式】NXP/LPC使用GPIO+定时器模拟UART串口接收
【2】超时中断原理说明:接收的数据包通过逻辑分析仪,如下所示:
由上面的数据可以看到,两个包之间的发送间隔为500us左右,而一个包的发送时间为170us(波特率为57600,那么每位数据是17us,一个包10位数据,就是170us),所以只要在串口收发的过程中加一个定时器,设定超时时间为400us(大于170us,小于500us即可),那么 只要超时了,说明下一次收到的位即为起始位。
STM32中有一个空闲中断(IDLE)的概念,而HC32中没有,取而代之的是串口接收超时中断,两者基本功能是类似的,都是在串口超过一段时间没有接收数据之后触发的一个中断功能。HC32F460的用户手册中对此也有详细说明(我们这边用的是USART4串口,所以相对应的需需要使用 Timer0 Unit2 B 通道):
【3】DMA原理说明:DMA(Direct Memory Access,直接存储器访问) 是单片机的一个外设,它的主要功能是用来搬移数据,但是不需要占用 CPU,即在传输数据的时候, CPU 可以干其他的事情,好像多线程一样。(具体可以参考:串口DMA传输模式)
这边用到DMA,是因为编码器发送数据比较快,若是一直进中断会挤占CPU的资源,所以考虑用DMA改进。
三 设计实现--超时定时器部分
【1】超时定时器初始化( Timer0 Unit2 B 通道 ),这个过程中主要关注一下定时器时间的设置,如下面的 stcTimerCfg.Tim0_CmpValue = 4200 ,它的时钟源是 Tim0_Pclk1 ,在HC32F460中,这个时钟是168MHz的一半,即84MHz,时钟的分频系数为8,根据公式:
T=CmpValue*ClockSource*ClockDivision
其中,T = 400us,ClockSource=1/84MHz,ClockDivision=8,计算出CmpValue=4200:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27void Timer0_Config(void) { stc_clk_freq_t stcClkTmp; stc_tim0_base_init_t stcTimerCfg; stc_tim0_trigger_init_t StcTimer0TrigInit; MEM_ZERO_STRUCT(stcClkTmp); MEM_ZERO_STRUCT(stcTimerCfg); MEM_ZERO_STRUCT(StcTimer0TrigInit); /* Timer0 peripheral enable */ PWC_Fcg2PeriphClockCmd(PWC_FCG2_PERIPH_TIM02, Enable); /* Clear CNTAR register for channel B */ TIMER0_WriteCntReg(M4_TMR02, Tim0_ChannelB, 0u); /* Config register for channel B */ stcTimerCfg.Tim0_CounterMode = Tim0_Sync; stcTimerCfg.Tim0_SyncClockSource = Tim0_Pclk1; stcTimerCfg.Tim0_ClockDivision = Tim0_ClkDiv8; stcTimerCfg.Tim0_CmpValue = 4200; TIMER0_BaseInit(M4_TMR02, Tim0_ChannelB, &stcTimerCfg); /* Clear compare flag */ TIMER0_ClearFlag(M4_TMR02, Tim0_ChannelB); /* Config timer0 hardware trigger */ StcTimer0TrigInit.Tim0_InTrigEnable = false; StcTimer0TrigInit.Tim0_InTrigClear = true; StcTimer0TrigInit.Tim0_InTrigStart = true; StcTimer0TrigInit.Tim0_InTrigStop = false; TIMER0_HardTriggerInit(M4_TMR02, Tim0_ChannelB, &StcTimer0TrigInit); }
四 设计实现--串口部分
【1】串口初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72/* USART baudrate definition */ #define USART4_BAUDRATE (57600) /* USART Interrupt Number */ #define USART4_IRQn (Int025_IRQn) #define USART4_ERR_IRQn (Int026_IRQn) #define USART4_RTO_IRQn (Int029_IRQn) /* USART RX Port/Pin definition */ #define USART4_RX_PORT (PortE) #define USART4_RX_PIN (Pin14) #define USART4_RX_FUNC (Func_Usart4_Rx) void initUART4(void) { en_result_t enRet = Ok; stc_irq_regi_conf_t stcIrqRegiCfg; /*配置串口使用的时钟和基本通信配置*/ const stc_usart_uart_init_t stcInitCfg = { UsartIntClkCkOutput, UsartClkDiv_1, UsartDataBits8, UsartDataLsbFirst, UsartOneStopBit, UsartParityNone, UsartSampleBit8, UsartStartBitFallEdge, UsartRtsEnable, }; /*打开时钟*/ PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_USART4, Enable); /*配置相应的IO作为串口的RX引脚*/ PORT_SetFunc(USART4_RX_PORT, USART4_RX_PIN, USART4_RX_FUNC, Disable); /*初始化串口配置*/ enRet = USART_UART_Init(M4_USART4, &stcInitCfg); if (enRet != Ok)while (1); /*串口波特率设置*/ enRet = USART_SetBaudrate(M4_USART4, USART4_BAUDRATE); if (enRet != Ok)while (1); /*设置串口接收中断*/ stcIrqRegiCfg.enIRQn = USART4_IRQn; stcIrqRegiCfg.pfnCallback = &Usart4RxIrqCallback; stcIrqRegiCfg.enIntSrc = INT_USART4_RI; enIrqRegistration(&stcIrqRegiCfg); NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn); NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn); /*设置串口接收错误中断*/ stcIrqRegiCfg.enIRQn = USART4_ERR_IRQn; stcIrqRegiCfg.pfnCallback = &Usart4ErrIrqCallback; stcIrqRegiCfg.enIntSrc = INT_USART4_EI; enIrqRegistration(&stcIrqRegiCfg); NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn); NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn); /*设置接收超时中断*/ stcIrqRegiCfg.enIRQn = USART4_RTO_IRQn; stcIrqRegiCfg.pfnCallback = &Usart4TimeoutIrqCallback; stcIrqRegiCfg.enIntSrc = INT_USART4_RTO; enIrqRegistration(&stcIrqRegiCfg); NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn); NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn); USART_FuncCmd(M4_USART4, UsartRx, Enable);//使能接收 USART_FuncCmd(M4_USART4, UsartRxInt, Enable);//使能接收中断 USART_FuncCmd(M4_USART4, UsartTimeOut, Enable);//使能超时 USART_FuncCmd(M4_USART4, UsartTimeOutInt, Enable);//使能超时中断 }
【2】串口接收中断回调:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#define ENCODER_LEN 6 uint8_t ecd_buf[ENCODER_LEN]; uint8_t ecd_timeout_flag; static void Usart4RxIrqCallback(void) { static uint8_t cnt = 0; while(1) { if (Set == USART_GetStatus(M4_USART4, UsartRxNoEmpty)) { if(ecd_timeout_flag == 1) //如果超时,下一个接收到的即为起始位 cnt = 0; ecd_buf[cnt++] = USART_RecData(M4_USART4); ecd_timeout_flag = 0; if(cnt > 5) cnt = 0; } else break; } }
【3】串口接收错误中断回调:
1
2
3
4
5
6
7
8
9static void Usart4ErrIrqCallback(void) { if (Set == USART_GetStatus(M4_USART4, UsartFrameErr)) USART_ClearStatus(M4_USART4, UsartFrameErr); if (Set == USART_GetStatus(M4_USART4, UsartParityErr)) USART_ClearStatus(M4_USART4, UsartParityErr); if (Set == USART_GetStatus(M4_USART4, UsartOverrunErr)) USART_ClearStatus(M4_USART4, UsartOverrunErr); }
【4】串口接收超时中断回调:
1
2
3
4
5
6
7static void Usart4TimeoutIrqCallback(void) { ecd_timeout_flag = 1; //下一次接收为通讯码的开始位 TIMER0_Cmd(M4_TMR02, Tim0_ChannelB,Disable); USART_ClearStatus(M4_USART4, UsartRxTimeOut); }
到这边为止,就可以正常的读到编码器的数据了,而且是以编码器的发送顺序排列在ecd_buf数组中,只要处理该数组就可以取到编码器的多圈圈数和单圈角度。
下面的DMA部分是想改进一下控制方案,使得不那么频繁地进入接收中断,以减小CPU的资源消耗。
五 设计实现--DMA部分
【1】DMA初始化和中断,其中主要关注几点:
一是接收的数据需要映射到ecd_buf的地址:
(stcDmaInit.u32DesAddr = (uint32_t)(&ecd_buf))
二是发送数据模式需要改为递增:
(stcDmaInit.stcDmaChCfg.enDesInc = AddressIncrease):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46static void DmaBtcIrqCallback(void) { USART_ClearStatus(M4_USART4, UsartRxTimeOut); //清楚接收超时标志 DMA_ClearIrqFlag(M4_DMA1, DmaCh0, BlkTrnCpltIrq); } static void DmaInit(void) { stc_dma_config_t stcDmaInit; stc_irq_regi_conf_t stcIrqRegiCfg; /* Enable peripheral clock */ PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_DMA1 | PWC_FCG0_PERIPH_DMA2,Enable); /* Enable DMA. */ DMA_Cmd(M4_DMA1,Enable); /* Initialize DMA. */ MEM_ZERO_STRUCT(stcDmaInit); stcDmaInit.u16BlockSize = 1u; /* 1 block */ stcDmaInit.u32SrcAddr = ((uint32_t)(&M4_USART4->DR)+2ul); /* Set source address. */ stcDmaInit.u32DesAddr = (uint32_t)(&ecd_buf); /* Set destination address. */ stcDmaInit.stcDmaChCfg.enSrcInc = AddressFix; /* Set source address mode. */ stcDmaInit.stcDmaChCfg.enDesInc = AddressIncrease; /* Set destination address mode. */ stcDmaInit.stcDmaChCfg.enIntEn = Enable; /* Enable interrupt. */ stcDmaInit.stcDmaChCfg.enTrnWidth = Dma8Bit; /* Set data width 8bit. */ DMA_InitChannel(M4_DMA1, DmaCh0, &stcDmaInit); /* Enable the specified DMA channel. */ DMA_ChannelCmd(M4_DMA1, DmaCh0, Enable); /* Clear DMA flag. */ DMA_ClearIrqFlag(M4_DMA1, DmaCh0, TrnCpltIrq); /* Enable peripheral circuit trigger function. */ PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS,Enable); /* Set DMA trigger source. */ DMA_SetTriggerSrc(M4_DMA1, DmaCh0, EVT_USART4_RI); /* Set DMA block transfer complete IRQ */ stcIrqRegiCfg.enIRQn = Int030_IRQn; stcIrqRegiCfg.pfnCallback = &DmaBtcIrqCallback; stcIrqRegiCfg.enIntSrc = INT_DMA1_BTC0; enIrqRegistration(&stcIrqRegiCfg); NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn); NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn); }
【2】串口接收中断就不用了,由DMA直接接收即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70//串口接收错误中断回调 static void Usart4ErrIrqCallback(void) { if (Set == USART_GetStatus(M4_USART4, UsartFrameErr)) USART_ClearStatus(M4_USART4, UsartFrameErr); if (Set == USART_GetStatus(M4_USART4, UsartParityErr)) USART_ClearStatus(M4_USART4, UsartParityErr); if (Set == USART_GetStatus(M4_USART4, UsartOverrunErr)) USART_ClearStatus(M4_USART4, UsartOverrunErr); } //串口接收超时中断回调 static void Usart4TimeoutIrqCallback(void) { TIMER0_Cmd(M4_TMR02, Tim0_ChannelB,Disable); USART_ClearStatus(M4_USART4, UsartRxTimeOut); DMA_ChannelCmd(M4_DMA1, DmaCh0, Disable); //超时重启DMA,以进行新一轮的接收 DMA_SetDesAddress(M4_DMA1, DmaCh0, (uint32_t)(ecd_buf)); DMA_SetTransferCnt(M4_DMA1, DmaCh0, ENCODER_LEN); DMA_ChannelCmd(M4_DMA1, DmaCh0, Enable); } void initUART4(void) { en_result_t enRet = Ok; stc_irq_regi_conf_t stcIrqRegiCfg; /*配置串口使用的时钟和基本通信配置*/ const stc_usart_uart_init_t stcInitCfg = { UsartIntClkCkOutput, UsartClkDiv_1, UsartDataBits8, UsartDataLsbFirst, UsartOneStopBit, UsartParityNone, UsartSampleBit8, UsartStartBitFallEdge, UsartRtsEnable, }; DmaInit(); /*打开时钟*/ PWC_Fcg1PeriphClockCmd(PWC_FCG1_PERIPH_USART4, Enable); /*配置相应的IO作为串口的RX引脚*/ PORT_SetFunc(USART4_RX_PORT, USART4_RX_PIN, USART4_RX_FUNC, Disable); /*初始化串口配置*/ enRet = USART_UART_Init(M4_USART4, &stcInitCfg); if (enRet != Ok)while (1); /*串口波特率设置*/ enRet = USART_SetBaudrate(M4_USART4, USART4_BAUDRATE); if (enRet != Ok)while (1); /*设置串口接收中断舍弃*/ /*设置串口接收错误中断*/ stcIrqRegiCfg.enIRQn = USART4_ERR_IRQn; stcIrqRegiCfg.pfnCallback = &Usart4ErrIrqCallback; stcIrqRegiCfg.enIntSrc = INT_USART4_EI; enIrqRegistration(&stcIrqRegiCfg); NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn); NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn); /*设置接收超时中断*/ stcIrqRegiCfg.enIRQn = USART4_RTO_IRQn; stcIrqRegiCfg.pfnCallback = &Usart4TimeoutIrqCallback; stcIrqRegiCfg.enIntSrc = INT_USART4_RTO; enIrqRegistration(&stcIrqRegiCfg); NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT); NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn); NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn); USART_FuncCmd(M4_USART4, UsartRx, Enable);//使能接收 USART_FuncCmd(M4_USART4, UsartRxInt, Enable);//使能接收中断 USART_FuncCmd(M4_USART4, UsartTimeOut, Enable);//使能超时 USART_FuncCmd(M4_USART4, UsartTimeOutInt, Enable);//使能超时中断 }
项目中只需要用到串口数据的接收,所以这边没有DMA发送的内容。
六 总结
综上,便可以通过串口接收超时中断或者串口接收超时中断+DMA进行接收了。通过DEBUG也可以看到ecd_buf中的数据按顺序排列为0x00,0x00,0xDD,0x2E,0x38,0x77,与逻辑分析仪中的一致:
最后
以上就是隐形星星最近收集整理的关于【嵌入式】HC32F460串口接收超时中断+DMA一 项目背景二 原理说明三 设计实现--超时定时器部分四 设计实现--串口部分五 设计实现--DMA部分六 总结的全部内容,更多相关【嵌入式】HC32F460串口接收超时中断+DMA一内容请搜索靠谱客的其他文章。
发表评论 取消回复