5.2.4 3.3-15V Adjustable Regulator
86 PD15 GPIO Output ~IO_8_TRIS (reserved for v2)
5.3 Software Design and Implementation
5.3.6 Data Route Handler
The data route handler component includes the parts of the microcontroller firmware that stream data in and out. To input and output data over GPIO, the DMA stream must be configured to the corresponding data. Reading and writing data use the same process, but with the source and destination switched. The data route handler will be informed from the scheduler as to what data to send and receive, and the corresponding hardware interface will be called. In the case of USB, the USB Data Buffer component can be called directly through the data route handler. For DMA, some additional steps are required to prepare the DMA for streaming. The main steps are already done during initialization, but the data source must still be set up before running.
The StartDMATransfer function is implemented to send data. When SendOutputData is called, the front of the queue is brought into StartDMATransfer .
IOM_ERROR SendOutputData(void) { if (output_buf_queue_size == 0) {
return IOM_ERROR_QUEUE_EMPTY; }
IOM_Output_Buffer* bufferData = &(output_buf_queue[output_buf_queue_out_ptr]); output_buf_queue_out_ptr = (output_buf_queue_out_ptr + 1) % OUTPUT_BUF_QUEUE_MAX_SIZE; output_buf_queue_size--; StartDMATransfer(bufferData); return IOM_OK; }
Note that bufferData is a pointer. However, this pointer is not being allocated since it is a circular buffer. As a result, There is no need to free this memory.
StartDMATransfer first configures the DMA for the correct data source and output. Since the DMA clock is not running yet, the HAL library function HAL_DMA_Start is utilized. Then, the DMA timer channel is enabled with __HAL_TIM_ENABLE_DMA . At this point, the DMA is technically started, but since the connected timer is not running yet, nothing will happen.
void StartDMATransfer(IOM_Output_Buffer* pBuffer) { bytesToSend = pBuffer->length;
HAL_DMA_Start(htim8.hdma[TIM_DMA_ID_CC4], (uint32_t)pBuffer->data, (uint32_t)(&(GPIOD->ODR)), pBuffer->length);
__HAL_TIM_ENABLE_DMA(&htim8, TIM_DMA_CC4);
Due to needing to start both the clock timers and the DMA output stream at as close to the same time as possible, the HAL library cannot be used. In testing, the HAL library was used and it was found to have too much delay between the clock signal and data signal. Rather, the timer
registers are edited directly. TIM8 is the timer connected to control DMA. These lines of code clear the enabled registers for the timer, then enable channel 4 for the timer and the
corresponding interrupt. Since the DMA interrupt does not work, the timer interrupt can be used to determine when all of the data has been sent.
TIM8->CCER &= ~(TIM_CCER_CC4E); TIM8->CCER |= TIM_CCER_CC4E; TIM8->BDTR |= TIM_BDTR_MOE; TIM8->DIER |= TIM_DIER_CC4IE;
Since each pin on the I/O Master can be dynamically configured as an input, output, or clock, each pin must be checked to see what it is set as and configure it accordingly before sending data. First, all of the clock pins have their timers setup.
TIM_TypeDef* pTim;
for (int i = 0; i < 4; i++) {
if (IO_Pins[i].dataState == IOCFG_DATA_STATE_CLOCK) { GetIOPinTimer(i + 1, pTim);
pTim->CCER &= ~(TIM_CCER_CC2E); } else {
pTim->CCER &= ~(TIM_CCER_CC1E); }
pTim->BDTR |= TIM_BDTR_MOE; pTim->CNT = 5;
}
Then, the chip select pins are set to their active state (opposite of their idle state). Afterwards, all other pins are set to their idle state.
else if (IO_Pins[i].dataState == IOCFG_DATA_STATE_CS) { if (IO_Pins[i].idleState == IOCFG_IDLE_STATE_HIGH) { //Drive the pin low
IOPIN_GPIO_OUTPUT_PORT->ODR &= ~(GetIOPinOutputMask(i + 1)); } else {
//Drive the pin high
IOPIN_GPIO_OUTPUT_PORT->ODR |= (GetIOPinOutputMask(i + 1)); }
} else {
if (IO_Pins[i].idleState == IOCFG_IDLE_STATE_HIGH) { //Set default to high
IOPIN_GPIO_OUTPUT_PORT->ODR |= (GetIOPinOutputMask(i + 1)); } else {
//Set default to low
IOPIN_GPIO_OUTPUT_PORT->ODR &= ~(GetIOPinOutputMask(i + 1)); }
}
}
The last step is to set the count of the DMA timer to immediately cause a rising edge and enable its timer.
TIM8->CNT = 5;
TIM8->CR1 |= TIM_CR1_CEN; }
The clock timers are not enabled because the data output must be changed before the clock changes. To achieve this, the other clocks are enabled in the TIM8 interrupt.
//Enable clocks TIM_TypeDef* pTim;
for (int i = 0; i < 4; i++) {
if (IO_Pins[i].dataSate == IOCFG_DATA_STATE_CLOCK) { GetIOPinTimer(i + 1, pTim);
//TIM5 uses channel 2, all others use channel 1 if (pTim == TIM5) {
pTim->CCER |= TIM_CCER_CC2E; } else {
pTim->CCER |= TIM_CCER_CC1E; }
Once the DMA stream has finished sending its last byte, the clock timers are all disabled.
if (bytesToSend == 1) {
//Disable timer if there is no more data to send pTim->CR1 &= ~(TIM_CR1_CEN);
pTim->CNT = 5; }
} }
The TIM8 interrupt flag must be disabled so the interrupt can be triggered again. If the last byte has been sent, then TIM8 is disabled, the DMA is reset, and the busy flag is turned off.
Otherwise, the bytesToSend variable decrements and the interrupt is exited. (CS)
//Disable flag
TIM8->SR &= ~(TIM_SR_CC4IF); if (bytesToSend == 1) {
TIM8->CR1 &= ~(TIM_CR1_CEN); ResetDMA(); DMABusyFlag = 0; } else { bytesToSend--; } }
The data route code for streaming data over DMA was tested to ensure that data could be written to every pin, a variable amount of data can be written, and that the chip select is activated
correctly. Testing was accomplished through communications with an SPI LCD screen, along with oscilloscope waveform and voltage measurements.