转自:http://ziye334.blog.163.com/blog/static/224306191201452833850647
前阵子,调项目时需要用到低波特率串口通讯(300的波特率),才发下发现在正常情况下(PCLK1时钟频率为72M,PCLK2时钟频率为36M):STM32的USART0的最低波特率只能设置到1200,;而USART1最低波特率只能设置到600。怎么设置STM32的600或以下的波特率呢?有两种方法:一种是改变外设时钟频率,而另一种方法就是使用IO口模拟串口通讯。今天就来讲讲,用IO口模拟串口通信!
1、串口传输协议
void VirtualCOM_ByteSend(u8 val)
{
u8 i;
IO_LOW(); //起始位,拉低电平
Delay(sometime);
for(i = 0; i < 8; i++) //8位数据位
{
if(val & 0x01)
IO_HIGH();
else
IO_LOW();
Dealy(sometime);
val >>= 1;
}
IO_HIGH(); //停止位,拉高电平
Delay(sometime);
}
#define COM_TX_PORT GPIOA
#define COM_TX_PIN GPIO_Pin_4
void VirtualCOM_TX_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* PA4最为数据输出口,模拟TX */
GPIO_InitStructure.GPIO_Pin = COM_TX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(COM_TX_PORT, &GPIO_InitStructure);
GPIO_SetBits(COM_TX_PORT, COM_TX_PIN);
}
(2)IO模拟串口发送一个字节
#define COM_TX_PORT GPIOA
#define COM_TX_PIN GPIO_Pin_4
#define COM_DATA_HIGH() GPIO_SetBits(COM_TX_PORT, COM_TX_PIN) //高电平
#define COM_DATA_LOW() GPIO_ResetBits(COM_TX_PORT, COM_TX_PIN) //低电平
u32 delayTime;
void VirtualCOM_ByteSend(u8 val)
{
u8 i = 0;
COM_DATA_LOW(); //起始位
Delay_us(delayTime);
for(i = 0; i < 8; i++) //8位数据位
{
if(val & 0x01)
COM_DATA_HIGH();
else
COM_DATA_LOW();
Delay_us(delayTime);
val >>= 1;
}
COM_DATA_HIGH(); //停止位
Delay_us(delayTime);
}
(3)IO模拟串口发送字符串
void VirtualCOM_StringSend(u8 *str)
{
while(*str != 0)
{
VirtualCOM_ByteSend(*str);
str++;
}
}
#define COM_RX_PORT GPIOA
#define COM_RX_PIN GPIO_Pin_5
void VirtualCOM_RX_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStructure;
/* PA5最为数据输入,模拟RX */
GPIO_InitStructure.GPIO_Pin = COM_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(COM_RX_PORT, &GPIO_InitStructure);
EXTI_InitStruct.EXTI_Line=EXTI_Line5;
EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿都中断
EXTI_InitStruct.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStructure.NVIC_IRQChannel=EXTI9_5_IRQn; //外部中断,边沿触发
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
(2)配置一个定时器用来定时接收数据void TIM2_Configuration(u16 period)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能TIM2的时钟
TIM_DeInit(TIM2); //复位TIM2定时器
TIM_InternalClockConfig(TIM2); //采用内部时钟给TIM2提供时钟源
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; //预分频系数为72,这样计数器时钟为72MHz/72 = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//设置计数器模式为向上计数模式
TIM_TimeBaseStructure.TIM_Period = period - 1; //设置计数溢出大小,每计period个数就产生一个更新事件
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //将配置应用到TIM2中
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除溢出中断标志
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启TIM2的中断
TIM_Cmd(TIM2,DISABLE); //关闭定时器TIM2
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //通道设置为TIM2中断
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应式中断优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //打开中断
NVIC_Init(&NVIC_InitStructure);
}
enum{
COM_START_BIT, //停止位
COM_D0_BIT, //bit0
COM_D1_BIT, //bit1
COM_D2_BIT, //bit2
COM_D3_BIT, //bit3
COM_D4_BIT, //bit4
COM_D5_BIT, //bit5
COM_D6_BIT, //bit6
COM_D7_BIT, //bit7
COM_STOP_BIT, //bit8
};
u8 recvStat = COM_STOP_BIT; //定义状态机
#define COM_RX_STAT GPIO_ReadInputDataBit(COM_RX_PORT, COM_RX_PIN)
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line5)!=RESET)
{
if(!COM_RX_STAT) //检测引脚高低电平,如果是低电平,则说明检测到下升沿
{
if(recvStat == COM_STOP_BIT) //状态为停止位
{
recvStat = COM_START_BIT; //接收到开始位
Delay(1000); //延时一定时间
TIM_Cmd(TIM2, ENABLE); //打开定时器,接收数据
}
}
EXTI_ClearITPendingBit(EXTI_Line5); //清除EXTI_Line1中断挂起标志位
}
}
u8 recvData;
然后,定时器中断中,每收到1位数据就改变下状态机并同时写入这个recvData对应的数据位中,当收到8为数据后,然后关闭定时器定时,以等待新的数据到来:void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检测是否发生溢出更新事件
{
TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);//清除中断标志
recvStat++; //改变状态机
if(recvStat == COM_STOP_BIT) //收到停止位
{
TIM_Cmd(TIM2, DISABLE); //关闭定时器
return; //并返回
}
if(COM_RX_STAT) //'1'
{
recvData |= (1 << (recvStat - 1));
}
else //'0'
{
recvData &= ~(1 <<(recvStat - 1));
}
}
}
void Delay(u32 t)
{
while(t--);
}
void Delay_us(u32 nus)
{
SysTick->LOAD=nus*9; //时间加载
SysTick->CTRL|=0x01; //开始倒数
while(!(SysTick->CTRL&(1<<16)));//等待时间到达
SysTick->CTRL=0X00000000; //关闭计数器
SysTick->VAL=0X00000000; //清空计数器
}
void Delay_ms(u16 nms)
{
SysTick->LOAD=(u32)nms*9000; //给重装载寄存器赋值,9000时,将产生1ms的时基
SysTick->CTRL|=0x01; //开始倒数
while(!(SysTick->CTRL&(1<<16))); //等待时间到达
SysTick->CTRL=0X00000000; //关闭计数器
SysTick->VAL=0X00000000; //清空计数器
}
这里能配置的只有300、600、1200三种波特率,其他的波特率我不想弄,也没有必要弄。下面编写一个初始化IO模拟的串口,包括引脚配置、波特率设置、定时时间设置等:
#define _300BuadRate 3150
#define _600BuadRate 1700
#define _1200BuadRate 800
void VirtualCOM_Config(u16 baudRate)
{
u32 period;
VirtualCOM_TX_GPIOConfig();
VirtualCOM_RX_GPIOConfig();
if(baudRate == _300BuadRate) //波特率300
period = _300BuadRate + 250;
else if (baudRate == _600BuadRate) //波特率600
period = _600BuadRate + 50;
else if (baudRate == _1200BuadRate) //波特率1200
period = _1200BuadRate + 50;
TIM2_Configuration(period); //设置对应模特率的定时器的定时时间
delayTime = baudRate; //设置IO串口发送的速率
}
void BSP_Init(void)
{
static volatile ErrorStatus HSEStartUpStatus = SUCCESS;
RCC_DeInit(); //默认配置SYSCLK, HCLK, PCLK2, PCLK1, 复位后就是该配置
RCC_HSEConfig(RCC_HSE_ON); //使能外部高速晶振HSEStartUpStatus = RCC_WaitForHSEStartUp (); //等待外部高速稳定
if ( HSEStartUpStatus == SUCCESS )
{
FLASH_PrefetchBufferCmd ( FLASH_PrefetchBuffer_Enable ); //使能flash预读取缓冲区
FLASH_SetLatency ( FLASH_Latency_2 ); //令Flash处于等待状态,2是针对高频时钟的
RCC_HCLKConfig ( RCC_SYSCLK_Div1 ); //HCLK = SYSCLK 设置高速总线时钟=系统时钟
RCC_PCLK2Config ( RCC_HCLK_Div1 ); //PCLK2 = HCLK 设置低速总线2时钟=高速总线时钟
RCC_PCLK1Config ( RCC_HCLK_Div2 ); //PCLK1 = HCLK/2 设置低速总线1的时钟=高速时钟的二分频
RCC_PLLConfig ( RCC_PLLSource_HSE_Div1 , RCC_PLLMul_9 ); //PLLCLK = 8MHz * 9 = 72 MHz 利用锁相环讲外部8Mhz晶振9倍频到72Mhz
RCC_PLLCmd ( ENABLE ); //使能PLL锁相环
while ( RCC_GetFlagStatus ( RCC_FLAG_PLLRDY ) == RESET ){} //等待锁相环输出稳定
RCC_SYSCLKConfig ( RCC_SYSCLKSource_PLLCLK ); //将锁相环输出设置为系统时钟
while ( RCC_GetSYSCLKSource () != 0x08 ){} //等待校验成功
} //使能GPIO口所使用的时钟
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_GPIOF | RCC_APB2Periph_GPIOG , ENABLE );
extern u8 recvData;
int main(void)
{
BSP_Init();
VirtualCOM_Config(_600BuadRate); //配置IO模拟串口的波特率为600
VirtualCOM_StringSend("HelloWorld!\r\n"); //发送“HelloWorld!”字符串
while(1)
{
VirtualCOM_ByteSend(recvData);
Delay(5000000);
}
}