|
Работа по учебнику Discovering the STM32 Microcontroller by Geoffrey Brown.
STM32 + Wii Nunchuk + LCD 5110(PCD8544). Интерфейсы SPI(3 wire) и I2C.
Использование игрового контроллера Wii Nunchuk. Получение данных от Wii Nunchuk по шине I2C и их вывод на дисплей Nokia5110 по шине SPI (только передача данных, 3 пина).
Также от джойстика поворачивается сервопривод.
datasheet PCD8544
datasheet servo SG90
- Весь исходный код. ide CooCox. -
Компоненты:
Дисплей Nokia5110
Модуль питания HW-131 (переключатель на 3 вольта)
Wii Nunchuk
Wii Nunchuk adapter (4-пиновый переходник для соединения на макетной плате)
STM32VLDiscovery
Micro Servo SG90
Вывод информации с Wii Nunchuk на дисплей:
в левом верхнем углу - положение двух-осевого джойстика (255, 128),
в левом нижнем углу - состояние кнопок C и Z (нажаты)
остальное - показания трёх-осевого акселерометра в градусах.
некоторые модули:
/* main.c */
#include <stm32f10x_i2c.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <stm32f10x_gpio.h>
#include "I2C.h"
#include "lcd5110.h"
#include <math.h>
#include "images.h"
#define master_addr 0x69 // "любой" адрес
#define speedV 370
#define btn_Z 0x01
#define btn_C 0x02
double pitch; //угол поворота по оси Х
double roll; //угол поворота по оси Y
double yaw; //угол поворота по оси Z
void intToStr(int16_t, char * );
void test();
void init_timers(void);
int main(void){
uint8_t data [6];
const uint8_t buf[] = {0};
Status rez;
char strImg[6];
uint8_t pZ = 1, pC = 1; //предыдущее состояние кнопок(отпущены)
uint16_t CC = 0, ZZ = 0; //количество нажатий
uint16_t C = 0, Z = 0; //удерживание
uint8_t JX, JY;
int16_t AX, AY, AZ;
strImg[5] = '\0';
// LCD init and test
lcd5110_init();
//lcd5110_invert(1);
//test();
//lcd5110_invert(0);
test();
// wii nunchuk init
#define NUNCHUK_ADDRESS 0xA4
const uint8_t buf1[] = {0xf0 , 0x55}; //{0x40 , 0x00};
const uint8_t buf2[] = {0xfb , 0x00};
init_timers();
I2C_LowLevel_Init(I2C1 , speedV , master_addr);
rez = I2C_Write(I2C1 , buf1 , 2, NUNCHUK_ADDRESS);
rez = I2C_Write(I2C1 , buf2 , 2, NUNCHUK_ADDRESS);
while(1){
if ( rez == 1 ){ // от разных ошибок и зависаний
I2C_LowLevel_Init(I2C1 , speedV , master_addr);
rez = I2C_Write(I2C1 , buf1 , 2, NUNCHUK_ADDRESS);
rez = I2C_Write(I2C1 , buf2 , 2, NUNCHUK_ADDRESS);
}
// Read
rez = I2C_Write(I2C1 , buf , 1, NUNCHUK_ADDRESS);
if ( rez == 0 ){
I2C_Read(I2C1 , data , 6, NUNCHUK_ADDRESS);
if (data[0] == 0xFF && data[1] == 0xFF && data[2] == 0xFF && data[3] == 0xFF &&
data[4] == 0xFF && data[5] == 0xFF){ //похоже на зависание
rez = 1;
continue;
}
// состояние кнопок
if ( (data[5] & btn_C) != btn_C ){
CC++; //удерживание
if( pC == 1 ){
C++; //нажатие
pC = 0;
}
}else{
pC = 1;
}
if ( (data[5] & btn_Z) != btn_Z ){ //бит сброшен = кнопка нажата
ZZ++; //удерживание
if( pZ == 1 ){
Z++; //нажатие
pZ = 0;
}
}else{
pZ = 1;
}
// Joystick
JX = data[0];
JY = data[1];
// Accelerometer
AX = (uint16_t)data[2] << 2 | (uint16_t)data[5] >> 2;
AY = (uint16_t)data[3] << 2 | (uint16_t)data[5] >> 4;
AZ = (uint16_t)data[4] << 2 | (uint16_t)data[5] >> 6;
//преобразуем 0..1023 в -512..511
AX -= 512;
AY -= 512;
AZ -= 512;
// вычисление углов от 0 до 90 градусов по данным акселерометра
pitch = AX / sqrt( (double)(AY*AY + AZ*AZ) );
pitch = atan( pitch ); // в радианах
pitch = pitch * 57.2957; // в градусах
roll = AY / sqrt( (double)(AX*AX + AZ*AZ) );
roll = atan( roll ); // в радианах
roll = roll * 57.2957; // в градусах
yaw = AZ / sqrt( (double)(AX*AX + AY*AY) );
yaw = atan( yaw ); // в радианах
yaw = yaw * 57.2957; // в градусах
/*double pitch2;
* вычисление углов от 0 до 180 градусов
pitch2 = atan2((double)AX, (double)AZ);
pitch2 = pitch2 * 57.2957; // в градусах
double roll2;
roll2 = atan2((double)AY, (double)AZ);
roll2 = roll2 * 57.2957; // в градусах
double yaw2;
yaw2 = atan2((double)AZ, (double)AX);
yaw2 = yaw2 * 57.2957; // в градусах
*/
// вывод на дисплей
intToStr(JX, strImg);
lcd5110_printstr(0, 0, strImg);
intToStr(JY, strImg);
lcd5110_printstr(0, 11, strImg);
intToStr( (int16_t)pitch, strImg);
lcd5110_printstr(35, 0, strImg);
lcd5110_printstr(78, 0, "X");
intToStr((int16_t)roll, strImg);
lcd5110_printstr(35, 11, strImg);
lcd5110_printstr(78, 11, "Y");
intToStr((int16_t)yaw, strImg);
lcd5110_printstr(35, 22, strImg);
lcd5110_printstr(78, 22, "Z");
lcd5110_printstr(44, 33, "УГОЛ");
if ( !pC )
lcd5110_printstr(4, 39, "C");
else
lcd5110_printstr(4, 39, " ");
if ( !pZ )
lcd5110_printstr(20, 39, "Z");
else
lcd5110_printstr(20, 39, " ");
lcd5110_refresh(0);
// управление Servo (sg90)
// управлять буду через ось Х
// Х изменяется от 0 до 255
// настройки таймера таковы, что регулировочное значение будет
// меняться от 50 ( 1мс; -45 градусов ) до 100 ( 2мс; +45 градусов )
// средняя точка = 75 ( 1.5мс; 0 градусов )
// поэтому значение Х от 0 до 255 нужно масштабировать на интервал от 50 до 100
// для этого перемещаю интервал "от 50 до 100" в ноль: "от 0 до 50"
// масштабирую: уголСерво = (Х*50)/255; остаток отбрасываю
// возвращаюсь к интервалу "от 50 до 100" : уголСерво = уголСерво + 50;
TIM_SetCompare3(TIM4, JX*50/255 + 50);
}
}
}
// преобразует число в строку.
// выравнивание по правому краю
void intToStr(int16_t dec, char * s){
uint16_t rem;
uint8_t i, neg = 0;
for ( i = 0; i < 4; i++ )
s[i] = ' ';
s[i] = '0';
if( dec < 0 ){
neg = 1;
dec = -1*dec;
}
while ( dec > 0 ){
rem = dec % 10;
dec = dec / 10;
s[i--] = (char)(rem + 48);
}
if ( neg )
s[i] = '-';
}
void test(){
lcd5110_refresh(mirossa84x48);
Delay(3000);
/*lcd5110_printstr(5, 9, "ARTYUIBGH9876543210~--1234567890+=|");
Delay(1000);
lcd5110_putpixel(0,0);
lcd5110_putpixel(83,0);
lcd5110_putpixel(0,47);
lcd5110_putpixel(83,47);
lcd5110_refresh(0);
Delay(1000);
lcd5110_horiz_line(1, 1, 84);
lcd5110_horiz_line(1, 13, 82);
lcd5110_refresh(0);
Delay(1000);
lcd5110_vert_line(5, 0, 48);
lcd5110_vert_line(12, 1, 48);
lcd5110_vert_line(21, 2, 6);
lcd5110_refresh(0);
Delay(1000);
lcd5110_draw_line(3,3, 70, 30);
lcd5110_draw_line(83,0, 0, 47);
lcd5110_refresh(0);
Delay(1000);
lcd5110_clear();
lcd5110_refresh(0);
*/
}
void init_timers(void) {
GPIO_InitTypeDef GPIO_InitStructure;
//setup GPIO for PA1, the output of TIM2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// enable timer clock
RCC_APB1PeriphClockCmd ( RCC_APB1Periph_TIM4 , ENABLE );
// configure timer
// PWM frequency = 50 hz with 24 ,000 ,000 hz system clock
// 24 ,000 ,000/480 = 50 ,000
// 100 ,000/100 = 50
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure );
TIM_TimeBaseStructure.TIM_Prescaler = SystemCoreClock /50000 - 1; // 0..479
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 0..999
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInit (TIM4, &TIM_TimeBaseStructure );
// PWM1 Mode configuration : Channel3
//Edge - aligned ; not single pulse mode
TIM_OCStructInit (&TIM_OCInitStructure );
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1 ;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable ;
TIM_OC3Init (TIM4, &TIM_OCInitStructure );
// Enable Timer
TIM_Cmd (TIM4, ENABLE );
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
/* Infinite loop */
/* Use GDB to find out why we're here */
while (1);
}
#endif
/* lcd5110.h */
#ifndef _H_LCD5110_
#define _H_LCD5110_
#include "spi.h"
#define GPIO_PIN_RST GPIO_Pin_9
#define GPIO_PIN_SCE GPIO_Pin_8
#define GPIO_PIN_DC GPIO_Pin_5
#define LCD5110_PORT GPIOB
void Delay(uint32_t nTime);
void lcd5110_init();
//очистка буфера дисплея
void lcd5110_clear();
//вывод данных из буфера на дисплей при p == 0
// иначе - вывод данных на дисплей из массива(504 байта), указатель
// на который передается в функцию
void lcd5110_refresh(const char * p);
// вывод символа 6x8 в буфер экрана в пределах всего экрана 84x48
// в функцию передаются координаты экрана в пикселях (от 0,0 до 83,47)
// в функции эти координаты преобразуются в : (0,0 до 83,5). y - от 0 до 6
// если символ не уместился по горизонтали, то не вместившаяся часть перенесётся на следующую строку
// (так же, как и при передаче данных напрямую на дисплей)
// если символ не вместился по вертикали, то он обрежется.
void lcd5110_putchar(uint8_t x, uint8_t y, char ch);
//выводит текст в буфер экрана в пределах всего экрана
// если символ не вмещается в строку полностью, то он полностью переносится на
// следующую строку.
// Если строка не вмещается по вертикали, то обрезается.
void lcd5110_printstr(uint8_t x, uint8_t y, char * ch);
//выводит пиксель в буфер экрана
void lcd5110_putpixel(uint8_t x, uint8_t y);
//выводит горизонтальную строку толщиной в один пиксель в буфер экрана
void lcd5110_horiz_line(uint8_t x, uint8_t y, uint8_t len);
//выводит вертикльную строку толщиной в один пиксель в буфер экрана
void lcd5110_vert_line(uint8_t x, uint8_t y, uint8_t len);
//линия под углом. выводится в буфер экрана
void lcd5110_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
//переключение режима инвертирования изображения.
// предполагается, что дисплей находится в режиме
// приёма основных команд(не расширенных)
void lcd5110_invert(uint8_t m);
//настройка напряжения для работы дисплея
//(влияет на контрастность)
void lcd3110_setVop(uint8_t m);
//Перевод дисплея в/из режима погашенного экрана.
// После выхода из этого режима инициализировать дисплей
// снова не нужно. На экране отобразится последнее изображение.
//
// значения параметра enable:
// 1 (что-то не равное нулю) и 0.
void lcd5110_powerDownMode(uint8_t enable);
#endif
70
|
|
Комментарии