前言
学习stm32需要记忆的东西太多了,网上很多博客都有写如何做舵机控制,也有写如何通过串口与上位机通信,这篇博客主要通过TCP客户端发送指令控制舵机运动到对应的角度,所以这篇博客的主要目的是记录下自己写下整个控制的流程,便于自己温故。
硬件
使用的板子是 STM32F103 C8T6 ,最小系统板,好兄弟们要借鉴我的话如果架构不一样可以参照 “STM32官方手册” 进行小修改。
舵机型号是 MG90s 淘宝上买10块钱买来的,是一个控制较为简单的角度伺服控制器,通过输出PWM波形控制旋转角度。
以及若干杜邦线,由于学习板上有很多外设,其他就直接用板子上的。
小结
开发板:STM32F103 C8T6最小系统板
舵机: MG90s
整体流程如下
接线
舵机部分
如图所示,使用的是PB9作为TIM4的CH4口复用,也可选用其他端口如:PA1作为TIM5的CH2口复用。需要使用TIM输出PWM波,详情可见博客:
MG90s的黄色、红色和褐色分别对应信号线、VCC和VDD,因此这里我们把黄色接入PB9,红色接入5v,褐色接入GND。
串口部分
如图所示,STM手册上有使用的是PA2作为USART2的TX口复用,PA3作为USART2的RX口复用,串口通信详情见如下博客。
LED部分
由于板子上有一个自带的LED小灯,其挂在PC13上因此无需接线。
小结
最后接线如图
代码
在本示例中使用的外设有GPIO、TIM、USART、RCC、EXTI、FSMC,在Keil中选择外设勾选上。
舵机代码
- mg90s.h
#ifndef _TIM_H_
#define _TIM_H_
#include "stm32f10x.h"
extern u8 ANGLE;
void TIM_init(void);
void TIM_Angle(float angle);
#endif
- mg90s.c
#include "mg90s.h"
/*
使用PWM波形输出步骤
1、定时器时钟使能
2、初始化定时器
a.预分频系数
b.重装载值
c.计数模式
3、比较输出口设置
a.端口配置
b.端口重映射
c.TIM比较配置
4、使能定时器
*/
#define ARR 9999 //重装载值 ->设置为10000-1 比较容易计算占空比
#define PSC 143 //预分频系数
u8 ANGLE=0;
// @desc: MG90s初始化
// @param: psc:预分频系数 arr:重装载值
// @rtn: None
void TIM_init(void){
TIM_TimeBaseInitTypeDef T4;
TIM_OCInitTypeDef OC4;
GPIO_InitTypeDef PB9;
/*时钟使能*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/*GPIOB-PB9初始化*/
PB9.GPIO_Mode = GPIO_Mode_AF_PP;
PB9.GPIO_Pin = GPIO_Pin_9;
PB9.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &PB9);
/*TIM4 初始化*/
T4.TIM_Prescaler = PSC;
T4.TIM_Period = ARR;
T4.TIM_CounterMode = TIM_CounterMode_Up;
T4.TIM_ClockDivision = TIM_CKD_DIV1;
T4.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM4, &T4);
/*OC4初始化*/
OC4.TIM_OCMode = TIM_OCMode_PWM1;
OC4.TIM_OCNPolarity = TIM_OCNPolarity_High;
OC4.TIM_OutputState = TIM_OutputState_Enable;
OC4.TIM_Pulse = 0;
TIM_OC4Init(TIM4, &OC4);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM4, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}
// @desc: MG90s设置角度,内部有将角度转换为比较值,在修改重装载值时需要修改
// @param: angle:预期角度值
// @rtn: None
void TIM_Angle(float angle){
angle = (u16)(50.0*angle/9.0+249.0);
TIM_SetCompare4(TIM4, angle);
}
串口代码
- usart.h
#ifndef _USART_H_
#define _USART_H_
#include "stm32f10x.h"
#define IS_SWITCH(PARAM) ((PARAM == 0) || (PARAM == 1) )
#define IS_ANGLE(PARAM) ((PARAM <= 180)|| (PARAM >= 0) )
extern u8 USART_BUF[80]; //数据缓冲区
void USART_init(void);
void USART_SendString(USART_TypeDef *USARTx, u8* String);
void Decode_String(u8* String);
void LED_USART_turn(u8 Switch);
void SVG_USART_turn(u8 Angle);
void FUN_USART_turn(u8);
#endif
- usart.c
#include "usart.h"
#include "led.h"
#include "tim.h"
// @desc: USART初始化
// @param: None
// @rtn: None
void USART_init(void){
GPIO_InitTypeDef GPA;
USART_InitTypeDef U2;
NVIC_InitTypeDef N2;
/*时钟使能*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/*TX端口设置*/
GPA.GPIO_Mode = GPIO_Mode_AF_PP;
GPA.GPIO_Pin = GPIO_Pin_2;
GPA.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPA);//TX
/*RX端口设置*/
GPA.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPA.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOA, &GPA);//RX
/*USART端口设置*/
USART_DeInit(USART2);
U2.USART_BaudRate = 115200;
U2.USART_WordLength = USART_WordLength_8b;
U2.USART_StopBits = USART_StopBits_1;
U2.USART_Parity = USART_Parity_No;
U2.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;
U2.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2, &U2);
/*中断服务配置*/
N2.NVIC_IRQChannel = USART2_IRQn;
N2.NVIC_IRQChannelCmd = ENABLE;
N2.NVIC_IRQChannelPreemptionPriority = 2;
N2.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&N2);
/*设置中断触发*/
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
/*串口使能*/
USART_Cmd(USART2, ENABLE);
}
u8 USART_BUF[80]; //数据缓冲区
u16 USART_INDEX = 0; //缓冲区索引
u8 LED_TEXT[] = "LED SETTING\n";
u8 SGV_TEXT[] = "SGV SETTING\n";
u8 WRG_TEXT[] = "WRONG PARAMETER\n";
u8 AP_CONNECT[] = "AT+CIPSEND=0\n";
u8 AP_sw = 0;
/*串口2服务函数*/
// @param: None
// @rtn: None
void USART2_IRQHandler(void){
if(USART_GetITStatus(USART2, USART_IT_RXNE)){ //终端类型判断
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除中断标志
USART_BUF[USART_INDEX] = USART_ReceiveData(USART2); //数据存入缓冲区
USART_INDEX++;
if(USART_BUF[USART_INDEX-1] == 0x0a || USART_INDEX == 80){ //若最后接收到0x0a或长度上限
//判断1
if(USART_BUF[0] == '+'){
/*+ 为命令头,用以标识由串口调试*/
USART_SendString(USART2, USART_BUF+1);
Decode_String(USART_BUF+1);
//判断2
}else if(USART_BUF[0] <= '4' && USART_BUF[0] >= '0'){
/*检测到有设备连接*/
AP_CONNECT[11] = USART_BUF[0];
USART_SendString(USART2,AP_CONNECT);
//判断3
}else{
/*其他情况将静默XXX所接受的数据发送*/
// USART_SendString(USART2, USART_BUF);
USART_INDEX = 0; //索引清零
}
USART_INDEX = 0; //索引清零
}
}
}
/*串口发送字符串*/
// @param: USARTx:发送端口 String:发送字符
// @rtn: None
void USART_SendString(USART_TypeDef *USARTx, u8* String){
u8 *pt = String;
while(*pt != 0x0a || pt-String >= 80){
//判断结尾
USART_SendData(USARTx, *pt); //发送
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == 0){}; //等待发送完毕
pt++;
};
USART_SendData(USARTx, 0x0d);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == 0){};
USART_SendData(USARTx, 0x0a); //等待发送0x0a结束
}
/*解码字符串*/
// @param: String:需解码字符
// @rtn: None
void Decode_String(u8* String){
u8 idx = 0; //定义索引
u8 value = 0; //定义传送参数
void (*fun)(u8); //定义函数指针
u8* pt;
while(*String != ':'){ //判断字符头,":"后的才是发送的有效数据,如 +ip,0,12:youxiaozifu
pt = String+2;
String++;
}
//判断控制LED
if( ( *(pt) == 'L' || *(pt) == 'l' ) &&
( *(pt+1) == 'E' || *(pt+1) == 'e' ) &&
( *(pt+2) == 'D' || *(pt+2) == 'd' ) ){
fun = LED_USART_turn; //指向LED
//判断控制舵机
}else if(
( *(pt) == 'S' || *(pt) == 's' ) &&
( *(pt+1) == 'V' || *(pt+1) == 'v' ) &&
( *(pt+2) == 'G' || *(pt+2) == 'g' ) ){
fun = SVG_USART_turn; //指向舵机
//其他功能
}else{
fun = FUN_USART_turn; //指向其他
}
//获取 传送数值
for(idx = 4; *(pt+idx)!= ','; idx++) {
value *= 10;
value += *(pt+idx) - '0';
}
//作用于控制的功能上
fun(value);
}
/*LED功能*/
void LED_USART_turn(u8 S){
if(IS_SWITCH(S)){
GPIO_WriteBit(GPIOC, GPIO_Pin_13, S);
USART_SendString(USART2, LED_TEXT);
}else{
USART_SendString(USART2, WRG_TEXT);
}
}
/*舵机功能*/
void SVG_USART_turn(u8 Angle){
// if(IS_ANGLE(Angle)){
ANGLE = Angle;
USART_SendString(USART2, SGV_TEXT);
// }else{
// USART_SendString(USART2, WRG_TEXT);
// }
}
/*待开发*/
void FUN_USART_turn(u8 param){
USART_SendString(USART2, "WAITINGFORDEVELOPMENT\n");
}
LED代码
- led.h
#ifndef _LED_H_
#define _LED_H_
#include "stm32f10x.h"
void LED_init(void);
void LED_turn(void);
#endif
*led.c
#include "stm32f10x.h"
#include "led.h"
void LED_init(void){
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
}
void LED_turn(void){
if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13))
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
else
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}
MAIN函数
- main.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "usart.h"
#include "tim.h"
void setup(void);
void ESP_setup(void);
int main(){
u16 i = 0, j;
setup(); //硬件初始化
LED_turn();
ESP_setup(); //ESP初始化
LED_turn();
while(1){
i++;
// LED_turn();
// delay_ms(2000);
// LED_turn();
delay_ms(50);
TIM_Angle((float)ANGLE);
if (i>=12000){
for(j=0;j<70;j++)
USART_BUF[j] = 0x0a;
i = 0;
}
}
}
void setup(void){
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
LED_init();
USART_init();
TIM_init();
}
u8 COMMAND[5][20] = {"AT+CWMODE=2\n", "AT+RST\n", "AT+CIPMUX=1\n", "AT+CIPSERVER=1,505\n", "AT+CIPSTO=300\n"};
void ESP_setup(void){
u8 i = 0;
for(;i<5;i++){
USART_SendString(USART2, COMMAND[i]);
delay_ms(1000);
}
}
效果
晚上有空回宿舍录制视频,我在代码中的指令是FUNC,PARAM,
的形式控制的,FUNC为功能,PARAM为调节参数。即控制led小灯开为LED,0,
,关为LED,1,
,舵机旋转134度为SVG,134,
。