CxemCAR на STM32 — Bluetooth управление машинкой с Android

Первая, вводная часть статьи проекта CxemCAR находится здесь. В данной статье я хотел бы описать практическую реализацию для STM32 платформы. Сразу скажу, что это мой первый проект на микроконтроллерах STM32, поэтому код может быть не слишком профессиональным. Идея реализовать проект CxemCAR для STM32 возникла во первых, из-за валявшейся уже почти год бесхозной платы STM32 Value Line Discovery, которую мне когда-то прислали нахаляву, а во вторых из-за давнего желания «пощупать» набирающие популярность STM32-микроконтроллеры.

Возможности платы STM32VLDISCOVERY покрывают наши требования более чем, тут есть и ШИМы и таймеры и UART и энергонезависимая память. Главное, все это правильно сконфигурировать в нашей программе и подключить.

Схема подключения платы STM32 Discovery к Bluetooth модулю HC-06 и драйверу двигателей L298N представлена ниже:

 

Питание — раздельное, для платы контроллера SMT32 я использовал Li-Po аккумулятор 3.7В емкостью 1400 мАч. Для питания двигателей — 5шт пальчиковых батареек типа АА (можно использовать 2 последовательно соединенных Li-Po аккумулятора).

 

Для удобства крепления платы Discovery, из рекламного пластика вырезал небольшую стойку, чтобы контакты и кнопки сброса не касались шасси машинки. Выключатель питания припаян к аккумулятору, также припаял провода с разъёмами для удобства соединения с платой контролера и зарядным устройством.

 

Аккумулятор закрепил при помощи хомута, а выключатель питания установил в штатное место 4WD платформы.

 

Плату драйвера L298N я разместил в отсеке с моторами и закрыл штатной крышкой:

 

В итоге, вся конструкция со снятой верхней крышкой выглядит так:

 

И в сборе:

 

Программное обеспечение

 

Программа для STM32 Discovery писалась в среде CooCox CoIDE. Полный проект для CoIDE вы можете скачать ниже. В программе использовались следующие компоненты:

USART — для связи с Bluetooth модулем;

TIM — для формирования ШИМ сигналов к L298N и для временных отсчетов;

а также использовались прерывания (прием данных и отсчет времени) и операции чтения/записи Flash памяти STM32. 

 

Листинг файла main.c: 

/* Подключение:
* PA9 — к Bluetooth модулю RX (pin 2), PA10 — к Bluetooth модулю TX (pin 1)
* PA5 — к пину IN2 драйвера L298N, PA6 (TIM3) — к пину IN1 драйвера L298N (левый мотор)
* PA1 — к пину IN4 драйвера L298N, PA0 (TIM2) — к пину IN3 драйвера L298N (правый мотор)
* PA4 — дополнительный канал, можно использовать для звукового сигнала или фар
*/

#include «stm32f10x_usart.h»
#include «stm32f10x_rcc.h»
#include «stm32f10x_gpio.h»
#include «stm32f10x_tim.h»
#include «misc.h»

void NVIC_Configuration(void);
void GPIO_Configuration(void);
void USART_Configuration(void);
void USART1_IRQHandler(void);
void UARTSend(const unsigned char *pucBuffer, unsigned long ulCount);
uint32_t flash_read(uint32_t address);

char L_Data[5]; // строковый массив для данных левого мотора L
uint8_t L_index = 0; // индекс массива L
char R_Data[5]; // строковый массив для данных правого мотора R
uint8_t R_index = 0; // индекс массива R
char H_Data[1]; // строковый массив для доп. канала
uint8_t H_index = 0; // индекс массива H
char F_Data[8]; // строковый массив данных для работы с EEPROM
uint8_t F_index = 0; // индекс массива F

char command; // команда: передача координат R, L или конец строки
char incomingByte;
int valueL, valueR; // значение ШИМ M1, M2

