华大半导体HC32F4A0笔记(三),RS485通信,使用串口USART1,DMA接收

5 篇文章 6 订阅
5 篇文章 14 订阅

一、USART的工作频率和波特率

在这里插入图片描述
看用户手册一上来就糊涂了,手册里面写的PCLK是什么?翻看手册第4章有关CMU章节。似乎这里说的PCLK就是PCLK1?手册是第一版,纰漏其实蛮多的。
在这里插入图片描述
在官方库函数验证了猜测,这里说的PCLK指的就是PCLK1:


en_result_t USART_SetBaudrate(M4_USART_TypeDef *USARTx,
                                    uint32_t u32Baudrate,
                                    float32_t *pf32Err)
{
    /* …… */

    /* Get USART clock frequency */
    u32UsartDiv = USART_DIV(USARTx);
    u32UsartClk = PCLK_FREQ / u32UsartDiv; 

    /* …… */
}

在设置波特率的函数中找到了这么一句话u32UsartClk = PCLK_FREQ / u32UsartDiv;看起来是在计算USART的频率的,展开PCLK_FREQ这个宏得到:

#define PCLK_FREQ                                                              \
(   SystemCoreClock >> (READ_REG32_BIT(M4_CMU->SCFGR, CMU_SCFGR_PCLK1S) >> CMU_SCFGR_PCLK1S_POS))

果然,PCLK就是PCLK1
在这里插入图片描述
手册中没有说明USART的工作频率限制,所以理论上最高的波特率就是PCLK1 / 8(Bps)。计算公式为:
在这里插入图片描述

本例波特率定在19200,并不是特别高。根据公式,并考虑到过采样要求,可以把串口的工作频率调到16分频。确定下来后,开始初始化USART。

二、初始化USART1

本例TX为PA09,RX为PA10,其功能20对应USART1的TX和RX:
在这里插入图片描述
故本例使用UASRT1:

/* UART unit definition */
#define USART_FUNCTION_CLK_GATE         (PWC_FCG3_USART1)
/* UART RX/TX Port/Pin definition */
#define USART_RX_PORT                   (GPIO_PORT_A)   /* PH13: USART1_RX */
#define USART_RX_PIN                    (GPIO_PIN_10)
#define USART_RX_GPIO_FUNC              (GPIO_FUNC_20_USART1_RX)

#define USART_TX_PORT                   (GPIO_PORT_A)   /* PH15: USART1_TX */
#define USART_TX_PIN                    (GPIO_PIN_09)
#define USART_TX_GPIO_FUNC              (GPIO_FUNC_20_USART1_TX)
    /* Enable peripheral clock */
    PWC_Fcg3PeriphClockCmd(USART_FUNCTION_CLK_GATE, Enable);
	
	/* Configure USART RX/TX pin. */
    GPIO_SetFunc(USART_RX_PORT, USART_RX_PIN, USART_RX_GPIO_FUNC, PIN_SUBFUNC_DISABLE);
    GPIO_SetFunc(USART_TX_PORT, USART_TX_PIN, USART_TX_GPIO_FUNC, PIN_SUBFUNC_DISABLE);

使能时钟,给USART1上电,配置PA9和PA10的功能为TX和RX。

/* UART unit definition */
#define USART_UNIT                      (M4_USART1)
#define USART_BAUDRATE                  (19200UL)
#define USART_DATA_BITS                 (8U)
#define USART_CHECK_BITS                (0U)
#define USART_STOP_BITS                 (1U)
#define USART_FRAME_BITS                (USART_DATA_BITS + USART_CHECK_BITS + \
										 USART_STOP_BITS + (1U))
    const stc_usart_uart_init_t stcUartInit = {
        .u32Baudrate = USART_BAUDRATE,
        .u32BitDirection = USART_LSB,
        .u32StopBit = USART_STOPBIT_1BIT,
        .u32Parity = USART_PARITY_NONE,
        .u32DataWidth = USART_DATA_LENGTH_8BIT,
        .u32ClkMode = USART_INTERNCLK_OUTPUT,
        .u32PclkDiv = USART_PCLK_DIV16,
        .u32OversamplingBits = USART_OVERSAMPLING_8BIT,
        .u32NoiseFilterState = USART_NOISE_FILTER_DISABLE,
        .u32SbDetectPolarity = USART_SB_DETECT_FALLING,
    };
	
	if (Ok != USART_UartInit(USART_UNIT, &stcUartInit))
    {
        for (;;)
        {
        }
    }

