diff --git a/lib/sensor_sdm120.c b/lib/sensor_sdm120.c index 7073772..f3a421c 100644 --- a/lib/sensor_sdm120.c +++ b/lib/sensor_sdm120.c @@ -16,7 +16,7 @@ * @file sensor_sdm120.c * @author King Kévin * @date 2016 - * @note peripherals used: USART @ref sensor_sdm120_usart , GPIO @ref sensor_sdm120_gpio + * @note peripherals used: USART @ref sensor_sdm120_usart , GPIO @ref sensor_sdm120_gpio , timer @ref sensor_sdm120_timer */ /* standard libraries */ #include // standard integer types @@ -27,6 +27,7 @@ #include // real-time control clock library #include // general purpose input output library #include // universal synchronous asynchronous receiver transmitter library +#include // timer utilities #include // interrupt handler #include // Cortex M3 utilities @@ -47,6 +48,13 @@ #define SENSOR_SDM120_REDE_PIN 12 /**< GPIO pin for RS-485 receiver and driver output enable signal */ /** @} */ +/** @defgroup sensor_sdm120_timer timer peripheral to enforce waiting time between messages + * @note 60 ms are recommended between messages in SDM630 ModBus protocol implementation and this seem to also apply to SDM120 + * @{ + */ +#define SENSOR_SDM120_TIMER 3 /**< timer number to count time */ +/** @} */ + /* input and output ring buffer, indexes, and available memory */ static uint8_t rx_buffer[9] = {0}; /**< buffer for received response (ModBus response messages can be 2+256+2 long but we will only read up to 2 registers) */ static volatile uint8_t rx_used = 0; /**< number of received data bytes in buffer */ @@ -55,6 +63,16 @@ static volatile uint8_t tx_used = 0; /**< number of byte to transmit */ volatile bool sensor_sdm120_measurement_received = false; +/**< the ModBus timeouts to respect for sending messages **/ +static enum timeout_t { + TIMEOUT_BEGIN = 0, /**< silent time before sending data */ + TIMEOUT_END, /**< silent time after sending data */ + TIMEOUT_BETWEEN, /**< time to wait between messages */ + TIMEOUT_MAX +} timeout; +/**< current timeout used */ +static uint16_t timeout_times[TIMEOUT_MAX] = {0}; + /** SDM120 3xxxx input register start addresses for the measurement types */ static const uint16_t register_input[] = { 0x0000, // 30001 voltage (in volts) @@ -106,7 +124,7 @@ static uint16_t crc_modbus(uint8_t* buffer, uint8_t size) return crc; } -void sensor_sdm120_setup(void) +void sensor_sdm120_setup(uint32_t baudrate) { // enable USART I/O peripheral rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (USART) @@ -117,7 +135,7 @@ void sensor_sdm120_setup(void) gpio_clear(USART_PORT(SENSOR_SDM120_USART), USART_PIN_RX(SENSOR_SDM120_USART)); // pull down to avoid noise when not connected (it will be set low by RS485 chip when RO is enabled) // setup USART parameters for electricity meter - usart_set_baudrate(USART(SENSOR_SDM120_USART), 9600); // get baud rate by scrolling through the measurements on the electricity meter's screen (default 2400) + usart_set_baudrate(USART(SENSOR_SDM120_USART), baudrate); // get baud rate by scrolling through the measurements on the electricity meter's screen (default 2400) usart_set_databits(USART(SENSOR_SDM120_USART), 8); usart_set_stopbits(USART(SENSOR_SDM120_USART), USART_STOPBITS_1); usart_set_mode(USART(SENSOR_SDM120_USART), USART_MODE_TX_RX); @@ -133,6 +151,19 @@ void sensor_sdm120_setup(void) gpio_set_mode(GPIO(SENSOR_SDM120_REDE_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(SENSOR_SDM120_REDE_PIN)); // setup GPIO pin for receiver and driver output enable pin gpio_clear(GPIO(SENSOR_SDM120_REDE_PORT),GPIO(SENSOR_SDM120_REDE_PIN)); // disable driver output and enable receive output + // setup timer to wait for minimal time before sending transmitting + rcc_periph_clock_enable(RCC_TIM(SENSOR_SDM120_TIMER)); // enable clock for timer block + timer_reset(TIM(SENSOR_SDM120_TIMER)); // reset timer state + timer_set_mode(TIM(SENSOR_SDM120_TIMER), TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); // set timer mode, use undivided timer clock,edge alignment (simple count), and count up + timer_one_shot_mode(TIM(SENSOR_SDM120_TIMER)); // stop counter after update event (we only need to count down once) + timer_set_prescaler(TIM(SENSOR_SDM120_TIMER), 80-1); // set the prescaler so this 16 bits timer allows to wait for 60 ms ( 1/(72E6/66/(2**16))=60.07ms ) + timeout_times[TIMEOUT_BEGIN] = (rcc_ahb_frequency/(TIM_PSC(TIM(SENSOR_SDM120_TIMER))+1))/baudrate/8/2.5; // wait at least 2.5 characters before sending data + timeout_times[TIMEOUT_END] = (rcc_ahb_frequency/(TIM_PSC(TIM(SENSOR_SDM120_TIMER))+1))/baudrate/8/2.5; // wait at least 2.5 characters after sending data + timeout_times[TIMEOUT_BETWEEN] = 0.06*(rcc_ahb_frequency/(TIM_PSC(TIM(SENSOR_SDM120_TIMER))+1)); // wait at least 60 ms before sending the next message + timer_clear_flag(TIM(SENSOR_SDM120_TIMER), TIM_SR_UIF); // clear flag + timer_enable_irq(TIM(SENSOR_SDM120_TIMER), TIM_DIER_UIE); // enable update interrupt for timer + nvic_enable_irq(NVIC_TIM_IRQ(SENSOR_SDM120_TIMER)); // allow interrupt for timer just to wake up + // reset states tx_used = 0; rx_used = 0; @@ -148,9 +179,6 @@ void sensor_sdm120_setup(void) */ static bool sensor_sdm120_transmit_request(uint8_t meter_id, uint8_t function, uint16_t address, float value) { - if (tx_used!=0) { // transmission is ongoing - return false; - } if (meter_id==0) { // broadcast request are not supported return false; } @@ -160,6 +188,10 @@ static bool sensor_sdm120_transmit_request(uint8_t meter_id, uint8_t function, u if (address%2) { // even register addresses are not supported by device return false; } + while (tx_used) { // transmission is ongoing + __WFI(); // wait until something happens (transmission ended) + } + // build request packet uint8_t packet[11]; // buffer to build ModBus message (without error check) uint8_t packet_size = 0; // ModBus message size (without error check) packet[0] = meter_id; // set slave device address @@ -189,10 +221,16 @@ static bool sensor_sdm120_transmit_request(uint8_t meter_id, uint8_t function, u tx_used = packet_size+2; // set request size rx_used = 0; // reset reset buffer sensor_sdm120_measurement_received = false; // reset measurement flag + while (TIM_CR1(TIM(SENSOR_SDM120_TIMER))&TIM_CR1_CEN) { // timer is already used + __WFI(); // wait until something happens (timer is available again) + } gpio_set(GPIO(SENSOR_SDM120_REDE_PORT),GPIO(SENSOR_SDM120_REDE_PIN)); // enable driver output and disable receive output - USART_SR(USART(SENSOR_SDM120_USART)) &= USART_SR_TXE; // clear interrupt flag - usart_enable_tx_interrupt(USART(SENSOR_SDM120_USART)); // enable interrupt to send other bytes - usart_send(USART(SENSOR_SDM120_USART),tx_buffer[--tx_used]); // start transmission + // start timeout + timeout = TIMEOUT_BEGIN; // select time before sending message + timer_set_period(TIM(SENSOR_SDM120_TIMER), timeout_times[timeout]); // set corresponding timeout + timer_set_counter(TIM(SENSOR_SDM120_TIMER), 0); // reset timer counter to get preset waiting time + timer_enable_counter(TIM(SENSOR_SDM120_TIMER)); // wait + return true; } @@ -276,7 +314,10 @@ void USART_ISR(SENSOR_SDM120_USART)(void) if (usart_get_interrupt_source(USART(SENSOR_SDM120_USART), USART_SR_TC)) { // data has been completely transmitted USART_CR1(USART(SENSOR_SDM120_USART)) |= USART_CR1_TCIE; // disable transfer complete interrupt USART_SR(USART(SENSOR_SDM120_USART)) &= ~USART_SR_TC; // clear flag - gpio_clear(GPIO(SENSOR_SDM120_REDE_PORT),GPIO(SENSOR_SDM120_REDE_PIN)); // disable driver output and enable receive output + timeout = TIMEOUT_END; // select wait time after sending data + timer_set_period(TIM(SENSOR_SDM120_TIMER), timeout_times[timeout]); // set corresponding timeout + timer_set_counter(TIM(SENSOR_SDM120_TIMER), 0); // reset timer counter to get preset waiting time + timer_enable_counter(TIM(SENSOR_SDM120_TIMER)); // wait } if (usart_get_interrupt_source(USART(SENSOR_SDM120_USART), USART_SR_RXNE)) { // data has been received if (gpio_get(GPIO(SENSOR_SDM120_REDE_PORT),GPIO(SENSOR_SDM120_REDE_PIN))) { // not in receiver mode @@ -295,5 +336,35 @@ void USART_ISR(SENSOR_SDM120_USART)(void) } else { // buffer full and unknown response received USART_SR(USART(SENSOR_SDM120_USART)) &= ~USART_SR_RXNE; // clear flag (wait for user to read measurement, this clears the buffer) } + timeout = TIMEOUT_END; // select time after receiving data + timer_set_period(TIM(SENSOR_SDM120_TIMER), timeout_times[timeout]); // set corresponding timeout + timer_set_counter(TIM(SENSOR_SDM120_TIMER), 0); // reset timer counter to get preset waiting time + timer_enable_counter(TIM(SENSOR_SDM120_TIMER)); // wait } } + +void TIM_ISR(SENSOR_SDM120_TIMER)(void) +{ + if (timer_get_flag(TIM(SENSOR_SDM120_TIMER), TIM_SR_UIF)) { // update event happened + timer_clear_flag(TIM(SENSOR_SDM120_TIMER), TIM_SR_UIF); // clear flag + // because of the one pulse mode the timer is stopped automatically + switch (timeout) { // timeout before action passed + case (TIMEOUT_BEGIN): // we can now send the data + USART_SR(USART(SENSOR_SDM120_USART)) &= USART_SR_TXE; // clear interrupt flag + usart_enable_tx_interrupt(USART(SENSOR_SDM120_USART)); // enable interrupt to send other bytes + usart_send(USART(SENSOR_SDM120_USART),tx_buffer[--tx_used]); // start transmission + break; + case (TIMEOUT_END): // we now have to wait before sending the next message + gpio_clear(GPIO(SENSOR_SDM120_REDE_PORT),GPIO(SENSOR_SDM120_REDE_PIN)); // disable driver output (and enable receive output) + timeout = TIMEOUT_BETWEEN; // select time between sending message + timer_set_period(TIM(SENSOR_SDM120_TIMER), timeout_times[timeout]); // set corresponding timeout + timer_set_counter(TIM(SENSOR_SDM120_TIMER), 0); // reset timer counter to get preset waiting time + timer_enable_counter(TIM(SENSOR_SDM120_TIMER)); // wait + case (TIMEOUT_BETWEEN): // nothing to do, we are allowed to send the next message + break; + default: + break; + } + } +} + diff --git a/lib/sensor_sdm120.h b/lib/sensor_sdm120.h index b5e8ccf..abef696 100644 --- a/lib/sensor_sdm120.h +++ b/lib/sensor_sdm120.h @@ -16,7 +16,7 @@ * @file sensor_sdm120.h * @author King Kévin * @date 2016 - * @note peripherals used: USART @ref sensor_sdm120_usart , GPIO @ref sensor_sdm120_gpio + * @note peripherals used: USART @ref sensor_sdm120_usart , GPIO @ref sensor_sdm120_gpio , timer @ref sensor_sdm120_timer */ #pragma once @@ -54,8 +54,10 @@ enum sensor_sdm120_configuration_type_t { SENSOR_SDM120_CONFIGURATION_MAX }; -/** setup peripherals to communicate with electricity meter */ -void sensor_sdm120_setup(void); +/** setup peripherals to communicate with electricity meter + * @param[in] baudrate baud rate of RS485 serial communication + */ +void sensor_sdm120_setup(uint32_t baudrate); /** request measurement from electricity meter * @param[in] meter_id electricity meter device ID * @param[in] type measurement type to request