Import YEEP-144 library 1.2 sources

Source-only snapshot of the 1.2 release. Drivers/ (the vendored ST HAL and
CMSIS) is excluded at 173 MB; fetch it from STMicroelectronics/STM32CubeH5 to
build.
This commit is contained in:
2026-10-28 14:37:42 +00:00
commit 50e4ba8a07
56 changed files with 7718 additions and 0 deletions

166
YEEP144lib/src/YEEP144.c Normal file
View File

@@ -0,0 +1,166 @@
#include "YEEP144.h"
#include "stm32h563xx.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_gpio.h"
#include "stm32h5xx_hal_rcc.h"
#include "stm32h5xx_hal_tim.h"
// Private Function Prototypes
void SystemClock_Config(void);
void GPIO_Init(void);
#define SYSTICK_LOAD (SystemCoreClock/1000000U)
#define SYSTICK_DELAY_CALIB (85)// Determined experimentally
/**
* This function initialises the minimum required for basic operation
* Each feature on top of this will be initialised by its own init function
*/
int YEEP144_Init(void){
HAL_Init();
SystemClock_Config();
return 0;
}
/**
* Initialises the specified GPIO with basic configuration. This assumes it will be one of the standard modes
* with no pull-ups or pull-downs, and a low frequency output
*/
void YEEP144_GPIO_Init(GPIO_TypeDef* port, uint32_t pins, uint32_t mode){
// Initialise GPIO pins
// Create structure to hold GPIO init data
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = pins; // Pins used
GPIO_InitStruct.Mode = mode; // Set pin mode
GPIO_InitStruct.Pull = GPIO_NOPULL; // No pull-ups or pull-downs
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // Low speed is sufficient for this task
// Initialise the port
HAL_GPIO_Init(port, &GPIO_InitStruct);
}
/**
* Initialises the specified GPIO with access to all of the configuration options, allowing you to configure
* pull-ups / pull-downs, GPIO speed, and access alternate functions such as timer IO, SPI, I2C etc
*/
void YEEP144_GPIO_InitAdv(GPIO_TypeDef* port, uint32_t pins, uint32_t mode, uint32_t pull, uint32_t speed, uint32_t alternate){
// Initialise GPIO pins
// Create structure to hold GPIO init data
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = pins; // Pins used
GPIO_InitStruct.Mode = mode; // Set pin mode
GPIO_InitStruct.Pull = pull; // Set pullup / pulldown
GPIO_InitStruct.Speed = speed; // Set speed - low is good for up to 8MHz
GPIO_InitStruct.Alternate = alternate; // Set alternate mode
// Initialise the port
HAL_GPIO_Init(port, &GPIO_InitStruct);
}
/**
* Initialises and configures the system clocks for standard operation
*
* By default we are configured for the maximum clock speed of 250MHz on the core
* and on the APBx peripheral and timer clocks
*
* As we do not have a HSE clock due to the configuration of the Nucleo board
* all clocks are based from the internal oscillators, primarily the HSI clock
* where possible as it should give use the most stable clock from the options available
*
* This code is mostly copied from an MX generated project
*/
void SystemClock_Config(void){
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_CSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.CSIState = RCC_CSI_ON;
RCC_OscInitStruct.CSICalibrationValue = RCC_CSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLL1_SOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 32;
RCC_OscInitStruct.PLL.PLLN = 250;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1_VCIRANGE_1;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
//Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
//Error_Handler();
}
/** Configure the programming delay
*/
__HAL_FLASH_SET_PROGRAM_DELAY(FLASH_PROGRAMMING_DELAY_2);
// Enable all GPIO clocks
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOI_CLK_ENABLE();
}
/**
* This function provides a delay in uS instead of ms
* It should only be used for small delays <1ms
*/
void delayUS(uint32_t us){
uint32_t startCount = SysTick->VAL;
uint32_t targetCount = startCount - (us * SYSTICK_LOAD) + SYSTICK_DELAY_CALIB;
// Handle wraparounds
// If the targetCount is greater than the LOAD value, this means the previous subtraction
// has underflowed
if(targetCount > SysTick->LOAD){
// Calculate a new targetCount taking the wraparound into account
targetCount = SysTick->LOAD - (us * SYSTICK_LOAD) + startCount + (SYSTICK_DELAY_CALIB);
// Wait until the SysTick reloads
while(SysTick->VAL < startCount);
startCount = SysTick->LOAD;
}
// Wait until we count down until the targetCount
while((SysTick->VAL > targetCount) && (SysTick->VAL < startCount));
}

View File

@@ -0,0 +1,126 @@
#include "YEEP144.h"
#include "YEEP144_Pinout.h"
#include "YEEP144_ADCPot.h"
#include "stm32h5xx_hal_adc.h"
#include "stm32h5xx_hal_gpio.h"
#define ADC_TIMEOUT 1000
ADC_HandleTypeDef hadc2;
/**
* Initialise the potentiometers. This consumes ADC2, along with 2 GPIO
*/
void YEEP144_ADCPot_Init(void){
// Setup and enable the ADC peripheral clock
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADCDAC;
PeriphClkInitStruct.AdcDacClockSelection = RCC_ADCDACCLKSOURCE_HSI; // HSI = 64MHz input clock
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
__HAL_RCC_ADC_CLK_ENABLE();
// Configure and initialise the ADC peripheral
hadc2.Instance = ADC2;
hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; // No clock division = 64MHz
hadc2.Init.Resolution = ADC_RESOLUTION_12B; // 12-bit resolution (maximum)
hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc2.Init.LowPowerAutoWait = DISABLE;
hadc2.Init.ContinuousConvMode = DISABLE;
hadc2.Init.NbrOfConversion = 2; // 2 channels in use
hadc2.Init.DiscontinuousConvMode = DISABLE;
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START; // Conversion triggered by software
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc2.Init.DMAContinuousRequests = DISABLE; // No DMA
hadc2.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc2.Init.OversamplingMode = ENABLE; // Enable oversampling
hadc2.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_256; // 256x oversampling gets us 4 extra bits of resolution
hadc2.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_8; // Shift by 8 to get us back to 12-bit output
hadc2.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
hadc2.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_CONTINUED_MODE;
if (HAL_ADC_Init(&hadc2) != HAL_OK){
//Error_Handler();
}
// Initialise the GPIO used
YEEP144_GPIO_Init(POT1_PORT, POT1_PIN | POT2_PIN, GPIO_MODE_ANALOG);
}
/**
* Gets the current value of potentiometer 1.
* Returns a 12-bit value in the range 0 - 4095.
* This blocks until the conversion is complete, ~1ms
*/
uint16_t YEEP144_ADCPot_GetPot1(void){
// Configure this channel to be converted next
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
// With a 64MHz clock and 256x oversampling, 247.5 cycles sample gives us a conversion time of ~1ms
// or a maximum sample rate of ~1kHz
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK){
//Error_Handler();
}
// Start the conversion
HAL_ADC_Start(&hadc2);
// Wait until the conversion is finished
HAL_ADC_PollForConversion(&hadc2, ADC_TIMEOUT);
// Grab the converted value now conversion is finished
uint16_t adcval = HAL_ADC_GetValue(&hadc2);
// Stop the ADC
HAL_ADC_Stop(&hadc2);
return adcval;
}
/**
* Gets the current value of potentiometer 2.
* Returns a 12-bit value in the range 0 - 4095.
* This blocks until the conversion is complete, ~1ms
*/
uint16_t YEEP144_ADCPot_GetPot2(void){
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_6;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
// With a 64MHz clock and 256x oversampling, 247.5 cycles sample gives us a conversion time of ~1ms
// or a maximum sample rate of ~1kHz
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK){
//Error_Handler();
}
// Start the conversion
HAL_ADC_Start(&hadc2);
// Wait until the conversion is finished
HAL_ADC_PollForConversion(&hadc2, ADC_TIMEOUT);
// Grab the converted value now conversion is finished
uint16_t adcval = HAL_ADC_GetValue(&hadc2);
// Stop the ADC
HAL_ADC_Stop(&hadc2);
return adcval;
}

View File

