From f90689c02149e4f11a16cc980598bb60541fc1c3 Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Thu, 10 Sep 2015 08:57:44 +0200 Subject: [PATCH] Add support for the NCN5120 KNX bus driver from NXP. To be tested. Part of manually merging the KNX branch --- extdrv/ncn5120.c | 746 ++++++++++++++++++++++++++++++++++++ include/extdrv/ncn5120.h | 408 ++++++++++++++++++++ include/lib/protocols/knx.h | 2 + 3 files changed, 1156 insertions(+) create mode 100644 extdrv/ncn5120.c create mode 100644 include/extdrv/ncn5120.h create mode 100644 include/lib/protocols/knx.h diff --git a/extdrv/ncn5120.c b/extdrv/ncn5120.c new file mode 100644 index 0000000..e3aec83 --- /dev/null +++ b/extdrv/ncn5120.c @@ -0,0 +1,746 @@ +/**************************************************************************** + * extdrv/ncn5120.c + * + * Copyright 2014 Nathael Pajani + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + *************************************************************************** */ + +#include +#include "core/lpc_regs_12xx.h" +#include "core/lpc_core_cm0.h" +#include "core/system.h" +#include "core/systick.h" +#include "core/pio.h" +#include "lib/string.h" +#include "lib/time.h" +#include "drivers/serial.h" +#include "drivers/gpio.h" +#include "extdrv/ncn5120.h" +#include "lib/protocols/knx.h" + +/* Debug */ +#include "lib/stdio.h" + +static struct ncn5120_device ncn5120 = { + .serial_num = 1, + .current_state = NCN5120_STATE_NORMAL, + .save_callback = NULL, + .reset_callback = NULL, + .rx_callback = NULL, + .address_set = 0, + .rx_data_running = 0, +}; + + + +/***************************************************************************** */ +/* Interrupt handlers */ + +/* Handle RESETB pin state change */ +static void reset_event(uint32_t gpio) +{ + if (ncn5120.reset_callback!= NULL) { + ncn5120.reset_callback(gpio); + } +} +/* Handle SAVEB pin state change */ +static void save_event(uint32_t gpio) +{ + if (ncn5120.save_callback != NULL) { + ncn5120.save_callback(gpio); + } +} + + +/* Handle data and replies received on NCN5120 UART */ +static void ncn5120_handle_rx(uint8_t c) +{ + if (ncn5120.next_data_is_cmd_reply > 0) { + /* Receive command reply */ + ncn5120.rx_cmd_buff[ncn5120.rx_cmd_buff_idx++] = c; + ncn5120.next_data_is_cmd_reply--; + } else { + /* Receive KNX data */ + static struct time_spec last_byte; + static uint32_t last_timer_tick; + + /* Timer based end of frame detection */ + if (ncn5120.rx_data_running == 0) { + ncn5120.rx_data_running = 1; + get_time_in_interrupt(&last_byte); + last_timer_tick = systick_get_timer_val(); + } else { + struct time_spec this_byte, diff; + uint32_t this_tick = systick_get_timer_val(); + get_time_in_interrupt(&this_byte); + /* Check end of Receiving condition. Consider that the time difference will never be above a few msec. */ + get_time_diff(&last_byte, &this_byte, &diff); + if (diff.msec >= 1) { + uint32_t reload = systick_get_timer_reload_val(); + uint32_t tick_diff = (this_tick + (reload * (diff.msec + 1)) - last_timer_tick); + if (((tick_diff * 10) / reload) > 26) { + /* More than 2.6ms elapsed since previous byte : end of frame */ + ncn5120.rx_data_running = 0; + } + } + last_byte.seconds = this_byte.seconds; + last_byte.msec = this_byte.msec; + last_timer_tick = this_tick; + } + if (ncn5120.rx_callback != NULL) { + if (ncn5120.rx_data_running == 1) { + ncn5120.rx_callback(c); + } else { + ncn5120.rx_callback(c | END_OF_DATA_PACKET); + } + } + } +} + + + +/***************************************************************************** */ +/* Get command replies */ + +static void ncn5120_drop_cmd_data(void) +{ + lpc_disable_irq(); + ncn5120.rx_cmd_buff_idx = 0; + dsb(); + lpc_enable_irq(); +} +/* Tell the UART Rx handler that we will receive some command reply bytes */ +static void ncn5120_set_nb_cmd_req_data(uint16_t len) +{ + ncn5120.next_data_is_cmd_reply += len; +} +/* Get the requested reply bytes */ +static void ncn5120_get_req_reply_data(uint8_t* buf, uint16_t len) +{ + /* Wait for data to be received (happens in ncn5120_handle_rx() interrupt routine) */ + do {} while (ncn5120.rx_cmd_buff_idx < len); + /* Lock */ + lpc_disable_irq(); + memcpy(buf, (void*)ncn5120.rx_cmd_buff, len); + ncn5120.rx_cmd_buff_idx -= len; + /* Most of the time all data is read, but ... */ + if (ncn5120.rx_cmd_buff_idx != 0) { + memcpy((void*)ncn5120.rx_cmd_buff, (void*)(ncn5120.rx_cmd_buff + len), ncn5120.rx_cmd_buff_idx); + } + dsb(); + lpc_enable_irq(); +} + +/***************************************************************************** */ +/* Send requests - Services access */ +static void ncn5120_send_service_request(uint8_t* buf, uint16_t len, uint16_t reply_len) +{ + /* Register how many byte this request should generate as reply */ + ncn5120_set_nb_cmd_req_data(reply_len); + /* And send the request */ + serial_write(ncn5120.serial_num, (char*)buf, len); +} + +/***************************************************************************** */ +/* Internal registers access */ +/* Set NCN5120 internal register by isuing U_IntRegWr.req command */ +static void ncn5120_set_internal_register(uint8_t reg_addr, uint8_t value) +{ + uint8_t buf[2]; + if (reg_addr > 0x03) { + return; + } + buf[0] = U_IntRegWr_Request | reg_addr; + buf[1] = value; + ncn5120_send_service_request(buf, 2, 0); +} +/* Get NCN5120 internal register by isuing U_IntRegRd.req command */ +static uint8_t ncn5120_get_internal_register(uint8_t reg_addr) +{ + uint8_t reg_val = 0; + + /* Register address cannot be above 0x03 */ + reg_addr &= 0x03; + + /* Drop all previously received command data */ + ncn5120_drop_cmd_data(); + + /* Send request */ + reg_addr |= U_IntRegRd_Request; + ncn5120_send_service_request(®_addr, 1, 1); + /* And wait for our data to come */ + ncn5120_get_req_reply_data(®_val, 1); + return reg_val; +} + + + +/***************************************************************************** */ +/* Set NCN5120 configuration */ +static void update_thermal_state(uint8_t thermal_warning) +{ + if (thermal_warning) { + ncn5120.thermal_state = NCN5120_STATE_THERMAL_WARNING; + } else { + ncn5120.thermal_state = NCN5120_STATE_NORMAL; + } +} + +/* Decode config reply */ +static void ncn5120_parse_conf_reply(struct ncn5120_conf* conf, uint8_t reply) +{ + conf->busy = (reply & NCN5120_BUSY); + if (conf->busy) { + ncn5120.current_op_mode = NCN5120_STATE_BUSY; + } + conf->auto_acknowledge = (reply & NCN5120_AUTO_ACK); + conf->auto_polling = (reply & NCN5120_AUTO_POLLING); + conf->crc_citt = (reply & NCN5120_CRC_CITT_ACTIVE); + conf->frame_end_marker = (reply & NCN5120_FRAME_END_MARK_ACTIVE); +} +/* Communication configuration + * Activate communication functionnality when corresponding parameter is not nul. + * It is only possible to activate services and functions. De-activation requires NCN5120 reset. + * Note : + * This driver does not support crc-citt and frame end indicators. + * These parameters will be ignored. + */ +static int ncn5120_set_comm_config(uint8_t auto_poll, uint8_t crc_citt, uint8_t use_frame_end_indicator) +{ + uint8_t request = U_Configure_Request; + uint8_t reply = 0; + /* Do not change config if NCN5120 is currently receiving data */ + if (ncn5120.rx_data_running) { + return -1; + } + /* Send the new config */ + if (auto_poll) { + request |= NCN5120_ACTIVATE_AUTO_POLL; + } + ncn5120_send_service_request(&request, 1, 1); + /* And read the configure byte in reply */ + ncn5120_get_req_reply_data(&reply, 1); + ncn5120_parse_conf_reply(&(ncn5120.comm_config), reply); + return 0; +} + + +/* Set analog configuration + * Activate analog functionnality when corresponding parameter is not nul. + */ +static void ncn5120_set_analog_config(uint8_t sleep_en, uint8_t v20v_en, uint8_t dc2_en, uint8_t xclock) +{ + uint8_t reg_val = 0; + if (sleep_en) { + reg_val |= NCN5120_SLEEP_ENABLE; + } + if (v20v_en) { + reg_val |= NCN5120_V20V_ENABLE; + } + if (dc2_en) { + reg_val |= NCN5120_DC2_ENABLE; + } + if (xclock) { + reg_val |= ((xclock & NCN5120_XCLOCK_MASK) << NCN5120_XCLOCK_SHIFT); + } + ncn5120_set_internal_register(NCN5120_ACR_0_ADDR, reg_val); +} + + + +/***************************************************************************** */ +/* NCN5120 Configuration and initialisation + * rx_callback must not be NULL if you want to receive data. It will be called for + * any received character which is not part of a command reply. + * If you did not set the device's address using ncn5120_set_address() then you are + * responsible of sending the ACK, NACK or BUSY using ncn5120_packet_acknowledge(). + * The UART will be configured in 38400, 8n1 and an intermediate callback will be + * used, so do not try to configure it yourself. + * Analog configuration will be setup according to Techno-Innov's KNX module's + * requirements and communication configuration according to this module's + * requirements. + */ +void ncn5120_init(uint8_t serial, void (*rx_callback)(uint32_t)) +{ + /* Serial configuration */ + ncn5120.serial_num = serial; + ncn5120.rx_callback = rx_callback; + uart_on(ncn5120.serial_num, 38400, ncn5120_handle_rx); + + /* Initialize time system. Can be called anytime, will just return if it has already been called. */ + time_init(); + + /* Initial driver configuration : sleep_en, v20v_en, dc2_en, external clock */ + ncn5120_set_analog_config(0, 0, 0, 0); + /* Communication config : auto_poll, crc_citt, use_frame_end_indicator + * Note that crc_citt and use_frame_end_indicator modes are not supported. */ + ncn5120_set_comm_config(0, 0, 0); +} + +/* Se callback on SAVEB and RESETB signal for emergency data saving. + * Set a callback on SAVEB signal (goes low on KNX system errors) + * Possible errors : Temp warning (TW), Thermal shutdown warning (TSD) + * Read the system status to get the warning source. refer to System status + * service description (page 33 in ncn5120 documentation) + * Set a callback on RESETB signal (goes low when watchdog is not reset) + */ +void ncn5120_register_handlers(const struct pio* save, void (*save_callback)(uint32_t), + const struct pio* reset, void (*reset_callback)(uint32_t)) +{ + /* Save pin */ + config_pio(reset, (LPC_IO_MODE_PULL_UP | LPC_IO_DIGITAL)); + pio_copy(&ncn5120.save_pin, save); + gpio_dir_in(ncn5120.save_pin); + set_gpio_callback(save_event, &ncn5120.save_pin, EDGES_BOTH); + ncn5120.save_callback = save_callback; + + /* Reset pin */ + config_pio(reset, (LPC_IO_MODE_PULL_UP | LPC_IO_DIGITAL)); + pio_copy(&ncn5120.reset_pin, reset); + gpio_dir_in(ncn5120.reset_pin); + set_gpio_callback(reset_event, &ncn5120.reset_pin, EDGES_BOTH); + ncn5120.reset_callback = reset_callback; +} + + +/***************************************************************************** */ +/* Watchdog configuration */ +/* Enable and configure NCN5120 watchdog. + * By default the watchdog is not active upon NCN5120 reset. + * delay is a value in mili-seconds, with minimal value of 33ms and 33ms steps. If delay + * is 0 then the watchdog is turned OFF. + * Refer to watchdog register description (page 50 in ncn5120 documentation). + * Do not forget to call this function again before then of the watchdog timer. You can + * register a callback on RESETB signal (goes low when watchdog is not reset). + */ +void ncn5120_watchdog_config(uint32_t delay) +{ + uint8_t reg_val = 0; + if (delay == 0) { + reg_val = NCN5120_WDT_DISABLE; + } else { + if (delay > NCN5120_WDT_DELAY_MAX_VALUE) { + delay = NCN5120_WDT_DELAY_MAX_VALUE; + } + /* Set WDEN and configure Watchdog timing WDT[3:0] (tWDTO) */ + reg_val = NCN5120_WDT_ENABLE; + reg_val |= ((delay >> NCN5120_WDT_DELAY_CONV_SHIFT) & NCN5120_WDT_DELAY_MASK); + } + ncn5120_set_internal_register(NCN5120_WDT_REG_ADDR, reg_val); +} + + +/***************************************************************************** */ +/* Change the NCN5120 physical address and activate the auto-acknowledge function + * NCN5120 starts accepting all frames whose destination address corresponds to the stored + * physical address or whose destination address is the group address by sending IACK on + * the bus. In case of an error detected during such frame reception, NCN5120 sends NACK + * instead of IACK. + * The documentation says that the Set Address Service can be issued any time and that the + * new physical address and the autoacknowledge function will only get active after the + * KNX bus becomes idle. Anyway, it does not provide information on when the configuration + * byte will be sent, thus we do not allow the set address operation while receiving data. + * + * Returns -1 when receiving data and address did not get changed. + * + * Autoacknowledge can only be deactivated by a Reset Service (call to ncn5120_reset()). + */ +int ncn5120_set_address(uint16_t address) +{ + uint8_t buf[4] = { U_SetAddress_Request, 0, 0, 0 }; + uint8_t reply = 0; + /* Do not change address if NCN5120 is currently receiving data */ + if (ncn5120.rx_data_running) { + return - 1; + } + buf[1] = ((address >> 8) & 0xFF); + buf[2] = (address & 0xFF); + /* Send the new address (three data bytes, the last one is ignored) */ + ncn5120_send_service_request(buf, 4, 1); + /* And read the configure byte in reply */ + ncn5120_get_req_reply_data(&reply, 1); + ncn5120_parse_conf_reply(&(ncn5120.comm_config), reply); + ncn5120.address_set = 1; + return 0; +} + + +/* Set repetition service + * Specifies the maximum repetition count for transmitted frames when not acknowledged with IACK. + * Separate counters can be set for NACK and BUSY frames. + * Initial value of both counters is 3. + * If the acknowledge from remote Data Link Layer is BUSY during frame transmission, NCN5120 + * tries to repeat after at least 150 bit times KNX bus idle. + * The BUSY counter determines the maximum amount of times the frame is repeated. + * If the BUSY acknowledge is still received after the last try, an L_Data.con with a negative + * conformation is sent back to the host controller. + * For all other cases (NACK acknowledgment received, invalid/corrupted acknowledge received or + * time−out after 30 bit times) NCN5120 will repeat after 50 bit times of KNX bus idle. + * The NACK counter determines the maximum retries. + * L_Data.con with a negative confirmation is send back to the host controller when the maximum + * retries were reached. In worst case, the same request is transmitted (NACK + BUSY + 1) times + * before NCN5120 stops retransmission. + */ +void ncn5120_set_number_of_retries(uint8_t busy_retries, uint8_t nack_retries) +{ + uint8_t buf[4] = { U_SetRepetition_Request, 0, 0, 0 }; + buf[1] = (((busy_retries & 0x07) << 4) | (nack_retries & 0x07)); + /* Send the new retry count */ + ncn5120_send_service_request(buf, 4, 0); +} + + + +/***************************************************************************** */ +/* Get analog status + * The operation_mode returned will be the latest known operation mode. It will not be updated + * during this call. + */ +int ncn5120_get_analog_status(struct ncn5120_status* status) +{ + uint8_t reg_val = 0; + /* Erase buffer, so that returned status is consistent */ + memset(status, 0, sizeof(struct ncn5120_status)); + /* Do not request status if NCN5120 is currently receiving data */ + if (ncn5120.rx_data_running) { + return -1; + } + /* Get internal status byte */ + reg_val = ncn5120_get_internal_register(NCN5120_STATUS_REG_ADDR); + /* Update struct according to received byte */ + status->out_of_sleep = (reg_val & NCN5120_STATUS_OUT_OF_SLEEP); + status->v20v_ok = (reg_val & NCN5120_STATUS_V20V_OK); + status->vdd2_ok = (reg_val & NCN5120_STATUS_VDD2_OK); + status->vbus_ok = (reg_val & NCN5120_STATUS_VBUS_OK); + status->vfilt_ok = (reg_val & NCN5120_STATUS_VFILT_OK); + status->xtal_running = (reg_val & NCN5120_STATUS_XTAL_OK); + status->thermal_warning = (reg_val & NCN5120_STATUS_THERMAL_WARN); + update_thermal_state(status->thermal_warning); + status->out_of_thermal_shutdown = (reg_val & NCN5120_STATUS_OUT_OF_TSD); + status->operation_mode = ncn5120.current_op_mode; + return 0; +} + + +/* Get commuinication status as returned by U_State.req service */ +int ncn5120_get_comm_status(struct ncn5120_comm_status* status) +{ + uint8_t request = U_State_Request; + uint8_t reply = 0; + /* Send communication status request */ + ncn5120_send_service_request(&request, 1, 1); + /* And wait for our data to come */ + ncn5120_get_req_reply_data(&reply, 1); + if ((reply & U_State_Reply) != U_State_Reply) { + return -1; + } + status->slave_colision = (reply & NCN5120_COMM_STATUS_SLAVE_COLISION); + status->host_rx_error = (reply & NCN5120_COMM_STATUS_HOST_RX_ERROR); + status->knx_tx_error = (reply & NCN5120_COMM_STATUS_KNX_TX_ERROR); + status->protocol_error = (reply & NCN5120_COMM_STATUS_PROTOCOL_ERROR); + status->thermal_warning = (reply & NCN5120_COMM_STATUS_THERMAL_WARNING); + update_thermal_state(status->thermal_warning); + return 0; +} + +/* Get system status. + * The 'out_of_sleep' and 'out_of_thermal_shutdown' field have no meaning for this request + * and will be '0' regardless of the real state. + */ +int ncn5120_get_system_status(struct ncn5120_status* status) +{ + uint8_t request = U_SystemStat_Request; + uint8_t reply[2], reg_val = 0; + /* Erase buffer, so that returned status is consistent */ + memset(status, 0, sizeof(struct ncn5120_status)); + /* Do not request system status if NCN5120 is currently receiving data */ + if (ncn5120.rx_data_running) { + return -1; + } + /* Send system status request */ + ncn5120_send_service_request(&request, 1, 2); + /* And wait for our data to come */ + ncn5120_get_req_reply_data(reply, 2); + if (reply[0] != U_SystemStat_Reply) { + return -2; + } + /* Decode operation mode */ + switch (reply[1] & NCN5120_OP_MODE_MASK) { + case NCN5120_OP_MODE_POWER_UP: + status->operation_mode = NCN5120_STATE_POWERUP; + break; + case NCN5120_OP_MODE_SYNC: + status->operation_mode = NCN5120_STATE_SYNC; + break; + case NCN5120_OP_MODE_STOP: + status->operation_mode = NCN5120_STATE_STOP; + break; + case NCN5120_OP_MODE_NORMAL: + status->operation_mode = NCN5120_STATE_NORMAL; + break; + } + ncn5120.current_op_mode = status->operation_mode; + /* The bit field is left shifted by compared to internal register status bitfield ... shift back */ + reg_val = (reply[1] >> 1); + status->v20v_ok = (reg_val & NCN5120_STATUS_V20V_OK); + status->vdd2_ok = (reg_val & NCN5120_STATUS_VDD2_OK); + status->vbus_ok = (reg_val & NCN5120_STATUS_VBUS_OK); + status->vfilt_ok = (reg_val & NCN5120_STATUS_VFILT_OK); + status->xtal_running = (reg_val & NCN5120_STATUS_XTAL_OK); + status->thermal_warning = (reg_val & NCN5120_STATUS_THERMAL_WARN); + update_thermal_state(status->thermal_warning); + return 0; +} + + +/***************************************************************************** */ +/* Request NCN5120 reset + * Return 0 on success, -1 on error + */ +int ncn5120_reset(void) +{ + uint8_t request = U_Reset_Request; + uint8_t reply = 0; + + /* Empty buffer before device request. Any data in it is not relevant anymore + * Drop all previously received command data */ + ncn5120_drop_cmd_data(); + + ncn5120_send_service_request(&request, 1, 1); + /* Update current state and clear rx_data_running flag */ + ncn5120.current_state = NCN5120_STATE_RESET; + ncn5120.rx_data_running = 0; + /* And wait for our data to come, which will mean that the device reset is done. */ + ncn5120_get_req_reply_data(&reply, 1); + if (reply != U_Reset_Reply) { + return -1; + } + ncn5120.current_state = NCN5120_STATE_NORMAL; + return 0; +} + +/* Enter sleep mode if it has been enabled by config in Analog Control register 0 (call to ncn5120_set_config() + * with sleep_en = 1). + * Warning: when entering sleep mode, only a Wake-Up Event on the FANIN/WAKE-pin can get the + * NCN5120 out of sleep. Make sure your hardware alows the generation of this event. + */ +void ncn5120_enter_sleep(void) +{ + ncn5120_set_internal_register(NCN5120_ACR_1_ADDR, NCN5120_ENTER_SLEEP); + ncn5120.current_state = NCN5120_STATE_SLEEP; +} + +/* Set (enter or quit) busy mode. + * Enter busy mode/state if 'state' is not 0. + * In Busy mode and when autoacknowledge is active the NCN5120 rejects the frames whose + * destination address corresponds to the stored physical address by sending the BUSY + * acknowledge. + * This service has no effect if autoacknowledge is not active. + * Refer to p30 of NCN5120 documentation for busy state an p31 for autoacknowledge and set + * address service + */ +void ncn5120_set_busy_state(uint8_t state) +{ + uint8_t request = 0; + + if (state != 0) { + request = U_SetBusy_Request; + ncn5120.current_state = NCN5120_STATE_BUSY; + } else { + request = U_QuitBusy_Request; + ncn5120.current_state = NCN5120_STATE_NORMAL; + } + ncn5120_send_service_request(&request, 1, 0); +} + +/* Set (enter or quit) stop mode. + * Enter stop mode/state if 'state' is not 0. + */ +void ncn5120_set_stop_state(uint8_t state) +{ + uint8_t request = 0; + uint8_t reply = 0; + + if (state != 0) { + request = U_StopMode_Request; + } else { + request = U_ExitStopMode_Request; + } + ncn5120_send_service_request(&request, 1, 1); + /* And wait for confirmation. */ + ncn5120_get_req_reply_data(&reply, 1); + if (state != 0) { + if (reply == U_StopMode_Reply) { + ncn5120.current_state = NCN5120_STATE_STOP; + } + } else { + if (reply == U_Reset_Reply) { + ncn5120.current_state = NCN5120_STATE_NORMAL; + } + } + /* Seems that there cannot be other cases, only infinite loops waiting for an idle KNX Bus */ +} + + +/* Enter Bus monitor mode + * In this mode all data received from the KNX bus is sent to the host controller without + * performing any filtering on Data Link Layer. Acknowledge Frames are also transmitted + * transparently. + * This state can only be exited by the Reset Service (ncn5120_reset()). + */ +void ncn5120_enter_monitor_mode(void) +{ + uint8_t request = U_Busmon_Request; + ncn5120_send_service_request(&request, 1, 0); + ncn5120.current_state = NCN5120_STATE_MONITOR; +} + + + +/***************************************************************************** */ +/* Receive packet helper + * When an address got set using ncn5120_set_address() then the auto-ACK function is activated. + * When it is not the case, the host is responsible for sending ACK, NACK or Busy on the Bus when + * it receives a packet + */ +void ncn5120_packet_acknowledge(uint8_t nack, uint8_t busy, uint8_t addressed) +{ + uint8_t request = U_Ackn_Request; + + if (ncn5120.address_set == 1) { + return; + } + + if (nack) { + request |= NCN5120_ACK_NACK; + } + if (busy) { + request |= NCN5120_ACK_BUSY; + } + if (addressed) { + request |= NCN5120_ACK_ADDRESSED; + } + ncn5120_send_service_request(&request, 1, 0); +} + + +/***************************************************************************** */ +/* Send packet + * When using a packet oriented communication with packet size and address included + * in the packet, these must be included in the packet by the software before + * calling this function. + * The request_builder buffer is used to build parts of the request by sending the control + * and data bytes in the right order. + * There is no need to send them all at once so a small buffer is OK and saves memory. A + * three bytes buffer is OK for one data byte and the possible two index bytes sent at + * once. + * The reply length depends on the communication configuration. It is at least the data + * size plus two control bytes. + * + * Return values: + * This function returns a negativ value on errors: + * -1 when size is above KNX_MAX_FRAME_SIZE + * -2 when data has not been sent because the bus is busy receiving data. + * -3 when there has been an error while transmitting data to the NCN5120 (or checksum error) + * -4 if the NCN5120 indicates Rx errors (while we were sending, what would they mean ?) + * -5 if the data does not end with L_Data confirmation byte. + * The function returns 1 when the packet got sent and received a positive ACK, or 0 when it + * received a negative ACK. + */ +#define REQUEST_BUILDER_SIZE 3 +int ncn5120_send_packet(uint8_t control_byte, uint8_t* buffer, uint16_t size) +{ + uint8_t request_builder[REQUEST_BUILDER_SIZE]; + uint16_t data_idx = 0; + uint8_t reply[2]; /* In our config there are two bytes after the end of frame silence. */ + uint8_t checksum = 0; + + if (size > KNX_MAX_FRAME_SIZE) { + return -1; + } + if (ncn5120.rx_data_running) { + return -2; + } + + /* Build the request */ + /* initial control part */ + request_builder[0] = U_L_DataStart_Request; + request_builder[1] = control_byte; + ncn5120_send_service_request(request_builder, 2, 0); + + /* Data part */ + while (data_idx < size) { + if (!(data_idx & 0x3F)) { + request_builder[0] = U_L_DataOffset_Request | ((data_idx >> 6) & 0x07); + request_builder[1] = U_L_DataCont_Request | (data_idx & 0x3F); + request_builder[2] = buffer[data_idx]; + ncn5120_send_service_request(request_builder, 3, 0); + } else { + request_builder[0] = U_L_DataCont_Request | (data_idx & 0x3F); + request_builder[1] = buffer[data_idx]; + ncn5120_send_service_request(request_builder, 2, 0); + } + /* Compute checksum */ + /* FIXME : check KNX documentation */ + checksum += buffer[data_idx++]; + } + + /* End marker and checksum */ + request_builder[0] = U_L_DataEnd_Request | (size & 0x3F); /* Low six bits of last byte index */ + request_builder[1] = checksum; + /* Send end marker and data checksum and set the right reply length. + * We always have these four : one for L_Data indicator (start), one for checksum, one for + * U_FrameState indicator (we are in 8bits UART mode), and one for L_Data confirmation */ + ncn5120_send_service_request(request_builder, 2, (size + 4)); + + + /* And wait for the echo and data end indicator and status information */ + ncn5120_get_req_reply_data(reply, 1); + /* Is it L_Data indicator (OK) or U_State indicator (error) */ + if ((reply[0] & U_State_Reply) == U_State_Reply) { + /* Error case */ + ncn5120_drop_cmd_data(); + /* FIXME : handle errors */ + return -3; + } + + /* Read all echo data to get rid of it */ + /* Note : we should no use ncn5120. structure here, but this trick will have the memcpy return + * immediatly as destination and source are the same. Add one for the checksum, no need to + * check again.*/ + ncn5120_get_req_reply_data((uint8_t*)ncn5120.rx_cmd_buff, (size + 1)); + + /* Read the remaining bytes (-2 for L_Data indicator and checksum) */ + ncn5120_get_req_reply_data(reply, 2); + /* And decode */ + /* First byte is the state. We are sender, there are no receive error ... (should not be) */ + if (reply[0] != U_FrameState_Reply) { + uprintf(0, "Tx but state indicates Rx errors .... (0x%02x)\n", reply[0]); + return -4; + } + /* And the next one is the interesting one : Positive or negative ACK */ + if ((reply[1] & 0x7F) != L_Data) { + uprintf(0, "Data Tx feedback does not end with L_Data confirmation byte ! (0x%02x)\n", reply[1]); + return -5; + } + /* Did we get a positive or negative ACK ? */ + if (reply[1] & 0x80) { + return 1; /* Positive ACK */ + } + return 0; /* Negative ACK */ +} + + diff --git a/include/extdrv/ncn5120.h b/include/extdrv/ncn5120.h new file mode 100644 index 0000000..cf08e7e --- /dev/null +++ b/include/extdrv/ncn5120.h @@ -0,0 +1,408 @@ +/**************************************************************************** + * extdrv/ncn5120.h + * + * Copyright 2014 Nathael Pajani + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + *************************************************************************** */ + + +#ifndef EXTDRV_NCN5120_H +#define EXTDRV_NCN5120_H + +#include + +struct ncn5120_status { + uint8_t operation_mode; + uint8_t out_of_sleep; + uint8_t v20v_ok; + uint8_t vdd2_ok; + uint8_t vbus_ok; + uint8_t vfilt_ok; + uint8_t xtal_running; + uint8_t thermal_warning; + uint8_t out_of_thermal_shutdown; +}; + +struct ncn5120_comm_status { + uint8_t slave_colision; + uint8_t host_rx_error; + uint8_t knx_tx_error; + uint8_t protocol_error; + uint8_t thermal_warning; +}; + +struct ncn5120_conf { + uint8_t busy; + uint8_t auto_acknowledge; + uint8_t auto_polling; + uint8_t crc_citt; + uint8_t frame_end_marker; +}; + +enum ncn5120_states { + NCN5120_STATE_NORMAL = 1, + NCN5120_STATE_STANDBY, + NCN5120_STATE_STARTUP, + NCN5120_STATE_POWERUP, + NCN5120_STATE_POWERUP_STOP, + NCN5120_STATE_SYNC, + NCN5120_STATE_STOP, + NCN5120_STATE_BUSY, + NCN5120_STATE_THERMAL_WARNING, + NCN5120_STATE_RESET, + NCN5120_STATE_MONITOR, + NCN5120_STATE_SLEEP, + NCN5120_STATE_THERMAL_SHUTDOWN, +}; + +/***************************************************************************** */ +/* NCN5120 */ +/***************************************************************************** */ +struct ncn5120_device { + uint8_t serial_num; + /* Last known state for these : */ + uint8_t current_state; + uint8_t current_op_mode; + uint8_t thermal_state; + /* Pin used for reset and save signals, and associated calbacks */ + struct pio reset_pin; /* RESET pin, used to know the state of the KNX chip */ + struct pio save_pin; /* SAVE pin, signal from KNX chip used as warning for power loss. */ + void (*save_callback)(uint32_t); + void (*reset_callback)(uint32_t); + + /* Config */ + struct ncn5120_conf comm_config; + int address_set; + + /* RX */ + /* Commands */ + volatile uint8_t rx_cmd_buff[280]; + volatile uint16_t rx_cmd_buff_idx; + volatile uint16_t next_data_is_cmd_reply; + /* Data */ + volatile uint8_t rx_data_running; + void (*rx_callback)(uint32_t); +}; + +/* This value cannot be received on the KNX bus. When passed to the rx_callback it indicates that + * we received the end of a data packet. + */ +#define END_OF_DATA_PACKET (0x100) + +/***************************************************************************** */ +/* List of services (Commands and data sent from host to NCN5120) */ +enum ncn5120_services { + /* Internal commands, device specific */ + U_Reset_Request = 0x01, /* U_Reset.req - No data */ + U_State_Request = 0x02, /* U_State.req - No data */ + U_SetBusy_Request = 0x03, /* U_SetBusy.req - No data */ + U_QuitBusy_Request = 0x04, /* U_QuitBusy.req - No data */ + U_Busmon_Request = 0x05, /* U_Busmon.req - No data */ + U_SetAddress_Request = 0xF1, /* U_SetAddress.req - 3 data bytes : AddrHigh, AddrLow, DontCare */ + U_SetRepetition_Request = 0xF2, /* U_SetRepetition.req - 3 data bytes : Rep_Count, DontCare, DontCare */ + U_L_DataOffset_Request = 0x08, /* U_L_DataOffset.req - No data - Ored with MSB byte index in three lower bits */ + U_SystemStat_Request = 0x0D, /* U_SystemStat.req - No data */ + U_StopMode_Request = 0x0E, /* U_StopMode.req - No data */ + U_ExitStopMode_Request = 0x0F, /* U_ExitStopMode.req - No data */ + U_Ackn_Request = 0x10, /* U_Ackn.req - No data - Ored with 'nack' (0x04), 'busy' (0x02) and 'addressed' (0x01) */ + U_Configure_Request = 0x18, /* U_Configure.req - No data - Ored with 'auto-polling' (0x04), 'CRC-CITT' (0x02) and 'marker' (0x01) */ + U_IntRegWr_Request = 0x28, /* U_IntRegWr.req - 1 data byte : data to write - Ored with two bits of internal reg addr */ + U_IntRegRd_Request = 0x38, /* U_IntRegRd.req - No data - Ored with two bits of internal reg addr */ + U_PollingState_Request = 0xE0, /* U_PollingState.req - 3 data bytes : PollAddrHigh, PollAddrLow, PollState - Ored with 4 bits of slot number */ + /* KNX transmit data commands */ + U_L_DataStart_Request = 0x80, /* U_L_DataStart.req - 1 data byte : Control byte */ + U_L_DataCont_Request = 0x80, /* U_L_DataCont.req - Ored with 6 bits of index - 1 data byte : data */ + U_L_DataEnd_Request = 0x40, /* U_L_DataEnd.req - Ored with 6 bits of index - 1 data byte : check byte */ +}; +/* List of replies (Replies to commands) */ +enum ncn5120_replies { + /* DLL (layer 2) services : device is transparent */ + L_Data_Standard = 0x90, /* FIXME : 3 indicator bits possible */ + L_Data_Extended = 0x10, /* FIXME : 3 indicator bits possible */ + L_Poll_Data = 0xF0, + /* Acknowledge services (device is transparent in Bus monitor mode) */ + L_Ackn = 0x00, /* FIXME : 4 possible bit set */ + L_Data = 0x0B, /* FIXME : 0x8B is positive confirmation, 0x0B is negative confirmation */ + /* Control services, device specific */ + U_Reset_Reply = 0x03, + U_State_Reply = 0x07, /* FIXME : upper 5 bits are state indicators */ + U_FrameState_Reply = 0x13, /* FIXME : 4 indicator bits possible */ + U_Configure_Reply = 0x01, /* FIXME : 5 indicator bits possible */ + U_Configure_Reply_Mask = 0x83, /* These are the constant bits */ + U_FrameEnd_Reply = 0xCB, + U_StopMode_Reply = 0x2B, + U_SystemStat_Reply = 0x4B, /* 1 data byte with 6 flags and 2 bits for mode */ +}; + + + +/***************************************************************************** */ +/* Services bits definitions */ + + +/* Communication status bits */ +#define NCN5120_COMM_STATUS_SLAVE_COLISION (0x01 << 7) /* Collision during transmission of polling state */ +#define NCN5120_COMM_STATUS_HOST_RX_ERROR (0x01 << 6) /* Corrupted bytes sent by host controller (us) */ +#define NCN5120_COMM_STATUS_KNX_TX_ERROR (0x01 << 5) /* Transceiver error detected during frame transmission */ +#define NCN5120_COMM_STATUS_PROTOCOL_ERROR (0x01 << 4) /* incorrect sequence of commands sent by host */ +#define NCN5120_COMM_STATUS_THERMAL_WARNING (0x01 << 3) /* Thermal warning condition detected */ + +/* Busy state */ +#define NCN5120_ENTER_BUSY_STATE 1 +#define NCN5120_QUIT_BUSY_STATE 0 + +/* Configure service status bits */ +#define NCN5120_BUSY (0x01 << 6) +#define NCN5120_AUTO_ACK (0x01 << 5) +#define NCN5120_AUTO_POLLING (0x01 << 4) +#define NCN5120_CRC_CITT_ACTIVE (0x01 << 3) +#define NCN5120_FRAME_END_MARK_ACTIVE (0x01 << 2) +/* Configure function request bits */ +#define NCN5120_ACTIVATE_END_FRAME_MARKER (0x01 << 0) +#define NCN5120_ACTIVATE_CRC_CITT (0x01 << 1) +#define NCN5120_ACTIVATE_AUTO_POLL (0x01 << 2) + +/* System State service */ +#define NCN5120_OP_MODE_MASK (0x03) +#define NCN5120_OP_MODE_POWER_UP 0x00 +#define NCN5120_OP_MODE_SYNC 0x01 +#define NCN5120_OP_MODE_STOP 0x02 +#define NCN5120_OP_MODE_NORMAL 0x03 + +/* ACK request bits */ +#define NCN5120_ACK_NACK (0x01 << 2) +#define NCN5120_ACK_BUSY (0x01 << 1) +#define NCN5120_ACK_ADDRESSED (0x01 << 0) + + +/***************************************************************************** */ +/* Internal registers */ +#define NCN5120_INT_REG_ADDR_SHIFT +#define NCN5120_INT_REG_ADDR_MASK + +/* Watchdog register */ +#define NCN5120_WDT_REG_ADDR 0x00 +#define NCN5120_WDT_WDEN_SHIFT 7 +#define NCN5120_WDT_ENABLE (0x01 << NCN5120_WDT_WDEN_SHIFT) +#define NCN5120_WDT_DISABLE 0x00 +#define NCN5120_WDT_DELAY_MASK 0x0F +#define NCN5120_WDT_DELAY_CONV_SHIFT 6 +#define NCN5120_WDT_DELAY_MAX_VALUE 524 + +/* Analog Control register 0 */ +#define NCN5120_ACR_0_ADDR 0x01 +/* Xclock */ +#define NCN5120_XCLOCK_SHIFT 3 +#define NCN5120_XCLOCK_MASK 0x03 +#define NCN5120_XCLOCK_DISABLE 0x00 +#define NCN5120_XCLOCK_8MHZ 0x02 +#define NCN5120_XCLOCK_16MHZ 0x03 +/* Sleep */ +#define NCN5120_SLEEP_ENABLE 0x80 +#define NCN5120_SLEEP_DISABLE 0x00 +/* V20V regulator */ +#define NCN5120_V20V_ENABLE 0x40 +/* DC2 converter */ +#define NCN5120_DC2_ENABLE 0x20 + +/* Analog Control register 1 */ +#define NCN5120_ACR_1_ADDR 0x02 +#define NCN5120_ENTER_SLEEP 0x80 + +/* Analog Status register */ +#define NCN5120_STATUS_REG_ADDR 0x03 +#define NCN5120_STATUS_OUT_OF_SLEEP (0x01 << 7) +#define NCN5120_STATUS_V20V_OK (0x01 << 6) +#define NCN5120_STATUS_VDD2_OK (0x01 << 5) +#define NCN5120_STATUS_VBUS_OK (0x01 << 4) +#define NCN5120_STATUS_VFILT_OK (0x01 << 3) +#define NCN5120_STATUS_XTAL_OK (0x01 << 2) +#define NCN5120_STATUS_THERMAL_WARN (0x01 << 1) +#define NCN5120_STATUS_OUT_OF_TSD (0x01 << 0) + + + +/***************************************************************************** */ +/* NCN5120 Configuration and initialisation + * rx_callback must not be NULL if you want to receive data. It will be called for + * any received character which is not part of a command reply. + * If you did not set the device's address using ncn5120_set_address() then you are + * responsible of sending the ACK, NACK or BUSY using ncn5120_packet_acknowledge(). + * The UART will be configured in 38400, 8n1 and an intermediate callback will be + * used, so do not try to configure it yourself. + * Analog configuration will be setup according to Techno-Innov's KNX module's + * requirements and communication configuration according to this module's + * requirements. + */ +void ncn5120_init(uint8_t serial, void (*rx_callback)(uint32_t)); + +/* Se callback on SAVEB and RESETB signal for emergency data saving. + * Set a callback on SAVEB signal (goes low on KNX system errors) + * Possible errors : Temp warning (TW), Thermal shutdown warning (TSD) + * Read the system status to get the warning source. refer to System status + * service description (page 33 in ncn5120 documentation) + * Set a callback on RESETB signal (goes low when watchdog is not reset) + */ +void ncn5120_register_handlers(const struct pio* save, void (*save_callback)(uint32_t), + const struct pio* reset, void (*reset_callback)(uint32_t)); + + +/***************************************************************************** */ +/* Watchdog configuration */ +/* Enable and configure NCN5120 watchdog. + * By default the watchdog is not active upon NCN5120 reset. + * delay is a value in mili-seconds, with minimal value of 33ms and 33ms steps. If delay + * is 0 then the watchdog is turned OFF. + * Refer to watchdog register description (page 50 in ncn5120 documentation). + * Do not forget to call this function again before then of the watchdog timer. You can + * register a callback on RESETB signal (goes low when watchdog is not reset). + */ +void ncn5120_watchdog_config(uint32_t delay); + + +/***************************************************************************** */ +/* Change the NCN5120 physical address and activate the auto-acknowledge function + * NCN5120 starts accepting all frames whose destination address corresponds to the stored + * physical address or whose destination address is the group address by sending IACK on + * the bus. In case of an error detected during such frame reception, NCN5120 sends NACK + * instead of IACK. + * The documentation says that the Set Address Service can be issued any time and that the + * new physical address and the autoacknowledge function will only get active after the + * KNX bus becomes idle. Anyway, it does not provide information on when the configuration + * byte will be sent, thus we do not allow the set address operation while receiving data. + * + * Returns -1 when receiving data and address did not get changed. + * + * Autoacknowledge can only be deactivated by a Reset Service (call to ncn5120_reset()). + */ +int ncn5120_set_address(uint16_t address); + + +/***************************************************************************** */ +/* Set repetition service + * Specifies the maximum repetition count for transmitted frames when not acknowledged with IACK. + * Separate counters can be set for NACK and BUSY frames. + * Initial value of both counters is 3. + * If the acknowledge from remote Data Link Layer is BUSY during frame transmission, NCN5120 + * tries to repeat after at least 150 bit times KNX bus idle. + * The BUSY counter determines the maximum amount of times the frame is repeated. + * If the BUSY acknowledge is still received after the last try, an L_Data.con with a negative + * conformation is sent back to the host controller. + * For all other cases (NACK acknowledgment received, invalid/corrupted acknowledge received or + * time−out after 30 bit times) NCN5120 will repeat after 50 bit times of KNX bus idle. + * The NACK counter determines the maximum retries. + * L_Data.con with a negative confirmation is send back to the host controller when the maximum + * retries were reached. In worst case, the same request is transmitted (NACK + BUSY + 1) times + * before NCN5120 stops retransmission. + */ +void ncn5120_set_number_of_retries(uint8_t busy_retries, uint8_t nack_retries); + + +/***************************************************************************** */ +/* Get analog status + * The operation_mode returned will be the latest known operation mode. It will not be updated + * during this call. + */ +int ncn5120_get_analog_status(struct ncn5120_status* status); + + +/* Get commuinication status as returned by U_State.req service */ +int ncn5120_get_comm_status(struct ncn5120_comm_status* status); + +/* Get system status. + * The 'out_of_sleep' and 'out_of_thermal_shutdown' field have no meaning for this request + * and will be '0' regardless of the real state. + */ +int ncn5120_get_system_status(struct ncn5120_status* status); + + +/***************************************************************************** */ +/* Request NCN5120 reset + * Return 0 on success, -1 on error + */ +int ncn5120_reset(void); + +/* Enter sleep mode if it has been enabled by config in Analog Control register 0 (call to ncn5120_set_config() + * with sleep_en = 1). + * Warning: when entering sleep mode, only a Wake-Up Event on the FANIN/WAKE-pin can get the + * NCN5120 out of sleep. Make sure your hardware alows the generation of this event. + */ +void ncn5120_enter_sleep(void); + +/* Set (enter or quit) busy mode. + * Enter busy mode/state if 'state' is not 0. + * In Busy mode and when autoacknowledge is active the NCN5120 rejects the frames whose + * destination address corresponds to the stored physical address by sending the BUSY + * acknowledge. + * This service has no effect if autoacknowledge is not active. + * Refer to p30 of NCN5120 documentation for busy state an p31 for autoacknowledge and set + * address service + */ +void ncn5120_set_busy_state(uint8_t state); + +/* Set (enter or quit) stop mode. + * Enter stop mode/state if 'state' is not 0. + */ +void ncn5120_set_stop_state(uint8_t state); + + +/* Enter Bus monitor mode + * In this mode all data received from the KNX bus is sent to the host controller without + * performing any filtering on Data Link Layer. Acknowledge Frames are also transmitted + * transparently. + * This state can only be exited by the Reset Service (ncn5120_reset()). + */ +void ncn5120_enter_monitor_mode(void); + + + +/***************************************************************************** */ +/* Receive packet helper + * When an address got set using ncn5120_set_address() then the auto-ACK function is activated. + * When it is not the case, the host is responsible for sending ACK, NACK or Busy on the Bus when + * it receives a packet + */ +void ncn5120_packet_acknowledge(uint8_t nack, uint8_t busy, uint8_t addressed); + + +/***************************************************************************** */ +/* Send packet + * When using a packet oriented communication with packet size and address included + * in the packet, these must be included in the packet by the software before + * calling this function. + * The request_builder buffer is used to build parts of the request by sending the control + * and data bytes in the right order. + * There is no need to send them all at once so a small buffer is OK and saves memory. A + * three bytes buffer is OK for one data byte and the possible two index bytes sent at + * once. + * The reply length depends on the communication configuration. It is at least the data + * size plus two control bytes. + * + * Return values: + * This function returns a negativ value on errors: + * -1 when size is above KNX_MAX_FRAME_SIZE + * -2 when data has not been sent because the bus is busy receiving data. + * -3 when there has been an error while transmitting data to the NCN5120 (or checksum error) + * -4 if the NCN5120 indicates Rx errors (while we were sending, what would they mean ?) + * -5 if the data does not end with L_Data confirmation byte. + * The function returns 1 when the packet got sent and received a positive ACK, or 0 when it + * received a negative ACK. + */ +int ncn5120_send_packet(uint8_t control_byte, uint8_t* buffer, uint16_t size); + + +#endif /* EXTDRV_NCN5120_H */ + diff --git a/include/lib/protocols/knx.h b/include/lib/protocols/knx.h new file mode 100644 index 0000000..7e07510 --- /dev/null +++ b/include/lib/protocols/knx.h @@ -0,0 +1,2 @@ +#define KNX_MAX_FRAME_SIZE 263 + -- 2.43.0