任意GPIO用TIM+DMA+EXTI模拟串口
原理
先从一个最简单的需求开始,用任意一个GPIO来模拟串口的TXD引脚,发送数据,数据格式选择8N1,波特率9600。
串口数据帧 串口的数据帧格式如图所示:
要用GPIO来模拟TXD引脚,就是按照数据帧的格式要求,按照指定的波特率改变引脚电平。 比如以9600波特率,发送数据0x55,就是要在TXD引脚上产生如下波形。
GPIO的BSRR寄存器
改变引脚电平,通过GPIO的BSRR寄存器可以方便的对实现。 比如:
GPIOD->BSRR = (GPIO_PIN_5) << 16; // PD5引脚置低 GPIOD->BSRR = GPIO_PIN5; // PD5引脚置高
按照指定的波特率改变引脚电平
定时中断的方式
9600的波特率,就需要以1/9600的频率改变引脚电平。用定时中断来完成的示意代码如下:
typedef enum { TX_STATE_IDLE, TX_STATE_PREP, TX_STATE_START, TX_STATE_DATA, TX_STATE_STOP, }enum_tx_state_t;
uint8_t tx_data; // 要发送的数据 void putchar(uint8_t x) { tx_data = x; enable_timer_isr(); } void timer_isr(void) { static enum_tx_state_t tx_state = TX_STATE_IDLE; static uint8_t tx_count = 0; static uint8_t tx_data;
switch(tx_state) { case TX_STATE_IDLE: tx_count = 0; tx_state = TX_STATE_START; break; case TX_STATE_START: TX_PIN_LOW(); tx_data = get_send_data(); break; case TX_STATE_DATA: if(tx_data & (1<<tx_count)){ TX_PIN_HIGH(); }else{ TX_PIN_LOW(); } if(++tx_count > 8){ tx_state = TX_STATE_STOP; } break; case TX_STATE_STOP: default: TX_PIN_HIGH(); tx_state = TX_STATE_IDLE; disable_timer_isr(); break; }
}
采用中断来模拟的缺点在于,如果要以较高的波特率来传输数据时,timer_isr中断频率太高,9600bps就要求0.1ms中断频率了,如果波特率再提高的话,对系统的造成非常高的负载。
采用TIM+DMA的方式 为了降低系统的负载,可以采用的一个方法是,准备一个TxBuffer,使用定时器触发DMA,由DMA将TxBuffer的数据传输到GPIO的BSRR寄存器。 比如,传输0x55这个数据,采用8N1的格式,就可以准备这样一个TxBuffer(假设使用的PD3引脚)
uint32_t TxBuffer[10] = { 0x00080000, // 起始位, PD5 = 0 0x00080000, // D0: PD5 = 0 0x00000008, // D1: PD5 = 1 0x00080000, // D2: PD5 = 0 0x00000008, // D3: PD5 = 1 0x00080000, // D3: PD5 = 0 0x00000008, // D3: PD5 = 1 0x00080000, // D3: PD5 = 0 0x00000008, // D3: PD5 = 1 0x00000008, // 停止位:PD5 = 1 };
传输不同的数据,就是在TxBuffer当中,准备不同的数据。 采用DMA的方式后,只需要在整个TxBuffer发送完成后,再响应一次DMA完成中断,这就可以使得中断的频率降低10倍,大大减轻系统的负载。
|