因为之前开设过嵌入式想过课程所以对stm32了解比较多,但是感觉学的东西停留在纸面上,所以跟着江科大一起边学边做,学了一半突然起兴感觉自己可以做一个小车了,然后就开始着手设计。主要很多模块的调试和设计花比较长的时间。写这篇文章的目的是简单记录一下我购买的小车设计,代码编写的过程。因为我买的是一款配置非常简单的小车,所以我感觉这个项目的主要难点之一就是怎么利用现有的材料来设计小车。当然项目还有很多地方自己理解不周,自己也是第一次接触,分享一下自己的学习经历。
主要技术:手机连接蓝牙使用PWM波控制直流电机和舵机
(一)材料准备
MG90S舵机
直流电机
TB6612FNG电机驱动模块
HC-05蓝牙模块
USB转TTL转换器
STM32
面包板
杜邦线若干
热熔枪(或者502)
小车框架(我再淘宝上购买的13.9¥)
商家链接: link
(二)线路连接
使用stlink调试,
STLink
stlink | 接口 |
---|---|
SWCLK | stm32SWCLK |
SWDIO | stm32SWIO |
GND | stm32gnd |
3.3V | stm32vcc |
5v | 舵机模块 |
5v | 电机驱动模块 |
舵机
舵机 | 接口 |
---|---|
红线 | STLINK的5V |
棕线 | GND |
橙线 | PA6 |
TB6612FNG电机驱动模块
电机驱动 | 接口 |
---|---|
PWMA | PA2 |
AIN2 | PA3 |
AIN1 | PA4 |
STBY | GND |
VM | STLINK的5V |
VCC | 3.3V |
GND | GND |
AO1 | 直流电机任意一根引线 |
AO2 | 直流电机任意一根引线 |
蓝牙模块
蓝牙 | 接口 |
---|---|
RXD | PA9 |
TXD | PA10 |
GND | GND |
VCC | 3.3V |
直流电机
参照TB6612FNG电机驱动模块
调试模块(1):旋转编码器
旋转编码器 | 接口 |
---|---|
A | PA0 |
B | PA1 |
GND | GND |
VCC | 3.3V |
调试模块(2):OLED
OLED | 接口 |
---|---|
SCL | PB8 |
SDA | PB9 |
GND | GND |
VCC | 3.3V |
总结一下:我在开发过程一直借助面包板和STlink,目前我还没实现有让小车脱离stlink运行,但是我感觉难度应该也不大。程序烧入stm32后,只需要在面包板上加入一个电池模块,为面包板供3.3v电压和5v电压,然后使用面包板给stm32、舵机和电机驱动模块供电,再将面包板放在小车上即可实现独立运行。
注意事项:在开发过程中踩了很多坑。
(1)面包板上下两个电源和接地的槽的中间处可能是断路!!
(2)stm32可能部分引脚损坏无法正常输入输出!!
(3)杜邦线连接一定要准确,很多错误可能都是杜邦线没查好或者错插导致的
我的一些解决策略
(1)使用LED小灯来检测电路(物理Debug)
(2)使用了上述的两个调试模块,边做边调试
(三)小车拼装
(1)买回来是这样的,该商家卖小车是只需要按上电源,没有控制系统,小车只能直着跑,非常简陋
(2)我在尝试使用皮筋传输动力时发,发现自带的直流电机输出的扭矩不能驱动旋转。所以我选择了直接使用齿轮带动旋转的方式,如图所示
在后轮车轴上串上一个大齿轮,直流电机上加上一个小齿轮,齿轮大小正好匹配可以咬合!
注意车轴架安装不要太靠后
注意要使用黄色的圈圈固定车轴位置*
(3)转向系统的设计。
在我购置的这套小车里,有两个直流电机,四个固定车轴的三角形固定架,所以资源非常有限。查阅资料发现网上大多数小车的转向系统一般都采用对称车轮不同的旋转方向达到小车转向的功能,但是一方面采用了一体化车轴无法实现对称车轴的轮子旋转方向不同,另一方面我觉得那种转弯方式不够帅。
但是如何设计可以转向的小车呢?而且用于固定车轴的三角形固定架只有四个,在固定后车轮时已经使用了两个。如果使用两个固定的固定架那么前车轮就无法转向,所以只能固定一个,另一个前后移动配合实现车轴转动的效果!恰巧我手边有多余的舵机,问题又来了?舵机输出的的半圆形的力,而我需要前后的移动,且输出的力有较好的定位效果(如何不能很好的定位那么小车无法实现平稳运行,就连最基本的直线行驶可能都做不到)保证方向的准确和可持续!最开始我尝试直接使用热熔胶将舵机和三角形固定架粘在一起,不过在测试中发现舵机转动的时候,粘合处运动发现和距离刚性过大,直接将热熔胶扯断,借此灵感我想到使用弹性物体进行固定!且购买的小车套件中多了两个短的铁杆,配合上三角形固定架和黄色圆圈的定位装置可以保证车轴在同一水平高度,同时极大扩展的该三角形固定架的受力的方和角度,终于我的天才方案诞生了。
正面设计图
背面设计图
此处使用LED灯的引脚铁丝的作用是将皮筋固定在舵机摇杆上
使用热熔胶固定舵机
注意调整舵机的位置,保证舵机摇杆朝下时是舵机大概90°的位置
led灯在项目开发过程中起到了太大太大的作用
自此小车的拼装和设计过程就完成了!接下来就代码开发部分
(四)代码开发
代码的框架采用的是江科大!系统文件部分就不多赘述,和江科大一致,且省去所有文件的头文件
主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "LightSensor.h"
#include "OLED.h"
#include "CountSensor.h"
//#include "Timer.h"
#include "PWM.h"
#include "Servo.h"
#include "Motor.h"
#include "Serial.h"
#include "HC05.h"
#include "PWM_servo.h"
uint8_t RxSTA = 1;
char RxData[100] = "None";
int8_t Num;
uint16_t ANGLE;
int main(void)
{
//GPIO_SetBits(GPIOC,GPIO_Pin_10);
//GPIO_ResetBits(GPIOC,GPIO_Pin_0);
//GPIO_WriteBit()
//GPIO_Write()
//LED_init();
//KEY_Init();
OLED_Init();
CountSensor_Init();
//PWM_Init();
Servo_Init();
//MOTOR_INIT();
Motor_Init();
Serial_Init();
PWM_servo_Init();
//PWM_servo_SetCompare3(1250);
//GPIO_SetBits(GPIOA,GPIO_Pin_6);
//GPIO_ResetBits(GPIOA,GPIO_Pin_9);
//Timer_Init();
HC05_Init();
OLED_ShowString(1,1,"Designed By Bi");
//OLED_ShowString(2,1,"202178040202");
OLED_ShowString(2,1,"Count:");
OLED_ShowString(3,1,"direction:");
OLED_ShowString(4,1,"turn:");
//PWM_SetCompare4(ANGLE);
//Motor_SetSpeed(0);
//Motor_SetSpeed(70);
while(1)
{
//HC05_GetData(RxData);
char x;
x = Serial_GetRxData();
//Motor_SetSpeed(0);
HC05_GetData(RxData);
Num += COUNT_GET(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
OLED_ShowSignedNum(2,7, Num, 5);
//OLED_ShowChar(3,8,x);
while(Serial_GetRxFlag())
{
if(x == 'w')
{
Motor_SetSpeed(40);
OLED_ShowString(3,11,"go");
}
else if(x == 's')
{
Motor_SetSpeed(-40);
OLED_ShowString(3,11,"back");
}
else if(x == 'r')
{
PWM_servo_SetCompare3(1000);
OLED_ShowString(4,6,"right");
//Delay_s(1);
//PWM_servo_SetCompare3(1200);
}
else if(x == 'l')
{
PWM_servo_SetCompare3(1300);
OLED_ShowString(4,6,"left");
//Delay_s(1);
//PWM_servo_SetCompare3(1200);
}
else if(x == 'p')
{
Motor_SetSpeed(0);
PWM_servo_SetCompare3(1200);
OLED_ShowString(3,11,"stop");
}
}
// if(RxSTA == 0)
// {
// OLED_ShowString(3,8,RxData);
// RxSTA = 1;
// if(RxData[1] == 'w')
// {
// Motor_SetSpeed(90);
// OLED_ShowSignedNum(4,7,50,5);
// }
// if(RxData[1] == 's')
// {
// Motor_SetSpeed(-90);
// OLED_ShowSignedNum(4,7,-50,5);
// }
// else
// {
// Motor_SetSpeed(0);
// OLED_ShowSignedNum(4,7,0,5);
// }
// }
// OLED_ShowSignedNum(4,7,Num *5,5);
// Motor_SetSpeed(Num * 5);
// ANGLE = Num * 50 + 1200;
// OLED_ShowSignedNum(4,6,ANGLE,5);
// PWM_servo_SetCompare3(ANGLE);
// PWM_servo_SetCompare3(ANGLE);
// PWM_SetCompare4(ANGLE);
// Servo_SetAngle(ANGLE);
// for(ANGLE = 1;ANGLE<=180;ANGLE++)
// {
//PWM_SetCompare4(ANGLE);
//}
// PWM_SetCompare1(Num * 10);
// for (i = 0; i <= 100; i++)
// {
// PWM_SET(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
// Delay_ms(10); //延时10ms
// }
// for (i = 0; i <= 100; i++)
// {
// PWM_SET(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
// Delay_ms(10); //延时10ms
// }
// NUM += Encoder_Get();
//
//
// flag = Get_KeyNum();//每次while更新一次
// if(flag == 1)
// {
// LED0_ON();
// Delay_ms(100);
// LED0_OFF();
// LED1_ON();
// Delay_ms(100);
// LED1_OFF();
// }
// else
// {
// LED0_OFF();
// LED1_OFF();
// }
// if(GetLightData() == 1)
// {
// LED2_ON();
// }
// else
// {
// LED2_OFF();
// }
}
}
舵机驱动模块
舵机PWM波生成与控制模块
#include "stm32f10x.h" // Device header
void PWM_servo_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM3); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period =20000-1 ;// //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 -1; // //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
//TIM_OC4Init(TIM2, &TIM_OCInitStructure);
/*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM2,定时器开始运行
}
void PWM_servo_SetCompare3(uint16_t Compare)
{
TIM_SetCompare1(TIM3, Compare); //设置CCR3的值
}
直流电机驱动模块
PWM生成模块
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period =100-1 ;// 20000-1 //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 36- 1 ; //72 -1
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //
TIM_OCStructInit(&TIM_OCInitStructure); // //避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //
//TIM_OC4Init(TIM2, &TIM_OCInitStructure);
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
//==============================3
// TIM_Cmd(TIM3, ENABLE);
//===============================3
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR3的值
}
电机控制模块
#include "stm32f10x.h" // Device header
#include "PWM.h"
#include "OLED.h"
void Motor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4和PA5引脚初始化为推挽输出
PWM_Init(); //初始化直流电机的底层PWM
}
void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0) //如果设置正转的速度值
{
GPIO_SetBits(GPIOA, GPIO_Pin_4); //PA4置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //PA5置低电平,设置方向为正转
PWM_SetCompare3(Speed); //PWM设置为速度值
}
else //否则,即设置反转的速度值
{
int8_t TEMP;
TEMP = -Speed;
// OLED_ShowString(2,1,"HelloPWM");
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //PA4置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_5); //PA5置高电平,设置方向为反转
PWM_SetCompare3(TEMP); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}
蓝牙模块
蓝牙模块的调试
(1)将蓝牙和usb转TTL转换器相连
转换器 | HC-05 |
---|---|
5v | VCC |
gnd | GND |
RXD | TXD |
TXD | RXD |
(2)按住蓝牙模块上的黑色按键的同时,将转换器插在电脑上
一定要按住按键的同时插上
成功后,可以看见蓝牙模块上的指示灯闪烁节奏的变化!
(3)使用电脑上的串口调试工具进行设置
我使用的xcom进行调试,输入AT,如果返回ok则说明正确连接
(4)设置波特率,主从角色
波特率设置为9600,输入
AT+UART=<9600,0,0>
设置为从角色,输入
AT+ROLE=0
关于蓝牙连接的细节和原理有很多写的很详细文章,这里就不做过多赘述了
或者参照这个视频!
链接: link
手机控制蓝牙部分代码
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_RxData;
uint8_t Serial_RxFlag;
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++)
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
}
}
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format, ...)
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_SendByte(Serial_RxData);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
调试模块(一):旋转编码器
#include "stm32f10x.h" // Device header
#include "Delay.h"
int16_t COUNT;
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//配置gpio和exti连接
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1);
//配置exti
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
//配置nvic
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) == SET)
{
Delay_ms(5);
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0)
{
COUNT ++;
}
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
Delay_ms(5);
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0)
{
COUNT --;
}
}
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
int16_t COUNT_GET(void)
{
int16_t TEMP;
TEMP = COUNT;
COUNT = 0;
return TEMP;
}
需要注意的是,我在跟着江科大实现该模块功能时候是有问题的!数字显示不准确,有时能读取旋转变化,有时候读取不到旋转变化!
解决方法!需要延迟函数进行按键消抖
调试模块(二):OLED
#include "stm32f10x.h"
#include "OLED_Font.h"
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}