Initial commit

This commit is contained in:
jslightham
2023-05-21 23:42:48 -04:00
commit efa6c14b2f
13 changed files with 1299 additions and 0 deletions

14
platformio.ini Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 = &current_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 = &current_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 = &current_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
View 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();