diff --git a/Makefile b/Makefile index 39bdfc04..4d6e8ffa 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ mongoose.c: Makefile $(wildcard src/*) $(wildcard mip/*.c) (cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c mip/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ mongoose.h: $(HDRS) Makefile - (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/config.h src/str.h src/fmt.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h mip/mip.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ + (cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/config.h src/str.h src/fmt.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/json.h src/rpc.h mip/mip.h mip/driver_*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ clean: rm -rf $(PROG) *.exe *.o *.dSYM *_test* ut fuzzer *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb slow-unit* _CL_* infer-out data.txt crash-* test/packed_fs.c pack unpacked diff --git a/examples/stm32/nucleo-f429zi-baremetal/main.c b/examples/stm32/nucleo-f429zi-baremetal/main.c index 1518519a..ad244c21 100644 --- a/examples/stm32/nucleo-f429zi-baremetal/main.c +++ b/examples/stm32/nucleo-f429zi-baremetal/main.c @@ -70,8 +70,9 @@ int main(void) { // Initialise Mongoose network stack // Specify MAC address, and use 0 for IP, mask, GW - i.e. use DHCP // For static configuration, specify IP/mask/GW in network byte order - struct mip_cfg c = {.mac = {0, 0, 1, 2, 3, 4}, .ip = 0, .mask = 0, .gw = 0}; - mip_init(&mgr, &c, &mip_driver_stm32, NULL); + struct mip_cfg c = {.mac = {0, 0, 1, 2, 3, 5}, .ip = 0, .mask = 0, .gw = 0}; + struct mip_driver_stm32 driver_data = {.mdc_cr = 4}; // See driver_stm32.h + mip_init(&mgr, &c, &mip_driver_stm32, &driver_data); MG_INFO(("Init done, starting main loop")); extern void device_dashboard_fn(struct mg_connection *, int, void *, void *); diff --git a/examples/stm32/nucleo-f746zg-baremetal/main.c b/examples/stm32/nucleo-f746zg-baremetal/main.c index b9227ec2..24035d85 100644 --- a/examples/stm32/nucleo-f746zg-baremetal/main.c +++ b/examples/stm32/nucleo-f746zg-baremetal/main.c @@ -71,7 +71,8 @@ int main(void) { // Specify MAC address, and use 0 for IP, mask, GW - i.e. use DHCP // For static configuration, specify IP/mask/GW in network byte order struct mip_cfg c = {.mac = {0, 0, 1, 2, 3, 4}, .ip = 0, .mask = 0, .gw = 0}; - mip_init(&mgr, &c, &mip_driver_stm32, NULL); + struct mip_driver_stm32 driver_data = {.mdc_cr = 4}; // See driver_stm32.h + mip_init(&mgr, &c, &mip_driver_stm32, &driver_data); MG_INFO(("Init done, starting main loop")); extern void device_dashboard_fn(struct mg_connection *, int, void *, void *); diff --git a/mip/driver_stm32.c b/mip/driver_stm32.c index 0f633986..f4835c00 100644 --- a/mip/driver_stm32.c +++ b/mip/driver_stm32.c @@ -1,17 +1,6 @@ #include "mip.h" #if MG_ENABLE_MIP && defined(__arm__) - -// define to your own clock if using external clocking -#if !defined(MG_STM32_CLK_HSE) -#define MG_STM32_CLK_HSE 8000000UL -#endif - -// define to your chip internal clock if different -#if !defined(MG_STM32_CLK_HSI) -#define MG_STM32_CLK_HSI 16000000UL -#endif - struct stm32_eth { volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR, MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR, @@ -44,9 +33,6 @@ static inline void spin(volatile uint32_t count) { while (count--) asm("nop"); } -static uint32_t hclk_get(void); -static uint8_t cr_guess(uint32_t hclk); - static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) { ETH->MACMIIAR &= (7 << 2); ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); @@ -63,7 +49,62 @@ static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { while (ETH->MACMIIAR & BIT(0)) spin(1); } +static uint32_t get_hclk(void) { + struct rcc { + volatile uint32_t CR, PLLCFGR, CFGR; + } *RCC = (struct rcc *) 0x40023800; + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */; + + if (RCC->CFGR & (1 << 2)) { + clk = hse; + } else if (RCC->CFGR & (1 << 3)) { + uint32_t vco, m, n, p; + m = (RCC->PLLCFGR & (0x3f << 0)) >> 0; + n = (RCC->PLLCFGR & (0x1ff << 6)) >> 6; + p = (((RCC->PLLCFGR & (3 << 16)) >> 16) + 1) * 2; + clk = (RCC->PLLCFGR & (1 << 22)) ? hse : hsi; + vco = (uint32_t) ((uint64_t) clk * n / m); + clk = vco / p; + } else { + clk = hsi; + } + int hpre = (RCC->CFGR & (0x0F << 4)) >> 4; + if (hpre < 8) return clk; + + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) + return ((uint32_t) clk) >> ahbptab[hpre - 8]; +} + +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, +// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived +// from the HSI (internal RC), and it can go above specs, the datasheets +// specify a range of frequencies and activate one of a series of dividers to +// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on +// HCLK with a +5% drift. If the user uses a different clock from our +// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) +static int guess_mdc_cr(void) { + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers + uint32_t hclk = get_hclk(); // Guess system HCLK + int result = -1; // Invalid CR value + if (hclk < 25000000) { + MG_ERROR(("HCLK too low")); + } else { + for (int i = 0; i < 6; i++) { + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { + result = crs[i]; + break; + } + } + if (result < 0) MG_ERROR(("HCLK too high")); + } + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); + return result; +} + static bool mip_driver_stm32_init(uint8_t *mac, void *userdata) { + struct mip_driver_stm32 *d = (struct mip_driver_stm32 *) userdata; // Init RX descriptors for (int i = 0; i < ETH_DESC_CNT; i++) { s_rxdesc[i][0] = BIT(31); // Own @@ -82,11 +123,15 @@ static bool mip_driver_stm32_init(uint8_t *mac, void *userdata) { ETH->DMABMR |= BIT(0); // Software reset while ((ETH->DMABMR & BIT(0)) != 0) spin(1); // Wait until done + + // Set MDC clock divider. If user told us the value, use it. Otherwise, guess + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; + ETH->MACMIIAR = (cr & 3) << 2; + // NOTE(cpq): we do not use extended descriptor bit 7, and do not use // hardware checksum. Therefore, descriptor size is 4, not 8 // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25); ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT - ETH->MACMIIAR = cr_guess(hclk_get()) << 2; // MDC clock ETH->MACFCR = BIT(7); // Disable zero quarta pause ETH->MACFFR = BIT(31); // Receive all eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY @@ -101,7 +146,6 @@ static bool mip_driver_stm32_init(uint8_t *mac, void *userdata) { ETH->MACA0HR = ((uint32_t) mac[5] << 8U) | mac[4]; ETH->MACA0LR = (uint32_t) (mac[3] << 24) | ((uint32_t) mac[2] << 16) | ((uint32_t) mac[1] << 8) | mac[0]; - (void) userdata; return true; } @@ -161,63 +205,4 @@ struct mip_driver mip_driver_stm32 = {.init = mip_driver_stm32_init, .tx = mip_driver_stm32_tx, .setrx = mip_driver_stm32_setrx, .up = mip_driver_stm32_up}; - -// Calculate HCLK from clock settings, -// valid for STM32F74xxx/75xxx (5.3) and STM32F42xxx/43xxx (6.3) -static const uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) -struct rcc { - volatile uint32_t CR, PLLCFGR, CFGR; -}; -#define RCC ((struct rcc *) 0x40023800) - -static uint32_t hclk_get(void) { - uint32_t clk = 0; - if (RCC->CFGR & (1 << 2)) { - clk = MG_STM32_CLK_HSE; - } else if (RCC->CFGR & (1 << 3)) { - uint32_t vco, m, n, p; - m = (RCC->PLLCFGR & (0x3FUL << 0)) >> 0; - n = (RCC->PLLCFGR & (0x1FFUL << 6)) >> 6; - p = (((RCC->PLLCFGR & (0x03UL << 16)) >> 16) + 1) * 2; - if (RCC->PLLCFGR & (1UL << 22)) - clk = MG_STM32_CLK_HSE; - else - clk = MG_STM32_CLK_HSI; - vco = (uint32_t) ((uint64_t) (((uint32_t) clk * (uint32_t) n)) / - ((uint32_t) m)); - clk = vco / p; - } else { - clk = MG_STM32_CLK_HSI; - } - int hpre = (RCC->CFGR & (0x0F << 4)) >> 4; - if (hpre < 8) return clk; - return ((uint32_t) clk) >> ahbptab[hpre - 8]; -} - -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, -// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived -// from the HSI (internal RC), and it can go above specs, the datasheets -// specify a range of frequencies and activate one of a series of dividers to -// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on -// HCLK with a +5% drift. If the user uses a different clock from our -// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) -#define CRDTAB_LEN 6 -static const uint8_t crdtab[CRDTAB_LEN][2] = { - // [{setting, div ratio},...] - {2, 16}, {3, 26}, {0, 42}, {1, 62}, {4, 102}, {5, 124}, -}; - -static uint8_t cr_guess(uint32_t hclk) { - MG_DEBUG(("HCLK: %u", hclk)); - if (hclk < 25000000) { - MG_ERROR(("HCLK too low")); - return CRDTAB_LEN; - } - for (int i = 0; i < CRDTAB_LEN; i++) - if (hclk / crdtab[i][1] <= 2375000UL) return crdtab[i][0]; // 2.5MHz - 5% - MG_ERROR(("HCLK too high")); - return CRDTAB_LEN; -} - -#endif // MG_ENABLE_MIP +#endif diff --git a/mip/driver_stm32.h b/mip/driver_stm32.h new file mode 100644 index 00000000..859fb68b --- /dev/null +++ b/mip/driver_stm32.h @@ -0,0 +1,16 @@ +#pragma once + +struct mip_driver_stm32 { + // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz + // HCLK range DIVIDER mdc_cr VALUE + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz HCLK/42 0 + // 100-150 MHz HCLK/62 1 + // 20-35 MHz HCLK/16 2 + // 35-60 MHz HCLK/26 3 + // 150-216 MHz HCLK/102 4 <-- value for Nucleo-F* on max speed + // 216-310 MHz HCLK/124 5 + // 110, 111 Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 +}; diff --git a/mongoose.c b/mongoose.c index 5239da57..e13bcf7c 100644 --- a/mongoose.c +++ b/mongoose.c @@ -5983,17 +5983,6 @@ struct mip_driver mip_driver_enc28j60 = {.init = mip_driver_enc28j60_init, #if MG_ENABLE_MIP && defined(__arm__) - -// define to your own clock if using external clocking -#if !defined(MG_STM32_CLK_HSE) -#define MG_STM32_CLK_HSE 8000000UL -#endif - -// define to your chip internal clock if different -#if !defined(MG_STM32_CLK_HSI) -#define MG_STM32_CLK_HSI 16000000UL -#endif - struct stm32_eth { volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR, MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR, @@ -6026,9 +6015,6 @@ static inline void spin(volatile uint32_t count) { while (count--) asm("nop"); } -static uint32_t hclk_get(void); -static uint8_t cr_guess(uint32_t hclk); - static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) { ETH->MACMIIAR &= (7 << 2); ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6); @@ -6045,7 +6031,62 @@ static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) { while (ETH->MACMIIAR & BIT(0)) spin(1); } +static uint32_t get_hclk(void) { + struct rcc { + volatile uint32_t CR, PLLCFGR, CFGR; + } *RCC = (struct rcc *) 0x40023800; + uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */; + + if (RCC->CFGR & (1 << 2)) { + clk = hse; + } else if (RCC->CFGR & (1 << 3)) { + uint32_t vco, m, n, p; + m = (RCC->PLLCFGR & (0x3f << 0)) >> 0; + n = (RCC->PLLCFGR & (0x1ff << 6)) >> 6; + p = (((RCC->PLLCFGR & (3 << 16)) >> 16) + 1) * 2; + clk = (RCC->PLLCFGR & (1 << 22)) ? hse : hsi; + vco = (uint32_t) ((uint64_t) clk * n / m); + clk = vco / p; + } else { + clk = hsi; + } + int hpre = (RCC->CFGR & (0x0F << 4)) >> 4; + if (hpre < 8) return clk; + + uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) + return ((uint32_t) clk) >> ahbptab[hpre - 8]; +} + +// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, +// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived +// from the HSI (internal RC), and it can go above specs, the datasheets +// specify a range of frequencies and activate one of a series of dividers to +// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on +// HCLK with a +5% drift. If the user uses a different clock from our +// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx +// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) +static int guess_mdc_cr(void) { + uint8_t crs[] = {2, 3, 0, 1, 4, 5}; // ETH->MACMIIAR::CR values + uint8_t div[] = {16, 26, 42, 62, 102, 124}; // Respective HCLK dividers + uint32_t hclk = get_hclk(); // Guess system HCLK + int result = -1; // Invalid CR value + if (hclk < 25000000) { + MG_ERROR(("HCLK too low")); + } else { + for (int i = 0; i < 6; i++) { + if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) { + result = crs[i]; + break; + } + } + if (result < 0) MG_ERROR(("HCLK too high")); + } + MG_DEBUG(("HCLK: %u, CR: %d", hclk, result)); + return result; +} + static bool mip_driver_stm32_init(uint8_t *mac, void *userdata) { + struct mip_driver_stm32 *d = (struct mip_driver_stm32 *) userdata; // Init RX descriptors for (int i = 0; i < ETH_DESC_CNT; i++) { s_rxdesc[i][0] = BIT(31); // Own @@ -6064,11 +6105,15 @@ static bool mip_driver_stm32_init(uint8_t *mac, void *userdata) { ETH->DMABMR |= BIT(0); // Software reset while ((ETH->DMABMR & BIT(0)) != 0) spin(1); // Wait until done + + // Set MDC clock divider. If user told us the value, use it. Otherwise, guess + int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr; + ETH->MACMIIAR = (cr & 3) << 2; + // NOTE(cpq): we do not use extended descriptor bit 7, and do not use // hardware checksum. Therefore, descriptor size is 4, not 8 // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25); ETH->MACIMR = BIT(3) | BIT(9); // Mask timestamp & PMT IT - ETH->MACMIIAR = cr_guess(hclk_get()) << 2; // MDC clock ETH->MACFCR = BIT(7); // Disable zero quarta pause ETH->MACFFR = BIT(31); // Receive all eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15)); // Reset PHY @@ -6083,7 +6128,6 @@ static bool mip_driver_stm32_init(uint8_t *mac, void *userdata) { ETH->MACA0HR = ((uint32_t) mac[5] << 8U) | mac[4]; ETH->MACA0LR = (uint32_t) (mac[3] << 24) | ((uint32_t) mac[2] << 16) | ((uint32_t) mac[1] << 8) | mac[0]; - (void) userdata; return true; } @@ -6143,66 +6187,7 @@ struct mip_driver mip_driver_stm32 = {.init = mip_driver_stm32_init, .tx = mip_driver_stm32_tx, .setrx = mip_driver_stm32_setrx, .up = mip_driver_stm32_up}; - -// Calculate HCLK from clock settings, -// valid for STM32F74xxx/75xxx (5.3) and STM32F42xxx/43xxx (6.3) -static const uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9}; // log2(div) -struct rcc { - volatile uint32_t CR, PLLCFGR, CFGR; -}; -#define RCC ((struct rcc *) 0x40023800) - -static uint32_t hclk_get(void) { - uint32_t clk = 0; - if (RCC->CFGR & (1 << 2)) { - clk = MG_STM32_CLK_HSE; - } else if (RCC->CFGR & (1 << 3)) { - uint32_t vco, m, n, p; - m = (RCC->PLLCFGR & (0x3FUL << 0)) >> 0; - n = (RCC->PLLCFGR & (0x1FFUL << 6)) >> 6; - p = (((RCC->PLLCFGR & (0x03UL << 16)) >> 16) + 1) * 2; - if (RCC->PLLCFGR & (1UL << 22)) - clk = MG_STM32_CLK_HSE; - else - clk = MG_STM32_CLK_HSI; - vco = (uint32_t) ((uint64_t) (((uint32_t) clk * (uint32_t) n)) / - ((uint32_t) m)); - clk = vco / p; - } else { - clk = MG_STM32_CLK_HSI; - } - int hpre = (RCC->CFGR & (0x0F << 4)) >> 4; - if (hpre < 8) return clk; - return ((uint32_t) clk) >> ahbptab[hpre - 8]; -} - -// Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3, -// it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived -// from the HSI (internal RC), and it can go above specs, the datasheets -// specify a range of frequencies and activate one of a series of dividers to -// keep the MDC clock safely below 2.5MHz. We guess a divider setting based on -// HCLK with a +5% drift. If the user uses a different clock from our -// defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx -// (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift) -#define CRDTAB_LEN 6 -static const uint8_t crdtab[CRDTAB_LEN][2] = { - // [{setting, div ratio},...] - {2, 16}, {3, 26}, {0, 42}, {1, 62}, {4, 102}, {5, 124}, -}; - -static uint8_t cr_guess(uint32_t hclk) { - MG_DEBUG(("HCLK: %u", hclk)); - if (hclk < 25000000) { - MG_ERROR(("HCLK too low")); - return CRDTAB_LEN; - } - for (int i = 0; i < CRDTAB_LEN; i++) - if (hclk / crdtab[i][1] <= 2375000UL) return crdtab[i][0]; // 2.5MHz - 5% - MG_ERROR(("HCLK too high")); - return CRDTAB_LEN; -} - -#endif // MG_ENABLE_MIP +#endif #ifdef MG_ENABLE_LINES #line 1 "mip/driver_w5500.c" diff --git a/mongoose.h b/mongoose.h index 8e08c751..4004d24e 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1472,6 +1472,22 @@ void qp_init(void); #define qp_mark(a, b) #endif + +struct mip_driver_stm32 { + // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz + // HCLK range DIVIDER mdc_cr VALUE + // ------------------------------------- + // -1 <-- tell driver to guess the value + // 60-100 MHz HCLK/42 0 + // 100-150 MHz HCLK/62 1 + // 20-35 MHz HCLK/16 2 + // 35-60 MHz HCLK/26 3 + // 150-216 MHz HCLK/102 4 <-- value for Nucleo-F* on max speed + // 216-310 MHz HCLK/124 5 + // 110, 111 Reserved + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 +}; + #ifdef __cplusplus } #endif