转自: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 &#61; 0; i <8; i&#43;&#43;) //8位数据位
{
if(val & 0x01)
IO_HIGH();
else
IO_LOW();
Dealy(sometime);
val >>&#61; 1;
}
IO_HIGH(); //停止位&#xff0c;拉高电平
Delay(sometime);
}
#define COM_TX_PORT GPIOA
#define COM_TX_PIN GPIO_Pin_4
void VirtualCOM_TX_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* PA4最为数据输出口&#xff0c;模拟TX */
GPIO_InitStructure.GPIO_Pin &#61; COM_TX_PIN;
GPIO_InitStructure.GPIO_Mode &#61; GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed &#61; GPIO_Speed_50MHz;
GPIO_Init(COM_TX_PORT, &GPIO_InitStructure);
GPIO_SetBits(COM_TX_PORT, COM_TX_PIN);
}
&#xff08;2&#xff09;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 &#61; 0;
COM_DATA_LOW(); //起始位
Delay_us(delayTime);
for(i &#61; 0; i < 8; i&#43;&#43;) //8位数据位
{
if(val & 0x01)
COM_DATA_HIGH();
else
COM_DATA_LOW();
Delay_us(delayTime);
val >>&#61; 1;
}
COM_DATA_HIGH(); //停止位
Delay_us(delayTime);
}
&#xff08;3&#xff09;IO模拟串口发送字符串
void VirtualCOM_StringSend(u8 *str)
{
while(*str !&#61; 0)
{
VirtualCOM_ByteSend(*str);
str&#43;&#43;;
}
}
#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最为数据输入&#xff0c;模拟RX */
GPIO_InitStructure.GPIO_Pin &#61; COM_RX_PIN;
GPIO_InitStructure.GPIO_Mode &#61; GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed &#61; GPIO_Speed_50MHz;
GPIO_Init(COM_RX_PORT, &GPIO_InitStructure);
EXTI_InitStruct.EXTI_Line&#61;EXTI_Line5;
EXTI_InitStruct.EXTI_Mode&#61;EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger&#61;EXTI_Trigger_Falling;//下降沿都中断
EXTI_InitStruct.EXTI_LineCmd&#61;ENABLE;
EXTI_Init(&EXTI_InitStruct);
NVIC_InitStructure.NVIC_IRQChannel&#61;EXTI9_5_IRQn; //外部中断&#xff0c;边沿触发
NVIC_InitStructure.NVIC_IRQChannelSubPriority &#61; 2;
NVIC_InitStructure.NVIC_IRQChannelCmd&#61;ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
&#xff08;2&#xff09;配置一个定时器用来定时接收数据
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 &#61; 72 - 1; //预分频系数为72&#xff0c;这样计数器时钟为72MHz/72 &#61; 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision &#61; 0; //设置时钟分频
TIM_TimeBaseStructure.TIM_CounterMode &#61; TIM_CounterMode_Up;//设置计数器模式为向上计数模式
TIM_TimeBaseStructure.TIM_Period &#61; period - 1; //设置计数溢出大小&#xff0c;每计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 &#61; TIM2_IRQn; //通道设置为TIM2中断
NVIC_InitStructure.NVIC_IRQChannelSubPriority &#61; 1;//响应式中断优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd &#61; 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 &#61; 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)!&#61;RESET)
{
if(!COM_RX_STAT) //检测引脚高低电平&#xff0c;如果是低电平&#xff0c;则说明检测到下升沿
{
if(recvStat &#61;&#61; COM_STOP_BIT) //状态为停止位
{
recvStat &#61; COM_START_BIT; //接收到开始位
Delay(1000); //延时一定时间
TIM_Cmd(TIM2, ENABLE); //打开定时器&#xff0c;接收数据
}
}
EXTI_ClearITPendingBit(EXTI_Line5); //清除EXTI_Line1中断挂起标志位
}
}
u8 recvData;
然后&#xff0c;定时器中断中&#xff0c;每收到1位数据就改变下状态机并同时写入这个recvData对应的数据位中&#xff0c;当收到8为数据后&#xff0c;然后关闭定时器定时&#xff0c;以等待新的数据到来&#xff1a;
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) !&#61; RESET) //检测是否发生溢出更新事件
{
TIM_ClearITPendingBit(TIM2 , TIM_FLAG_Update);//清除中断标志
recvStat&#43;&#43;; //改变状态机
if(recvStat &#61;&#61; COM_STOP_BIT) //收到停止位
{
TIM_Cmd(TIM2, DISABLE); //关闭定时器
return; //并返回
}
if(COM_RX_STAT) //&#39;1&#39;
{
recvData |&#61; (1 << (recvStat - 1));
}
else //&#39;0&#39;
{
recvData &&#61; ~(1 <<(recvStat - 1));
}
}
}
void Delay(u32 t)
{
while(t--);
}
void Delay_us(u32 nus)
{
SysTick->LOAD&#61;nus*9; //时间加载
SysTick->CTRL|&#61;0x01; //开始倒数
while(!(SysTick->CTRL&(1<<16)));//等待时间到达
SysTick->CTRL&#61;0X00000000; //关闭计数器
SysTick->VAL&#61;0X00000000; //清空计数器
}
void Delay_ms(u16 nms)
{
SysTick->LOAD&#61;(u32)nms*9000; //给重装载寄存器赋值&#xff0c;9000时&#xff0c;将产生1ms的时基
SysTick->CTRL|&#61;0x01; //开始倒数
while(!(SysTick->CTRL&(1<<16))); //等待时间到达
SysTick->CTRL&#61;0X00000000; //关闭计数器
SysTick->VAL&#61;0X00000000; //清空计数器
}
这里能配置的只有300、600、1200三种波特率&#xff0c;其他的波特率我不想弄&#xff0c;也没有必要弄。下面编写一个初始化IO模拟的串口&#xff0c;包括引脚配置、波特率设置、定时时间设置等&#xff1a;
#define _300BuadRate 3150
#define _600BuadRate 1700
#define _1200BuadRate 800
void VirtualCOM_Config(u16 baudRate)
{
u32 period;
VirtualCOM_TX_GPIOConfig();
VirtualCOM_RX_GPIOConfig();
if(baudRate &#61;&#61; _300BuadRate) //波特率300
period &#61; _300BuadRate &#43; 250;
else if (baudRate &#61;&#61; _600BuadRate) //波特率600
period &#61; _600BuadRate &#43; 50;
else if (baudRate &#61;&#61; _1200BuadRate) //波特率1200
period &#61; _1200BuadRate &#43; 50;
TIM2_Configuration(period); //设置对应模特率的定时器的定时时间
delayTime &#61; baudRate; //设置IO串口发送的速率
}
void BSP_Init(void)
{
static volatile ErrorStatus HSEStartUpStatus &#61; SUCCESS;
RCC_DeInit(); //默认配置SYSCLK, HCLK, PCLK2, PCLK1, 复位后就是该配置
RCC_HSEConfig(RCC_HSE_ON); //使能外部高速晶振
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);
}
}
7、现象