参考官方例程写的,这个代码风格跟TRM和ADC的例程不一样。

  • 波特率19200

  • 左对齐

  • 停止位1

  • 校验位无

  • 数据位8

  • 时钟模式后面说

  • 64分频

  • 8位过采样,这个不是很清楚,猜测比特率为192,000,USART工作频率为6,250,000,为其32倍,32是个8位数。
    在这里插入图片描述

  • 不开滤波

  • 开始位检测方式为RX管脚低电平

时钟模式
在这里插入图片描述
当作UART使用,并使用内部时钟时,可以设置为00或者01,本例只有TX RX,没有配置CK脚,按道理可以配置成00。但是配置成00的话,CR1寄存器中的RTOF标志立不起来,串口也进不了TIMEOUT中断。例程里面用的是01。使用01后就没有这个故障,以后再找原因。

三、配置TIMEOUT中断

以常用的1起始位,8数据位,0校验位,1停止位来说,UART的一帧数据是10bit,其中有效的数据位为8bit,即一帧只有一字节。为了避免每收到一个字节就通过一次中断来进行处理,造成的频繁中断问题,一般会采用DMA接收方式。STM32提供了一个idle中断,它在RX空闲的时候触发,可以用来表示应用层的一帧数据发送完毕,我们可以在这个中断中将DMA的传输目的地地址复位。HC32F4A0的USART没有IDLE中断,取而代之的是一个灵活性更强但是使用起来也相对更复杂的TIMEOUT中断。

IDLE是在监测到数据接收后(即串口的RXNE位被置位)开始检测,当总线上在一个字节对应的周期内未再有新的数据接收时,触发空闲中断IDLE位就会被被硬件置1。这个触发条件时固定,一个周期没收到新数据就会触发。而TIMEOUT中断的空闲时长则允许用户自定义。想要使用该中断,甚至还必须需要引入TMR0这个外设来协助完成。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里不详细介绍TMR0,只列出用以配合USART1的TIMEOUT中断的配置方式。
配置USART1的TIOMEOUT中断(调用了从STM32移植过来的MISC):

/* UART unit interrupt definition */
#define USART_RXTO_INT_SRC              (INT_USART1_RTO)
#define USART_RXTO_INT_IRQn             (Int002_IRQn)

	/* Setting up USART1 interrupts*/
    stc_irq_signin_config_t stcIrqSigninCfg;
	NVIC_InitTypeDef NVIC_InitStructure;	
	
    /* Register RX timeout IRQ handler && configure NVIC. */	
    stcIrqSigninCfg.enIRQn = USART_RXTO_INT_IRQn;
    stcIrqSigninCfg.enIntSrc = USART_RXTO_INT_SRC;
    stcIrqSigninCfg.pfnCallback = &USART_RxTimeout_IrqCallback;
    (void)INTC_IrqSignIn(&stcIrqSigninCfg);
	NVIC_InitStructure.NVIC_IRQChannel = USART_RXTO_INT_IRQn;      
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;		
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			
    NVIC_InitStructure.NVIC_IRQChannelCmd = Enable;
    NVIC_Init(&NVIC_InitStructure);
    
    /* Enable TX && RX && RX interrupt function */
    USART_FuncCmd(USART_UNIT, (USART_RX | USART_INT_RX | USART_TX | \
                               USART_RTO | USART_INT_RTO), Enable);

配置TMR0

