zhe最近在搞一个IO口模拟LIN的代码,其中包括IO口模拟UART的部分,就记录一下,希望对像我们这样的初学者能有所帮助。
1. 串口协议
串口的特点:全双工,串行,异步
串口协议(我这里选用最常见的一种):一个起始位,八个字节长度,无奇偶校验,一个停止位,一帧包括十个比特。图的话就不上传了,这个大家应该都很清楚。
2. 如何模拟
2.1 模拟发送
发送倒是很简单,直接按协议发就完事,只需要注意波特率的设置,我这里选用9600,每个比特传输时间为104us,这个怎么算其实很简单,用1/9600转换成us就行了,我这里发的话就没有用到定时器,直接使用滴答计时器延时函数。代码如下:
void uart1_putchar(uint8_t val)
{
uint8_t i = 0;
UART_TX_LOW() ;//拉低发送引脚,起始位
delay_us((1000000*1)/LIN_BPS);
for(;i<8;i++){
if(val&0x01){
UART_TX_HIGH();
delay_us((1000000*1)/LIN_BPS);
}else{
UART_TX_LOW();
delay_us((1000000*1)/LIN_BPS);
}
val>>=1;
}
UART_TX_HIGH();//拉高发送引脚,停止位
delay_us((1000000*1)/LIN_BPS);
}
2.2 模拟接收
接收的话稍微复杂一点,需要用到外部中断和定时器中断,我们把定时器周期调为52us,外部中断为下降沿中断,逻辑为:外部触发下降沿中断,中断函数中如果读取引脚电平为0且为起始位的话,启动定时器中断,关闭外部中断,在第一次进定时器中断时,多次读取对应电平,如果均为低电平,则表示起始位正确,然后偶数次进中断不做处理,奇数次进中断多次读取电平值,取其多数值,最后index为19时表明一个字节接收完毕,启动外部中断,关闭定时器中断。代码如下:
void PORTE_IRQHandler(void)//外部中断
{
if((PTE->PDIR & (1<<16))==0x00)
{
delay_us(10);
if(UART_RX_Value() == 0){
if(RBtIdx == 0){
INT_SYS_DisableIRQ(PORTE_IRQn);//使能外部中断
LPIT0->TMR[1].TCTRL = 0x00000001;//使能定时器中断
}
}
}
PORTE->ISFR |= 0x4000;//清楚中断标志
}
void LPIT0_Ch1_IRQHandler (void){
LPIT0->MSR |= LPIT_MSR_TIF1_MASK;//清楚中断问题
RBtIdx++;
if(RBtIdx == 1){
if(FourTimesDetectStart() == 0){
RBtIdx = 0;
return ;
}
}else if(RBtIdx == 19)
{
LPIT0->TMR[1].TCTRL = 0x00000000;
INT_SYS_EnableIRQ(PORTE_IRQn);
RBtIdx = 0;
}
else if(RBtIdx % 2)
{
RxData>>=1;
if(FourTimesDetectData())
{
RxData|=0x80;
}
}
}
3 附加引脚初始化,定时器初始化,和延时函数代码初始化
我使用的IO引脚是PB4(out)和PD16(in)
PCC->PCCn[PCC_PORTB_INDEX] |= PCC_PCCn_CGC_MASK;
PTB->PDDR |= 1<<4;
PORTB->PCR[4] = 0x00000100;
PTB-> PSOR |= 1<<4;
PCC->PCCn[PCC_PORTE_INDEX] |= PCC_PCCn_CGC_MASK;
PTE->PDDR &= ~(1<<16);
PORTE->PCR[16] = PORT_PCR_MUX(1)|PORT_PCR_IRQC(0x0a)|PORT_PCR_PS(1)|PORT_PCR_PE(1);//上拉,下降沿中断
S32_NVIC_EnableIRQ(PORTE_IRQn,9);
定时器使用的是IT0的第一个通道
PCC->PCCn[PCC_LPIT_INDEX] = PCC_PCCn_PCS(6);
PCC->PCCn[PCC_LPIT_INDEX] |= PCC_PCCn_CGC_MASK; /* Enable clk to LPIT0 regs */
LPIT0->MCR = 0x00000001;
LPIT0->MIER = 0x00000001;
LPIT0->TMR[1].TVAL = 2080; /* Chan 0 Timeout period: 0.04M clocks */ //52us
延时函数初始化
void s32_systick_init(void){
S32_SysTick->CSR = S32_SysTick_CSR_ENABLE(0);
fac_us = 80000000 / 1000000;
S32_SysTick->RVR = 0xFFFFFF; //重装载寄存器
S32_SysTick->CVR = 0; //当前计数
S32_SysTick->CSR = 0; //控制寄存器
S32_SysTick->CSR |= S32_SysTick_CSR_TICKINT(1u)|S32_SysTick_CSR_ENABLE(1)|S32_SysTick_CSR_CLKSOURCE(1u);
}
void delay_us(uint32t nus){
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=S32_SysTick->RVR; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told= S32_SysTick->CVR ; //刚进入时的计数器值
while(1)
{
tnow= S32_SysTick->CVR ;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
4.总结
IO口模拟UART的收发按照UART的时序来即可,虽然看起来很简单,但是动手的时候还是会有一些问题的,踏踏实实做好每一步,不要眼高手低。