//#define autoOFF 2500 // кол-во миллисекунд через которое робот останавливается при потери связи
#define cmdL ‘L’ // команда UART для левого двигателя
#define cmdR ‘R’ // команда UART для правого двигателя
#define cmdH ‘H’ // команда UART для доп. канала 1 (к примеру сигнал Horn)
#define cmdF ‘F’ // команда UART для работы с EEPROM памятью МК для хранения настроек
#define cmdr ‘r’ // команда UART для работы с EEPROM памятью МК для хранения настроек (чтение)
#define cmdw ‘w’ // команда UART для работы с EEPROM памятью МК для хранения настроек (запись)

#define FLASH_KEY1 ((uint32_t)0x45670123)
#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)
#define FLASH_PAGE ((uint8_t)0x7F)

int main(void)
{
GPIO_Configuration();
usart_rxtx();
PWM_Init1();
PWM_Init2();
Timer_Init();

while(1)
{
}
}

void GPIO_Configuration(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // Включаем тактирование порта

GPIO_InitTypeDef GPIO_Config;

/* Пин направления вращения двигателя для L298N */
GPIO_Config.GPIO_Pin = GPIO_Pin_5;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Config.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_Config);

/* Пин направления вращения двигателя для L298N */
GPIO_Config.GPIO_Pin = GPIO_Pin_1;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Config.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_Config);

/* Пин доп. канала 1. К примеру сигнал Horn */
GPIO_Config.GPIO_Pin = GPIO_Pin_4;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Config.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_Config);
}

void PWM_Init1(void)
{
// Настройка TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

GPIO_InitTypeDef GPIO_Config;

GPIO_Config.GPIO_Pin = GPIO_Pin_0;
GPIO_Config.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Config.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_Config);

TIM_TimeBaseInitTypeDef TIM_BaseConfig;
TIM_OCInitTypeDef TIM_OCConfig;

TIM_BaseConfig.TIM_Prescaler = (uint16_t) (SystemCoreClock / 1000000) — 1;
TIM_BaseConfig.TIM_Period = 255;
TIM_BaseConfig.TIM_ClockDivision = 0;
TIM_BaseConfig.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_BaseConfig);

TIM_OCConfig.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCConfig.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCConfig.TIM_Pulse = 0;
TIM_OCConfig.TIM_OCPolarity = TIM_OCPolarity_High;

TIM_OC1Init(TIM2, &TIM_OCConfig);

TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);

TIM_Cmd(TIM2, ENABLE); // Запускаем таймер TIM3
}

void PWM_Init2(void)
{
// Настройка TIM3
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

GPIO_InitTypeDef GPIO_Config;

GPIO_Config.GPIO_Pin = GPIO_Pin_6;
GPIO_Config.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Config.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_Config);

TIM_TimeBaseInitTypeDef TIM_BaseConfig;
TIM_OCInitTypeDef TIM_OCConfig;

TIM_BaseConfig.TIM_Prescaler = (uint16_t) (SystemCoreClock / 1000000) — 1;
TIM_BaseConfig.TIM_Period = 255;
TIM_BaseConfig.TIM_ClockDivision = 0;
TIM_BaseConfig.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_BaseConfig);

TIM_OCConfig.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCConfig.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCConfig.TIM_Pulse = 0;
TIM_OCConfig.TIM_OCPolarity = TIM_OCPolarity_High;

TIM_OC1Init(TIM3, &TIM_OCConfig);

TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);

TIM_Cmd(TIM3, ENABLE); // Запускаем таймер TIM3
}

void Timer_Init(void)
{
uint32_t st_address = FLASH_BASE + FLASH_PAGE * 1024;
uint8_t sw_autoOFF = flash_read(st_address);

if(sw_autoOFF == ‘1’){
char var_Data[4];

var_Data[0] = flash_read(st_address)>>8;
var_Data[1] = flash_read(st_address)>>16;
var_Data[2] = flash_read(st_address)>>24;
uint16_t autoOFF = atoi(var_Data)*100;

// Настройка TIM6
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

TIM_TimeBaseInitTypeDef TIM_BaseConfig;

TIM_BaseConfig.TIM_Prescaler = (uint16_t) (SystemCoreClock / 1000) — 1; // 1кГц
TIM_BaseConfig.TIM_Period = autoOFF;
TIM_TimeBaseInit(TIM6, &TIM_BaseConfig);

TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);

TIM_Cmd(TIM6, ENABLE);

NVIC_EnableIRQ(TIM6_DAC_IRQn); // Включаем прерывание по таймеру
}
else{
TIM_Cmd(TIM6, DISABLE);
NVIC_DisableIRQ(TIM6_DAC_IRQn); // Выключаем прерывание по таймеру
}
}

void TIM6_DAC_IRQHandler()
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) // Если таймер потери связи отсчитал до нуля
{
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
Control4WD(0,0,0); // Останавливаем машинку
}
}

void USART1_IRQHandler(void)
{
if ((USART1->SR & USART_FLAG_RXNE) != (u16)RESET)
{
TIM_SetCounter(TIM6, 0); // сброс таймера 6

incomingByte = USART_ReceiveData(USART1);

if(incomingByte == cmdL) { // если пришли данные для мотора L
command = cmdL;
memset(L_Data,0,sizeof(L_Data)); // очистка массива
L_index = 0; // сброс индекса массива
}
else if(incomingByte == cmdR) {
command = cmdR;
memset(R_Data,0,sizeof(R_Data));
R_index = 0;
}
else if(incomingByte == cmdH) {
command = cmdH;
memset(H_Data,0,sizeof(H_Data));
H_index = 0;
}
else if(incomingByte == cmdF) {
command = cmdF;
memset(F_Data,0,sizeof(F_Data));
F_index = 0;
}
else if(incomingByte == ‘r’) command = ‘e’; // конец строки
else if(incomingByte == ‘t’) command = ‘t’; // конец строки для flash команд

if(command == cmdL && incomingByte != cmdL){
L_Data[L_index] = incomingByte;
L_index++;
}
else if(command == cmdR && incomingByte != cmdR){
R_Data[R_index] = incomingByte;
R_index++;
}
else if(command == cmdH && incomingByte != cmdH){
H_Data[H_index] = incomingByte;
H_index++;
}
else if(command == cmdF && incomingByte != cmdF){
F_Data[F_index] = incomingByte;
F_index++;
}
else if(command == ‘e’){
Control4WD(atoi(L_Data),atoi(R_Data),H_Data);
}
else if(command == ‘t’){
Flash_Op(F_Data[0],F_Data[1],F_Data[2],F_Data[3],F_Data[4]);
}
}
}

void usart_rxtx(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
NVIC_Configuration();
USART_Configuration();
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

/* Настройка USART для связи с Bluetooth модулем */
void USART_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/* Конфигурируем USART1 Tx (нога PA.09) как симметричный (push-pull) пин */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Конфигурируем USART1 Rx (PA.10) как вход без подтяжки */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate = 9600; // скорость передачи
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
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);

/* Включаем USART1 */
USART_Cmd(USART1, ENABLE);
}

/* Настройка прерывания по USART */
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;

/* Включаем прерывание от USARTx */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

void Flash_Op(char FCMD, uint8_t z1, uint8_t z2, uint8_t z3, uint8_t z4){
uint32_t st_address = FLASH_BASE + FLASH_PAGE * 1024;

if(FCMD == cmdr){ // если команда чтения Flash данных
uint8_t v1,v2,v3,v4;

v1 = flash_read(st_address);
v2 = flash_read(st_address)>>8;
v3 = flash_read(st_address)>>16;
v4 = flash_read(st_address)>>24;

UARTSend(«FData:»,6); // посылаем данные с Flash
uart_write_var(v1);
uart_write_var(v2);
uart_write_var(v3);
uart_write_var(v4);
UARTSend(«rn»,2); // маркер конца передачи Flash данных
}
else if(FCMD == cmdw){ // если команда записи Flash данных
flash_write_variables(z1, z2, z3, z4);
Timer_Init(); // переинициализируем таймер
UARTSend(«FWOKrn»,6); // посылаем сообщение, что данные успешно записаны
}
}