/* UART unit definition */
#define USART_FRAME_BITS                (USART_DATA_BITS + USART_CHECK_BITS + \
										 USART_STOP_BITS + (1U))
/* Timer0 unit & channel definition */
#define TMR0_UNIT                       (M4_TMR0_1)
#define TMR0_CH                         (TMR0_CH_A)
#define TMR0_FUNCTION_CLK_GATE          (PWC_FCG2_TMR0_1)

/**
 * @brief  Configure TMR0.
 * @param  None
 * @retval None
 */
static void TMR0_Config(void)
{
    uint32_t u32CmpVal;
    stc_tmr0_init_t stcTmr0Init;

    PWC_Fcg2PeriphClockCmd(TMR0_FUNCTION_CLK_GATE, Enable);

    /* Clear CNTAR register for channel A */
    TMR0_SetCntVal(TMR0_UNIT, TMR0_CH, 0U);

    /* TIMER0 basetimer function initialize */
    (void)TMR0_StructInit(&stcTmr0Init);
    stcTmr0Init.u32ClockDivision = TMR0_CLK_DIV8;
    stcTmr0Init.u32ClockSource = TMR0_CLK_SRC_XTAL32;
    stcTmr0Init.u32HwTrigFunc = (TMR0_BT_HWTRG_FUNC_START | TMR0_BT_HWTRG_FUNC_CLEAR);
    if (TMR0_CLK_DIV1 == stcTmr0Init.u32ClockDivision)
    {
        u32CmpVal = (USART_FRAME_BITS*3 - 4UL);
    }
    else if (TMR0_CLK_DIV2 == stcTmr0Init.u32ClockDivision)
    {
        u32CmpVal = (USART_FRAME_BITS*3/2UL - 2UL);
    }
    else
    {
        u32CmpVal = (USART_FRAME_BITS*3 / (1UL << (stcTmr0Init.u32ClockDivision >> TMR0_BCONR_CKDIVA_POS)) - 1UL);
    }
    DDL_ASSERT(u32CmpVal <= 0xFFFFUL);
    stcTmr0Init.u16CmpValue =  (uint16_t)(u32CmpVal);
    (void)TMR0_Init(TMR0_UNIT, TMR0_CH, &stcTmr0Init);

    /* Clear compare flag */
    TMR0_ClearStatus(TMR0_UNIT, TMR0_CH);
}

RTB的起初按照STM32的IDLE来设置的,即10bit长度对应的周期数时长。结果后面实测时发现,在10bit,甚至20bit长度的等待时延下,进入中断后读取DMA的目标地址有时候不准确,放长到30bitUSART_FRAME_BITS*3长度时测试每次都可以读准。

配置DMA:

#define USART1_DMA_TRIGGER_SOURCE              (EVT_USART1_RI)
	DMA_SetTriggerSrc(M4_DMA1, USART1_DMA_CH, USART1_DMA_TRIGGER_SOURCE);

接收到一个字节就触发DMA接收。

#define USART1_DMA_SRC_ADDR            		   ((uint32_t)(&M4_USART1->DR) + 2UL)
#define USART1_DMA_CH                          (DMA_CH0)
#define USART1_BUFFER_SIZE            		   (512Ul)
__IO uint8_t USART1_R_data[USART1_BUFFER_SIZE] __attribute__ ((at(0x20001000)));

	stc_dma_init_t stcDmaInit;
	(void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32IntEn = DMA_INT_DISABLE;
    stcDmaInit.u32BlockSize = 1UL;
    stcDmaInit.u32TransCnt = 0UL;
    stcDmaInit.u32DataWidth = DMA_DATAWIDTH_8BIT;
    stcDmaInit.u32DestAddr = (uint32_t)(&USART1_R_data[0]);
    stcDmaInit.u32SrcAddr = USART1_DMA_SRC_ADDR;
    stcDmaInit.u32SrcInc = DMA_SRC_ADDR_FIX;
    stcDmaInit.u32DestInc = DMA_DEST_ADDR_INC;
    (void)DMA_Init(M4_DMA1, USART1_DMA_CH, &stcDmaInit);
    
    DMA_Cmd(M4_DMA1, Enable);
    DMA_ChannelCmd(M4_DMA1, USART1_DMA_CH, Enable);

USART的32为数据寄存器DR的高16位保存接收数据信息,所以&M4_USART1->DR) + 2UL

