历史上的今天
今天是:2025年01月31日(星期五)
2019年01月31日 | 基于STM32系列的模拟串口(非阻塞式)
2019-01-31 来源:eefocus
STM32单片机一般少则3个串口,多则5个,而我这次的项目还偏偏5个硬件串口还是不够用.
至于不够用的原因,哎,是项目做到后面有定制,随便哪个串口都省不得,没得办法,只能另想法子咯~
板子上有几个预留IO口,可以用来模拟串口. 模拟串口一般都选9600,速度最快试了也才19200,
所以限制还是较多的,一般不得以情况下才会用到.
在写程序之前我也是参考了前辈们,觉得写的不咋地,照抄他的代码,那我的系统啥事情也
不要干了,就在那里delay吧!!! 模拟串口分收和发:收比较难,发送比较容易,那就先将接收这块吧。
接收:
有2种思路: 一种是第一个下降沿开始启动定时器,每个bit都去采样Rx电平;
从第一个边沿开始统计时间戳,接收完10个bit后再解析.
我这边碰巧选择的是第二种方法,具体实现思路是:
① 启动定时器2用来做背景时间,定时器分频后的计数频率为1MHz,那么对于9600bps来说1个bit就是104.16us,我们取整数104就可以了;
② 再同时启动一个定时器3,定时时间为104*9.5,这个定时器中断发生的时候表示一个字节接收完毕了;

我们以0x37串口通讯时序图为例, ,传输的时候是LSB 1st, Rx引脚选择边沿触发, 第1次边沿到的时候启动定时器2,同时启动定时器3, 第2个边沿的时候将定时器2的计数值存到数组里,此时是104,第3个边沿触发的时候将其计数104*5计数值,同理将4,5,6三个边沿对应的TIM2值存到缓存里,最终是 0,104,416,520,728,936,除以104变成0,1,4,5,7,9;
如何将0,1,4,5,7,9解析成0x37,看似简单的问题其实还是有点麻烦的~ 给个思路,先将0和9这样的数字去掉,变成1,4,5,7;
然后从1开始数到8,跟数组里元素是否有匹配,如果匹配就将状态取反,没有就维持之前的状态:
1有,2,3没有,4有,5有,6没有,7有,8没有-->11101100->倒序后就是0x37了;
再来分析几个处理后的值:4,5,7: 1,2,3没有,4有,5有,6没有,7有,8没有-->00010011->逆序后11001000->0xC8
1,2: 1有,2有,3,4,5,6,7,8没有->10000000->逆序后00000001->0x01
1,2,3,4,6,7,8:1有,2有,3有,4有,5没有,6有,7有,8有->10100101->逆序后10100101->0xA5
7,8:1,2,3,4,5,6没有,7有,8有->00000010->逆序后01000000->0x40
总结下如果第一个是1开头的那么得出的bit就是1,如果第一个是其他数字开头的,则 "1到其他数字" 之间用0来填充;
下面我们就根据总结出来的规律编写解析代码:
int process_byte(int nums)
{
int i;
u8 a=0;
u8 byte=0;
u8 new_array[8];
memset(new_array,0,sizeof(new_array));
for(i=0;i<=nums;i++) //1...7
{
timerecode[i]+=ONE_BIT_TIME/2;
timerecode[i]/=ONE_BIT_TIME;
}
if(timerecode[nums]>=9) //计算下标是从1开始的,并且去掉了9,所以剩下的也不多了
nums--;
if(nums<=0) //全0特殊处理
return 0;
find_max=nums; //去掉第一个0
/*
假设收到的是0,2,4,9
经过处理后就剩下2,4
将2放到new_array下标为1的地方,4放到下标为3的地方
*/
for(i=0;i { find_array[i]=timerecode[i+1]; if(find_array[i]!=0) { new_array[find_array[i]-1]=find_array[i]; } } /* 然后在for(i=find_array[0]-1;i<8;i++) 这个循环里找,如果对应的位有边沿改变,就改变a的值 找不到就复制a的值,这里有点要注意,i循环是从高电平 开始的 */ for(i=find_array[0]-1;i<8;i++) { byte>>=1; if(i==new_array[i]-1) a=!a; if(a) byte|=0x80; } return byte; } 对于上图0x37的例子,当定时器3中断时,就调用process_byte(nums-1),其中nums=6,就 是边沿次数,来解析我们接收到的byte啦. 下面的截图是我用模拟串口编写的收发例子: 收发48个任意字节都正常,没有哪个是解析错的. 其他一些函数都比较简单,建议大家自己动手! 有感兴趣的,我会接着写 模拟中断发送!今天到此为止,希望各位喜欢~ 发送部分:这部分完全依赖定时器7了,定时周期104us。 发送的原理很简单,我这里没有使用delay方式来发送,而是在定时器里实现发送的; #define VIR_TXBUFF_SIZ 128 #define VIR_RXBUFF_SIZ 128 typedef struct { //---------Rx------------ u8 rxov; u8 rxlen; u8 rxbuff_idx; u8 rx_decode_flag; u8 RxBuff[VIR_RXBUFF_SIZ];//大小一定要是2的次方关系 u8 RXREG; //---------Tx------------ u8 send_flag; u8 send_max; u8 send_cnt; u8 send_mode; //0-阻塞式发送 1-中断发送(放到sendbuff里,指定send_max即可) u8 sendbuff[VIR_TXBUFF_SIZ]; u8 TXREG; }VIRTUAL_UART_t; static u8 send_a_byte(u8 dat) { if(VirtualUart.send_flag) return 1; VirtualUart.TXREG=dat; TIM7->CR1 |= TIM_CR1_CEN; Tx_Pin=0; VirtualUart.send_flag=1; return 0; } static void send_remain_byte(void) { if(VirtualUart.send_cnt>=VirtualUart.send_max) { VirtualUart.send_flag=0; //发送完毕 } else { VirtualUart.TXREG=VirtualUart.sendbuff[VirtualUart.send_cnt++]; Tx_Pin=0; //产生START信号 } } //TIM7定时器里调用 static u8 tim_send_byte(void (*Callback)(void)) { static int sendidx=0; sendidx++; if(sendidx<=8) //DATA 1,2,3...8 { Tx_Pin=VirtualUart.TXREG&0x01; VirtualUart.TXREG>>=1; } else if(sendidx==9) { //STOP Tx_Pin=1; } else if(sendidx==10) { //STOP的发送完毕了 sendidx=0; if(Callback!=NULL) //有多个字节要发送,调用回调函数继续发送下一个字节 Callback(); else VirtualUart.send_flag=0; //已经发送完毕了 return 0; } return 1; //1-busy } //阻塞式发送 void vu_send_string(u8 *s) { VirtualUart.send_mode=0; while(*s) { while(VirtualUart.send_flag!=0); send_a_byte(*s); s++; } } //阻塞式发送 void vu_send_len(u8 *s,int len) { VirtualUart.send_mode=0; while(len--) { while(VirtualUart.send_flag!=0); send_a_byte(*s); s++; } } //中断发送 int vu_send_some_byte_noblock(int len) { if(VirtualUart.send_flag) return -1; if(len>VIR_TXBUFF_SIZ) return -2; VirtualUart.send_mode=1; VirtualUart.TXREG=VirtualUart.sendbuff[0]; TIM7->CR1 |= TIM_CR1_CEN; Tx_Pin=0; //产生START信号 VirtualUart.send_flag=1; VirtualUart.send_max=len; VirtualUart.send_cnt=1; return 0; } //主要发送部分代码就在这儿了 void TIM7_IRQHandler(void) { TIM7->SR = (uint16_t)~TIM_IT_Update; if(VirtualUart.send_mode) { if(tim_send_byte(send_remain_byte)==0) { if(VirtualUart.send_flag==0) TIM7->CR1 &= ~TIM_CR1_CEN; TIM7->CNT=0; } }else { if(tim_send_byte(NULL)==0) { TIM7->CR1 &= ~TIM_CR1_CEN; TIM7->CNT=0; } } }
上一篇:关于stm32的GPIO的操作
下一篇:STM32 定时器周期动态修改
史海拾趣
|
ARM培训视频-dsp-CPLD-FPGA开发视频-嵌入式系统开发-单片机开发视频 ARM培训视频-dsp-CPLD-FPGA开发视频-嵌入式系统开发-单片机开发视频 ------------------------------------------------------------------ 【ARM培训视频】课程主要内容 1.概念和基本工具: 嵌入式系统基本概念、嵌入式操作系统介绍 嵌入式系 ...… 查看全部问答> |
|
汽车伴我行——京郊另类玩家经典路线、汽车新科技、经典车型大罗列 汽车伴我行 1. 京郊另类玩家经典路线推荐 https://bbs.eeworld.com.cn/thread-2611-1-40.html 2. 智能车赛道记忆算法的研究 https://bbs.eeworld.com.cn/thread-9739-1-18.html 3. 汽车新科技 https://bbs.eeworld.com.cn/thread-7089-1-17.html ...… 查看全部问答> |
|
联华众科FPGA开发板 FA130 北京联华众科科技有限公司 http://www.lianhua-zhongke.com.cn Abstract 摘 要:联华众科FPGA开发板FA130核心器件为 Altera Cyclone系列的EP1C3,配置芯片为EPCS1,FA130上可以运行SOPC Bui ...… 查看全部问答> |
|
如何让wince5.0的IE支持flash呢?在adobe的网站上找到了用于ppc的flash插件,但不能用于wince, 听说,有人把ppc相应的dll考到了wince里,wince就可以用ppc的软件了,真的可能吗? 还有我的硬件是x86的,能否找到用于x86的ppc系统文件呢(dll), ...… 查看全部问答> |
|
各位高手大虾,我的开发板上有3个串口,串口1用来调试,串口2没有用,串口3接红外,由于我要加接GPS,又不想占用调试串口,就想把串口2打通, 在BSP中添加了相关代码后,在CE注册表下可以看到串口2,可是接上GPS线却什么反应的都没有,而串口1就可 ...… 查看全部问答> |
|
同步电路,post_sta和post_sim不同结果,谁碰到过? post_sta全通过,analysis_coverage分析基本都覆盖了,post_sim报setup违反,谁碰到过类似问题?用的是smic sc-m库 请各位高手支招!… 查看全部问答> |
|
简单地说,Bootloader 就是这么一小段程序,它在系统上电时开始执行,初始化硬件设备、准备好软件环境,最后调用操作系统内核。 可以增强Bootloader 的功能,比如增加网络功能、从PC 上通过串口或网络下载文 ...… 查看全部问答> |
|
求助!附程序!MSP430F169用I2C控制ADV7183写数据程序调试不成功!!急!! #include "io430.h" #include "In430.h" int tx_count; volatile unsigned char I2CBuffer[3]; void Init_Port(void) { P1DIR=0xFF; P2DIR=0XFF; P3DIR=0xFF; P4DIR=0xFF; P5DIR=0xFF; P6DIR=0xFF; P1SEL = 0; } void ...… 查看全部问答> |
|
我是一个什么都不懂的菜鸟,想学有关硬件的开发,请各位前辈指导 我偶然间发现这个论坛,然后对这个开发感兴趣,而且和自己的工作有那么一丁点关系,在工作中使用3维制作软件,里面会用到虚拟的摄像机,我就想如果有传感器控制会更加方便。所以希望在此学习。请各位前辈告诉我该如何入门。谢谢… 查看全部问答> |




