一。单片机通信的知识
1. 通信的两种方式
2. 串行通信的三种传输方式
半双工数据发送和接收数据不能同时传输,全双工发送和接收互不影响,数据传输可以同时进行。
3. 串行通信的通信方式
对于同步通信,除了一条数据线以外,还需要一条时钟线,用于传输同步时钟信号,数据的每个位传输都是随着时钟传输。
异步通信没有时钟信号,这就需要通信双方事先要进行约定,比如UART通信要事先约定好波特率,才能保证通信数据的正确。
4. 常见的串行通信接口
5. STM32的串口通信接口
PC机上的DB9接口电平为232电平,不能直接跟单片机上TTL信号连接,两者的电平不兼容。
7. UART异步通信方式的引脚
对于大容量的STM32最多有5个串口。
RXD: 数据输入引脚,接收数据。
TXD: 数据输出引脚,发送数据。
二。UART异步通信的特点
奇偶校验位:比如传输8位数据,其中有3个1,如果是偶校验,因为前面有3个1,就需要在奇偶校验位加一个1,使传输的1为偶数个,如果前面传输的数据有4个1,就在奇偶校验位补0。奇校验则相反,如果前面的数据中有四个1,就需要在奇校验位补一个1,是1成为奇数个,如果前面有奇数个1,就在奇偶校验位补0。
设置奇偶校验是为了提高数据传输的准确率。
三。UART框图
2. 发送和接收共用一个波特率发生器。
对于STM32F103R8T6只有3个USART,串口1,串口2,串口3.
串口1的时钟来源于PCLK2
串口2-串口4的时钟来源于PCLK1.
PCLKx进来后进入USARTDIV进行分频。分频值的大小由USART_BRR寄存器进行配置。
时钟可以进行整数的分频,还可以进行小数的分频,比如可以 /36,还可以 /36.5(前面只是举了一个例子,实际上小数必须是1/16的整数倍),分频后再除以16,产生的时钟进入发送器时钟或接收器时钟。
SR寄存器发送接收数据过程中的标志位。
CR1寄存器一部分为发送接收使能位,另外还有中断使能位,可以编写中断函数,在中断函数中判断是哪个中断发生了。
四。串口常用的寄存器
1. SR状态寄存器
TC:发送完成,数据发送完成后置1
2. DR寄存器
3. BRR寄存器
波特率寄存器
位4-位15,USARTDIV的整数部分
PCLK1用于串口2,3,4,5,为36M
PCLK2用于串口1,为72M
4. CR1寄存器
控制寄存器,主要有发送和接收使能,以及相关的中断使能
RE:接收使能
TE:发送使能
RXNEIE:接收缓冲区非空中断使能,在接收到完整的数据后如果在这里使能了中断,就可以产生中断。
六。 串口配置的一般步骤
中断的通道在stm32f10x.h中
USART1的中断通道为 USART1_IRQn。
中断文件的格式在启动文件 startup_stm32f10x_hd.s 中。
七。 串口几个重要函数
1.void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct); 串口初始化函数
作用:初始化串口的一些重要参数,包括波特率,奇偶校验位,停止位等
2.void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
作用:开启相应的串口的中断。
3.void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
作用:使能相应的串口
4.void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
作用:串口发送数据,往串口发送一个数据
5.uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
作用:串口接收数据
6.void USART_DeInit(USART_TypeDef* USARTx);
作用:串口复位
7.void USART1_IRQHandler(void) //串口1中断服务函数
{
}
通过以上几个函数就可以实现数据的发送和接收
中断状态的获取,复位相关的一些函数
1. FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
2. void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
3. ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
4. void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
八。串口配置的一般步骤
1) 串口时钟使能,GPIO 时钟使能,需要使能GPIOA和USART1的时钟
2) 串口复位
3) GPIO 端口模式设置
4) 串口参数初始化
5) 开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤)
6) 使能串口
7) 编写中断处理函数
1. 配置RXD,TXD引脚
发送TXD(PA9)设置为推挽复用输出
接收RXD(PA10)设置为浮空或带上拉输入
在misc.c文件中有中断分组的配置函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
例:串口初始化
void My_USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStru;
USART_InitTypeDef USART_InitStru;
NVIC_InitTypeDef NVIC_InitStru;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//USART1时钟来自PCLK2
GPIO_InitStru.GPIO_Mode= GPIO_Mode_AF_PP; //推挽复用输出
GPIO_InitStru.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStru.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStru);
GPIO_InitStru.GPIO_Mode= GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStru.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStru.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStru);
USART_InitStru.USART_BaudRate = 115200; //波特率为115200
USART_InitStru.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制 为无
USART_InitStru.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; //工作模式为发送和接收
USART_InitStru.USART_Parity = USART_Parity_No; //没有奇偶校验
USART_InitStru.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStru.USART_WordLength = USART_WordLength_8b; //字长为8(因为没有奇偶校验)
USART_Init(USART1,&USART_InitStru);
USART_Cmd(USART1,ENABLE); //使能串口1
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //设置串口中断的类型,这里选择接收缓冲区非空 产生中断
NVIC_InitStru.NVIC_IRQChannel = USART1_IRQn; //定义在哪个通道,在顶层文件stm32f10x.h文件中 定义
NVIC_InitStru.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStru.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级 0-3
NVIC_InitStru.NVIC_IRQChannelSubPriority = 1; //子优先级 0-3
NVIC_Init(&NVIC_InitStru); //设置中断抢占优先级和响应优先级
}
中断服务函数的格式在启动文件startup_stm32f10x_hd.s中定义
void USART1_IRQHandler(void)
{
u8 res;
if( USART_GetITStatus(USART1,USART_IT_RXNE)) //判断是否是接收到数据产生的中断
{
res = USART_ReceiveData(USART1); //接收数据
USART_SendData(USART1,res); //重新发送回这个数据
}
}
//初始化IO,串口1
//bound:波特率为入口参数
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
//使能USART1,GPIOA的时钟
USART_DeInit(USART1); //复位串口1
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率一般设置为9600;
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); //初始化串口
#if EN_USART1_RX //如果使能了接收
//注:在usart.h头文件中定义了
//#define EN_USART1_RX 1 //使能(1),禁止(0)串口1中断接收
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启接收中断,接收数据时产生中断
#endif
USART_Cmd(USART1, ENABLE); //使能串口
}
九。串口实验
串口信号是按照从低到高的顺序发送数据
例如发送数据0xAA(1010 1010)
1. 每一位的时长为8.60us,程序设定的波特率为115200,也就是每秒发送115200位,也就是每一位时长为8.68us,跟测量值基本一致。
2. 串口信号发送一开始先发送一位的低电平,然后是数据,发送顺序是从低到高,0101 0101,也就是数据0xAA。
发送数据0x6A (0110 1010),示波器波形
十。串口实验讲解
1. 在uart.h中定义了一个接收缓冲区
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern 的作用:告诉你这个变量是在外部定义的,在这里只是一个声明,其他文件如果包含了uart.h这个头文件后就可以使用这个变量。
在uart.c中定义了这个接收缓冲区
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
2.串口的初始化
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
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_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
3. 串口中断函数
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//判断上次是否接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//如果上次已经接收到了0x0d,但这次接收的数据不是0x0a,则接收 错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //如果上次没有接收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000; //判断这次接收的数据是不是0x0d,如果是的话,把 bit14位置1
else //如果这次接收的数据不是0x0d
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; //把数据存取接收缓冲区
USART_RX_STA++; //接收的数据个数+1
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新 开始接收
}
}
}
}
}
在中断服务函数中定了一个小的协议
接收到的数据储存在USART_RX_BUF的数组中,这个数组最多接收200个字节。
接收的数据必须以 0x0D 和 0x0A 为结尾。
每接收到一个数据都在USART_RX_STA中的bit10-0把数据的个数+1
每接收到一个数据都要判断是不是接收到了0x0D, 接收到0x0D时不会把数据个数+1,如果接收到了要把bit14设置为1,然后判断下一个数据是不是0x0A,如果是的话就把结束标识符bit15也设置为1.如果不是0x0A就要重新开始接收。
比如接收到的数据有50个,这50个数据存放在接收缓冲区数组中,在bit13-0中的数据为50,为数据长度,bit14为1,bit15为1.
不停的分析USART_RX_STA这个变量的最高位,如果最高位为1,表示已经完成了一次接收,知道接收到的数据有50个,然后把数据接收缓冲区中的前50个数据发送出去。也就是发送了什么数据就返回什么数据。同时在发送完以后把这些标志位全部清零,以便下一次接续接收数据。
while(1)
{
if(USART_RX_STA&0x8000) //判断最高位是不是1,是1的话表示接收完成
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n\r\n"); //打印一串数据到串口
for(t=0;t《len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0; // 处理完成后把USART_RX_STA清零,以便于下次接收
}else
{
times++;
if(timesP00==0)
{
printf("\r\n战舰STM32开发板 串口实验\r\n"); // '\r'是回车,'\n'是换行
printf("正点原子@ALIENTEK\r\n\r\n");
}
if(times 0==0)printf("请输入数据,以回车键结束\n");
if(times0==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
}
5. printf函数
发送数据到串口1,是因为在uart.c中定义了以下代码
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
对这部分代码不需要理解,如果把printf函数改为串口2,只需要重新定义fputc函数,里面改成串口2就可以了。
printf函数的格式
1 一般格式
printf(格式控制,输出表列)
例如:printf("i=%d,ch=%c\n",i,ch);
说明:
(1)“格式控制”是用双撇号括起来的字符串,也称“转换控制字符串”,它包括两种信息:
①格式说明:由“%”和格式字符组成,它的作用是将输出的数据转换为指定的格式输出。
②普通字符,即需要原样输出的字符。
(2)“输出表列”是需要输出的一些数据,可以是表达式
(3) printf函数的一般形式可以表示为
printf(参数1,参数2,……,参数n)
功能是将参数2~参数n按参数1给定的格式输出
2 格式字符(9种)
(1)d(或i)格式符。用来输出十进制整数,有以下几种用法:
①%d,按整型数据的实际长度输出。
②%md,m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出。
③%ld(%mld 也可),输出长整型数据。
例如:long a=123456;
printf("%ld",a);
(2)o格式符,以八进制数形式输出整数。格式:%o,%mo,%lo,%mlo都可。
(3)x(或X)格式符,以十六进制数形式输出整数。格式:%x,%mx,%lx,%mlx都可。
(4)u格式符,用来输出unsigned型数据,即无符号数,以十进制数形式输出。格式:%u,%mu,%lu都可。
参见:li4-3.c
(5)c格式符,用来输出一个字符。格式:%c,%mc都可。
(6)s格式符,用来输出一个字符串。格式:%s,%ms,%-ms,%m.ns,%-m.ns都可。
(7)f格式符,用来输出实数(包括单、双精度),以小数形式输出。格式:%f,%m.nf,%-m.nf都可。
注意:单精度实数的有效位数一般为7位,双精度为16位。
(8)e(或E)格式符,以指数形式输出实数。格式:%e,%m.ne,%-m.ne都可。
(9)g(或G)格式符,用来输出实数,它根据数值的大小,自动选f格式或e格式(选择输出时占宽度较小的一种)。
3 说明
(1)除了X、E、G(用大写字母表示)外,其他格式字符必须用小写字母;
(2)“格式控制”字符串内可以包含转义字符;
(3)如果想输出字符“%”,则应该在“格式控制”字符串中用连续两个%表示,如:
printf("%f%%",1.0/3);
(4)格式字符表参见下表
表1 printf格式字符
格式字符 | 说 明 |
d,i | 以带符号的十进制形式输出整数(正数不输出符号) |
o | 以八进制无符号形式输出整数(不输出前导符0) |
x,X | 以十六进制无符号形式输出整数(不输出前导符0x),用x则输出十六进制数的a~f时以小写形式输出,用X时,则以大写字母输出 |
u | 以无符号十进制形式输出整数 |
c | 以字符形式输出,只输出一个字符 |
s | 输出字符串 |
f | 以小数形式输出单、双精度数,隐含输出6位小数 |
e,E | 以指数形式输出实数 |
g,G | 选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0 |
表2 printf的附加格式说明字符
上一篇:11. GPIO原理与配置(跑马灯,蜂鸣器,按键)
下一篇:13. 外部中断实验
推荐阅读最新更新时间:2024-03-16 15:43
设计资源 培训 开发板 精华推荐
- 在校准中使用埋入式齐纳技术带来极高精度优势
- Manz亚智科技RDL制程打造CoPoS板级封装路线, 满足FOPLP/TGV应用于下一代AI需求
- AMD 官宣 CES 2025 发布会,将展示“游戏领域的下一代创新”
- 联发科技 Genio 130 Smart Plug 解决方案
- 智能手表扬声器的硅xMEMS微型扬声器超越了入耳式应用
- 联想YOGA Pad Pro搭载多颗汇顶芯片
- 意法半导体车规八通道栅极驱动引入专利技术,降低电机驱动设计的物料成本
- 打造 “CPU+” 异构计算平台,Arm 灵活应对各类 AI 工作负载
- 爱普生SG-8018CB可编程晶振在智能穿戴设备中的应用
- [S5PV210] 网络挂载文件系统
- 泰克WiFi预一致性测量方案介绍会 填问卷 赢好礼
- 有奖直播|安森美全新 ADAS 电源 IC 提升 ADAS 系统的清晰度及安全性
- ST MEMS传感器交流论坛正式上线啦!
- 速度与激情,为你私人订制。R&S携多功能基础示波器带你身临其境!
- 【EEWORLD大学堂】玩转LaunchPad及TI MSP430G2XX系列超值单片机!!!
- 我与BeagleBone 有个约会!
- 下资料赢京东卡|泰克“软硬”兼施 打造超值示波器
- 免费测评TI LAUNCHXL-CC2650
- EEWorld 芯积分兑换年度回馈来袭~多种赚积分捷径曝光+礼品兑换剧透
- 赢京东卡 | 场景寻宝,与英飞凌一起开启未来之家探索!