看用户手册一上来就糊涂了,手册里面写的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。
本例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后就没有这个故障,以后再找原因。
以常用的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);
}
/* 发送单字节 */
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发送函数。但这么做是不被提倡的,如果确实有这个实时性的必要,至少要在死循环内加上一个超时异常。