【应用笔记】TAE32F5300 IO 模拟串口应用笔记
此应用笔记详细描述了 TAE32F5300 芯片 IO 模拟串口的详细过程和方法,使用外部中断对 RXD 的下降沿进行触发,使用 TMR4 按照 9600 波特率进行定时数据接收。
Keil 5,TAE32F5300 开发板,串口线,串口调试软件。
IO 模拟串口采用的是外部中断+定时器的组合来实现的,将接收引脚(PA15)配置为外部中断引脚,下降沿触发。配置为外部中断主要作用是用来检测是否有数据发送过来,当有数据过来时,首先接收的是起始位,此时产生中断,然后在中断中使能定时器,这时再使能外部中断,此时外部中断不再参与接收,数据流接收全部交给定时器来处理。定时器分频设置为 1Mhz,重载值初始设置为波特率对应时间的一半(例如 9600 波特率,一比特需要 104us,那么定时器重装载值设置为 52us),定时器开始工作后,触发第一个更新中断(52us),此时读取 PA15 引脚电平,判断是否是起始位,起始位判断完成后立马将定时器的重装载值更改为 104us,然后每隔 104us 对 RXD 引脚电平进行采样,采样 8 次,然后检测停止位是否到来,停止位检测完成后,表示一个字节数据接收完成,此时进行空闲检测,检测方法是连续 3 次采样都是高电平,说明信号线处在空闲状态了,此时可以认为一帧数据接收完毕,可以置位标志位通知主程序进行数据处理。
由于 UART 的通信方式是由 1 个起始位,8 个数据位,一个奇偶校验位,和结束位构成,因此我们可以通过 5300 两个普通 IO 口的电平变化对相应时序进行模拟,串口帧结构如下图 3.1 所示:
本次的传输过程中选用的是波特率速率为 9600,也就是 1s 中发送 9600 个字节,因此对每个字节数据进行计算 1000000us/9600 可以得出,发一个字节的数据需要进行大概 104.16us,并且对于相应电平的持续时间要求误差不能超过±5%,因此我们对时间的控制要求就显得比较重要,需要自己做一个 us 级的定时函数用来对时间进行控制。以下是对 IO 模拟串口相关变量的初始化配置,具体见代码清单 4.1。
// 1/9600 = 104us
#define IOUART_BAUDRATE 9600 //用户波特率配置
#define DATA_BIT 8 //用户数据位配置
#define RECV_LEN 8 //用户接收数据 buf 长度配置
#define IO_UART_SENDDELAY_TIME (1000000 / IOUART_BAUDRATE)
#define IOUART_TX_PORT GPIOA
#define IOUART_RX_PORT GPIOA
#define IOUART_TX_PIN GPIO_PIN_12 //TX 引脚
#define IOUART_RX_PIN GPIO_PIN_15 //RX 引脚
#define IO_HIGH 1
#define IO_LOW 0
#define TMR_MAX_PER 65535
enum{
COM_START_BIT,
COM_D0_BIT,
COM_D1_BIT,
COM_D2_BIT,
COM_D3_BIT,
COM_D4_BIT,
COM_D5_BIT,
COM_D6_BIT,
COM_D7_BIT,
COM_STOP_BIT,
};
extern uint8_t iouart_rx_buf[RECV_LEN];
void iouart_SendByte(uint8_t byte);
void iouart_SendBuf(uint8_t *data, uint16_t size);
void iouart_init(void);
IO 模拟串口初始化:PA12(TX)配置为输出模式,PA15(RX)配置为外部中断。使能 TMR 和 GPIOA 中断,具体配置参考代码清单 4.2。
void iouart_init(void)
{
GPIO_InitTypeDef iouart_init;
iouart_init.Mode = GPIO_MODE_OUTPUT;
iouart_init.OType = GPIO_OTYPE_PP;
iouart_init.Pin = IOUART_TX_PIN;
iouart_init.Pull = GPIO_NOPULL;
iouart_init.Speed = GPIO_SPEED_FREQ_HIGH;
LL_GPIO_Init(IOUART_TX_PORT, &iouart_init);
iouart_init.Pin = IOUART_RX_PIN;
iouart_init.Mode = GPIO_MODE_IT_FALLING;
iouart_init.Pull = GPIO_PULLUP;
LL_GPIO_Init(IOUART_RX_PORT, &iouart_init);
LL_NVIC_EnableIRQ(GPIOA_IRQn);
LL_NVIC_EnableIRQ (TMR4_IRQn);
tmr3_init_app();
tmr4_init_app();
}
对 TMR3 进行初始化,设置 us 级定时模拟串口延时。具体配置参考代码清单 4.3。
//模拟串口延时
void tmr3_init_app(void)
{
TMR_InitTypeDef TMR_Init;
TMR_Init.TBInit.ClockSource = TMR_CLKSOURCE_INTERNAL;
TMR_Init.TBInit.ContinuousMode = TMR_CONTINUOUS_MODE_ENABLE;
TMR_Init.TBInit.StartValue = 0;
TMR_Init.TBInit.EndValue = TMR_MAX_PER - 1;
TMR_Init.TBInit.AutoReloadPreload = TMR_AUTORELOAD_PRELOAD_ENABLE;
TMR_Init.TBInit.UpdateEnable = TMR_UPDATE_ENABLE;
TMR_Init.TBInit.UpdateSource = TMR_UPDATE_SOURCE_COUNTER;
TMR_Init.TBInit.Prescaler = 90 - 1 ;
TMR_Init.ExtInit.ExtEnable = DISABLE;
TMR_Init.ExtInit.ExtTRGOTrigger = TMR_EXT_TRGO_TRIGGER_DISABLE;
TMR_Init.ExtInit.ExtPWMWave = TMR_EXT_PWM_WAVE_DISABLE;
TMR_Init.ExtInit.ExtCCTrigger = TMR_EXT_CC_TRIGGER_DISABLE;
LL_TMR_Init(TMR3, &TMR_Init);
LL_TMR_Start(TMR3);
}
TMR4 定时器初始化:对 TMR4 进行初始化操作使得定时器可以检测到各个位的电平持续时间,从而对接收到的数据进行分析。计时结束后进入中断 TMR 处理接收到的数据。具体配置参考代码清单 4.4。
void tmr4_init_app(void)
{
TMR_InitTypeDef TMR_Init;
TMR_Init.TBInit.ClockSource = TMR_CLKSOURCE_INTERNAL;
TMR_Init.TBInit.ContinuousMode = TMR_CONTINUOUS_MODE_ENABLE;
TMR_Init.TBInit.StartValue = 0;
TMR_Init.TBInit.EndValue= IO_UART_SENDDELAY_TIME;
TMR_Init.TBInit.AutoReloadPreload = TMR_AUTORELOAD_PRELOAD_ENABLE;
TMR_Init.TBInit.UpdateEnable = TMR_UPDATE_ENABLE;
TMR_Init.TBInit.UpdateSource = TMR_UPDATE_SOURCE_COUNTER;
TMR_Init.TBInit.Prescaler = 90 - 1 ;
TMR_Init.ExtInit.ExtEnable = DISABLE;
TMR_Init.ExtInit.ExtTRGOTrigger = TMR_EXT_TRGO_TRIGGER_DISABLE;
TMR_Init.ExtInit.ExtPWMWave = TMR_EXT_PWM_WAVE_DISABLE;
TMR_Init.ExtInit.ExtCCTrigger = TMR_EXT_CC_TRIGGER_DISABLE;
LL_TMR_Init(TMR4, &TMR_Init);
__LL_TMR_IT_ENABLE(TMR4, TMR_IT_UIE);
}
4.2 中断处理
GPIO 外部中断处理:当检测到下降沿时,将当前状态置为开始位,延时 52us 后开启 TMR4 计时,具体配置参考代码清单 4.5。
void LL_GPIO_ExtTrigCallback(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
if(iouart_rx() == 0)
{
if(recvState == COM_STOP_BIT){
recvState = COM_START_BIT;
iouart_delayus(50);
LL_TMR_Start(TMR4);
}
}
}
TMR4 中断处理:产生更新事件时,进入此中断,将当前状态位移动到第一位数据,依此循坏采集 RXD 各个电平,当状态位为 COM_STOP_BIT 时,停止 TMR4,将采集到的各个数据传递给iouart_rx_buf,通过回显到串口调试助手中。具体配置参考代码清单4.6。
uint16_t iouart_rxcnt = 0;
void LL_TMR_TB_UpdateCallback(TMR_TypeDef *Instance)
{
if(Instance == TMR4){
recvState ++;
if(recvState == COM_STOP_BIT){
LL_TMR_Stop(TMR4);
iouart_rx_buf[iouart_rxcnt] = recvData;
iouart_rxcnt ++;
if(iouart_rxcnt >= RECV_LEN)
iouart_rxcnt = 0;
return;
}
if(iouart_rx()){
recvData |= (1 << (recvState - 1));
}else{
recvData &= ~(1 << (recvState - 1));
}
}
}
4.3 用户接口
发送字节函数:由于发送的信号是将 TXD 信号进行拉低处理,因此在拉低 TXD 相应的 IO 口之后进行延时处理,再进行循环,对需要发送的各个位的数据继续进行发送循环,发送完成之后将电平拉高代表停止位。具体配置参考代码清单 4.7。
void iouart_tx(uint8_t option)
{
if(option == 1)
{
LL_GPIO_WritePin(IOUART_TX_PORT, IOUART_TX_PIN, GPIO_PIN_SET);
}else
{
LL_GPIO_WritePin(IOUART_TX_PORT, IOUART_TX_PIN, GPIO_PIN_RESET);
}
}
void iouart_SendByte(uint8_t datatoSend)
{
uint8_t i, temp; //起始位
iouart_tx(0); //将 TX 引脚拉低
iouart_delayus(IO_UART_SENDDELAY_TIME);
for(i = 0; i < DATA_BIT; i++){
temp = (datatoSend >> i) & 0x01;
if(temp == 0){
iouart_tx(IO_LOW);
iouart_delayus(IO_UART_SENDDELAY_TIME);
}
else{
iouart_tx(IO_HIGH);
iouart_delayus(IO_UART_SENDDELAY_TIME);
}
}
//结束位
iouart_tx(1);
iouart_delayus(IO_UART_SENDDELAY_TIME);
}
发送 buf 函数:其中的*data 为需要发送的数据,size 为数据长度,进行循环调用 iouart_SendByte()进行一个字节一个字节的数据发送。具体配置参考代码清单 4.8。
void iouart_SendBuf(uint8_t *data, uint16_t size)
{
while(size)
{
iouart_SendByte(*data++);
size--;
}
}
模拟串口发送(PA12):在主循环调用模拟串口发送函数:iouart_SendBuf(test_buf, 4);如下图 5.1 是串口调试软件接收数据的截图。
模拟串口接收(PA15):串口软件发送 58、31、33、44、55、66、77、88 字节,发送完之后 PA15 接收到数据会进入 LL_TMR_IRQHandler(TMR4)中断,如下图 5.2 是串口调试软件发送数据的截图。
串口软件发送完数据后,接收到的数据保存在 iouart_rx_buf 数值里,如下图 5.3 所示: