完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
实验目的
通过本实验的学习,掌握STM32的串口使用,实现通过串口发送和接受数据。 实验简介 串口通信是一种设备很常用的串口通讯方式,串口按位(bit)发送和接受字节,尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据,大部分电子设备都支持该通讯设备,作为计算机与单片机交互数据的主要接口,广泛用于各类仪器仪表,工业检测以及自动控制领域,通信协议是需要通信的双方所达成的一种约定,它对包括数据格式,同步方式,传送速度,传送步骤,检纠错方式以及控制字符定义等问题作出统一的规定,通信双方都必须共同遵守。 串口通讯我们一般分为物理层和协议层,物理层规定通讯系统中具有机械,电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包,解包标准。 物理层:就是最底层的电平信号传输,大家熟知的RS232,RS485这些芯片,就是实现的物理层的电平转换,将单片机的TTL电平,转换成相应的接口电平。 星光枭龙STM32F103开发板,板载了一个USB转串口芯片,将TTL信号转成USB接口的差分信号,这也算是一种物理层芯片。 串口通信分为单工,半双工,全双工,取决于物理层的收发芯片: 单工:只能由一方A传到另一方B。 半双工:可以双向传输,但不能同时存在。 全双工:可以同时存在双向传输 协议层:串口传输一个字符的格式分为起始位,数据位,奇偶校验位,停止位和空闲位。 起始位:发出一个逻辑“0”信号,表示传输字符开始。 数据位:紧接在起始位之后,数据为的个数可以是5,6,7,8,一般采用8位,首先传送最低位。 奇偶校验位:奇偶校验是串口通信的一种简单纠错方式,数据位加上这一位后,使得1的位数是偶数(偶校验)或奇数(奇校验), 停止位:它是一个字符数据传输结束的标志位,可以是1位,1.5位,2位的高电平 空闲位:处于逻辑“1”状态,表示当前传输线路上没有数据。 电路设计 星光STM32F103开发板板载一个USB串口,芯片采用CH340G,接口采用MINI USB接口,连接STM32的USART1的PA9 PA10. 寄存器代码 usart.h #ifndef __USART_H #define __USART_H #include "sys.h" #include "stdio.h" #define USART_REC_LEN 200 //定义最大接收字数 #define EN_USART1_RX 1 //使能1 禁止 0 extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲最大为USART_REC_LEN个字节 extern u16 USART_RX_STA; //接收状态标记 void uart_init(u32 pclk2,u32 bound); //PCLK2时钟频率 波特率 #endif usart.c #include "usart.h" #if SYSTEM_SUPPORT_OS #include "includes.h" //ucos 使用 #endif #if 1 #pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; _sys_exit(int x) { x=x; } int fputc(int ch,FILE *f) //重定向fputc函数 //printf的输出,指向fputc,由fputc输出到串口 //这里使用串口1(USART1)输出printf信息 { while((USART1->SR&0x40) == 0); //等待上一次串口数据发送完成 USART1->DR = (u8)ch; //写DR,串口1将发送数据 return ch; } #endif #if EN_USART1_RX //如果使能了接收 u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲 u16 USART_RX_STA = 0; //接收状态标记 void USART1_IRQHandler(void) { u8 res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真 OSIntEnter(); #endif if(USART1->SR&(1<<5)) //接收到数据 { res = USART1->DR; //将接收到的数据存在res中 if((USART_RX_STA&0x8000)==0) //接收未完成 { if(USART_RX_STA&0x4000) //接收到了0x0d,换行符0x0d+0x0a { if(res != 0x0a) USART_RX_STA = 0; //接收错误,重新开始 else USART_RX_STA |= 0x8000; //接收完成了 } else//还没收到0x0d; { if(res == 0x0d) //如果收到0x0d USART_RX_STA |=0x400; else { USART_RX_BUF[USART_RX_STA&0x3FFF] = res; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1)) USART_RX_STA = 0; } } } } #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS 为真,则需要支持OS OSIntExit(); #endif } #endif void uart_init(u32 pclk2,u32 bound) //初始化IO (PCLK2时钟频率,波特率) { float temp; u16 mantissa; u16 fraction; temp = (float)(pclk2*1000000)/(bound*16); //得到USARTDIV mantissa = temp; //得到整数部分 fraction = (temp-mantissa)*16; //得到小数部分 mantissa<<=4; mantissa += fraction; RCC->APB2ENR |= 1<<2; //使能PORTA口时钟 RCC->APB2ENR |= 1<<14; //使能串口时钟 GPIOA->CRH &= 0xFFFFF00F; GPIOA->CRH |= 0x000008B0; //PA17 18 IO状态设置,一接收,一发送 RCC->APB2RSTR |= 1<<14; //复位串口1 RCC->APB2RSTR &= ~(1<<14); //停止复位 USART1->BRR = mantissa; //波特率设计 USART1->CR1 = 0x200C; //1停止位,无校验位,接收发送使 #if EN_USART1_RX //如果使能了接收 USART1->CR1 |= 1<<5; //接收缓冲区非空中断使能 MY_NVIC_Init(3,3,USART1_IRQn,2); //中断初始化 #endif } HAL库代码 main.c #include "MyIncludes.h" #define RECV_LENGTH 100 //接受缓冲长度 #define SEND_LENGTH 100 //发送缓冲长度 u8 test_txbuf[100]; //发送数组 u8 test_rxbuf[100]; //接收数组 u8 tx_index = 0; u8 send_len; //发送长度 u8 recv_len; //接受长度 u8 recv_timeout = 0; //接受超时 u8 recv_complete = 0; //接受完成 u16 sys_cnt = 0; void systick_isr(void) { if(sys_cnt <100 ) sys_cnt++; else { sys_cnt = 0; HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_4|GPIO_PIN_5); } if( recv_timeout <30 ) recv_timeout++; if(recv_timeout ==20 ) { recv_complete = 1; } //systick定时器的作用: //PC4 PC5灯亮,并让接受超时recv_timeout //运行,如果超过20 则说明发送完成 } u8 rx_index; void uart_rx(unsigned char RxData) //作用将接收的数据存放在text_rxbuf[] //函数中, { if( rx_index < RECV_LENGTH ) { test_rxbuf[rx_index++] = RxData; } recv_timeout = 0; //接收超时计数器清零 } void Uart_Fill_Tx(u8 *buf,u8 len) //发送数据函数 { u8 i; send_len = len; if(len > sizeof(test_txbuf)) send_len = sizeof(test_txbuf); //这两行是为了确保长度要小于100 //也就是上面的 for( i=0; i test_txbuf = *buf++; } //将接收到的赋值于要发送的数组中 tx_index = 0; //为下次做准备 USART1_Send(test_txbuf,send_len); //发送函数,两个参数,发送数组(指针) //和发送长度 } void Uart_RecvProcess(void) //接收数据函数 { if(recv_complete == 1) //接收完成等于1 接收完成 { recv_complete = 0; //赋值为零为下次做准备 recv_len = rx_index; //得到接收数组的长度 rx_index = 0; Uart_Fill_Tx(test_rxbuf,recv_len); //发送函数,两个参数 ,接收数组,以及 //接收数组的长度 } } int main(void) { System_Init(); LED_Init(); SysTick_Init(systick_isr); recv_timeout = 150; //初始化超时值 USART1_Init(115200,NULL,uart_rx); //串口1初始化 3个参数 分别是波特率115200 //发送完成回调为空 和接受回调uart_rx; while(1) { Uart_RecvProcess(); //接受处理函数 Key_Read(); if(Key_Info.Num_Last != Key_Info.Num) { Key_Info.Num_Last = Key_Info.Num; if(Key_Info.Num != 0) { switch(Key_Info.Num) { case KEY_ONE: printf("STM32F103 USART1 TEST!!!rn"); //这个迷茫了一下,这里应该是单片机发送数据 break; default:break; } } } } } /*因为本人水平有限,所以刚看的时候有点懵,刚开 始看代买的时候,如果水平高怎么看都可以,如果感 觉到很懵,我建议就是从main()函数中看起,就像 这个main.c,我们从main()开始看:首先是系统初 始化,LED初始化,然后滴答定时器函数,我们看滴答 定时器:发现作用就是短时间内反转PC4 PC5灯,然后 如果接收超时大于等于30没改变,小于30 每运行一次 接收超时加一,等于20的时候就说明不再发送那么接收完成变为1 , 我目前觉得作用就是让接受超时recv_timeout运行,在20以内, 就是还在发送,否则发送完成,然后main()函数的下一行代码, 初始化超时值150,在这里150大于30,如果没有运行串口通信 150就不会归零,也就是systick定时器里的recv_timeout 就不会运行然后下一个函数USART1_Init(115200,NULL,uart_rx); 串口通信初始化,我们先不用了解里面是怎么样运行的 主要了解函数内里这三个参数 波特率 115200 ,发送 回调函数 但是为空,也就是不用管这个,以及函数指针 uart_rx()函数,然后我们看这个函数,这个函数的作用 是将接收到的RECV_LENGTH(100)以内的数据存在test_rxbuf【】 数组中,并记录个数rx_index++,因为每次接受到了数据, 所以要将超时计数器清零,然后Uart_RecvProcess();这个函数 如果接收完成,为下次做准备,并将接收到的数据发送出去 也就是Uart_Fill_Tx(test_rxbuf,recv_len)这个函数,发送的 数据在sizeof(test_txbuf)以内,test_txbuf[100]也就是100个 数据,然后将接收到的数据赋值于发送数组中,然后发送 USART1_Send(test_txbuf,send_len)*/ Usart.h #ifndef __USART_H_ #define __USART_H_ #include "stm32f1xx.h" #include "stm32_types.h" #include "stm32f1xx_hal.h" #define UART_LSR_RDR (1<<5) //接收逻辑右移 100000(32) #define UART_LSR_THRE (1<<6) //发送逻辑右移 1000000(64) #define SEND_BUF1 (USART1->DR) // 发送缓冲区 串口1的数据寄存器 #define RECV_BUF1 (USART1->DR) // 接收缓冲区 串口1的数据寄存器 #define USART1_SR (USART1->SR) // 串口1的状态寄存器 #define USART1_TX_ISR_ENABLE() (USART1->CR1 |=(1<<6)) // 串口1 发送 中断 使能 1000000(64) #define USART1_TX_ISR_DISABLE() (USART1->CR1 &=~(1<<6)) // 串口1 发送 中断 禁能 100000 (64) typedef struct { void (*U1TXOver)(void); //发送完成回调函数指针 void (*U1RXOperation)(unsigned char RxData); //接收回调函数指针 }_USART_ISR_; extern _USART_ISR_ Usart_ISR; void USART1_Init(unsigned int BPS,void (*TXFunction)(void),void(*RxFunction)(unsigned char RX_Data) ); //串口初始化函数 void USART1_Send(uint8_t *buff,uint16_t Size); //串口发送函数 #endif /*Usart.h文件作为一个小白,感觉很多专有名词不懂,知道了就好。*/ Usart.c #include "Usart.h" _USART_ISR_ Usart_ISR; UART_HandleTypeDef Uart1_Handle; //结构体声明 void USART1_IRQHandker(void) //串口中断服务函数 { unsigned long temp; temp = USART1_SR; if(temp&UART_LSR_RDR) //接收到数据 { if(Usart_ISR.U1RXOperation != NULL) Usart_ISR.U1RXOperation(RECV_BUF1); } HAL_UART_IRQHandler(&Uart1_Handle); //使能中断,选定中断源 } void USART1_Init(unsigned int BPS,void Tx_Over(void),void(*RxFunction)(unsigned char RX_Data)) //串口初始化 参数:波特率 发送回调函数 接收回调函数 { GPIO_InitTypeDef GPIO_InitStruct; //结构体声明 Usart_ISR.U1TXOver = Tx_Over; //main.c中为NULL Usart_ISR.U1RXOperation = RxFunction; //main.c中的uart_rx()函数 __GPIOA_CLK_ENABLE(); //使能GPIOA时钟 __USART1_CLK_ENABLE(); //使能USART1时钟 //USART1端口配置 GPIO_InitStruct.Pin = GPIO_PIN_9; //USART1对应的端口为PA9 TX GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; //复用开漏输出模式 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //高速 GPIO_InitStruct.Pull = GPIO_PULLUP; //上拉 HAL_GPIO_Init(GPIOA,&GPIO_InitStruct); //初始化PA9 PA10; GPIO_InitStruct.Pin = GPIO_PIN_10; //UASRT1对应的IO口PA10 RX GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT; //配置为复用开漏输入 HAL_GPIO_Init(GPIOA,&GPIO_InitStruct); //初始化PA10 Uart1_Handle.Instance = USART1; //串口1 Uart1_Handle.Init.BaudRate = BPS; //波特率设置 Uart1_Handle.Init.WordLength = UART_WORDLENGTH_8B; //字节长8位 Uart1_Handle.Init.StopBits = UART_STOPBITS_1; //1个停止位l Uart1_Handle.Init.Parity = UART_PARITY_NONE; //无校验 Uart1_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流 Uart1_Handle.Init.Mode = UART_MODE_TX_RX; //串口发送接收 Uart1_Handle.Init.OverSampling = UART_OVERSAMPLING_16; //16倍过采样 HAL_UART_Init(&Uart1_Handle); //初始化串口 __HAL_UART_ENABLE_IT(&Uart1_Handle,UART_IT_RXNE); //使能接收中断 HAL_NVIC_SetPriority(USART1_IRQn,0,1); //设置中断优先级 HAL_NVIC_EnableIRQ(USART1_IRQn); //使能中断 } void USART1_Send(uint8_t *buff,uint16_t Size) //串口发送 { HAL_UART_Transmit_IT(&Uart1_Handle,(uint8_t*)buff,Size); } int fputc(int ch,FILE *f) //重定义fputc函数,重定向后可使用printf函数 { while((USART1_SR&UART_LSR_THRE) == 0); //等待发送完成 SEND_BUF1 = (u8) ch; return ch; } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1758 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1613 浏览 1 评论
1055 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
721 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1670 浏览 2 评论
1932浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
722浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
563浏览 3评论
590浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
548浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-20 05:54 , Processed in 0.660935 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号