Initial commit
This commit is contained in:
14
platformio.ini
Normal file
14
platformio.ini
Normal file
@@ -0,0 +1,14 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:nucleo_f401re]
|
||||
platform = ststm32
|
||||
board = nucleo_f401re
|
||||
framework = stm32cube
|
||||
119
src/Display.c
Normal file
119
src/Display.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "Display.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "LiquidCrystal.h"
|
||||
|
||||
// Convert the 4-bit integer a, into the first button value
|
||||
int getButton1(int a) {
|
||||
int n1, n2, n3, n4;
|
||||
// take the remainder dividing the button by 2, to convert to binary
|
||||
n1 = a % 2;
|
||||
a = a / 2;
|
||||
n2 = a % 2;
|
||||
a = a / 2;
|
||||
n3 = a % 2;
|
||||
a = a / 2;
|
||||
n4 = a % 2;
|
||||
|
||||
// Given the binary value, return the first button value
|
||||
if (n1 == 1) {
|
||||
return 4;
|
||||
} else if (n2 == 1) {
|
||||
return 3;
|
||||
}
|
||||
else if (n3 == 1) {
|
||||
return 2;
|
||||
}
|
||||
else if (n4 == 1) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert the 4-bit integer a, into the second button value
|
||||
int getButton2(int a) {
|
||||
int n1, n2, n3, n4;
|
||||
n1 = a % 2;
|
||||
a = a / 2;
|
||||
n2 = a % 2;
|
||||
a = a / 2;
|
||||
n3 = a % 2;
|
||||
a = a / 2;
|
||||
n4 = a % 2;
|
||||
|
||||
// Given the binary value, return the second button value
|
||||
bool hasButton1 = false;
|
||||
if (n1 == 1 && !hasButton1) {
|
||||
hasButton1 = true; // Button 1 will never be the second button.
|
||||
}
|
||||
if (n2 == 1) {
|
||||
if (hasButton1) {
|
||||
return 3;
|
||||
} else {
|
||||
hasButton1 = true;
|
||||
}
|
||||
}
|
||||
if (n3 == 1) {
|
||||
if (hasButton1) {
|
||||
return 2;
|
||||
} else {
|
||||
hasButton1 = true;
|
||||
}
|
||||
}
|
||||
if (n4 == 1) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Display the 6 given notes (4-bit numbers) on the LCD
|
||||
void displayNotes(int a, int b, int c, int d, int e, int f) {
|
||||
char *str1 = malloc(sizeof(char) * 15); // Line 1 of LCD
|
||||
char *str2 = malloc(sizeof(char) * 15); // Line 2 of LCD
|
||||
|
||||
// Manually set each LCD letter
|
||||
str1[0] = 'A';
|
||||
str1[1] = ':';
|
||||
str1[2] = getButton1(a) + '0'; // add the 0 character to convert to ASCII
|
||||
str1[3] = getButton2(a) + '0';
|
||||
str1[4] = ' ';
|
||||
str1[5] = 'B';
|
||||
str1[6] = ':';
|
||||
str1[7] = getButton1(b) + '0';
|
||||
str1[8] = getButton2(b) + '0';
|
||||
str1[9] = ' ';
|
||||
str1[10] = 'C';
|
||||
str1[11] = ':';
|
||||
str1[12] = getButton1(c) + '0';
|
||||
str1[13] = getButton2(c) + '0';
|
||||
str1[14] = ' ';
|
||||
str1[15] = '\0';
|
||||
|
||||
// Manually set each LCD letter
|
||||
str2[0] = 'D';
|
||||
str2[1] = ':';
|
||||
str2[2] = getButton1(d) + '0';
|
||||
str2[3] = getButton2(d) + '0';
|
||||
str2[4] = ' ';
|
||||
str2[5] = 'E';
|
||||
str2[6] = ':';
|
||||
str2[7] = getButton1(e) + '0';
|
||||
str2[8] = getButton2(e) + '0';
|
||||
str2[9] = ' ';
|
||||
str2[10] = 'F';
|
||||
str2[11] = ':';
|
||||
str2[12] = getButton1(f) + '0';
|
||||
str2[13] = getButton2(f) + '0';
|
||||
str2[14] = ' ';
|
||||
str2[15] = '\0';
|
||||
|
||||
// Write to the LCD
|
||||
setCursor(1, 0);
|
||||
print(str1);
|
||||
setCursor(1, 1);
|
||||
print(str2);
|
||||
|
||||
// Free the memory
|
||||
free(str1);
|
||||
free(str2);
|
||||
}
|
||||
2
src/Display.h
Normal file
2
src/Display.h
Normal file
@@ -0,0 +1,2 @@
|
||||
// Display the 6 given notes (4-bit numbers) on the LCD
|
||||
void displayNotes(int a, int b, int c, int d, int e, int f);
|
||||
358
src/LiquidCrystal.c
Normal file
358
src/LiquidCrystal.c
Normal file
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* LiquidCrystal.c - LiquidCrystal Library for STM32 ARM microcontrollers
|
||||
*
|
||||
* Created on: April 12, 2018
|
||||
* Author: S. Saeed Hosseini (sayidhosseini@hotmail.com)
|
||||
* Ported from: Arduino, Adafruit (https://github.com/arduino-libraries/LiquidCrystal)
|
||||
* Published to: Github (https://github.com/SayidHosseini/STM32LiquidCrystal)
|
||||
*/
|
||||
|
||||
#include "stm32f4xx_hal.h" // change this line accordingly
|
||||
#include "LiquidCrystal.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// change this to 0 if you want to use 8-bit mode
|
||||
uint8_t _fourbit_mode = 1;
|
||||
|
||||
// change this to LCD_5x10DOTS for some 1 line displays that can select a 10 pixel high font
|
||||
uint8_t dotsize = LCD_5x8DOTS;
|
||||
|
||||
// pin definitions and other LCD variables
|
||||
uint16_t _rs_pin; // LOW: command. HIGH: character.
|
||||
uint16_t _rw_pin; // LOW: write to LCD. HIGH: read from LCD.
|
||||
uint16_t _enable_pin; // activated by a HIGH pulse.
|
||||
uint16_t _data_pins[8];
|
||||
GPIO_TypeDef *_port;
|
||||
|
||||
uint8_t _displayfunction;
|
||||
uint8_t _displaycontrol;
|
||||
uint8_t _displaymode;
|
||||
|
||||
uint8_t _initialized;
|
||||
|
||||
uint8_t _numlines;
|
||||
uint8_t _row_offsets[4];
|
||||
|
||||
void LiquidCrystal(GPIO_TypeDef *gpioport, uint16_t rs, uint16_t rw, uint16_t enable,
|
||||
uint16_t d0, uint16_t d1, uint16_t d2, uint16_t d3)
|
||||
{
|
||||
if(_fourbit_mode)
|
||||
init(1, gpioport, rs, rw, enable, d0, d1, d2, d3, 0, 0, 0, 0);
|
||||
else
|
||||
init(0, gpioport, rs, rw, enable, d0, d1, d2, d3, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void init(uint8_t fourbitmode, GPIO_TypeDef *gpioport, uint16_t rs, uint16_t rw, uint16_t enable,
|
||||
uint16_t d0, uint16_t d1, uint16_t d2, uint16_t d3,
|
||||
uint16_t d4, uint16_t d5, uint16_t d6, uint16_t d7)
|
||||
{
|
||||
_rs_pin = rs;
|
||||
_rw_pin = rw;
|
||||
_enable_pin = enable;
|
||||
_port = gpioport;
|
||||
|
||||
_data_pins[0] = d0;
|
||||
_data_pins[1] = d1;
|
||||
_data_pins[2] = d2;
|
||||
_data_pins[3] = d3;
|
||||
_data_pins[4] = d4;
|
||||
_data_pins[5] = d5;
|
||||
_data_pins[6] = d6;
|
||||
_data_pins[7] = d7;
|
||||
|
||||
if (fourbitmode)
|
||||
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||
else
|
||||
_displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||
|
||||
begin(16, 2);
|
||||
}
|
||||
|
||||
void begin(uint8_t cols, uint8_t lines) {
|
||||
if (lines > 1) {
|
||||
_displayfunction |= LCD_2LINE;
|
||||
}
|
||||
_numlines = lines;
|
||||
|
||||
setRowOffsets(0x00, 0x40, 0x00 + cols, 0x40 + cols);
|
||||
|
||||
// for some 1 line displays you can select a 10 pixel high font
|
||||
if ((dotsize != LCD_5x8DOTS) && (lines == 1)) {
|
||||
_displayfunction |= LCD_5x10DOTS;
|
||||
}
|
||||
|
||||
//Initializing GPIO Pins
|
||||
enableClock();
|
||||
|
||||
GPIO_InitTypeDef gpio_init;
|
||||
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
|
||||
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
|
||||
if(_fourbit_mode)
|
||||
gpio_init.Pin = _rs_pin | _rw_pin | _enable_pin | _data_pins[0] | _data_pins[1] | _data_pins[2] | _data_pins[3];
|
||||
else
|
||||
gpio_init.Pin = _rs_pin | _rw_pin | _enable_pin | _data_pins[0] | _data_pins[1] | _data_pins[2] | _data_pins[3] |
|
||||
_data_pins[4] | _data_pins[5] | _data_pins[6] | _data_pins[7];
|
||||
|
||||
HAL_GPIO_Init(_port, &gpio_init);
|
||||
|
||||
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
|
||||
// according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||
// so we'll wait 50 just to make sure
|
||||
HAL_Delay(50);
|
||||
|
||||
// Now we pull both RS and R/W low to begin commands
|
||||
HAL_GPIO_WritePin(_port, _rs_pin, GPIO_PIN_RESET);
|
||||
HAL_GPIO_WritePin(_port, _enable_pin, GPIO_PIN_RESET);
|
||||
|
||||
if (_rw_pin != 255) {
|
||||
HAL_GPIO_WritePin(_port, _rw_pin, GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
//put the LCD into 4 bit or 8 bit mode
|
||||
if (! (_displayfunction & LCD_8BITMODE)) {
|
||||
// this is according to the hitachi HD44780 datasheet
|
||||
// figure 24, pg 46
|
||||
|
||||
// we start in 8bit mode, try to set 4 bit mode
|
||||
write4bits(0x03);
|
||||
HAL_Delay(5); // wait min 4.1ms
|
||||
|
||||
// second try
|
||||
write4bits(0x03);
|
||||
HAL_Delay(5); // wait min 4.1ms
|
||||
|
||||
// third go!
|
||||
write4bits(0x03);
|
||||
HAL_Delay(1);
|
||||
|
||||
// finally, set to 4-bit interface
|
||||
write4bits(0x02);
|
||||
} else {
|
||||
// this is according to the hitachi HD44780 datasheet
|
||||
// page 45 figure 23
|
||||
|
||||
// Send function set command sequence
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
HAL_Delay(5); // wait more than 4.1ms
|
||||
|
||||
// second try
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
HAL_Delay(1);
|
||||
|
||||
// third go
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
}
|
||||
|
||||
// finally, set # lines, font size, etc.
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
|
||||
// turn the display on with no cursor or blinking default
|
||||
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
display();
|
||||
|
||||
// clear it off
|
||||
clear();
|
||||
|
||||
// Initialize to default text direction (for romance languages)
|
||||
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||
// set the entry mode
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
|
||||
}
|
||||
|
||||
// enables GPIO RCC Clock
|
||||
void enableClock(void)
|
||||
{
|
||||
if(_port == GPIOA)
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE();
|
||||
else if(_port == GPIOB)
|
||||
__HAL_RCC_GPIOB_CLK_ENABLE();
|
||||
else if(_port == GPIOB)
|
||||
__HAL_RCC_GPIOB_CLK_ENABLE();
|
||||
else if(_port == GPIOC)
|
||||
__HAL_RCC_GPIOC_CLK_ENABLE();
|
||||
else if(_port == GPIOD)
|
||||
__HAL_RCC_GPIOD_CLK_ENABLE();
|
||||
else if(_port == GPIOE)
|
||||
__HAL_RCC_GPIOE_CLK_ENABLE();
|
||||
|
||||
// if you have a port that is not listed add it below the other else ifs
|
||||
}
|
||||
|
||||
void setRowOffsets(int row0, int row1, int row2, int row3)
|
||||
{
|
||||
_row_offsets[0] = row0;
|
||||
_row_offsets[1] = row1;
|
||||
_row_offsets[2] = row2;
|
||||
_row_offsets[3] = row3;
|
||||
}
|
||||
|
||||
/********** high level commands, for the user! */
|
||||
void clear(void)
|
||||
{
|
||||
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
|
||||
HAL_Delay(2); // this command takes a long time!
|
||||
}
|
||||
|
||||
void home(void)
|
||||
{
|
||||
command(LCD_RETURNHOME); // set cursor position to zero
|
||||
HAL_Delay(2); // this command takes a long time!
|
||||
}
|
||||
|
||||
void setCursor(uint8_t col, uint8_t row)
|
||||
{
|
||||
const size_t max_lines = sizeof(_row_offsets) / sizeof(*_row_offsets);
|
||||
if ( row >= max_lines ) {
|
||||
row = max_lines - 1; // we count rows starting w/0
|
||||
}
|
||||
if ( row >= _numlines ) {
|
||||
row = _numlines - 1; // we count rows starting w/0
|
||||
}
|
||||
|
||||
command(LCD_SETDDRAMADDR | (col + _row_offsets[row]));
|
||||
}
|
||||
|
||||
// Turn the display on/off (quickly)
|
||||
void noDisplay(void) {
|
||||
_displaycontrol &= ~LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void display(void) {
|
||||
_displaycontrol |= LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turns the underline cursor on/off
|
||||
void noCursor(void) {
|
||||
_displaycontrol &= ~LCD_CURSORON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void cursor(void) {
|
||||
_displaycontrol |= LCD_CURSORON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turn on and off the blinking cursor
|
||||
void noBlink(void) {
|
||||
_displaycontrol &= ~LCD_BLINKON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
void blink(void) {
|
||||
_displaycontrol |= LCD_BLINKON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// These commands scroll the display without changing the RAM
|
||||
void scrollDisplayLeft(void) {
|
||||
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
|
||||
}
|
||||
void scrollDisplayRight(void) {
|
||||
command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
|
||||
}
|
||||
|
||||
// This is for text that flows Left to Right
|
||||
void leftToRight(void) {
|
||||
_displaymode |= LCD_ENTRYLEFT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This is for text that flows Right to Left
|
||||
void rightToLeft(void) {
|
||||
_displaymode &= ~LCD_ENTRYLEFT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will 'right justify' text from the cursor
|
||||
void autoscroll(void) {
|
||||
_displaymode |= LCD_ENTRYSHIFTINCREMENT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will 'left justify' text from the cursor
|
||||
void noAutoscroll(void) {
|
||||
_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
}
|
||||
|
||||
// This will print character string to the LCD
|
||||
size_t print(const char str[]) {
|
||||
if (str == NULL) return 0;
|
||||
|
||||
const uint8_t *buffer = (const uint8_t *)str;
|
||||
size_t size = strlen(str);
|
||||
size_t n = 0;
|
||||
|
||||
while (size--) {
|
||||
if (write(*buffer++)) n++;
|
||||
else break;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// Allows us to fill the first 8 CGRAM locations
|
||||
// with custom characters
|
||||
void createChar(uint8_t location, uint8_t charmap[]) {
|
||||
location &= 0x7; // we only have 8 locations 0-7
|
||||
command(LCD_SETCGRAMADDR | (location << 3));
|
||||
for (int i=0; i<8; i++) {
|
||||
write(charmap[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*********** mid level commands, for sending data/cmds */
|
||||
|
||||
inline void command(uint8_t value) {
|
||||
send(value, GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
inline size_t write(uint8_t value) {
|
||||
send(value, GPIO_PIN_SET);
|
||||
return 1; // assume sucess
|
||||
}
|
||||
|
||||
/************ low level data pushing commands **********/
|
||||
|
||||
// write either command or data, with automatic 4/8-bit selection
|
||||
void send(uint8_t value, GPIO_PinState mode) {
|
||||
HAL_GPIO_WritePin(_port, _rs_pin, mode);
|
||||
|
||||
// if there is a RW pin indicated, set it low to Write
|
||||
if (_rw_pin != 255) {
|
||||
HAL_GPIO_WritePin(_port, _rw_pin, GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
if (_displayfunction & LCD_8BITMODE) {
|
||||
write8bits(value);
|
||||
} else {
|
||||
write4bits(value>>4);
|
||||
write4bits(value);
|
||||
}
|
||||
}
|
||||
|
||||
void pulseEnable(void) {
|
||||
HAL_GPIO_WritePin(_port, _enable_pin, GPIO_PIN_RESET);
|
||||
HAL_Delay(1);
|
||||
HAL_GPIO_WritePin(_port, _enable_pin, GPIO_PIN_SET);
|
||||
HAL_Delay(1); // enable pulse must be >450ns
|
||||
HAL_GPIO_WritePin(_port, _enable_pin, GPIO_PIN_RESET);
|
||||
HAL_Delay(1); // commands need > 37us to settle
|
||||
}
|
||||
|
||||
void write4bits(uint8_t value) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
HAL_GPIO_WritePin(_port, _data_pins[i], ((value >> i) & 0x01)?GPIO_PIN_SET:GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
pulseEnable();
|
||||
}
|
||||
|
||||
void write8bits(uint8_t value) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
HAL_GPIO_WritePin(_port, _data_pins[i], ((value >> i) & 0x01)?GPIO_PIN_SET:GPIO_PIN_RESET);
|
||||
}
|
||||
|
||||
pulseEnable();
|
||||
}
|
||||
95
src/LiquidCrystal.h
Normal file
95
src/LiquidCrystal.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* LiquidCrystal.h - LiquidCrystal Library for STM32 ARM microcontrollers
|
||||
*
|
||||
* Created on: April 12, 2018
|
||||
* Author: S. Saeed Hosseini (sayidhosseini@hotmail.com)
|
||||
* Ported from: Arduino, Adafruit (https://github.com/arduino-libraries/LiquidCrystal)
|
||||
* Published to: Github (https://github.com/SayidHosseini/STM32LiquidCrystal)
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "ece198.h"
|
||||
|
||||
#ifndef LiquidCrystal_h
|
||||
#define LiquidCrystal_h
|
||||
|
||||
// commands
|
||||
#define LCD_CLEARDISPLAY 0x01
|
||||
#define LCD_RETURNHOME 0x02
|
||||
#define LCD_ENTRYMODESET 0x04
|
||||
#define LCD_DISPLAYCONTROL 0x08
|
||||
#define LCD_CURSORSHIFT 0x10
|
||||
#define LCD_FUNCTIONSET 0x20
|
||||
#define LCD_SETCGRAMADDR 0x40
|
||||
#define LCD_SETDDRAMADDR 0x80
|
||||
|
||||
// flags for display entry mode
|
||||
#define LCD_ENTRYRIGHT 0x00
|
||||
#define LCD_ENTRYLEFT 0x02
|
||||
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||
|
||||
// flags for display on/off control
|
||||
#define LCD_DISPLAYON 0x04
|
||||
#define LCD_DISPLAYOFF 0x00
|
||||
#define LCD_CURSORON 0x02
|
||||
#define LCD_CURSOROFF 0x00
|
||||
#define LCD_BLINKON 0x01
|
||||
#define LCD_BLINKOFF 0x00
|
||||
|
||||
// flags for display/cursor shift
|
||||
#define LCD_DISPLAYMOVE 0x08
|
||||
#define LCD_CURSORMOVE 0x00
|
||||
#define LCD_MOVERIGHT 0x04
|
||||
#define LCD_MOVELEFT 0x00
|
||||
|
||||
// flags for function set
|
||||
#define LCD_8BITMODE 0x10
|
||||
#define LCD_4BITMODE 0x00
|
||||
#define LCD_2LINE 0x08
|
||||
#define LCD_1LINE 0x00
|
||||
#define LCD_5x10DOTS 0x04
|
||||
#define LCD_5x8DOTS 0x00
|
||||
|
||||
|
||||
// low-level functions
|
||||
void send(uint8_t, GPIO_PinState);
|
||||
void write4bits(uint8_t);
|
||||
void write8bits(uint8_t);
|
||||
void pulseEnable(void);
|
||||
|
||||
// initializers
|
||||
void LiquidCrystal(GPIO_TypeDef *gpioport, uint16_t rs, uint16_t rw, uint16_t enable,
|
||||
uint16_t d0, uint16_t d1, uint16_t d2, uint16_t d3);
|
||||
|
||||
void init(uint8_t fourbitmode, GPIO_TypeDef *gpioport, uint16_t rs, uint16_t rw, uint16_t enable,
|
||||
uint16_t d0, uint16_t d1, uint16_t d2, uint16_t d3,
|
||||
uint16_t d4, uint16_t d5, uint16_t d6, uint16_t d7);
|
||||
|
||||
void begin(uint8_t cols, uint8_t rows);
|
||||
void enableClock(void);
|
||||
|
||||
// high-level functions
|
||||
void clear(void);
|
||||
void home(void);
|
||||
|
||||
void noDisplay(void);
|
||||
void display(void);
|
||||
void noBlink(void);
|
||||
void blink(void);
|
||||
void noCursor(void);
|
||||
void cursor(void);
|
||||
void scrollDisplayLeft(void);
|
||||
void scrollDisplayRight(void);
|
||||
void leftToRight(void);
|
||||
void rightToLeft(void);
|
||||
void autoscroll(void);
|
||||
void noAutoscroll(void);
|
||||
size_t print(const char []);
|
||||
|
||||
void setRowOffsets(int row1, int row2, int row3, int row4);
|
||||
void createChar(uint8_t, uint8_t[]);
|
||||
void setCursor(uint8_t, uint8_t);
|
||||
size_t write(uint8_t);
|
||||
void command(uint8_t);
|
||||
|
||||
#endif
|
||||
25
src/Servo.c
Normal file
25
src/Servo.c
Normal file
@@ -0,0 +1,25 @@
|
||||
// Minimum position for servo: 0.1ms on, 19.9ms off
|
||||
// Maximum position for servo: 2.75ms on, 18.25ms off
|
||||
|
||||
#include "ece198.h"
|
||||
#include "Servo.h"
|
||||
|
||||
// Lock the prize door
|
||||
void closePrizeDoor(void) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);
|
||||
HAL_Delay(1);
|
||||
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);
|
||||
HAL_Delay(19);
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock the prize door
|
||||
void openPrizeDoor(void) {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);
|
||||
HAL_Delay(2.75);
|
||||
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);
|
||||
HAL_Delay(18.25);
|
||||
}
|
||||
}
|
||||
5
src/Servo.h
Normal file
5
src/Servo.h
Normal file
@@ -0,0 +1,5 @@
|
||||
// Lock the prize door
|
||||
void closePrizeDoor(void);
|
||||
|
||||
// Unlock the prize door
|
||||
void openPrizeDoor(void);
|
||||
14
src/Sound.c
Normal file
14
src/Sound.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "ece198.h"
|
||||
#include "Sound.h"
|
||||
|
||||
// Play a note with a given frequency for the given note length
|
||||
void playNote(int note, int length) {
|
||||
int i = 0;
|
||||
double delay = 1000.0/(note*2.0);
|
||||
// Generate a square wave with given frequency
|
||||
while (i * delay < length) {
|
||||
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9);
|
||||
HAL_Delay(delay);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
39
src/Sound.h
Normal file
39
src/Sound.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Frequencies are taken from: https://pages.mtu.edu/~suits/notefreqs.html
|
||||
// Place a 120ohm resistor between the speaker and signal line
|
||||
|
||||
#define NOTE_C3 130
|
||||
#define NOTE_CS3 138
|
||||
#define NOTE_D3 146
|
||||
#define NOTE_DS3 155
|
||||
#define NOTE_E3 164
|
||||
#define NOTE_F3 174
|
||||
#define NOTE_FS3 185
|
||||
#define NOTE_G3 196
|
||||
#define NOTE_GS3 208
|
||||
#define NOTE_A3 220
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 246
|
||||
|
||||
#define NOTE_C4 261
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_D4 293
|
||||
#define NOTE_DS4 311
|
||||
#define NOTE_E4 329
|
||||
#define NOTE_F4 349
|
||||
#define NOTE_FS4 369
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_GS4 415
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_AS4 466
|
||||
#define NOTE_B4 493
|
||||
|
||||
#define NOTE_A 100
|
||||
#define NOTE_B 250
|
||||
#define NOTE_C 400
|
||||
#define NOTE_D 550
|
||||
#define NOTE_E 700
|
||||
#define NOTE_F 900
|
||||
|
||||
|
||||
// Play note note for length length (in milliseconds)
|
||||
void playNote(int note, int length);
|
||||
308
src/ece198.c
Normal file
308
src/ece198.c
Normal file
@@ -0,0 +1,308 @@
|
||||
// Support routines for ECE 198 (University of Waterloo)
|
||||
|
||||
// Written by Bernie Roehl, July 2021
|
||||
|
||||
#include <stdbool.h> // for bool datatype
|
||||
|
||||
#include "ece198.h"
|
||||
|
||||
///////////////////////
|
||||
// Initializing Pins //
|
||||
///////////////////////
|
||||
|
||||
// Initialize a pin (or pins) to a particular mode, with optional pull-up or pull-down resistors
|
||||
// and possible alternate function
|
||||
// (we use this so students don't need to know about structs)
|
||||
|
||||
void InitializePin(GPIO_TypeDef *port, uint16_t pins, uint32_t mode, uint32_t pullups, uint8_t alternate)
|
||||
{
|
||||
GPIO_InitTypeDef GPIO_InitStruct;
|
||||
GPIO_InitStruct.Pin = pins;
|
||||
GPIO_InitStruct.Mode = mode;
|
||||
GPIO_InitStruct.Pull = pullups;
|
||||
GPIO_InitStruct.Alternate = alternate;
|
||||
HAL_GPIO_Init(port, &GPIO_InitStruct);
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Serial Port //
|
||||
/////////////////
|
||||
|
||||
UART_HandleTypeDef UART_Handle; // the serial port we're using
|
||||
|
||||
// initialize the serial port at a particular baud rate (PlatformIO serial monitor defaults to 9600)
|
||||
|
||||
HAL_StatusTypeDef SerialSetup(uint32_t baudrate)
|
||||
{
|
||||
__USART2_CLK_ENABLE(); // enable clock to USART2
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); // serial port is on GPIOA
|
||||
|
||||
GPIO_InitTypeDef GPIO_InitStruct;
|
||||
|
||||
// pin 2 is serial RX
|
||||
GPIO_InitStruct.Pin = GPIO_PIN_2;
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
|
||||
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
||||
|
||||
// pin 3 is serial TX (most settings same as for RX)
|
||||
GPIO_InitStruct.Pin = GPIO_PIN_3;
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
|
||||
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
||||
|
||||
// configure the serial port
|
||||
UART_Handle.Instance = USART2;
|
||||
UART_Handle.Init.BaudRate = baudrate;
|
||||
UART_Handle.Init.WordLength = UART_WORDLENGTH_8B;
|
||||
UART_Handle.Init.StopBits = UART_STOPBITS_1;
|
||||
UART_Handle.Init.Parity = UART_PARITY_NONE;
|
||||
UART_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
||||
UART_Handle.Init.Mode = UART_MODE_TX_RX;
|
||||
|
||||
return HAL_UART_Init(&UART_Handle);
|
||||
}
|
||||
|
||||
// wait for a character to arrive from the serial port, then return it
|
||||
|
||||
char SerialGetc()
|
||||
{
|
||||
while ((UART_Handle.Instance->SR & USART_SR_RXNE) == 0)
|
||||
; // wait for the receiver buffer to be Not Empty
|
||||
return UART_Handle.Instance->DR; // return the incoming character
|
||||
}
|
||||
|
||||
// send a single character out the serial port
|
||||
|
||||
void SerialPutc(char c)
|
||||
{
|
||||
while ((UART_Handle.Instance->SR & USART_SR_TXE) == 0)
|
||||
; // wait for transmitter buffer to be empty
|
||||
UART_Handle.Instance->DR = c; // send the character
|
||||
}
|
||||
|
||||
// write a string of characters to the serial port
|
||||
|
||||
void SerialPuts(char *ptr)
|
||||
{
|
||||
while (*ptr)
|
||||
SerialPutc(*ptr++);
|
||||
}
|
||||
|
||||
// get a string of characters (up to maxlen) from the serial port into a buffer,
|
||||
// collecting them until the user presses the enter key;
|
||||
// also echoes the typed characters back to the user, and handles backspacing
|
||||
|
||||
void SerialGets(char *buff, int maxlen)
|
||||
{
|
||||
int i = 0;
|
||||
while (1)
|
||||
{
|
||||
char c = SerialGetc();
|
||||
if (c == '\r') // user pressed Enter key
|
||||
{
|
||||
buff[i] = '\0';
|
||||
SerialPuts("\r\n"); // echo return and newline
|
||||
return;
|
||||
}
|
||||
else if (c == '\b') // user pressed Backspace key
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
--i;
|
||||
SerialPuts("\b \b"); // overwrite previous character with space
|
||||
}
|
||||
}
|
||||
else if (i < maxlen - 1) // user pressed a regular key
|
||||
{
|
||||
buff[i++] = c; // store it in the buffer
|
||||
SerialPutc(c); // echo it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// Rotary Encoder //
|
||||
////////////////////
|
||||
|
||||
// read a rotary encoder (handles the quadrature encoding)
|
||||
//(uses a previousClk boolean variable provided by the caller)
|
||||
|
||||
int ReadEncoder(GPIO_TypeDef *clkport, int clkpin, GPIO_TypeDef *dtport, int dtpin, bool *previousClk)
|
||||
{
|
||||
bool clk = HAL_GPIO_ReadPin(clkport, clkpin);
|
||||
bool dt = HAL_GPIO_ReadPin(dtport, dtpin);
|
||||
int result = 0; // default to zero if encoder hasn't moved
|
||||
if (clk != *previousClk) // if the clk signal has changed since last time we were called...
|
||||
result = dt != clk ? 1 : -1; // set the result to the direction (-1 if clk == dt, 1 if they differ)
|
||||
*previousClk = clk; // store for next time
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Pulse Width Modulation //
|
||||
////////////////////////////
|
||||
|
||||
// set up a specified timer with the given period (whichTimer might be TIM2, for example)
|
||||
|
||||
void InitializePWMTimer(TIM_HandleTypeDef *timer, TIM_TypeDef *whichTimer, uint16_t period, uint16_t prescale )
|
||||
{
|
||||
timer->Instance = whichTimer;
|
||||
timer->Init.CounterMode = TIM_COUNTERMODE_UP;
|
||||
timer->Init.Prescaler = prescale;
|
||||
timer->Init.Period = period;
|
||||
timer->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
|
||||
timer->Init.RepetitionCounter = 0;
|
||||
HAL_TIM_PWM_Init(timer);
|
||||
}
|
||||
|
||||
// set up a particular channel (e.g. TIM_CHANNEL_1) on the given timer instance
|
||||
|
||||
void InitializePWMChannel(TIM_HandleTypeDef *timer, uint32_t channel)
|
||||
{
|
||||
TIM_OC_InitTypeDef outputChannelInit;
|
||||
outputChannelInit.OCMode = TIM_OCMODE_PWM1;
|
||||
outputChannelInit.OCPolarity = TIM_OCPOLARITY_HIGH;
|
||||
outputChannelInit.OCFastMode = TIM_OCFAST_ENABLE;
|
||||
outputChannelInit.Pulse = timer->Init.Period;
|
||||
HAL_TIM_PWM_ConfigChannel(timer, &outputChannelInit, channel);
|
||||
HAL_TIM_PWM_Start(timer, channel);
|
||||
}
|
||||
|
||||
// set the number of ticks in a cycle (i.e. <= the timer's period) for which the output should be high
|
||||
|
||||
void SetPWMDutyCycle(TIM_HandleTypeDef *timer, uint32_t channel, uint32_t value)
|
||||
{
|
||||
switch (channel)
|
||||
{
|
||||
case TIM_CHANNEL_1:
|
||||
timer->Instance->CCR1 = value;
|
||||
break;
|
||||
case TIM_CHANNEL_2:
|
||||
timer->Instance->CCR2 = value;
|
||||
break;
|
||||
case TIM_CHANNEL_3:
|
||||
timer->Instance->CCR3 = value;
|
||||
break;
|
||||
case TIM_CHANNEL_4:
|
||||
timer->Instance->CCR4 = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Keypad Scanning //
|
||||
/////////////////////
|
||||
|
||||
struct { GPIO_TypeDef *port; uint32_t pin; }
|
||||
rows[] = {
|
||||
{ GPIOC, GPIO_PIN_7 },
|
||||
{ GPIOA, GPIO_PIN_9 },
|
||||
{ GPIOA, GPIO_PIN_8 },
|
||||
{ GPIOB, GPIO_PIN_10 }
|
||||
},
|
||||
cols[] = {
|
||||
{ GPIOB, GPIO_PIN_4 },
|
||||
{ GPIOB, GPIO_PIN_5 },
|
||||
{ GPIOB, GPIO_PIN_3 },
|
||||
{ GPIOA, GPIO_PIN_10 }
|
||||
};
|
||||
|
||||
void InitializeKeypad() {
|
||||
// rows are outputs, columns are inputs and are pulled low so they don't "float"
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
InitializePin(rows[i].port, rows[i].pin, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, 0);
|
||||
InitializePin(cols[i].port, cols[i].pin, GPIO_MODE_INPUT, GPIO_PULLDOWN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int ReadKeypad() {
|
||||
// scan a 4x4 key matrix by applying a voltage to each row in succession and seeing which column is active
|
||||
// (should work with a 4x3 matrix, since last column will just return zero)
|
||||
for (int row = 0; row < 4; ++row) {
|
||||
// enable the pin for (only) this row
|
||||
for (int i = 0; i < 4; ++i)
|
||||
HAL_GPIO_WritePin(rows[i].port, rows[i].pin, i == row); // all low except the row we care about
|
||||
for (int col = 0; col < 4; ++col) // check all the column pins to see if any are high
|
||||
if (HAL_GPIO_ReadPin(cols[col].port, cols[col].pin))
|
||||
return row*4+col;
|
||||
}
|
||||
return -1; // none of the keys were pressed
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// 7-Segment Display //
|
||||
///////////////////////
|
||||
|
||||
struct { GPIO_TypeDef *port; uint32_t pin; }
|
||||
segments[] = {
|
||||
{ GPIOA, GPIO_PIN_0 }, // A
|
||||
{ GPIOA, GPIO_PIN_1 }, // B
|
||||
{ GPIOA, GPIO_PIN_4 }, // C
|
||||
{ GPIOB, GPIO_PIN_0 }, // D
|
||||
{ GPIOC, GPIO_PIN_1 }, // E
|
||||
{ GPIOC, GPIO_PIN_0 }, // F
|
||||
{ GPIOB, GPIO_PIN_8 }, // G
|
||||
{ GPIOB, GPIO_PIN_9 }, // H (also called DP)
|
||||
};
|
||||
|
||||
// for each digit, we have a byte (uint8_t) which stores which segments are on and off
|
||||
// (bits are ABCDEFGH, right to left, so the low-order bit is segment A)
|
||||
uint8_t digitmap[10] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7C, 0x07, 0x7F, 0x67 };
|
||||
|
||||
void Initialize7Segment() {
|
||||
for (int i = 0; i < 8; ++i)
|
||||
InitializePin(segments[i].port, segments[i].pin, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, 0);
|
||||
}
|
||||
|
||||
void Display7Segment(int digit) {
|
||||
int value = 0; // by default, don't turn on any segments
|
||||
if (digit >= 0 && digit <= 9) // see if it's a valid digit
|
||||
value = digitmap[digit]; // convert digit to a byte which specifies which segments are on
|
||||
//value = ~value; // uncomment this line for common-anode displays
|
||||
// go through the segments, turning them on or off depending on the corresponding bit
|
||||
for (int i = 0; i < 8; ++i)
|
||||
HAL_GPIO_WritePin(segments[i].port, segments[i].pin, (value >> i) & 0x01); // move bit into bottom position and isolate it
|
||||
}
|
||||
|
||||
/////////
|
||||
// ADC //
|
||||
/////////
|
||||
|
||||
// Written by Rodolfo Pellizzoni, September 2021
|
||||
|
||||
void InitializeADC(ADC_HandleTypeDef* adc, ADC_TypeDef* whichAdc) // whichADC might be ADC1, for example
|
||||
{
|
||||
adc->Instance = whichAdc;
|
||||
adc->Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
|
||||
adc->Init.Resolution = ADC_RESOLUTION_12B;
|
||||
adc->Init.ScanConvMode = DISABLE;
|
||||
adc->Init.ContinuousConvMode = DISABLE;
|
||||
adc->Init.DiscontinuousConvMode = DISABLE;
|
||||
adc->Init.NbrOfDiscConversion = 1;
|
||||
adc->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
|
||||
adc->Init.ExternalTrigConv = ADC_SOFTWARE_START;
|
||||
adc->Init.DataAlign = ADC_DATAALIGN_RIGHT;
|
||||
adc->Init.NbrOfConversion = 1;
|
||||
adc->Init.DMAContinuousRequests = DISABLE;
|
||||
adc->Init.EOCSelection = ADC_EOC_SINGLE_CONV;
|
||||
HAL_ADC_Init(adc);
|
||||
}
|
||||
|
||||
// read from the specified ADC channel
|
||||
|
||||
uint16_t ReadADC(ADC_HandleTypeDef* adc, uint32_t channel) // channel might be ADC_CHANNEL_1 for example
|
||||
{
|
||||
ADC_ChannelConfTypeDef sConfig = {0};
|
||||
sConfig.Channel = channel;
|
||||
sConfig.Rank = 1;
|
||||
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
|
||||
HAL_ADC_ConfigChannel(adc, &sConfig);
|
||||
HAL_ADC_Start(adc);
|
||||
HAL_ADC_PollForConversion(adc, HAL_MAX_DELAY);
|
||||
uint16_t res = HAL_ADC_GetValue(adc);
|
||||
HAL_ADC_Stop(adc);
|
||||
return res;
|
||||
}
|
||||
|
||||
34
src/ece198.h
Normal file
34
src/ece198.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Header file for ece198.c
|
||||
|
||||
// Written by Bernie Roehl, July 2021
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "stm32f4xx_hal.h"
|
||||
|
||||
void InitializePin(GPIO_TypeDef *port, uint16_t pins, uint32_t mode, uint32_t pullups, uint8_t alternate);
|
||||
|
||||
HAL_StatusTypeDef SerialSetup(uint32_t baudrate);
|
||||
|
||||
char SerialGetc();
|
||||
void SerialGets(char *buff, int maxlen);
|
||||
|
||||
void SerialPutc(char c);
|
||||
void SerialPuts(char *ptr);
|
||||
|
||||
// macro for reading an entire port (we provide this so students don't need to know about structs)
|
||||
#define ReadPort(port) (port->IDR)
|
||||
|
||||
int ReadEncoder(GPIO_TypeDef *clkport, int clkpin, GPIO_TypeDef *dtport, int dtpin, bool *previous);
|
||||
|
||||
void InitializePWMTimer(TIM_HandleTypeDef *timer, TIM_TypeDef *whichTimer, uint16_t period, uint16_t prescale);
|
||||
void InitializePWMChannel(TIM_HandleTypeDef *timer, uint32_t channel);
|
||||
void SetPWMDutyCycle(TIM_HandleTypeDef *timer, uint32_t channel, uint32_t value);
|
||||
|
||||
void InitializeKeypad();
|
||||
int ReadKeypad();
|
||||
|
||||
void Initialize7Segment();
|
||||
void Display7Segment(int digit);
|
||||
|
||||
void InitializeADC(ADC_HandleTypeDef* adc, ADC_TypeDef* whichAdc);
|
||||
uint16_t ReadADC(ADC_HandleTypeDef* adc, uint32_t channel);
|
||||
267
src/project.c
Normal file
267
src/project.c
Normal file
@@ -0,0 +1,267 @@
|
||||
|
||||
#include <stdbool.h> // booleans, i.e. true and false
|
||||
#include <stdio.h> // sprintf() function
|
||||
#include <stdlib.h> // srand() and random() functions
|
||||
|
||||
#include "ece198.h"
|
||||
#include "Sound.h"
|
||||
#include "LiquidCrystal.h"
|
||||
#include "Display.h"
|
||||
#include "Servo.h"
|
||||
|
||||
// Structure for a button, which stores the port and pins
|
||||
struct button {
|
||||
GPIO_TypeDef *port;
|
||||
uint16_t pin;
|
||||
};
|
||||
|
||||
struct button buttons[4];
|
||||
|
||||
// Check current button inputs, button combinations are stored as 4 bit numbers
|
||||
// return a 4 bit number
|
||||
// eg. 15 = 1111 (button 1, 2, 3, 4)
|
||||
// eg. 3 = 0011 (button 3, 4)
|
||||
int check_button_input(){
|
||||
return (!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) * 8 +
|
||||
!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) * 4 +
|
||||
!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) * 2 +
|
||||
!HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0));
|
||||
}
|
||||
|
||||
// initialize all ports, inputs, outputs
|
||||
void initialize(){
|
||||
|
||||
HAL_Init(); // initialize the Hardware Abstraction Layer
|
||||
|
||||
// Declare button Pins & Ports
|
||||
buttons[0].port = GPIOA;
|
||||
buttons[0].pin = GPIO_PIN_0;
|
||||
buttons[1].port = GPIOA;
|
||||
buttons[1].pin = GPIO_PIN_1;
|
||||
buttons[2].port = GPIOA;
|
||||
buttons[2].pin = GPIO_PIN_4;
|
||||
buttons[3].port = GPIOB;
|
||||
buttons[3].pin = GPIO_PIN_0;
|
||||
|
||||
__HAL_RCC_GPIOA_CLK_ENABLE(); // enable port A (for the buttons)
|
||||
__HAL_RCC_GPIOB_CLK_ENABLE(); // enable port B (for the LCD Display, and buttons)
|
||||
__HAL_RCC_GPIOC_CLK_ENABLE(); // enable port C (for the on-board blue pushbutton, servo, and speaker)
|
||||
|
||||
SerialSetup(9600);
|
||||
|
||||
// inputs for buttons
|
||||
InitializePin(GPIOA, GPIO_PIN_0, GPIO_MODE_INPUT, GPIO_PULLUP, 0); // A0
|
||||
InitializePin(GPIOA, GPIO_PIN_1, GPIO_MODE_INPUT, GPIO_PULLUP, 0); // A1
|
||||
InitializePin(GPIOA, GPIO_PIN_4, GPIO_MODE_INPUT, GPIO_PULLUP, 0); // A4
|
||||
InitializePin(GPIOB, GPIO_PIN_0, GPIO_MODE_INPUT, GPIO_PULLUP, 0); // B0
|
||||
|
||||
// Servo Motors
|
||||
InitializePin(GPIOC, GPIO_PIN_1, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, 0);
|
||||
|
||||
// Initialize LCD
|
||||
LiquidCrystal(GPIOB, GPIO_PIN_8, GPIO_PIN_9, GPIO_PIN_10, GPIO_PIN_3, GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6);
|
||||
|
||||
// Initialize Speaker
|
||||
InitializePin(GPIOC, GPIO_PIN_9, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, 0); // put a 120 ohm resistor on the speaker so it doesn't draw too much current
|
||||
}
|
||||
|
||||
// Return a number between index from and to
|
||||
int random_number(int from, int to) {
|
||||
return (random() % (to) + from);
|
||||
}
|
||||
|
||||
// if current button combination matches note, return true
|
||||
bool button_match_note(int current_button, int note){
|
||||
return current_button == note;
|
||||
}
|
||||
|
||||
// set current_round_tune and current_round_size
|
||||
void set_tune(int current_round_tune[], int tune_to_set[], int *current_round_size, int size_to_set){
|
||||
(*current_round_size) = size_to_set;
|
||||
for(int i = 0; i < (*current_round_size); i++){
|
||||
current_round_tune[i] = tune_to_set[i];
|
||||
}
|
||||
}
|
||||
|
||||
// main function
|
||||
// contains main loop
|
||||
int main(void){
|
||||
initialize(); // Initialize all ports, inputs, outputs
|
||||
|
||||
// press on board blue button to start
|
||||
while (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)){
|
||||
}
|
||||
|
||||
srand(HAL_GetTick()); // seed for randon number
|
||||
|
||||
int note_b = 0;
|
||||
int note_a = 0;
|
||||
int note_c = 0; // combination for each note
|
||||
int note_d = 0;
|
||||
int note_e = 0;
|
||||
int note_f = 0;
|
||||
|
||||
// Shuffle a known array of valid button combinations to get randomiozed buttons for the game
|
||||
int validNumbers[6] = {3, 5, 6, 9, 10, 12};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int n1 = random_number(0, 5);
|
||||
int n2 = random_number(0, 5);
|
||||
int temp = validNumbers[n1];
|
||||
validNumbers[n1] = validNumbers[n2];
|
||||
validNumbers[n2] = temp;
|
||||
}
|
||||
note_a = validNumbers[0];
|
||||
note_b = validNumbers[1];
|
||||
note_c = validNumbers[2];
|
||||
note_d = validNumbers[3];
|
||||
note_e = validNumbers[4];
|
||||
note_f = validNumbers[5];
|
||||
|
||||
// Display the first round notes on the LCD
|
||||
displayNotes(note_a, note_b, note_c, note_d, note_e, note_f);
|
||||
|
||||
int current_button = 0; // current button combination
|
||||
|
||||
int round_1_tune[3] = {note_a, note_b, note_c}; // note combination for each round
|
||||
int round_2_tune[4] = {note_a, note_b, note_c, note_d};
|
||||
int round_3_tune[5] = {note_a, note_b, note_c, note_d, note_e};
|
||||
|
||||
int round_1_size = 3; // number of notes for each round
|
||||
int round_2_size = 4;
|
||||
int round_3_size = 5;
|
||||
|
||||
int current_round_tune[5] = {0, 0, 0, 0, 0}; // note combination for current round
|
||||
|
||||
int current_round_size = 0;
|
||||
|
||||
int current_note_index = 0; // which note in the tune the player is currently on
|
||||
|
||||
int current_round = 1; // current round for the player, corresponds to round_#_tune
|
||||
bool current_round_win = false; // if player won the current round
|
||||
|
||||
bool all_rounds_win = false;
|
||||
|
||||
int *p_current_round_size = ¤t_round_size;
|
||||
set_tune(current_round_tune, round_1_tune, p_current_round_size, round_1_size); // setup tune for first round
|
||||
|
||||
closePrizeDoor(); // Ensure the prize door is locked before game start
|
||||
|
||||
|
||||
// main loop
|
||||
while(!all_rounds_win){ // while player has not won yet
|
||||
// Play the round's note on the speaker
|
||||
if(current_round_tune[current_note_index] == note_a){
|
||||
playNote(NOTE_A, 1000);
|
||||
}
|
||||
else if(current_round_tune[current_note_index] == note_b){
|
||||
playNote(NOTE_B, 1000);
|
||||
}
|
||||
else if(current_round_tune[current_note_index] == note_c){
|
||||
playNote(NOTE_C, 1000);
|
||||
}
|
||||
else if(current_round_tune[current_note_index] == note_d){
|
||||
playNote(NOTE_D, 1000);
|
||||
}
|
||||
else if(current_round_tune[current_note_index] == note_e){
|
||||
playNote(NOTE_E, 1000);
|
||||
}
|
||||
else if(current_round_tune[current_note_index] == note_f){
|
||||
playNote(NOTE_F, 1000);
|
||||
}
|
||||
|
||||
while(current_button != note_a && current_button != note_b && current_button != note_c && current_button != note_d && current_button != note_e && current_button != note_f){
|
||||
// check which button combination player pressed
|
||||
current_button = check_button_input();
|
||||
}
|
||||
|
||||
// check if button matches any note, if there is a match, play it on speaker
|
||||
if(button_match_note(current_button, note_a)){
|
||||
playNote(NOTE_A, 1000);
|
||||
}
|
||||
else if(button_match_note(current_button, note_b)){
|
||||
playNote(NOTE_B, 1000);
|
||||
}
|
||||
else if(button_match_note(current_button, note_c)){
|
||||
playNote(NOTE_C, 1000);
|
||||
}
|
||||
else if(button_match_note(current_button, note_d)){
|
||||
playNote(NOTE_D, 1000);
|
||||
}
|
||||
else if(button_match_note(current_button, note_e)){
|
||||
playNote(NOTE_E, 1000);
|
||||
}
|
||||
else if(button_match_note(current_button, note_f)){
|
||||
playNote(NOTE_F, 1000);
|
||||
}
|
||||
else{ // if there is no match
|
||||
// play a long, low note to indicate a wrong button combination
|
||||
playNote(50, 2000);
|
||||
}
|
||||
|
||||
// check if player input matches tune
|
||||
if(current_button == current_round_tune[current_note_index]){ // if player played correct in the tune
|
||||
current_note_index ++;
|
||||
}
|
||||
else{ // if player plays wrong note
|
||||
current_note_index = 0; // reset to start of round
|
||||
}
|
||||
|
||||
// check if player wins current round
|
||||
if(current_note_index == current_round_size){ // if player has played all the notes for the round
|
||||
current_round_win = true;
|
||||
// Play a win tune
|
||||
playNote(NOTE_A, 500);
|
||||
playNote(NOTE_B, 500);
|
||||
playNote(NOTE_C, 500);
|
||||
HAL_Delay(100);
|
||||
playNote(NOTE_C, 500);
|
||||
HAL_Delay(100);
|
||||
playNote(NOTE_C, 500);
|
||||
HAL_Delay(100);
|
||||
playNote(NOTE_C, 500);
|
||||
|
||||
if(current_round == 3){ // if player wins last round
|
||||
all_rounds_win = true;
|
||||
}
|
||||
current_note_index = 0; // reset index to beginning for next round
|
||||
}
|
||||
|
||||
// set up next round
|
||||
if(current_round_win){ // if player wins the current round
|
||||
if(current_round == 1){ // if player won round 1
|
||||
int *p_current_round_size = ¤t_round_size;
|
||||
set_tune(current_round_tune, round_2_tune, p_current_round_size, round_2_size); // setup round 2
|
||||
}
|
||||
else if(current_round == 2){
|
||||
int *p_current_round_size = ¤t_round_size;
|
||||
set_tune(current_round_tune, round_3_tune, p_current_round_size, round_3_size);
|
||||
}
|
||||
current_round ++;
|
||||
current_round_win = false;
|
||||
}
|
||||
current_button = 0; // reset current button
|
||||
HAL_Delay(1000);
|
||||
}
|
||||
|
||||
openPrizeDoor();
|
||||
|
||||
// when the player wins all rounds
|
||||
// play sound for winning
|
||||
playNote(NOTE_A, 500);
|
||||
playNote(NOTE_B, 500);
|
||||
playNote(NOTE_C, 500);
|
||||
playNote(NOTE_D, 500);
|
||||
playNote(NOTE_E, 500);
|
||||
playNote(NOTE_F, 500);
|
||||
HAL_Delay(100);
|
||||
playNote(NOTE_F, 500);
|
||||
HAL_Delay(100);
|
||||
playNote(NOTE_F, 500);
|
||||
HAL_Delay(100);
|
||||
playNote(NOTE_F, 500);
|
||||
}
|
||||
|
||||
// This function is called by the HAL once every millisecond
|
||||
void SysTick_Handler(void) {
|
||||
HAL_IncTick(); // tell HAL that a new tick has happened
|
||||
}
|
||||
19
src/project.h
Normal file
19
src/project.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// check current button inputs
|
||||
// return a 4 bit number
|
||||
// eg. 15 = 1111
|
||||
int check_button_input();
|
||||
|
||||
// initialize all ports, inputs, outputs
|
||||
void initialize();
|
||||
|
||||
// return a random number from 1-15
|
||||
int random_number();
|
||||
|
||||
// if current button combination matches note, return true
|
||||
bool button_match_note(int current_button, int note);
|
||||
|
||||
// set current_round_tune and current_round_size
|
||||
void set_tune(int current_round_tune[], int tune_to_set[], int *current_round_size, int size_to_set);
|
||||
|
||||
// contains main loop
|
||||
int main();
|
||||
Reference in New Issue
Block a user