标签:
UART:universal asynchronous receiver and transmitter 通用 异步 接收 发送 [总线信号] TX , RX USART:universal synchronous asynchronous receiver and transmitter 通用 同步 异步 接收 发送 [总线信号] TX, RX, CK
总体来说,usart只是比uart多了一个同步信号,usart可以使用同步方式进行信息的收发
同步传输
传输过程由主机的SCLK信号控制,在SCLK的一个上升沿或者下降沿到来的时候,一位数据的传输,不受时间长短限制,只与主机的SCLK信号有关。这个跟SPI协议很像
异步传输
传输过程中标志一个数据的开始是一个起始位,通常是逻辑’0’。传输bit数据靠接收端的采样,这个是有严格的时间限定的,接收方与发送方必须采用严格的一致的发送与采样时间间隔,也就是说,信息的发送速度靠发送方与接收方共同决定。这个是我们通常用到的异步串口通讯
- 可通过DMA实现高速度的数据传输
- 全双工, 异步
- NRZ 标准格式 (Mark/Space)
- 分步波特率发生系统
- 通用可编程波特率大小
- 可编程数据长度 (8 or 9 bits)
- 可配置的停止位,支持 1 or 2 停止位
- LIN 主机同步中断发送和 LIN 从机中断检测
- 当usart被硬件配置为 LIN模式的时候支持13-bit 中断产生和 10/11 bit 中断检测
- 发送端的时钟输出作为传输同步信号
- 单工半双工通信
传输检测标志: – 接收缓冲区满 – 发送缓冲区空 – 传输结束 奇偶检验: – 传输奇偶检验位 – 检查接收到数据的奇偶检验位 四个错误检测标志: – 溢出错误 – 噪声检测 – 帧错误 – 奇偶检验错误 中断源: – CTS 改变 – LIN 中断监测 – 发送数据寄存器空 – 传输结束 – 接收寄存器满 – 接收到线路空闲 – 溢出错误 – 帧错误 – 噪声错误 – 奇偶校验错误
流控的用途举例:比如电脑发送给单片机的数据,而这些数据比较重要,不允许有大量的丢失,但是电脑的速度比较快,发送给单片机的数据单片机可能没有来得及处理完毕,就有新的数据发送过来,这样就可能会造成数据缓冲区溢出,这个时候可以向电脑端发送数据停止标志来让电脑停止发送数据一段时间,等到数据处理完毕之后再重新进行发送。而现在普遍使用软件流控,也就是通过向发送端发送XON与XOFF标志来实现,XON就是16进制的ctrl+q组合键对应的键码,XOFF是16进制的ctrl+s对应的组合键键码
有8bit与9bit数据格式,每种数据格式都是由逻辑’0’作为起始位,以逻辑’1’作为结束位。break frame与idle frame有少许不同,具体如下图:
配置停止位
1 stop bit : 默认的停止位配置 2 Stop bits : USART, 单线和 modem 模式均支持该停类型停止位 0.5 stop bit : 主要用于 Smartcard 模式下接收数据 1.5 stop bits : 用于 Smartcard 模式下接收与发送数据 \# 一个 idle 帧的传输必须包含停止位 #
停止位:
- 以下情况下TC被清除
- 对 USART_SR 寄存器的读操作
- 对 USART_DR 寄存器的写操作
下图是TC与TXE在传输过程中的状态变化
? 如果 RXNEIE 被设置的话,系统就会产生一个中断
? 如果有帧错误的话,错误标志就会被设置
? 在多缓冲区模式下, RXNE 在每次接收完1byte数据之后就会被置位并且被 DMA 的读数据寄存器操作清除置位
? 在单缓冲区模式下, 对 USART_DR 寄存器的读操作会清除 RXNE 标志。该位也可以通过写0来进行清除。并且在下一次传输之前 RXNE 位必须被清除,以防止数据被覆盖
串口功能表:
当 OVER8=0时, DIV_fraction[3:0] 可以被读写 当 OVER8=1时, DIV_fraction[2:0] 可以被读写,并且 DIV_fraction[3] 必须被清零
波特率计算公式:
PS:下面的0d27指的是10进制的27,也就是我们常说的自然数27,其他的0dxx也是类似理解
OVER8 = 0时的例子:
If DIV_Mantissa = 0d27 and DIV_Fraction = 0d12 (USART_BRR = 0x1BC), then
Mantissa (USARTDIV) = 0d27
Fraction (USARTDIV) = 12/16 = 0d0.75
Therefore USARTDIV = 0d27.75
To program USARTDIV = 0d25.62
This leads to:
DIV_Fraction = 16*0d0.62 = 0d9.92
The nearest real number is 0d10 = 0xA
DIV_Mantissa = mantissa (0d25.620) = 0d25 = 0x19
Then, USART_BRR = 0x19A hence USARTDIV = 0d25.625
To program USARTDIV = 0d50.99
This leads to:
DIV_Fraction = 16*0d0.99 = 0d15.84
The nearest real number is 0d16 = 0x10 => overflow of DIV_frac[3:0] => carry must be
added up to the mantissa
DIV_Mantissa = mantissa (0d50.990 + carry) = 0d51 = 0x33
Then, USART_BRR = 0x330 hence USARTDIV = 0d51.000
假如我们想要在时钟为84M频率下设置波特率为115200,并且OVER8 = 0,由上面的波特率计算公式以及USARTDIV计算公式可知:
- 首先算出USARTDIV需要被设置为45.57
- 拆分整数部分与小数部分,分别是45与0.57
- 整数部分由 USART_BRR 的 DIV_Mantissa 决定,这个自然不必说,而小数部分由 DIV_Fraction 决定,可以知道 DIV_Fraction 的值需要是 0.57 * 16 = 9.12,最接近9.12的是9,所以需要将 DIV_Fraction 的值设置为9
OVER8 = 1时的例子:
If DIV_Mantissa = 0x27 and DIV_Fraction[2:0]= 0d6 (USART_BRR = 0x1B6), then
Mantissa (USARTDIV) = 0d27
Fraction (USARTDIV) = 6/8 = 0d0.75
Therefore USARTDIV = 0d27.75
To program USARTDIV = 0d25.62
This leads to:
DIV_Fraction = 8*0d0.62 = 0d4.96
The nearest real number is 0d5 = 0x5
DIV_Mantissa = mantissa (0d25.620) = 0d25 = 0x19
Then, USART_BRR = 0x195 => USARTDIV = 0d25.625
To program USARTDIV = 0d50.99
This leads to:
DIV_Fraction = 8*0d0.99 = 0d7.92
The nearest real number is 0d8 = 0x8 => overflow of the DIV_frac[2:0] => carry must be
added up to the mantissa
DIV_Mantissa = mantissa (0d50.990 + carry) = 0d51 = 0x33
Then, USART_BRR = 0x0330 => USARTDIV = 0d51.000
下面是不同的PCLK频率下对应波特率的关系。我们由时钟那一节可以知道,USART1与USART6是属于APB2域,也就是PCLK2域,此域手册已经说明了最大是84M,而其余的串口是在APB1域,也就是PCLK1域,该域最大时钟频率为42M
在异步接收模式下,只有系统时钟偏差小于接收器能够接受的最大偏差值的时候接收器才能够正确接收数据:
接收器最大忍受偏差值取决于10 or 11 bit 数据长度选择 8 or 16 过采样选择 是否使用分段波特率。(也就是带小数位的波特率计算) 使用 1 bit or 3 bits 对数据进行采样
在设置任何模块的时候,都要先使能它的时钟,然后才能够设置它的寄存器,否则寄存器设置不成功
/* 初始化usart1
* 波特率:115200
* 模块时钟:84M
*/
void bym_uart_init(void)
{
BYM_RCC_struct->APB2ENR |= (1 << 4); //Enable usart1‘s clock
// WRITE_R_BIT_BAND(0x23844, 4, 1); //位带操作,同上一个语句效果一样,该宏定义在 STM32内存与总线一节有提到
BYM_USART_struct->BRR &= ~((0xfff << 4) | (0xf < 0)); //Clear DIV_Mantissa and DIV_Fraction
BYM_USART1_struct->BRR |= ((45 << 4) | (9 << 0)); //DIV_Mantissa = 45, DIV_Fraction = 9 USARTDIV = 45.57
/* oversampling by 16 USART enabled 1 Start bit, 8 Data bits, n Stop bit
* Parity control disabled All interrupts are disiabled
* BYM_USART_struct->CR1
*/
BYM_USART1_struct->CR1 &= ~(1 << 15); //oversampling by 16
BYM_USART1_struct->CR1 |= (1 << 3); //TX enabled
BYM_USART1_struct->CR1 |= (1 << 13); //USART enabled
/* 1 stop bit SCLK pin disabled
* BYM_USART_struct->CR2
*/
/* No CTS/RTS SCLK pin disabled
* BYM_USART_struct->CR3
*/
/* 引脚复用为usart功能,串口1对应的是PA9与PA10引脚 */
gpio_init(BYM_GPIOA, BYM_Px9, BYM_PULL_UP, BYM_GPALT, BYM_LOW_LEVEL, BYM_PUSH_PULL);
gpio_set_alt_speed(BYM_GPIOA, BYM_Px9, BYM_AF7, BYM_GPIO_HIGI_SPEED);
gpio_init(BYM_GPIOA, BYM_Px10, BYM_PULL_UP, BYM_GPALT, BYM_LOW_LEVEL, BYM_PUSH_PULL);
gpio_set_alt_speed(BYM_GPIOA, BYM_Px10, BYM_AF7, BYM_GPIO_HIGI_SPEED);
}
该初始化函数并不支持动态的指定时钟频率与串口波特率,但是根据上面的公式可以实现一个动态的设定时钟频率与串口波特率的串口初始化函数。更改部分代码如下:
void bym_uart_init(u32 clk, u32 baud)
{
u32 usart_div = 0;
BYM_RCC_struct->APB2ENR |= (1 << 4); //Enable usart1‘s clock
usart_div = ((clk * 1000000 / 16) * 100) / baud;
usart_div = (usart_div / 100) * 100 + (usart_div % 100) * 16 / 100;
// BYM_USART_struct->BRR &= ~((0xfff << 4) | (0xf < 0)); //Clear DIV_Mantissa and DIV_Fraction
BYM_USART1_struct->BRR |= (((usart_div / 100) << 4) | (usart_div % 100)); //DIV_Mantissa = 45, DIV_Fraction = 9 USARTDIV = 45.57
... ...
}
struct bym_usart_init_structure
{
u32 baud;
u32 pclk;
u8 oversample;
u8 tx_enable;
u8 rx_enable;
};
void bym_uart_init(struct bym_usart_init_structure *usart_struct)
{
u32 usart_div = 0;
BYM_RCC_struct->APB2ENR |= (1 << 4); //Enable usart1‘s clock
// WRITE_R_BIT_BAND(0x23844, 4) = 1;
usart_div = ((usart_struct->pclk * 1000000 / 16) * 100) / usart_struct->baud;
usart_div = (usart_div / 100) * 100 + (usart_div % 100) * 16 / 100;
// BYM_USART_struct->BRR &= ~((0xfff << 4) | (0xf < 0)); //Clear DIV_Mantissa and DIV_Fraction
BYM_USART1_struct->BRR |= (((usart_div / 100) << 4) | (usart_div % 100)); //DIV_Mantissa , DIV_Fraction , USARTDIV
/* oversampling by 16 USART enabled 1 Start bit, 8 Data bits, n Stop bit
* Parity control disabled All interrupts are disiabled
* BYM_USART_struct->CR1
*/
BYM_USART1_struct->CR1 &= ~(1 << 15); //oversampling by 16
BYM_USART1_struct->CR1 |= (usart_struct->oversample << 15);
BYM_USART1_struct->CR1 &= ~(1 << 3);
BYM_USART1_struct->CR1 |= (usart_struct->tx_enable << 3); //TX set
BYM_USART1_struct->CR1 &= ~(1 << 2);
BYM_USART1_struct->CR1 |= (usart_struct->rx_enable << 2); //RX set
BYM_USART1_struct->CR1 |= (1 << 13); //USART enabled
/* 1 stop bit SCLK pin disabled
* BYM_USART_struct->CR2
*/
/* No CTS/RTS SCLK pin disabled
* BYM_USART_struct->CR3
*/
/* 引脚复用为usart功能,串口1对应的是PA9与PA10引脚 */
gpio_init(BYM_GPIOA, BYM_Px9, BYM_PULL_UP, BYM_GPALT, BYM_LOW_LEVEL, BYM_PUSH_PULL);
gpio_set_alt_speed(BYM_GPIOA, BYM_Px9, BYM_AF7, BYM_GPIO_HIGI_SPEED);
gpio_init(BYM_GPIOA, BYM_Px10, BYM_PULL_UP, BYM_GPALT, BYM_LOW_LEVEL, BYM_PUSH_PULL);
gpio_set_alt_speed(BYM_GPIOA, BYM_Px10, BYM_AF7, BYM_GPIO_HIGI_SPEED);
}
struct bym_usart_init_structure usart_struct;
usart_struct.baud = 115200; //baud
usart_struct.oversample = 0;
usart_struct.pclk = 84;
usart_struct.rx_enable = 0; //Disable
usart_struct.tx_enable = 1; //Enable
bym_uart_init(&usart_struct);
上面只是实现了一个及其简单的面向对象的操作,在需要初始化一个串口的时候,只需要申请一个 bym_usart_init_structure 类型的结构体,填充结构体里面的数据,然后将结构体转交给 bym_uart_init 执行串口的初始化,并不需要过多的关注内部结构,并且又能够很轻松的实现自定义参数的初始化。这种操作在linux内核里面非常常见,基本上写驱动都是采用这样的面向对象风格的编程,以后的代码风格尽量就会采用这种风格进行编写。由于时间比较仓促, 本模块并没有很好的实现面向对象,函数的容错性很小,并且不支持对多个串口使用同一个函数进行初始化,还有停止位,校验位等等都不能够实现自定义,该函数功能太过弱小,以待改进。
标签:
原文地址:http://blog.csdn.net/u013904227/article/details/51468853