implement timer for reliable message transmission

This commit is contained in:
King Kévin 2016-10-14 10:46:45 +02:00
parent cf44707281
commit 89f7df59d4
2 changed files with 86 additions and 13 deletions

View File

@ -16,7 +16,7 @@
* @file sensor_sdm120.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @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 <stdint.h> // standard integer types
@ -27,6 +27,7 @@
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
#include <libopencm3/stm32/timer.h> // timer utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencmsis/core_cm3.h> // 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;
}
}
}

View File

@ -16,7 +16,7 @@
* @file sensor_sdm120.h
* @author King Kévin <kingkevin@cuvoodoo.info>
* @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