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