单片机串口在异步通信的时候通常采用过采样的方式来发送和接收串行数据,如下面数据手册中的说明:
反复看了多遍,大概意思是说减小过采样的倍数可以产生更精确的波特率,但是减小过采样倍数后,由于采样次数的减少在输入数据发生抖动的时候会导致误码率的增加,对于这段话的具体理解过程如下:
串口的设备时钟U_PCLK用于产生串口波特率时钟,比如设备时钟为12M,波特率为115200,则波特率时钟周期为12M/115200,用这个值来设置BRG寄存器;但是由于使用了过采样来实现串行数据的收发,因此需要使用到过采样寄存器(OSR),OSR寄存器中的值表示每个数据比特位的收发使用到的设备时钟数,比如OSR的值为0xF,表示每个数据比特位的收发需要使用到16个设备时钟周期,也就是说采样频率是波特率的16倍,最小可以到5倍,这个时候BRG寄存器的数值就应该设置为12M/115200/(OSR值+1),比如: LPC_USART0->OSR = 0x07;
LPC_SYSCON->UARTCLKDIV = 1; /* UART时钟分频值为 1 */
LPC_SYSCON->SYSAHBCLKCTRL |= (1 << 14); /* 初始化UART AHB时钟 */
LPC_USART0->BRG = SystemCoreClock * LPC_SYSCON->SYSAHBCLKDIV /
(LPC_SYSCON->UARTCLKDIV * (LPC_USART0->OSR+1) * UART_BPS) - 1; /* 串口通信波特率 */
这样通过BRG寄存器和OSR寄存器的设置就能进行正常的串口通信了,由于BRG寄存器为整数寄存器,在波特率比较高或者设置时钟频率低的时候误差比较大;比如OSR为16倍采样率的时候,经过上面式子计算出的BRG=6,用下面的算式计算出的波特率为:
uiRealbaud = (uint32_t)(SystemCoreClock * LPC_SYSCON->SYSAHBCLKDIV /
((LPC_USART0->OSR+1) * uiClkDiv * uiUartBRG * (1 + (double)uiFDRMul / uiFDRDiv)))。12M/(16*1*7)=107,142.85,误差为:(115200-107143)/115200=7%,所以减小采样频率可以提高波特率精度。采样数据的减少,可能导致数据比特的错误,可以如下图来说明:
上图采样脉冲为3个,当使用5倍波特率进行采样的时候,这时采样设备时钟数为5个,第一个为开始采样时间,中间3个为连续的数据比特采样时钟,最后一个为结束采样时钟。如果数据比特的斜率有抖动,如下图,数据采样时钟可能采样到不对的电平信号,导致比特错误。
过采样能增强抗干扰的原理分析,以16倍频为例:标准UART的RXD前端有一个"1到0跳变检测器",当其连续接受到8个RXD上的地电平时,该检测器就认为RXD线出现了起始位,进入接受数据状态.在接受状态,接受控制器对数据位7,8,9三个脉冲采样,并遵从三中取二的原则确定最终值.采用这一方法的根本目的还是为了增强抗干扰,提高数据传送的可靠性,采样信号总是在每个接受位的中间位置,可以避开数据位两端的边沿失真,也可以防止接受时钟频率和发送时钟频率不完全同步引起的误差.异步串口通信的数据格式:
由于在空闲状态时,传送线为逻辑“1”状态,而数据的传送总是以一个起始位“0”开始,所以当接收器检测到一个从“1”向“0”的跳变时,便视为可能的起始位(要排除干扰引起的跳变);起始位被确认后,就知道发送器已开始发送,接收器就可以按这个数据通信格式接收后续的数据了;当检测到停止位“1”后就表明一帧字符数据已发送完毕。关于接收器的设计最主要的一点是如何提高采样的准确率,最好是保证采样点处于被采样数据的时间中间点。所以,在接收采样时要用比数据波特率高n倍(n≥1)速率的时钟对数据进行采样。在本程序中用16倍波特率时钟进行采样。结合图示,我们讲解一下如何让采样时刻处于被采样数据的时间中间点:
1. 在t1时刻若检测到低电平,就开始对这个低电平进行连续的检测
2. 当检测了8个时钟周期后,到达t2,此刻,若前面的8个周期都是低电平,则认为检测到了起始脉冲。否则就认为是干扰,重新检测。
3. 在检测到起始位后,再计数16个采样时钟周期就到达了第一个数据位的时间中间点t3,在此刻采样数据并进行保存。
4. 然后再经过16个周期,就是第二个数据位的时间中间点,在此时刻进行采样;然后,再经过16个周期,就是第三个数据位的时间中间点, 在此时刻进行采样…..一直这样采样,直到把所有的数据位采样完毕。
分数波特率发生器:上面程序中计算波特率的方法是整数的方法,在设备时钟频率低或者波特率比较高的时候误差比较大,这个时候可以使用分数波特率发生器。比如,115200波特率,BRG寄存器的值=12M/(16*115200)=6.5104166666666666666666666666667=6(取整),然后mcu产生的实际波特率,用上面提到的计算公式为12M/(16*1*7)=107,142.85,误差为:(115200-107143)/115200=7%;2400波特率的时候,BRG寄存器的值=12M/(16*2400)=312.5=312(取整),然后mcu产生的实际波特率,用上面提到的计算公式为12M/(16*1*313)=2,396.166134185,误差为:(2400-2396)/2400=0.1667%。分数波特率发生器采用多位小数的分数逼近法,该算法的数学原理是:任何一个多位小数,无论是无理数还是有理数,均可以用一个分数来近似它,并可以用一定的程序使其误差越来越小,直至达到所需的精确度。比如,115200波特率采用分数波特率发生器的时候通过计算如下:
用分数发生器,则BRG=5,分子部分为58,分母部分为192+1=193,用前面的公式进行计算,实际波特率=12M/(16*1*5*(1+58/193))=115,338.6454,误差为:(115200-15,338)/115200=0.119%,计算出来的波特率精度高。
void FDRCalculate( uint32_t uiMainClk, /* 主时钟 */
uint32_t uiBPS, /* 波特率 */
uint32_t *puiUartBRG, /* 波特率产生器 */
uint32_t *puiFDRMul, /* 小数波特率发生器分子 */
uint32_t *puiFDRDiv) /* 小数波特率发生器分母 */
{
uint32_t uiClkDiv = LPC_SYSCON->UARTCLKDIV;
uint32_t uiFDRDiv = LPC_SYSCON->UARTFRGDIV + 1;
uint32_t uiDIVmin, uiDIVmax, uiDIVtemp;
uint32_t uiBRGmin, uiBRGmax, uiBRGtemp, uiMULTtemp;
static double fFDR = 1.0, fErr = 0.0, fErrmin = 1.0;
fFDR = (double)uiMainClk / (uiClkDiv * 16 * uiBPS); /* 小数分频期望值 */
/*
* 如果小数分频器期望值为小数,进行小数波特率发生器配置值计算;如果为整数,则不进行。
*/
if ((fFDR - (uint32_t)fFDR) != 0.0) {
uiDIVmin = uiFDRDiv / 2 + 1;
uiDIVmax = uiFDRDiv;
uiBRGmin = (uint32_t)(fFDR / 2) + 1;
uiBRGmax = (uint32_t)fFDR;
/*
* 循环计算求取误差最小的 uiUartBRG 和 uiFDRMul 值。
*/
for (uiDIVtemp = uiDIVmax; uiDIVtemp >= uiDIVmin; uiDIVtemp--) {
for (uiBRGtemp = uiBRGmin; uiBRGtemp <= uiBRGmax; uiBRGtemp++) {
uiMULTtemp = (uint32_t)((fFDR / uiBRGtemp - 1) * uiDIVtemp + 0.5);
fErr = fabs(uiBRGtemp * (1 + (double)uiMULTtemp / uiDIVtemp) - fFDR);
if (fErr < fErrmin) {
fErrmin = fErr;
*puiUartBRG = uiBRGtemp;
*puiFDRMul = uiMULTtemp;
*puiFDRDiv = uiDIVtemp;
if (fErrmin == 0.0)
return;
}
}
}
} else { /* 小数分频期望值为整数 */
*puiUartBRG = (uint32_t)fFDR;
*puiFDRMul = 0;
}
}