你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32模拟串口的经验分享

[复制链接]
攻城狮Melo 发布时间:2023-3-22 18:02
1、添加头文件
首先我们先添加相应的头文件。既然我们要进行对串口的模拟,因此我们要先了解uart相关的通信协议。由于UART的通信方式是由1个起始位,8个数据位,包含一个奇偶校验位,和结束位构成因此我们将使用单片机中的两个普通的IO口电平的高低进行对相应时序的模拟。

微信图片_20230322180245.png
  1. #include "stm32f10x.h"
  2. #include "vuart2.h"
复制代码



2、宏定义
使用到的io口为
  1. #define OI2_TXD PDout(6)
  2. #define OI2_RXD PDin(7)

  3. #define BuadRate2_9600 104
  4. #define Recive2_Byte 19 //接收缓冲器的个数
  5. u8 len2 = 0; //接收计数
  6. u8 USART2_buf[Recive2_Byte];  //接收缓冲区
复制代码

将IO口相应的位带操作函数进行宏定义从而使得在对不同的电平的进行转换的时候更为方便,并且减少了调用其他函数的过程所消耗的时间,程序执行效率更高。
在本次的传输过程中我选用的是使用波特率速率为9600,也就是1s中发送9600个字节,因此对每个字节数据进行计算1000000us/9600可以得出,发一个字节的数据需要进行大概需要 104.16us并且对于相应的电平持续时间要求误差不能超过±5%因此对我们进行时间的控制要求就显得比较重要了。

3、枚举出各个位
  1. enum{
  2.     COM_START_BIT,
  3.     COM_D0_BIT,
  4.     COM_D1_BIT,
  5.     COM_D2_BIT,
  6.     COM_D3_BIT,
  7.     COM_D4_BIT,
  8.     COM_D5_BIT,
  9.     COM_D6_BIT,
  10.     COM_D7_BIT,
  11.     COM_STOP_BIT,
  12. };

  13. u8 recvStat2 = COM_STOP_BIT;
  14. u8 recvData2 = 0;
复制代码


4、IO——TXD进行模拟
  1. void IO2_TXD(u8 Data)
  2. {
  3.     u8 i = 0;
  4.     OI2_TXD = 0;  
  5.     delay_us(BuadRate2_9600);
  6.     for(i = 0; i < 8; i++)
  7.     {
  8.         if(Data&0x01)
  9.             OI2_TXD = 1;  
  10.         else
  11.             OI2_TXD = 0;  
  12.         
  13.         delay_us(BuadRate2_9600);
  14.         Data = Data>>1;
  15.     }
  16.     OI2_TXD = 1;
  17.     delay_us(BuadRate2_9600);
  18. }
复制代码


由于发送的信号是将TXD信号进行拉低处理,因此在拉低TXD相应的IO口之后进行延时处理,再进行循环对我们需要发送的各个位的数据继续进行发送循环发送完成之后将电平拉高代表停止位。

5、构建发送函数
  1. void USART2_Send(u8 *buf, u8 len2)
  2. {
  3.     u8 t;
  4.     for(t = 0; t < len2; t++)
  5.     {
  6.         IO2_TXD(buf[t]);
  7.     }
  8. }
复制代码


其中的*buf为需要发送的数据,len2为数据长度,进行循环调用IO_TXD进行一个字节一个字节的数据发送。

6、IO口初始化
  1. void IO2Config(void)
  2. {
  3.     GPIO_InitTypeDef  GPIO_InitStructure;//初始化gpio
  4.     NVIC_InitTypeDef NVIC_InitStructure;//中断初始化函数
  5.      EXTI_InitTypeDef EXTI_InitStruct;
  6.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);  //使能PD,PC端口时钟
  7.      
  8.      //SoftWare Serial TXD
  9.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;       //选择io口6
  10.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
  11.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //IO口速度为50MHz  
  12.     GPIO_Init(GPIOD, &GPIO_InitStructure);      
  13.     GPIO_SetBits(GPIOD,GPIO_Pin_6);       //TXD默认电平拉高
  14.      
  15.     //SoftWare Serial RXD
  16.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  17.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  18.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
  19.     GPIO_Init(GPIOD, &GPIO_InitStructure);  

  20.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);  //对D7的下降沿进行中断采样,当接收到下降沿时代表接收到数据触发中断处理函数
  21.     EXTI_InitStruct.EXTI_Line = EXTI_Line7;//用到了中断7
  22.     EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
  23.     EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
  24.     EXTI_InitStruct.EXTI_LineCmd=ENABLE;
  25.     EXTI_Init(&EXTI_InitStruct);//初始化中断


  26.     NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ; //中断发生于9-5的中断之中
  27.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
  28.     NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
  29.     NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
  30.     NVIC_Init(&NVIC_InitStructure);  
  31. }
复制代码


7、定时器初始化
  1. void TIM5_Int_Init(u16 arr,u16 psc)
  2. {
  3.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  4.     NVIC_InitTypeDef NVIC_InitStructure;
  5.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
  6.    
  7.     //定时器TIM5初始化
  8.     TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
  9.     TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
  10.     TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
  11.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
  12.     TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
  13.     TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
  14.     TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断

  15.     //中断优先级NVIC设置
  16.     NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM5中断
  17.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
  18.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
  19.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
  20.     NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器   
  21. }
复制代码


对TIM5进行初始化操作使得定时器可以检测到各个位的电平持续性时间从而对接收到的数据进行分析。计时结束后进入中断TIM5处理。

8、外部中断处理函数
  1. void EXTI9_5_IRQHandler(void)
  2. {
  3.     if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)//对中断标志位进行采集
  4.     {
  5.         if(OI2_RXD == 0)
  6.         {
  7.             if(recvStat2 == COM_STOP_BIT)
  8.             {
  9.                 recvStat2 = COM_START_BIT;//将当前的状态设置为开始位
  10.                 TIM_Cmd(TIM5, ENABLE);//开启定时器计数
  11.             }
  12.         }
  13.         EXTI_ClearITPendingBit(EXTI_Line7); //清除中断标志
  14.     }
  15. }
复制代码



9、定时器中断处理函数
  1. void TIM5_IRQHandler(void)
  2. {  
  3.     if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
  4.     {
  5.         TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update); //清除中断标志位
  6.         recvStat2++; //将位置移动到第一位的数据
  7.         if(recvStat2 == COM_STOP_BIT)//当运行到停止位时进入
  8.         {
  9.             TIM_Cmd(TIM5, DISABLE);//停止tim5
  10.             USART2_buf[len2++] = recvData2;//将采集到的各个数据传递给USART2_buf
  11.             if(len2 > Recive2_Byte-1)//将数据通过回显到串口调试助手中
  12.             {
  13.                 len2 = 0;
  14.                 USART2_Send(USART2_buf,Recive2_Byte);
  15.             }
  16.             return;
  17.         }
  18.         if(OI2_RXD)//采集RXD各个电平
  19.         {
  20.             recvData2 |= (1 << (recvStat2 - 1));
  21.         }else{
  22.             recvData2 &= ~(1 << (recvStat2 - 1));
  23.         }
  24.   }  
  25. }
复制代码