@@ -0,0 +1,257 @@
// YEEP144 includes
#include "YEEP144.h"
#include "YEEP144_BluetoothUART.h"
#include "YEEP144_Pinout.h"
// HAL includes
#include "stm32h563xx.h"
#include "stm32h5xx.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_cortex.h"
#include "stm32h5xx_hal_dma.h"
#include "stm32h5xx_hal_gpio_ex.h"
#include "stm32h5xx_hal_rcc.h"
#include "stm32h5xx_hal_uart.h"
#include "stm32h5xx_hal_uart_ex.h"
// Std includes
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define UART_POLL_TIMEOUT 1000
// Structure for UART info
UART_HandleTypeDef huart2;
DMA_HandleTypeDef handle_GPDMA1_Channel0;
/**
* Initialises USART2 for use with the Bluetooth Serial addon board
*
* This consumes the USART2 peripheral and all of the UART pins on the comms headers
* as well as Channel 0 on GPDMA1
*/
void YEEP144_BluetoothUART_Init(void){
// DMA clock and interrupt
__HAL_RCC_GPDMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
// Initialise and start the clock
// Structure to hold clock config
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART2; // Configuring USART2
PeriphClkInitStruct.Lptim3ClockSelection = RCC_USART2CLKSOURCE_PCLK1; // Select PCLK1
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
// Enable the clock
__HAL_RCC_USART2_CLK_ENABLE();
// Populate the UART struct with the configuration data
// 115200 8-N-1 with flow control
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200; // 115200 Baud rate
huart2.Init.WordLength = UART_WORDLENGTH_8B; // 8 bit
huart2.Init.Parity = UART_PARITY_NONE; // None parity
huart2.Init.StopBits = UART_STOPBITS_1; // 1 Stop bit
huart2.Init.Mode = UART_MODE_TX_RX; // Full duplex
huart2.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS; // Full flow control (RTS, CTS)
huart2.Init.OverSampling = UART_OVERSAMPLING_8;
huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
if(HAL_UART_Init(&huart2) != HAL_OK){
}
// Initialise GPIO pins
YEEP144_GPIO_InitAdv(UART2_TX_PORT, UART2_RX_PIN | UART2_TX_PIN | UART2_CTS_PIN | UART2_RTS_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF7_USART2);
// Enable USART IRQs
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
HAL_UARTEx_DisableFifoMode(&huart2);
// Populate DMA init structure
handle_GPDMA1_Channel0.Instance = GPDMA1_Channel0;
handle_GPDMA1_Channel0.Init.Request = GPDMA1_REQUEST_USART2_RX;
handle_GPDMA1_Channel0.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
handle_GPDMA1_Channel0.Init.Direction = DMA_PERIPH_TO_MEMORY;
handle_GPDMA1_Channel0.Init.SrcInc = DMA_SINC_FIXED;
handle_GPDMA1_Channel0.Init.DestInc = DMA_DINC_INCREMENTED;
handle_GPDMA1_Channel0.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
handle_GPDMA1_Channel0.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
handle_GPDMA1_Channel0.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
handle_GPDMA1_Channel0.Init.SrcBurstLength = 1;
handle_GPDMA1_Channel0.Init.DestBurstLength = 1;
handle_GPDMA1_Channel0.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
handle_GPDMA1_Channel0.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
handle_GPDMA1_Channel0.Init.Mode = DMA_NORMAL;
if (HAL_DMA_Init(&handle_GPDMA1_Channel0) != HAL_OK)
{
//Error_Handler();
}
__HAL_LINKDMA(&huart2, hdmarx, handle_GPDMA1_Channel0);
if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel0, DMA_CHANNEL_NPRIV) != HAL_OK)
{
//Error_Handler();
}
// Set default character match to carriage return
YEEP144_BluetoothUART_SetCharMatch('\n');
// Disable buffering on stdin. This prevents problems when using scanf with the UART redirection
setvbuf(stdin, NULL, _IONBF, 0);
}
/**
* Prints to the Bluetooth UART, regardless of what the default UART is set to.
* Arguments and returns are the same as the default printf
*
* Internally this doesn't work using a stream like default printf, but uses a buffer, so the command may
* fail if the buffer cannot be allocated, or the output may be truncated if expanding the format specifiers causes
* the string to expand by more than 256 bytes
*/
int YEEP144_BluetoothUART_printf(const char * format, ...){
// Calculate how long the input string is, plus extra space for substitition in the format specifiers
size_t bufSize = strlen(format) + 256;
// Try to allocate that buffer
char* buffer = malloc(bufSize);
// If the buffer cannot be allocated, return -1
if (buffer == NULL) return -1;
// Get the pointer to the variadic args
va_list args;
va_start (args, format);
// Create our final string in the buffer using vsnprintf, passing through our argument pointer
vsnprintf(buffer, bufSize, format, args);
// Calulate the final size of the string
bufSize = strlen(buffer);
// Transmit over UART
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, bufSize, UART_POLL_TIMEOUT);
// Free the memory used
free(buffer);
// Return number of characters printed
return bufSize;
}
/**
* Enables the character match IRQ. When the configured character received an interrupt is fired.
* The current transfer will be terminated and the YEEP144_BluetoothUART_MatchCallback will be called
*/
void YEEP144_BluetoothUART_EnableCharMatchIRQ(void){
__HAL_UART_ENABLE_IT(&huart2, UART_IT_CM);
}
/**
* Disables the character match IRQ
*/
void YEEP144_BluetoothUART_DisableCharMatchIRQ(void){
__HAL_UART_DISABLE_IT(&huart2, UART_IT_CM);
}
/**
* Set the character match IRQ character. When the configured character received an interrupt is fired.
* The current transfer will be terminated and the YEEP144_BluetoothUART_MatchCallback will be called
*/
void YEEP144_BluetoothUART_SetCharMatch(char match){
__HAL_UART_DISABLE(&huart2);
MODIFY_REG(huart2.Instance->CR2, USART_CR2_ADD_Msk, match<< USART_CR2_ADD_Pos);
__HAL_UART_ENABLE(&huart2);
}
/**
* Sets the pre and post delimiters for the BT status messages
* Setting both to null disables the status messages entirely
*/
void YEEP144_BluetoothUART_SetStatusDelimiter(char prefix, char postfix){
// Save this register so we can restore character match IRQ if needed
uint32_t config = READ_REG(huart2.Instance->CR1);
// Disable the character match irq so it doesn't fire during this function
YEEP144_BluetoothUART_DisableCharMatchIRQ();
uint8_t rxBuf[10];
uint16_t rxSize;
// Enter command mode
HAL_UART_Transmit(&huart2, "$$$", 3, UART_POLL_TIMEOUT);
// Wait to receive data back from BT module
HAL_UARTEx_ReceiveToIdle(&huart2, rxBuf, 10, &rxSize, UART_POLL_TIMEOUT);
// Set delimiters
char txBuf[8] = {0};
if(prefix == '\0' && postfix == '\0'){
sprintf(txBuf, "SO,,%c",'\r');
}
else if(prefix == '\0'){
sprintf(txBuf, "SO,,%c%c", postfix, '\r');
}
else if(postfix == '\0'){
sprintf(txBuf, "SO,%c,%c", prefix, '\r');
}
else{
sprintf(txBuf, "SO,%c,%c%c", prefix, postfix, '\r');
}
HAL_UART_Transmit(&huart2, txBuf, sizeof(rxBuf), UART_POLL_TIMEOUT);
// Wait to receive data back from BT module
HAL_UARTEx_ReceiveToIdle(&huart2, rxBuf, 10, &rxSize, UART_POLL_TIMEOUT);
// Reboot device to make changes
HAL_UART_Transmit(&huart2, "R,1", 3, UART_POLL_TIMEOUT);
// Wait to receive data back from BT module
HAL_UARTEx_ReceiveToIdle(&huart2, rxBuf, 10, &rxSize, UART_POLL_TIMEOUT);
// Re-enable the character match IRQ if it previously was
if(config & USART_CR1_CMIE){
YEEP144_BluetoothUART_EnableCharMatchIRQ();
}
}
#if UARTSEL == BLUETOOTHUART
/**
* This function overrides a function from the stdio library, effectively redirecting
* output to the UART port. This means that printf can print directly to the UART.
*/
int __io_putchar(int ch){
HAL_UART_Transmit(&huart2, (uint8_t *) &ch, 1, UART_POLL_TIMEOUT);
return ch;
}
/**
* This function overrides a function from the stdio library, effectively redirecting
* input from the UART port. This means that scanf can read directly from the UART.
*/
int __io_getchar(void){
uint8_t ch = 0;
/* Clear the Overrun flag just before receiving the first character */
__HAL_UART_CLEAR_OREFLAG(&huart2);
/* Wait for reception of a character on the USART RX line and echo this
* character on console */
HAL_UART_Receive(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
#endif

View File

@@ -0,0 +1,135 @@
#include "YEEP144.h"
#include "YEEP144_Pinout.h"
#include "YEEP144_Buzzer.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_gpio.h"
#include "stm32h5xx_hal_gpio_ex.h"
#include "stm32h5xx_hal_tim.h"
#include <math.h>
#include <stdlib.h>
TIM_HandleTypeDef htim17buzzer;
/**
* Initialises the buzzer. This consumes timer 17 and 1 GPIO. This resource is shared with servo 4 - only 1 should be initialised
* Ensure DIP switch SW5 is correctly set to enable the buzzer output. Set both to off when using servo 4 to avoid hearing
* the servo PWM signal through the buzzer
*/
void YEEP144_Buzzer_Init(void){
// Initialise TIM17
// Create structures to store our timer configuration information
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
// Enable the clock input to TIM3
__HAL_RCC_TIM17_CLK_ENABLE();
// Setup our configuration data
// Prescaler is set so that 1 timer tick = 1us for 250MHz input clock, as the frequency calculation assumes this
htim17buzzer.Instance = TIM17;
htim17buzzer.Init.Prescaler = 249;
htim17buzzer.Init.CounterMode = TIM_COUNTERMODE_UP;
htim17buzzer.Init.Period = 65535;
htim17buzzer.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim17buzzer.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
// Initialise the timer
if (HAL_TIM_PWM_Init(&htim17buzzer) != HAL_OK){
//Error_Handler();
}
// Configure the Output Compare (OC) settings
// This determines how the PWM signal is generated based on the count of our timer
sConfigOC.OCMode = TIM_OCMODE_PWM1; // Edge-Aligned mode
sConfigOC.Pulse = 0; // Default CCR value for our duty cycle
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // Non-inverted polarity
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
// Configure the PWM channels
if (HAL_TIM_PWM_ConfigChannel(&htim17buzzer, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){
//Error_Handler();
}
// Disable the break dead time feature as its not needed
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim17buzzer, &sBreakDeadTimeConfig) != HAL_OK){
//Error_Handler();
}
// Setup the GPIO output from the timer
YEEP144_GPIO_InitAdv(BUZZER_PORT, BUZZER_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF1_TIM17);
// Start PWM generation
HAL_TIM_PWM_Start(&htim17buzzer, TIM_CHANNEL_1);
// Default to no tone
YEEP144_Buzzer_NoTone();
}
/**
* Sound the buzzer at the chosen frequency (Hz) for the alloted duration (ms)
* This is blocking as it uses HAL_Delay to wait the alloted time
*/
void YEEP144_Buzzer_Tone(uint32_t freq, uint32_t duration){
YEEP144_Buzzer_GoTone(freq); // Start playing the tone
HAL_Delay(duration); // Wait for tone duration
YEEP144_Buzzer_NoTone(); // Cease playing the tone
}
/**
* Sound the buzzer at the chosen frequency (Hz) continuously until stopped by NoTone
* This is non-blocking, so you can go do other things while the tone is playing
*/
void YEEP144_Buzzer_GoTone(uint32_t freq){
if(freq != 0){
TIM17->ARR = (1000000UL / freq) - 1; // Set PWM frequency
TIM17->CCR1 = (TIM17->ARR >> 1); // Set the duty cycle to 50% by setting Capture Compare Register to half the frequency
}
}
/**
* Stops the buzzer from sounding by setting the duty cycle to 0%
*/
void YEEP144_Buzzer_NoTone(void){
TIM17->CCR1 = 0; // Set duty cycle to 0%
}
/**
* Buzzer rendition of Daisy Bell taken from:
* https://github.com/ayoungblood/daisy-bell/blob/master/arduino/daisy_basic/daisy_basic.ino
*/
#define A440 440 // frequency constant
#define QUARTER 180 // quarter note length in ms
const uint8_t DAISY_P[] = {
74, 71, 67, 62, 64, 66, 67, 64, 67, 62,
69, 74, 71, 67, 64, 66, 67, 69, 71, 69,
71, 72, 71, 69, 74, 71, 69, 67, 69, 71, 67, 64, 67, 64, 62,
62, 67, 71, 69, 67, 71, 69, 71, 72, 74, 71, 67, 69, 62, 67
};
const uint8_t DAISY_T[] = {
3, 3, 3, 3, 1, 1, 1, 2, 1, 6,
3, 3, 3, 3, 1, 1, 1, 2, 1, 6,
1, 1, 1, 1, 2, 1, 1, 4, 1, 2, 1, 2, 1, 1, 5,
1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 5
};
void YEEP144_Buzzer_Daisy(void){
for(int i=0; i<50; ++i){
float freq = pow(2,(DAISY_P[i]-69)/12.0f)*A440;
YEEP144_Buzzer_Tone((uint32_t)freq, DAISY_T[i]*QUARTER);
}
}

View File

@@ -0,0 +1,386 @@
// YEEP Includes
#include "YEEP144.h"
#include "YEEP144_DCMotor.h"
#include "YEEP144_Pinout.h"
// HAL Includes
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_gpio_ex.h"
#include "stm32h5xx_hal_rcc.h"
#include "stm32h5xx_hal_tim.h"
#include "stm32h5xx_hal_lptim.h"
#include "stm32h5xx_hal_gpio.h"
// System Includes
#include <stdint.h>
// Structs for timer handles
// TIM3 used for all 4 DC motors
TIM_HandleTypeDef htim3;
// TIM3, LPTIM2, LPTIM3, LPTIM5 used for encoders
TIM_HandleTypeDef htim4;
LPTIM_HandleTypeDef hlptim2;
LPTIM_HandleTypeDef hlptim3;
LPTIM_HandleTypeDef hlptim5;
/**
* Sets the specified motors speed by altering the duty cycle of the PWM signal
* This is done by setting the Capture and Compare Register for the relevant channel
* to a value between 0 and the Period of the timer where CCR = 0 is 0% duty cycle and
* CCR = Period is 100% duty cycle
*/
void YEEP144_DCMotor_SetSpeed(uint8_t motor, uint16_t speed){
// Cap speed if out of bounds
if(speed > DCMOTOR_MAX_SPEED){
speed = DCMOTOR_MAX_SPEED;
}
// Update the Capture Compare Register (CCR) for the relevant motor channel
switch(motor){
case DCMOTOR1:
TIM3->CCR1 = speed;
break;
case DCMOTOR2:
TIM3->CCR2 = speed;
break;
case DCMOTOR3:
TIM3->CCR3 = speed;
break;
case DCMOTOR4:
TIM3->CCR4 = speed;
break;
}
}
/**
* Sets the two direction bits for the specified motor. There are 4 valid options
* for stopping, counter-clockwise movement, clockwise movement, and a short
* brake. There are #defines available for these states in YEEP144_DCMotor.h
*/
void YEEP144_DCMotor_SetDirection(uint8_t motor, uint8_t direction){
// Check that the inputs are valid
if(direction > 3 || motor > 4){
return;
}
// Set the direction GPIO pins
// Some bit manipulation is used to mask the relevant bit and shift it into the LSB position
if(motor == DCMOTOR1){
HAL_GPIO_WritePin(MOT1A_PORT, MOT1A_PIN, (direction>>1) & 1);
HAL_GPIO_WritePin(MOT1B_PORT, MOT1B_PIN, direction & 1);
}
else if (motor == DCMOTOR2) {
HAL_GPIO_WritePin(MOT2A_PORT, MOT2A_PIN, (direction>>1) & 1);
HAL_GPIO_WritePin(MOT2B_PORT, MOT2B_PIN, direction & 1);
}
else if (motor == DCMOTOR3) {
HAL_GPIO_WritePin(MOT3A_PORT, MOT3A_PIN, (direction>>1) & 1);
HAL_GPIO_WritePin(MOT3B_PORT, MOT3B_PIN, direction & 1);
}
else if (motor == DCMOTOR4) {
HAL_GPIO_WritePin(MOT4A_PORT, MOT4A_PIN, (direction>>1) & 1);
HAL_GPIO_WritePin(MOT4B_PORT, MOT4B_PIN, direction & 1);
}
}
/**
* Gets the current value of encoder 1
*/
uint16_t YEEP144_Encoder1_Get(void){
return TIM4->CNT;
}
/**
* Gets the current value of encoder 2
*/
uint16_t YEEP144_Encoder2_Get(void){
return LPTIM3->CNT;
}
/**
* Gets the current value of encoder 3
*/
uint16_t YEEP144_Encoder3_Get(void){
return LPTIM5->CNT;
}
/**
* Gets the current value of encoder 4
*/
uint16_t YEEP144_Encoder4_Get(void){
return LPTIM2->CNT;
}
/**
* This function initialises all 4 DC motor driver channels
* They all share TIM3 for their PWM generation
* After initialisation the PWM will be set to 0%, and direction set to STOP
*
* This consumes TIM3 and the 4 GPIO it outputs on, as well as the 8 GPIO used for motor direction control
*/
void YEEP144_DCMotor_Init(void){
// Initialise TIM3
// Create structures to store our timer configuration information
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
// Enable the clock input to TIM3
__HAL_RCC_TIM3_CLK_ENABLE();
// Setup our configuration data
// For an input clock of 250MHz and a Period of 4095 these setting will produce
// a PWM signal of ~61kHz which is within the range accepted by the motor driver
// 4095 was chosen as the period as it maps 1-to-1 with the reading from a 12-bit ADC
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = DCMOTOR_MAX_SPEED;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
// Initialise the timer
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK){
//Error_Handler();
}
// Configure master output
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK){
//Error_Handler();
}
// Configure the Output Compare (OC) settings
// This determines how the PWM signal is generated based on the count of our timer
sConfigOC.OCMode = TIM_OCMODE_PWM1; // Edge-Aligned mode
sConfigOC.Pulse = 0; // Default CCR value for our duty cycle
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // Non-inverted polarity
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // Fast mode is not required for this application
// Configure the PWM channels
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){
//Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK){
//Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK){
//Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4) != HAL_OK){
//Error_Handler();
}
// Initialise GPIO pins used for PWM output
// 3 of our PWM pins are on port C and 1 on port B
// We must configure each port separately
YEEP144_GPIO_InitAdv(MOT1PWM_PORT, MOT1PWM_PIN | MOT2PWM_PIN | MOT4PWM_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF2_TIM3);
YEEP144_GPIO_InitAdv(MOT3PWM_PORT, MOT3PWM_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF2_TIM3);
// Initialise GPIO for 2-bit direction control
// The pins used are split across 3 ports
// Each port must be configured separately
YEEP144_GPIO_Init(MOT1A_PORT, MOT1A_PIN | MOT1B_PIN | MOT2A_PIN | MOT2B_PIN, GPIO_MODE_OUTPUT);
YEEP144_GPIO_Init(MOT3A_PORT, MOT3A_PIN | MOT3B_PIN, GPIO_MODE_OUTPUT);
YEEP144_GPIO_Init(MOT4A_PORT, MOT4A_PIN | MOT4B_PIN, GPIO_MODE_OUTPUT);
// Set default outputs to stop
YEEP144_DCMotor_SetDirection(DCMOTOR1, DCMOTOR_STOP);
YEEP144_DCMotor_SetDirection(DCMOTOR2, DCMOTOR_STOP);
YEEP144_DCMotor_SetDirection(DCMOTOR3, DCMOTOR_STOP);
YEEP144_DCMotor_SetDirection(DCMOTOR4, DCMOTOR_STOP);
// Start all the PWM channels
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
}
/**
* This initialises Encoder 1
* This consumes TIM4 and the two GPIO it takes input from
*/
void YEEP144_Encoder1_Init(void){
// Initialise TIM4
// Enable TIM4 clock
__HAL_RCC_TIM4_CLK_ENABLE();
// Create structs to hold config data
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 65535;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
// Configure encoder mode and input channels
sConfig.EncoderMode = TIM_ENCODERMODE_TI1;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 0;
sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 0;
// Initialise the timer
if (HAL_TIM_Encoder_Init(&htim4, &sConfig) != HAL_OK){
//Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK){
//Error_Handler();
}
// Initialise the GPIO used for the encoder input
YEEP144_GPIO_InitAdv(ENC1A_PORT, ENC1A_PIN | ENC1B_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF2_TIM4);
// Start the encoder counting
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
}
/**
* This initialises Encoder 2
* This consumes LPTIM3 and the two GPIO it takes input from
*/
void YEEP144_Encoder2_Init(void){
// Setup clock for LPTIM3
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPTIM3;
PeriphClkInitStruct.Lptim3ClockSelection = RCC_LPTIM3CLKSOURCE_PCLK3;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
// Enable clock for LPTIM3
__HAL_RCC_LPTIM3_CLK_ENABLE();
// Configure LPTIM3 for encoder input mode
hlptim3.Instance = LPTIM3;
hlptim3.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
hlptim3.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
hlptim3.Init.UltraLowPowerClock.Polarity = LPTIM_CLOCKPOLARITY_RISING;
hlptim3.Init.UltraLowPowerClock.SampleTime = LPTIM_CLOCKSAMPLETIME_DIRECTTRANSITION;
hlptim3.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim3.Init.Period = 65535;
hlptim3.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim3.Init.CounterSource = LPTIM_COUNTERSOURCE_EXTERNAL;
hlptim3.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
hlptim3.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
hlptim3.Init.RepetitionCounter = 0;
if (HAL_LPTIM_Init(&hlptim3) != HAL_OK){
//Error_Handler();
}
// Initialise the GPIO used for the encoder input
YEEP144_GPIO_InitAdv(ENC2A_PORT, ENC2A_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF2_LPTIM3);
YEEP144_GPIO_InitAdv(ENC2B_PORT, ENC2B_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF2_LPTIM3);
// Start the encoder counting
HAL_LPTIM_Encoder_Start(&hlptim3);
}
/**
* This initialises Encoder 3
* This consumes LPTIM5 and the two GPIO it takes input from
*/
void YEEP144_Encoder3_Init(void){
// Setup clock for LPTIM5
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPTIM5;
PeriphClkInitStruct.Lptim3ClockSelection = RCC_LPTIM3CLKSOURCE_PCLK3;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
// Enable clock for LPTIM3
__HAL_RCC_LPTIM5_CLK_ENABLE();
// Configure LPTIM5 for encoder input mode
hlptim5.Instance = LPTIM5;
hlptim5.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
hlptim5.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
hlptim5.Init.UltraLowPowerClock.Polarity = LPTIM_CLOCKPOLARITY_RISING;
hlptim5.Init.UltraLowPowerClock.SampleTime = LPTIM_CLOCKSAMPLETIME_DIRECTTRANSITION;
hlptim5.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim5.Init.Period = 65535;
hlptim5.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim5.Init.CounterSource = LPTIM_COUNTERSOURCE_EXTERNAL;
hlptim5.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
hlptim5.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
hlptim5.Init.RepetitionCounter = 0;
if (HAL_LPTIM_Init(&hlptim5) != HAL_OK){
//Error_Handler();
}
// Initialise the GPIO used for the encoder input
YEEP144_GPIO_InitAdv(ENC3A_PORT, ENC3A_PIN | ENC3B_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF13_LPTIM5);
// Start the encoder counting
HAL_LPTIM_Encoder_Start(&hlptim5);
}
/**
* This initialises Encoder 4
* This consumes LPTIM2 and the two GPIO it takes input from
*/
void YEEP144_Encoder4_Init(void){
// Setup clock for LPTIM2
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LPTIM2;
PeriphClkInitStruct.Lptim3ClockSelection = RCC_LPTIM3CLKSOURCE_PCLK3;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
// Enable clock for LPTIM2
__HAL_RCC_LPTIM2_CLK_ENABLE();
// Configure LPTIM2 for encoder input mode
hlptim2.Instance = LPTIM2;
hlptim2.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
hlptim2.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
hlptim2.Init.UltraLowPowerClock.Polarity = LPTIM_CLOCKPOLARITY_RISING;
hlptim2.Init.UltraLowPowerClock.SampleTime = LPTIM_CLOCKSAMPLETIME_DIRECTTRANSITION;
hlptim2.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim2.Init.Period = 65535;
hlptim2.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim2.Init.CounterSource = LPTIM_COUNTERSOURCE_EXTERNAL;
hlptim2.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
hlptim2.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
hlptim2.Init.RepetitionCounter = 0;
if (HAL_LPTIM_Init(&hlptim2) != HAL_OK){
//Error_Handler();
}
// Initialise the GPIO used for the encoder input
YEEP144_GPIO_InitAdv(ENC4A_PORT, ENC4A_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF3_LPTIM2);
YEEP144_GPIO_InitAdv(ENC4B_PORT, ENC4B_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF3_LPTIM2);
// Start the encoder counting
HAL_LPTIM_Encoder_Start(&hlptim2);
}

View File

