前言:前一段时间需要编写一个使用双路串口的程序采集传感器数据,由于自身能力有限所以遇到了很多坑,后来经过多方学习和调试基本完成了所需功能,现将自己的一些经(踩)验(过)方(的)法(坑),与大家分享。由于本人水平有限文章中有不足之处也欢迎大家指出改正!
1、串口配置
本人采用的是stm32F407的串口1和串口3(串口2因为硬件问题让我给烧坏了…尴尬, 在此也提醒大家一定要确保硬件连接无误),配置串口的过程不再赘述,直接上代码
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//USART1端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
//USART_ClearFlag(USART1, USART_FLAG_TC);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启相关中断
// USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
}
void uart3_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);//使能USART1时钟
//串口3对应引脚复用映射
GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3); //GPIOA10复用为USART1
//USART3端口配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //GPIOB10与GPIOB11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化PA9,PA10
//USART3 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure); //初始化串口3
USART_Cmd(USART3, ENABLE); //使能串口3
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //开启接收中断
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); //开启空闲中断
//Usart3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
}
这里需要说明的强调的是,在配置串口3的时候开启了空闲中断,这里是为了实现接收不定长数据,会在下文详细说明。
2、接收不定长数据的实现
首先说明一下个人的思路,如下图。
这里写图片描述
首先是由上位机通过串口1向stm32发送一条相关指令,stm32再将指令转发给传感器端,传感器接收到相关指令后通过串口3发送相关数据给stm32,最后再由stm32将数据进行相应解析后通过串口1回传给上位机。
但是无论是上位机指令还是传感器回传的数据,其数据长度都不是固定的,以下给出两种方法分别实现对串口1和串口3的数据接收。
2.1、使用循环队列的方法接收串口1的数据
如上图所示,RevBuff1为串口1的接收缓存区;usart1_in为队头,每次接收到一个字节的数据后usart1_in++,用来记录当前入队的位置;usart1_out为队尾,每次出队后队尾记录上次入队的位置。串口1中断函数如下:
uint32_t usart1_in = 0;
uint32_t usart1_out = 0;
u8 RevBuff1[BUFFSIZE] = {0};
u8 flag = 0;
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 res1 = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收到数据
{
res1 = USART_ReceiveData(USART1);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
RevBuff1[usart1_in++] = res1;
if (usart1_in >= BUFFSIZE) {
usart1_in = 0;
}
flag = 1;
}
}
按照这个思路,要想将串口1的接收到的数据发送给串口3时,只需要判断队头和队尾的位置,然后发送队尾至队头之间的数据就可以实现不定长数据的发送了,具体代码实现如下:
//把串口1接收到的数据透传发送给串口3
if (flag == 1) {
if (usart1_out != usart1_in) { //在队头队尾不相等的情况下,即有数据存入RecBuff1
if (usart1_out < usart1_in) { //若队尾小于队头,直接发送队尾至队头之间的数据
for (t = usart1_out; t < usart1_in; t++) {
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=SET);
USART_SendData(USART3, RevBuff1[t]);
}
usart1_out = usart1_in; //发送完成后队尾移至当前队头的位置
flag = 0;
}
else {
//若队尾大于队头,先发送队尾至RecBuff1[BUFFSIZE]之间的数据
//然后发送RecBuff1[0]至队头之间的元素
for (t = usart1_out; t < BUFFSIZE; t++) {
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=SET);
USART_SendData(USART3, RevBuff1[t]);
}
for (t = 0; t < usart1_in; t++) {
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)!=SET);
USART_SendData(USART3, RevBuff1[t]);
}
usart1_out = 0; //发送完成后队尾移至RecBuff1[0]的位置
flag = 0;
}
}
}
2.2、利用串口的RXNE和IDLE中断接收串口3的数据
首先说明,IDLE中断就是串口收到一帧数据后,发生的中断;所谓一帧数据是指给单片机一次发来1个字节,或者一次发来若干个字节,这些一次发来的数据,就称为一帧数据。 而当接收到1个字节,就会产生RXNE中断。
上图为状态寄存器描述图,当串口接收到数据时,bit5就会自动变成1,当接收完一帧数据后,bit4就会变成1。
需要注意的是,在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断。IDLE中断,如果是F0系列的单片机,需要用ICR寄存器来清除,如果是F1系列的单片机,清除方法是“先读SR寄存器,再读DR寄存器”,经测试,F4系列与F1系列的方法相同。在使用IDLE中断之前先要在串口3配置时开启相应中断(上文已经提到过),如下图:
接着来看串口3的中断程序:
void USART3_IRQHandler(void) //串口3中断服务程序
{
u8 res3 = 0;
u8 Clear = 0;
//如果接收到一个字节
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {
res3 = USART_ReceiveData(USART3);
RevBuff3[usart3_in++] = res3;
if (usart3_in >= BUFFSIZE) {
usart3_in = 0;
}
flag3 = 1;
}
//如果接收到一帧数据
else if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) {
Clear = USART3->SR; //读SR寄存器
Clear = USART3->DR; //读DR寄存器(先读SR再度DR,可以清除IDLE中断)
ReceiveStave = 1; //标记接收到1帧的数据
}
}
串口3中断函数中,分别来判断是否接收到1个字节,以及判断是否接收到1帧数据,这样不仅能接收不定长数据,同时usart3_in中也记录了RevBuff3的大小。此时再进行其他的控制就容易多了,以下是我自己控制部分的代码:
//当串口3接收到一个完整数据帧,处理数据帧,将处理好结果发回串口1
if (ReceiveStave == 1) {
ReceiveStave = 0;
t = 0;
//复制RevBuff3到tmp
for (t = 0, tmpLen = 0; t < usart3_in; t++,tmpLen++) {
tmp[tmpLen] = RevBuff3[t];
}
//得到处理后的数据
p = ReadRealTimeData(tmp, tmpLen);
//发送处理后的数据
for (i = 0; i < strlen( p ); i++) {
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);
USART_SendData(USART1, p[i]);
// while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
usart3_in = 0;
}
3、串口发送数据的TC/TXE行为
先来看看串口数据发送的过程是什么样的:
先写数据到DR寄存器->移位寄存器->TX管脚。当数据从DR寄存器移出到移位寄存器(即DR寄存器空)时TXE就置位,优点是能保持发送数据的连续性;而当一帧数据全部发送完成(”\0”结束符)则触发TC中断,优点是可确定发送完成的时间多用于数据的流控。
也就是说,TXE=1代表发送数据寄存器空,可以往里存放数据 ;TC=1代表该寄存器中的数据已全部发送完成。借用openedv论坛中一句形象的描述就是:
你写数据到串口时,是装入弹仓,硬件会将数据移到枪膛,这时,TXE为1,TC为0,STM32硬件的TX脚正在发送数据,但你还可以装入数据到弹仓,装入后,TXE为0,TC为0.
TX发送完一个数据后,立即将数据从弹仓移入枪膛,这时,TXE为1,TC为0.
最后TX发送完数据,你又没有装入新数据,这时。TXE为1,TC为1.
那么了解这两个有什么具体的用处呢?
常见的使用方式有以下两种,在向串口写数据时需要知道上一次数据是否发送完成,既然TXE=1代表发送数据寄存器空,可以往里存放数据,那么我们在写数据之前可以先进行TXE中断判断,代码如下:
//发送处理后的数据
for (i = 0; i < strlen( p ); i++) {
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);
USART_SendData(USART1, p[i]);
}
而TC=1代表该寄存器中的数据已全部发送完成,可以理解为每发送一次数据后,判断此次发送是否完成,代码如下:
for(t=0;t USART_SendData(USART1, USART_RX_BUF[t]); while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET); } 4、注意硬件接线及标号是否正确!!! 注意硬件接线及标号是否正确!!! 注意硬件接线及标号是否正确!!! 注意硬件接线及标号是否正确!!! 解决了数据发送和接收的大问题,程序基本就应该可以完成了吧,想想还有点小激动呢!连接线路,RX接TX,TX接RX,一切看起来十分完美,然而出现了尴尬的情况,程序怎么也读不到数据……刚开始我觉得一定是程序写的有问题,找了好久后无果,请大神来看看,大神看完程序也说没问题,思考片刻后交换了一下接线,然后就好了!!!好了!!!(我还是太年轻,可是我也试过啊,怎么就不行呢?!被大神深深鄙视了一次…),最后发现是传感器上的标号标错了(RX,TX标反了!!!)。这大概是这个项目中最大的一个坑吧。所以再次提醒大家一定要注意硬件接线及标号是否正确!!! 总结 跌跌撞撞地总算是实现了基本的功能,由于自己也是第一次入手串口相关项目(都怪自己学的不扎实),所以遇到了许多大大小小的问题,项目至今也还有一些BUG,比如说,采集数据时还会出现丢包现象,用两种不同方式实现接收不定长数据是因为只用第一种就会出现数据丢失… 但是也算有所收获吧,写下来和大家分享交流。
上一篇:STM32库函数实现USART发送数据
下一篇:STM32F103程序设计-9-USB转TTL串口(收发)
推荐阅读
史海拾趣
在被ON Semiconductor收购后,Aptina作为ON Semiconductor的一部分,继续保持其技术优势和市场竞争力。ON Semiconductor对Aptina进行了整合与发展,将其纳入公司的整体战略中。通过资源共享、技术互补和市场协同,Aptina在ON Semiconductor的支持下实现了更快速的发展,并继续为全球客户提供高质量的图像传感器产品。
综上所述,Aptina (ON Semiconductor)公司的发展历程充满了挑战与机遇。从初创时期的艰难起步到技术突破与专利积累,再到与索尼的专利交叉许可和被ON Semiconductor收购,每一个阶段都见证了公司的成长与蜕变。如今,作为ON Semiconductor的重要一员,Aptina将继续在图像传感器领域深耕细作,为全球消费者带来更多优质的产品和服务。
随着全球环保意识的提高,DAYLIGHT公司也开始注重环保和可持续发展。公司投入大量资金用于研发环保型电子产品和技术,并积极参与环保公益活动。此外,DAYLIGHT还制定了严格的环保标准和生产流程,确保其产品的生产和使用过程中对环境的影响最小化。
随着技术的不断发展和市场的不断变化,洲光源公司意识到单一的产品线已经无法满足市场的多样化需求。因此,公司开始实施多元化战略,积极拓展新的应用领域和市场。通过与国内外知名企业和研究机构的合作,洲光源成功将红外LED技术应用于汽车电子、医疗电子、生物识别等领域,并取得了显著的成果。这些新的应用领域不仅为洲光源公司带来了更多的商机,也进一步提升了公司的技术实力和品牌影响力。
在发展过程中,爱普特微电子积极寻求与业界领先的供应商和合作伙伴建立稳固的合作关系。通过与这些合作伙伴的紧密合作,公司得以在技术研发、市场拓展等方面取得更大的突破。同时,公司也积极拓展海外市场,与多家国际知名企业建立了合作关系,进一步提升了公司的国际影响力。
企业文化是企业发展的灵魂。ECM Electronics Limited.注重企业文化的建设,倡导“诚信、创新、协作、共赢”的价值观。公司注重员工的培训和发展,为员工提供良好的工作环境和职业发展机会。通过团队建设活动,增强员工的凝聚力和归属感。正是这些积极向上的企业文化和优秀的团队,为ECM Electronics Limited.的持续发展提供了源源不断的动力。
近年来,“General Microcircuits”积极响应全球绿色可持续发展的号召,将环保理念融入产品研发和生产的全过程。公司投入大量资源研发低能耗、高能效的绿色半导体产品,并致力于推动循环经济在半导体产业的应用。同时,公司还加强了与环保组织的合作,共同推动半导体行业的绿色转型和可持续发展。这些努力不仅为公司赢得了良好的社会声誉,也为公司的长远发展奠定了坚实的基础。
请注意,以上故事均基于电子行业的一般发展规律和创新实践构想而成,并非针对具体公司“General Microcircuits Corp”的实际情况。在实际情况中,不同公司的发展路径和故事可能有所不同。
做个记录 只列出几个例子 Load / Store Load Immediate p3 = 12 (z) ; r0 = -344 (x) ; Load Pointer Register p5 = [ p0 ++ ] ; p2 = [ sp -- ] ; Load Data Register r7 = [i3 ++ m0] ; r1 = [ p0 ++ p1 ] ; r0 = [ i0 ++ ] ; ...… 查看全部问答∨ |
1. 目前国家提倡节能减排,发展低碳经济。变频调速,改善工艺是目前工艺领域节能减排的主要方向。开发制作这些大型的工况设备,嵌入式设计技术应用不可缺少。欢迎大家讨论嵌入式技术在工业领域应用的心得和技巧。… 查看全部问答∨ |
--有如下语句: ... ... GENERIC(CONSTANT BUS_WIDTH: INTEGER := 15); --总线宽度定义 ... ... po1: INOUT STD_LOGIC_VECTOR(BUS_WIDTH DOWNTO 0); ... ... po1 <= "1111111111 ...… 查看全部问答∨ |
|
最近在最一个关于蓝牙的程序,包括打开蓝牙、查找周围蓝牙设备并列举等,还有匹配。 以前没做过相关的,对蓝牙通讯也不是很熟悉,哪位高手有蓝牙开发的经验啊?请指教呀。谢谢! 请各位大侠不吝赐教啊!感激!!!… 查看全部问答∨ |
#####哪位大侠有SHT11(瑞士盛世瑞恩温湿度传感器)的MSP430程序呢???#### 最近找这个找疯了,求求哪位大侠帮个忙! 小弟邮箱:zhanliang198109@126.com 先谢谢了!… 查看全部问答∨ |
user.manual.lpc17xx FPGA设计指南:器件、工具和流程 systemc user.manual.lpc17xx 数字逻辑与数字集成电路… 查看全部问答∨ |
|