这个也是想了很久了,今天终于能实现了
记录一下
首先说一下基础知识
串口通讯:
串口通信原理和模拟串口发送数据的原理
标准串口数据格式为:起始位(1bit)+数据位(8bit)+校验位(1bit)+停止位(1bit)。其中起始位为低电平,停止位为高电平。
串口通讯需要设置波特率和检查COM口。 网上找到好多,都是使用2,感觉太浪费了。我这里改成4
思路是这样的,我们使用定时器TIM2来定时,每隔一段时间发送一个位,从而实现模拟串口发送数据。
计算9600波特率 对应的ARR自动装载的值
不要校验位,因此共有10个位的数据。
我以stm8中9600bit/s的波特率计算的过程为例(1秒钟传输9600位)。
可以计算出传输1位所需要的时间 T1 = 1/9600 约为104us。
stm8 内部晶振频率为16M,我采用16分频也就是1M,故MCU震荡周期为 1/1M = 1us。
由上面的计算我们可以知道要发送一位数据,定时器中定时的自动装载的值应设为为 104/1 =104。
实现过程
void main(void)
{
All_Config(); // 各种初始化
enableInterrupts(); // 使能中断
while (1)
{
SimUART_TxByte( 0xF0 );
Delay_ms(1000); // 延时 1ms
}
}
这里发送了 一个 F0,相对来说 4高4低,方便看波形,不敢相信,我自己舍不得买一个示波器,伤心啊。
先看初始化函数
void All_Config( void )
{
CLK_HSICmd(ENABLE); //使用内部高速晶振
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 设置时钟分频为1
GPIO_Init(GPIOA, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST); // 初始化 虚拟串口IO输出管脚 TX
TIM4_Init(); // 计时器 4 初始化
}
GPIO_Init(GPIOA, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST); 我使用了 A5 作为发送端口,这里可以改,也可以做个 宏定义。
然后就是初始化 定时器4
来看一下怎么初始化
/*其中,TIM4_DeInit() 函数用于将定时器4的所有寄存器恢复到默认值,
以确保定时器4处于初始状态。TIM4_Cmd() 函数用于使能定时器4,使其开始计数。
TIM4_TimeBaseInit() 函数用于配置定时器4的时基,其中 TIM4_PRESCALER_16 表示分频系数为16,
0x68 表示自动重载值为0x68,即当计数器计数到0x68时,定时器4会自动重新计数。
最后,TIM4->CNTR = 0; 语句将定时器4的计数器清零,以确保定时器4从0开始计数。*/
void TIM4_Init()
{
TIM4_DeInit(); // 复位定时器4
TIM4_Cmd(ENABLE); // 使能定时器4
TIM4_TimeBaseInit(TIM4_PRESCALER_16, 0x68); // 配置定时器4的时基,分频系数为16,自动重载值为0x68 这里68 对应 104 也就是 104us
TIM4->CNTR = 0; // 将定时器4的计数器清零
}
注释都写在里面了,就不解释了。
最后就是发送函数了
void SimUART_TxByte( u8 SendData )
{
static u16 Send_All = 0; // 静态变量,用于存储需要发送的10个位的数据
static u8 BitNum = 0; // 静态变量,用于记录当前发送的位数
Send_All = SendData; // 将需要发送的数据存储到 Send_All 变量中
Send_All = ( Send_All << 1 ) | 0X0200; // 将需要发送的数据转换为10位数据,并存储到 Send_All 变量中
TIM4->CNTR = 0; // 将定时器4的计数器清零
TIM4->SR1 &= ~TIM2_SR1_UIF; // 清除定时器4的更新标志位
// 先发送低位,再发送高位
for( BitNum = 0; BitNum<10 ; BitNum++ )
{
if( Send_All & 0X0001 ) // 如果当前位是高电平,发送高电平
{
GPIO_WriteHigh(GPIOA, GPIO_PIN_5);
}
else // 如果当前位是低电平,发送低电平
{
GPIO_WriteLow(GPIOA, GPIO_PIN_5);
}
Send_All >>= 1; // 将 Send_All 右移一位,准备发送下一位数据
// 等待波特率时间
while( (TIM4->SR1 & TIM4_SR1_UIF) == 0 );
TIM4->SR1 &= ~TIM4_SR1_UIF; // 清除定时器4的更新标志位
}
}
当然,关于那个GPIO 的问题,依旧可以使用宏定义,我是只用这一个端口,懒得改了。这样也好理解。
测试结果
上图,使用单片机IO口连接电脑串口,串口助手显示一点问题没有