From efa6c14b2f3ba5fde8561cb5e2732bc218357fd7 Mon Sep 17 00:00:00 2001 From: jslightham Date: Sun, 21 May 2023 23:42:48 -0400 Subject: [PATCH] Initial commit --- platformio.ini | 14 ++ src/Display.c | 119 +++++++++++++++ src/Display.h | 2 + src/LiquidCrystal.c | 358 ++++++++++++++++++++++++++++++++++++++++++++ src/LiquidCrystal.h | 95 ++++++++++++ src/Servo.c | 25 ++++ src/Servo.h | 5 + src/Sound.c | 14 ++ src/Sound.h | 39 +++++ src/ece198.c | 308 +++++++++++++++++++++++++++++++++++++ src/ece198.h | 34 +++++ src/project.c | 267 +++++++++++++++++++++++++++++++++ src/project.h | 19 +++ 13 files changed, 1299 insertions(+) create mode 100644 platformio.ini create mode 100644 src/Display.c create mode 100644 src/Display.h create mode 100644 src/LiquidCrystal.c create mode 100644 src/LiquidCrystal.h create mode 100644 src/Servo.c create mode 100644 src/Servo.h create mode 100644 src/Sound.c create mode 100644 src/Sound.h create mode 100644 src/ece198.c create mode 100644 src/ece198.h create mode 100644 src/project.c create mode 100644 src/project.h diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..eb63291 --- /dev/null +++ b/platformio.ini @@ -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 diff --git a/src/Display.c b/src/Display.c new file mode 100644 index 0000000..2659bd9 --- /dev/null +++ b/src/Display.c @@ -0,0 +1,119 @@ +#include "Display.h" +#include +#include +#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); +} \ No newline at end of file diff --git a/src/Display.h b/src/Display.h new file mode 100644 index 0000000..61eb6c7 --- /dev/null +++ b/src/Display.h @@ -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); diff --git a/src/LiquidCrystal.c b/src/LiquidCrystal.c new file mode 100644 index 0000000..23386a9 --- /dev/null +++ b/src/LiquidCrystal.c @@ -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 +#include + +// 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(); +} diff --git a/src/LiquidCrystal.h b/src/LiquidCrystal.h new file mode 100644 index 0000000..fe3cfea --- /dev/null +++ b/src/LiquidCrystal.h @@ -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 +#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 diff --git a/src/Servo.c b/src/Servo.c new file mode 100644 index 0000000..7eb23b4 --- /dev/null +++ b/src/Servo.c @@ -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); + } +} \ No newline at end of file diff --git a/src/Servo.h b/src/Servo.h new file mode 100644 index 0000000..b4db7dd --- /dev/null +++ b/src/Servo.h @@ -0,0 +1,5 @@ +// Lock the prize door +void closePrizeDoor(void); + +// Unlock the prize door +void openPrizeDoor(void); \ No newline at end of file diff --git a/src/Sound.c b/src/Sound.c new file mode 100644 index 0000000..35f87e1 --- /dev/null +++ b/src/Sound.c @@ -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++; + } +} diff --git a/src/Sound.h b/src/Sound.h new file mode 100644 index 0000000..578c876 --- /dev/null +++ b/src/Sound.h @@ -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); diff --git a/src/ece198.c b/src/ece198.c new file mode 100644 index 0000000..593a67f --- /dev/null +++ b/src/ece198.c @@ -0,0 +1,308 @@ +// Support routines for ECE 198 (University of Waterloo) + +// Written by Bernie Roehl, July 2021 + +#include // 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; +} + diff --git a/src/ece198.h b/src/ece198.h new file mode 100644 index 0000000..8e49c8f --- /dev/null +++ b/src/ece198.h @@ -0,0 +1,34 @@ +// Header file for ece198.c + +// Written by Bernie Roehl, July 2021 + +#include +#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); \ No newline at end of file diff --git a/src/project.c b/src/project.c new file mode 100644 index 0000000..c5afdb8 --- /dev/null +++ b/src/project.c @@ -0,0 +1,267 @@ + +#include // booleans, i.e. true and false +#include // sprintf() function +#include // 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 +} diff --git a/src/project.h b/src/project.h new file mode 100644 index 0000000..1e64e40 --- /dev/null +++ b/src/project.h @@ -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(); \ No newline at end of file