整体代码
vuart2.c
  1. #include "stm32f10x.h"
  2. #include "vuart2.h"
  3. /**
  4. *软件串口的实现(IO模拟串口)
  5. * 波特率:9600    1-8-N
  6. * TXD : PD6
  7. * RXD : PD7
  8. * 使用外部中断对RXD的下降沿进行触发,使用定时器5按照9600波特率进行定时数据接收。
  9. * Demo功能: 接收11个数据,然后把接收到的数据发送出去
  10. */


  11. #define OI2_TXD PDout(6)
  12. #define OI2_RXD PDin(7)

  13. #define BuadRate2_9600 104
  14. #define Recive2_Byte 19 //接收缓冲器的个数
  15. u8 len2 = 0; //接收计数
  16. u8 USART2_buf[Recive2_Byte];  //接收缓冲区

  17. enum{
  18.     COM_START_BIT,
  19.     COM_D0_BIT,
  20.     COM_D1_BIT,
  21.     COM_D2_BIT,
  22.     COM_D3_BIT,
  23.     COM_D4_BIT,
  24.     COM_D5_BIT,
  25.     COM_D6_BIT,
  26.     COM_D7_BIT,
  27.     COM_STOP_BIT,
  28. };

  29. u8 recvStat2 = COM_STOP_BIT;
  30. u8 recvData2 = 0;

  31. void IO2_TXD(u8 Data)
  32. {
  33.     u8 i = 0;
  34.     OI2_TXD = 0;  
  35.     delay_us(BuadRate2_9600);
  36.     for(i = 0; i < 8; i++)
  37.     {
  38.         if(Data&0x01)
  39.             OI2_TXD = 1;  
  40.         else
  41.             OI2_TXD = 0;  
  42.         
  43.         delay_us(BuadRate2_9600);
  44.         Data = Data>>1;
  45.     }
  46.     OI2_TXD = 1;
  47.     delay_us(BuadRate2_9600);
  48. }
  49.    
  50. void USART2_Send(u8 *buf, u8 len2)
  51. {
  52.     u8 t;
  53.     for(t = 0; t < len2; t++)
  54.     {
  55.         IO2_TXD(buf[t]);
  56.     }
  57. }
  58.    
  59. void IO2Config(void)
  60. {
  61.     GPIO_InitTypeDef  GPIO_InitStructure;
  62.     NVIC_InitTypeDef NVIC_InitStructure;
  63.      EXTI_InitTypeDef EXTI_InitStruct;
  64.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);  //使能PB,PC端口时钟
  65.      
  66.      //SoftWare Serial TXD
  67.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;     
  68.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
  69.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //IO口速度为50MHz  
  70.     GPIO_Init(GPIOD, &GPIO_InitStructure);      
  71.     GPIO_SetBits(GPIOD,GPIO_Pin_6);      
  72.      
  73.     //SoftWare Serial RXD
  74.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  75.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  76.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
  77.     GPIO_Init(GPIOD, &GPIO_InitStructure);  

  78.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOD, GPIO_PinSource7);
  79.     EXTI_InitStruct.EXTI_Line = EXTI_Line7;
  80.     EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
  81.     EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发中断
  82.     EXTI_InitStruct.EXTI_LineCmd=ENABLE;
  83.     EXTI_Init(&EXTI_InitStruct);


  84.     NVIC_InitStructure.NVIC_IRQChannel= EXTI9_5_IRQn ;
  85.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
  86.     NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  
  87.     NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;  
  88.     NVIC_Init(&NVIC_InitStructure);  
  89. }

  90. void TIM5_Int_Init(u16 arr,u16 psc)
  91. {
  92.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  93.     NVIC_InitTypeDef NVIC_InitStructure;
  94.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
  95.    
  96.     //定时器TIM5初始化
  97.     TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
  98.     TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
  99.     TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
  100.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
  101.     TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
  102.     TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
  103.     TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断

  104.     //中断优先级NVIC设置
  105.     NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM4中断
  106.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
  107.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //从优先级1级
  108.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
  109.     NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器   
  110. }
  111. void EXTI9_5_IRQHandler(void)
  112. {
  113.     if(EXTI_GetFlagStatus(EXTI_Line7) != RESET)
  114.     {
  115.         if(OI2_RXD == 0)
  116.         {
  117.             if(recvStat2 == COM_STOP_BIT)
  118.             {
  119.                 recvStat2 = COM_START_BIT;
  120.                 TIM_Cmd(TIM5, ENABLE);
  121.             }
  122.         }
  123.         EXTI_ClearITPendingBit(EXTI_Line7);
  124.     }
  125. }

  126. void TIM5_IRQHandler(void)
  127. {  
  128.     if(TIM_GetFlagStatus(TIM5, TIM_FLAG_Update) != RESET)
  129.     {
  130.         TIM_ClearITPendingBit(TIM5, TIM_FLAG_Update);
  131.         recvStat2++;
  132.         if(recvStat2 == COM_STOP_BIT)
  133.         {
  134.             TIM_Cmd(TIM5, DISABLE);
  135.             USART2_buf[len2++] = recvData2;
  136.         if(len2 > Recive2_Byte-1)
  137.         {
  138.             len2 = 0;
  139.             USART2_Send(USART2_buf,Recive2_Byte);
  140.         }
  141.             return;
  142.         }
  143.         if(OI2_RXD)
  144.         {
  145.             recvData2 |= (1 << (recvStat2 - 1));
  146.         }else{
  147.             recvData2 &= ~(1 << (recvStat2 - 1));
  148.         }
  149.   }  
  150. }
复制代码


vuart2.h
  1. #ifndef __VUART2__H
  2. #define __VUART2__H
  3. #include "stm32f10x.h"

  4. void IO2_TXD(u8 Data);
  5. void USART2_Send(u8 *buf, u8 len);
  6. void IO2Config(void);
  7. void TIM5_Int_Init(u16 arr,u16 psc);
  8. #endif
复制代码

转载自:硬件攻城狮
如有侵权请联系删除

收藏 评论1 发布时间:2023-3-22 18:02

举报

1个回答
goyhuan 回答时间:2023-3-23 10:42:54

如果需要高速串口怎么办

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版