1. 串口通讯协议
通用异步收发器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种串行、异步、全双工的通信协议,在嵌入式领域应用的非常广泛。
数据通讯格式:
空闲位:
UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平,表示当前线路上没有数据传输。
起始位:
每开始一次通信时发送方先发出一个逻辑”0”的信号(低电平),表示传输字符的开始。因为总线空闲时为高电平所以开始一次通信时先发送一个明显区别于空闲状态的信号即低电平。
数据位:
起始位之后就是我们所要传输的数据,数据位可以是5、6、7、8,9位等,构成一个字符(一般都是8位)。如ASCII码(7位),扩展BCD码(8位)。先发送最低位,最后发送最高位,使用低电平表示‘0’高电平表示‘1’完成数据位的传输。
奇偶校验位:
数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。校验位其实是调整个数,串口校验分几种方式:
1、无校验(no parity)。
2、奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”,如果“1”的数目是奇数,校验位为“0”。
3、偶校验(even parity):如果数据为中“1”的数目是偶数,则校验位为“0”,如果为奇数,校验位为“1”。
4、mark parity:校验位始终为1(不常用)。
5、parity:校验位始终为0(不常用)。
停止位:
它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢。
波特率:
数据传输速率使用波特率来表示。单位bps(bits per second),常见的波特率9600bps、115200bps等等,其他标准的波特率是1200,2400,4800,19200,38400,57600。举个例子,如果串口波特率设置为9600bps,那么传输一个比特需要的时间是1/9600≈104.2us。
2. GPIO模拟UART(9600bps)
2.1. GPIO模拟发送
a. 阻塞式发送:
利用delay()函数进行数据模拟发送(delay 104us):
优点:程序编写简单,适合单纯只有数据发送的情况
缺点:阻塞式发送,CPU占用高,发送时候无法进行其他操作
void gpio_uart_send(unsigned char *data, unsigned char data_len)
{
unsigned char i = 0, j = 0;
unsigned char start_flag = 0;
for (i; i < data_len; i++)
{
if (0 == start_flag)
{
PORTA &= ~(0x01 << gpio_cfg_data.tx_pin);
sys_delay_us(gpio_uart_delay_time);
start_flag = 1;
}
for (j = 0; j < gpio_cfg_data.bits; j++)
{
if ((data[i] >> j) & 0x01)
{
PORTA |= (0x01 << gpio_cfg_data.tx_pin);
}
else
{
PORTA &= ~(0x01 << gpio_cfg_data.tx_pin);
}
sys_delay_us(gpio_uart_delay_time);
}
PORTA |= (0x01 << gpio_cfg_data.tx_pin);
sys_delay_us(gpio_uart_delay_time);
start_flag = 0;
}
}
b. 使用硬件定时器方式发送(推荐使用该方式)
优点:发送过程中不阻塞,可以执行其它任务
缺点:占用一个us级别硬件定时器资源,程序编写有一定逻辑文章来源:https://www.toymoban.com/news/detail-793843.html
- 初始化一个104us的硬件定时器(以下使用一个51内核单片机为例)
static void __timer0_init(void)
{
TMR0 = 52; // 时间计算 1/16000000 * 2 * (255-52) * 4 ≈ 104us(16M主频,2T指令周期,255溢出时间,时钟4分频)
T0IF = 0; //清空T0软件中断
// T0IE = 1; //开定时器/计数器0中断
}
- 配置GPIO为上拉输出
// 将TX引脚配置成输出模式,并上拉
PORTA |= (1 << cfg->tx_pin);
TRISA &= (1 << cfg->tx_pin);
WPUA |= (1 << cfg->tx_pin);
- 定时器中断中去发送数据
void gpio_uart_send_bit(void)
{
static unsigned char data_len = 0, bit_cnt = 0;
static unsigned char bit_start_flag = 1;
if (1 == bit_start_flag) {
PORTA &= ~(0x01 << gpio_cfg_data.tx_pin);
bit_start_flag = 0;
} else if (bit_cnt < 8) {
if ((send_data.data[data_len] >> bit_cnt) & 0x01) {
PORTA |= (0x01 << gpio_cfg_data.tx_pin);
} else {
PORTA &= ~(0x01 << gpio_cfg_data.tx_pin);
}
bit_cnt++;
} else {
bit_start_flag = 1;
bit_cnt = 0;
PORTA |= (0x01 << gpio_cfg_data.tx_pin);
data_len++;
}
if (data_len == send_data.data_len) {
T0IE = 0; //发送完毕,关闭定时器T0
data_len = 0;
send_data_cache.finish_flag = 1;
}
}
- 发送api函数
void gpio_uart_send(unsigned char *data, unsigned char data_len)
{
if (data_len > 5) {
data_len = 5;
}
if (1 == send_data.finish_flag) {
memcpy(&send_data.data[0], data, data_len);
send_data.data_len = data_len;
send_data.finish_flag = 0;
} else { //缓存一个数据
memcpy(&send_data_cache.data[0], data, data_len);
send_data_cache.data_len = data_len;
send_data_cache.finish_flag = 0;
}
if (0 == T0IE) { // 当前未发送
__timer0_init();
T0IE = 1; //开定时器/计数器0中断
}
}
2.2. GPIO模拟接收
GPIO模拟接收也需要一个us级的硬件定时器以及一个GPIO外部中断文章来源地址https://www.toymoban.com/news/detail-793843.html
- 配置接口GPIO为上拉输入,并且开启GPIO中断
// 将RX引脚配置成输入模式,并上拉
PORTA &= (1 << cfg->rx_pin);
TRISA |= (1 << cfg->rx_pin);
WPUA |= (1 << cfg->rx_pin);
// 开启PA2中断
INTEDG = 0; //使能PA2下降沿中断
INTF = 0; // 清PA2中断标志位
INTE = 1; // 使能PA2中断
- 配置定时器52us进入一次中断
static void __timer2_init(void)
{
//T2CON = 0; //Bit[1,0]=00,T2时钟分频 1:1
//Bit[6-3]=0000,T2输出时钟分频1:1
T2CON = 0B00000001; //Bit[1,0]=01,T2时钟分频 1:4
//Bit[6-3]=0000,T2输出时钟分频1:1
//T2CON = 0X79; //Bit[1,0]=01,T2时钟分频 1:4
//Bit[6-3]=1111,T2输出时钟分频1:1 6
TMR2 = 0; //TMR2赋初值
PR2 = 104; //设置TMR2输出比较值定时52us=(1/16000000)*2*4*104(PR2)
//16M-2T-4分频
TMR2IF = 0;//清TMER2中断标志
TMR2IE = 1;//使能TMER2的中断
TMR2ON = 1;//使能TMER2启动
PEIE = 1; //使能外设中断
}
- GPIO中断处理:在接收到下降沿中断来临的时候,开启定时器中断
if (INTE && INTF) { // RA2 INT中断
INTF = 0;
RA6 = RA2;
if (RA2) { //RA2为高电平时,开启下降沿中断
INTEDG = 0;
} else { //RA2为低电平时,开启上升沿中断
INTEDG = 1;
cnt= 0;
TMR2 = 0;
PR2 = 100;
INTE = 0;//关闭IO中断
}
}
- 定时器中断处理
//中断函数里面
if (TMR2IE && TMR2IF) { //定时器52us中断
TMR2IF = 0;
TMR2 = 0;
PR2 = 200; //设置TMR2输出比较值定时104us=(1/16000000)*2*4*200(PR2)
if (cnt < 0xff) {
cnt++;
}
if ((cnt < 10) && (cnt >= 2)){
gpio_uart_get_bit(cnt-2, RA2);
} else if (10 == cnt) {
INTE = 1;
} else if (cnt >= 40) {
cnt = 0xff;
RA6 = 0;
}
}
//中断函数结束
void gpio_uart_get_bit(unsigned char data_bit_cnt, unsigned char data_bit)
{
unsigned char temp_bit = data_bit;
if (0 == recive_data.finish_flag) {
recive_data.data[recive_data.data_len] |= (temp_bit << data_bit_cnt);
}
if (7 == data_bit_cnt) {
recive_data.data_len++;
}
if (recive_data.data_len == 5) {
recive_data.finish_flag = 1;
}
}
到了这里,关于GPIO模拟UART串口发送和接收的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!