中断服务函数:

/**
 * @brief  USART RX timeout IRQ callback.
 * @param  None
 * @retval None
 */
static void USART_RxTimeout_IrqCallback(void)
{
    TMR0_Cmd(TMR0_UNIT, TMR0_CH, Disable);
    USART_ClearStatus(USART_UNIT, USART_CLEAR_FLAG_RTOF);  
    DMA_ChannelCmd(M4_DMA1, USART1_DMA_CH, Disable);
	uint32_t DMA_DestAddr = DMA_GetDestAddr(M4_DMA1, USART1_DMA_CH);
	uint8_t* p = (uint8_t*)DMA_DestAddr;
	*p = '\0';
	DMA_SetDestAddr(M4_DMA1, USART1_DMA_CH, (uint32_t)(&USART1_R_data[0]));
    DMA_ChannelCmd(M4_DMA1, USART1_DMA_CH, Enable);
    
	//RS485_SendData(USART1_R_data, 10);
	
}
  • 关TMR01的通道A(每次由USART1硬件激活)
  • 清USART1中断标志
  • 关DMA通道
  • 读DMA目标地址指针,并在该处加上‘\0’截止符。
  • 重置DMA目标地址
  • 开启DMA通道。

四、USART发送单字节/多字节/字符串

/* 发送单字节 */
static void RS485_SendByte(uint8_t byte)
{
	USART_SendData(USART_UNIT, (uint16_t)byte);
	while (Reset == USART_GetStatus(USART_UNIT, USART_SR_TXE))
	{		
	}
}

/* 发送多字节 */
void RS485_SendData(uint8_t* str, uint32_t len)
{
	uint32_t k=0;
	do 
	{
	  RS485_SendByte(*(str + k));
	  k++;
	} while((k < USART1_BUFFER_SIZE)&&(k < len));
	while (Reset == USART_GetStatus(USART_UNIT, USART_FLAG_TC))
	{		
	}
}

/* 发送字符串 */
void RS485_SendString(char* str)
{
	uint32_t k=0;
	do 
	{
	  RS485_SendByte(*(str + k));
	  k++;
	} while((*(str + k) != '\0')&&(k < USART1_BUFFER_SIZE));
	while (Reset == USART_GetStatus(USART_UNIT, USART_FLAG_TC))
	{		
	}
}

