赞
踩
1.设备上电后四个灯灭。
2.按下KEY1,LED1灯亮,同时串口发送“LED1灯亮”。
3.再次按下KEY1,LED1灯灭,同时串口发送“LED1灯灭”。
4.以此类推,设置KEY2,KEY3,KEY4以及对应的LED灯。
在linux和C语言的学习中,每当编译完程序时代码编译没错,但功能实现却出现错误时,我们通常会使用printf来打印一下,观察程序到底是运行到哪里出错了。
在STM32中,由于现阶段还没有学习屏幕显示,当你在程序中直接添加printf时,我们无法观察到具体现象。此时,学会如何通过串口来调试就显得及其重要了。
首先,打开原理图找到USART1(串口)的位置
打开数据手册,找到系统结构图,查看控制串口和引脚的时钟在那个总线。
由上图中可知,控制USART1和GPIOA的时钟均在APB2时钟总线上。
查看手册中APB2外设时钟使能寄存器是如何控制的:
由上图可知,控制USART1和GPIOA的时钟是由APB2外设时钟使能寄存器的第14位和第2位控制的。该模块均为1使能0关闭。
在代码中为:
//开时钟:GPIOA,USART1
RCC->APB2ENR |= 0x01<<2;//GPIOA
RCC->APB2ENR |= 0x01<<14;//USART1
由于配置对应的IO口 PA9(TX)和 PA10(RX)都是高八位,所以直接打开手册找到端口配置高寄存器。
模式的确定是由手册中8.1.11 外设的GPIO配置确定的
通信方式:
串行:串行数据传输时,数据是一位一位地在通信线上传输的。
并行:并行通信传输中有多个数据位,同时在两个设备之间传输。
单工:设备只能发送,或者只能接收 只有一种功能,比如收音机。
双工:设备能够发送,也能够接收。
全双工:发送和接收可以独立工作,可以同时进行收发,互不影响。
半双工:发送的时候不能接收,接收的时候不能发送 比如对讲机。
同步:设备之间使用同一个时钟线。
异步:设备有自己的时钟,但是通信时时钟要设置成一样。
时钟作用:决定了通信的速率。
通常情况下我们使用的串口都是串行全双工异步通信接口。
所以此时我们要将PA9(TX)配置为复用推挽(1011),PA10(RX)配置成浮空输入(0100)
代码如下:
//配置对应的IO口
GPIOA->CRH &= ~(0x0f<<4);//PA9清0
GPIOA->CRH |= 0x0B<<4;//设置成复用推挽输出
GPIOA->CRH &= ~(0x0f<<8);//PA10清0
GPIOA->CRH |= 0x04<<8;//设置成浮空
串口1配置时需要设置数据位、校验位、停止位和波特率。
本次配置为8数据位,0校验位,1停止位,115200波特率
在手册中找到串口的控制寄存器CR1和CR2
在CR1中可以看到数据位在12位,校验位在第10位,发送使能在第3位,串口使能在第13位。
要注意的是发送使能和串口使能要最后再配制,防止没配置好串口就开始工作了。
在CR2中可以看到停止位在13和12位,此时全配1即可。
代码如下:
USART1->CR1 &= ~(0x01<<12);//数据位置0
USART1->CR1 &= ~(0x01<<10);//设置0位校验位
USART1->CR2 &= ~(0x03<<12);//设置1个停止位
接下来就要配置波特率了
波特率的配置是由波特比率寄存器决定的
DIV_Mantissa是USARTDIV的整数部分
DIV_Fraction[3:0]:USARTDIV的小数部分
计算方式为:
72M/usartdiv/16=115200
usartdiv = 72M/16/115200 = 39.0625
39.0625 = DIV_Mantissa + (DIV_Fraction/16)
DIV_Mantissa(整数部分) = 39
DIV_Fraction(小数部分) = 0.0625*16 = 1;
其中72M是由所在的时钟总线APB2决定的。
APB2总线默认时钟频率与系统时钟频率保持一致是72MHz。
此时,完整的串口1配置代码为:
//配置串口1 8数据位,0校验位,1停止位,波特率115200
USART1->CR1 &= ~(0x01<<12);//数据位置0
USART1->CR1 &= ~(0x01<<10);//设置0位校验位
USART1->CR2 &= ~(0x03<<12);//设置1个停止位
USART1->BRR = (39<<4)+1;//设置波特率
USART1->CR1 |= 0x01<<3;//发送使能
USART1->CR1 |= 0x01<<13;//使能串口1
此时的串口就完全配置好了,可以直接通过串口的数据寄存器发送一个8位的数据。
USART1->DR = 'A';
配置完串口后,此时想要使用printf函数进行调试,还是不行的。
我们要将printf函数进行重定向,让打印的数据通过串口输出出来。
printf函数的重定向在官方给的标准库中是存在的,只需打开官方例程即可找到。
找到后复制过来即可:
int fputc(int ch, FILE *f)
{
//printf函数最终会跳转到这里来运行
while((USART1->SR&0x1<<6)==0);
//发送数据
USART1->DR = (uint8_t)ch;
return ch;
}
复制完后记得在头文件中声明一下。
完成以上步骤就可实现printf串口调试功能了。
需求均已实现,完整代码如下:
main.c
#include "stm32f10x.h" #include "led.h" #include "key.h" #include "delay.h" #include "usart.h" #include "stdio.h" int a,b,c,d;//LED灯的标志位 int main() { Led_Init(); key_Init(); Beep_Init(); Usart1_Config(); while(1) { if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1)//key1 { Delay_nms(10); if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1) { while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1) {} Led_Toggle(1); a++; if(a==2) { printf("LED1灯灭\r\n"); a=0; } else { printf("LED1灯亮\r\n"); } } } if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)==0)//key2 { Delay_nms(10); if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)==0) { while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4)==0) {} Led_Toggle(2); b++; if(b==2) { printf("LED2灯灭\r\n"); b=0; } else { printf("LED2灯亮\r\n"); } } } if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0)//key3 { Delay_nms(10); if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0) { while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==0) {} Led_Toggle(3); c++; if(c==2) { printf("LED3灯灭\r\n"); c=0; } else { printf("LED3灯亮\r\n"); } } } if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)==0)//key4 { Delay_nms(10); if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)==0) { while(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)==0) {} Led_Toggle(4); d++; if(d==2) { printf("LED4灯灭\r\n"); d=0; } else { printf("LED4灯亮\r\n"); } } } } }
usart.c
#include "usart.h" #include "stdio.h" void Usart1_Config() { //开时钟:GPIOA,USART1 RCC->APB2ENR |= 0x01<<2;//GPIOA RCC->APB2ENR |= 0x01<<14;//USART1 //配置对应的IO口 PA9(tx):复用推挽 PA10(RX):浮空输入 GPIOA->CRH &= ~(0x0f<<4);//PA9清0 GPIOA->CRH |= 0x0B<<4;//设置成复用推挽输出 GPIOA->CRH &= ~(0x0f<<8);//PA10清0 GPIOA->CRH |= 0x04<<8;//设置成浮空 //配置串口1 8数据位,0校验位,1停止位,波特率115200 USART1->CR1 &= ~(0x01<<12);//数据位置0 USART1->CR1 &= ~(0x01<<10);//设置0位校验位 USART1->CR2 &= ~(0x03<<12);//设置1个停止位 /* 72M/usartdiv/16=115200 usartdiv = 72M/16/115200 = 39.0625 39.0625 = DIV_Mantissa + (DIV_Fraction/16) DIV_Mantissa(整数部分) = 39 DIV_Fraction(小数部分) = 0.0625*16 = 1; 0000 0010 0111 0001 */ USART1->BRR = (39<<4)+1;//设置波特率 USART1->CR1 |= 0x01<<3;//使能串口发送 USART1->CR1 |= 0x01<<13;//使能串口1 } void SendData(uint8_t data) { while((USART1->SR&0x01<<6)==0){}//等待上次发送完成 USART1->DR = data;//发送数据 } int fputc(int ch, FILE *f) { //printf函数最终会跳转到这里来运行 while((USART1->SR&0x1<<6)==0); //发送数据 USART1->DR = (uint8_t)ch; return ch; }
usart.h
#ifndef _USART_H_
#define _USART_H_
#include "stm32f10x.h"
#include "stdio.h"
void Usart1_Config();
void SendData(uint8_t data);
int fputc(int ch, FILE *f);
#endif
key.c
#include "stm32f10x.h" void key_Init() { //开时钟 RCC->APB2ENR |= 0x01<<4;//PC RCC->APB2ENR |= 0x01<<2;//PA //配置模式 GPIOC->CRL &=~(0X0F << 24);//PC6 key4 GPIOC->CRL |= 0X04 << 24; GPIOC->CRL &=~(0X0F << 20);//PC5 key3 GPIOC->CRL |= 0X04 << 20; GPIOC->CRL &=~(0X0F << 16);//PC4 key2 GPIOC->CRL |= 0X04 << 16; GPIOA->CRL &=~0X0F;//PA0 key1 GPIOA->CRL |= 0X04; } int Get_Key_Val(void) { int key_val = 0; if(!!(GPIOA->IDR &(0X01 << 0))==1) key_val = 1; if(!!(GPIOC->IDR &(0X01 << 4))==0) key_val = 2; if(!!(GPIOC->IDR &(0X01 << 5))==0) key_val = 3; if(!!(GPIOC->IDR &(0X01 << 6))==0) key_val = 4; return key_val; }
key.h
#ifndef _KEY_H_
#define _KEY_H_
void key_Init();
int Get_Key_Val(void);
#endif
led.c
#include "stm32f10x.h" void Led_Init() { //配置好模式,然后全灭 //开APB2时钟 RCC->APB2ENR |= 0X01 << 6; //配置PE2--PE5为通用推挽输出 GPIOE->CRL &=~(0X0F << 20);//PE5 GPIOE->CRL |= 0X03 << 20; GPIOE->CRL &=~(0X0F << 16);//PE4 GPIOE->CRL |= 0X03 << 16; GPIOE->CRL &=~(0X0F << 12);//PE3 GPIOE->CRL |= 0X03 << 12; GPIOE->CRL &=~(0X0F << 8);//PE2 GPIOE->CRL |= 0X03 << 8; //4个引脚均输出高电平 GPIOE->ODR |= (0x0F << 2); } //开关灯 void Led1_Ctrl(int flag) { if(!!flag) { GPIOE->ODR &= ~(0x0F << 2); } else { GPIOE->ODR |= (0x0F << 2); } } void Led_Toggle(int flag) { GPIOE->ODR ^= 0x01<<(flag+1); }
led.h
#ifndef _LED_H_
#define _LED_H_
void Led_Init();
void Led1_Ctrl(int flag);
void Led_Toggle(int flag);
#endif
delay.c
#include "stm32f10x.h" #include "delay.h" void Delay_nus(uint32_t time) { uint32_t i=0; for(i=0;i<time;i++){ delay1us(); } } void Delay_nms(uint32_t time) { uint32_t i=0; for(i=0;i<time;i++){ Delay_nus(1000);//延时1ms } }
delay.h
#ifndef _DELAY_H_ #define _DELAY_H_ #include "stm32f10x.h" #define delay1us() {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\ __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();} void Delay_nus(uint32_t time); void Delay_nms(uint32_t time); #endif
1.了解多种通信方式和作用。
2.学会了如何在固件库中查找以及使用官方例程。
3.学会了USART1的配置及使用,以后就可以对程序直接进行printf调试了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。