文章目录
一、串口通信原理
并行通信
- 数据同时传输
- 速度快,但占用引脚多
串行通信
- 数据按位传输
- 速度慢,但占用引脚少
按照数据传输方向,可分为:
单工:数据传输只支持数据在一个方向上传输。
半双工:允许数据在两个方向上传输,但是在某一时刻只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;
全双工:允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备接收设备都有独立的接收和发送能力。
通信方式
- 同步通信
- 异步通信
常见的串行通信接口
STM32的串口通信接口
- UART:通用异步收发器
- USART:通用同步异步收发器
UART异步通信方式特点
异步通信方式引脚:
- RXD:数据输入引脚:数据接受;
- TXD:数据发送引脚:数据发送。
关于波特率的计算:
串口通信过程
- 数据接收过程:外部设备按照一定的波特率将数据传送到串行输入移位寄存器,串行输入移位寄存器将所接收到的数据一次性传入到输入数据缓存器,之后再将数据传送到MCU。
- 数据发送过程:与数据接收过程相反。
串口异步通信需要定义的参数
常用寄存器和库函数
串口设置的一般步骤可以总结为如下几个步骤:
- 串口时钟使能,GPIO 时钟使能
- 串口复位
- GPIO 端口模式设置
- 串口参数初始化
- 开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤)
- 使能串口
- 编写中断处理函数
代码实现如下:
void MY_USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStrue;
USART_InitTypeDef USART_InitStrue;
NVIC_InitTypeDef NVIC_InitStrue;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口时钟使能
GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStrue);
GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_InitStrue.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStrue); //GPIO端口初始化
USART_InitStrue.USART_BaudRate=115200; //波特率设置
USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //设置收发模式
USART_InitStrue.USART_Parity=USART_Parity_No; //无奇偶校验位
USART_InitStrue.USART_StopBits=USART_StopBits_1; //停止位设置为1
USART_InitStrue.USART_WordLength=USART_WordLength_8b; // 字长设置为8位
USART_Init(USART1,&USART_InitStrue); //串口初始化
USART_Cmd(USART1,ENABLE);//使能串口
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启接收中断,开启中断服务函数,设置中断类型
NVIC_InitStrue.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE; //通道使能
NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级1
NVIC_InitStrue.NVIC_IRQChannelSubPriority=1; //子优先级1
NVIC_Init(&NVIC_InitStrue); //中断优先级初始化
}
void USART1_IRQHandler(void)函数
void USART1_IRQHandler(void)函数是串口 1 的中断响应函数,当串口 1 发生了相应的中断后,就会跳到该函数执行。中断相应函数的名字是不能随便定义的,一般我们都遵循MDK定义的函数名。这些函数名字在启动文startup_stm32f10x_hd.s文件中可以找到。
函数体里面通过函数:
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
判断是否接受中断,如果是串口接受中断,则读取串口接受到的数据:
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
读到数据后接下来就对数据进行分析。
通过这个函数 , 配合一个数组USART_RX_BUF[],一个接收状态寄存器 USART_RX_STA(此寄存器其实就是一个全局变量,由作者自行添加。由于它起到类似寄存器的功能,这里暂且称之为寄存器)实现对串口数据的接收管理。USART_RX_BUF 的大小由 USART_REC_LEN 定义,也就是一次接收的数据最大不能超过 USART_REC_LEN 个字节。USART_RX_STA 是一个接收状态寄存器其各的定义如下:
设计思路如下:
当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2 个字节组成:0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到 0X0D,那么在接收数据超过 USART_REC_LEN 的时候,则会丢弃前面的数据,重新接收。
printf函数
通过 printf 函数向串口发送我们需要的内容,方便开发过程中查看代码执行情况以及一些变量值。
以下是正点提供的一段代码:
int main(void)
{
u8 t;
u8 len;
u16 times=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
uart_init(9600); //串口初始化为9600
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nALIENTEK MiniSTM32开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
}
}
二、外部中断
外部中断相关知识:
STM32 的每个 IO 都可以作为外部中断的中断输入口,这点也是 STM32 的强大之处。STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103的 19 个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。
typedef struct
{
uint32_t EXTI_Line; //指定要配置的中短线
EXTIMode_TypeDef EXTI_Mode; //模式:事件OR中断
EXTITrigger_TypeDef EXTI_Trigger; //触发方式:上升沿,下降沿,双触发沿
FunctionalState EXTI_LineCmd; //使能OR中断
}EXTI_InitTypeDef;
使用 IO 口外部中断的一般步骤:
1)初始化 IO 口为输入。
2)开启 IO 口复用时钟AFIO,设置 IO 口与中断线的映射关系。
3)初始化线上中断,设置触发条件等。
4)配置中断分组(NVIC),并使能中断。
5)编写中断服务函数
6) 清除中断标志位
下面通过一段代码来展示以上步骤:
//外部中断初始化函数
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStrue;
NVIC_InitTypeDef NVIC_InitStrue;
KEY_Init();//初始化IO口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启IO口复用时钟,AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);//设置IO口与中断线的映射关系
EXTI_InitStrue.EXTI_Line=EXTI_Line5;//指定要配置的中断线
EXTI_InitStrue.EXTI_LineCmd=ENABLE;
EXTI_InitStrue.EXTI_Mode=EXTI_Mode_Interrupt;//设置中断触发模式
EXTI_InitStrue.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿
EXTI_Init(&EXTI_InitStrue);
NVIC_InitStrue.NVIC_IRQChannel=EXTI9_5_IRQn;//使能按键所在的外部中断通道
NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级2
NVIC_InitStrue.NVIC_IRQChannelSubPriority=2;//设置子优先级
NVIC_Init(&NVIC_InitStrue);
//KEY0中断服务函数初始化
}
void EXTI9_5_IRQHandler(void)
{
delay_ms(10);//消抖
if(KEY0==0)
{
LED0=!LED0;
// LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line5);//清除Line5上的中断标志位
}
三、使用keil 5编译过程中遇到的问题汇总
warning:#1-D:last line of file ends without a new line
一般情况下,写完代码之后,在程序末尾需要点击回车键才会消除警告;但有的时候明明已经按了回车键,但还是会报错。此时的解决办法是:将光标移至出错的程序末尾,按住delete键,直至确认后面没有空格,之后再点击回车,此时光标会移至下一行,要特别注意的是,光标一定要在空白行的最前端,然后警告才会消失。
Undefined symbol TIM_Int_Init (referred from main .o)
解决办法:
3.当project window找不到时,像这样:
可以采取下面的办法:
将view中的project window 选中即可。