Skip to content

Commit a40e819

Browse files
author
Jamie Smith
authored
Fix PWM API for reading period and pulsewidth on LPC1768 (ARMmbed#274)
* Fix PWM API for reading microseconds on LPC1768 * Fix semihosting related compile errors, fix pwm deinit * Fix some mistakes, fix PWM frequency adjustment * Fix a few more issues
1 parent cd37bf4 commit a40e819

File tree

11 files changed

+96
-54
lines changed

11 files changed

+96
-54
lines changed

hal/tests/TESTS/mbed_hal/sleep/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
229229
#endif
230230
ticker_suspend(get_us_ticker_data());
231231

232-
#ifdef DEVICE_SEMIHOST
232+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
233233
// Disconnect semihosting now, because otherwise it will get disconnected on the first sleep call and
234234
// cause said call to take several milliseconds, leading to a test failure.
235235
mbed_interface_disconnect();

platform/include/platform/mbed_interface.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
extern "C" {
4848
#endif
4949

50-
#if DEVICE_SEMIHOST
50+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
5151

5252
/**
5353
* \defgroup platform_interface interface functions

platform/source/mbed_interface.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
#include "platform/mbed_semihost_api.h"
2121

22-
#if DEVICE_SEMIHOST
22+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
2323

2424
// return true if a debugger is attached, indicating mbed interface is connected
2525
int mbed_interface_connected(void)
@@ -90,7 +90,7 @@ WEAK int mbed_uid(char *uid)
9090

9191
WEAK void mbed_mac_address(char *mac)
9292
{
93-
#if DEVICE_SEMIHOST
93+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
9494
char uid[DEVICE_ID_LENGTH + 1];
9595
int i;
9696

@@ -115,7 +115,7 @@ WEAK void mbed_mac_address(char *mac)
115115
mac[3] = 0xF0;
116116
mac[4] = 0x00;
117117
mac[5] = 0x00;
118-
#if DEVICE_SEMIHOST
118+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
119119
}
120120
#endif
121121
}

platform/source/mbed_retarget.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1570,7 +1570,7 @@ extern "C" void exit(int return_code)
15701570
fsync(STDERR_FILENO);
15711571
#endif
15721572

1573-
#if DEVICE_SEMIHOST
1573+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
15741574
if (mbed_interface_connected()) {
15751575
semihost_exit();
15761576
}

rtos/tests/TESTS/mbed_rtos/MemoryPool/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
676676
{
677677
GREENTEA_SETUP(20, "default_auto");
678678

679-
#ifdef DEVICE_SEMIHOST
679+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
680680
// Disconnect semihosting now, because otherwise it will get disconnected on the first sleep call and
681681
// cause said call to take several milliseconds, leading to a test failure.
682682
mbed_interface_disconnect();

rtos/tests/TESTS/mbed_rtos/mail/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ utest::v1::status_t test_setup(const size_t number_of_cases)
479479
{
480480
GREENTEA_SETUP(10, "default_auto");
481481

482-
#ifdef DEVICE_SEMIHOST
482+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
483483
// Disconnect semihosting now, because otherwise it will get disconnected on the first sleep call and
484484
// cause said call to take several milliseconds, leading to a test failure.
485485
mbed_interface_disconnect();

rtos/tests/TESTS/mbed_rtos/queue/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ utest::v1::status_t test_setup(const size_t number_of_cases)
319319
{
320320
GREENTEA_SETUP(5, "default_auto");
321321

322-
#ifdef DEVICE_SEMIHOST
322+
#if MBED_CONF_TARGET_SEMIHOSTING_ENABLED
323323
// Disconnect semihosting now, because otherwise it will get disconnected on the first sleep call and
324324
// cause said call to take several milliseconds, leading to a test failure.
325325
mbed_interface_disconnect();

targets/TARGET_NXP/TARGET_LPC176X/TARGET_MBED_LPC1768/PinNames.h

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,6 @@ typedef enum {
7070
p29 = P0_5,
7171
p30 = P0_4,
7272

73-
// Other mbed Pin Names
74-
#ifdef MCB1700
75-
LED1 = P1_28,
76-
LED2 = P1_29,
77-
LED3 = P1_31,
78-
LED4 = P2_2,
79-
#else
80-
LED1 = P1_18,
81-
LED2 = P1_20,
82-
LED3 = P1_21,
83-
LED4 = P1_23,
84-
#endif
8573
CONSOLE_TX = P0_2,
8674
CONSOLE_RX = P0_3,
8775

@@ -111,18 +99,25 @@ typedef enum {
11199
A5 = P1_31,
112100

113101
// Not connected
114-
NC = (int)0xFFFFFFFF,
115-
116-
I2C_SCL0 = NC,
117-
I2C_SDA0 = NC,
118-
I2C_SCL1 = p10,
119-
I2C_SDA1 = p9,
120-
I2C_SCL2 = P0_11, // pin used by application board
121-
I2C_SDA2 = P0_10, // pin used by application board
122-
I2C_SCL = I2C_SCL2,
123-
I2C_SDA = I2C_SDA2,
102+
NC = (int)0xFFFFFFFF
124103
} PinName;
125104

105+
// Standard buttons and LEDs
106+
#define LED1 P1_18
107+
#define LED2 P1_20
108+
#define LED3 P1_21
109+
#define LED4 P1_23
110+
111+
// I2C pin names
112+
#define I2C_SCL0 NC
113+
#define I2C_SDA0 NC
114+
#define I2C_SCL1 p10
115+
#define I2C_SDA1 p9
116+
#define I2C_SCL2 P0_11 // pin used by application board
117+
#define I2C_SDA2 P0_10 // pin used by application board
118+
#define I2C_SCL I2C_SCL2
119+
#define I2C_SDA I2C_SDA2
120+
126121
typedef enum {
127122
PullUp = 0,
128123
PullDown = 3,

targets/TARGET_NXP/TARGET_LPC176X/pwmout_api.c

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
#include "cmsis.h"
2020
#include "pinmap.h"
2121

22+
#include <math.h>
23+
#include <stdlib.h>
24+
25+
// Change to 1 to enable debug prints of what's being calculated.
26+
// Must comment out the critical section calls in PwmOut to use.
27+
#define LPC1768_PWMOUT_DEBUG 0
28+
29+
#if LPC1768_PWMOUT_DEBUG
30+
#include <stdio.h>
31+
#include <inttypes.h>
32+
#endif
33+
2234
#define TCR_CNT_EN 0x00000001
2335
#define TCR_RESET 0x00000002
2436

@@ -53,7 +65,7 @@ __IO uint32_t *PWM_MATCH[] = {
5365

5466
#define TCR_PWM_EN 0x00000008
5567

56-
static unsigned int pwm_clock_mhz;
68+
static unsigned int pwm_clocks_per_us;
5769

5870
void pwmout_init(pwmout_t *obj, PinName pin)
5971
{
@@ -67,7 +79,8 @@ void pwmout_init(pwmout_t *obj, PinName pin)
6779
// ensure the power is on
6880
LPC_SC->PCONP |= 1 << 6;
6981

70-
// ensure clock to /4
82+
// Set PWM clock to CCLK / 4. This means that the PWM counter will increment every (4 / SystemCoreClock) seconds,
83+
// or 41.7ns (1us/24) with default clock settings.
7184
LPC_SC->PCLKSEL0 &= ~(0x3 << 12); // pclk = /4
7285
LPC_PWM1->PR = 0; // no pre-scale
7386

@@ -77,7 +90,8 @@ void pwmout_init(pwmout_t *obj, PinName pin)
7790
// enable the specific PWM output
7891
LPC_PWM1->PCR |= 1 << (8 + pwm);
7992

80-
pwm_clock_mhz = SystemCoreClock / 4000000;
93+
// Calculate microseconds -> clocks conversion, factoring in the prescaler of 4 we set earlier.
94+
pwm_clocks_per_us = SystemCoreClock / 4 / 1000000;
8195

8296
// default to 20ms: standard for servos, and fine for e.g. brightness control
8397
pwmout_period_ms(obj, 20);
@@ -89,7 +103,11 @@ void pwmout_init(pwmout_t *obj, PinName pin)
89103

90104
void pwmout_free(pwmout_t *obj)
91105
{
92-
// [TODO]
106+
// From testing, it seems like if you just disable the output by clearing the the bit in PCR, the output can get stuck
107+
// at either 0 or 1 depending on what level the PWM was at when it was disabled.
108+
// Instead, we just set the duty cycle of the output to 0 so that it's guaranteed to go low.
109+
*obj->MR = 0;
110+
LPC_PWM1->LER = 1 << obj->pwm;
93111
}
94112

95113
void pwmout_write(pwmout_t *obj, float value)
@@ -101,17 +119,24 @@ void pwmout_write(pwmout_t *obj, float value)
101119
}
102120

103121
// set channel match to percentage
104-
uint32_t v = (uint32_t)((float)(LPC_PWM1->MR0) * value);
122+
uint32_t v = (uint32_t)lroundf((float)(LPC_PWM1->MR0) * value);
105123

106-
// workaround for PWM1[1] - Never make it equal MR0, else we get 1 cycle dropout
124+
// workaround for the LPC1768 PWM1[1] errata - bad stuff happens for output 1 if the MR register equals the MR0 register
125+
// for the module (this could happen if the user tries to set exactly 100% duty cycle). To avoid this,
126+
// if the match register value would equal MR0, increment it again to make it greater than MR0. This doesn't
127+
// cause any side effects so long as we make sure MR0 is less than UINT32_MAX - 1.
107128
if (v == LPC_PWM1->MR0) {
108129
v++;
109130
}
110131

111132
*obj->MR = v;
112133

134+
#if LPC1768_PWMOUT_DEBUG
135+
printf("Calculated MR=%" PRIu32 " for duty cycle %.06f (MR0 = %" PRIu32 ")\n", v, value, LPC_PWM1->MR0);
136+
#endif
137+
113138
// accept on next period start
114-
LPC_PWM1->LER |= 1 << obj->pwm;
139+
LPC_PWM1->LER = 1 << obj->pwm;
115140
}
116141

117142
float pwmout_read(pwmout_t *obj)
@@ -133,30 +158,46 @@ void pwmout_period_ms(pwmout_t *obj, int ms)
133158
// Set the PWM period, keeping the duty cycle the same.
134159
void pwmout_period_us(pwmout_t *obj, int us)
135160
{
161+
// If the passed value is larger than this, it will overflow the MR0 register and bad stuff will happen
162+
const uint32_t max_period_us = (UINT32_MAX - 2) / pwm_clocks_per_us;
163+
136164
// calculate number of ticks
137-
uint32_t ticks = pwm_clock_mhz * us;
165+
if((uint32_t)us > max_period_us)
166+
{
167+
us = max_period_us;
168+
}
169+
if(us < 1)
170+
{
171+
us = 1;
172+
}
173+
uint32_t new_mr0_val = pwm_clocks_per_us * us;
138174

139175
// set reset
140176
LPC_PWM1->TCR = TCR_RESET;
141177

142-
// set the global match register
143-
LPC_PWM1->MR0 = ticks;
178+
// Scale the pulse width to preserve the duty ratio. Must use 64-bit math as the numerator can fairly easily overflow 32 bits.
179+
uint32_t new_mr_val = (*obj->MR * ((uint64_t)new_mr0_val)) / LPC_PWM1->MR0;
180+
#if LPC1768_PWMOUT_DEBUG
181+
printf("Changing MR0 from %" PRIu32 " to %" PRIu32 ", changing MR from %" PRIu32 " to %" PRIu32 " to preserve duty cycle\n", LPC_PWM1->MR0, new_mr0_val, *obj->MR, new_mr_val);
182+
#endif
183+
*obj->MR = new_mr_val;
144184

145-
// Scale the pulse width to preserve the duty ratio
146-
if (LPC_PWM1->MR0 > 0) {
147-
*obj->MR = (*obj->MR * ticks) / LPC_PWM1->MR0;
148-
}
185+
// set the global match register. Note that based on testing we do *not* need to subtract 1 here,
186+
// e.g. setting MR0 to 4 causes the PWM to reset every 4 clocks. This appears to be because the internal
187+
// counter starts at 1 from reset, not 0.
188+
LPC_PWM1->MR0 = new_mr0_val;
149189

150190
// set the channel latch to update value at next period start
151-
LPC_PWM1->LER |= 1 << 0;
191+
LPC_PWM1->LER = 1 << 0;
152192

153193
// enable counter and pwm, clear reset
154194
LPC_PWM1->TCR = TCR_CNT_EN | TCR_PWM_EN;
155195
}
156196

157197
int pwmout_read_period_us(pwmout_t *obj)
158198
{
159-
return (LPC_PWM1->MR0);
199+
// Add half a us worth of clocks for correct integer rounding.
200+
return (LPC_PWM1->MR0 + pwm_clocks_per_us/2) / pwm_clocks_per_us;
160201
}
161202

162203
void pwmout_pulsewidth(pwmout_t *obj, float seconds)
@@ -172,9 +213,9 @@ void pwmout_pulsewidth_ms(pwmout_t *obj, int ms)
172213
void pwmout_pulsewidth_us(pwmout_t *obj, int us)
173214
{
174215
// calculate number of ticks
175-
uint32_t v = pwm_clock_mhz * us;
216+
uint32_t v = pwm_clocks_per_us * us;
176217

177-
// workaround for PWM1[1] - Never make it equal MR0, else we get 1 cycle dropout
218+
// workaround for PWM1[1] - Never make it equal MR0, else we get 1 cycle dropout. See pwmout_write() for more details.
178219
if (v == LPC_PWM1->MR0) {
179220
v++;
180221
}
@@ -183,12 +224,18 @@ void pwmout_pulsewidth_us(pwmout_t *obj, int us)
183224
*obj->MR = v;
184225

185226
// set the channel latch to update value at next period start
186-
LPC_PWM1->LER |= 1 << obj->pwm;
227+
LPC_PWM1->LER = 1 << obj->pwm;
187228
}
188229

189230
int pwmout_read_pulsewidth_us(pwmout_t *obj)
190231
{
191-
return (*obj->MR);
232+
uint32_t mr_ticks = *(obj->MR);
233+
if(mr_ticks > LPC_PWM1->MR0)
234+
{
235+
mr_ticks = LPC_PWM1->MR0;
236+
}
237+
// Add half a us worth of clocks for correct integer rounding.
238+
return (mr_ticks + pwm_clocks_per_us/2) / pwm_clocks_per_us;
192239
}
193240

194241
const PinMap *pwmout_pinmap()

targets/TARGET_NXP/TARGET_LPC176X/sleep.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
void hal_sleep(void) {
2929

30-
#if (DEVICE_SEMIHOST == 1)
30+
#if (MBED_CONF_TARGET_SEMIHOSTING_ENABLED == 1)
3131
// ensure debug is disconnected
3232
mbed_interface_disconnect();
3333
#endif
@@ -70,7 +70,7 @@ void hal_sleep(void) {
7070

7171
void hal_deepsleep(void) {
7272

73-
#if (DEVICE_SEMIHOST == 1)
73+
#if (MBED_CONF_TARGET_SEMIHOSTING_ENABLED == 1)
7474
// ensure debug is disconnected
7575
mbed_interface_disconnect();
7676
#endif

tools/cmake/mbed_greentea.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ function(mbed_greentea_add_test)
115115
# the serial port. Doing that type of reset also seems to give the Pitaya-Link probe trouble.
116116
# However, for targets which support semihosting (currently just LPC1768), we do need the reset as otherwise
117117
# semihosting stuff like localfilesystem won't work.
118-
if(NOT "DEVICE_SEMIHOST=1" IN_LIST MBED_TARGET_DEFINITIONS)
118+
if(NOT "MBED_CONF_TARGET_SEMIHOSTING_ENABLED=1" IN_LIST MBED_CONFIG_DEFINITIONS)
119119
list(APPEND MBED_HTRUN_ARGUMENTS --skip-reset)
120120
endif()
121121

0 commit comments

Comments
 (0)