软件串口的实现原理
一切发送和接收的过程都是在后台完成的,具体实现需要一个带捕获&匹配功能的定时器,本实现用的是TIM1。任意具有捕获输入功能的引脚都可以用作
接收引脚,任意GPIO引脚都可以用作发送引脚。此实现用TIM_CH4作为发送引脚,TIM1_CH3作为接收引脚。
整个数据传输过程基于定时器1的溢出事件,溢出周期为发送半个bit的时间,这是因为发送和接收用的是同一个定时器。
发送环节:
当有数据字节进入发送缓存后,发送请求标志被置位,最近的一个事件更新中断用于启动此次发送传输,从产生发送请求到开始发送的最长延时为一个
溢出周期。在每个偶数的溢出中断中设置相应的发送引脚的电平。
接收环节:
空闲状态下,CH3一直处于输入捕获状态,当捕获到第一个下降沿时(起始位),此时计数器的值会自动保存到CCR3中用于之后的匹配,在捕获中断中
将通道模式改为匹配,同时禁止该引脚的捕获/匹配功能,使其成为普通的GPIO引脚,以便检测输入电平。由于半个bit周期的溢出事件存在,所以最近
的一次匹配肯定出现在起始位的中间点,此时读取引脚上的电平,以得到该位的逻辑,之后丢弃偶数次的匹配中断,在奇数次匹配中断中读取剩余的位
的值,知道接收到完整的一字节数据(包括停止位),将通道模式改为捕获,等待下一字节。
具体实现代码如
#define BAUDRATE_SWUART (1200)#define USR_OVF_ONLY (0x04)#define TIM1_CR_CEN (0x01) //定时器4计数使能#define TIM1_SR_UIF (0x01)#define TIM1_SR_CC3IF (0x08)#define UPDATE_INTER_ENABLE (0x01)#define CAP_COMP_INTER_ENABLE3 (0x08)#define CHANN3_INPUT_TI3FP3 (0x01)#define CHANN3_OUTPUT (0xFC)#define CHANN3_FUNC_MASK (0x03)#define CHANN3_CAP_COMP_ENABLE (0x01)#define CHANN3_CAP_COMP_DISB (0xFE)#define CAP_FALL_EDGE (0x02)#define START_BIT_POS (0x01) //起始位位置#ifdef HAVE_PARITY#define WORD_LENGTH (11) //1个起始位,8个数据位,1个奇偶校验位,1个停止位#define SET_RCV_BIT (0x400)#define STOP_BIT_POS (0x200) //停止位位置#define CHECK_BIT_POS (0x100)#else#define WORD_LENGTH (10)#define SET_RCV_BIT (0x200)#define STOP_BIT_POS (0x100)#endif#define BIT_MASK (0x01)#define EVEN_CHECK (0)/*********************************************************************************************************** GLOBAL VARIABLES**********************************************************************************************************/bool IsFirstBit;bool TxByteReady;bool IsOdd;uint8_t TxBitCnt; //发送位数计数uint8_t RxBitCnt;uint16_t TxByte; //移位发送缓存uint16_t RxByte;/*********************************************************************************************************** Description: Swuart初始化** Arguments : none** Returns : none** Notes : 1200**********************************************************************************************************/void InitSwuart (void){ TIM1->ARRH = (F_MASTER / (BAUDRATE_SWUART * 2)) >> 8; TIM1->ARRL = (uint8_t)(F_MASTER / (BAUDRATE_SWUART * 2)); TIM1->CR1 = USR_OVF_ONLY; //仅计数器溢出才产生更新事件 TIM1->CCMR3 = CHANN3_INPUT_TI3FP3; TIM1->CCER2 = CHANN3_CAP_COMP_ENABLE | CAP_FALL_EDGE; TIM1->IER = UPDATE_INTER_ENABLE |CAP_COMP_INTER_ENABLE3; TIM1->CR1 = TIM1_CR_CEN; SwuartTxByte(0xFF); //初始化后第一个发送的第一个字节要丢弃}/*********************************************************************************************************** Description: 发送一个字节** Arguments : byte: 待发送的字节** Returns : none** Notes : none**********************************************************************************************************/void SwuartTxByte (uint8_t byte){ TxByte = byte; TxBitCnt = WORD_LENGTH; TxByte |= STOP_BIT_POS; //添加停止位 #ifdef HAVE_PARITY if (GetParity(byte, EVEN_CHECK)){ TxByte |= CHECK_BIT_POS; } #endif TxByte = TxByte << START_BIT_POS; //添加起始位 TxByteReady = true; IsFirstBit = true;}/*********************************************************************************************************** Description: 发送数据** Arguments : data: 指向待发送数据的指针* len: 数据长度** Returns : none** Notes : none**********************************************************************************************************/void SwuartSend (const uint8_t *data , uint8_t len){ uint8_t i; static uint8_t SwuartSendBuf[50]; for (i = 0; i < len; i++){ SwuartSendBuf[i] = data[i]; } i = 0; while (len--){ SwuartTxByte(SwuartSendBuf[i++]); while (TxByteReady){ ; } }}/*********************************************************************************************************** Description: 奇偶校验** Arguments : byte: 待校验的字节* parity: 校验类型选择** Returns : none** Notes : 校验位的值**********************************************************************************************************/#ifdef HAVE_PARITYuint8_t GetParity (uint8_t byte, uint8_t parity){ uint8_t i; uint8_t n; uint16_t data; data = byte; n = 0; if (data){ do{ i = data & 0x01; if ( i > 0 ){ n++; } data = data >> 1; } while ( data > 0 ) ; } n = n & 0x01; if (parity){ //奇校验 if(n){ return 0; } else { return 1; } } else { //偶校验 if(n){ return 1; } else { return 0; } }}#endif#pragma vector = TIM1_OVF_IRQn__interrupt void Uart_Tx_Timing (void){ static uint8_t OvfInterCnt; TIM1->SR1 &= ~TIM1_SR_UIF; if (!TxByteReady){ return; } if (IsFirstBit){ OvfInterCnt = 0; IsFirstBit = false; } if (!(OvfInterCnt % 2) && TxBitCnt){ //丢弃奇数此溢出中断,因为发送1位的时间为两个溢出周期 GPIOC->ODR.ODR4 = TxByte & BIT_MASK; TxByte = TxByte >> 1; TxBitCnt--; } OvfInterCnt++; if (!TxBitCnt && !(OvfInterCnt % 2)){ //一个字节发送完毕,保证一个完整的停止位 TxByteReady = false; }}#pragma vector = TIM1_CAP_COMP_IRQn__interrupt void Uart_Rx_Timing (void){ uint8_t tmp; static uint8_t CapCompCnt; TIM1->SR1 &= ~TIM1_SR_CC3IF; if ((TIM1->CCMR3 && CHANN3_FUNC_MASK) == CHANN3_INPUT_TI3FP3){ TIM1->CCER2 &= CHANN3_CAP_COMP_DISB; //此处顺序不能颠倒,要先禁止,后修改通道模式 TIM1->CCMR3 &= CHANN3_OUTPUT; CapCompCnt = 1; RxBitCnt = WORD_LENGTH; RxByte = 0; return; } if ((CapCompCnt % 2) && (RxBitCnt > 1)){ //在奇数次中断中读取数据 if (GPIOC->IDR.IDR3 && (CapCompCnt == 1)){ //软件滤波 TIM1->CCMR3 &= ~CHANN3_FUNC_MASK; TIM1->CCMR3 |= CHANN3_INPUT_TI3FP3; TIM1->CCER2 |= CHANN3_CAP_COMP_ENABLE; return; } #ifdef HAVE_PARITY if (RxBitCnt == 2){ //奇偶校验 if (GPIOC->IDR.IDR3 != GetParity((uint8_t)(RxByte >> 1), EVEN_CHECK)){ TIM1->CCMR3 &= ~CHANN3_FUNC_MASK; TIM1->CCMR3 |= CHANN3_INPUT_TI3FP3; TIM1->CCER2 |= CHANN3_CAP_COMP_ENABLE; return; } } #endif if (GPIOC->IDR.IDR3){ RxByte |= SET_RCV_BIT; } RxBitCnt--; RxByte = RxByte >> 1; } CapCompCnt++; if ((RxBitCnt == 1) && (CapCompCnt % 2)){ //检测到停止位后,重新切换到捕获模式 TIM1->CCMR3 &= ~CHANN3_FUNC_MASK; TIM1->CCMR3 |= CHANN3_INPUT_TI3FP3; TIM1->CCER2 |= CHANN3_CAP_COMP_ENABLE; if (GPIOC->IDR.IDR3){ if (!QueueAdd(&queue_uart2, (uint8_t)(RxByte >> 1))){ tmp = (uint8_t)(RxByte >> 1); //队列满则丢弃该字节 tmp = tmp; //避免编译器警告 } } }}