STMicroelectronics' implementation of an I2S driver, also including DMA support.

Dependents:   temp X_NUCLEO_CCA01M1 X_NUCLEO_CCA01M1 X_NUCLEO_CCA02M1

Platform compatibility

This driver has been designed to support a wide range of the Nucleo F4 Family of platforms and MCUs, but not all members of this family support I2S and/or some of the members might require slight modifications to the sources of this driver in order to make it work on those.

This driver has for now been tested only with the following platforms:

Revision:
13:fa1b24df9025
Parent:
12:7309748f058a
Child:
14:0060a9850c5f
--- a/targets/TARGET_STM/stm_i2s_api.c	Thu Dec 22 15:00:34 2016 +0100
+++ b/targets/TARGET_STM/stm_i2s_api.c	Fri Dec 23 12:27:30 2016 +0100
@@ -790,7 +790,7 @@
  *  @param dev_i2s reference to the I2S object.
  *  @return the computed real frequency.
  */
-static float i2s_compute_real_frequency(i2s_t *dev_i2s)
+static uint32_t i2s_compute_real_frequency(i2s_t *dev_i2s)
 {
     uint32_t i2sclk = 0U, i2sdiv = 2U, i2sodd = 0U, packetlength = 1U, tmp = 0U;
     I2S_HandleTypeDef *hi2s;
@@ -840,11 +840,12 @@
     /* Compute the I2S frequencies. */
     uint32_t format_factor = (hi2s->Init.DataFormat == I2S_DATAFORMAT_16B ? 16 : 32);
     uint32_t mclk_factor = (hi2s->Init.MCLKOutput == I2S_MCLKOUTPUT_ENABLE ? (format_factor == 16 ? 8 : 4) : 1);
-    float f = i2sclk / (2 * format_factor * ((2 * i2sdiv) + i2sodd) * mclk_factor);
+    uint32_t f = (uint32_t)((float)i2sclk / (float)(2 * format_factor * ((2 * i2sdiv) + i2sodd) * mclk_factor));
 
     return f;
 }
 
+#if 0 // betzw
 /** Computes the two-div-plus-odd factor of a given I2S objects
  *  on a desired frequency.
  *
@@ -895,17 +896,155 @@
 
     return f;
 }
+#endif // 0
 
-// betzw - QUESTION: seems to resolve only the case of multiples of 2!?!
+static uint32_t i2s_compute_closest_frequency(i2s_t *dev_i2s, uint32_t target_freq, uint8_t *mclk_enabled) {
+    uint32_t i2sclk = 0U, i2sdiv = 2U, i2sodd = 0U, packetlength = 1U, tmp = 0U;
+    uint32_t f1, f2;
+    I2S_HandleTypeDef *hi2s;
+
+    /* init mclk_enabled */
+    *mclk_enabled = 0;
+
+    /* Get the I2S handle. */
+    hi2s = i2s_get_handle(dev_i2s);
+
+    /* Check the frame length (For the Prescaler computing). */
+    if (hi2s->Init.DataFormat != I2S_DATAFORMAT_16B)
+    {
+        /* Packet length is 32 bits */
+        packetlength = 2U;
+    }
+
+    /* Get I2S source Clock frequency. */
+    i2sclk = I2S_GetInputClock(hi2s);
+
+    if(hi2s->Init.MCLKOutput == I2S_MCLKOUTPUT_DISABLE) { // betzw: one more degree of freedom, i.e. mclk can be enabled
+        /* MCLK output is disabled. */
+        tmp = (uint32_t)(((((i2sclk / (32U * packetlength)) * 10U) / target_freq)) + 5U);
+
+        /* Remove the flatting point. */
+        tmp = tmp / 10U;
+
+        /* Check the parity of the divider. */
+        i2sodd = (uint32_t)(tmp & (uint32_t)1U);
+
+        /* Compute the i2sdiv prescaler. */
+        i2sdiv = (uint32_t)((tmp - i2sodd) / 2U);
+
+        /* Test if the divider is 1 or 0 or greater than 0xFF. */
+        if ((i2sdiv < 2U) || (i2sdiv > 0xFFU))
+        {
+            /* Set the default values. */
+            i2sdiv = 2U;
+            i2sodd = 0U;
+        }
+
+        /* Compute the I2S frequency */
+        uint32_t format_factor = (hi2s->Init.DataFormat == I2S_DATAFORMAT_16B ? 16 : 32);
+        uint32_t mclk_factor = 1;
+        f1 = (uint32_t)((float)i2sclk / (float)(2 * format_factor * ((2 * i2sdiv) + i2sodd) * mclk_factor));
+
+        if(f1 == target_freq) return f1;
+
+        /* set mclk_enabled */
+        *mclk_enabled = 1;
+    }
+
+    /* Set mclk enabled */
+    hi2s->Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;
+
+    /* MCLK output is enabled. */
+    tmp = (uint32_t)(((((i2sclk / 256U) * 10U) / target_freq)) + 5U);
+
+    /* Remove the flatting point. */
+    tmp = tmp / 10U;
+
+    /* Check the parity of the divider. */
+    i2sodd = (uint32_t)(tmp & (uint32_t)1U);
+
+    /* Compute the i2sdiv prescaler. */
+    i2sdiv = (uint32_t)((tmp - i2sodd) / 2U);
+
+    /* Test if the divider is 1 or 0 or greater than 0xFF. */
+    if ((i2sdiv < 2U) || (i2sdiv > 0xFFU))
+    {
+        /* Set the default values. */
+        i2sdiv = 2U;
+        i2sodd = 0U;
+    }
+
+    /* Compute the I2S frequency */
+    uint32_t format_factor = (hi2s->Init.DataFormat == I2S_DATAFORMAT_16B ? 16 : 32);
+    uint32_t mclk_factor = (format_factor == 16 ? 8 : 4);
+
+    f2 = (uint32_t)((float)i2sclk / (float)(2 * format_factor * ((2 * i2sdiv) + i2sodd) * mclk_factor));
+    if(f2 == target_freq) return f2;
+
+    /* return closest result */
+    uint32_t diff1, diff2;
+
+    if(*mclk_enabled != 0) {
+    	if(target_freq > f1) diff1 = target_freq - f1;
+    	else diff1 = f1 - target_freq;
+    } else { // no 'f1' available
+    	return f2;
+    }
+
+    if(target_freq > f2) diff2 = target_freq - f2;
+    else diff2 = f2 - target_freq;
+
+    if(diff1 < diff2) {
+    	/* reset mclk */
+        hi2s->Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
+
+        /* reset mclk_enabled */
+        *mclk_enabled = 0;
+
+        return f1;
+    }
+
+    return f2;
+}
+
+static inline void i2s_reset_mclk(i2s_t *dev_i2s) {
+    I2S_HandleTypeDef *hi2s;
+
+    /* Get the I2S handle. */
+    hi2s = i2s_get_handle(dev_i2s);
+
+    /* rest mclk enabled */
+    hi2s->Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
+}
+
+static inline uint32_t i2s_align_freq(uint32_t real_freq) {
+	uint32_t ret = (uint32_t)real_freq;
+
+	/* align to used digital audio frequencies */
+	if((ret == 11025) || (ret == 44056)) return ret;
+
+	/* align to 10 */
+	ret /= 10;
+	ret *= 10;
+
+	if((ret == 22050) || (ret == 47250)) return ret;
+
+	/* align to 100 */
+	ret /= 100;
+	ret *= 100;
+
+	return ret;
+}
+
 int8_t i2s_harmonize(i2s_t *dev_i2s_1, uint32_t *freq_i2s_1, i2s_t *dev_i2s_2, uint32_t *freq_i2s_2)
 {
     /* Compute the real frequencies. */
-    float f1 = i2s_compute_real_frequency(dev_i2s_1);
-    float f2 = i2s_compute_real_frequency(dev_i2s_2);
+    uint32_t f1 = i2s_compute_real_frequency(dev_i2s_1);
+    uint32_t f2 = i2s_compute_real_frequency(dev_i2s_2);
 
     if (f1 == f2) {
-        *freq_i2s_1 = (uint32_t)f1;
-        *freq_i2s_2 = (uint32_t)f2;
+        *freq_i2s_1 = i2s_align_freq(f1);
+        *freq_i2s_2 = i2s_align_freq(f2);
         return 0;
     }
 
@@ -913,6 +1052,83 @@
 
     /* Compute the desired frequencies so that they are multiple one of the
        other. */
+
+    /* figure out multiply value and i2s to adapt and it's target freq */
+    uint32_t multiplier;
+    i2s_t *target_dev;
+    uint32_t target_freq;
+
+    if(f1 > f2) {
+    	multiplier = (uint32_t)((float)f1 / (float)f2);
+    	target_freq = f2 * multiplier;
+    	target_dev = dev_i2s_1;
+    } else {
+    	multiplier = (uint32_t)((float)f2 / (float)f1);
+    	target_freq = f1 * multiplier;
+    	target_dev = dev_i2s_2;
+    }
+
+    if(multiplier == 1) { // nothing that can be reasonably done
+    	return -1;
+    }
+
+    /* compute closest freq */
+    uint8_t mclk_enabled = 0;
+    uint32_t closest_freq = i2s_compute_closest_frequency(target_dev, target_freq, &mclk_enabled);
+
+    if(closest_freq == target_freq) {
+    	if(target_dev == dev_i2s_1) {
+    		/* Return the desired frequencies. */
+    		*freq_i2s_1 = i2s_align_freq(closest_freq);
+    		*freq_i2s_2 = i2s_align_freq(f2);
+    	} else {
+    		/* Return the desired frequencies. */
+    		*freq_i2s_1 = i2s_align_freq(f1);
+    		*freq_i2s_2 = i2s_align_freq(closest_freq);
+    	}
+    	return 0;
+    }
+
+    /* reset mclk */
+    if(mclk_enabled != 0) {
+    	i2s_reset_mclk(target_dev);
+    }
+
+    /* try other way round */
+    if(target_dev == dev_i2s_1) {
+    	target_dev = dev_i2s_2;
+    	target_freq = (uint32_t)((float)f1 / (float)multiplier);
+    } else {
+    	target_dev = dev_i2s_1;
+    	target_freq = (uint32_t) ((float)f2 / (float)multiplier);
+    }
+
+    /* compute closest freq */
+    mclk_enabled = 0;
+    closest_freq = i2s_compute_closest_frequency(target_dev, target_freq, &mclk_enabled);
+
+    if(closest_freq == target_freq) {
+    	if(target_dev == dev_i2s_1) {
+    		/* Return the desired frequencies. */
+    		*freq_i2s_1 = i2s_align_freq(closest_freq);
+    		*freq_i2s_2 = i2s_align_freq(f2);
+    	} else {
+    		/* Return the desired frequencies. */
+    		*freq_i2s_1 = i2s_align_freq(f1);
+    		*freq_i2s_2 = i2s_align_freq(closest_freq);
+    	}
+    	return 0;
+    }
+
+    /* reset mclk */
+    if(mclk_enabled != 0) {
+    	i2s_reset_mclk(target_dev);
+    }
+
+    /* return failure */
+    return -1;
+
+#if 0 // betzw - QUESTION: seems to resolve only the case of multiples of 2!?!
     float q;
     if (f1 < f2)
         q = f2 / f1;
@@ -966,6 +1182,7 @@
     *freq_i2s_2 = (uint32_t)f2;
 
     return 0;
+#endif // 0
 }
 
 #endif