Thanks, for all the input - I think I cracked it!
Forget the HAL shit and use direct setting of the peripheral registers.
I get the sentiment but I prefer to use the HAL where possible since it makes the code a lot more understandable (except when it doesn't
) Nonetheless, your link was helpful when it came to debugging everything - thank you.
Seems like a job for the timer DMA burst feature.
I think this function specifically allows you to write to the timer peripheral only. The destination address you provide is referenced to the timer address.
Read out and check/post content of TIM, DMA and relevant GPIO registers.
This is what I did and I this helped me to find the issue.
Basically, I followed section 9.3.18 "Stream configuration procedure" of the STM32F446 reference manual and this showed me the registers I need to set to make it work. Something like this worked for me:
uint32_t pixelclock[4] = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };
__HAL_RCC_DMA2_CLK_ENABLE();
DMA2_Stream1->PAR = (uint32_t)&(GPIOA->BSRR); // Set peripheral address
DMA2_Stream1->M0AR = (uint32_t) pixelclock; // Set memory address
DMA2_Stream1->NDTR = 4; // set number of bytes to transfer
DMA2_Stream1->CR |= (6 << DMA_SxCR_CHSEL_Pos); // enable channel 6 (TIM1_CH1)
DMA2_Stream1->CR |= (2 << DMA_SxCR_PL_Pos); // set to high priority
// FIFO should be disabled by default
DMA2_Stream1->CR |= (1 << DMA_SxCR_DIR_Pos); //set memory-to-peripheral
DMA2_Stream1->CR |= (1 << DMA_SxCR_MINC_Pos); //Incremement memory address
// Assume PINC is 0
//Assume burst is set to single transfer
DMA2_Stream1->CR |= (2 << DMA_SxCR_MSIZE_Pos); // Set memory data width to a word
DMA2_Stream1->CR |= (2 << DMA_SxCR_PSIZE_Pos); // Set peripheral data width to a word
DMA2_Stream1->CR |= (1 << DMA_SxCR_CIRC_Pos); // Set to circular mode
// Assume DBM is zero
DMA2_Stream1->CR |= (1 << DMA_SxCR_EN_Pos); //enable DMA
I then compared the CR register to the value when I used the HAL code in my original post and I found that actually it only wrote a couple of the bits, but not enough to actually make the DMA run. I then stumbled across the "HAL_DMA_Init" function that basically does the exact code above!
So now I have a working implementation that looks like:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM1_Init();
MX_DMA_Init();
/* USER CODE BEGIN 2 */
uint32_t pixelclock[4] = {0x00000001, 0x00010000, 0x00000001, 0x00010000 };
HAL_DMA_Init(&hdma_tim1_ch1);
HAL_DMA_RegisterCallback(&hdma_tim1_ch1, HAL_DMA_XFER_CPLT_CB_ID, DMA_XFER_CPLT);
HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);
TIM1->DIER |= (1 << 9);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(500);
HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)pixelclock, (uint32_t)&(GPIOA->BSRR), 4);
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1);
}
/* USER CODE END 3 */
}
void DMA_XFER_CPLT(DMA_HandleTypeDef *hdma)
{
// Disable the DMA (not clear if this is necessary)
HAL_DMA_Abort_IT(&hdma_tim1_ch1);
// Stop the timer
HAL_TIM_OC_Stop(&htim1, TIM_CHANNEL_1);
// This appears necessary to deassert the trigger and prevent DMA triggering immediately when enabled
TIM1->DIER &= ~(1 << 9);
TIM1->DIER |= (1 << 9);
}