很多EE出身的工程师包括我在内,在做嵌入式开发时着重与功能开发,软件细节上比较粗糙。在很多嵌入式软件里都可以看到开发者在中断中调用USART发送函数。但这么做是不被提倡的,如果确实有这个实时性的必要,至少要在死循环内加上一个超时异常。

  • 12
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
### 回答1: STM32F103 USART使用DMA接收发送的框图如下: ![STM32F103的USART使用DMA接收发送框图](https://i.imgur.com/rsldyhT.png) 首先,框图的USART模块用来实现与外部设备进行串行通信。USART模块包括了发送器和接收器两个功能部分。发送器负责将数据从处理器发送到外部设备,接收器负责将外部设备发送的数据接收到处理器。 在USART模块的发送/接收方向,连接有DMA控制器。DMA(直接存储器访问)是一种独立于处理器的数据传输方式,它通过直接从外部设备读写数据,而不需要处理器的干预。DMA控制器有多个通道,用于连接不同的外设。在本框图DMA通道与USART模块相连。 对于USART的发送,当处理器要发送数据时,首先将待发送数据写入USART模块的发送寄存器。然后,触发DMA控制器,将发送寄存器的数据送到DMA通道,最后DMA将数据传输到USART外设,并且把发送完的数据长度传送到处理器,以便处理器知道数据是否发送完成。 对于USART的接收,当外部设备发送数据到USART接收寄存器时,触发USART接收断,通知处理器接收到了数据。然后,处理器触发DMA控制器,将接收数据的DMA通道启动,将数据从USART外设传输到DMA寄存器。接着,处理器可以通过查询DMA寄存器,获取接收到的数据。 通过使用DMA接收和发送数据,可以减轻处理器的负担,提高系统的运行效率。 ### 回答2: stm32f103是意法半导体推出的一款32位单片机系列产品,其USART是一种通用异步收发传输器。使用DMA(直接存储器访问)来实现USART接收和发送功能,可以提高数据传输的效率。 以下是STM32F103 USART使用DMA接收发送的框图解读: 1. USART 模块:USART模块是STM32F103系列的一种通信模块,用于数据的异步传输。它包含了需要将数据发送到外部设备或从外部设备接收数据的各种寄存器和控制逻辑。 2. DMA控制器:DMA控制器是STM32F103系列的一个重要模块,用于在外设和内存之间进行高速数据传输,减少CPU的负担。 3. 数据内存:数据内存用于存储接收到的数据或待发送的数据。 4. NVIC:NVIC是断控制器,用于管理各种断源和断优先级。 5. DMA传输请求:当USART接收或发送缓冲区准备好数据时,会产生DMA传输请求,将数据传输到或从数据内存。 6. DMA配置寄存器:DMA配置寄存器用于配置DMA通道的参数,如数据传输方向、数据长度等。 7. DMA传输完成断:当DMA传输完成时,会产生断请求,引起断处理程序的执行。 8. 断处理程序:断处理程序负责处理DMA传输完成断请求,可以在断处理程序接收到的数据进行处理或触发其他操作。 通过使用DMA来实现USART接收和发送,可以实现数据的高效传输和处理。接收时,当USART接收缓冲器准备好数据后,它会产生DMA传输请求,将数据传输到数据内存。当数据传输完成时,DMA会产生断请求,触发断处理程序对接收到的数据进行处理。发送时,将待发送的数据写入数据内存,并设置DMA传输方向和数据长度等参数。DMA会自动从数据内存读取数据并将其传输到USART发送缓冲器进行发送。当数据传输完成时,同样会产生DMA传输完成断请求。 总之,使用DMA来处理USART的接收和发送,可以提高数据传输效率,减轻CPU负担,从而更好地满足系统对数据通信的需求。 ### 回答3: STM32F103是一款32位的ARM Cortex-M3系列微控制器,具有多个通用同步/异步收发器(USART)接口,用于与外部设备进行数据通信。这款芯片还支持使用DMA(直接内存访问)功能来实现USART的数据传输。 在使用DMA进行数据接收和发送时,首先需要设置USART的相应寄存器(例如,配置波特率、数据位数和停止位等参数),然后设置DMA控制器的寄存器,以使其与特定的USART通道相连接。 接下来,通过配置DMA通道的源和目的地址,将USART的RDR(接收数据寄存器)作为DMA的源地址,将USART的TDR(发送数据寄存器)作为DMA的目的地址。然后,设置DMA通道的传输数量,以确定需要传输的数据的大小。 在接收数据时,一旦USART接收到数据,并将其存储在RDR寄存器DMA控制器就会自动将该数据从RDR寄存器传输到目的地址。这样,通过DMA传输数据可以减轻主CPU的负担,提高数据传输效率。 在发送数据时,将要发送的数据存储在TDR寄存器,并通过DMA控制器将其传输到目的地址。这样,通过DMA发送数据可以使主CPU能够在数据传输过程执行其他任务,提高整个系统的性能。 总结起来,使用DMA进行USART数据接收和发送时,需要配置USART和DMA控制器的寄存器,并设置源和目的地址以及传输数量。通过这种方式,可以实现高效的数据传输,减轻主CPU的负担,提高系统的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值