📄 avr中的串口模拟.c
字号:
/*
编译环境ICCAVR ,MCU:MEGA16@8MHZ
模拟串口实现的方法:
方法1、使用外部中断接收+函数发送/定时发送。这个方法适合高波特率通信。
方法2、使用定时器定时查询。适合较低的波特率通信,容易实现全双工通信。
以下是用方法2实现的,要非常注意的地方是:
1、优先级控制宏,实现串口模拟定时器中断优先级最高,
防止定时漂移,实现100%正确率。
2、定时器的精确定时,防止采样漂移。
3、使用宏方便移植
作者:busy
邮件:ZBY8020@163.COM
*/
#include<iom16v.h>
#include<macros.h>
#define U8 unsigned char
#define bool unsigned char
#define U16 unsigned int
/*++++++++++++++++++++++++++++++++++=宏定义=++++++++++++++++++++++++++++++++++++*/
/* 宏中断优先级的使用:
1、要保存你所使用到的所有中断的使能位,然后关闭它们,仅开模拟串口定时器中断。
恢复时,只需恢复所有中断的使能位即可。
2、进入其它除了模拟串口定时器中断外,先调用 IRQ_IP_OPEN() ;然后
是你在该中断处理的代码,退出该中断是调用 IRQ_IP_CLOSE() ;然后退出该中断。
*/
// 中断优先级控制中要保持的变量
volatile U8 saveUCSRB; // 串口中断
volatile U8 saveGICR; // 外部中断
volatile U8 saveTIMSK; // 定时器中断
// 中断优先级开启宏定义
/* 在这儿屏蔽其它非高优先级中断 */
#define IRQ_IP_OPEN() do { \
saveGICR = GICR; \
saveTIMSK = TIMSK;\
saveUCSRB = UCSRB;\
GICR = 0x00; \ // 屏蔽INT0.2中断
TIMSK = 0x40; \ // 屏蔽TIMER0中断,只开TIMER2中断模拟串口
UCSRB &= 0x1F; \ // 屏蔽UART0中断
SEI();
} while (0)
// 中断优先级恢复宏定义
#define IRQ_IP_CLOSE() do { \
CLI(); \ // 在这儿恢复其它非高优先级中断
GICR = saveGICR; \ // 恢复INT0.2中断
TIMSK = saveTIMSK; \ // 恢复TIMER0.2中断
UCSRB = saveUCSRB; \ // 恢复UART0接收、空中断
} while (0)
// 脚定义
#define GET_VM232_RX() (PIND & (1<<PD0)) // 端口D的PD0模拟接收
#define SET_VM232_TX() (PORTD |= (1<<PD1)) // 端口D的PD1发送
#define CLR_VM232_TX() (PORTD &= ~(1<<PD1)) // 端口D的PD1发送
#define GET_VM485_RX() (PIND & (1<<PD2)) // 端口D的PD2模拟接收
#define SET_VM485_TX() (PORTD |= (1<<PD3)) // 端口D的PD3发送
#define CLR_VM485_TX() (PORTD &= ~(1<<PD3)) // 端口D的PD3发送
/*++++++++++++++++++++++=系统52US定时器,用于模拟2个串口=++++++++++++++++++++++++*/
// 模拟串口的状态码
enum {
START,
SDATA,
STOP
};
U8 inRS232 = 0; // 模拟232的数据接收输入指针
U8 outRS232 = 0; // 模拟232的数据接收取出指针
U8 vmRS232Buf[20]; // 模拟232的数据接收缓冲区
U8 inRS485 = 0; // 模拟485的数据接收输入指针
U8 outRS485 = 0; // 模拟485的数据接收取出指针
U8 vmRS485Buf[20]; // 模拟485的数据接收缓冲区
void vm_rs232_rx(U8 dataBit) // 模拟232接收函数
{
static U8 status = START;
static U8 cnt = 0;
static U8 number = 0;
static U8 rData;
switch (status)
{
case START:
if (dataBit) {
cnt = 0;
}
else {
if (++cnt > 2) {
cnt = 0;
number = 0;
status = SDATA;
}
}
break;
case SDATA:
if(++cnt > 3)
{
cnt = 0;
if(dataBit) // 数据存放在最高位,每收到一个bit,右移一位
{
rData |= 0x80;
}
else
{
rData &= 0x7F;
}
if(++number < 8)
{
rData >>= 1;
}
else
{
number = 0;
status = STOP;
}
}
break;
case STOP:
if(++cnt > 3)
{
cnt = 0;
if(dataBit)
{
vmRS232Buf[inRS232++] = rData;
if(inRS232 >= 20) // 环型缓冲区
{
inRS232 = 0;
}
// 在这模拟的接收中断,不太实用
}
status = START;
}
break;
default:
cnt = 0;
status = START;
break;
}
}
void vm_rs485_rx(U8 dataBit) // 模拟485接收函数
{
static U8 status = START;
static U8 cnt = 0;
static U8 number = 0;
static U8 rData;
switch(status)
{
case START:
if(dataBit)
{
cnt = 0;
}
else
{
if(++cnt > 2)
{
cnt = 0;
number = 0;
status = SDATA;
}
}
break;
case SDATA:
if(++cnt > 3)
{
cnt = 0;
if(dataBit)
{
rData |= 0x80;
}
else
{
rData &= 0x7F;
}
if(++number < 8)
{
rData >>= 1;
}
else
{
number = 0;
status = STOP;
}
}
break;
case STOP:
if(++cnt > 3)
{
cnt = 0;
if(dataBit)
{
vmRS485Buf[inRS485++] = rData;
if(inRS485 >= 20) // 环型缓冲区
{
inRS485 = 0;
}
// 在这模拟的接收中断,不太实用
}
status = START;
}
break;
default:
cnt = 0;
status = START;
break;
}
}
bool flgVmRs232tx = 0; // 模拟232是否有数据要发送的标志,1有数据要发送,完成清零
U8 vmRS232SBUF; // 模拟232要发送的一字节缓冲
bool flgVmRs485tx = 0; // 模拟485是否有数据要发送的标志,1有数据要发送,完成清零
U8 vmRS485SBUF; // 模拟485要发送的一字节缓冲
void vm_rs232_tx(void) // 模拟232发送函数
{
static U8 status = START;
static U8 cnt = 0;
static U8 number = 0;
if(flgVmRs232tx)
{
switch(status)
{
case START:
CLR_VM232_TX();
if(++cnt > 3)
{
cnt = 0;
number = 0;
status = SDATA;
}
break;
case SDATA:
if(vmRS232SBUF & 0x01)
{
SET_VM232_TX();
}
else
{
CLR_VM232_TX();
}
if(++cnt > 3)
{
cnt = 0;
if(++number < 8)
{
vmRS232SBUF >>= 1;
}
else
{
//number = 0;
status = STOP;
}
}
break;
case STOP:
SET_VM232_TX();
if(++cnt > 3)
{
cnt = 0;
status = START;
flgVmRs232tx = 0;
// 在这模拟的发送中断,不太实用
}
break;
default:
SET_VM232_TX();
cnt = 0;
status = START;
flgVmRs232tx = 0;
break;
}
}
}
void vm_rs485_tx(void) // 模拟485发送函数
{
static U8 status = START;
static U8 cnt = 0;
static U8 number = 0;
if(flgVmRs485tx)
{
switch(status)
{
case START:
CLR_VM485_TX();
if(++cnt > 3)
{
cnt = 0;
number = 0;
status = SDATA;
}
break;
case SDATA:
if(vmRS485SBUF & 0x01)
{
SET_VM485_TX();
}
else
{
CLR_VM485_TX();
}
if(++cnt > 3)
{
cnt = 0;
if(++number < 8)
{
vmRS485SBUF >>= 1;
}
else
{
//number = 0;
status = STOP;
}
}
break;
case STOP:
SET_VM485_TX();
if(++cnt > 3)
{
cnt = 0;
status = START;
flgVmRs485tx = 0;
// 在这模拟的发送中断,不太实用
}
break;
default:
SET_VM485_TX();
cnt = 0;
status = START;
flgVmRs485tx = 0;
break;
}
}
}
// 波特率都是2400BIT/S ,416US/BIT
// 5中取3 ,每104US抽样(也可以3中取2)
// 52US中断交替采样2个模拟串口
//TIMER2 initialize - prescale:8
// WGM: Normal
// desired value: 52uSec
// actual value: 52.000uSec (0.0%)
void timer2_init(void)
{
TCCR2 = 0x00; //stop
ASSR = 0x00; //set async mode
TCNT2 = 0xd3; //setup
OCR2 = 0x34;
TCCR2 = 0x02; //start
}
#pragma interrupt_handler timer2_ovf_isr:5
void timer2_ovf_isr(void) // 最高优先级中断
{
static bool sw = 0; // 模拟串口交替使用定时器开关,实现104US采样
volatile U8 vmRS232, vmRS485; // 记录2模拟串口接收脚的高低电平
TCNT2 = 0xd3; //reload counter value
// 在这采样接收脚保证不漂移
vmRS232 = GET_VM232_RX();
vmRS485 = GET_VM485_RX();
// 在这增加串口接收检测
if(sw) // 乒乓操作
{
vm_rs232_tx();
vm_rs232_rx(vmRS232);
// 在这增加串口
sw = 0;
}
else
{
vm_rs485_tx();
vm_rs485_rx(vmRS485);
// 在这增加串口
sw = 1;
}
}
/*+++++++++++++++++++++++++++++++++= MAIN =++++++++++++++++++++++++++++++++++++++*/
void port_init(void)
{
PORTA = 0xFF;
DDRA = 0x00;
PORTB = 0xFF;
DDRB = 0x00;
PORTC = 0xFF;
DDRC = 0x00;
PORTD = 0xFF;
DDRD = 0x0A; // PD1 PD3 输出
}
void init_devices(void)
{
//stop errant interrupts until set up
CLI(); //disable all interrupts
port_init();
timer2_init();
MCUCR = 0x00;
GICR = 0x00;
TIMSK = 0x40; //timer interrupt sources
SEI(); //re-enable interrupts
//all peripherals are now initialized
}
void main(void)
{
init_devices();
// 收到什么就回发什么
while(1)
{
if(outRS232 != inRS232)
{
while(flgVmRs232tx);
vmRS232SBUF = vmRS232Buf[outRS232++];
flgVmRs232tx = 1;
if(outRS232 >= 20) // 接收、发送都是环型缓冲区
{
outRS232 = 0;
}
}
if(outRS485 != inRS485)
{
while(flgVmRs485tx);
vmRS485SBUF = vmRS485Buf[outRS485++];
flgVmRs485tx = 1;
if(outRS485 >= 20) // 接收、发送都是环型缓冲区
{
outRS485 = 0;
}
}
}
}
/*++++++++++++++++++++++++++++++++++=end=++++++++++++++++++++++++++++++++++++++++*/
/*
以上是我开始做的2个模拟串口测试全部程序,大家可以按照自己的需要修改使用。
而在我的工程中我改成了模拟接收完一帧(MAX 30BYTES)数据才请求处理。发送
则是用了一个50BYTES环型缓冲区实现随意的发送数据,使用起来非常方便。
以上调试中,请大家务必设置好定时器的准确定时。因为实现US级的定时,ICCAVR
的‘应用生成’设置不准!另外,单片机的工作频率尽可能高些!!!
以上只是一个实现的思想,不足之处请大家指点,我不用3中取2,只是想模拟100%
的正确率,在这请大家调试后有什么心得也写出来让大家分享!!
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -