STM32F3Discovery, PWM and LED

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderator: RoccoMarco

User avatar
part1zan_
Posts: 44
Joined: Sun Sep 08, 2013 9:04 pm

STM32F3Discovery, PWM and LED

Post by part1zan_ »

I'm trying to use PWM on an STM32F3discovery to make the LEDs glow. The code I'm using looks like that:

Code: Select all

//blah-blah-blah, irrelevant whatever
static PWMConfig pwmcfg = {
   10000,
   10000,
   NULL,
   {
      {PWM_OUTPUT_DISABLED, NULL},
      {PWM_OUTPUT_DISABLED, NULL},
      {PWM_OUTPUT_DISABLED, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL}
   },
   0
};

int main() {
// also, some irrelevant code
   pwmStart(&PWMD4, &pwmcfg);
   pwmEnableChannel(&PWMD4, 3, 5000);
// and an infinite cycle, as we always do
}

What my question is: which modes do I set for the LEDs (using palSetPadMode) to make that work?
upd: of course, I updated mcuconf.h && halconf.h to use PWM & TIM4. The code compiles, but the LEDs don't glow
User avatar
Tectu
Posts: 1225
Joined: Thu May 10, 2012 9:50 am

Re: STM32F3Discovery, PWM and LED

Post by Tectu »

I took a look at the F3 schematics and the LEDs are on port PE8 to PE15. From the datasheet of the STM32F303VCT6 we see that PE8 to PE14 are usable as timer outputs (but most of them are the same, just inverted). This alternate function is in the AF2 column. Therefore it would look like this:

Code: Select all

palSetPadMode(GPIOE, 8, PAL_MODE_ALTERNATE(2));


So my question: Why do you use PWD4 there? I never really worked with the F3 myself but I assume that it's very similar to the F4 and in this case you'd be forced to use PWMD1, no?


~ Tectu
Last edited by Tectu on Mon Oct 28, 2013 8:47 pm, edited 1 time in total.
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: STM32F3Discovery, PWM and LED

Post by Giovanni »

Hi,

You have to program the LED pins to the appropriate Alternate Mode the default is GPIO that is not affected by PWM. See the F4 discovery demo for an example.

Edit: What Tectu said :)

Giovanni
User avatar
part1zan_
Posts: 44
Joined: Sun Sep 08, 2013 9:04 pm

Re: STM32F3Discovery, PWM and LED

Post by part1zan_ »

Tectu, as the example says, I'm not: the example itself uses PWMD4. Also, setting mode to ALTERNATE(2) didn't help. Maybe I'm missing something? Maybe something in the config?
User avatar
part1zan_
Posts: 44
Joined: Sun Sep 08, 2013 9:04 pm

Re: STM32F3Discovery, PWM and LED

Post by part1zan_ »

After reading the datasheet (which Tectu pointed me to), I managed to get one of the LEDs to blink, but not glow. BTW, Tectu was right: I am definitely forced to use TIM1 and PWMD1.
The datasheet also says that PE8 uses some thingy called TIM1_CH1N. Is it some division of Channel 1 of TIM1? How do I get it to work?
User avatar
Tectu
Posts: 1225
Joined: Thu May 10, 2012 9:50 am

Re: STM32F3Discovery, PWM and LED

Post by Tectu »

TIM1_CH1N is TIM1_CH1 just inverted. You enable it as you do with TIM1_CH1: Set the pin to the appropriate mode (AF2 again), and enable it in the timer config register (not sure if this is needed for the dedicated pin - I don't think so).
To get the LED to "glow", you need to get to use an appropriate frequency. In your code from your first post you seem to work with 10MHz which is by far too much. Do you have any scope to make sure that you get a working PWM at all?


~ Tectu
User avatar
part1zan_
Posts: 44
Joined: Sun Sep 08, 2013 9:04 pm

Re: STM32F3Discovery, PWM and LED

Post by part1zan_ »

I made the frequency a bit less, but the LEDs didn't glow: they continue blinking. So I assume the PWM doesn't seem to work OK.
User avatar
Tectu
Posts: 1225
Joined: Thu May 10, 2012 9:50 am

Re: STM32F3Discovery, PWM and LED

Post by Tectu »

Hmm, probably the 10MHz wouldn't be such a big problem at all - I had motors in mind, sorry :P
Do you have a scope to check the output of the pin? And why do they blink? Are you still modifying the pin states through the PAL commands? In what frequency? Can you please post your full code (main.c)?


~ Tectu
User avatar
part1zan_
Posts: 44
Joined: Sun Sep 08, 2013 9:04 pm

Re: STM32F3Discovery, PWM and LED

Post by part1zan_ »

You mean an oscilloscope? No, I don't have one unfortunately. I don't use PALs anymore, and here's my whole code (it's a bit messy)

Code: Select all

/*
   ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010,
   2011,2012 Giovanni Di Sirio.

   This file is part of ChibiOS/RT.

   ChibiOS/RT is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   ChibiOS/RT is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */

#include "ch.h"
#include "hal.h"
#include "test.h"
#include "spi.h"
#include "i2c.h"
#include "usbcfg.h"
#include "chprintf.h"
#include "shell.h"
#include "chthreads.h"
// testing rtc
#include "chrtclib.h"

// FIXME :: awaiting fix from the ChibiOS/RT team
/*
#define usb_lld_connect_bus(usbp)
#define usb_lld_disconnect_bus(usbp)
*/
// This didn't work =(^U IT'S ALIVE! ALIVE!!!
#define USB_GPIO_PORT GPIOA
#define USBDM_BIT GPIOA_USB_DM
#define USBDP_BIT GPIOA_USB_DP

void usb_lld_disconnect_bus(USBDriver *usbp)
{
   (void) usbp;
   palClearPort(USB_GPIO_PORT, (1<<USBDM_BIT) | (1<<USBDP_BIT));
   palSetPadMode(USB_GPIO_PORT, USBDM_BIT, PAL_MODE_OUTPUT_PUSHPULL);
   palSetPadMode(USB_GPIO_PORT, USBDP_BIT, PAL_MODE_OUTPUT_PUSHPULL);
}

void usb_lld_connect_bus(USBDriver *usbp)
{
   (void) usbp;
   palClearPort(USB_GPIO_PORT, (1<<USBDM_BIT) | (1<<USBDP_BIT));
   palSetPadMode(USB_GPIO_PORT, USBDM_BIT, PAL_MODE_ALTERNATE(14));
   palSetPadMode(USB_GPIO_PORT, USBDP_BIT, PAL_MODE_ALTERNATE(14));
}

/* Virtual serial port over USB.*/
SerialUSBDriver SDU1;

static float mdps_per_digit = 8.75;

uint8_t schema = 0;
#define MAXSCH 2

static const SPIConfig spi1cfg = {
   NULL,
   /* HW dependent part.*/
   GPIOE,
   GPIOE_SPI1_CS,
   SPI_CR1_BR_0 | SPI_CR1_BR_1 | SPI_CR1_CPOL | SPI_CR1_CPHA,
   0
};

static const I2CConfig i2cconfig = {
   0x00902025, //voodoo magic
   0,
   0
};

/*
static uint8_t readByteSPI(uint8_t reg)
{
   char txbuf[2] = {0x80 | reg, 0xFF};
   char rxbuf[2];
   spiSelect(&SPID1);
   spiExchange(&SPID1, 2, txbuf, rxbuf);
   spiUnselect(&SPID1);
   return rxbuf[1];
}
*/
static uint8_t writeByteSPI(uint8_t reg, uint8_t val)
{
   char txbuf[2] = {reg, val};
   char rxbuf[2];
   spiSelect(&SPID1);
   spiExchange(&SPID1, 2, txbuf, rxbuf);
   spiUnselect(&SPID1);
   return rxbuf[1];
}

/*
static uint8_t readByteI2C(uint8_t addr, uint8_t reg)
{
   uint8_t data;
   i2cAcquireBus(&I2CD1);
   (void)i2cMasterTransmitTimeout(&I2CD1, addr, &reg, 1, &data, 1, TIME_INFINITE);
   i2cReleaseBus(&I2CD1);
   return data;
}
*/
static void writeByteI2C(uint8_t addr, uint8_t reg, uint8_t val)
{
   uint8_t cmd[] = {reg, val};
   i2cAcquireBus(&I2CD1);
   (void)i2cMasterTransmitTimeout(&I2CD1, addr, cmd, 2, NULL, 0, TIME_INFINITE);
   i2cReleaseBus(&I2CD1);
}

static void initGyro(void)
{
   /* see the L3GD20 Datasheet */
   writeByteSPI(0x20, 0xcF);
}
static void initAccel(void)
{
   // Highest speed, enable all axes
   writeByteI2C(0x19, 0x20, 0x97);
}
static void initMag(void)
{
   // Highest speed
   writeByteI2C(0x1E, 0x00, 0x1C);
   writeByteI2C(0x1E, 0x02, 0x00);
}
static uint8_t readGyro(float* data)
{
   /* read from L3GD20 registers and assemble data */
   /* 0xc0 sets read and address increment */
   char txbuf[8] = {0xc0 | 0x27, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
   char rxbuf[8];
   spiSelect(&SPID1);
   spiExchange(&SPID1, 8, txbuf, rxbuf);
   spiUnselect(&SPID1);
   if (rxbuf[1] & 0x7) {
      int16_t val_x = (rxbuf[3] << 8) | rxbuf[2];
      int16_t val_y = (rxbuf[5] << 8) | rxbuf[4];
      int16_t val_z = (rxbuf[7] << 8) | rxbuf[6];
      data[0] = (((float)val_x) * mdps_per_digit)/1000.0;
      data[1] = (((float)val_y) * mdps_per_digit)/1000.0;
      data[2] = (((float)val_z) * mdps_per_digit)/1000.0;
      return 1;
   }
   return 0;
}
static uint8_t readAccel(float* data)
{
   // setting MSB makes it increment the address for a multiple byte read
   uint8_t start_reg = 0x27 | 0x80;
   uint8_t out[7];
   i2cAcquireBus(&I2CD1);
   msg_t f = i2cMasterTransmitTimeout(&I2CD1, 0x19, &start_reg, 1, out, 7, TIME_INFINITE);
   (void)f;
   i2cReleaseBus(&I2CD1);
   if (out[0] & 0x8) {
      int16_t val_x = (out[2] << 8) | out[1];
      int16_t val_y = (out[4] << 8) | out[3];
      int16_t val_z = (out[6] << 8) | out[5];
      // Accel scale is +- 2.0g
      data[0] = ((float)val_x)*(4.0/(65535.0))*9.81;
      data[1] = ((float)val_y)*(4.0/(65535.0))*9.81;
      data[2] = ((float)val_z)*(4.0/(65535.0))*9.81;
      return 1;
   }
   return 0;
}
static uint8_t readMag(float* data)
{
   uint8_t start_reg = 0x03;
   uint8_t out[7];
   i2cAcquireBus(&I2CD1);
   msg_t f = i2cMasterTransmitTimeout(&I2CD1, 0x1E, &start_reg, 1, out, 7, TIME_INFINITE);
   (void)f;
   i2cReleaseBus(&I2CD1);
   //out[6] doesn't seem to reflect actual new data, so just push out every time
   int16_t val_x = (out[0] << 8) | out[1];
   int16_t val_z = (out[2] << 8) | out[3];
   int16_t val_y = (out[4] << 8) | out[5];
   data[0] = ((float)val_x)*1.22;
   data[1] = ((float)val_y)*1.22;
   data[2] = ((float)val_z)*1.22;
   return 1;
}

#define SHELL_WA_SIZE   THD_WA_SIZE(1024)

static void cmd_test(BaseSequentialStream *chp, int argc, char *argv[]) {
   (void) argc;
   (void) argv;
   chprintf(chp, "ChibiOS test suite\r\n");
   TestThread(chp);
}

static void cmd_gyrodata(BaseSequentialStream *chp, int argc, char *argv[]) {
   float gyrodata[3];
   uint32_t times = 5;
   uint32_t delay = 200;
   if (argc >= 1) {
      times = atoi(argv[0]);
   }
   if (argc >= 2) {
      delay = atoi(argv[1]);
   }
   uint32_t i = 0;
   chprintf(chp, " Number 1\t Number 2\t Number 3\t #\r\n");
   for (i = 0; i < times; i++) {
      if (readGyro(gyrodata)) {
         chprintf(chp, " %f\t %f\t %f\t %d\r\n", gyrodata[0], gyrodata[1], gyrodata[2], i);
      }
      chThdSleepMilliseconds(delay);
   }
}

static void cmd_magdata(BaseSequentialStream *chp, int argc, char *argv[]) {
   float magdata[3];
   uint32_t times = 5;
   uint32_t delay = 200;
   if (argc >= 1) {
      times = atoi(argv[0]);
   }
   if (argc >= 2) {
      delay = atoi(argv[1]);
   }
   uint32_t i = 0;
   chprintf(chp, " Number 1\t Number 2\t Number 3\t #\r\n");
   for (i = 0; i < times; i++) {
      if (readMag(magdata)) {
         chprintf(chp, " %f\t %f\t %f\t %d\r\n", magdata[0], magdata[1], magdata[2], i);
      }
      chThdSleepMilliseconds(delay);
   }
}

static void cmd_adjust(BaseSequentialStream *chp, int argc, char *argv[]) {
   float gyrodata[2][3];
   (void) argc;
   (void) argv;
   chprintf(chp, "Adjust the motorcycle vertically and press the User button\r\n");
   while (!palReadPad(GPIOA, GPIOA_BUTTON)) { }
   if (!readGyro(gyrodata[0])) {
      chprintf(chp, "Error getting gyrodata!\r\n");
      return;
   }
   else {
      chprintf(chp, "Got gyrodata for the 1st time!\r\n");
   }
   chThdSleepMilliseconds(5000);
   chprintf(chp, "Now place it on the side stand and press the button again\r\n");
   while (!palReadPad(GPIOA, GPIOA_BUTTON)) { }
   if (!readGyro(gyrodata[1])) {
      chprintf(chp, "Error getting gyrodata!\r\n");
      return;
   } else {
      chprintf(chp, "Got gyrodata for the 2nd time!\r\n");
   }
   chprintf(chp, "Got your gyrodata successfully\r\n");
   uint8_t i = 0;
   for (i = 0; i < 2; i++) {
      chprintf(chp, "%d\t %f\t %f\t %f\r\n", i, gyrodata[i][0], gyrodata[i][1], gyrodata[i][2]);
   }
}

static void cmd_time(BaseSequentialStream *chp, int argc, char *argv[]) {
   if (argc == 0) {
      time_t unixTime = rtcGetTimeUnixSec(&RTCD1);
      struct tm ts = *gmtime(&unixTime);
      chprintf(chp, "current rtc time: %d\r\n", unixTime);
      chprintf(chp, "which is %d-%02d-%02d %02d:%02d:%02d UTC\r\n", (1900+ts.tm_year), (1+ts.tm_mon), ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec);
   } else if (argc == 1) {
      time_t newtime = atoi(argv[0]);
      rtcSetTimeUnixSec(&RTCD1, newtime);
      struct tm ts = *gmtime(&newtime);
      chprintf(chp, "New time is: %d\r\n", newtime);
      chprintf(chp, "which is %d-%02d-%02d %02d:%02d:%02d UTC\r\n", (1900+ts.tm_year), (1+ts.tm_mon), ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec);

   }
}

static void cmd_mem(BaseSequentialStream *chp, int argc, char *argv[]) {
//   size_t n, size;
   (void) argv;
   (void) argc;

//   n = chHeapStatus(&size);
   chprintf(chp, "core free memory : %u bytes\r\n", chCoreStatus());
//   chprintf(chp, "heap fragments   : %u\r\n", n);
//   chprintf(chp, "heap free total  : %u bytes\r\n", size);
}

static void cmd_reboot(BaseSequentialStream *chp, int argc, char *argv[]) {
   (void) argc;
   (void) argv;
   chprintf(chp, "Rebooting...\r\n");
   chThdSleepMilliseconds(100);
   NVIC_SystemReset();
}

static void cmd_schema(BaseSequentialStream *chp, int argc, char *argv[]) {
   if (argc == 0) {
      chprintf(chp, "Current blink schema is %d\r\n", schema);
   } else {
      uint8_t newsch = atoi(argv[0]);
      if (newsch > MAXSCH) {
         newsch = schema;
      }
      schema = newsch;
      chprintf(chp, "Set blinking schema to %d\r\n", schema);
   }
}

static const ShellCommand shCmds[] = {
   {"test",      cmd_test},
   {"gyrodata",   cmd_gyrodata},
   {"magdata", cmd_magdata},
   {"adjust", cmd_adjust},
   {"schema", cmd_schema},
   {"time", cmd_time},
   {"free", cmd_mem},
   {"reboot", cmd_reboot},
   {NULL, NULL}
};

static const ShellConfig shCfg = {
   (BaseSequentialStream *)&SDU1,
   shCmds
};

static WORKING_AREA(waThreadButton, 128);
static msg_t ThreadButton(void *arg) {
   (void) arg;

   chRegSetThreadName("button");

   while (TRUE) {
      while (!palReadPad(GPIOA, GPIOA_BUTTON)) {
      }
      if (schema == MAXSCH) {
         schema = 0;
      } else {
         schema++;
      }
      chprintf((BaseSequentialStream *)&SDU1, "Schema set to %d\r\n", schema);
      chThdSleepMilliseconds(500);
   }
   return 0; // nevar forget
}

static WORKING_AREA(waThreadBlink, 128);

static msg_t ThreadBlink(void *arg) {
   (void) arg;

   chRegSetThreadName("blinker");
   uint8_t i = GPIOE_LED4_BLUE;

   while (TRUE) {
      if (schema == 0) { // all LEDs on, then off in the other direction
         if (i > GPIOE_LED6_GREEN + GPIOE_LED4_BLUE) {
            i = GPIOE_LED4_BLUE;
         }
         if (i <= GPIOE_LED6_GREEN) {
            palSetPad(GPIOE, i);
         }
         else {
            palClearPad(GPIOE, 31-i);
         }
         i++;
      }
      else if (schema == 1) { // All LEDs on, then off in same direction
         if (i > GPIOE_LED6_GREEN) {
            i = GPIOE_LED4_BLUE;
         }
         palTogglePad(GPIOE, i);
         i++;
      }
      else if (schema == 2) { // LED snake, length = 3
         if (i > GPIOE_LED6_GREEN) {
            i = GPIOE_LED4_BLUE;
         }
         if (i+3 <= GPIOE_LED6_GREEN) {
            palSetPad(GPIOE, i+3);
         }
         else {
            palSetPad(GPIOE, i-5);
         }
         palClearPad(GPIOE, i);
         i++;
      }
      else if (schema == 3) {
      }
      chThdSleepMilliseconds(125);
   }
   return 0; // never returns
}

static PWMConfig pwmcfg = {
   10000,
   10000,
   NULL,
   {
      {PWM_OUTPUT_ACTIVE_HIGH, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL}
   },
   0
};


int main(void) {
   Thread *sh = NULL;

   halInit();
   chSysInit();

   shellInit();
   
   usbDisconnectBus(serusbcfg.usbp);
   chThdSleepMilliseconds(1000);
   usbStart(serusbcfg.usbp, &usbcfg);
   usbConnectBus(serusbcfg.usbp);
   
   sduObjectInit(&SDU1);
   sduStart(&SDU1, &serusbcfg);

   spiStart(&SPID1, &spi1cfg);
   i2cStart(&I2CD1, &i2cconfig);
   initGyro();
   initAccel();
   initMag();
   
   pwmStart(&PWMD1, &pwmcfg);
   pwmEnableChannel(&PWMD1, 0, 5000);
   pwmEnableChannel(&PWMD1, 1, 5000);
   pwmEnableChannel(&PWMD1, 2, 5000);
   pwmEnableChannel(&PWMD1, 3, 5000);
   uint8_t i = 8;
   for (i = 8; i < 15; i++) {
      palSetPadMode(GPIOE, i, PAL_MODE_ALTERNATE(2));
   }
   //chThdCreateStatic(waThreadBlink, sizeof(waThreadBlink), NORMALPRIO, ThreadBlink, NULL);
   //chThdCreateStatic(waThreadButton, sizeof(waThreadButton), NORMALPRIO, ThreadButton, NULL);

    while (TRUE) {
      if (!sh) {
         sh = shellCreate(&shCfg, SHELL_WA_SIZE, NORMALPRIO);
      }
      else if (chThdTerminated(sh)) {
         chThdRelease(sh);
         sh = NULL;
      }
      chThdSleepMilliseconds(1000);
      /*
   float gyroData[3];
        float accelData[3];
        float magData[3];
        if (readGyro(gyroData) && readAccel(accelData) && readMag(magData)) {
            chprintf((BaseSequentialStream *)&SDU1, "%f %f %f ", gyroData[0], gyroData[1], gyroData[2]);
            chprintf((BaseSequentialStream *)&SDU1, "%f %f %f ", accelData[0], accelData[1], accelData[2]);
            chprintf((BaseSequentialStream *)&SDU1, "%f %f %f\n", magData[0], magData[1], magData[2]);
        }
      */
   }
   return 0; // never returns, lol
}

You might find the tail of the code (int main() and a little bit above) useful.
User avatar
Tectu
Posts: 1225
Joined: Thu May 10, 2012 9:50 am

Re: STM32F3Discovery, PWM and LED

Post by Tectu »

You need to understand the first and the second parameter of the config struct properly. The first one sets the timer frequency in 1/s and the second one sets the ticks per cycle. Therefore you'll get a PWM frequency of first_parameter / second_parameter.
Have a look at this working example to PWM some LED:

Code: Select all

#include "ch.h"
#include "hal.h"

static PWMConfig pwmcfg = {
   200000,
   1000,
   NULL,
   {
      {PWM_OUTPUT_ACTIVE_HIGH, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL},
      {PWM_OUTPUT_ACTIVE_HIGH, NULL}
   },
   0,
   0
};

int main(void) {
   halInit();
   chSysInit();

   palSetPadMode(GPIOE, 8, PAL_MODE_ALTERNATE(2));
   palSetPadMode(GPIOE, 9, PAL_MODE_ALTERNATE(2));

   pwmStart(&PWMD1, &pwmcfg);

   while (1) {
      pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 1000));   // 10% duty cycle
                chThdSleepMilliseconds(2000);
      pwmEnableChannel(&PWMD1, 0, PWM_PERCENTAGE_TO_WIDTH(&PWMD1, 9000));   // 90% duty cycle
                chThdSleepMilliseconds(2000);
   }

   return 0;
}

Also notice the use of the PWM_PERCENTAGE_TO_WIDTH() macro to get the correct duty cycle.

You have to take a good look to see the difference but it's definitely working that way - I also confirmed the change of the duty cycle using a scope.


~ Tectu
Post Reply