UART通信协议
- 波特率:通常为9600,19200,115200等选项,单位为bit/s,bit(比特)是表示信息的最小单位。一个0或者1就是1bit。所以9600bit/s,就是一秒的时间内可以传输9600bit的数据。
- 起始位:在发送的时候,先发出一个逻辑”0”的信号,告诉对方,我要开始传输数据了。在接收的时候,就是正常情况下高电平,然后检测到低电平0,就代表对方发来了信息,我要开始接收了。
- 数据位:一般是8位,8位为一个字节,这是真正要传输的信息。
- 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。就是数1的位数,如果设置了偶校验,那1的数量就必须是偶数,不是的话代表数据传输得不对。这个位可有可无。
- 结束位:同起始位同理。
- 帧:起始位+数据位+(校验位)+结束位,就是一帧数据。
- 帧间隔:即传送数据的帧与帧之间的间隔大小。
此次试验使用的波特率为9600bps,传输1bit的时间约等于0.104ms。数据由1bit起始位+8bit数据位+1bit停止位共10bit的数据帧组成,起始位为低电平,停止位为高电平。IO口默认拉高,数据传输由低位到高位。
说明
每个芯片有不同的库函数,所以这个例程只是给大家一个参考,给大家一个思路,大家可以根据自己现有的芯片库函数编写对应的IO串口函数。
串口通讯初始化
#define SU_TRUE 1
#define SU_FALSE 0
void softuart_init( void )
{
flag_rx_ready = SU_FALSE; //标志位置0
flag_rx_off = SU_FALSE;
io_init(); //IO管脚初始化
tim_init(tim6);
set_tim_cb(tim6, softuart_tim_cb);//设置定时器中断函数
}
一开始先设置softuart()初始化,将所有标志位都置0。先进行IO口的初始化,然后对定时器进行初始化。初始化TIM6基本定时器,将softuart_tim_cb()这个函数设置为tim6定时器的中断函数。
定时器初始化
void tim_init(tim_id_t id)
{
if(id == tim6)
{
uint32_t uwPrescalerValue = (uint32_t) ((SystemCoreClock / 1000000) - 1);
__HAL_RCC_TIM6_CLK_ENABLE();
tim6_handle.Instance = TIM6;
tim6_handle.Init.Period = 103;
tim6_handle.Init.Prescaler = uwPrescalerValue;
tim6_handle.Init.ClockDivision = 0;
tim6_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_NVIC_SetPriority(TIM6_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
HAL_TIM_Base_Init(&tim6_handle);
HAL_TIM_Base_Start_IT(&tim6_handle);
HAL_TIM_Base_Stop_IT(&tim6_handle);
}
}
IO口初始化
static void io_init(void)
{
GpioInit(&rx, SORFUART_RX, PIN_INPUT, PIN_PUSH_PULL, PIN_PULL_UP, 1);
GpioSetInterrupt(&rx, IRQ_FALLING_EDGE, IRQ_LOW_PRIORITY, rx_irqhandler);
}
初始化IO口,将rx引脚设置为输入引脚,然后设置rx中断,为下降沿中断,且中断调用函数为rx_irqhandler函数。
下降沿中断函数
static void rx_irqhandler(void)
{
if(flag_rx_ready == SU_FALSE)
{
// wait some time so that measure the middle part of signal
delay();
flag_rx_ready = SU_TRUE;
start_rx(); //开启定时器中断读取数据
}
}
#define start_rx() (HAL_TIM_Base_Start_IT(&tim6_handle))
//开启读取定时器中断
- Rx引脚检测到下降沿,便会引起IO口中断,然后调用中断rx_irqhandler()。这个函数先判断flag_rx_ready,这是代表rx引脚是否准备好的标志。如果标志位为1,说明引脚正在准备接受数据,所以就不用理会这次的下降沿信号,先让他处理好它手头的事情。如果标志位为0,说明引脚正在空闲状态,什么事都没。先等待一会,等数据位到来。然后把标志位置1,这样就不会有下一次中断干扰到数据的接收。然后开启定时器中断,读取数据。
- Start_rx()函数是一个宏定义,定义为HAL库的一个函数,就是开启定时器中断的意思。
- softuart_tim_cb()函数是定时器中断函数,在开始的初始化函数里面设置好的。
最终得到一个容量为128字节大小的inbuf数组,数据都存储在里面。
定时器中断函数
void softuart_tim_cb(void)
{
static unsigned char flag_rx_waiting_for_stop_bit = SU_FALSE; //读取结束标志位
static unsigned char rx_mask = 1;
static unsigned char rx_bit_cnt = 0;
static unsigned char internal_rx_buffer = 0;
unsigned char flag_in;
// Receiver Section
if ( flag_rx_off == SU_FALSE ) //【关闭rx】为0,就是rx正在启动状态
{
if ( flag_rx_waiting_for_stop_bit ) //rx在等待停止位当中
{
inbuf[qin] = internal_rx_buffer; //将一个字节的数据存入inbuf数组
internal_rx_buffer = 0; //清0,等待下一个字节的数据读入
if(softuart_notify != NULL)
{
softuart_notify(UART_NOTIFY_RX);
}
stop_rx(); //停止rx
if ( ++qin >= SOFTUART_IN_BUF_SIZE ) //读取数据缓冲区为128,所以数据缓冲区一共有128字节
{
// overflow - reset inbuf-index
qin = 0;
}
flag_rx_ready = SU_FALSE; //【开启rx】置0
flag_rx_waiting_for_stop_bit = SU_FALSE;//数据存储完成,不再等待停止位
}
else
{ //、【等待停止位】为0,就是还没有到等待停止位的时候
if ( flag_rx_ready == SU_TRUE ) //rx正在启动状态
{
flag_in = get_rx_pin_status(); //读取输入管脚状态
if ( flag_in ) //如果输入为1
{
internal_rx_buffer |= rx_mask; //读入数据1
}
rx_mask <<= 1;
if ( ++rx_bit_cnt == RX_NUM_OF_BITS ) //如果读取的数据达到8个
{
flag_rx_waiting_for_stop_bit = SU_TRUE; //数据位读完了,等待停止位
rx_bit_cnt = 0; //重新计数字节
rx_mask = 1;
}
}
}
}
}
从输入缓冲区inbuf【128】读取一个字节数据
uint8_t softuart_getchar( uint8_t *data )
{
if(qout != qin)
{
*data = inbuf[qout];
if ( ++qout >= SOFTUART_IN_BUF_SIZE )
{
qout = 0;
}
return 0;
}
return 1; //如果qout=qin,说明此时qin正在输入,返回1代表busy
}