void Control4WD(int mLeft, int mRight, uint8_t Horn){

uint8_t directionL, directionR; // направление вращения для L298N

if(mLeft > 0){
valueL = mLeft;
directionL = 0;
}
else if(mLeft < 0){
valueL = 255 — abs(mLeft);
directionL = 1;
}
else {
directionL = 0;
valueL = 0;
}

if(mRight > 0){
valueR = mRight;
directionR = 0;
}
else if(mRight < 0){
valueR = 255 — abs(mRight);
directionR = 1;
}
else {
directionR = 0;
valueR = 0;
}

if(directionL == 1) { // направление вращения для L мотора
GPIO_WriteBit(GPIOA,GPIO_Pin_5,Bit_SET);
}
else {
GPIO_WriteBit(GPIOA,GPIO_Pin_5,Bit_RESET);
}

if(directionR == 1) { // направление вращения для R мотора
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
}
else {
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
}

TIM2->CCR1 = valueR; // запись ШИМ данных
TIM3->CCR1 = valueL; // запись ШИМ данных

if(Horn == 1){
GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET); // дополнительный канал
}
else{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);
}
}

void uart_write_var(uint8_t data) {
USART_SendData(USART1, (uint8_t) data);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
{
}
}

void UARTSend(const unsigned char *pucBuffer, unsigned long ulCount)
{
//
// Loop while there are more characters to send.
//
while(ulCount—)
{
USART_SendData(USART1, (uint16_t) *pucBuffer++);
/* Loop until the end of transmission */
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
{
}
}
}

uint8_t flash_ready(void) {
return !(FLASH->SR & FLASH_SR_BSY);
}

/* Стираем страницу в Flash*/
void flash_erase_page(uint32_t address) {
FLASH->CR|= FLASH_CR_PER;
FLASH->AR = address;
FLASH->CR|= FLASH_CR_STRT;
while(!flash_ready())
;
FLASH->CR&= ~FLASH_CR_PER;
}

/* Разблокируем Flash */
void flash_unlock(void) {
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}

/* Блокировка Flash */
void flash_lock() {
FLASH->CR |= FLASH_CR_LOCK;
}

/* Запись во Flash */
void flash_write(uint32_t address,uint32_t data) {

FLASH->CR |= FLASH_CR_PG;
while(!flash_ready())
;
*(__IO uint16_t*)address = (uint16_t)data;
while(!flash_ready())
;
address+=2;
data>>=16;
*(__IO uint16_t*)address = (uint16_t)data;
while(!flash_ready())
;
FLASH->CR &= ~(FLASH_CR_PG);
}

/* Чтение Flash */
uint32_t flash_read(uint32_t address) {
return (*(__IO uint32_t*) address);
}

/* Функция записи 4-х байт данных в Flash*/
void flash_write_variables(uint8_t var0, uint8_t var1, uint8_t var2, uint8_t var3){
uint32_t wr_data = var3<<24 | var2<<16 | var1<<8 | var0;
uint32_t st_address = FLASH_BASE + FLASH_PAGE * 1024;

flash_unlock();
flash_erase_page(st_address);
flash_lock();

flash_unlock();
uint16_t tmp;
for(tmp=0;tmp<1024;tmp+=4)
flash_write(st_address+tmp,wr_data);
flash_lock();
}

Код программы я постарался снабдить комментариями, но как обычно это бывает, программа хороша понятна мне, но может быть совсем не понятна Вам, поэтому если будут вопросы — спрашивайте на форуме в соответствующей теме. Примерно 1/3 программы занимают функции для операций чтения/записи Flash памяти. А используется она всего лишь для хранения одной настройки: количество миллисекунд через которое робот останавливается при потери связи. Можно эту настройку «жестко» прописать в программе, для этого раскомментируйте строчку #define autoOFF 2500 (где 2500 кол-во миллисекунд). После этого, можно удалить следующие функции: Flash_Op(), flash_ready(), flash_erase_page(), flash_unlock(), flash_lock(), flash_write(), flash_read() и flash_write_variables(), а также подредактировать функцию Timer_Init().

 

Контроллер по USART от Bluetooth модуля получает готовые данные для левого и правого двигателя. Т.е. все основные расчеты происходят в Android приложении. Остается лишь выполнить начальную инициализацию периферии, принять и определить назначение команды и выполнить соответствующее ей действие.Прикрепленные файлы:

Добавить комментарий

Ваш адрес email не будет опубликован.