适用芯片
- CH579/ch578/ch577
- CH573/CH571
- CH583/CH582/CH581
- CH569/CH565(理论上适用)
UART 外设特点
- 串口的时钟来至系统主频的整数分频
- 收发分别有8个字节的硬件FIFO,支持fifo阈值中断
- 接收支持超时中断
UART初始化
官网的历程中,初始化写的比较简单,下面我们以ch579 为例
void ch57x_uart3_init(uint8_t remap) {
if(remap){
//重映射
GPIOPinRemap( ENABLE, RB_PIN_UART3 );
//TXD gpio初始化
GPIOB_SetBits(bTXD3_);
GPIOB_ModeCfg(bTXD3_, GPIO_ModeOut_PP_5mA);
//RXD gpio初始化
GPIOB_SetBits(bRXD3_);
GPIOB_ModeCfg(bRXD3_, GPIO_ModeIN_PU);
} else {
//不使能重映射
GPIOPinRemap( DISABLE, RB_PIN_UART3 );
//TXD gpio初始化
GPIOA_SetBits(bTXD3);
GPIOA_ModeCfg(bTXD3, GPIO_ModeOut_PP_5mA);
//RXD gpio初始化
GPIOA_SetBits(bRXD3);
GPIOA_ModeCfg(bRXD3, GPIO_ModeIN_PU);
}
//uart3 init
UART3_DefInit();
UART3_BaudRateCfg(115200);
//enable interupt
UART3_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
#if defined(CH579)
NVIC_EnableIRQ( UART3_IRQn );
#else
PFIC_EnableIRQ(UART3_IRQn);
#endif
}
UART接收处理
uint16_t user_rx_buffer_length_mask = 1024-1;
uint16_t user_rx_buffer_write_index = 0;
uint8_t uart_rx_buffer[1024];
void UART3_IRQHandler(void){
switch( UART3_GetITFlag() ){
case UART_II_LINE_STAT: // 线路状态错误
UART3_GetLinSTA();
break;
case UART_II_RECV_RDY:
case UART_II_RECV_TOUT:
while(R8_UART3_RFC) {
uart_rx_buffer[user_rx_buffer_write_index & user_rx_buffer_length_mask] = R8_UART3_RBR;
user_rx_buffer_write_index += 1;
}
break;
case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}
UART自带的UART超时处理(必须RX的触发设置>1字节,否则永远不会产生UART_II_RECV_TOUT,这不是WCH推荐的用法):
uint16_t user_rx_buffer_length_mask = 1024-1;
uint16_t user_rx_buffer_write_index = 0;
uint8_t uart_rx_buffer[1024];
//UART_II_RECV_TOUT的触发前提是当前rx fifo里面有数据,且串口持续了4个字节的时间没有数据进来
//不是每次接收都会触发UART_II_RECV_TOUT,如果我们在UART_II_RECV_RDY 每次都把fifo的数据读完
//如果一定要每次接收UART_II_RECV_TOUT都产生,就必须保证rxfifo 里面的数据不能为空
/*** Keep at least 1 byte in the RX fifo and UART_II_RECV_TOUT will be triggered at the end of every reception .***/
void UART3_IRQHandler(void){
switch( UART3_GetITFlag() ){
case UART_II_LINE_STAT: // 线路状态错误
UART3_GetLinSTA();
break;
case UART_II_RECV_RDY: // 数据达到设置触发点 ,这里可以留一个在接收fifo里面,保证每次都能产生接收超时
while( R8_UART3_RFC > 1) { // 这个方式必须保证uart的接收触发中断是大于1字节的
uart_rx_buffer[user_rx_buffer_write_index & user_rx_buffer_length_mask] = R8_UART3_RBR;
user_rx_buffer_write_index += 1;
}
break;
case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成
//for(uint8_t i=0;i < R8_UART3_RFC;i++) //There is an issue here
while(0 != R8_UART3_RFC) {
uart_rx_buffer[user_rx_buffer_write_index & user_rx_buffer_length_mask] = R8_UART3_RBR;
user_rx_buffer_write_index += 1;
}
break;
case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}
UART 查询发送
//tx process
//本查询函数需要扔到主循环不断调用
while(R8_UART1_TFC < UART_FIFO_SIZE){
//判断发送软件缓冲区,是否空,如果不空,就一个一个读出来,填到硬件fifo里
if((user_tx_buffer_write_index - user_tx_buffer_read_index )& user_tx_buffer_length_mask) {
//把软件缓冲区的数据填到uart的硬件发送fifo里
R8_UART1_THR = uart_tx_buffer[user_tx_buffer_read_index & user_tx_buffer_length_mask] ;
user_tx_buffer_read_index += 1;
}else{
break;
}
}
UART 中断发送
//开始发送,这里,我们需要先向串口的发送fifo里面写入数据,然后串口的发送fifo空的时候才会报中断上来,接下来我们在中断服务程序里面处理就好了
void uart_start_send(void) {
//这里涉及到中断中修改标志位,我们关中断后进行查标志位
__disable_irq();
if(false == uart1_tx_on_going) {
__enable_irq();
//由于写fifo的操作非常快,我们这里直接while循环即可
while(R8_UART1_TFC < UART_FIFO_SIZE){
//发送缓冲区是否有数据,这里我们用软件实现了一个缓冲区,如果有数据,就把数据取出来放到uart的fifo里
if((user_rx_buffer_write_index - user_rx_buffer_read_index )& user_rx_buffer_length_mask) {
R8_UART1_THR = uart_rx_buffer[user_rx_buffer_read_index & user_rx_buffer_length_mask] ;
user_rx_buffer_read_index += 1;
uart1_tx_on_going = true;
}else{
break;
}
}
}else{
__enable_irq();
}
}
//中断服务程序中的处理,这里主要关注 UART_II_THR_EMPTY 里面的处理
void UART1_IRQHandler(void) {
UINT8 i;
//GPIOA_SetBits(GPIO_Pin_0);
//GPIOA_ResetBits(GPIO_Pin_0);
switch( UART1_GetITFlag() ) {
case UART_II_LINE_STAT: // 线路状态错误
UART1_GetLinSTA();
break;
case UART_II_RECV_RDY: // 数据达到设置触发点
case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成
while(0 != R8_UART1_RFC) {
uart_rx_buffer[user_rx_buffer_write_index & user_rx_buffer_length_mask] = R8_UART1_RBR;
user_rx_buffer_write_index += 1;
}
break;
case UART_II_THR_EMPTY: //发送缓存区空,可继续发送
//这个事件产生只能说fifo是空的了,但是串口的发送缓冲区可能还有正在发送的数据在发送,如果是想要控制RS485之类的信号,请在这个事件产生时候,查询( R8_UART1_LSR & RB_LSR_TX_ALL_EMP ) 标志位
while(R8_UART1_TFC < UART_FIFO_SIZE){
if((user_tx_buffer_write_index - user_tx_buffer_read_index )& user_tx_buffer_length_mask) {
R8_UART1_THR = uart_tx_buffer[user_tx_buffer_read_index & user_tx_buffer_length_mask] ;
user_tx_buffer_read_index += 1;
}else{
uart1_tx_on_going = false;
break;
}
}
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}
BLE工程中UART收发处理:
CH57x的ble协议栈是查询方式实现的,实际应用中,中断在微观上会打断BLE的运行,所以能少用中断就少用中断,在中断张能少占用时间就少占用一些,
但是而ch57x的uart 又没有dma,只有有8个直接的fifo,所以,
在没有串口流控(CTS RTS)的情况下,建议uart的接收使用中断方式,而uart发送使用查询方式
Q&A
1.休眠情况下UART能收发数据吗
UART依赖于高频时钟,休眠下高频时钟会停止,所以收会停止
2.开启休眠,串口打印输出不完整,总是后面丢几个字节或者乱码
由于芯片存在硬件fifo和发送缓冲区,在进入睡眠时候fifo的数据或者发送缓冲区数据可能还没发完,
我们可以等待每个串口的 R8_UARTx_LSR寄存器 的 RB_LSR_TX_ALL_EMP位有效后才标志真的发送完成
支持的波特率
波特率计算与误差