@@ -0,0 +1,200 @@
// YEEP144 includes
#include "YEEP144.h"
#include "YEEP144_DebugUART.h"
#include "YEEP144_Pinout.h"
// HAL includes
#include "stm32h563xx.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_uart.h"
// Std includes
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#define UART_POLL_TIMEOUT 1000
// Structure for UART info
UART_HandleTypeDef huart3;
DMA_HandleTypeDef handle_GPDMA1_Channel1;
/**
* Initialises USART3 for use as a debug console output via the STLink
* Virtual COM Port (VCP)
*
* This consumes the USART3 peripheral and no YEEP GPIO as it is routed internally
* on the Nucleo board and is not broken out
*/
void YEEP144_DebugUART_Init(void){
// Initialise and start the clock
// DMA clock and interrupt
__HAL_RCC_GPDMA1_CLK_ENABLE();
HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
// Structure to hold clock config
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART3; // Configuring USART3
PeriphClkInitStruct.Lptim3ClockSelection = RCC_USART3CLKSOURCE_PCLK1; // Select PCLK1
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
// Enable the clock
__HAL_RCC_USART3_CLK_ENABLE();
// Populate the UART struct with the configuration data
// 115200 8-N-1
huart3.Instance = USART3;
huart3.Init.BaudRate = 115200; // 115200 Baud rate
huart3.Init.WordLength = UART_WORDLENGTH_8B; // 8 bit
huart3.Init.Parity = UART_PARITY_NONE; // None parity
huart3.Init.StopBits = UART_STOPBITS_1; // 1 Stop bit
huart3.Init.Mode = UART_MODE_TX_RX; // Full duplex
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; // No hardware flow control (RTS, CTS)
huart3.Init.OverSampling = UART_OVERSAMPLING_8;
huart3.Init.ClockPrescaler = UART_PRESCALER_DIV1;
if(HAL_UART_Init(&huart3) != HAL_OK){
}
// Initialise GPIO pins
YEEP144_GPIO_InitAdv(UART3_TX_PORT, UART3_RX_PIN | UART3_TX_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF7_USART3);
// Enable USART IRQs
HAL_NVIC_SetPriority(USART3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
HAL_UARTEx_DisableFifoMode(&huart3);
// Populate DMA init structure
handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_USART3_RX;
handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
handle_GPDMA1_Channel1.Init.Direction = DMA_PERIPH_TO_MEMORY;
handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;
handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_INCREMENTED;
handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
if (HAL_DMA_Init(&handle_GPDMA1_Channel1) != HAL_OK)
{
//Error_Handler();
}
__HAL_LINKDMA(&huart3, hdmarx, handle_GPDMA1_Channel1);
if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV) != HAL_OK)
{
//Error_Handler();
}
// Set default character match to carriage return
YEEP144_DebugUART_SetCharMatch('\r');
// Disable buffering on stdin. This prevents problems when using scanf with the UART redirection
setvbuf(stdin, NULL, _IONBF, 0);
}
/**
* Prints to the Debug UART, regardless of what the default UART is set to.
* Arguments and returns are the same as the default printf
*
* Internally this doesn't work using a stream like default printf, but uses a buffer, so the command may
* fail if the buffer cannot be allocated, or the output may be truncated if expanding the format specifiers causes
* the string to expand by more than 256 bytes
*/
int YEEP144_DebugUART_printf(const char * format, ...){
// Calculate how long the input string is, plus extra space for substitition in the format specifiers
size_t bufSize = strlen(format) + 256;
// Try to allocate that buffer
char* buffer = malloc(bufSize);
// If the buffer cannot be allocated, return -1
if (buffer == NULL) return -1;
// Get the pointer to the variadic args
va_list args;
va_start (args, format);
// Create our final string in the buffer using vsnprintf, passing through our argument pointer
vsnprintf(buffer, bufSize, format, args);
// Calulate the final size of the string
bufSize = strlen(buffer);
// Transmit over UART
HAL_UART_Transmit(&huart3, (uint8_t*)buffer, bufSize, UART_POLL_TIMEOUT);
// Free the memory used
free(buffer);
// Return number of characters printed
return bufSize;
}
/**
* Enables the character match IRQ. When the configured character received an interrupt is fired.
* The current transfer will be terminated and the YEEP144_DebugUART_MatchCallback will be called
*/
void YEEP144_DebugUART_EnableCharMatchIRQ(void){
__HAL_UART_ENABLE_IT(&huart3, UART_IT_CM);
}
/**
* Disables the character match IRQ
*/
void YEEP144_DebugUART_DisableCharMatchIRQ(void){
__HAL_UART_DISABLE_IT(&huart3, UART_IT_CM);
}
/**
* Set the character match IRQ character. When the configured character received an interrupt is fired.
* The current transfer will be terminated and the YEEP144_DebugUART_MatchCallback will be called
*/
void YEEP144_DebugUART_SetCharMatch(char match){
__HAL_UART_DISABLE(&huart3);
MODIFY_REG(huart3.Instance->CR2, USART_CR2_ADD_Msk, match<< USART_CR2_ADD_Pos);
__HAL_UART_ENABLE(&huart3);
}
#if UARTSEL == DEBUG
/**
* This function overrides a function from the stdio library, effectively redirecting
* output to the UART port. This means that printf can print directly to the UART.
*/
int __io_putchar(int ch){
HAL_UART_Transmit(&huart3, (uint8_t *) &ch, 1, UART_POLL_TIMEOUT);
return ch;
}
/**
* This function overrides a function from the stdio library, effectively redirecting
* input from the UART port. This means that scanf can read directly from the UART.
*/
int __io_getchar(void){
uint8_t ch = 0;
/* Clear the Overrun flag just before receiving the first character */
__HAL_UART_CLEAR_OREFLAG(&huart3);
/* Wait for reception of a character on the USART RX line and echo this
* character on console */
HAL_UART_Receive(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
#endif

View File

@@ -0,0 +1,18 @@
#include "YEEP144_GP2Y0E02B.h"
#include "YEEP144_I2C.h"
#include "stm32h5xx_hal_i2c.h"
#include <math.h>
#define I2C_TIMEOUT 1000
float YEEP144_GP2Y0E02B_Read(void){
uint8_t IRShift;
uint8_t IRValues[2] ={0};
HAL_I2C_Mem_Read(&hi2c2, GP2Y0E02B_I2C_ADDR, GP2Y0E02B_SHIFT_REG_ADDR, I2C_MEMADD_SIZE_8BIT, &IRShift, 1, I2C_TIMEOUT);
HAL_I2C_Mem_Read(&hi2c2, GP2Y0E02B_I2C_ADDR, GP2Y0E02B_DISTANCE_REG_ADDR, I2C_MEMADD_SIZE_8BIT, IRValues, 2, I2C_TIMEOUT);
return ((IRValues[0]*16.0) + IRValues[1])/ 16.0 / pow(2.0, IRShift);
}

View File

@@ -0,0 +1,596 @@
#include "YEEP144_HCSR04.h"
#include "YEEP144.h"
#include "YEEP144_Pinout.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_cortex.h"
#include "stm32h5xx_hal_exti.h"
#include "stm32h5xx_hal_gpio.h"
#include "stm32h5xx_hal_tim.h"
#include <stdint.h>
#define USONIC_PING_LEN 10
#define BLOCKING_TIMEOUT_TICKS 1000
#define USONIC1_MASK (1 << 0)
#define USONIC2_MASK (1 << 1)
#define USONIC3_MASK (1 << 2)
#define USONIC4_MASK (1 << 3)
uint8_t ultrasonicInit = 0;
uint8_t ultrasonicTIMInit = 0;
uint8_t ultrasonicNextPing = USONIC1_MASK;
uint8_t ultrasonicPing = 0;
uint8_t ultrasonicEcho = 0;
uint16_t ultrasonicTickStart[4];
uint16_t ultrasonicTickLen[4];
TIM_HandleTypeDef htim6;
EXTI_HandleTypeDef hexti12;
EXTI_ConfigTypeDef hexti12_conf;
void YEEP144_Ultrasonic_TIMInit(void);
void YEEP144_Ultrasonic1_Callback(uint16_t ticks);
void YEEP144_Ultrasonic2_Callback(uint16_t ticks);
void YEEP144_Ultrasonic3_Callback(uint16_t ticks);
void YEEP144_Ultrasonic4_Callback(uint16_t ticks);
/**
* Initialises ultrasonic sensor 1. This consumes the two GPIO assigned to that sensor,
* TIM6 (shared by all 4 ultrasonics), and EXTI line 10 (share by the first 3 ultrasonics)
*/
void YEEP144_Ultrasonic1_Init(void){
YEEP144_GPIO_Init(USONIC1_TRIG_PORT, USONIC1_TRIG_PIN, GPIO_MODE_OUTPUT);
HAL_NVIC_SetPriority(USONIC1_ECHO_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(USONIC1_ECHO_EXTI);
ultrasonicInit |= USONIC1_MASK;
// Only initialise the timer if it hasn't already
if(ultrasonicTIMInit == 0){
YEEP144_Ultrasonic_TIMInit();
}
}
/**
* Initialises ultrasonic sensor 2. This consumes the two GPIO assigned to that sensor,
* TIM6 (shared by all 4 ultrasonics), and EXTI line 10 (share by the first 3 ultrasonics)
*/
void YEEP144_Ultrasonic2_Init(void){
YEEP144_GPIO_Init(USONIC2_TRIG_PORT, USONIC2_TRIG_PIN, GPIO_MODE_OUTPUT);
HAL_NVIC_SetPriority(USONIC2_ECHO_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(USONIC2_ECHO_EXTI);
ultrasonicInit |= USONIC2_MASK;
// Only initialise the timer if it hasn't already
if(ultrasonicTIMInit == 0){
YEEP144_Ultrasonic_TIMInit();
}
}
/**
* Initialises ultrasonic sensor 3. This consumes the two GPIO assigned to that sensor,
* TIM6 (shared by all 4 ultrasonics), and EXTI line 10 (share by the first 3 ultrasonics)
*/
void YEEP144_Ultrasonic3_Init(void){
YEEP144_GPIO_Init(USONIC3_TRIG_PORT, USONIC3_TRIG_PIN, GPIO_MODE_OUTPUT);
HAL_NVIC_SetPriority(USONIC3_ECHO_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(USONIC4_ECHO_EXTI);
ultrasonicInit |= USONIC3_MASK;
// Only initialise the timer if it hasn't already
if(ultrasonicTIMInit == 0){
YEEP144_Ultrasonic_TIMInit();
}
}
/**
* Initialises ultrasonic sensor 4. This consumes the two GPIO assigned to that sensor,
* TIM6 (shared by all 4 ultrasonics), and EXTI line 12 (shared with button 3 / red)
*/
void YEEP144_Ultrasonic4_Init(void){
YEEP144_GPIO_Init(USONIC4_TRIG_PORT, USONIC4_TRIG_PIN, GPIO_MODE_OUTPUT);
HAL_NVIC_SetPriority(USONIC4_ECHO_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(USONIC4_ECHO_EXTI);
ultrasonicInit |= USONIC4_MASK;
// Only initialise the timer if it hasn't already
if(ultrasonicTIMInit == 0){
YEEP144_Ultrasonic_TIMInit();
}
}
/**
* Starts a reading on the next sensor in the sequence using the non-blocking method, skipping over any uninitialised sensors
* Only 1 sensor can be read at a time as they (mostly) share the same EXTI line, so this function
* checks to make sure that noting else is currently pinging.
*
* If no sensor is currently initialised returns 255
* If a sensor is already pinging, return 0
* If a new reading is successfully started, return the status byte indicating which sensor is currently pinging
*
* The relevant callback function will be called when pinging finishes and a new distance value is available
*/
uint8_t YEEP144_Ultrasonic_PingNext(void){
// If nothing is currently pinging
if(!ultrasonicPing){
// What is the next in the sequence
switch(ultrasonicNextPing){
case USONIC1_MASK:
// Only start if it is enabled
if(ultrasonicInit&USONIC1_MASK){
// Reinitialise the pin to setup the interrupt
YEEP144_GPIO_Init(USONIC1_ECHO_PORT, USONIC1_ECHO_PIN, GPIO_MODE_IT_RISING_FALLING);
// Toggle the pin on for 10uS
HAL_GPIO_WritePin(USONIC1_TRIG_PORT, USONIC1_TRIG_PIN, GPIO_PIN_SET);
delayUS(USONIC_PING_LEN);
HAL_GPIO_WritePin(USONIC1_TRIG_PORT, USONIC1_TRIG_PIN, GPIO_PIN_RESET);
// Set the ping bit so we know this sensor is pinging
ultrasonicPing |= USONIC1_MASK;
// Move to next sensor
ultrasonicNextPing = USONIC2_MASK;
break; // Break is within the if statement, so if a sensor is not initialised it will fall through to the next
}
else{
// Move to next sensor
ultrasonicNextPing = USONIC2_MASK;
}
// fallthrough
case USONIC2_MASK:
if(ultrasonicInit&USONIC2_MASK){
// Reinitialise the pin to setup the interrupt
YEEP144_GPIO_Init(USONIC2_ECHO_PORT, USONIC2_ECHO_PIN, GPIO_MODE_IT_RISING_FALLING);
// Toggle the pin on for 10uS
HAL_GPIO_WritePin(USONIC2_TRIG_PORT, USONIC2_TRIG_PIN, GPIO_PIN_SET);
delayUS(USONIC_PING_LEN);
HAL_GPIO_WritePin(USONIC2_TRIG_PORT, USONIC2_TRIG_PIN, GPIO_PIN_RESET);
// Set the ping bit so we know this sensor is pinging
ultrasonicPing |= USONIC2_MASK;
// Move to next sensor
ultrasonicNextPing = USONIC3_MASK;
break; // Break is within the if statement, so if a sensor is not initialised it will fall through to the next
}
else{
// Move to next sensor
ultrasonicNextPing = USONIC3_MASK;
}
// fallthrough
case USONIC3_MASK:
if(ultrasonicInit&USONIC3_MASK){
// Reinitialise the pin to setup the interrupt
YEEP144_GPIO_Init(USONIC3_ECHO_PORT, USONIC3_ECHO_PIN, GPIO_MODE_IT_RISING_FALLING);
// Toggle the pin on for 10uS
HAL_GPIO_WritePin(USONIC3_TRIG_PORT, USONIC3_TRIG_PIN, GPIO_PIN_SET);
delayUS(USONIC_PING_LEN);
HAL_GPIO_WritePin(USONIC3_TRIG_PORT, USONIC3_TRIG_PIN, GPIO_PIN_RESET);
// Set the ping bit so we know this sensor is pinging
ultrasonicPing |= USONIC3_MASK;
// Move to next sensor
ultrasonicNextPing = USONIC4_MASK;
break; // Break is within the if statement, so if a sensor is not initialised it will fall through to the next
}
else{
// Move to next sensor
ultrasonicNextPing = USONIC4_MASK;
}
// fallthrough
case USONIC4_MASK:
if(ultrasonicInit&USONIC4_MASK){
// As the interrupt line for ultrasonic 4 is shared with pushbutton 3,
// we want to save the EXTI configuration to restore when pinging is finished
HAL_EXTI_GetHandle(&hexti12, EXTI_LINE_12);
HAL_EXTI_GetConfigLine(&hexti12, &hexti12_conf);
// Reinitialise the pin to setup the interrupt
YEEP144_GPIO_Init(USONIC4_ECHO_PORT, USONIC4_ECHO_PIN, GPIO_MODE_IT_RISING_FALLING);
// Toggle the pin on for 10uS
HAL_GPIO_WritePin(USONIC4_TRIG_PORT, USONIC4_TRIG_PIN, GPIO_PIN_SET);
delayUS(USONIC_PING_LEN);
HAL_GPIO_WritePin(USONIC4_TRIG_PORT, USONIC4_TRIG_PIN, GPIO_PIN_RESET);
// Set the ping bit so we know this sensor is pinging
ultrasonicPing |= USONIC4_MASK;
// Move to next sensor
ultrasonicNextPing = USONIC1_MASK;
break; // Break is within the if statement, so if a sensor is not initialised it will fall through to the next
}
else{
// Move to next sensor
ultrasonicNextPing = USONIC1_MASK;
}
}
// If a ping was successfully started return the status byte, else return 255
if(!ultrasonicPing){
return ultrasonicPing;
}
else{
// This should only happen if this function is called with no sensor initialised
return 255;
}
}
else{
// Returns 0 if a sensor is already pinging
return 0;
}
}
/**
* Starts a reading on ultrasonic 1 using the non-blocking method
*
* If no sensor is currently initialised returns 255
* If a sensor is already pinging, return 0
* If a new reading is successfully started, return the status byte indicating which sensor is currently pinging
*
* YEEP144_Ultrasonic1_Callback will be called when a new value is available
*/
uint8_t YEEP144_Ultrasonic1_Ping(void){
ultrasonicNextPing = USONIC1_MASK;
return YEEP144_Ultrasonic_PingNext();
}
/**
* Starts a reading on ultrasonic 2 using the non-blocking method
*
* If no sensor is currently initialised returns 255
* If a sensor is already pinging, return 0
* If a new reading is successfully started, return the status byte indicating which sensor is currently pinging
*
* YEEP144_Ultrasonic2_Callback will be called when a new value is available
*/
uint8_t YEEP144_Ultrasonic2_Ping(void){
ultrasonicNextPing = USONIC2_MASK;
return YEEP144_Ultrasonic_PingNext();
}
/**
* Starts a reading on ultrasonic 3 using the non-blocking method
*
* If no sensor is currently initialised returns 255
* If a sensor is already pinging, return 0
* If a new reading is successfully started, return the status byte indicating which sensor is currently pinging
*
* YEEP144_Ultrasonic3_Callback will be called when a new value is available
*/
uint8_t YEEP144_Ultrasonic3_Ping(void){
ultrasonicNextPing = USONIC3_MASK;
return YEEP144_Ultrasonic_PingNext();
}
/**
* Starts a reading on ultrasonic 4 using the non-blocking method
*
* If no sensor is currently initialised returns 255
* If a sensor is already pinging, return 0
* If a new reading is successfully started, return the status byte indicating which sensor is currently pinging
*
* YEEP144_Ultrasonic4_Callback will be called when a new value is available
*/
uint8_t YEEP144_Ultrasonic4_Ping(void){
ultrasonicNextPing = USONIC4_MASK;
return YEEP144_Ultrasonic_PingNext();
}
/**
* Starts a reading on ultrasonic 1 with blocking waits until the new value is available
*
* Returns 0 if the sensor is not initialised, or the function times out, otherwise returns the tick value
*
* YEEP144_Ultrasonic1_Callback will still be called when the new value is available
*
*/
uint16_t YEEP144_Ultrasonic1_PingBlocking(void){
uint32_t blockingTimeoutStart = HAL_GetTick();
uint8_t pingReturn;
// Attempt pings until successful
// If 255 is returned, sensor is uninitialised, so fail and return 0
// If we exceed timeout then return 0;
do{
pingReturn = YEEP144_Ultrasonic1_Ping();
if(pingReturn == 255) return 0;
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
} while(pingReturn != USONIC1_MASK);
// Wait until pinging starts
// If we exceed timeout then return 0
while(ultrasonicPing != USONIC1_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Wait until pinging ends
// If we exceed timeout then return 0
while(ultrasonicPing == USONIC1_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Return the read value
return ultrasonicTickLen[0];
}
/**
* Starts a reading on ultrasonic 2 with blocking waits until the new value is available
*
* Returns 0 if the sensor is not initialised, or the function times out, otherwise returns the tick value
*
* YEEP144_Ultrasonic1_Callback will still be called when the new value is available
*
*/
uint16_t YEEP144_Ultrasonic2_PingBlocking(void){
uint32_t blockingTimeoutStart = HAL_GetTick();
uint8_t pingReturn;
// Attempt pings until successful
// If 255 is returned, sensor is uninitialised, so fail and return 0
// If we exceed timeout then return 0;
do{
pingReturn = YEEP144_Ultrasonic2_Ping();
if(pingReturn == 255) return 0;
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
} while(pingReturn != USONIC2_MASK);
// Wait until pinging starts
// If we exceed timeout then return 0
while(ultrasonicPing != USONIC2_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Wait until pinging ends
// If we exceed timeout then return 0
while(ultrasonicPing == USONIC2_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Return the read value
return ultrasonicTickLen[1];
}
/**
* Starts a reading on ultrasonic 3 with blocking waits until the new value is available
*
* Returns 0 if the sensor is not initialised, or the function times out, otherwise returns the tick value
*
* YEEP144_Ultrasonic1_Callback will still be called when the new value is available
*
*/
uint16_t YEEP144_Ultrasonic3_PingBlocking(void){
uint32_t blockingTimeoutStart = HAL_GetTick();
uint8_t pingReturn;
// Attempt pings until successful
// If 255 is returned, sensor is uninitialised, so fail and return 0
// If we exceed timeout then return 0;
do{
pingReturn = YEEP144_Ultrasonic3_Ping();
if(pingReturn == 255) return 0;
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
} while(pingReturn != USONIC3_MASK);
// Wait until pinging starts
// If we exceed timeout then return 0
while(ultrasonicPing != USONIC3_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Wait until pinging ends
// If we exceed timeout then return 0
while(ultrasonicPing == USONIC3_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Return the read value
return ultrasonicTickLen[2];
}
/**
* Starts a reading on ultrasonic 4 with blocking waits until the new value is available
*
* Returns 0 if the sensor is not initialised, or the function times out, otherwise returns the tick value
*
* YEEP144_Ultrasonic1_Callback will still be called when the new value is available
*
*/
uint16_t YEEP144_Ultrasonic4_PingBlocking(void){
uint32_t blockingTimeoutStart = HAL_GetTick();
uint8_t pingReturn;
// Attempt pings until successful
// If 255 is returned, sensor is uninitialised, so fail and return 0
// If we exceed timeout then return 0;
do{
pingReturn = YEEP144_Ultrasonic4_Ping();
if(pingReturn == 255) return 0;
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
} while(pingReturn != USONIC4_MASK);
// Wait until pinging starts
// If we exceed timeout then return 0
while(ultrasonicPing != USONIC4_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Wait until pinging ends
// If we exceed timeout then return 0
while(ultrasonicPing == USONIC4_MASK){
if((HAL_GetTick()- blockingTimeoutStart) >= BLOCKING_TIMEOUT_TICKS) return 0;
}
// Return the read value
return ultrasonicTickLen[3];
}
/**
* This function handles the interrupt for sensors 1, 2, and 3 connected to EXTI 10
* This should be called from the main EXTI10 IRQ handler found in YEEP144_IRQ.c
*/
void YEEP144_Ultrasonic_EXTI10Handler(void){
// Store the value from the timer at the start of the ISR to avoid execution time affecting the result
uint16_t TIMVal = __HAL_TIM_GET_COUNTER(&htim6);
// Rising edge interrupt means pulse is starting
if(__HAL_GPIO_EXTI_GET_RISING_IT(USONIC1_ECHO_PIN)){
// Check to see which sensor is currently pinging based on the GPIO and the ping status byte
if((HAL_GPIO_ReadPin(USONIC1_ECHO_PORT, USONIC1_ECHO_PIN) == GPIO_PIN_SET) && (ultrasonicPing & USONIC1_MASK)){
// Store the starting timer value and set the relevant status bit
ultrasonicTickStart[0] = TIMVal;
ultrasonicEcho |= USONIC1_MASK;
}
if((HAL_GPIO_ReadPin(USONIC2_ECHO_PORT, USONIC2_ECHO_PIN) == GPIO_PIN_SET) && (ultrasonicPing & USONIC2_MASK)){
ultrasonicTickStart[1] = TIMVal;
ultrasonicEcho |= USONIC2_MASK;
}
if((HAL_GPIO_ReadPin(USONIC3_ECHO_PORT, USONIC3_ECHO_PIN) == GPIO_PIN_SET) && (ultrasonicPing & USONIC3_MASK)){
ultrasonicTickStart[2] = TIMVal;
ultrasonicEcho |= USONIC3_MASK;
}
__HAL_GPIO_EXTI_CLEAR_RISING_IT(USONIC1_ECHO_PIN); // Clear the interrupt
return;
}
// Falling edge interrupt means pulse is ending
else if(__HAL_GPIO_EXTI_GET_FALLING_IT(USONIC1_ECHO_PIN)){
// Check which sensor is pinging based on the GPIO and status byte
if((HAL_GPIO_ReadPin(USONIC1_ECHO_PORT, USONIC1_ECHO_PIN) == GPIO_PIN_RESET) && (ultrasonicEcho & USONIC1_MASK)){
// Reset the status bits
ultrasonicPing &= ~USONIC1_MASK;
ultrasonicEcho &= ~USONIC1_MASK;
ultrasonicTickLen[0] = TIMVal - ultrasonicTickStart[0]; // Calculate the time elapsed
YEEP144_Ultrasonic1_Callback(ultrasonicTickLen[0]); // Call the relevant callback function to signal the reading is done
}
if((HAL_GPIO_ReadPin(USONIC2_ECHO_PORT, USONIC2_ECHO_PIN) == GPIO_PIN_RESET) && (ultrasonicEcho & USONIC2_MASK)){
ultrasonicPing &= ~USONIC2_MASK;
ultrasonicEcho &= ~USONIC2_MASK;
ultrasonicTickLen[1] = TIMVal - ultrasonicTickStart[1];
YEEP144_Ultrasonic2_Callback(ultrasonicTickLen[1]);
}
if((HAL_GPIO_ReadPin(USONIC3_ECHO_PORT, USONIC3_ECHO_PIN) == GPIO_PIN_RESET) && (ultrasonicEcho & USONIC3_MASK)){
ultrasonicPing &= ~USONIC3_MASK;
ultrasonicEcho &= ~USONIC3_MASK;
ultrasonicTickLen[2] = TIMVal - ultrasonicTickStart[2];
YEEP144_Ultrasonic3_Callback(ultrasonicTickLen[2]);
}
__HAL_GPIO_EXTI_CLEAR_FALLING_IT(USONIC1_ECHO_PIN); // Clear the interrupt
return;
}
}
/**
* This function handles the interrupt for sensor 4 connected to EXTI 12
* This should be called from the main EXTI12 IRQ handler found in YEEP144_IRQ.c
*/
void YEEP144_Ultrasonic_EXTI12Handler(void){
// Store the value from the timer at the start of the ISR to avoid execution time affecting the result
uint16_t TIMVal = __HAL_TIM_GET_COUNTER(&htim6);
// Rising edge interrupt means pulse is starting
if(__HAL_GPIO_EXTI_GET_RISING_IT(USONIC4_ECHO_PIN)){
// Check to see which sensor is currently pinging based on the GPIO and the ping status byte
if((HAL_GPIO_ReadPin(USONIC4_ECHO_PORT, USONIC4_ECHO_PIN) == GPIO_PIN_SET) && (ultrasonicPing & USONIC4_MASK)){
// Store the starting timer value and set the relevant status bit
ultrasonicTickStart[3] = TIMVal;
ultrasonicEcho |= USONIC4_MASK;
}
__HAL_GPIO_EXTI_CLEAR_RISING_IT(USONIC4_ECHO_PIN); // Clear the interrupt
return;
}
// Falling edge interrupt means pulse is ending
else if(__HAL_GPIO_EXTI_GET_FALLING_IT(USONIC4_ECHO_PIN)){
// Check which sensor is pinging based on the GPIO and status byte
if((HAL_GPIO_ReadPin(USONIC4_ECHO_PORT, USONIC4_ECHO_PIN) == GPIO_PIN_RESET) && (ultrasonicEcho & USONIC4_MASK)){
// Reset the status bits
ultrasonicPing &= ~USONIC4_MASK;
ultrasonicEcho &= ~USONIC4_MASK;
ultrasonicTickLen[3] = TIMVal - ultrasonicTickStart[3]; // Calculate the time elapsed
YEEP144_Ultrasonic4_Callback(ultrasonicTickLen[3]); // Call the relevant callback function to signal the reading is done
}
__HAL_GPIO_EXTI_CLEAR_FALLING_IT(USONIC4_ECHO_PIN); // Clear the interrupt
// Restore the previous exti configuration
HAL_EXTI_GetHandle(&hexti12, EXTI_LINE_12);
HAL_EXTI_SetConfigLine(&hexti12, &hexti12_conf);
return;
}
}
/**
* Helper function to convert timer ticks to a centimeter value
*/
float YEEP144_Ultrasonic_TickTocm(uint16_t ticks){
// Total number of ticks is return trip time, we halve it to get distance to object
// Speed of sound taken as 343m/s = 34300 cm/s = 0.0343 cm/us
return ticks/2.0 * 0.0343;
}
/**
* Internal function used to initialise the timer used for measuring the received pulses
*/
void YEEP144_Ultrasonic_TIMInit(void){
// Enable the clock to the timer
__HAL_RCC_TIM6_CLK_ENABLE();
// Create empty config structure
TIM_MasterConfigTypeDef sMasterConfig = {0};
// Populate config structure. The should configure it for 1uS per tick, assuming 250MHz peripheral clock
htim6.Instance = TIM6;
htim6.Init.Prescaler = 249;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 65535;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK){
//Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK){
//Error_Handler();
}
// Start the counting. This counter is left free running as relevant functions
// calculate time elapsed by recording the value at the start and end of an event
HAL_TIM_Base_Start(&htim6);
// Set the init flag so the timer doesn't get initialised multiple times
ultrasonicTIMInit = 1;
}
/**
* These callback functions are weakly defined so they can be overwritten by the user's
* own callback function. They have to exist at least in weak form so that the code compiles
* even if the user chooses not to define their own
*/
__weak void YEEP144_Ultrasonic1_Callback(uint16_t ticks){
}
__weak void YEEP144_Ultrasonic2_Callback(uint16_t ticks){
}
__weak void YEEP144_Ultrasonic3_Callback(uint16_t ticks){
}
__weak void YEEP144_Ultrasonic4_Callback(uint16_t ticks){
}

View File

@@ -0,0 +1,70 @@
#include "YEEP144.h"
#include "YEEP144_I2C.h"
#include "YEEP144_Pinout.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_gpio_ex.h"
#include "stm32h5xx_hal_i2c.h"
#include <stdint.h>
I2C_HandleTypeDef hi2c2;
void YEEP144_I2C_MuxSelect(uint8_t busNumber){
uint8_t I2CBusSel;
// If busNumber is greater than 3, deselect all busses
if(busNumber > 3){
I2CBusSel = 0;
}
else{ // Else convert to 1-hot to activate bus
I2CBusSel = 1 << busNumber;
}
// Send byte to mux chip to select bus
HAL_I2C_Master_Transmit(&hi2c2, I2C_MUX_ADDR, &I2CBusSel, 1, 1000);
}
void YEEP144_I2C_Init(void){
// Initialise and start the clock
// Structure to hold clock config
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2C2; // Configuring I2C2
PeriphClkInitStruct.Lptim3ClockSelection = RCC_I2C2CLKSOURCE_PCLK1; // Select PCLK1
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
// Enable the clock
__HAL_RCC_I2C2_CLK_ENABLE();
// Configure the peripheral
hi2c2.Instance = I2C2;
hi2c2.Init.Timing = 0x60808CD3; // Magic number taken from MX for 100KHz - see Reference Manual RM0481, section 48.4.5
// We are not operating as a target, so the rest are just defaults
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c2.Init.OwnAddress2 = 0;
hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c2) != HAL_OK){
//Error_Handler();
}
//Configure Analogue filter (Enabled, which is default)
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK){
//Error_Handler();
}
//Configure Digital filter (Disabled, which is default)
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK){
//Error_Handler();
}
// Initialise GPIO pins
YEEP144_GPIO_InitAdv(I2C_SDA_PORT, I2C_SDA_PIN | I2C_SCL_PIN, GPIO_MODE_AF_OD, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF4_I2C2);
}

View File

@@ -0,0 +1,241 @@
/**
* This file contains all of the IRQ handlers that are required for various parts
* of the HAL and YEEP libraries to function. Unhandled IRQs will cause the system
* to jump to the default handler, trapping it in an infinite loop
*/
#include "YEEP144_HCSR04.h"
#include "YEEP144_PushButton.h"
#include "YEEP144_PiSPI.h"
#include "stm32h563xx.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_dma.h"
#include "stm32h5xx_hal_spi.h"
#include "stm32h5xx_hal_uart.h"
extern DMA_HandleTypeDef handle_GPDMA1_Channel0;
extern DMA_HandleTypeDef handle_GPDMA1_Channel1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3;
extern SPI_HandleTypeDef hspi4;
void YEEP144_BluetoothUART_MatchCallback(void);
void YEEP144_DebugUART_MatchCallback(void);
/**
* Handler for the SysTick interrupt
* The HAL configures the SysTick to generate an interrupt every 1ms
* HAL_IncTick needs to be called in here for the HAL to function properly
*/
void SysTick_Handler(void){
HAL_IncTick();
}
/**
* Handler for EXTI line 6. In the YEEP libraries this is used by the yellow pushbutton
*/
void EXTI6_IRQHandler(void){
YEEP144_PushButton_YellowHandler();
}
/**
* Handler for EXTI line 7. In the YEEP libraries this is used by the green pushbutton
*/
void EXTI7_IRQHandler(void){
YEEP144_PushButton_GreenHandler();
}
/**
* Handler for EXTI line 10. In the YEEP libraries this is used by ultrasonic sensors 1, 2, and 3
*/
void EXTI10_IRQHandler(void){
YEEP144_Ultrasonic_EXTI10Handler();
}
/**
* Handler for EXTI line 12. In the YEEP libraries this is used by ultrasonic sensor 4, and pushbutton 3 / red
*/
void EXTI12_IRQHandler(void){
YEEP144_PushButton_RedHandler();
YEEP144_Ultrasonic_EXTI12Handler();
}
/**
* Handler for EXTI line 14. In the YEEP libraries this is used by the blue pushbutton
*/
void EXTI13_IRQHandler(void){
YEEP144_PushButton_BlueHandler();
}
/**
* Handler for GPDMA1 channel 0. In the YEEP libraries this is used by the BT UART reception
* We just call the relevant HAL handler to let it deal with it
*/
void GPDMA1_CH0_IRQHandler(void){
HAL_DMA_IRQHandler(&handle_GPDMA1_Channel0);
}
/**
* Handler for GPDMA1 channel 0. In the YEEP libraries this is used by the BT UART reception
* We just call the relevant HAL handler to let it deal with it
*/
void GPDMA1_CH1_IRQHandler(void){
HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);
}
/**
* Handler for SPI4 IRQ. We just call the relevant HAL handler to let it deal with it
*/
void SPI4_IRQHandler(void){
HAL_SPI_IRQHandler(&hspi4);
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi){
if(hspi->Instance == SPI4){
YEEP144_PiSPI_TxRxHandler();
}
}
/**
* Handler for USART2. In the YEEP libraries this is used by the BT UART.
* We do our own checks for the character match interrupt as this is not handled
* by the HAL. For any other interrupt we just let the HAL handle it
*/
void USART2_IRQHandler(void){
uint32_t isrflags = READ_REG(huart2.Instance->ISR);
uint32_t cr1its = READ_REG(huart2.Instance->CR1);
// If the character match is enabled and character match flag raised
if (((isrflags & USART_ISR_CMF) != 0U) && ((cr1its & USART_CR1_CMIE) != 0U)){
__HAL_UART_CLEAR_FLAG(&huart2, UART_CLEAR_CMF); // Clear the character match flag
HAL_DMA_Abort(huart2.hdmarx); // End the current DMA
// Reset the state of the UART HAL
huart2.RxState = HAL_UART_STATE_READY;
huart2.ReceptionType = HAL_UART_RECEPTION_STANDARD;
// Clear the idle line bit so the reception can be rearmed
ATOMIC_CLEAR_BIT(huart2.Instance->CR1, USART_CR1_IDLEIE);
// Call the match callback
YEEP144_BluetoothUART_MatchCallback();
return;
}
// If this is not the character match IRQ, let the HAL handle it
HAL_UART_IRQHandler(&huart2);
}
/**
* Handler for USART3. In the YEEP libraries this is used by the BT UART.
* We do our own checks for the character match interrupt as this is not handled
* by the HAL. For any other interrupt we just let the HAL handle it
*/
void USART3_IRQHandler(void){
uint32_t isrflags = READ_REG(huart3.Instance->ISR);
uint32_t cr1its = READ_REG(huart3.Instance->CR1);
// If the character match is enabled and character match flag raised
if (((isrflags & USART_ISR_CMF) != 0U) && ((cr1its & USART_CR1_CMIE) != 0U)){
__HAL_UART_CLEAR_FLAG(&huart3, UART_CLEAR_CMF); // Clear the character match flag
HAL_DMA_Abort(huart3.hdmarx); // End the current DMA
// Reset the state of the UART HAL
huart3.RxState = HAL_UART_STATE_READY;
huart3.ReceptionType = HAL_UART_RECEPTION_STANDARD;
// Clear the idle line bit so the reception can be rearmed
ATOMIC_CLEAR_BIT(huart3.Instance->CR1, USART_CR1_IDLEIE);
// Call the match callback
YEEP144_DebugUART_MatchCallback();
return;
}
// If this is not the character match IRQ, let the HAL handle it
HAL_UART_IRQHandler(&huart3);
}
/**
* These weakly defined callback functions are here so that the code always compiles
* You shouldn't edit these functions, but instead implement your own with the exact same name
* in one of your own files, like main.c
*/
__weak void YEEP144_BluetoothUART_MatchCallback(void){
return;
}
__weak void YEEP144_DebugUART_MatchCallback(void){
return;
}
/**
* These functions copied from STM's interrupt code to provide stubs for handling
* the internal Cortex Processor interrupts and exceptions
*/
/**
* This function handles Non maskable interrupt.
*/
void NMI_Handler(void){
while (1){
}
}
/**
* This function handles Hard fault interrupt.
*/
void HardFault_Handler(void){
while (1){
}
}
/**
* This function handles Memory management fault.
*/
void MemManage_Handler(void){
while (1){
}
}
/**
* This function handles Prefetch fault, memory access fault.
*/
void BusFault_Handler(void){
while (1){
}
}
/**
* This function handles Undefined instruction or illegal state.
*/
void UsageFault_Handler(void){
while (1){
}
}
/**
* This function handles System service call via SWI instruction.
*/
void SVC_Handler(void){
}
/**
* This function handles Debug monitor.
*/
void DebugMon_Handler(void){
}
/**
* This function handles Pendable request for system service.
*/
void PendSV_Handler(void){
}

View File

@@ -0,0 +1,173 @@
#include "YEEP144_Pinout.h"
#include "stm32h563xx.h"
#include "stm32h5xx.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_cortex.h"
#include "stm32h5xx_hal_def.h"
#include "stm32h5xx_hal_gpio.h"
#include "stm32h5xx_hal_gpio_ex.h"
#include "stm32h5xx_hal_rcc.h"
#include "stm32h5xx_hal_spi.h"
#include "YEEP144.h"
#include "YEEP144_PiSPI.h"
#include "stm32h5xx_hal_spi_ex.h"
#include <string.h>
void YEEP144_PiSPI_RxCallback(void);
SPI_HandleTypeDef hspi4;
uint8_t PiSPIRxBuf[PISPI_BUFFER_SIZE];
uint8_t PiSPITxBuf[PISPI_BUFFER_SIZE];
uint8_t PiSPITxBufInternal[PISPI_BUFFER_SIZE];
/**
* Initialises SPI communications for use with an attached Raspberry Pi. In this configuration the Pi will act as the master
* therefore the STM32 will be configured as a slave. This means that the master can send / ask for data at any time, and the SPI
* peripheral must be ready to avoid under/overruns.
*/
void YEEP144_PiSPI_Init(void){
// Setup and enable clock
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI4;
PeriphClkInitStruct.Spi4ClockSelection = RCC_SPI4CLKSOURCE_HSI; // HSI = 64MHz clock
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK){
//Error_Handler();
}
__HAL_RCC_SPI4_CLK_ENABLE();
// Setup GPIO
YEEP144_GPIO_InitAdv(SPI_MOSI_PORT, SPI_MOSI_PIN | SPI_MISO_PIN | SPI_SCK_PIN , GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF5_SPI4);
YEEP144_GPIO_Init(SPI_CS_PORT, SPI_CS_PIN, GPIO_MODE_OUTPUT);
YEEP144_PiSPI_ResetReady();
// Enable interrupt
HAL_NVIC_SetPriority(SPI4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI4_IRQn);
hspi4.Instance = SPI4;
hspi4.Init.Mode = SPI_MODE_SLAVE; // Slave mode
hspi4.Init.Direction = SPI_DIRECTION_2LINES; // Full duplex transmission
hspi4.Init.DataSize = SPI_DATASIZE_8BIT; // Standard 8-bit word size
hspi4.Init.CLKPolarity = SPI_POLARITY_LOW; // Clock polarity and phase set for SPI mode 0
hspi4.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi4.Init.NSS = SPI_NSS_SOFT; // Chip Select managed by software - we assume we are always selected so the CS line can be repurposed for the ready signal
hspi4.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB first
hspi4.Init.TIMode = SPI_TIMODE_DISABLE; // Not using TI mode
hspi4.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // Not using CRC
hspi4.Init.CRCPolynomial = 0x7;
hspi4.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; // Chip select is held not pulsed
hspi4.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; // Chip select is active low
hspi4.Init.FifoThreshold = SPI_FIFO_THRESHOLD_04DATA; // FIFO threshold is 4-bytes which is the maximum
hspi4.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; // Master configuration is defaults as we aren't master
hspi4.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
hspi4.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
hspi4.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
hspi4.Init.IOSwap = SPI_IO_SWAP_DISABLE; // Don't need to swap IO pins
hspi4.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY; // We aren't using the hardware ready signal
hspi4.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;
if (HAL_SPI_Init(&hspi4) != HAL_OK){
//Error_Handler();
}
// Set up our interrupt to wait for start of transfer from the master
HAL_SPI_TransmitReceive_IT(&hspi4, PiSPITxBufInternal, PiSPIRxBuf, PISPI_BUFFER_SIZE);
// Set the ready flag as we are now ready to communicate
YEEP144_PiSPI_SetReady();
}
/**
* Resets the ready signal to ask the master to stop sending / requesting data
*/
void YEEP144_PiSPI_ResetReady(void){
HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_RESET);
}
/**
* Sets the ready signal to tell the master we are ready to receive data
*/
void YEEP144_PiSPI_SetReady(void){
HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_SET);
}
/**
* Loads the latest data into the SPI peripheral. Call this function every time you change PiSPITxBuf.
*/
void YEEP144_PiSPI_LoadTx(void){
/**
* As we cannot know when the master will initiate a transfer, we must always be ready to receive and send data
* to avoid overrun when receiving, or underrun when sending. This means that the SPI peripheral must be active
* with a transfer pending before the master begins a transfer by begining the clock and sending data, and
* this pending transfer must be kept active as much of the time as possible. This means that we always start a
* transfer with HAL_SPI_TransmitReceive_IT at the end of SPI initialisation, and immediately after the previous
* transfer has finished in the relevant ISR.
*
* When a transfer is started with HAL_SPI_TransmitReceive_IT, the first x bytes from the transmit buffer is
* pre-loaded into the SPI peripheral's internal TX FIFO, with x depending on the FIFO Threshold configuration,
* to avoid any issues with data underrun. This means that if data in the transmit buffer is changed after a
* transfer is started, the first x bytes will remain from the state of the buffer when the transfer was started,
* leading to messages with a mix of old and new data.
*
* In order to avoid this, the transmit data is double buffered, so that the user code cannot change the data while
* a transfer is in progress, and this function aborts the transfer, which flushes the FIFO, and copies the front
* buffer into the back buffer, before restarting the transfer, which prevents the buffer changeover from also
* occurring mid transaction.
*
* This means there is a small window each time this function is called, and shortly after the end of a transfer
* where the SPI will not be active as the data is changed over. The ready signal should be reset during this period
* which will lead to a small delay on the master side
*/
// Wait for current transfer to end
while(((SPI4->SR & SPI_SR_CTSIZE_Msk) >> SPI_SR_CTSIZE_Pos) != PISPI_BUFFER_SIZE){}
// Clear the ready signal to tell the master we are no longer ready
YEEP144_PiSPI_ResetReady();
// Stop the current transfer which flushes the old data from the FIFO
HAL_SPI_Abort_IT(&hspi4);
// Copy the user facing buffer into the internal buffer
memcpy(PiSPITxBufInternal, PiSPITxBuf, PISPI_BUFFER_SIZE);
// Restart the transfer now the buffer has been updated
HAL_SPI_TransmitReceive_IT(&hspi4, PiSPITxBufInternal, PiSPIRxBuf, PISPI_BUFFER_SIZE);
// Set the ready signal to tell the master we are now ready to continue communication
YEEP144_PiSPI_SetReady();
}
/**
* This function should be called by HAL_SPI_TxRxCpltCallback in YEEP144_IRQ to handle the end of a transfer
* It loads the latest data into the transmit buffer, reinitialises the SPI peripheral ready for the next transfer,
* and calls the user callback
*/
void YEEP144_PiSPI_TxRxHandler(void){
// Clear the ready signal as we are not ready to receive until the message has been handled
YEEP144_PiSPI_ResetReady();
// Copy the user facing buffer into the internal buffer
memcpy(PiSPITxBufInternal, PiSPITxBuf, PISPI_BUFFER_SIZE);
// Restart the transfer now the buffer has been updated
HAL_SPI_TransmitReceive_IT(&hspi4, PiSPITxBufInternal, PiSPIRxBuf, PISPI_BUFFER_SIZE);
// Call the user callback function - the user will need to restore the ready signal when done
// for more messages to be received
YEEP144_PiSPI_RxCallback();
}
/**
* This is a weakly defined function for the RX Callback function.
* It should be overriden by an identically named function in user code
*/
__weak void YEEP144_PiSPI_RxCallback(void){
return;
}

View File

@@ -0,0 +1,111 @@
#include "YEEP144_PushButton.h"
#include "YEEP144.h"
#include "YEEP144_Pinout.h"
#include "stm32h5xx_hal_cortex.h"
#include "stm32h5xx_hal_def.h"
#include "stm32h5xx_hal_gpio.h"
void YEEP144_PushButton_BlueCallback(void);
void YEEP144_PushButton_GreenCallback(void);
void YEEP144_PushButton_YellowCallback(void);
void YEEP144_PushButton_RedCallback(void);
/**
* Initialise the blue button. This consumes the button's GPIO and EXTI13
*/
void YEEP144_PushButton_BlueInit(void){
YEEP144_GPIO_Init(BUTTON_BLUE_PORT, BUTTON_BLUE_PIN, GPIO_MODE_IT_FALLING);
HAL_NVIC_SetPriority(BUTTON_BLUE_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(BUTTON_BLUE_EXTI);
}
/**
* Initialise the green button. This consumes the button's GPIO and EXTI7
*/
void YEEP144_PushButton_GreenInit(void){
YEEP144_GPIO_Init(BUTTON_GREEN_PORT, BUTTON_GREEN_PIN, GPIO_MODE_IT_FALLING);
HAL_NVIC_SetPriority(BUTTON_GREEN_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(BUTTON_GREEN_EXTI);
}
/**
* Initialise the yellow button. This consumes the button's GPIO and EXTI6
*/
void YEEP144_PushButton_YellowInit(void){
YEEP144_GPIO_Init(BUTTON_YELLOW_PORT, BUTTON_YELLOW_PIN, GPIO_MODE_IT_FALLING);
HAL_NVIC_SetPriority(BUTTON_YELLOW_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(BUTTON_YELLOW_EXTI);
}
/**
* Initialise the blue button. This consumes the button's GPIO and EXTI12 (shared with ultrasonic 4)
*/
void YEEP144_PushButton_RedInit(void){
YEEP144_GPIO_Init(BUTTON_RED_PORT, BUTTON_RED_PIN, GPIO_MODE_IT_FALLING);
HAL_NVIC_SetPriority(BUTTON_RED_EXTI, 1, 0);
HAL_NVIC_EnableIRQ(BUTTON_RED_EXTI);
}
/**
* Handles the interrupt for the blue button. This should be called from the main EXTI13 handler, and in turn calls YEEP144_PushButton_BlueCallback
*/
void YEEP144_PushButton_BlueHandler(void){
// Check this interrupt has been called due to the pushbutton
if(__HAL_GPIO_EXTI_GET_IT(BUTTON_BLUE_PIN) && (HAL_GPIO_ReadPin(BUTTON_BLUE_PORT, BUTTON_BLUE_PIN) == GPIO_PIN_RESET)){
__HAL_GPIO_EXTI_CLEAR_IT(BUTTON_BLUE_PIN); // Clear the interrupt
YEEP144_PushButton_BlueCallback(); // Call the callback function
}
}
/**
* Handles the interrupt for the green button. This should be called from the main EXTI7 handler, and in turn calls YEEP144_PushButton_GreenCallback
*/
void YEEP144_PushButton_GreenHandler(void){
// Check this interrupt has been called due to the pushbutton
if(__HAL_GPIO_EXTI_GET_IT(BUTTON_GREEN_PIN) && (HAL_GPIO_ReadPin(BUTTON_GREEN_PORT, BUTTON_GREEN_PIN) == GPIO_PIN_RESET)){
__HAL_GPIO_EXTI_CLEAR_IT(BUTTON_GREEN_PIN); // Clear the interrupt
YEEP144_PushButton_GreenCallback(); // Call the callback function
}
}
/**
* Handles the interrupt for the yellow button. This should be called from the main EXTI6 handler, and in turn calls YEEP144_PushButton_YellowCallback
*/
void YEEP144_PushButton_YellowHandler(void){
// Check this interrupt has been called due to the pushbutton
if(__HAL_GPIO_EXTI_GET_IT(BUTTON_YELLOW_PIN) && (HAL_GPIO_ReadPin(BUTTON_YELLOW_PORT, BUTTON_YELLOW_PIN) == GPIO_PIN_RESET)){
__HAL_GPIO_EXTI_CLEAR_IT(BUTTON_YELLOW_PIN); // Clear the interrupt
YEEP144_PushButton_YellowCallback(); // Call the callback function
}
}
/**
* Handles the interrupt for the red button. This should be called from the main EXTI12 handler, and in turn calls YEEP144_PushButton_RedCallback
*/
void YEEP144_PushButton_RedHandler(void){
// Check this interrupt has been called due to the pushbutton
if(__HAL_GPIO_EXTI_GET_IT(BUTTON_RED_PIN) && (HAL_GPIO_ReadPin(BUTTON_RED_PORT, BUTTON_RED_PIN) == GPIO_PIN_RESET)){
__HAL_GPIO_EXTI_CLEAR_IT(BUTTON_RED_PIN); // Clear the interrupt
YEEP144_PushButton_RedCallback(); // Call the callback function
}
}
/**
* These versions of the callback functions are weakly defined so they can be overwritten by the user's function elsewhere.
* They need to exist so that the code will still compile even if the user hasn't defined their own.
*/
__weak void YEEP144_PushButton_BlueCallback(void){
}
__weak void YEEP144_PushButton_GreenCallback(void){
}
__weak void YEEP144_PushButton_YellowCallback(void){
}
__weak void YEEP144_PushButton_RedCallback(void){
}

View File

@@ -0,0 +1,148 @@
#include "YEEP144.h"
#include "YEEP144_RGB.h"
#include "YEEP144_I2C.h"
#include "stm32h5xx_hal_i2c.h"
#define RGB_ADDR 0x50
#define RGB_CHIP_EN_REG 0x000
#define RGB_DEV_CONFIG_0_REG 0x001
#define RGB_DEV_CONFIG_1_REG 0x002
#define RGB_DEV_CONFIG_2_REG 0x003
#define RGB_DEV_CONFIG_3_REG 0x004
#define RGB_DEV_CONFIG_4_REG 0x005
#define RGB_DEV_CONFIG_5_REG 0x006
#define RGB_DEV_CONFIG_6_REG 0x007
#define RGB_DEV_CONFIG_7_REG 0x008
#define RGB_DEV_CONFIG_8_REG 0x009
#define RGB_DEV_CONFIG_9_REG 0x00A
#define RGB_DEV_CONFIG_10_REG 0x00B
#define RGB_DEV_CONFIG_11_REG 0x00C
#define RGB_DEV_CONFIG_12_REG 0x00D
#define RGB_CMD_UPDATE_REG 0x010
#define RGB_CMD_START_REG 0x011
#define RGB_CMD_STOP_REG 0x012
#define RGB_CMD_PAUSE_REG 0x013
#define RGB_CMD_CONTINUE_REG 0x014
#define RGB_LED_EN_1_REG 0x020
#define RGB_LED_EN_2_REG 0x021
#define RGB_FAULT_CLEAR_REG 0x022
#define RGB_RESET_REG 0x023
#define RGB_MANUAL_DC_BASE_REG 0x030
#define RGB_MANUAL_DC_4_REG 0x034
#define RGB_MANUAL_DC_5_REG 0x035
#define RGB_MANUAL_DC_6_REG 0x036
#define RGB_MANUAL_DC_7_REG 0x037
#define RGB_MANUAL_DC_8_REG 0x038
#define RGB_MANUAL_DC_9_REG 0x039
#define RGB_MANUAL_DC_10_REG 0x03A
#define RGB_MANUAL_DC_11_REG 0x03B
#define RGB_MANUAL_DC_12_REG 0x03C
#define RGB_MANUAL_DC_13_REG 0x03D
#define RGB_MANUAL_DC_14_REG 0x03E
#define RGB_MANUAL_DC_15_REG 0x03F
#define RGB_MANUAL_PWM_BASE_REG 0x040
#define RGB_MANUAL_PWM_4_REG 0x044
#define RGB_MANUAL_PWM_5_REG 0x045
#define RGB_MANUAL_PWM_6_REG 0x046
#define RGB_MANUAL_PWM_7_REG 0x047
#define RGB_MANUAL_PWM_8_REG 0x048
#define RGB_MANUAL_PWM_9_REG 0x049
#define RGB_MANUAL_PWM_10_REG 0x04A
#define RGB_MANUAL_PWM_11_REG 0x04B
#define RGB_MANUAL_PWM_12_REG 0x04C
#define RGB_MANUAL_PWM_13_REG 0x04D
#define RGB_MANUAL_PWM_14_REG 0x04E
#define RGB_MANUAL_PWM_15_REG 0x04F
#define I2C_Timeout 1000
/**
* Initialise the RGB led driver. This requires that the I2C has already been initialised
*/
void YEEP144_RGB_Init(void){
// Reset the LP5812 - 0x66 is magic number according to datasheet
YEEP144_RGB_WriteReg(RGB_RESET_REG, 0x66);
// Enable the chip
YEEP144_RGB_WriteReg(RGB_CHIP_EN_REG, 1);
// Set the scan mode - 24kHz PWM, scan drive mode with 4 scans, no direct drive outputs
YEEP144_RGB_WriteReg(RGB_DEV_CONFIG_1_REG, 0x40);
// Set protection options - shutdown on open LED detected, shutdown on short LED detected, short threshold 0.65 * VCC
YEEP144_RGB_WriteReg(RGB_DEV_CONFIG_12_REG, 0x0F);
// Apply updated configuration - 0x55 is magic number as per datasheet
YEEP144_RGB_WriteReg(RGB_CMD_UPDATE_REG, 0x55);
// Set red current limits to 50% of max 25.2mA
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB1_RED , 0xFF / 2);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB2_RED , 0xFF / 2);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB3_RED , 0xFF / 2);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB4_RED , 0xFF / 2);
// Set green and blue current limits to 20% of max
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB1_GREEN , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB1_BLUE , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB2_GREEN , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB2_BLUE , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB3_GREEN , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB3_BLUE , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB4_GREEN , 0xFF / 5);
YEEP144_RGB_WriteReg(RGB_MANUAL_DC_BASE_REG + RGB4_BLUE , 0xFF / 5);
// Enable all the LEDS
YEEP144_RGB_WriteReg(RGB_LED_EN_1_REG, 0xF0);
YEEP144_RGB_WriteReg(RGB_LED_EN_2_REG, 0xFF);
// Set default colours
YEEP144_RGB_SetRGB(1, RGB_GREEN);
YEEP144_RGB_SetRGB(2, RGB_GOLD);
YEEP144_RGB_SetRGB(3, RGB_RED);
YEEP144_RGB_SetRGB(4, RGB_INDIGO);
}
/**
* Individually sets the brightness of an LED. Use the defines in the header file
* to specify which LED e.g. RGB1_GREEN
*/
void YEEP144_RGB_SetLED(uint8_t led, uint8_t value){
YEEP144_RGB_WriteReg(RGB_MANUAL_PWM_BASE_REG + led, value);
}
/**
* Sets the brightness for all 3 elements of an RGB led at one.
* Instead of specifiying the RGB values, you can also substitute in one of the defines e.g. RGB_GOLD
*/
void YEEP144_RGB_SetRGB(uint8_t led, uint8_t red, uint8_t green, uint8_t blue){
uint8_t offset = ((led - 1) * 3);
YEEP144_RGB_WriteReg(RGB_MANUAL_PWM_BASE_REG + RGB1_RED + offset, red);
YEEP144_RGB_WriteReg(RGB_MANUAL_PWM_BASE_REG + RGB1_GREEN + offset, green);
YEEP144_RGB_WriteReg(RGB_MANUAL_PWM_BASE_REG + RGB1_BLUE + offset, blue);
}
/**
* Helper function that writes a byte to the RGB controller at the specified register address
*/
void YEEP144_RGB_WriteReg(uint16_t reg, uint8_t data){
// As there are so many registers in this chip, it uses a couple of the I2C address bits as part of the register address
// This means we need to do some bit level manipulation to build our transmission address and data
uint8_t I2CAddress = RGB_ADDR + (reg >> 8); // set the bottom two bits of the address to the top two bits of the register
// Cast the register to a uint8_t so we just get the lowest 8 bits
uint8_t reg8Bit = (uint8_t) reg;
// Pack out register address and data into an array for transmission
uint8_t RGBCommand[2];
RGBCommand[0] = reg8Bit;
RGBCommand[1] = data;
// Send out the data. The constructed I2CAddress still needs to be shifted by 1 due to 7/8-bit addressing differences
HAL_I2C_Master_Transmit(&hi2c2, I2CAddress << 1, RGBCommand, 2, I2C_Timeout);
}

View File

@@ -0,0 +1,779 @@
// This code is largely inspired by the Adafruit SSD1306 and Adafruit GFX Arduino libraries
// re-written to simplify it down to plain C rather than object based C++, optimise for STM32
// rather than Arduino, and to focus on specifically the monochrome SSD1306 displays rather than
// a general purpose graphics library
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_i2c.h"
#include "YEEP144_I2C.h"
#include "YEEP144_SSD1306.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>
#include <strings.h>
#define WIDTH 128
#define HEIGHT 32
#define SSD1306_ADDR 0x3C << 1
#define I2C_TIMEOUT 1000
#define FONTWIDTH 5
#define FONTHEIGHT 7
#define MAXCHAR ((WIDTH / (FONTWIDTH + 1)) * (HEIGHT / (FONTHEIGHT + 1)))
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR 0x22
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_EXTERNALVCC 0x01
#define SSD1306_SWITCHCAPVCC 0x02
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
uint8_t *displayTXBuffer;
uint8_t *displayBuffer;
uint8_t cursorX = 0;
uint8_t cursorY = 0;
uint8_t textSize = 1;
uint8_t textColour = SSD1306_WHITE;
uint8_t textBGColour = SSD1306_BLACK;
uint8_t textWrap = 1;
void YEEP144_SSD1306_DrawFastHLine(uint8_t x, uint8_t y, int8_t w, uint8_t colour);
void YEEP144_SSD1306_DrawFastVLine(uint8_t x, uint8_t y, int8_t h, uint8_t colour);
void YEEP144_SSD1306_DrawLineInternal(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t colour);
const unsigned char UOYLogo [] = {
0x00, 0x00, 0x00, 0x30, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x60, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe0, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xc8, 0x18, 0x0d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x3c, 0x04, 0x00, 0x40, 0x69, 0x24, 0xd7, 0xb8, 0xcd, 0xe9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xbc, 0x00, 0x10, 0x00, 0x49, 0xa4, 0x02, 0x25, 0x08, 0x86, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xdc, 0x01, 0x16, 0x40, 0x49, 0x64, 0x42, 0x28, 0xc8, 0x84, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xff, 0x8f, 0xe8, 0x00, 0x49, 0x24, 0x62, 0xad, 0x48, 0x84, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3e, 0x77, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7e, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xf6, 0x0d, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe0, 0x1c, 0xf8, 0x00, 0x00, 0x0c, 0x38, 0x03, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0xc0, 0x86, 0xff, 0x80, 0x00, 0x14, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0xef, 0x9f, 0xfb, 0x00, 0x00, 0x20, 0x18, 0x10, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0xa0, 0xfe, 0x00, 0x1e, 0x20, 0x10, 0x60, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x18, 0x58, 0xb6, 0x00, 0x27, 0xf8, 0x30, 0xe0, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xb8, 0x3c, 0x33, 0x00, 0x4c, 0x40, 0x31, 0xc0, 0x00, 0x60, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x98, 0x7e, 0x33, 0x00, 0x84, 0xc0, 0x63, 0x86, 0x30, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf8, 0xc2, 0x10, 0x00, 0x88, 0x80, 0x6b, 0x12, 0xb7, 0xc3, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x69, 0xdb, 0x1a, 0x00, 0x90, 0x80, 0xd6, 0x22, 0x38, 0x86, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x79, 0xff, 0x90, 0x00, 0xe1, 0x00, 0xec, 0x42, 0x31, 0x98, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7b, 0xff, 0xd0, 0x00, 0x02, 0x01, 0x98, 0xc4, 0x61, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x6c, 0x3c, 0x2c, 0x00, 0x04, 0x01, 0x30, 0x8c, 0x43, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0xa4, 0xa0, 0x00, 0x00, 0x00, 0x70, 0x98, 0x82, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0xc3, 0xe0, 0x00, 0x00, 0x00, 0x60, 0xe0, 0x84, 0x0f, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0x99, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x11, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x18, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x40, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char font[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 0x3E, 0x6B,
0x4F, 0x6B, 0x3E, 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 0x18, 0x3C, 0x7E, 0x3C,
0x18, 0x1C, 0x57, 0x7D, 0x57, 0x1C, 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00,
0x18, 0x3C, 0x18, 0x00, 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00, 0x18, 0x24,
0x18, 0x00, 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x30, 0x48, 0x3A, 0x06, 0x0E,
0x26, 0x29, 0x79, 0x29, 0x26, 0x40, 0x7F, 0x05, 0x05, 0x07, 0x40, 0x7F,
0x05, 0x25, 0x3F, 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 0x7F, 0x3E, 0x1C, 0x1C,
0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 0x14, 0x22, 0x7F, 0x22, 0x14, 0x5F,
0x5F, 0x00, 0x5F, 0x5F, 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, 0x66, 0x89,
0x95, 0x6A, 0x60, 0x60, 0x60, 0x60, 0x60, 0x94, 0xA2, 0xFF, 0xA2, 0x94,
0x08, 0x04, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x20, 0x10, 0x08, 0x08,
0x2A, 0x1C, 0x08, 0x08, 0x1C, 0x2A, 0x08, 0x08, 0x1E, 0x10, 0x10, 0x10,
0x10, 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 0x30, 0x38, 0x3E, 0x38, 0x30, 0x06,
0x0E, 0x3E, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F,
0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14,
0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49,
0x56, 0x20, 0x50, 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x1C, 0x22, 0x41,
0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x08,
0x08, 0x3E, 0x08, 0x08, 0x00, 0x80, 0x70, 0x30, 0x00, 0x08, 0x08, 0x08,
0x08, 0x08, 0x00, 0x00, 0x60, 0x60, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02,
0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x72, 0x49,
0x49, 0x49, 0x46, 0x21, 0x41, 0x49, 0x4D, 0x33, 0x18, 0x14, 0x12, 0x7F,
0x10, 0x27, 0x45, 0x45, 0x45, 0x39, 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x41,
0x21, 0x11, 0x09, 0x07, 0x36, 0x49, 0x49, 0x49, 0x36, 0x46, 0x49, 0x49,
0x29, 0x1E, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x34, 0x00, 0x00,
0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41,
0x22, 0x14, 0x08, 0x02, 0x01, 0x59, 0x09, 0x06, 0x3E, 0x41, 0x5D, 0x59,
0x4E, 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E,
0x41, 0x41, 0x41, 0x22, 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x49, 0x49,
0x49, 0x41, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x3E, 0x41, 0x41, 0x51, 0x73,
0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, 0x20, 0x40,
0x41, 0x3F, 0x01, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x7F, 0x40, 0x40, 0x40,
0x40, 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E,
0x41, 0x41, 0x41, 0x3E, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x3E, 0x41, 0x51,
0x21, 0x5E, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x26, 0x49, 0x49, 0x49, 0x32,
0x03, 0x01, 0x7F, 0x01, 0x03, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x1F, 0x20,
0x40, 0x20, 0x1F, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x63, 0x14, 0x08, 0x14,
0x63, 0x03, 0x04, 0x78, 0x04, 0x03, 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00,
0x7F, 0x41, 0x41, 0x41, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41,
0x41, 0x7F, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40, 0x7F, 0x28,
0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x28,
0x7F, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x08, 0x7E, 0x09, 0x02, 0x18,
0xA4, 0xA4, 0x9C, 0x78, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7D,
0x40, 0x00, 0x20, 0x40, 0x40, 0x3D, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00,
0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x78, 0x04, 0x78, 0x7C, 0x08,
0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xFC, 0x18, 0x24, 0x24,
0x18, 0x18, 0x24, 0x24, 0x18, 0xFC, 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48,
0x54, 0x54, 0x54, 0x24, 0x04, 0x04, 0x3F, 0x44, 0x24, 0x3C, 0x40, 0x40,
0x20, 0x7C, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x40, 0x30, 0x40, 0x3C,
0x44, 0x28, 0x10, 0x28, 0x44, 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x44, 0x64,
0x54, 0x4C, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x77, 0x00,
0x00, 0x00, 0x41, 0x36, 0x08, 0x00, 0x02, 0x01, 0x02, 0x04, 0x02, 0x3C,
0x26, 0x23, 0x26, 0x3C, 0x1E, 0xA1, 0xA1, 0x61, 0x12, 0x3A, 0x40, 0x40,
0x20, 0x7A, 0x38, 0x54, 0x54, 0x55, 0x59, 0x21, 0x55, 0x55, 0x79, 0x41,
0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut
0x21, 0x55, 0x54, 0x78, 0x40, 0x20, 0x54, 0x55, 0x79, 0x40, 0x0C, 0x1E,
0x52, 0x72, 0x12, 0x39, 0x55, 0x55, 0x55, 0x59, 0x39, 0x54, 0x54, 0x54,
0x59, 0x39, 0x55, 0x54, 0x54, 0x58, 0x00, 0x00, 0x45, 0x7C, 0x41, 0x00,
0x02, 0x45, 0x7D, 0x42, 0x00, 0x01, 0x45, 0x7C, 0x40, 0x7D, 0x12, 0x11,
0x12, 0x7D, // A-umlaut
0xF0, 0x28, 0x25, 0x28, 0xF0, 0x7C, 0x54, 0x55, 0x45, 0x00, 0x20, 0x54,
0x54, 0x7C, 0x54, 0x7C, 0x0A, 0x09, 0x7F, 0x49, 0x32, 0x49, 0x49, 0x49,
0x32, 0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut
0x32, 0x4A, 0x48, 0x48, 0x30, 0x3A, 0x41, 0x41, 0x21, 0x7A, 0x3A, 0x42,
0x40, 0x20, 0x78, 0x00, 0x9D, 0xA0, 0xA0, 0x7D, 0x3D, 0x42, 0x42, 0x42,
0x3D, // O-umlaut
0x3D, 0x40, 0x40, 0x40, 0x3D, 0x3C, 0x24, 0xFF, 0x24, 0x24, 0x48, 0x7E,
0x49, 0x43, 0x66, 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 0xFF, 0x09, 0x29, 0xF6,
0x20, 0xC0, 0x88, 0x7E, 0x09, 0x03, 0x20, 0x54, 0x54, 0x79, 0x41, 0x00,
0x00, 0x44, 0x7D, 0x41, 0x30, 0x48, 0x48, 0x4A, 0x32, 0x38, 0x40, 0x40,
0x22, 0x7A, 0x00, 0x7A, 0x0A, 0x0A, 0x72, 0x7D, 0x0D, 0x19, 0x31, 0x7D,
0x26, 0x29, 0x29, 0x2F, 0x28, 0x26, 0x29, 0x29, 0x29, 0x26, 0x30, 0x48,
0x4D, 0x40, 0x20, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x38, 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 0x2F, 0x10, 0x28, 0x34, 0xFA, 0x00,
0x00, 0x7B, 0x00, 0x00, 0x08, 0x14, 0x2A, 0x14, 0x22, 0x22, 0x14, 0x2A,
0x14, 0x08, 0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old
// code
0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block
0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block
0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x10, 0x10, 0xFF, 0x00, 0x14, 0x14,
0x14, 0xFF, 0x00, 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0xF0, 0x10,
0xF0, 0x14, 0x14, 0x14, 0xFC, 0x00, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x14, 0x14, 0x17,
0x10, 0x1F, 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0x1F, 0x00,
0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10,
0x10, 0x1F, 0x10, 0x10, 0x10, 0x10, 0xF0, 0x10, 0x00, 0x00, 0x00, 0xFF,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, 0x00,
0x00, 0x00, 0xFF, 0x14, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x1F,
0x10, 0x17, 0x00, 0x00, 0xFC, 0x04, 0xF4, 0x14, 0x14, 0x17, 0x10, 0x17,
0x14, 0x14, 0xF4, 0x04, 0xF4, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x17,
0x14, 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x10,
0x10, 0xF0, 0x10, 0xF0, 0x00, 0x00, 0x1F, 0x10, 0x1F, 0x00, 0x00, 0x00,
0x1F, 0x14, 0x00, 0x00, 0x00, 0xFC, 0x14, 0x00, 0x00, 0xF0, 0x10, 0xF0,
0x10, 0x10, 0xFF, 0x10, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x14, 0x10, 0x10,
0x10, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x10, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x38, 0x44, 0x44,
0x38, 0x44, 0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta
0x7E, 0x02, 0x02, 0x06, 0x06, 0x02, 0x7E, 0x02, 0x7E, 0x02, 0x63, 0x55,
0x49, 0x41, 0x63, 0x38, 0x44, 0x44, 0x3C, 0x04, 0x40, 0x7E, 0x20, 0x1E,
0x20, 0x06, 0x02, 0x7E, 0x02, 0x02, 0x99, 0xA5, 0xE7, 0xA5, 0x99, 0x1C,
0x2A, 0x49, 0x2A, 0x1C, 0x4C, 0x72, 0x01, 0x72, 0x4C, 0x30, 0x4A, 0x4D,
0x4D, 0x30, 0x30, 0x48, 0x78, 0x48, 0x30, 0xBC, 0x62, 0x5A, 0x46, 0x3D,
0x3E, 0x49, 0x49, 0x49, 0x00, 0x7E, 0x01, 0x01, 0x01, 0x7E, 0x2A, 0x2A,
0x2A, 0x2A, 0x2A, 0x44, 0x44, 0x5F, 0x44, 0x44, 0x40, 0x51, 0x4A, 0x44,
0x40, 0x40, 0x44, 0x4A, 0x51, 0x40, 0x00, 0x00, 0xFF, 0x01, 0x03, 0xE0,
0x80, 0xFF, 0x00, 0x00, 0x08, 0x08, 0x6B, 0x6B, 0x08, 0x36, 0x12, 0x36,
0x24, 0x36, 0x06, 0x0F, 0x09, 0x0F, 0x06, 0x00, 0x00, 0x18, 0x18, 0x00,
0x00, 0x00, 0x10, 0x10, 0x00, 0x30, 0x40, 0xFF, 0x01, 0x01, 0x00, 0x1F,
0x01, 0x01, 0x1E, 0x00, 0x19, 0x1D, 0x17, 0x12, 0x00, 0x3C, 0x3C, 0x3C,
0x3C, 0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP
};
/**
* Initialise the SSD1306 OLED display. This requires that the I2C has already been initialised.
*/
void YEEP144_SSD1306_Init(void){
// Allocate space in memory for the framebuffer to be stored
// As the display is monochrome we only need 1 bit per pixel, so the data is packed
// to save on memory space - i.e. 8 pixels per byte
// The arithmetic used ensures that the buffer size is always padded to a whole byte number
//
// This is also how the framebuffer is stored within the OLED chip, so it can be directly
// copied across without additional manipulation
//
// We make the TX buffer 1 byte larger than needed for the display data so the value 0x40 can be prepended
// When it comes time to send the framebuffer to the display, that initial byte is required so the data is treated
// as display data, not commands
displayTXBuffer = (uint8_t *)malloc((WIDTH * ((HEIGHT + 7) / 8)) + 1);
displayTXBuffer[0] = 0x40;
// For programming ease we store a separate pointer offset by 1 to directly access the display data portion of the buffer
displayBuffer = displayTXBuffer + 1;
// Ensure the display buffer is empty
YEEP144_SSD1306_ClearDisplay();
// This list of commands sets up the display and turns it on
uint8_t initCommandList[] = {
0x00, // Tell the display these are commands not graphics data
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV,
0x80, // Recommended clock div
SSD1306_SETMULTIPLEX,
HEIGHT - 1,
SSD1306_SETDISPLAYOFFSET,
0x0, // No display offset
SSD1306_SETSTARTLINE | 0x0, // Start at line 0
SSD1306_CHARGEPUMP,
0x14, // Enable internal charge pump
SSD1306_MEMORYMODE,
0x00,
SSD1306_SEGREMAP | 0x1,
SSD1306_COMSCANDEC,
SSD1306_SETCOMPINS,
0x02, // This is hardcoded for the value for a 128x32 screen
SSD1306_SETCONTRAST,
0x8F, // This is hardcoded for the value for a 128x32 screen
SSD1306_SETPRECHARGE,
0xF1, // Value for when charge pump is enabled
SSD1306_SETVCOMDETECT,
0x40,
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
SSD1306_DEACTIVATE_SCROLL,
SSD1306_DISPLAYON
};
// Send the commands
HAL_I2C_Master_Transmit(&hi2c2, SSD1306_ADDR, initCommandList, sizeof(initCommandList), I2C_TIMEOUT);
}
/**
* Update the display by transfering the contents of the display buffer to the display.
* No changes to the displayed graphics will be made until this command is called
*/
void YEEP144_SSD1306_Display(void){
// Before sending graphics data we need to define where the data goes
// As we are always copying over the whole frame buffer, we want our transfer to fill
// the whole graphics memory on the driver chip
uint8_t commandList[] = {
0x00, // Following data are commands
SSD1306_PAGEADDR,
0, // Starting page
0xFF, // Ending page
SSD1306_COLUMNADDR,
0, // Starting column
(WIDTH-1), // Ending column
//0x40 // Following data will be graphics not commands
};
// Send these commands
HAL_I2C_Master_Transmit(&hi2c2, SSD1306_ADDR, commandList, sizeof(commandList), I2C_TIMEOUT);
// Send the graphics data. We use the TXbuffer here so it includes the additional byte at the start that tells the chip this is graphics data
HAL_I2C_Master_Transmit(&hi2c2, SSD1306_ADDR, displayTXBuffer, (WIDTH * ((HEIGHT + 7) / 8)) + 1, I2C_TIMEOUT);
}
/**
* Clears the display by clearing the display buffer
*/
void YEEP144_SSD1306_ClearDisplay(void){
// Clear the display by filling the buffer with all zeros
memset(displayBuffer, 0, WIDTH * ((HEIGHT + 7) / 8));
}
/**
* Sets a single pixel in the display buffer to the specified colour
*/
void YEEP144_SSD1306_SetPixel(uint8_t x, uint8_t y, uint8_t colour){
// As the data is packed with 8 pixels per byte, we need to do some bit-level
// manipulation to set an individual pixel
switch(colour){
// Set the bit for white (on)
case SSD1306_WHITE:
displayBuffer[x + (y / 8) * WIDTH] |= (1 << (y & 7));
break;
// Clear the bit for black (off)
case SSD1306_BLACK:
displayBuffer[x + (y / 8) * WIDTH] &= ~(1 << (y & 7));
break;
// Invert the bit to invert the colour
case SSD1306_INVERSE:
displayBuffer[x + (y / 8) * WIDTH] ^= (1 << (y & 7));
break;
}
}
/**
* Returns the current value of the specified pixel from the display buffer.
* Note that this is what is currently stored in the local buffer, not
* necessarily what is currently being displayed if Display has not been called
*/
bool YEEP144_SSD1306_GetPixel(uint8_t x, uint8_t y){
// As the 1-bit pixels are packed into bytes, we need to do some bit manipulation to retrieve the individual bit
return (displayBuffer[x + (y / 8) * WIDTH] & (1 << (y & 7)));
}
/**
* Draws a line between the specified coordinates, using the specified colour
*/
void YEEP144_SSD1306_DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t colour){
// This function checks if the draw can be fufilled by one of the optimised horizontal or vertical
// line drawing functions, if not passes to the generalised one
if(x0 == x1){ // If both x coordinates are the same then this is vertical
if (y0 > y1){ // Normalise direction by swapping the coords if needed
uint8_t t = y0;
y0 = y1;
y1 = t;
}
YEEP144_SSD1306_DrawFastVLine(x0, y0, y1 - y0, colour);
}
else if(y0 == y1){ // If both y coordinates are the same then this is horizontal
if(x0 > x1){ // Normalise direction by swapping the coords if needed
uint8_t t = x0;
x0 = x1;
x1 = t;
}
YEEP144_SSD1306_DrawFastHLine(x0, y0, x1 - x0, colour);
}
else{
// If this cannot be handled by either of the fast algorithms, fallback to the general one
YEEP144_SSD1306_DrawLineInternal(x0, y0, x1, y1, colour);
}
}
/**
* Draws a line between the two specified points using Bresenham's algorithm
* This is only used when the line is not vertical or horizontal
* This will only be an approximation of requested line due to the limited resolution of the display
* and the lack of options for antialiasing
*/
void YEEP144_SSD1306_DrawLineInternal(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t colour){
// Bresenham's algorithm only works when going in the positive direction, which would be down and right
// for a display with (0,0) in the top left corner. So that this can be used for all cases,
// we need to detect the direction of the requested line and transform the coordinates
uint8_t steep = abs(y1 - y0) > abs(x1 - x0);
if(steep){
uint8_t t = x0;
x0 = y0;
y0 = t;
t = x1;
x1 = y1;
y1 = t;
}
if(x0 > x1){
uint8_t t = x0;
x0 = x1;
x1 = t;
t = y0;
y0 = y1;
y1 = t;
}
int8_t dx = x1 - x0;
int8_t dy = abs(y1 - y0);
int8_t err = dx / 2;
int8_t ystep;
if(y0 < y1){
ystep = 1;
}
else{
ystep = -1;
}
for(; x0 <= x1; x0++){
if(steep){
YEEP144_SSD1306_SetPixel(y0, x0, colour);
}
else{
YEEP144_SSD1306_SetPixel(x0, y0, colour);
}
err -= dy;
if(err < 0){
y0 += ystep;
err += dx;
}
}
}
/**
* Draw a purely horizontal line. This is faster than the general algorithm,
* but only works for horizontal lines
*/
void YEEP144_SSD1306_DrawFastHLine(uint8_t x, uint8_t y, int8_t w, uint8_t colour){
if (y < HEIGHT) { // Y coord in bounds?
if ((x + w) > WIDTH) { // Clip right
w = (WIDTH - x);
}
if (w > 0) { // Proceed only if width is positive
uint8_t *pBuf = &displayBuffer[(y / 8) * WIDTH + x];
uint8_t mask = 1 << (y & 7);
switch (colour) {
case SSD1306_WHITE:
while (w--) {
*pBuf++ |= mask;
};
break;
case SSD1306_BLACK:
mask = ~mask;
while (w--) {
*pBuf++ &= mask;
};
break;
case SSD1306_INVERSE:
while (w--) {
*pBuf++ ^= mask;
};
break;
}
}
}
}
/**
* Draw a purely vertical line. This is faster than the general algorithm,
* but only works for vertical lines
*/
void YEEP144_SSD1306_DrawFastVLine(uint8_t x, uint8_t y, int8_t h, uint8_t colour){
if (x < WIDTH) { // X coord in bounds?
if ((y + h) > HEIGHT) { // Clip bottom
h = (HEIGHT - y);
}
if (h > 0) { // Proceed only if height is now positive
uint8_t *pBuf = &displayBuffer[(y / 8) * WIDTH + x];
// do the first partial byte, if necessary - this requires some masking
uint8_t mod = (y & 7);
if (mod) {
// mask off the high n bits we want to set
mod = 8 - mod;
// note - lookup table results in a nearly 10% performance
// improvement in fill* functions
// uint8_t mask = ~(0xFF >> mod);
static const uint8_t premask[8] = {0x00, 0x80, 0xC0, 0xE0,
0xF0, 0xF8, 0xFC, 0xFE};
uint8_t mask = premask[mod];
// adjust the mask if we're not going to reach the end of this byte
if (h < mod)
mask &= (0XFF >> (mod - h));
switch (colour) {
case SSD1306_WHITE:
*pBuf |= mask;
break;
case SSD1306_BLACK:
*pBuf &= ~mask;
break;
case SSD1306_INVERSE:
*pBuf ^= mask;
break;
}
pBuf += WIDTH;
}
if (h >= mod) { // More to go?
h -= mod;
// Write solid bytes while we can - effectively 8 rows at a time
if (h >= 8) {
if (colour == SSD1306_INVERSE) {
// separate copy of the code so we don't impact performance of
// black/white write version with an extra comparison per loop
do {
*pBuf ^= 0xFF; // Invert byte
pBuf += WIDTH; // Advance pointer 8 rows
h -= 8; // Subtract 8 rows from height
} while (h >= 8);
} else {
// store a local value to work with
uint8_t val = (colour != SSD1306_BLACK) ? 255 : 0;
do {
*pBuf = val; // Set byte
pBuf += WIDTH; // Advance pointer 8 rows
h -= 8; // Subtract 8 rows from height
} while (h >= 8);
}
}
if (h) { // Do the final partial byte, if necessary
mod = h & 7;
// this time we want to mask the low bits of the byte,
// vs the high bits we did above
// uint8_t mask = (1 << mod) - 1;
// note - lookup table results in a nearly 10% performance
// improvement in fill* functions
static const uint8_t postmask[8] = {0x00, 0x01, 0x03, 0x07,
0x0F, 0x1F, 0x3F, 0x7F};
uint8_t mask = postmask[mod];
switch (colour) {
case SSD1306_WHITE:
*pBuf |= mask;
break;
case SSD1306_BLACK:
*pBuf &= ~mask;
break;
case SSD1306_INVERSE:
*pBuf ^= mask;
break;
}
}
}
} // endif positive height
} // endif x in bounds
}
/**
* Draws an outline of a rectangle with no fill. Coordinates represent the top left corner
*/
void YEEP144_SSD1306_DrawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t colour){
YEEP144_SSD1306_DrawFastHLine(x, y, w, colour);
YEEP144_SSD1306_DrawFastHLine(x, y + h - 1, w, colour);
YEEP144_SSD1306_DrawFastVLine(x, y, h, colour);
YEEP144_SSD1306_DrawFastVLine(x + w - 1, y, h, colour);
}
/**
* Draw a filled rectangle of given width and height, with the top left corner specified
* by the given coordinates
*/
void YEEP144_SSD1306_DrawFillRect(uint8_t x, uint8_t y, int8_t w, int8_t h, uint8_t colour){
for (int16_t i = x; i < x + w; i++) {
YEEP144_SSD1306_DrawFastVLine(i, y, h, colour);
}
}
/**
* Prints text to the screen using printf-style formatting. Cursor starting position, font size and
* text wrapping can be controlled using the provided functions
*/
int YEEP144_SSD1306_printf(const char * format, ...){
// Set the buffer size to the maximum that can fit on the screen with the smallest font
size_t bufSize = MAXCHAR;
// Try to allocate that buffer
char* buffer = malloc(bufSize);
// If the buffer cannot be allocated, return -1
if (buffer == NULL) return -1;
// Get the pointer to the variadic args
va_list args;
va_start (args, format);
// Create our final string in the buffer using vsnprintf, passing through our argument pointer
vsnprintf(buffer, bufSize, format, args);
// Calulate the final size of the string
bufSize = strlen(buffer);
// Write our characters one by one to the screen
for(size_t i = 0; i < bufSize; i++){
char c = buffer[i];
if(c == '\n'){// Newline - move cursor down by 1 line, and back to start of line
cursorX = 0;
cursorY += textSize * (FONTHEIGHT + 1);
}
else if(c != '\r'){ // All other text - ignoring carriage returns
if(textWrap && ((cursorX + textSize * (FONTWIDTH + 1)) > WIDTH)){
// If wrapping is enabled and this character will overflow the display, move to start of next line
cursorX = 0;
cursorY += textSize * (FONTHEIGHT + 1);
}
if((cursorY + FONTHEIGHT) > HEIGHT){ // If we have reached the bottom of the screen, start back at the top
cursorY = 0;
cursorX = 0;
}
// Draw the character at the cursor coordinates
YEEP144_SSD1306_DrawChar(cursorX, cursorY, c, textColour, textBGColour, textSize);
// Move the cursor forward by one character
cursorX += (FONTWIDTH + 1) * textSize;
}
}
// Free the memory used
free(buffer);
// Return number of characters printed
return bufSize;
}
/**
* Draws a single character to the screen, with the specified coordinates being the top left of the character
* Uses the specified text colour, background colour and size multiplier
*/
void YEEP144_SSD1306_DrawChar(uint8_t x, uint8_t y, uint8_t c, uint8_t colour, uint8_t bg, uint8_t size){
if ((x >= WIDTH) || // Clip right
(y >= HEIGHT) || // Clip bottom
((x + 6 * size - 1) < 0) || // Clip left
((y + 8 * size - 1) < 0)) // Clip top
return;
for (int8_t i = 0; i < 5; i++) { // Char bitmap = 5 columns
uint8_t line = font[c * 5 + i];
for (int8_t j = 0; j < 8; j++, line >>= 1) {
if (line & 1) {
if (size == 1)
YEEP144_SSD1306_SetPixel(x + i, y + j, colour);
else
YEEP144_SSD1306_DrawFillRect(x + i * size, y + j * size, size, size, colour);
} else if (bg != colour) {
if (size == 1)
YEEP144_SSD1306_SetPixel(x + i, y + j, bg);
else
YEEP144_SSD1306_DrawFillRect(x + i * size, y + j * size, size, size, bg);
}
}
}
if (bg != colour) { // If opaque, draw vertical line for last column
if (size == 1)
YEEP144_SSD1306_DrawFastVLine(x + 5, y, 8, bg);
else
YEEP144_SSD1306_DrawFillRect(x + 5 * size, y, size, 8 * size, bg);
}
}
/**
* Sets the text scaling.
* 1 = Normal, 4 lines will fit on display
* 2 = 2 lines will fit
* 4 = 1 line will fit
*/
void YEEP144_SSD1306_SetTextSize(uint8_t size){
textSize = size;
}
/**
* Set the position of the cursor
* The coordinates defire the top left corner of the character
*/
void YEEP144_SSD1306_SetTextCursor(uint8_t x, uint8_t y){
cursorX = x;
cursorY = y;
}
/**
* Enable or disable text line wrapping
* 0 is disabled, 1 is enabled
*/
void YEEP144_SSD1306_SetTextWrap(uint8_t wrap){
textWrap = wrap;
}
/**
* Draws a 1-bit bitmap to the screen at the specified position
*/
void YEEP144_SSD1306_DrawBitmap(uint8_t x, uint8_t y, const uint8_t bitmap[], uint8_t w, uint8_t h){
uint8_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte
uint8_t b = 0;
for (uint8_t j = 0; j < h; j++, y++) {
for (uint8_t i = 0; i < w; i++) {
if (i & 7){
b <<= 1;
}
else{
b = bitmap[j * byteWidth + i / 8];
}
YEEP144_SSD1306_SetPixel(x + i, y, (b & 0x80) ? SSD1306_WHITE : SSD1306_BLACK);
}
}
}
/**
* Inverts the colours of the display. Note that this happens using a hardware command to the display
* and doesn't touch the buffer
*/
void YEEP144_SSD1306_InvertDisplay(bool i){
YEEP144_SSD1306_SendCommand(i ? SSD1306_INVERTDISPLAY : SSD1306_NORMALDISPLAY);
}
/**
* Helper function that sends a single command to the display
*/
void YEEP144_SSD1306_SendCommand(uint8_t command){
// For data to be recognised as a command, the I2C transaction must start with 0x00
uint8_t data[] = {0x00, command};
// Transmit the data
HAL_I2C_Master_Transmit(&hi2c2, SSD1306_ADDR, data, 2, I2C_TIMEOUT);
}
/*
Much of this code was taken from or inspired by the Adafruit_SSD1306 and Adafruit_GFX libraries,
adapted for use on STM32 rather than the Arduino platform. For that code the following disclaimer applies:
Copyright (c) 2013 Adafruit Industries. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

View File

@@ -0,0 +1,313 @@
#include "YEEP144.h"
#include "YEEP144_Servo.h"
#include "YEEP144_Pinout.h"
#include "stm32h563xx.h"
#include "stm32h5xx_hal.h"
#include "stm32h5xx_hal_gpio.h"
#include "stm32h5xx_hal_gpio_ex.h"
#include "stm32h5xx_hal_rcc.h"
#include "stm32h5xx_hal_tim.h"
#include <stdio.h>
TIM_HandleTypeDef htim5;
TIM_HandleTypeDef htim13;
TIM_HandleTypeDef htim17servo;
uint16_t servoMinTicks[4] = {2048, 2048, 2048, 2048};
uint16_t servoMaxTicks[4] = {4095, 4095, 4095, 4095};
float servoMinDegrees[4] = {0, 0, 0, 0};
float servoMaxDegrees[4] = {180, 180, 180, 180};
uint16_t YEEP144_Servo_MapAngleToTicks(float angle, float minAngle, float maxAngle, uint16_t minTicks, uint16_t maxTicks);
/**
* Initialises servos 1 and 2. This consumes timer 5 and two GPIO
*/
void YEEP144_Servo_Init1_2(void){
// Enable clock to TIM5
__HAL_RCC_TIM5_CLK_ENABLE();
// Configure and initialise TIM5
// Servo PWM is a 1ms - 2ms pulse every 20ms
// This is done using a 50Hz PWM with a duty cycle between 5 and 10%
// Our ARR is set to 40950, the range of the ADC is 0-4095, /2 = 2048, which represents 5% of the total range
// Offsetting by 2048 means we cover the 5 to 10% range
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim5.Instance = TIM5;
htim5.Init.Prescaler = 121;
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
htim5.Init.Period = 40950;
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim5) != HAL_OK){
//Error_Handler();
}
// Disable synchronisation as it is not needed
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK){
//Error_Handler();
}
// Configure the two used PWM channels
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){
//Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim5, &sConfigOC, TIM_CHANNEL_4) != HAL_OK){
//Error_Handler();
}
// Configure GPIOs
YEEP144_GPIO_InitAdv(SERVO1_PORT, SERVO1_PIN | SERVO2_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF2_TIM5);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_4);
}
/**
* Initialises servo 3. This consumes timer 13 and one GPIO
*/
void YEEP144_Servo_Init3(void){
// Enable clock to TIM13
__HAL_RCC_TIM13_CLK_ENABLE();
// Configure and initialise TIM13
// Servo PWM is a 1ms - 2ms pulse every 20ms
// This is done using a 50Hz PWM with a duty cycle between 5 and 10%
// Our ARR is set to 40950, the range of the ADC is 0-4095, /2 = 2048, which represents 5% of the total range
// Offsetting by 2048 means we cover the 5 to 10% range
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim13.Instance = TIM13;
htim13.Init.Prescaler = 121;
htim13.Init.CounterMode = TIM_COUNTERMODE_UP;
htim13.Init.Period = 40950;
htim13.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim13.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim13) != HAL_OK){
//Error_Handler();
}
// Disable synchronisation as it is not needed
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim13, &sMasterConfig) != HAL_OK){
//Error_Handler();
}
// Configure the two used PWM channels
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim13, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){
//Error_Handler();
}
// Configure GPIOs
YEEP144_GPIO_InitAdv(SERVO3_PORT, SERVO3_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF9_TIM13);
HAL_TIM_PWM_Start(&htim13, TIM_CHANNEL_1);
}
/**
* Initialises servo 4. This consumes timer 17 and one GPIO. These resources are share with the buzzer.
* Ensure only one is initialised. When using servo 4, disable the buzzer using DIP switch SW5 so the
* PWM signal is not made audible
*/
void YEEP144_Servo_Init4(void){
// Enable clock to TIM17
__HAL_RCC_TIM17_CLK_ENABLE();
// Configure and initialise TIM17
// Servo PWM is a 1ms - 2ms pulse every 20ms
// This is done using a 50Hz PWM with a duty cycle between 5 and 10%
// Our ARR is set to 40950, the range of the ADC is 0-4095, /2 = 2048, which represents 5% of the total range
// Offsetting by 2048 means we cover the 5 to 10% range
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim17servo.Instance = TIM17;
htim17servo.Init.Prescaler = 121;
htim17servo.Init.CounterMode = TIM_COUNTERMODE_UP;
htim17servo.Init.Period = 40950;
htim17servo.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim17servo.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim17servo) != HAL_OK){
//Error_Handler();
}
// Disable synchronisation as it is not needed
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim17servo, &sMasterConfig) != HAL_OK){
//Error_Handler();
}
// Configure the two used PWM channels
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim17servo, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){
//Error_Handler();
}
// Configure GPIOs
YEEP144_GPIO_InitAdv(SERVO4_PORT, SERVO4_PIN, GPIO_MODE_AF_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, GPIO_AF1_TIM17);
HAL_TIM_PWM_Start(&htim17servo, TIM_CHANNEL_1);
}
/**
* Sets the zero point for a given servo
*
* Default configuration is 2048, yielding a 5% to 10% duty cycle, or 1ms - 2ms pulse, which is the typical standard
* Adjusting the zero offset can help account for differences in servos
* that means 5% duty cycle does not exactly reach 0 degrees
*
* The offset applies to both SetDirect and SetAngle
*/
void YEEP144_Servo_SetMinTicks(uint8_t servo, uint16_t min){
if(servo == 0 || servo >= 4) return;
servoMinTicks[servo-1] = min;
}
/**
* Sets the max point for a given servo
*
* Default configuration is 4096, yielding a 5% to 10% duty cycle, or 1ms - 2ms pulse, which is the typical standard
* Adjusting the max offset can help account for differences in servos
* that means 10% duty cycle does not exactly reach 180 degrees
*
* This only applies when using SetAngle. SetDirect has no limit on what you can set
*/
void YEEP144_Servo_SetMaxTicks(uint8_t servo, uint16_t max){
if(servo == 0 || servo >= 4) return;
servoMaxTicks[servo-1] = max;
}
/**
* Sets the minimum angle range for a given servo
*
* Default is 0, for 0-180deg servos
*
* This only applies to SetAngle
*/
void YEEP144_Servo_SetMinAngle(uint8_t servo, float min){
if(servo == 0 || servo >= 4) return;
servoMinDegrees[servo-1] = min;
}
/**
* Sets the maximum angle range for a given servo
*
* Default is 180, for 0-180deg servos
*
* This only applies to SetAngle
*/
void YEEP144_Servo_SetMaxAngle(uint8_t servo, float max){
if(servo == 0 || servo >= 4) return;
servoMaxDegrees[servo-1] = max;
}
/**
* Directly set the pulse value of the timer to control the position of the servo
* Typically this should be in the range 0-2047
* The function will accept larger pulse values, which will result in a duty cycle >10%, or a pulse of >2ms
* This may be useful for servos who's maximum angle is not achieved at 10%
* Along with setting the zero offset, this should account for troublesome servos, albeit with less elegant maths
*/
void YEEP144_Servo1_SetDirect(uint16_t pulse){
TIM5->CCR1 = pulse + servoMinTicks[0];
}
/**
* Directly set the pulse value of the timer to control the position of the servo
* Typically this should be in the range 0-2047
* The function will accept larger pulse values, which will result in a duty cycle >10%, or a pulse of >2ms
* This may be useful for servos who's maximum angle is not achieved at 10%
* Along with setting the zero offset, this should account for troublesome servos, albeit with less elegant maths
*/
void YEEP144_Servo2_SetDirect(uint16_t pulse){
TIM5->CCR4 = pulse + servoMinTicks[1];
}
/**
* Directly set the pulse value of the timer to control the position of the servo
* Typically this should be in the range 0-2047
* The function will accept larger pulse values, which will result in a duty cycle >10%, or a pulse of >2ms
* This may be useful for servos who's maximum angle is not achieved at 10%
* Along with setting the zero offset, this should account for troublesome servos, albeit with less elegant maths
*/
void YEEP144_Servo3_SetDirect(uint16_t pulse){
TIM13->CCR1 = pulse + servoMinTicks[2];
}
/**
* Directly set the pulse value of the timer to control the position of the servo
* Typically this should be in the range 0-2047
* The function will accept larger pulse values, which will result in a duty cycle >10%, or a pulse of >2ms
* This may be useful for servos who's maximum angle is not achieved at 10%
* Along with setting the zero offset, this should account for troublesome servos, albeit with less elegant maths
*/
void YEEP144_Servo4_SetDirect(uint16_t pulse){
TIM17->CCR1 = pulse + servoMinTicks[3];
}
/**
* Set the servo to a specified angle. This will be based on the minimum and maximum angles set for the servo
* By default this will work in the range 0 to 180
*/
void YEEP144_Servo1_SetAngle(float angle){
if(angle < servoMinDegrees[0] || angle > servoMaxDegrees[0]) return; // If the angle is out of range, do nothing
TIM5->CCR1 = YEEP144_Servo_MapAngleToTicks(angle, servoMinDegrees[0], servoMaxDegrees[0], servoMinTicks[0], servoMaxTicks[0]);
}
/**
* Set the servo to a specified angle. This will be based on the minimum and maximum angles set for the servo
* By default this will work in the range 0 to 180
*/
void YEEP144_Servo2_SetAngle(float angle){
if(angle < servoMinDegrees[1] || angle > servoMaxDegrees[1]) return; // If the angle is out of range, do nothing
TIM5->CCR1 = YEEP144_Servo_MapAngleToTicks(angle, servoMinDegrees[1], servoMaxDegrees[1], servoMinTicks[1], servoMaxTicks[1]);
}
/**
* Set the servo to a specified angle. This will be based on the minimum and maximum angles set for the servo
* By default this will work in the range 0 to 180
*/
void YEEP144_Servo3_SetAngle(float angle){
if(angle < servoMinDegrees[0] || angle > servoMaxDegrees[0]) return; // If the angle is out of range, do nothing
TIM5->CCR1 = YEEP144_Servo_MapAngleToTicks(angle, servoMinDegrees[0], servoMaxDegrees[0], servoMinTicks[0], servoMaxTicks[0]);
}
/**
* Set the servo to a specified angle. This will be based on the minimum and maximum angles set for the servo
* By default this will work in the range 0 to 180
*/
void YEEP144_Servo4_SetAngle(float angle){
if(angle < servoMinDegrees[0] || angle > servoMaxDegrees[0]) return; // If the angle is out of range, do nothing
TIM5->CCR1 = YEEP144_Servo_MapAngleToTicks(angle, servoMinDegrees[0], servoMaxDegrees[0], servoMinTicks[0], servoMaxTicks[0]);
}
/**
* Helper function used to map a float angle to a timer tick value, based on the min and max ranges specified
* e.g. map the angle 90 from the range 0 to 180, into the range 2048 - 4095
*/
uint16_t YEEP144_Servo_MapAngleToTicks(float angle, float minAngle, float maxAngle, uint16_t minTicks, uint16_t maxTicks){
// Map the angle value into the tick range using the min and max parameters
return (uint16_t)(( ((angle - minAngle) * (maxTicks - minTicks)) / (maxAngle - minAngle) ) + minTicks);
}

