• No results found

Data Route Handler

In document I/O Master (Page 140-144)

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.  

In document I/O Master (Page 140-144)

Related documents