View File

@@ -0,0 +1,143 @@
#include "YEEP144.h"
#include "YEEP144_Pinout.h"
#include "YEEP144_DebugUART.h"
#define ASCII_ESC 27
// NEC Protocol Timing Constants (in microseconds)
#define NEC_START_LOW 9000 // 9ms leading pulse
#define NEC_START_HIGH 4500 // 4.5ms space
#define NEC_BIT_LOW 560 // 560us burst
#define NEC_BIT_ONE_HIGH 1690 // 1.69ms space for logical 1
#define NEC_BIT_ZERO_HIGH 560 // 560us space for logical 0
#define NEC_TOLERANCE 400 // +/- margin of error
// State Machine States
typedef enum{
IR_STATE_IDLE,
IR_STATE_LEADER,
IR_STATE_DATA
} IR_State_t;
// Variables
volatile IR_State_t ir_state = IR_STATE_IDLE;
volatile uint32_t ir_last_time = 0;
volatile uint32_t ir_raw_bits = 0;
volatile uint8_t ir_bit_count = 0;
volatile uint8_t ir_code_ready = 0;
volatile uint32_t ir_code = 0;
// Enable hardware DWT (Data Watchpoint and Trace) cycle counter
void DWT_Init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
// Arduino-style microsecond measurement
uint32_t micros(void) {
return DWT->CYCCNT / (SystemCoreClock / 1000000);
}
// This exact function name matches the STM32H563 hardware vector table entry
void EXTI3_IRQHandler(void)
{
// Tells the HAL layer to clear the flag and fire your callback routine
HAL_GPIO_EXTI_IRQHandler(IR_RECEIVER_PIN);
}
void YEEP144_TSOP4838_Init(){
GPIO_InitTypeDef GPIO_InitStruct = {0};
// Enable DWT for microsecond timing
DWT_Init();
// Configure Pin F3 for Interrupt on Both Edges
GPIO_InitStruct.Pin = IR_RECEIVER_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; // Triggers on rising or falling change
GPIO_InitStruct.Pull = GPIO_NOPULL; // Uses the TSOP4838 internal pull-up
HAL_GPIO_Init(IR_RECEIVER_PORT, &GPIO_InitStruct);
// Enable and set EXTI Line 3 Vector for STM32H563
HAL_NVIC_SetPriority(IR_RECEIVER_EXTI, 2, 0);
HAL_NVIC_EnableIRQ(IR_RECEIVER_EXTI);
}
uint8_t YEEP144_TSOP4838_Received(void){
return ir_code_ready;
}
uint32_t YEEP144_TSOP4838_Read(void){
return ir_code;
ir_code_ready = 0;
}
// These functions override the weak HAL geometric EXTI callbacks
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin){
if (GPIO_Pin == IR_RECEIVER_PIN)
{
uint32_t current_time = micros();
uint32_t duration = current_time - ir_last_time;
ir_last_time = current_time;
switch(ir_state) {
case IR_STATE_LEADER: // Rising edge (End of 9ms pulse)
if (duration < (NEC_START_LOW - NEC_TOLERANCE) || duration > (NEC_START_LOW + NEC_TOLERANCE)){
ir_state = IR_STATE_IDLE; // False start
}
break;
case IR_STATE_IDLE:
break;
case IR_STATE_DATA:
break;
}
}
}
// For newer STM32 HAL libraries, rising and falling callbacks are separated for memory efficiency
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin){
if (GPIO_Pin == IR_RECEIVER_PIN)
{
uint32_t current_time = micros();
uint32_t duration = current_time - ir_last_time;
ir_last_time = current_time;
switch(ir_state){
case IR_STATE_IDLE: // Falling edge (Start of 9ms pulse)
ir_state = IR_STATE_LEADER;
break;
case IR_STATE_LEADER: // Falling edge (End of 4.5ms space)
if (duration >= (NEC_START_HIGH - NEC_TOLERANCE) && duration <= (NEC_START_HIGH + NEC_TOLERANCE)){
ir_state = IR_STATE_DATA;
ir_bit_count = 0;
ir_raw_bits = 0;
} else {
ir_state = IR_STATE_IDLE; // Invalid preamble
}
break;
case IR_STATE_DATA: // Falling edge (End of a data bit's high space)
// The high duration determines if the bit is a 0 or a 1
if (duration >= (NEC_BIT_ZERO_HIGH - NEC_TOLERANCE) && duration <= (NEC_BIT_ZERO_HIGH + NEC_TOLERANCE)) {
ir_raw_bits >>= 1; // It's a 0
ir_bit_count++;
}
else if (duration >= (NEC_BIT_ONE_HIGH - NEC_TOLERANCE) && duration <= (NEC_BIT_ONE_HIGH + NEC_TOLERANCE)) {
ir_raw_bits >>= 1;
ir_raw_bits |= 0x80000000; // It's a 1
ir_bit_count++;
}
else {
ir_state = IR_STATE_IDLE; // Timing error, reset state
}
uint32_t ir_bits = 0;
if (ir_bit_count == 32) {
ir_bits = ir_raw_bits;
ir_code = (ir_bits >> 16) & 0xFF;
uint32_t command_inverse = (ir_bits >> 24) & 0xFF;
if (ir_code == (uint8_t)(~command_inverse))
ir_code_ready = 1;
ir_state = IR_STATE_IDLE;
}
break;
}
}
}